diff --git a/sheets/CMakeLists.txt b/sheets/CMakeLists.txt index b6c6a2586dc..7b757786997 100644 --- a/sheets/CMakeLists.txt +++ b/sheets/CMakeLists.txt @@ -1,464 +1,465 @@ project(calligra-sheets) include_directories( ${CMAKE_SOURCE_DIR}/interfaces ${KOMAIN_INCLUDES} ${KOTEXT_INCLUDES} ${TEXTLAYOUT_INCLUDES} ${Boost_INCLUDE_DIR} ${EIGEN3_INCLUDE_DIR} ) if (SHOULD_BUILD_PART_SHEETS) # have their own translation domain add_subdirectory( shape ) add_subdirectory( plugins ) add_definitions(-DTRANSLATION_DOMAIN=\"sheets\") add_subdirectory( data ) add_subdirectory( tests ) add_subdirectory( dtd ) add_subdirectory( functions ) #add_definitions(-DCALLIGRA_SHEETS_MT) if(NOT Qt5Sql_FOUND) add_definitions(-DQT_NO_SQL) endif() ########### next target ############### set (chart_DIR_SRCS chart/ChartDatabaseSelectorFactory.cpp chart/ChartDatabaseSelector.cpp chart/ChartDialog.cpp ) ki18n_wrap_ui(chart_DIR_SRCS chart/ChartDatabaseSelector.ui ) set (commands_DIR_SRCS commands/AbstractRegionCommand.cpp commands/ApplyFilterCommand.cpp commands/AutoFillCommand.cpp commands/AutoFilterCommand.cpp commands/AutoFormatCommand.cpp commands/BorderColorCommand.cpp commands/CommentCommand.cpp commands/ConditionCommand.cpp commands/CopyCommand.cpp commands/CSVDataCommand.cpp commands/DataManipulators.cpp commands/DeleteCommand.cpp commands/IndentationCommand.cpp commands/LinkCommand.cpp commands/MergeCommand.cpp commands/NamedAreaCommand.cpp commands/PageBreakCommand.cpp commands/PasteCommand.cpp commands/PrecisionCommand.cpp commands/RowColumnManipulators.cpp commands/SheetCommands.cpp commands/SortManipulator.cpp commands/SpellCheckCommand.cpp commands/StyleCommand.cpp commands/ValidityCommand.cpp ) set (database_DIR_SRCS #database/Database.cpp #database/DatabaseManager.cpp database/DatabaseSource.cpp database/DatabaseSourceQuery.cpp database/DatabaseSourceSql.cpp database/DatabaseSourceTable.cpp #database/Filter.cpp database/FilterPopup.cpp ) set (dialogs_DIR_SRCS dialogs/AddNamedAreaDialog.cpp dialogs/AngleDialog.cpp dialogs/AutoFormatDialog.cpp dialogs/CharacterSelectDialog.cpp dialogs/CommentDialog.cpp dialogs/ConditionalDialog.cpp dialogs/ConsolidateDialog.cpp dialogs/CSVDialog.cpp dialogs/DatabaseDialog.cpp dialogs/DocumentSettingsDialog.cpp dialogs/FindDialog.cpp dialogs/FormulaDialog.cpp dialogs/GoalSeekDialog.cpp dialogs/GotoDialog.cpp dialogs/InsertDialog.cpp dialogs/LayoutDialog.cpp dialogs/LinkDialog.cpp dialogs/ListDialog.cpp dialogs/NamedAreaDialog.cpp dialogs/PasteInsertDialog.cpp dialogs/Resize2Dialog.cpp dialogs/SeriesDialog.cpp dialogs/ShowDialog.cpp dialogs/ShowColRowDialog.cpp dialogs/SortDialog.cpp dialogs/SpecialPasteDialog.cpp dialogs/StyleManagerDialog.cpp dialogs/SubtotalDialog.cpp dialogs/ValidityDialog.cpp dialogs/pivot.cpp dialogs/pivotfilters.cpp dialogs/pivotoptions.cpp dialogs/pivotmain.cpp ) ki18n_wrap_ui(dialogs_DIR_SRCS dialogs/ConsolidateWidget.ui dialogs/ConsolidateDetailsWidget.ui dialogs/FontWidget.ui dialogs/GoalSeekWidget.ui dialogs/PositionWidget.ui dialogs/ProtectionWidget.ui dialogs/SpecialPasteWidget.ui dialogs/SortWidget.ui dialogs/SortDetailsWidget.ui dialogs/SubtotalWidget.ui dialogs/SubtotalsDetailsWidget.ui dialogs/pivot.ui dialogs/pivotfilters.ui dialogs/pivotoptions.ui dialogs/pivotmain.ui ) set (functions_DIR_SRCS functions/helper.cpp ) if(QT_QTDBUS_FOUND) set (interfaces_DIR_SRCS interfaces/MapAdaptor.cpp interfaces/SheetAdaptor.cpp interfaces/ViewAdaptor.cpp ) endif() set (odf_DIR_SRCS odf/SheetsOdfDoc.cpp odf/SheetsOdfMap.cpp odf/SheetsOdfSheet.cpp odf/SheetsOdfCell.cpp + odf/SheetsOdfStyle.cpp ) set (part_DIR_SRCS part/CanvasBase.cpp part/Canvas.cpp part/CanvasItem.cpp part/CellTool.cpp part/CellToolFactory.cpp #part/Digest.cpp part/Doc.cpp part/Part.cpp part/Factory.cpp part/Find.cpp part/Headers.cpp part/HeaderWidgets.cpp part/HeaderItems.cpp part/PrintJob.cpp part/ToolRegistry.cpp part/TabBar.cpp part/View.cpp part/commands/DefinePrintRangeCommand.cpp part/commands/PageLayoutCommand.cpp part/dialogs/PageLayoutDialog.cpp part/dialogs/PreferenceDialog.cpp part/dialogs/SheetPropertiesDialog.cpp part/dialogs/SheetSelectPage.cpp ) ki18n_wrap_ui(part_DIR_SRCS part/dialogs/FileOptionsWidget.ui part/dialogs/InterfaceOptionsWidget.ui part/dialogs/PageLayoutSheetPage.ui part/dialogs/SheetPropertiesWidget.ui part/dialogs/SheetSelectWidget.ui ) set (ui_DIR_SRCS ui/AbstractSelectionStrategy.cpp ui/ActionOptionWidget.cpp ui/AutoFillStrategy.cpp ui/CellEditorBase.cpp ui/CellEditor.cpp ui/CellEditorDocker.cpp ui/CellToolBase.cpp ui/CellToolBase_p.cpp ui/CellView.cpp ui/DragAndDropStrategy.cpp ui/FormulaEditorHighlighter.cpp ui/FunctionCompletion.cpp ui/ExternalEditor.cpp ui/HyperlinkStrategy.cpp ui/LocationComboBox.cpp ui/MapViewModel.cpp ui/MergeStrategy.cpp ui/PasteStrategy.cpp ui/PixmapCachingSheetView.cpp ui/RegionSelector.cpp ui/RightToLeftPaintingStrategy.cpp ui/Selection.cpp ui/SelectionStrategy.cpp ui/SheetView.cpp ) set (calligrasheetscommon_LIB_SRCS MapModel.cpp PageManager.cpp RegionModel.cpp tests/inspector.cpp ${chart_DIR_SRCS} ${commands_DIR_SRCS} ${database_DIR_SRCS} ${dialogs_DIR_SRCS} ${functions_DIR_SRCS} ${part_DIR_SRCS} ${ui_DIR_SRCS} ) if(Qt5DBus_FOUND) set (calligrasheetscommon_LIB_SRCS ${calligrasheetscommon_LIB_SRCS} ${interfaces_DIR_SRCS} ) endif() set (calligrasheetsodf_LIB_SRCS SheetsDebug.cpp part/Digest.cpp ApplicationSettings.cpp Binding.cpp BindingManager.cpp BindingModel.cpp BindingStorage.cpp CalculationSettings.cpp Cell.cpp CellStorage.cpp Cluster.cpp Condition.cpp ConditionsStorage.cpp Currency.cpp Damages.cpp DependencyManager.cpp DocBase.cpp Format.cpp Formula.cpp GenValidationStyle.cpp HeaderFooter.cpp Localization.cpp Map.cpp NamedAreaManager.cpp Number.cpp PrintSettings.cpp ProtectableObject.cpp RecalcManager.cpp RectStorage.cpp Region.cpp RowColumnFormat.cpp RowFormatStorage.cpp RowRepeatStorage.cpp ShapeApplicationData.cpp Sheet.cpp SheetAccessModel.cpp SheetModel.cpp Style.cpp StyleManager.cpp StyleStorage.cpp Util.cpp Validity.cpp ValidityStorage.cpp Value.cpp ValueCalc.cpp ValueConverter.cpp ValueFormatter.cpp ValueParser.cpp database/Database.cpp database/DatabaseManager.cpp database/DatabaseStorage.cpp database/Filter.cpp ${odf_DIR_SRCS} # TODO: move the formula evaluation out of Formula.cpp so these files can move out of libcalligrasheetsodf Function.cpp FunctionDescription.cpp FunctionModule.cpp FunctionModuleRegistry.cpp FunctionRepository.cpp # TODO: move HeaderFooter from SheetPrint to PrintSettings, and replace SheetPrint with PrintSettings in Sheet to get rid of this dependency SheetPrint.cpp SheetPrint_p.cpp ) add_library(calligrasheetsodf SHARED ${calligrasheetsodf_LIB_SRCS}) generate_export_header(calligrasheetsodf EXPORT_FILE_NAME sheets_odf_generated_export.h BASE_NAME CALLIGRA_SHEETS_ODF ) target_link_libraries(calligrasheetsodf komain KF5::KDELibs4Support) target_link_libraries(calligrasheetsodf LINK_INTERFACE_LIBRARIES komain KF5::KDELibs4Support) set_target_properties(calligrasheetsodf PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS calligrasheetsodf ${INSTALL_TARGETS_DEFAULT_ARGS}) add_library(calligrasheetscommon SHARED ${calligrasheetscommon_LIB_SRCS}) generate_export_header(calligrasheetscommon EXPORT_FILE_NAME sheets_common_generated_export.h BASE_NAME CALLIGRA_SHEETS_COMMON ) target_link_libraries(calligrasheetscommon komain calligrasheetsodf KF5::SonnetCore KF5::SonnetUi KF5::NotifyConfig ) if(Qt5Sql_FOUND) target_link_libraries(calligrasheetscommon Qt5::Sql) endif() target_link_libraries(calligrasheetscommon LINK_INTERFACE_LIBRARIES komain calligrasheetsodf ) set_target_properties(calligrasheetscommon PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS calligrasheetscommon ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### next target ############### set (calligrasheetspart_PART_SRCS part/Factory_init.cpp ) add_library(calligrasheetspart MODULE ${calligrasheetspart_PART_SRCS}) kcoreaddons_desktop_to_json(calligrasheetspart sheetspart.desktop) target_link_libraries(calligrasheetspart calligrasheetscommon ) install(TARGETS calligrasheetspart DESTINATION ${CALLIGRA_PLUGIN_INSTALL_DIR}) ########### install files ############### install( FILES sheets.rc sheets_readonly.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/sheets) install( FILES ui/CellToolOptionWidgets.xml DESTINATION ${DATA_INSTALL_DIR}/sheets) install( FILES sheets.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) install( FILES sheets_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) install( FILES sheets_viewplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) install( FILES sheetsrc DESTINATION ${CONFIG_INSTALL_DIR}) install( FILES sheets_odf_export.h ${CMAKE_CURRENT_BINARY_DIR}/sheets_odf_generated_export.h sheets_common_export.h ${CMAKE_CURRENT_BINARY_DIR}/sheets_common_generated_export.h calligra_sheets_limits.h Cell.h CellStorage.h Condition.h Currency.h DocBase.h Format.h Global.h Map.h Number.h OdfLoadingContext.h PointStorage.h PrintSettings.h ProtectableObject.h RectStorage.h Region.h RowColumnFormat.h RowFormatStorage.h RTree.h Sheet.h Style.h Value.h ValueCalc.h ValueConverter.h ValueStorage.h DESTINATION ${INCLUDE_INSTALL_DIR}/sheets COMPONENT Devel) install( FILES part/CanvasBase.h part/CanvasItem.h part/CellTool.h part/Doc.h part/Part.h part/Find.h part/HeaderItems.h part/Headers.h part/ToolRegistry.h part/View.h DESTINATION ${INCLUDE_INSTALL_DIR}/sheets/part COMPONENT Devel) install( FILES ui/CellToolBase.h ui/CellEditorBase.h ui/Selection.h ui/SheetView.h DESTINATION ${INCLUDE_INSTALL_DIR}/sheets/ui COMPONENT Devel) install( FILES database/Database.h database/Filter.h DESTINATION ${INCLUDE_INSTALL_DIR}/sheets/database COMPONENT Devel) install( FILES commands/AbstractRegionCommand.h commands/DataManipulators.h commands/SortManipulator.h dialogs/pivot.h dialogs/pivotfilters.h dialogs/pivotoptions.h dialogs/pivotmain.h DESTINATION ${INCLUDE_INSTALL_DIR}/sheets/commands COMPONENT Devel) endif () ########### APP ############### if (SHOULD_BUILD_APP_SHEETS) set (calligrasheets_KDEINIT_SRCS part/Main.cpp ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/data/pics/*-apps-calligrasheets.png") ecm_add_app_icon(calligrasheets_KDEINIT_SRCS ICONS ${ICONS_SRCS}) kf5_add_kdeinit_executable( calligrasheets ${calligrasheets_KDEINIT_SRCS}) if (APPLE) set_target_properties(calligrasheets PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template) set_target_properties(calligrasheets PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.calligra.sheets") set_target_properties(calligrasheets PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Calligra Sheets 2") install( FILES ${CMAKE_CURRENT_BINARY_DIR}/calligrasheets_KDEINIT_SRCS.icns DESTINATION ${BUNDLE_INSTALL_DIR}/calligrasheets.app/Contents/Resources) endif () target_link_libraries(kdeinit_calligrasheets komain KF5::KDELibs4Support) install(TARGETS kdeinit_calligrasheets ${INSTALL_TARGETS_DEFAULT_ARGS}) target_link_libraries( calligrasheets kdeinit_calligrasheets ) install(TARGETS calligrasheets ${INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS sheets.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install( FILES sheets.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/appdata/) endif () diff --git a/sheets/Cell.cpp b/sheets/Cell.cpp index 3ded2734229..92f8377cb3c 100644 --- a/sheets/Cell.cpp +++ b/sheets/Cell.cpp @@ -1,2463 +1,1576 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2006-2007 Stefan Nikolaus Copyright 2005 Raphael Langerhorst Copyright 2004-2005 Tomas Mecir Copyright 2004-2006 Inge Wallin Copyright 1999-2002,2004,2005 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Reinhart Geiser Copyright 2003-2005 Meni Livne Copyright 2003 Peter Simonsson Copyright 1999-2002 David Faure Copyright 2000-2002 Werner Trobin Copyright 1999,2002 Harri Porten Copyright 2002 John Dailey Copyright 1998-2000 Torben Weis Copyright 2000 Bernd Wuebben Copyright 2000 Simon Hausmann Copyright 1999 Michael Reiher Copyright 1999 Boris Wedl Copyright 1998-1999 Reginald Stadlbauer 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. */ // Local #include "Cell.h" #include #include #include #include #include "SheetsDebug.h" #include "CalculationSettings.h" #include "CellStorage.h" #include "Condition.h" #include "Formula.h" -#include "GenValidationStyle.h" #include "Global.h" #include "Localization.h" #include "LoadingInfo.h" #include "Map.h" #include "NamedAreaManager.h" -#include "OdfLoadingContext.h" -#include "OdfSavingContext.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" -#include "ShapeApplicationData.h" #include "Sheet.h" #include "Style.h" #include "StyleManager.h" #include "Util.h" #include "Value.h" #include "Validity.h" #include "ValueConverter.h" #include "ValueFormatter.h" #include "ValueParser.h" #include "StyleStorage.h" -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include #include #include #include using namespace Calligra::Sheets; class Q_DECL_HIDDEN Cell::Private : public QSharedData { public: Private() : sheet(0), column(0), row(0) {} Sheet* sheet; uint column : 17; // KS_colMax uint row : 21; // KS_rowMax }; Cell::Cell() : d(0) { } Cell::Cell(const Sheet* sheet, int col, int row) : d(new Private) { Q_ASSERT(sheet != 0); Q_ASSERT_X(1 <= col && col <= KS_colMax, __FUNCTION__, QString("%1 out of bounds").arg(col).toLocal8Bit()); Q_ASSERT_X(1 <= row && row <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds").arg(row).toLocal8Bit()); d->sheet = const_cast(sheet); d->column = col; d->row = row; } Cell::Cell(const Sheet* sheet, const QPoint& pos) : d(new Private) { Q_ASSERT(sheet != 0); Q_ASSERT_X(1 <= pos.x() && pos.x() <= KS_colMax, __FUNCTION__, QString("%1 out of bounds").arg(pos.x()).toLocal8Bit()); Q_ASSERT_X(1 <= pos.y() && pos.y() <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds").arg(pos.y()).toLocal8Bit()); d->sheet = const_cast(sheet); d->column = pos.x(); d->row = pos.y(); } Cell::Cell(const Cell& other) : d(other.d) { } Cell::~Cell() { } // Return the sheet that this cell belongs to. Sheet* Cell::sheet() const { Q_ASSERT(!isNull()); return d->sheet; } KLocale* Cell::locale() const { return sheet()->map()->calculationSettings()->locale(); } // Return true if this is the default cell. bool Cell::isDefault() const { // check each stored attribute if (!value().isEmpty()) return false; if (formula() != Formula::empty()) return false; if (!link().isEmpty()) return false; if (doesMergeCells() == true) return false; if (!style().isDefault()) return false; if (!comment().isEmpty()) return false; if (!conditions().isEmpty()) return false; if (!validity().isEmpty()) return false; return true; } // Return true if this is the default cell (apart from maybe a custom style). bool Cell::hasDefaultContent() const { // check each stored attribute if (value() != Value()) return false; if (formula() != Formula::empty()) return false; if (!link().isEmpty()) return false; if (doesMergeCells() == true) return false; if (!comment().isEmpty()) return false; if (!conditions().isEmpty()) return false; if (!validity().isEmpty()) return false; return true; } bool Cell::isEmpty() const { // empty = no value or formula if (value() != Value()) return false; if (formula() != Formula()) return false; return true; } bool Cell::isNull() const { return (!d); } // Return true if this cell is a formula. // bool Cell::isFormula() const { return !formula().expression().isEmpty(); } // Return the column number of this cell. // int Cell::column() const { // Make sure this isn't called for the null cell. This assert // can save you (could have saved me!) the hassle of some very // obscure bugs. Q_ASSERT(!isNull()); Q_ASSERT(1 <= d->column); //&& d->column <= KS_colMax ); return d->column; } // Return the row number of this cell. int Cell::row() const { // Make sure this isn't called for the null cell. This assert // can save you (could have saved me!) the hassle of some very // obscure bugs. Q_ASSERT(!isNull()); Q_ASSERT(1 <= d->row); //&& d->row <= KS_rowMax ); return d->row; } // Return the name of this cell, i.e. the string that the user would // use to reference it. Example: A1, BZ16 // QString Cell::name() const { return name(column(), row()); } // Return the name of any cell given by (col, row). // // static QString Cell::name(int col, int row) { return columnName(col) + QString::number(row); } // Return the name of this cell, including the sheet name. // Example: sheet1!A5 // QString Cell::fullName() const { return fullName(sheet(), column(), row()); } // Return the full name of any cell given a sheet and (col, row). // // static QString Cell::fullName(const Sheet* s, int col, int row) { return s->sheetName() + '!' + name(col, row); } // Return the symbolic name of the column of this cell. Examples: A, BB. // QString Cell::columnName() const { return columnName(column()); } // Return the symbolic name of any column. // // static QString Cell::columnName(uint column) { if (column < 1) //|| column > KS_colMax) return QString("@@@"); QString str; unsigned digits = 1; unsigned offset = 0; --column; for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits) offset += limit; for (unsigned col = column - offset; digits; --digits, col /= 26) str.prepend(QChar('A' + (col % 26))); return str; } QString Cell::comment() const { return sheet()->cellStorage()->comment(d->column, d->row); } void Cell::setComment(const QString& comment) { sheet()->cellStorage()->setComment(Region(cellPosition()), comment); } Conditions Cell::conditions() const { return sheet()->cellStorage()->conditions(d->column, d->row); } void Cell::setConditions(const Conditions& conditions) { sheet()->cellStorage()->setConditions(Region(cellPosition()), conditions); } Database Cell::database() const { return sheet()->cellStorage()->database(d->column, d->row); } Formula Cell::formula() const { return sheet()->cellStorage()->formula(d->column, d->row); } void Cell::setFormula(const Formula& formula) { sheet()->cellStorage()->setFormula(column(), row(), formula); } Style Cell::style() const { return sheet()->cellStorage()->style(d->column, d->row); } Style Cell::effectiveStyle() const { Style style = sheet()->cellStorage()->style(d->column, d->row); // use conditional formatting attributes const Style conditionalStyle = conditions().testConditions(*this); if (!conditionalStyle.isEmpty()) { style.merge(conditionalStyle); } return style; } void Cell::setStyle(const Style& style) { sheet()->cellStorage()->setStyle(Region(cellPosition()), style); sheet()->cellStorage()->styleStorage()->contains(cellPosition()); } Validity Cell::validity() const { return sheet()->cellStorage()->validity(d->column, d->row); } void Cell::setValidity(Validity validity) { sheet()->cellStorage()->setValidity(Region(cellPosition()), validity); } // Return the user input of this cell. This could, for instance, be a // formula. // QString Cell::userInput() const { const Formula formula = this->formula(); if (!formula.expression().isEmpty()) return formula.expression(); return sheet()->cellStorage()->userInput(d->column, d->row); } void Cell::setUserInput(const QString& string) { QString old = userInput(); if (!string.isEmpty() && string[0] == '=') { // set the formula Formula formula(sheet(), *this); formula.setExpression(string); setFormula(formula); // remove an existing user input (the non-formula one) sheet()->cellStorage()->setUserInput(d->column, d->row, QString()); } else { // remove an existing formula setFormula(Formula::empty()); // set the value sheet()->cellStorage()->setUserInput(d->column, d->row, string); } if (old != string) { // remove any existing richtext setRichText(QSharedPointer()); } } void Cell::setRawUserInput(const QString& string) { if (!string.isEmpty() && string[0] == '=') { // set the formula Formula formula(sheet(), *this); formula.setExpression(string); setFormula(formula); } else { // set the value sheet()->cellStorage()->setUserInput(d->column, d->row, string); } } // Return the out text, i.e. the text that is visible in the cells // square when shown. This could, for instance, be the calculated // result of a formula. // QString Cell::displayText(const Style& s, Value *v, bool *showFormula) const { if (isNull()) return QString(); QString string; const Style style = s.isEmpty() ? effectiveStyle() : s; // Display a formula if warranted. If not, display the value instead; // this is the most common case. if ( isFormula() && !(sheet()->isProtected() && style.hideFormula()) && ( (showFormula && *showFormula) || (!showFormula && sheet()->getShowFormula()) ) ) { string = userInput(); if (showFormula) *showFormula = true; } else if (!isEmpty()) { Value theValue = sheet()->map()->formatter()->formatText(value(), style.formatType(), style.precision(), style.floatFormat(), style.prefix(), style.postfix(), style.currency().symbol(), style.customFormat(), style.thousandsSep()); if (v) *v = theValue; string = theValue.asString(); if (showFormula) *showFormula = false; } return string; } // Return the value of this cell. // const Value Cell::value() const { return sheet()->cellStorage()->value(d->column, d->row); } // Set the value of this cell. // void Cell::setValue(const Value& value) { sheet()->cellStorage()->setValue(d->column, d->row, value); } QSharedPointer Cell::richText() const { return sheet()->cellStorage()->richText(d->column, d->row); } void Cell::setRichText(QSharedPointer text) { sheet()->cellStorage()->setRichText(d->column, d->row, text); } // FIXME: Continue commenting and cleaning here (ingwa) void Cell::copyFormat(const Cell& cell) { Q_ASSERT(!isNull()); // trouble ahead... Q_ASSERT(!cell.isNull()); Value value = this->value(); value.setFormat(cell.value().format()); sheet()->cellStorage()->setValue(d->column, d->row, value); if (!style().isDefault() || !cell.style().isDefault()) setStyle(cell.style()); if (!conditions().isEmpty() || !cell.conditions().isEmpty()) setConditions(cell.conditions()); } void Cell::copyAll(const Cell& cell) { Q_ASSERT(!isNull()); // trouble ahead... Q_ASSERT(!cell.isNull()); copyFormat(cell); copyContent(cell); if (!comment().isEmpty() || !cell.comment().isEmpty()) setComment(cell.comment()); if (!validity().isEmpty() || !cell.validity().isEmpty()) setValidity(cell.validity()); } void Cell::copyContent(const Cell& cell) { Q_ASSERT(!isNull()); // trouble ahead... Q_ASSERT(!cell.isNull()); if (cell.isFormula()) { // change all the references, e.g. from A1 to A3 if copying // from e.g. B2 to B4 Formula formula(sheet(), *this); formula.setExpression(decodeFormula(cell.encodeFormula())); setFormula(formula); } else { // copy the user input sheet()->cellStorage()->setUserInput(d->column, d->row, cell.userInput()); } // copy the value in both cases sheet()->cellStorage()->setValue(d->column, d->row, cell.value()); } bool Cell::needsPrinting() const { if (!userInput().trimmed().isEmpty()) return true; if (!comment().trimmed().isEmpty()) return true; const Style style = effectiveStyle(); // Cell borders? if (style.hasAttribute(Style::TopPen) || style.hasAttribute(Style::LeftPen) || style.hasAttribute(Style::RightPen) || style.hasAttribute(Style::BottomPen) || style.hasAttribute(Style::FallDiagonalPen) || style.hasAttribute(Style::GoUpDiagonalPen)) return true; // Background color or brush? if (style.hasAttribute(Style::BackgroundBrush)) { QBrush brush = style.backgroundBrush(); // Only brushes that are visible (ie. they have a brush style // and are not white) need to be drawn if ((brush.style() != Qt::NoBrush) && (brush.color() != Qt::white || !brush.texture().isNull())) return true; } if (style.hasAttribute(Style::BackgroundColor)) { debugSheetsRender << "needsPrinting: Has background color"; QColor backgroundColor = style.backgroundColor(); // We don't need to print anything, if the background is white opaque or fully transparent. if (!(backgroundColor == Qt::white || backgroundColor.alpha() == 0)) return true; } return false; } QString Cell::encodeFormula(bool fixedReferences) const { if (!isFormula()) return QString(); QString result('='); const Tokens tokens = formula().tokens(); for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; switch (token.type()) { case Token::Cell: case Token::Range: { if (sheet()->map()->namedAreaManager()->contains(token.text())) { result.append(token.text()); // simply keep the area name break; } const Region region(token.text(), sheet()->map()); // Actually, a contiguous region, but the fixation is needed Region::ConstIterator end = region.constEnd(); for (Region::ConstIterator it = region.constBegin(); it != end; ++it) { if (!(*it)->isValid()) continue; if ((*it)->type() == Region::Element::Point) { if ((*it)->sheet()) result.append((*it)->sheet()->sheetName() + '!'); const QPoint pos = (*it)->rect().topLeft(); if ((*it)->isColumnFixed()) result.append(QString("$%1").arg(pos.x())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1").arg(pos.x())); else result.append(QString("#%1").arg(pos.x() - (int)d->column)); if ((*it)->isRowFixed()) result.append(QString("$%1#").arg(pos.y())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1#").arg(pos.y())); else result.append(QString("#%1#").arg(pos.y() - (int)d->row)); } else { // ((*it)->type() == Region::Range) if ((*it)->sheet()) result.append((*it)->sheet()->sheetName() + '!'); QPoint pos = (*it)->rect().topLeft(); if ((*it)->isLeftFixed()) result.append(QString("$%1").arg(pos.x())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1").arg(pos.x())); else result.append(QString("#%1").arg(pos.x() - (int)d->column)); if ((*it)->isTopFixed()) result.append(QString("$%1#").arg(pos.y())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1#").arg(pos.y())); else result.append(QString("#%1#").arg(pos.y() - (int)d->row)); result.append(':'); pos = (*it)->rect().bottomRight(); if ((*it)->isRightFixed()) result.append(QString("$%1").arg(pos.x())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1").arg(pos.x())); else result.append(QString("#%1").arg(pos.x() - (int)d->column)); if ((*it)->isBottomFixed()) result.append(QString("$%1#").arg(pos.y())); else if (fixedReferences) result.append(QChar(0xA7) + QString("%1#").arg(pos.y())); else result.append(QString("#%1#").arg(pos.y() - (int)d->row)); } } break; } default: { result.append(token.text()); break; } } } //debugSheets << result; return result; } QString Cell::decodeFormula(const QString &_text) const { QString erg; unsigned int pos = 0; const unsigned int length = _text.length(); if (_text.isEmpty()) return QString(); while (pos < length) { if (_text[pos] == '"') { erg += _text[pos++]; while (pos < length && _text[pos] != '"') { erg += _text[pos++]; // Allow escaped double quotes (\") if (pos < length && _text[pos] == '\\' && _text[pos+1] == '"') { erg += _text[pos++]; erg += _text[pos++]; } } if (pos < length) erg += _text[pos++]; } else if (_text[pos] == '#' || _text[pos] == '$' || _text[pos] == QChar(0xA7)) { bool abs1 = false; bool abs2 = false; bool era1 = false; // if 1st is relative but encoded absolutely bool era2 = false; QChar _t = _text[pos++]; if (_t == '$') abs1 = true; else if (_t == QChar(0xA7)) era1 = true; int col = 0; unsigned int oldPos = pos; while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos; if (pos != oldPos) col = _text.mid(oldPos, pos - oldPos).toInt(); if (!abs1 && !era1) col += d->column; // Skip '#' or '$' _t = _text[pos++]; if (_t == '$') abs2 = true; else if (_t == QChar(0xA7)) era2 = true; int row = 0; oldPos = pos; while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos; if (pos != oldPos) row = _text.mid(oldPos, pos - oldPos).toInt(); if (!abs2 && !era2) row += d->row; // Skip '#' or '$' ++pos; if (row < 1 || col < 1 || row > KS_rowMax || col > KS_colMax) { debugSheetsODF << "Cell::decodeFormula: row or column out of range (col:" << col << " | row:" << row << ')'; erg += Value::errorREF().errorMessage(); } else { if (abs1) erg += '$'; erg += Cell::columnName(col); //Get column text if (abs2) erg += '$'; erg += QString::number(row); } } else erg += _text[pos++]; } return erg; } // ---------------------------------------------------------------- // Formula handling bool Cell::makeFormula() { // debugSheetsFormula ; // sanity check if (!isFormula()) return false; // parse the formula and check for errors if (!formula().isValid()) { sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed.", fullName())); setValue(Value::errorPARSE()); return false; } return true; } int Cell::effectiveAlignX() const { const Style style = effectiveStyle(); int align = style.halign(); if (align == Style::HAlignUndefined) { //numbers should be right-aligned by default, as well as BiDi text if ((style.formatType() == Format::Text) || value().isString()) align = (displayText().isRightToLeft()) ? Style::Right : Style::Left; else { Value val = value(); while (val.isArray()) val = val.element(0, 0); if (val.isBoolean() || val.isNumber()) align = Style::Right; else align = Style::Left; } } return align; } double Cell::width() const { const int rightCol = d->column + mergedXCells(); double width = 0.0; for (int col = d->column; col <= rightCol; ++col) width += sheet()->columnFormat(col)->width(); return width; } double Cell::height() const { const int bottomRow = d->row + mergedYCells(); return sheet()->rowFormats()->totalRowHeight(d->row, bottomRow); } // parses the text void Cell::parseUserInput(const QString& text) { // debugSheets ; // empty string? if (text.isEmpty()) { setValue(Value::empty()); setUserInput(text); setFormula(Formula::empty()); return; } // a formula? if (text[0] == '=') { Formula formula(sheet(), *this); formula.setExpression(text); setFormula(formula); // parse the formula and check for errors if (!formula.isValid()) { sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed.", fullName())); setValue(Value::errorPARSE()); return; } return; } // keep the old formula and value for the case, that validation fails const Formula oldFormula = formula(); const QString oldUserInput = userInput(); const Value oldValue = value(); // here, the new value is not a formula anymore; clear an existing one setFormula(Formula()); Value value; if (style().formatType() == Format::Text) value = Value(QString(text)); else { // Parses the text and return the appropriate value. value = sheet()->map()->parser()->parse(text); #if 0 // Parsing as time acts like an autoformat: we even change the input text // [h]:mm:ss -> might get set by ValueParser if (isTime() && (formatType() != Format::Time7)) setUserInput(locale()->formatTime(value().asDateTime(sheet()->map()->calculationSettings()).time(), true)); #endif // convert first letter to uppercase ? if (sheet()->getFirstLetterUpper() && value.isString() && !text.isEmpty()) { QString str = value.asString(); value = Value(str[0].toUpper() + str.right(str.length() - 1)); } } // set the new value setUserInput(text); setValue(value); // validation if (!sheet()->isLoading()) { Validity validity = this->validity(); if (!validity.testValidity(this)) { debugSheetsODF << "Validation failed"; //reapply old value if action == stop setFormula(oldFormula); setUserInput(oldUserInput); setValue(oldValue); } } } QString Cell::link() const { return sheet()->cellStorage()->link(d->column, d->row); } void Cell::setLink(const QString& link) { sheet()->cellStorage()->setLink(d->column, d->row, link); if (!link.isEmpty() && userInput().isEmpty()) parseUserInput(link); } bool Cell::isDate() const { const Format::Type t = style().formatType(); return (Format::isDate(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Date))); } bool Cell::isTime() const { const Format::Type t = style().formatType(); return (Format::isTime(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Time))); } bool Cell::isText() const { const Format::Type t = style().formatType(); return t == Format::Text; } // Return true if this cell is part of a merged cell, but not the // master cell. bool Cell::isPartOfMerged() const { return sheet()->cellStorage()->isPartOfMerged(d->column, d->row); } Cell Cell::masterCell() const { return sheet()->cellStorage()->masterCell(d->column, d->row); } // Merge a number of cells, i.e. make this cell obscure a number of // other cells. If _x and _y == 0, then the merging is removed. void Cell::mergeCells(int _col, int _row, int _x, int _y) { sheet()->cellStorage()->mergeCells(_col, _row, _x, _y); } bool Cell::doesMergeCells() const { return sheet()->cellStorage()->doesMergeCells(d->column, d->row); } int Cell::mergedXCells() const { return sheet()->cellStorage()->mergedXCells(d->column, d->row); } int Cell::mergedYCells() const { return sheet()->cellStorage()->mergedYCells(d->column, d->row); } bool Cell::isLocked() const { return sheet()->cellStorage()->isLocked(d->column, d->row); } QRect Cell::lockedCells() const { return sheet()->cellStorage()->lockedCells(d->column, d->row); } // ================================================================ // Saving and loading QDomElement Cell::save(QDomDocument& doc, int xOffset, int yOffset, bool era) { // Save the position of this cell QDomElement cell = doc.createElement("cell"); cell.setAttribute("row", row() - yOffset); cell.setAttribute("column", column() - xOffset); // // Save the formatting information // QDomElement formatElement(doc.createElement("format")); style().saveXML(doc, formatElement, sheet()->map()->styleManager()); if (formatElement.hasChildNodes() || formatElement.attributes().length()) // don't save empty tags cell.appendChild(formatElement); if (doesMergeCells()) { if (mergedXCells()) formatElement.setAttribute("colspan", mergedXCells()); if (mergedYCells()) formatElement.setAttribute("rowspan", mergedYCells()); } Conditions conditions = this->conditions(); if (!conditions.isEmpty()) { QDomElement conditionElement = conditions.saveConditions(doc, sheet()->map()->converter()); if (!conditionElement.isNull()) cell.appendChild(conditionElement); } Validity validity = this->validity(); if (!validity.isEmpty()) { QDomElement validityElement = validity.saveXML(doc, sheet()->map()->converter()); if (!validityElement.isNull()) cell.appendChild(validityElement); } const QString comment = this->comment(); if (!comment.isEmpty()) { QDomElement commentElement = doc.createElement("comment"); commentElement.appendChild(doc.createCDATASection(comment)); cell.appendChild(commentElement); } // // Save the text // if (!userInput().isEmpty()) { // Formulas need to be encoded to ensure that they // are position independent. if (isFormula()) { QDomElement txt = doc.createElement("text"); // if we are cutting to the clipboard, relative references need to be encoded absolutely txt.appendChild(doc.createTextNode(encodeFormula(era))); cell.appendChild(txt); /* we still want to save the results of the formula */ QDomElement formulaResult = doc.createElement("result"); saveCellResult(doc, formulaResult, displayText()); cell.appendChild(formulaResult); } else if (!link().isEmpty()) { // KSpread pre 1.4 saves link as rich text, marked with first char ' // Have to be saved in some CDATA section because of too many special charatcers. QDomElement txt = doc.createElement("text"); QString qml = "!" + userInput() + ""; txt.appendChild(doc.createCDATASection(qml)); cell.appendChild(txt); } else { // Save the cell contents (in a locale-independent way) QDomElement txt = doc.createElement("text"); saveCellResult(doc, txt, userInput()); cell.appendChild(txt); } } if (cell.hasChildNodes() || cell.attributes().length() > 2) // don't save empty tags // (the >2 is due to "row" and "column" attributes) return cell; else return QDomElement(); } bool Cell::saveCellResult(QDomDocument& doc, QDomElement& result, QString str) { QString dataType = "Other"; // fallback if (value().isNumber()) { if (isDate()) { // serial number of date QDate dd = value().asDateTime(sheet()->map()->calculationSettings()).date(); dataType = "Date"; str = "%1/%2/%3"; str = str.arg(dd.year()).arg(dd.month()).arg(dd.day()); } else if (isTime()) { // serial number of time dataType = "Time"; str = value().asDateTime(sheet()->map()->calculationSettings()).time().toString(); } else { // real number dataType = "Num"; if (value().isInteger()) str = QString::number(value().asInteger()); else str = QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG); } } if (value().isBoolean()) { dataType = "Bool"; str = value().asBoolean() ? "true" : "false"; } if (value().isString()) { dataType = "Str"; str = value().asString(); } result.setAttribute("dataType", dataType); const QString displayText = this->displayText(); if (!displayText.isEmpty()) result.setAttribute("outStr", displayText); result.appendChild(doc.createTextNode(str)); return true; /* really isn't much of a way for this function to fail */ } -void Cell::saveOdfAnnotation(KoXmlWriter &xmlwriter) -{ - const QString comment = this->comment(); - if (!comment.isEmpty()) { - // - xmlwriter.startElement("office:annotation"); - const QStringList text = comment.split('\n', QString::SkipEmptyParts); - for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) { - xmlwriter.startElement("text:p"); - xmlwriter.addTextNode(*it); - xmlwriter.endElement(); - } - xmlwriter.endElement(); - } -} - -QString Cell::saveOdfCellStyle(KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles) -{ - const Conditions conditions = this->conditions(); - if (!conditions.isEmpty()) { - // this has to be an automatic style - currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell"); - conditions.saveOdfConditions(currentCellStyle, sheet()->map()->converter()); - } - return style().saveOdf(currentCellStyle, mainStyles, d->sheet->map()->styleManager()); -} - - -bool Cell::saveOdf(int row, int column, int &repeated, - OdfSavingContext& tableContext) -{ - KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter(); - KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles(); - - // see: OpenDocument, 8.1.3 Table Cell - if (!isPartOfMerged()) - xmlwriter.startElement("table:table-cell"); - else - xmlwriter.startElement("table:covered-table-cell"); -#if 0 - //add font style - QFont font; - Value const value(cell.value()); - if (!cell.isDefault()) { - font = cell.format()->textFont(i, row); - m_styles.addFont(font); - - if (cell.format()->hasProperty(Style::SComment)) - hasComment = true; - } -#endif - // NOTE save the value before the style as long as the Formatter does not work correctly - if (link().isEmpty()) - saveOdfValue(xmlwriter); - - const Style cellStyle = style(); - - // Either there's no column and row default and the style's not the default style, - // or the style is different to one of them. The row default takes precedence. - if ((!tableContext.rowDefaultStyles.contains(row) && - !tableContext.columnDefaultStyles.contains(column) && - !(cellStyle.isDefault() && conditions().isEmpty())) || - (tableContext.rowDefaultStyles.contains(row) && tableContext.rowDefaultStyles[row] != cellStyle) || - (tableContext.columnDefaultStyles.contains(column) && tableContext.columnDefaultStyles[column] != cellStyle)) { - KoGenStyle currentCellStyle; // the type determined in saveOdfCellStyle - QString styleName = saveOdfCellStyle(currentCellStyle, mainStyles); - // skip 'table:style-name' attribute for the default style - if (!currentCellStyle.isDefaultStyle()) { - if (!styleName.isEmpty()) - xmlwriter.addAttribute("table:style-name", styleName); - } - } - - // group empty cells with the same style - const QString comment = this->comment(); - if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() && - !tableContext.cellHasAnchoredShapes(sheet(), row, column)) { - bool refCellIsDefault = isDefault(); - int j = column + 1; - Cell nextCell = sheet()->cellStorage()->nextInRow(column, row); - while (!nextCell.isNull()) { - // if - // the next cell is not the adjacent one - // or - // the next cell is not empty - if (nextCell.column() != j || (!nextCell.isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, column))) { - if (refCellIsDefault) { - // if the origin cell was a default cell, - // we count the default cells - repeated = nextCell.column() - j + 1; - - // check if any of the empty/default cells we skipped contained anchored shapes - int shapeColumn = tableContext.nextAnchoredShape(sheet(), row, column); - if (shapeColumn) { - repeated = qMin(repeated, shapeColumn - column); - } - } - // otherwise we just stop here to process the adjacent - // cell in the next iteration of the outer loop - // (in Sheet::saveOdfCells) - break; - } - - if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() || - !nextCell.comment().isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, nextCell.column()) || - !(nextCell.style() == cellStyle && nextCell.conditions() == conditions())) { - break; - } - ++repeated; - // get the next cell and set the index to the adjacent cell - nextCell = sheet()->cellStorage()->nextInRow(j++, row); - } - //debugSheetsODF << "Cell::saveOdf: empty cell in column" << column - //<< "repeated" << repeated << "time(s)" << endl; - - if (repeated > 1) - xmlwriter.addAttribute("table:number-columns-repeated", QString::number(repeated)); - } - - Validity validity = Cell(sheet(), column, row).validity(); - if (!validity.isEmpty()) { - GenValidationStyle styleVal(&validity, sheet()->map()->converter()); - xmlwriter.addAttribute("table:validation-name", tableContext.valStyle.insert(styleVal)); - } - if (isFormula()) { - //debugSheetsODF <<"Formula found"; - QString formula = Odf::encodeFormula(userInput(), locale()); - xmlwriter.addAttribute("table:formula", formula); - } - - if (doesMergeCells()) { - int colSpan = mergedXCells() + 1; - int rowSpan = mergedYCells() + 1; - - if (colSpan > 1) - xmlwriter.addAttribute("table:number-columns-spanned", QString::number(colSpan)); - - if (rowSpan > 1) - xmlwriter.addAttribute("table:number-rows-spanned", QString::number(rowSpan)); - } - - saveOdfAnnotation(xmlwriter); - - if (!isFormula() && !link().isEmpty()) { - //debugSheetsODF<<"Link found"; - xmlwriter.startElement("text:p"); - xmlwriter.startElement("text:a"); - const QString url = link(); - //Reference cell is started by '#' - if (Util::localReferenceAnchor(url)) - xmlwriter.addAttribute("xlink:href", ('#' + url)); - else - xmlwriter.addAttribute("xlink:href", url); - xmlwriter.addAttribute("xlink:type", "simple"); - xmlwriter.addTextNode(userInput()); - xmlwriter.endElement(); - xmlwriter.endElement(); - } - - if (!isEmpty() && link().isEmpty()) { - QSharedPointer doc = richText(); - if (doc) { - QTextCharFormat format = style().asCharFormat(); - ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); - - KoTextWriter writer(tableContext.shapeContext); - - writer.write(doc.data(), 0); - } else { - xmlwriter.startElement("text:p"); - xmlwriter.addTextNode(displayText().toUtf8()); - xmlwriter.endElement(); - } - } - - // flake - // Save shapes that are anchored to this cell. - // see: OpenDocument, 2.3.1 Text Documents - // see: OpenDocument, 9.2 Drawing Shapes - if (tableContext.cellHasAnchoredShapes(sheet(), row, column)) { - const QList shapes = tableContext.cellAnchoredShapes(sheet(), row, column); - for (int i = 0; i < shapes.count(); ++i) { - KoShape* const shape = shapes[i]; - const QPointF bottomRight = shape->boundingRect().bottomRight(); - qreal endX = 0.0; - qreal endY = 0.0; - const int scol = sheet()->leftColumn(bottomRight.x(), endX); - const int srow = sheet()->topRow(bottomRight.y(), endY); - qreal offsetX = sheet()->columnPosition(column); - qreal offsetY = sheet()->rowPosition(row); - tableContext.shapeContext.addShapeOffset(shape, QTransform::fromTranslate(-offsetX, -offsetY)); - shape->setAdditionalAttribute("table:end-cell-address", Region(QPoint(scol, srow)).saveOdf()); - shape->setAdditionalAttribute("table:end-x", QString::number(bottomRight.x() - endX) + "pt"); - shape->setAdditionalAttribute("table:end-y", QString::number(bottomRight.y() - endY) + "pt"); - shape->saveOdf(tableContext.shapeContext); - shape->removeAdditionalAttribute("table:end-cell-address"); - shape->removeAdditionalAttribute("table:end-x"); - shape->removeAdditionalAttribute("table:end-y"); - tableContext.shapeContext.removeShapeOffset(shape); - } - } - - xmlwriter.endElement(); - return true; -} - -void Cell::saveOdfValue(KoXmlWriter &xmlWriter) -{ - // Determine the format that we will be storing. - // This is usually the format that is actually shown - doing so mixes style and content, but that's how - // LO does it, so we need to stay compatible - Format::Type shownFormat = style().formatType(); - if (shownFormat == Format::Generic) - shownFormat = sheet()->map()->formatter()->determineFormatting(value(), shownFormat); - Value::Format saveFormat = Value::fmt_None; - Value::Format valueFormat = value().format(); - if (valueFormat == Value::fmt_Boolean) - saveFormat = Value::fmt_Boolean; - else if (valueFormat == Value::fmt_String) // if it's a text, it needs to be stored as a text - saveFormat = Value::fmt_String; - else if (Format::isDate(shownFormat)) - saveFormat = Value::fmt_Date; - else if (Format::isTime(shownFormat)) - saveFormat = Value::fmt_Time; - else if (Format::isNumber(shownFormat)) - saveFormat = Value::fmt_Number; - else if (Format::isMoney(shownFormat)) - saveFormat = Value::fmt_Money; - else if (shownFormat == Format::Percentage) - saveFormat = Value::fmt_Percent; - else if (shownFormat == Format::Text) - saveFormat = Value::fmt_String; - else if (shownFormat == Format::Custom) - saveFormat = valueFormat; - - switch (saveFormat) { - case Value::fmt_None: break; //NOTHING HERE - case Value::fmt_Boolean: { - xmlWriter.addAttribute("office:value-type", "boolean"); - xmlWriter.addAttribute("office:boolean-value", (value().asBoolean() ? "true" : "false")); - break; - } - case Value::fmt_Number: { - xmlWriter.addAttribute("office:value-type", "float"); - if (value().isInteger()) - xmlWriter.addAttribute("office:value", QString::number(value().asInteger())); - else - xmlWriter.addAttribute("office:value", QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG)); - break; - } - case Value::fmt_Percent: { - xmlWriter.addAttribute("office:value-type", "percentage"); - xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value().asFloat()))); - break; - } - case Value::fmt_Money: { - xmlWriter.addAttribute("office:value-type", "currency"); - const Style style = this->style(); - if (style.hasAttribute(Style::CurrencyFormat)) { - Currency currency = style.currency(); - xmlWriter.addAttribute("office:currency", currency.code()); - } - xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value().asFloat()))); - break; - } - case Value::fmt_DateTime: break; //NOTHING HERE - case Value::fmt_Date: { - xmlWriter.addAttribute("office:value-type", "date"); - xmlWriter.addAttribute("office:date-value", - value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate)); - break; - } - case Value::fmt_Time: { - xmlWriter.addAttribute("office:value-type", "time"); - xmlWriter.addAttribute("office:time-value", - value().asTime().toString("'PT'hh'H'mm'M'ss'S'")); - break; - } - case Value::fmt_String: { - xmlWriter.addAttribute("office:value-type", "string"); - xmlWriter.addAttribute("office:string-value", value().asString()); - break; - } - }; -} - -bool Cell::loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext, - const Styles& autoStyles, const QString& cellStyleName, - QList& shapeData) -{ - static const QString sFormula = QString::fromLatin1("formula"); - static const QString sValidationName = QString::fromLatin1("validation-name"); - static const QString sValueType = QString::fromLatin1("value-type"); - static const QString sBoolean = QString::fromLatin1("boolean"); - static const QString sBooleanValue = QString::fromLatin1("boolean-value"); - static const QString sTrue = QString::fromLatin1("true"); - static const QString sFalse = QString::fromLatin1("false"); - static const QString sFloat = QString::fromLatin1("float"); - static const QString sValue = QString::fromLatin1("value"); - static const QString sCurrency = QString::fromLatin1("currency"); - static const QString sPercentage = QString::fromLatin1("percentage"); - static const QString sDate = QString::fromLatin1("date"); - static const QString sDateValue = QString::fromLatin1("date-value"); - static const QString sTime = QString::fromLatin1("time"); - static const QString sTimeValue = QString::fromLatin1("time-value"); - static const QString sString = QString::fromLatin1("string"); - static const QString sStringValue = QString::fromLatin1("string-value"); - static const QString sNumberColumnsSpanned = QString::fromLatin1("number-columns-spanned"); - static const QString sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned"); - static const QString sAnnotation = QString::fromLatin1("annotation"); - static const QString sP = QString::fromLatin1("p"); - - static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" << "of:" << "msoxl:"; - - //Search and load each paragraph of text. Each paragraph is separated by a line break. - loadOdfCellText(element, tableContext, autoStyles, cellStyleName); - - // - // formula - // - bool isFormula = false; - if (element.hasAttributeNS(KoXmlNS::table, sFormula)) { - isFormula = true; - QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, QString())); - // debugSheetsODF << "cell:" << name() << "formula :" << oasisFormula; - // each spreadsheet application likes to safe formulas with a different namespace - // prefix, so remove all of them - QString namespacePrefix; - foreach(const QString &prefix, formulaNSPrefixes) { - if (oasisFormula.startsWith(prefix)) { - oasisFormula.remove(0, prefix.length()); - namespacePrefix = prefix; - break; - } - } - oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix); - setUserInput(oasisFormula); - } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to the text to avoid = to be painted - setUserInput(userInput().prepend('\'')); - - // - // validation - // - if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) { - const QString validationName = element.attributeNS(KoXmlNS::table, sValidationName, QString()); - debugSheetsODF << "cell:" << name() << sValidationName << validationName; - Validity validity; - validity.loadOdfValidation(this, validationName, tableContext); - if (!validity.isEmpty()) - setValidity(validity); - } - - // - // value type - // - if (element.hasAttributeNS(KoXmlNS::office, sValueType)) { - const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, QString()); - // debugSheetsODF << "cell:" << name() << "value-type:" << valuetype; - if (valuetype == sBoolean) { - const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, QString()).toLower(); - if ((val == sTrue) || (val == sFalse)) - setValue(Value(val == sTrue)); - } - - // integer and floating-point value - else if (valuetype == sFloat) { - bool ok = false; - Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); - if (ok) { - value.setFormat(Value::fmt_Number); - setValue(value); -#if 0 - Style style; - style.setFormatType(Format::Number); - setStyle(style); -#endif - } - // always set the userInput to the actual value read from the cell, and not whatever happens to be set as text, as the textual representation of a value may be less accurate than the value itself - if (!isFormula) - setUserInput(sheet()->map()->converter()->asString(value).asString()); - } - - // currency value - else if (valuetype == sCurrency) { - bool ok = false; - Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); - if (ok) { - value.setFormat(Value::fmt_Money); - setValue(value); - - Currency currency; - if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) { - currency = Currency(element.attributeNS(KoXmlNS::office, sCurrency, QString())); - } - /* TODO: somehow make this work again, all setStyle calls here will be overwritten by cell styles later - if( style.isEmpty() ) { - Style style; - style.setCurrency(currency); - setStyle(style); - } */ - } - } else if (valuetype == sPercentage) { - bool ok = false; - Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); - if (ok) { - value.setFormat(Value::fmt_Percent); - setValue(value); - if (!isFormula && userInput().isEmpty()) - setUserInput(sheet()->map()->converter()->asString(value).asString()); -// FIXME Stefan: Should be handled by Value::Format. Verify and remove! -#if 0 - Style style; - style.setFormatType(Format::Percentage); - setStyle(style); -#endif - } - } else if (valuetype == sDate) { - QString value = element.attributeNS(KoXmlNS::office, sDateValue, QString()); - - // "1980-10-15" or "2001-01-01T19:27:41" - int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0; - bool hasTime = false; - bool ok = false; - - int p1 = value.indexOf('-'); - if (p1 > 0) { - year = value.left(p1).toInt(&ok); - if (ok) { - int p2 = value.indexOf('-', ++p1); - month = value.mid(p1, p2 - p1).toInt(&ok); - if (ok) { - // the date can optionally have a time attached - int p3 = value.indexOf('T', ++p2); - if (p3 > 0) { - hasTime = true; - day = value.mid(p2, p3 - p2).toInt(&ok); - if (ok) { - int p4 = value.indexOf(':', ++p3); - hours = value.mid(p3, p4 - p3).toInt(&ok); - if (ok) { - int p5 = value.indexOf(':', ++p4); - minutes = value.mid(p4, p5 - p4).toInt(&ok); - if (ok) - seconds = value.right(value.length() - p5 - 1).toInt(&ok); - } - } - } else { - day = value.right(value.length() - p2).toInt(&ok); - } - } - } - } - - if (ok) { - if (hasTime) - setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, minutes, seconds)), sheet()->map()->calculationSettings())); - else - setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); -// FIXME Stefan: Should be handled by Value::Format. Verify and remove! -//Sebsauer: Fixed now. Value::Format handles it correct. -#if 0 - Style s; - s.setFormatType(Format::ShortDate); - setStyle(s); -#endif - // debugSheetsODF << "cell:" << name() << "Type: date, value:" << value << "Date:" << year << " -" << month << " -" << day; - } - } else if (valuetype == sTime) { - QString value = element.attributeNS(KoXmlNS::office, sTimeValue, QString()); - - // "PT15H10M12S" - int hours = 0, minutes = 0, seconds = 0; - int l = value.length(); - QString num; - bool ok = false; - for (int i = 0; i < l; ++i) { - if (value[i].isNumber()) { - num += value[i]; - continue; - } else if (value[i] == 'H') - hours = num.toInt(&ok); - else if (value[i] == 'M') - minutes = num.toInt(&ok); - else if (value[i] == 'S') - seconds = num.toInt(&ok); - else - continue; - //debugSheetsODF << "Num:" << num; - num.clear(); - if (!ok) - break; - } - - if (ok) { - // Value kval( timeToNum( hours, minutes, seconds ) ); - // cell.setValue( kval ); - setValue(Value(QTime(hours % 24, minutes, seconds))); -// FIXME Stefan: Should be handled by Value::Format. Verify and remove! -#if 0 - Style style; - style.setFormatType(Format::Time); - setStyle(style); -#endif - // debugSheetsODF << "cell:" << name() << "Type: time:" << value << "Hours:" << hours << "," << minutes << "," << seconds; - } - } else if (valuetype == sString) { - if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) { - QString value = element.attributeNS(KoXmlNS::office, sStringValue, QString()); - setValue(Value(value)); - } else { - // use the paragraph(s) read in before - setValue(Value(userInput())); - } -// FIXME Stefan: Should be handled by Value::Format. Verify and remove! -#if 0 - Style style; - style.setFormatType(Format::Text); - setStyle(style); -#endif - } else { - // debugSheetsODF << "cell:" << name() << " Unknown type. Parsing user input."; - // Set the value by parsing the user input. - parseUserInput(userInput()); - } - } else { // no value-type attribute - // debugSheetsODF << "cell:" << name() << " No value type specified. Parsing user input."; - // Set the value by parsing the user input. - parseUserInput(userInput()); - } - - // - // merged cells ? - // - int colSpan = 1; - int rowSpan = 1; - if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) { - bool ok = false; - int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, QString()).toInt(&ok); - if (ok) colSpan = span; - } - if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) { - bool ok = false; - int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, QString()).toInt(&ok); - if (ok) rowSpan = span; - } - if (colSpan > 1 || rowSpan > 1) - mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1); - - // - // cell comment/annotation - // - KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, sAnnotation); - if (!annotationElement.isNull()) { - QString comment; - KoXmlNode node = annotationElement.firstChild(); - while (!node.isNull()) { - KoXmlElement commentElement = node.toElement(); - if (!commentElement.isNull()) - if (commentElement.localName() == sP && commentElement.namespaceURI() == KoXmlNS::text) { - if (!comment.isEmpty()) comment.append('\n'); - comment.append(commentElement.text()); - } - - node = node.nextSibling(); - } - if (!comment.isEmpty()) - setComment(comment); - } - - loadOdfObjects(element, tableContext, shapeData); - - return true; -} - -// Similar to KoXml::namedItemNS except that children of span tags will be evaluated too. -KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const QString &localName) -{ - KoXmlNode n = node.firstChild(); - for (; !n.isNull(); n = n.nextSibling()) { - if (n.isElement()) { - if (n.localName() == localName && n.namespaceURI() == nsURI) { - return n.toElement(); - } - if (n.localName() == "span" && n.namespaceURI() == nsURI) { - KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not recursive - if (!e.isNull()) { - return e; - } - } - } - } - return KoXmlElement(); -} - -// recursively goes through all children of parent and returns true if there is any element -// in the draw: namespace in this subtree -static bool findDrawElements(const KoXmlElement& parent) -{ - KoXmlElement element; - forEachElement(element , parent) { - if (element.namespaceURI() == KoXmlNS::draw) - return true; - if (findDrawElements(element)) - return true; - } - return false; -} - -QString loadOdfCellTextNodes(const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace) -{ - QString cellText; - bool countedOwnFragments = false; - bool prevWasText = false; - for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { - if (n.isText()) { - prevWasText = true; - QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), *stripLeadingSpace); - if (!t.isEmpty()) { - *stripLeadingSpace = t[t.length() - 1].isSpace(); - cellText += t; - if (!countedOwnFragments) { - // We only count the number of different parent elements which have text. That is - // so cause different parent-elements may mean different styles which means - // rich-text while the same parent element means the same style so we can easily - // put them together into one string. - countedOwnFragments = true; - ++(*textFragmentCount); - } - } - } else { - KoXmlElement e = n.toElement(); - if (!e.isNull()) { - if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() - 1].isSpace()) { - // A trailing space of the cellText collected so far needs to be preserved when - // more text-nodes within the same parent follow but if an element like e.g. - // text:s follows then a trailing space needs to be removed. - cellText.chop(1); - } - prevWasText = false; - - // We can optimize some elements like text:s (space), text:tab (tabulator) and - // text:line-break (new-line) to not produce rich-text but add the equivalent - // for them in plain-text. - const bool isTextNs = e.namespaceURI() == KoXmlNS::text; - if (isTextNs && e.localName() == "s") { - const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", QString()).toInt()); - cellText += QString().fill(32, howmany); - } else if (isTextNs && e.localName() == "tab") { - cellText += '\t'; - } else if (isTextNs && e.localName() == "line-break") { - cellText += '\n'; - ++(*lineCount); - } else if (isTextNs && e.localName() == "span") { - // Nested span-elements means recursive evaluation. - cellText += loadOdfCellTextNodes(e, textFragmentCount, lineCount, hasRichText, stripLeadingSpace); - } else if (!isTextNs || - ( e.localName() != "annotation" && - e.localName() != "bookmark" && - e.localName() != "meta" && - e.localName() != "tag" )) { - // Seems we have an element we cannot easily translate to a string what - // means it's all rich-text now. - *hasRichText = true; - } - } - } - } - return cellText; -} - -void Cell::loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName) -{ - //Search and load each paragraph of text. Each paragraph is separated by a line break - KoXmlElement textParagraphElement; - QString cellText; - - int lineCount = 0; - bool hasRichText = false; - bool stripLeadingSpace = true; - - forEachElement(textParagraphElement , parent) { - if (textParagraphElement.localName() == "p" && - textParagraphElement.namespaceURI() == KoXmlNS::text) { - - // the text:a link could be located within a text:span element - KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, KoXmlNS::text, "a"); - if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href")) { - QString link = textA.attributeNS(KoXmlNS::xlink, "href", QString()); - cellText = textA.text(); - setUserInput(cellText); - hasRichText = false; - lineCount = 0; - // The value will be set later in loadOdf(). - if ((!link.isEmpty()) && (link[0] == '#')) - link.remove(0, 1); - setLink(link); - // Abort here cause we can handle only either a link in a cell or (rich-)text but not both. - break; - } - - if (!cellText.isNull()) - cellText += '\n'; - - ++lineCount; - int textFragmentCount = 0; - - // Our text could contain formating for value or result of formula or a mix of - // multiple text:span elements with text-nodes and line-break's. - cellText += loadOdfCellTextNodes(textParagraphElement, &textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace); - - // If we got text from multiple different sources (e.g. from the text:p and a - // child text:span) then we have very likely rich-text. - if (!hasRichText) - hasRichText = textFragmentCount >= 2; - } - } - - if (!cellText.isNull()) { - if (hasRichText && !findDrawElements(parent)) { - // for now we don't support richtext and embedded shapes in the same cell; - // this is because they would currently be loaded twice, once by the KoTextLoader - // and later properly by the cell itself - - Style style; style.setDefault(); - if (!cellStyleName.isEmpty()) { - if (autoStyles.contains(cellStyleName)) - style.merge(autoStyles[cellStyleName]); - else { - const CustomStyle* namedStyle = sheet()->map()->styleManager()->style(cellStyleName); - if (namedStyle) - style.merge(*namedStyle); - } - } - - QTextCharFormat format = style.asCharFormat(); - ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); - - QSharedPointer doc(new QTextDocument); - KoTextDocument(doc.data()).setStyleManager(sheet()->map()->textStyleManager()); - - Q_ASSERT(tableContext.shapeContext); - KoTextLoader loader(*tableContext.shapeContext); - QTextCursor cursor(doc.data()); - loader.loadBody(parent, cursor); - - setUserInput(doc->toPlainText()); - setRichText(doc); - } else { - setUserInput(cellText); - } - } - - // enable word wrapping if multiple lines of text have been found. - if (lineCount >= 2) { - Style newStyle; - newStyle.setWrapText(true); - setStyle(newStyle); - } -} - -void Cell::loadOdfObjects(const KoXmlElement &parent, OdfLoadingContext& tableContext, QList& shapeData) -{ - // Register additional attributes, that identify shapes anchored in cells. - // Their dimensions need adjustment after all rows are loaded, - // because the position of the end cell is not always known yet. - KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( - KoXmlNS::table, "end-cell-address", - "table:end-cell-address")); - KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( - KoXmlNS::table, "end-x", - "table:end-x")); - KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( - KoXmlNS::table, "end-y", - "table:end-y")); - - KoXmlElement element; - forEachElement(element, parent) { - if (element.namespaceURI() != KoXmlNS::draw) - continue; - - if (element.localName() == "a") { - // It may the case that the object(s) are embedded into a hyperlink so actions are done on - // clicking it/them but since we do not supported objects-with-hyperlinks yet we just fetch - // the inner elements and use them to at least create and show the objects (see bug 249862). - KoXmlElement e; - forEachElement(e, element) { - if (e.namespaceURI() != KoXmlNS::draw) - continue; - ShapeLoadingData data = loadOdfObject(e, *tableContext.shapeContext); - if (data.shape) { - shapeData.append(data); - } - } - } else { - ShapeLoadingData data = loadOdfObject(element, *tableContext.shapeContext); - if (data.shape) { - shapeData.append(data); - } - } - } -} - -ShapeLoadingData Cell::loadOdfObject(const KoXmlElement &element, KoShapeLoadingContext &shapeContext) -{ - ShapeLoadingData data; - data.shape = 0; - KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext); - if (!shape) { - debugSheetsODF << "Unable to load shape with localName=" << element.localName(); - return data; - } - - d->sheet->addShape(shape); - - // The position is relative to the upper left sheet corner until now. Move it. - QPointF position = shape->position(); - // Remember how far we're off from the top-left corner of this cell - double offsetX = position.x(); - double offsetY = position.y(); - for (int col = 1; col < column(); ++col) - position += QPointF(d->sheet->columnFormat(col)->width(), 0.0); - if (this->row() > 1) - position += QPointF(0.0, d->sheet->rowFormats()->totalRowHeight(1, this->row() - 1)); - shape->setPosition(position); - - dynamic_cast(shape->applicationData())->setAnchoredToCell(true); - - // All three attributes are necessary for cell anchored shapes. - // Otherwise, they are anchored in the sheet. - if (!shape->hasAdditionalAttribute("table:end-cell-address") || - !shape->hasAdditionalAttribute("table:end-x") || - !shape->hasAdditionalAttribute("table:end-y")) { - debugSheetsODF << "Not all attributes found, that are necessary for cell anchoring."; - return data; - } - - Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address")), - d->sheet->map(), d->sheet); - if (!endCell.isValid() || !endCell.isSingular()) - return data; - - QString string = shape->additionalAttribute("table:end-x"); - if (string.isNull()) - return data; - double endX = KoUnit::parseValue(string); - - string = shape->additionalAttribute("table:end-y"); - if (string.isNull()) - return data; - double endY = KoUnit::parseValue(string); - - data.shape = shape; - data.startCell = QPoint(column(), row()); - data.offset = QPointF(offsetX, offsetY); - data.endCell = endCell; - data.endPoint = QPointF(endX, endY); - - // The column dimensions are already the final ones, but not the row dimensions. - // The default height is used for the not yet loaded rows. - // TODO Stefan: Honor non-default row heights later! - // subtract offset because the accumulated width and height we calculate below starts - // at the top-left corner of this cell, but the shape can have an offset to that corner - QSizeF size = QSizeF(endX - offsetX, endY - offsetY); - for (int col = column(); col < endCell.firstRange().left(); ++col) - size += QSizeF(d->sheet->columnFormat(col)->width(), 0.0); - if (endCell.firstRange().top() > this->row()) - size += QSizeF(0.0, d->sheet->rowFormats()->totalRowHeight(this->row(), endCell.firstRange().top() - 1)); - shape->setSize(size); - - return data; -} - bool Cell::load(const KoXmlElement & cell, int _xshift, int _yshift, Paste::Mode mode, Paste::Operation op, bool paste) { bool ok; // // First of all determine in which row and column this // cell belongs. // d->row = cell.attribute("row").toInt(&ok) + _yshift; if (!ok) return false; d->column = cell.attribute("column").toInt(&ok) + _xshift; if (!ok) return false; // Validation if (d->row < 1 || d->row > KS_rowMax) { debugSheets << "Cell::load: Value out of range Cell:row=" << d->row; return false; } if (d->column < 1 || d->column > KS_colMax) { debugSheets << "Cell::load: Value out of range Cell:column=" << d->column; return false; } // // Load formatting information. // KoXmlElement formatElement = cell.namedItem("format").toElement(); if (!formatElement.isNull() && ((mode == Paste::Normal) || (mode == Paste::Format) || (mode == Paste::NoBorder))) { int mergedXCells = 0; int mergedYCells = 0; if (formatElement.hasAttribute("colspan")) { int i = formatElement.attribute("colspan").toInt(&ok); if (!ok) return false; // Validation if (i < 0 || i > KS_spanMax) { debugSheets << "Value out of range Cell::colspan=" << i; return false; } if (i) mergedXCells = i; } if (formatElement.hasAttribute("rowspan")) { int i = formatElement.attribute("rowspan").toInt(&ok); if (!ok) return false; // Validation if (i < 0 || i > KS_spanMax) { debugSheets << "Value out of range Cell::rowspan=" << i; return false; } if (i) mergedYCells = i; } if (mergedXCells != 0 || mergedYCells != 0) mergeCells(d->column, d->row, mergedXCells, mergedYCells); Style style; if (!style.loadXML(formatElement, mode)) return false; setStyle(style); } // // Load the condition section of a cell. // KoXmlElement conditionsElement = cell.namedItem("condition").toElement(); if (!conditionsElement.isNull()) { Conditions conditions; Map *const map = sheet()->map(); ValueParser *const valueParser = map->parser(); conditions.loadConditions(conditionsElement, valueParser); if (!conditions.isEmpty()) setConditions(conditions); } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) { //clear the conditional formatting setConditions(Conditions()); } KoXmlElement validityElement = cell.namedItem("validity").toElement(); if (!validityElement.isNull()) { Validity validity; if (validity.loadXML(this, validityElement)) setValidity(validity); } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) { // clear the validity setValidity(Validity()); } // // Load the comment // KoXmlElement comment = cell.namedItem("comment").toElement(); if (!comment.isNull() && (mode == Paste::Normal || mode == Paste::Comment || mode == Paste::NoBorder)) { QString t = comment.text(); //t = t.trimmed(); setComment(t); } // // The real content of the cell is loaded here. It is stored in // the "text" tag, which contains either a text or a CDATA section. // // TODO: make this suck less. We set data twice, in loadCellData, and // also here. Not good. KoXmlElement text = cell.namedItem("text").toElement(); if (!text.isNull() && (mode == Paste::Normal || mode == Paste::Text || mode == Paste::NoBorder || mode == Paste::Result)) { /* older versions mistakenly put the datatype attribute on the cell instead of the text. Just move it over in case we're parsing an old document */ QString dataType; if (cell.hasAttribute("dataType")) // new docs dataType = cell.attribute("dataType"); KoXmlElement result = cell.namedItem("result").toElement(); QString txt = text.text(); if ((mode == Paste::Result) && (txt[0] == '=')) // paste text of the element, if we want to paste result // and the source cell contains a formula setUserInput(result.text()); else //otherwise copy everything loadCellData(text, op, dataType); if (!result.isNull()) { QString dataType; QString t = result.text(); if (result.hasAttribute("dataType")) dataType = result.attribute("dataType"); // boolean ? if (dataType == "Bool") { if (t == "false") setValue(Value(false)); else if (t == "true") setValue(Value(true)); } else if (dataType == "Num") { bool ok = false; double dd = t.toDouble(&ok); if (ok) setValue(Value(dd)); } else if (dataType == "Date") { bool ok = false; double dd = t.toDouble(&ok); if (ok) { Value value(dd); value.setFormat(Value::fmt_Date); setValue(value); } else { int pos = t.indexOf('/'); int year = t.mid(0, pos).toInt(); int pos1 = t.indexOf('/', pos + 1); int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); int day = t.right(t.length() - pos1 - 1).toInt(); QDate date(year, month, day); if (date.isValid()) setValue(Value(date, sheet()->map()->calculationSettings())); } } else if (dataType == "Time") { bool ok = false; double dd = t.toDouble(&ok); if (ok) { Value value(dd); value.setFormat(Value::fmt_Time); setValue(value); } else { int hours = -1; int minutes = -1; int second = -1; int pos, pos1; pos = t.indexOf(':'); hours = t.mid(0, pos).toInt(); pos1 = t.indexOf(':', pos + 1); minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); second = t.right(t.length() - pos1 - 1).toInt(); QTime time(hours, minutes, second); if (time.isValid()) setValue(Value(time)); } } else { setValue(Value(t)); } } } return true; } bool Cell::loadCellData(const KoXmlElement & text, Paste::Operation op, const QString &_dataType) { //TODO: use converter()->asString() to generate userInput() QString t = text.text(); t = t.trimmed(); // A formula like =A1+A2 ? if ((!t.isEmpty()) && (t[0] == '=')) { t = decodeFormula(t); parseUserInput(pasteOperation(t, userInput(), op)); makeFormula(); } // rich text ? else if ((!t.isEmpty()) && (t[0] == '!')) { // KSpread pre 1.4 stores hyperlink as rich text (first char is '!') // extract the link and the correspoding text // This is a rather dirty hack, but enough for Calligra Sheets generated XML bool inside_tag = false; QString qml_text; QString tag; QString qml_link; for (int i = 1; i < t.length(); ++i) { QChar ch = t[i]; if (ch == '<') { if (!inside_tag) { inside_tag = true; tag.clear(); } } else if (ch == '>') { if (inside_tag) { inside_tag = false; if (tag.startsWith(QLatin1String("a href=\""), Qt::CaseSensitive) && tag.endsWith(QLatin1Char('"'))) { qml_link.remove(0, 8).chop(1); } tag.clear(); } } else { if (!inside_tag) qml_text += ch; else tag += ch; } } if (!qml_link.isEmpty()) setLink(qml_link); setUserInput(qml_text); setValue(Value(qml_text)); } else { bool newStyleLoading = true; QString dataType = _dataType; if (dataType.isNull()) { if (text.hasAttribute("dataType")) { // new docs dataType = text.attribute("dataType"); } else { // old docs: do the ugly solution of parsing the text // ...except for date/time if (isDate() && (t.count('/') == 2)) dataType = "Date"; else if (isTime() && (t.count(':') == 2)) dataType = "Time"; else { parseUserInput(pasteOperation(t, userInput(), op)); newStyleLoading = false; } } } if (newStyleLoading) { // boolean ? if (dataType == "Bool") setValue(Value(t.toLower() == "true")); // number ? else if (dataType == "Num") { bool ok = false; if (t.contains('.')) setValue(Value(t.toDouble(&ok))); // We save in non-localized format else setValue(Value(t.toLongLong(&ok))); if (!ok) { warnSheets << "Couldn't parse '" << t << "' as number."; } /* We will need to localize the text version of the number */ KLocale* locale = sheet()->map()->calculationSettings()->locale(); /* KLocale::formatNumber requires the precision we want to return. */ int precision = t.length() - t.indexOf('.') - 1; if (style().formatType() == Format::Percentage) { if (value().isInteger()) t = locale->formatNumber(value().asInteger() * 100); else t = locale->formatNumber(numToDouble(value().asFloat() * 100.0), precision); setUserInput(pasteOperation(t, userInput(), op)); setUserInput(userInput() + '%'); } else { if (value().isInteger()) t = locale->formatLong(value().asInteger()); else t = locale->formatNumber(numToDouble(value().asFloat()), precision); setUserInput(pasteOperation(t, userInput(), op)); } } // date ? else if (dataType == "Date") { int pos = t.indexOf('/'); int year = t.mid(0, pos).toInt(); int pos1 = t.indexOf('/', pos + 1); int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); int day = t.right(t.length() - pos1 - 1).toInt(); setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); if (value().asDate(sheet()->map()->calculationSettings()).isValid()) // Should always be the case for new docs setUserInput(locale()->formatDate(value().asDate(sheet()->map()->calculationSettings()), KLocale::ShortDate)); else { // This happens with old docs, when format is set wrongly to date parseUserInput(pasteOperation(t, userInput(), op)); } } // time ? else if (dataType == "Time") { int hours = -1; int minutes = -1; int second = -1; int pos, pos1; pos = t.indexOf(':'); hours = t.mid(0, pos).toInt(); pos1 = t.indexOf(':', pos + 1); minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); second = t.right(t.length() - pos1 - 1).toInt(); setValue(Value(QTime(hours, minutes, second))); if (value().asTime().isValid()) // Should always be the case for new docs setUserInput(locale()->formatTime(value().asTime(), true)); else { // This happens with old docs, when format is set wrongly to time parseUserInput(pasteOperation(t, userInput(), op)); } } else { // Set the cell's text setUserInput(pasteOperation(t, userInput(), op)); setValue(Value(userInput())); } } } if (!sheet()->isLoading()) parseUserInput(userInput()); return true; } QTime Cell::toTime(const KoXmlElement &element) { //TODO: can't we use tryParseTime (after modification) instead? QString t = element.text(); t = t.trimmed(); int hours = -1; int minutes = -1; int second = -1; int pos, pos1; pos = t.indexOf(':'); hours = t.mid(0, pos).toInt(); pos1 = t.indexOf(':', pos + 1); minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); second = t.right(t.length() - pos1 - 1).toInt(); setValue(Value(QTime(hours, minutes, second))); return value().asTime(); } QDate Cell::toDate(const KoXmlElement &element) { QString t = element.text(); int pos; int pos1; int year = -1; int month = -1; int day = -1; pos = t.indexOf('/'); year = t.mid(0, pos).toInt(); pos1 = t.indexOf('/', pos + 1); month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); day = t.right(t.length() - pos1 - 1).toInt(); setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); return value().asDate(sheet()->map()->calculationSettings()); } QString Cell::pasteOperation(const QString &new_text, const QString &old_text, Paste::Operation op) { if (op == Paste::OverWrite) return new_text; QString tmp_op; QString tmp; QString old; if (!new_text.isEmpty() && new_text[0] == '=') { tmp = new_text.right(new_text.length() - 1); } else { tmp = new_text; } if (old_text.isEmpty() && (op == Paste::Add || op == Paste::Mul || op == Paste::Sub || op == Paste::Div)) { old = "=0"; } if (!old_text.isEmpty() && old_text[0] == '=') { old = old_text.right(old_text.length() - 1); } else { old = old_text; } bool b1, b2; tmp.toDouble(&b1); old.toDouble(&b2); if (b1 && !b2 && old.length() == 0) { old = '0'; b2 = true; } if (b1 && b2) { switch (op) { case Paste::Add: tmp_op = QString::number(old.toDouble() + tmp.toDouble()); break; case Paste::Mul : tmp_op = QString::number(old.toDouble() * tmp.toDouble()); break; case Paste::Sub: tmp_op = QString::number(old.toDouble() - tmp.toDouble()); break; case Paste::Div: tmp_op = QString::number(old.toDouble() / tmp.toDouble()); break; default: Q_ASSERT(0); } return tmp_op; } else if ((new_text[0] == '=' && old_text[0] == '=') || (b1 && old_text[0] == '=') || (new_text[0] == '=' && b2)) { switch (op) { case Paste::Add : tmp_op = "=(" + old + ")+" + '(' + tmp + ')'; break; case Paste::Mul : tmp_op = "=(" + old + ")*" + '(' + tmp + ')'; break; case Paste::Sub: tmp_op = "=(" + old + ")-" + '(' + tmp + ')'; break; case Paste::Div: tmp_op = "=(" + old + ")/" + '(' + tmp + ')'; break; default : Q_ASSERT(0); } tmp_op = decodeFormula(tmp_op); return tmp_op; } tmp = decodeFormula(new_text); return tmp; } Cell& Cell::operator=(const Cell & other) { d = other.d; return *this; } bool Cell::operator<(const Cell& other) const { if (sheet() != other.sheet()) return sheet() < other.sheet(); // pointers! if (row() < other.row()) return true; return ((row() == other.row()) && (column() < other.column())); } bool Cell::operator==(const Cell& other) const { return (row() == other.row() && column() == other.column() && sheet() == other.sheet()); } bool Cell::operator!() const { return (!d); // isNull() } bool Cell::compareData(const Cell& other) const { if (value() != other.value()) return false; if (formula() != other.formula()) return false; if (link() != other.link()) return false; if (mergedXCells() != other.mergedXCells()) return false; if (mergedYCells() != other.mergedYCells()) return false; if (style() != other.style()) return false; if (comment() != other.comment()) return false; if (conditions() != other.conditions()) return false; if (validity() != other.validity()) return false; return true; } QPoint Cell::cellPosition() const { Q_ASSERT(!isNull()); return QPoint(column(), row()); } diff --git a/sheets/Cell.h b/sheets/Cell.h index de62343b1a0..c939f8b6556 100644 --- a/sheets/Cell.h +++ b/sheets/Cell.h @@ -1,667 +1,613 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2006-2007 Stefan Nikolaus Copyright 2004 Tomas Mecir Copyright 1999-2002,2004 Laurent Montel Copyright 2002,2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2003 Stefan Hetzl Copyright 2001-2002 Philipp Mueller Copyright 2002 Harri Porten Copyright 2002 John Dailey Copyright 1999-2001 David Faure Copyright 2000-2001 Werner Trobin Copyright 2000 Simon Hausmann Copyright 1999 Michael Reiher Copyright 1999 Reginald Stadlbauer 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 CALLIGRA_SHEETS_CELL #define CALLIGRA_SHEETS_CELL #include #include #include #include "Global.h" #include "Style.h" class QDomElement; class QDomDocument; class QRect; class QPoint; class QDate; class KLocale; -class KoXmlWriter; -class KoGenStyles; -class KoGenStyle; -class KoShapeLoadingContext; namespace Calligra { namespace Sheets { class Conditions; class Database; class Doc; class Formula; -class OdfLoadingContext; -class OdfSavingContext; class Sheet; class Validity; class Value; class CellTest; -struct ShapeLoadingData; /** * An accessor to the actual cell data. * The Cell object acts as accessor to the actual data stored in the separate * storages in CellStorage. It provides methods to alter and retrieve this data * and methods related to loading and saving the contents. */ class CALLIGRA_SHEETS_ODF_EXPORT Cell { public: /** * Constructor. * Creates the null cell. * \note Accessing the sheet(), column() or row() methods or any related method, that makes use * of the former, will fail. */ Cell(); /** * Constructor. * Creates a Cell for accessing the data in \p sheet at position \p col , \p row . */ Cell(const Sheet* sheet, int column, int row); /** * Constructor. * Creates a Cell for accessing the data in \p sheet at position \p pos . */ Cell(const Sheet* sheet, const QPoint& pos); /** * Copy constructor. */ Cell(const Cell& other); /** * Destructor. */ ~Cell(); /** * \return the sheet this cell belongs to */ Sheet* sheet() const; /** * Returns the locale setting of this cell. */ KLocale* locale() const; /** * Returns true, if this is a default cell, i.e. if the cell has no value, formula, link and * does not merge any other cells, and has no custom style. */ bool isDefault() const; /** * Returns true, if this is a cell with default content, i.e. if the cell has no value, formula, link and * does not merge any other cells. This is the same as isDefault, except that the style * is not taken into account here. */ bool hasDefaultContent() const; /** * Returns true, if this cell has no content, i.e no value and no formula. */ bool isEmpty() const; /** * Returns true if this cell is the null cell. */ bool isNull() const; /** * Returns true if this cell holds a formula. */ bool isFormula() const; /** * Returns the cell's column. */ int column() const; /** * Returns the cell's row. */ int row() const; /** * Returns the name of the cell. For example, the cell in first column and * first row is "A1". */ QString name() const; /** * Returns the full name of the cell, i.e. including the worksheet name. * Example: "Sheet1!A1" */ QString fullName() const; /** * Returns the column name of the cell. */ QString columnName() const; /** * Given the cell position, this static function returns the name of the cell. * Example: name(5,4) will return "E4". */ static QString name(int col, int row); /** * Given the sheet and cell position, this static function returns the full name * of the cell, i.e. with the name of the sheet. */ static QString fullName(const Sheet *s, int col, int row); /** * Given the column number, this static function returns the corresponding * column name, i.e. the first column is "A", the second is "B", and so on. */ static QString columnName(uint column); /** * \return the output text, e.g. the result of a formula */ QString displayText(const Style& s = Style(), Value* v = 0, bool *showFormula = 0) const; /** * \return the comment associated with this cell */ QString comment() const; void setComment(const QString& comment); /** * \return the conditions associated with this cell */ Conditions conditions() const; void setConditions(const Conditions& conditions); /** * \return the database associated with this cell */ Database database() const; /** * The cell's formula. Usable to analyze the formula's tokens. * \return pointer to the cell's formula object */ Formula formula() const; /** * Sets \p formula as associated formula of this cell. */ void setFormula(const Formula& formula); /** * Returns the link associated with cell. It is empty if this cell * contains no link. */ QString link() const; /** * Sets a link for this cell. For example, setLink( "mailto:joe@somewhere.com" ) * will open a new e-mail if this cell is clicked. * Possible choices for link are URL (web, ftp), e-mail address, local file, * or another cell. */ void setLink(const QString& link); /** * \return the Style associated with this Cell */ Style style() const; /** * The effective style takes conditional style attributes into account. * \return the effective Style associated with this Cell */ Style effectiveStyle() const; void setStyle(const Style& style); /** * \return the validity checks associated with this cell */ Validity validity() const; void setValidity(Validity validity); /** * Returns the value that this cell holds. It could be from the user * (i.e. when s/he enters a value) or a result of formula. */ const Value value() const; /** * Sets the value for this cell. * It also clears all errors, if the value itself is not an error. * In addition to this, it calculates the outstring and sets the dirty * flags so that a redraw is forced. * \param value the new value * * \see setUserInput, parseUserInput */ void setValue(const Value& value); /** * Returns the richtext that this cell holds. */ QSharedPointer richText() const; /** * Sets the richtext for this cell. * If \p text is empty, richtext is removed. * * \param text the new richtext */ void setRichText(QSharedPointer text); /** * Return the text the user entered. This could be a value (e.g. "14.03") * or a formula (e.g. "=SUM(A1:A10)") */ QString userInput() const; /** * Sets the user input without parsing it. * If \p text is a formula, creates a formula object and sets \p text as * its expression. Otherwise, simply stores \p text as user input. * * \see parseUserInput, setValue */ void setUserInput(const QString& text); /** * Sets the user input without parsing it, without clearing existing formulas * userinput or rich text. Used during loading of documents only! * If \p text is a formula, creates a formula object and sets \p text as * its expression. Otherwise, simply stores \p text as user input. * * \see parseUserInput, setValue */ void setRawUserInput(const QString& text); /** * Sets the user input and parses it. * If \p text is a formula, creates a formula object and sets \p text as * its expression. Otherwise, parses \p text, creates an appropriate value, * including the proper type, validates the value and, if accepted, stores * \p text as the user input and the value as the cell's value. * * \see setUserInput, setValue */ void parseUserInput(const QString& text); /** * \ingroup NativeFormat */ bool load(const KoXmlElement& cell, int _xshift, int _yshift, Paste::Mode pm = Paste::Normal, Paste::Operation op = Paste::OverWrite, bool paste = false); /** * \ingroup NativeFormat * Save this cell. * @param doc document to save cell in * @param xOffset x offset * @param yOffset y offset * @param era set this to true if you want to encode relative references as absolutely (they will be switched * back to relative references during decoding) - is used for cutting to clipboard * Usually this is false, to only store the properties explicitly set. */ QDomElement save(QDomDocument& doc, int xOffset = 0, int yOffset = 0, bool era = false); /** * \ingroup NativeFormat * Decodes a string into a time value. */ QTime toTime(const KoXmlElement &element); /** * \ingroup NativeFormat * Decodes a string into a date value. */ QDate toDate(const KoXmlElement &element); - /** - * \ingroup OpenDocument - * Loads a cell from an OASIS XML element. - * @param element An OASIS XML element - * @param tableContext The loading context assoiated with the XML element - */ - bool loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext, - const Styles& autoStyles, const QString& cellStyleName, - QList& shapeData); - - /** - * \ingroup OpenDocument - */ - bool saveOdf(int row, int column, int &repeated, - OdfSavingContext& savingContext); - /** * Copies the format from \p cell . * * @see copyAll(Cell *cell) */ void copyFormat(const Cell& cell); /** * Copies the content from \p cell . * * @see copyAll(Cell *cell) */ void copyContent(const Cell& cell); /** * Copies the format and the content from \p cell . * * @see copyContent( const Cell& cell ) * @see copyFormat( const Cell& cell ) */ void copyAll(const Cell& cell); /** * @return the width of this cell as double */ double width() const; /** * @return the height of this cell as double */ double height() const; /** * \return the position of this cell */ QPoint cellPosition() const; /** * @return true if the cell should be printed in a print out. * That's the case, if it has any content, border, backgroundcolor, * or background brush. * * @see Sheet::print */ bool needsPrinting() const; // //END // ////////////////////////////////////////////////////////////////////////// // //BEGIN Merging // /** * If this cell is part of a merged cell, then the marker may * never reside on this cell. * * @return true if another cell has this one merged into itself. */ bool isPartOfMerged() const; /** * \return the merging cell (might be this cell) */ Cell masterCell() const; /** * Merge a number of cells, i.e. force the cell to occupy other * cells space. If '_x' and '_y' are 0 then the merging is * disabled. * * @param _col is the column this cell is assumed to be in. * @param _row is the row this cell is assumed to be in. * @param _x tells to occupy _x additional cells in the horizontal * @param _y tells to occupy _y additional cells in the vertical * */ void mergeCells(int _col, int _row, int _x, int _y); /** * @return true if the cell is forced to obscure other cells. */ bool doesMergeCells() const; /** * @return the number of obscured cells in the horizontal direction as a * result of cell merging (forced obscuring) */ int mergedXCells() const; /** * @return the number of obscured cells in the vertical direction as a * result of cell merging (forced obscuring) */ int mergedYCells() const; // //END Merging // ////////////////////////////////////////////////////////////////////////// // //BEGIN Matrix locking // bool isLocked() const; QRect lockedCells() const; // //END Matrix locking // ////////////////////////////////////////////////////////////////////////// // //BEGIN Cut & paste // /** * Encodes the cell's formula into a text representation. * * \param fixedReferences encode relative references absolutely (this is used for copying * a cell to make the paste operation create a formula that points * to the original cells, not the cells at the same relative position) * \see decodeFormula() */ QString encodeFormula(bool fixedReferences = false) const; /** * Decodes a text representation \p text into a formula expression. * * \see encodeFormula() */ QString decodeFormula(const QString& text) const; /** * Merges the @p new_text with @p old_text during a paste operation. * If both texts represent doubles, then the operation is performed on both * values and the result is returned. If both texts represents a formula or * one a formula and the other a double value, then a formula is returned. * In all other cases @p new_text is returned. * * @return the merged text. */ QString pasteOperation(const QString &new_text, const QString &old_text, Paste::Operation op); // //END Cut & paste // ////////////////////////////////////////////////////////////////////////// // //BEGIN // /** * Parses the formula. * @return @c false on error. */ bool makeFormula(); // //END // ////////////////////////////////////////////////////////////////////////// // //BEGIN Effective style attributes // /** returns horizontal alignment, depending on style and value type */ int effectiveAlignX() const; /** returns true, if cell format is of date type or content is a date */ bool isDate() const; /** returns true, if cell format is of time type or content is a time */ bool isTime() const; /** returns true, if cell format is of text type */ bool isText() const; // //END Effective style attributes // ////////////////////////////////////////////////////////////////////////// // //BEGIN Operators // /** * Assignment. */ Cell& operator=(const Cell& other); /** * Tests whether this cell's location is less than the \p other 's. * (QMap support) * \note Does not compare the cell attributes/data. */ bool operator<(const Cell& other) const; /** * Tests for equality with \p other 's location only. * (QHash support) * \note Does not compare the cell attributes/data. */ bool operator==(const Cell& other) const; /** * Is null. */ bool operator!() const; // //END Operators // ////////////////////////////////////////////////////////////////////////// // //BEGIN // /** * Tests for equality of all cell attributes/data to those in \p other . */ bool compareData(const Cell& other) const; -protected: - /** - * \ingroup OpenDocument - * Load the text paragraphs from an OASIS XML cell description. - * @param parent The DOM element representing the cell. - */ - void loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName); - - /** - * \ingroup OpenDocument - */ - void loadOdfObjects(const KoXmlElement& e, OdfLoadingContext& tableContext, QList& shapeData); - - - /** - * \ingroup OpenDocument - */ - void saveOdfAnnotation(KoXmlWriter &xmlwriter); -public: - ShapeLoadingData loadOdfObject(const KoXmlElement& element, KoShapeLoadingContext& shapeContext); private: friend class CellTest; class Private; QSharedDataPointer d; /** * \ingroup NativeFormat */ bool loadCellData(const KoXmlElement &text, Paste::Operation op, const QString &dataType = QString()); /** * \ingroup NativeFormat */ bool saveCellResult(QDomDocument& doc, QDomElement& result, QString str); - - /** - * \ingroup OpenDocument - */ - void saveOdfValue(KoXmlWriter &xmlWriter); - - /** - * \ingroup OpenDocument - * @return the OASIS style's name - */ - QString saveOdfCellStyle(KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles); }; inline uint qHash(const Cell& cell) { return (static_cast(cell.column()) << 16) + static_cast(cell.row()); } } // namespace Sheets } // namespace Calligra Q_DECLARE_TYPEINFO(Calligra::Sheets::Cell, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(Calligra::Sheets::Cell) /*************************************************************************** QDebug support ****************************************************************************/ inline QDebug operator<<(QDebug str, const Calligra::Sheets::Cell& cell) { return str << qPrintable(QString("%1%2").arg(Calligra::Sheets::Cell::columnName(cell.column())).arg(QString::number(cell.row()))); } #endif // CALLIGRA_SHEETS_CELL diff --git a/sheets/Style.cpp b/sheets/Style.cpp index 68dcc9c8446..74b8f2bb920 100644 --- a/sheets/Style.cpp +++ b/sheets/Style.cpp @@ -1,2868 +1,2943 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2006 Stefan Nikolaus Copyright 2003 Norbert Andres 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. */ // Local #include "Style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "SheetsDebug.h" #include "Condition.h" #include "Currency.h" #include "Global.h" #include "StyleManager.h" #include "Util.h" using namespace Calligra::Sheets; ///////////////////////////////////////////////////////////////////////////// // // SubStyles // ///////////////////////////////////////////////////////////////////////////// namespace Calligra { namespace Sheets { static uint calculateValue(QPen const & pen) { uint n = pen.color().red() + pen.color().green() + pen.color().blue(); n += 1000 * pen.width(); n += 10000 * (uint) pen.style(); return n; } // specialized debug method template<> QString SubStyleOne::debugData(bool withName) const { QString out; if (withName) out = name(Style::CurrencyFormat) + ' '; QDebug qdbg(&out); qdbg << value1.symbol(); return out; } template class PenStyle : public SubStyleOne { public: PenStyle(const QPen& p = Qt::NoPen) : SubStyleOne(p) {} }; template class BorderPenStyle : public PenStyle { public: BorderPenStyle(const QPen& p = Qt::NoPen) : PenStyle(p), value(calculateValue(p)) {} int value; }; QString SubStyle::name(Style::Key key) { QString name; switch (key) { case Style::DefaultStyleKey: name = "Default style"; break; case Style::NamedStyleKey: name = "Named style"; break; case Style::LeftPen: name = "Left pen"; break; case Style::RightPen: name = "Right pen"; break; case Style::TopPen: name = "Top pen"; break; case Style::BottomPen: name = "Bottom pen"; break; case Style::FallDiagonalPen: name = "Fall diagonal pen"; break; case Style::GoUpDiagonalPen: name = "Go up diagonal pen"; break; case Style::HorizontalAlignment: name = "Horz. alignment"; break; case Style::VerticalAlignment: name = "Vert. alignment"; break; case Style::MultiRow: name = "Wrap text"; break; case Style::VerticalText: name = "Vertical text"; break; case Style::Angle: name = "Angle"; break; case Style::Indentation: name = "Indentation"; break; case Style::ShrinkToFit: name = "Shrink to Fit"; break; case Style::Prefix: name = "Prefix"; break; case Style::Postfix: name = "Postfix"; break; case Style::Precision: name = "Precision"; break; case Style::ThousandsSep: name = "Thousands separator"; break; case Style::FormatTypeKey: name = "Format type"; break; case Style::FloatFormatKey: name = "Float format"; break; case Style::FloatColorKey: name = "Float color"; break; case Style::CurrencyFormat: name = "Currency"; break; case Style::CustomFormat: name = "Custom format"; break; case Style::BackgroundBrush: name = "Background brush"; break; case Style::BackgroundColor: name = "Background color"; break; case Style::FontColor: name = "Font color"; break; case Style::FontFamily: name = "Font family"; break; case Style::FontSize: name = "Font size"; break; case Style::FontBold: name = "Font bold"; break; case Style::FontItalic: name = "Font italic"; break; case Style::FontStrike: name = "Font strikeout"; break; case Style::FontUnderline: name = "Font underline"; break; case Style::DontPrintText: name = "Do not print text"; break; case Style::NotProtected: name = "Not protected"; break; case Style::HideAll: name = "Hide all"; break; case Style::HideFormula: name = "Hide formula"; break; } return name; } SharedSubStyle SharedSubStyle::s_defaultStyle(new SubStyle()); } // namespace Sheets } // namespace Calligra ///////////////////////////////////////////////////////////////////////////// // // Style::Private // ///////////////////////////////////////////////////////////////////////////// class Q_DECL_HIDDEN Style::Private : public QSharedData { public: QHash subStyles; }; ///////////////////////////////////////////////////////////////////////////// // // Style // ///////////////////////////////////////////////////////////////////////////// Style::Style() : d(new Private) { } Style::Style(const Style& style) : d(style.d) { } Style::~Style() { } Style::StyleType Style::type() const { return AUTO; } QString Style::parentName() const { if (!d->subStyles.contains(NamedStyleKey)) return QString(); return static_cast(d->subStyles[NamedStyleKey].data())->name; } void Style::setParentName(const QString& name) { d->subStyles.insert(NamedStyleKey, SharedSubStyle(new NamedStyle(name))); } void Style::clearAttribute(Key key) { d->subStyles.remove(key); } bool Style::hasAttribute(Key key) const { return d->subStyles.contains(key); } void Style::loadAttributes(const QList& subStyles) { d->subStyles.clear(); for (int i = 0; i < subStyles.count(); ++i) { // already existing items are replaced d->subStyles.insert(subStyles[i]->type(), subStyles[i]); } } void Style::loadOdfStyle(KoOdfStylesReader& stylesReader, const KoXmlElement& element, Conditions& conditions, const StyleManager* styleManager, const ValueParser *parser) { // NOTE Stefan: Do not fill the style stack with the parent styles! KoStyleStack styleStack; styleStack.push(element); styleStack.setTypeProperties("table-cell"); loadOdfTableCellProperties(stylesReader, styleStack); styleStack.setTypeProperties("text"); loadOdfTextProperties(stylesReader, styleStack); styleStack.setTypeProperties("paragraph"); loadOdfParagraphProperties(stylesReader, styleStack); KoXmlElement e; forEachElement(e, element) { if (e.namespaceURI() == KoXmlNS::style && e.localName() == "map") conditions.loadOdfConditions(e, parser, styleManager); } loadOdfDataStyle(stylesReader, element, conditions, styleManager, parser); } typedef QPair StringPair; void Style::loadOdfDataStyle(KoOdfStylesReader& stylesReader, const KoXmlElement& element, Conditions& conditions, const StyleManager* styleManager, const ValueParser *parser) { QString str; if (element.hasAttributeNS(KoXmlNS::style, "data-style-name")) { const QString styleName = element.attributeNS(KoXmlNS::style, "data-style-name", QString()); loadOdfDataStyle(stylesReader, styleName, conditions, styleManager, parser); } } void Style::loadOdfDataStyle(KoOdfStylesReader &stylesReader, const QString &styleName, Conditions &conditions, const StyleManager *styleManager, const ValueParser *parser) { if (stylesReader.dataFormats().contains(styleName)) { Style* theStyle = this; QPair dataStylePair = stylesReader.dataFormats()[styleName]; const KoOdfNumberStyles::NumericStyleFormat& dataStyle = dataStylePair.first; const QList > styleMaps = dataStyle.styleMaps; if(styleMaps.count() > 0) { theStyle = new Style(); for (QList >::const_iterator it = styleMaps.begin(); it != styleMaps.end(); ++it) { const Conditional c = conditions.loadOdfCondition(it->first, it->second, QString(), parser); if (styleManager->style(c.styleName) == 0) { CustomStyle* const s = new CustomStyle(c.styleName); s->loadOdfDataStyle(stylesReader, c.styleName, conditions, styleManager, parser); const_cast(styleManager)->insertStyle(s); } } } KoStyleStack styleStack; styleStack.push(*dataStylePair.second); styleStack.setTypeProperties("text"); theStyle->loadOdfTextProperties(stylesReader, styleStack); QString tmp = dataStyle.prefix; if (!tmp.isEmpty()) { theStyle->setPrefix(tmp); } tmp = dataStyle.suffix; if (!tmp.isEmpty()) { theStyle->setPostfix(tmp); } // determine data formatting switch (dataStyle.type) { case KoOdfNumberStyles::Number: theStyle->setFormatType(Format::Number); if (!dataStyle.currencySymbol.isEmpty()) theStyle->setCurrency(numberCurrency(dataStyle.currencySymbol)); else theStyle->setCurrency(numberCurrency(dataStyle.formatStr)); break; case KoOdfNumberStyles::Scientific: theStyle->setFormatType(Format::Scientific); break; case KoOdfNumberStyles::Currency: debugSheetsODF << " currency-symbol:" << dataStyle.currencySymbol; if (!dataStyle.currencySymbol.isEmpty()) theStyle->setCurrency(numberCurrency(dataStyle.currencySymbol)); else theStyle->setCurrency(numberCurrency(dataStyle.formatStr)); break; case KoOdfNumberStyles::Percentage: theStyle->setFormatType(Format::Percentage); break; case KoOdfNumberStyles::Fraction: // determine format of fractions, dates and times by using the // formatting string tmp = dataStyle.formatStr; if (!tmp.isEmpty()) { theStyle->setFormatType(Style::fractionType(tmp)); } break; case KoOdfNumberStyles::Date: // determine format of fractions, dates and times by using the // formatting string tmp = dataStyle.formatStr; if (!tmp.isEmpty()) { theStyle->setFormatType(Style::dateType(tmp)); } break; case KoOdfNumberStyles::Time: // determine format of fractions, dates and times by using the // formatting string tmp = dataStyle.formatStr; if (!tmp.isEmpty()) { theStyle->setFormatType(Style::timeType(tmp)); } break; case KoOdfNumberStyles::Boolean: theStyle->setFormatType(Format::Number); break; case KoOdfNumberStyles::Text: theStyle->setFormatType(Format::Text); break; } if (dataStyle.precision > -1) { // special handling for precision // The Style default (-1) and the storage default (0) differ. // The maximum is 10. Replace the Style value 0 with -11, which always results // in a storage value < 0 and is interpreted as Style value 0. int precision = dataStyle.precision; if (type() == AUTO && precision == 0) precision = -11; theStyle->setPrecision(precision); } theStyle->setThousandsSep(dataStyle.thousandsSep); theStyle->setCustomFormat(dataStyle.formatStr); if(styleMaps.count() > 0) { conditions.setDefaultStyle(*theStyle); delete theStyle; } } } +QString encodePen(const QPen & pen) +{ +// debugSheets<<"encodePen( const QPen & pen ) :"<attributeNS(KoXmlNS::svg, "font-family")); debugSheetsODF << "\t\t\t svg:font-family:" << fontFamily(); } } } static QString convertDateFormat(const QString& date) { QString result = date; result.replace("%Y", "yyyy"); result.replace("%y", "yy"); result.replace("%n", "M"); result.replace("%m", "MM"); result.replace("%e", "d"); result.replace("%d", "dd"); result.replace("%b", "MMM"); result.replace("%B", "MMMM"); result.replace("%a", "ddd"); result.replace("%A", "dddd"); return result; } Format::Type Style::dateType(const QString &_f) { const QString dateFormatShort = convertDateFormat(KLocale::global()->dateFormatShort()); const QString dateFormat = convertDateFormat(KLocale::global()->dateFormat()); QString _format = _f; _format.replace(' ', '-'); if (_format == "d-MMM-yy" || _format == "dd-MMM-yy") return Format::Date1; else if (_format == "dd-MMM-yyyy") return Format::Date2; else if (_format == "d-MM") return Format::Date3; else if (_format == "dd-MM") //TODO ??????? return Format::Date4; else if (_format == "dd/MM/yy") return Format::Date5; else if (_format == "dd/MM/yyyy") return Format::Date6; else if (_format == "MMM-yy") return Format::Date7; else if (_format == "MMMM-yy") return Format::Date8; else if (_format == "MMMM-yyyy") return Format::Date9; else if (_format == "MMMMM-yy" || _format == "X-yy") return Format::Date10; else if (_format == "dd/MMM") return Format::Date11; else if (_format == "dd/MM") return Format::Date12; else if (_format == "dd/MMM/yyyy") return Format::Date13; else if (_format == "yyyy/MMM/dd") return Format::Date14; else if (_format == "yyyy-MMM-dd") return Format::Date15; else if (_format == "yyyy-MM-dd") return Format::Date16; else if (_format == "d MMMM yyyy") return Format::Date17; else if (_format == "MM/dd/yyyy") return Format::Date18; else if (_format == "MM/dd/yy") return Format::Date19; else if (_format == "MMM/dd/yy") return Format::Date20; else if (_format == "MMM/dd/yyyy") return Format::Date21; else if (_format == "MMM-yyyy") return Format::Date22; else if (_format == "yyyy") return Format::Date23; else if (_format == "yy") return Format::Date24; else if (_format == "yyyy/MM/dd") return Format::Date25; else if (_format == "yyyy/MMM/dd") return Format::Date26; else if (_format == "MMM/yy") return Format::Date27; else if (_format == "MMM/yyyy") return Format::Date28; else if (_format == "MMMM/yy") return Format::Date29; else if (_format == "MMMM/yyyy") return Format::Date30; else if (_format == "dd-MM") return Format::Date31; else if (_format == "MM/yy") return Format::Date32; else if (_format == "MM-yy") return Format::Date33; else if (QRegExp("^[d]+[\\s]*[d]{1,2}[\\s]+[M]{1,4}[\\s]+[y]{2,2}$").indexIn(_f) >= 0) return Format::Date34; else if (QRegExp("^[d]+[\\s]*[d]{1,2}[\\s]+[M]{1,}[\\s]+[y]{2,4}$").indexIn(_f) >= 0) return Format::Date35; else if (_format == dateFormatShort) return Format::ShortDate; else if (_format == dateFormat) return Format::TextDate; else { debugSheets << "Unhandled date format=" << _format; return Format::ShortDate; } } Format::Type Style::timeType(const QString &_format) { if (_format == "h:mm AP") return Format::Time1; else if (_format == "h:mm:ss AP") return Format::Time2; else if (_format == "hh \\h mm \\m\\i\\n ss \\s") return Format::Time3; else if (_format == "hh:mm") return Format::Time4; else if (_format == "hh:mm:ss") return Format::Time5; else if (_format == "m:ss") return Format::Time6; else if (_format == "h:mm:ss") return Format::Time7; else if (_format == "h:mm") return Format::Time8; else return Format::Time; } Currency Style::numberCurrency(const QString &_format) { // Look up if a prefix or postfix is in the currency table, // return the currency symbol to use for formatting purposes. if(!_format.isEmpty()) { QString f = QString(_format.at(0)); Currency currStart = Currency(f); if (currStart.index() > 1) return currStart; f = QString(_format.at(_format.size()-1)); Currency currEnd = Currency(f); if (currEnd.index() > 1) return currEnd; } return Currency(QString()); } Format::Type Style::fractionType(const QString &_format) { if (_format.endsWith(QLatin1String("/2"))) return Format::fraction_half; else if (_format.endsWith(QLatin1String("/4"))) return Format::fraction_quarter; else if (_format.endsWith(QLatin1String("/8"))) return Format::fraction_eighth; else if (_format.endsWith(QLatin1String("/16"))) return Format::fraction_sixteenth; else if (_format.endsWith(QLatin1String("/10"))) return Format::fraction_tenth; else if (_format.endsWith(QLatin1String("/100"))) return Format::fraction_hundredth; else if (_format.endsWith(QLatin1String("/?"))) return Format::fraction_one_digit; else if (_format.endsWith(QLatin1String("/??"))) return Format::fraction_two_digits; else if (_format.endsWith(QLatin1String("/???"))) return Format::fraction_three_digits; else return Format::fraction_three_digits; } QString Style::saveOdfStyleNumeric(KoGenStyle &style, KoGenStyles &mainStyles, Format::Type _style, const QString &_prefix, const QString &_postfix, int _precision, const QString& symbol, bool thousandsSep) { // debugSheetsODF ; QString styleName; QString valueType; switch (_style) { case Format::Number: styleName = saveOdfStyleNumericNumber(mainStyles, _style, _precision, _prefix, _postfix, thousandsSep); valueType = "float"; break; case Format::Text: styleName = saveOdfStyleNumericText(mainStyles, _style, _precision, _prefix, _postfix); valueType = "string"; break; case Format::Money: styleName = saveOdfStyleNumericMoney(mainStyles, _style, symbol, _precision, _prefix, _postfix); valueType = "currency"; break; case Format::Percentage: styleName = saveOdfStyleNumericPercentage(mainStyles, _style, _precision, _prefix, _postfix); valueType = "percentage"; break; case Format::Scientific: styleName = saveOdfStyleNumericScientific(mainStyles, _style, _prefix, _postfix, _precision, thousandsSep); valueType = "float"; break; case Format::ShortDate: case Format::TextDate: styleName = saveOdfStyleNumericDate(mainStyles, _style, _prefix, _postfix); valueType = "date"; break; case Format::Time: case Format::SecondeTime: case Format::Time1: case Format::Time2: case Format::Time3: case Format::Time4: case Format::Time5: case Format::Time6: case Format::Time7: case Format::Time8: styleName = saveOdfStyleNumericTime(mainStyles, _style, _prefix, _postfix); valueType = "time"; break; case Format::fraction_half: case Format::fraction_quarter: case Format::fraction_eighth: case Format::fraction_sixteenth: case Format::fraction_tenth: case Format::fraction_hundredth: case Format::fraction_one_digit: case Format::fraction_two_digits: case Format::fraction_three_digits: styleName = saveOdfStyleNumericFraction(mainStyles, _style, _prefix, _postfix); valueType = "float"; break; case Format::Date1: case Format::Date2: case Format::Date3: case Format::Date4: case Format::Date5: case Format::Date6: case Format::Date7: case Format::Date8: case Format::Date9: case Format::Date10: case Format::Date11: case Format::Date12: case Format::Date13: case Format::Date14: case Format::Date15: case Format::Date16: case Format::Date17: case Format::Date18: case Format::Date19: case Format::Date20: case Format::Date21: case Format::Date22: case Format::Date23: case Format::Date24: case Format::Date25: case Format::Date26: case Format::Date27: case Format::Date28: case Format::Date29: case Format::Date30: case Format::Date31: case Format::Date32: case Format::Date33: case Format::Date34: case Format::Date35: styleName = saveOdfStyleNumericDate(mainStyles, _style, _prefix, _postfix); valueType = "date"; break; case Format::Custom: styleName = saveOdfStyleNumericCustom(mainStyles, _style, _prefix, _postfix); break; case Format::Generic: case Format::None: if (_precision > -1 || !_prefix.isEmpty() || !_postfix.isEmpty()) { styleName = saveOdfStyleNumericNumber(mainStyles, _style, _precision, _prefix, _postfix, thousandsSep); valueType = "float"; } break; case Format::DateTime: default: ; } if (!styleName.isEmpty()) { style.addAttribute("style:data-style-name", styleName); } return styleName; } QString Style::saveOdfStyleNumericNumber(KoGenStyles& mainStyles, Format::Type /*_style*/, int _precision, const QString& _prefix, const QString& _postfix, bool thousandsSep) { QString format; if (_precision == -1) format = '0'; else { QString tmp; for (int i = 0; i < _precision; i++) { tmp += '0'; } format = "0." + tmp; } return KoOdfNumberStyles::saveOdfNumberStyle(mainStyles, format, _prefix, _postfix, thousandsSep); } QString Style::saveOdfStyleNumericText(KoGenStyles& /*mainStyles*/, Format::Type /*_style*/, int /*_precision*/, const QString& /*_prefix*/, const QString& /*_postfix*/) { return ""; } QString Style::saveOdfStyleNumericMoney(KoGenStyles& mainStyles, Format::Type /*_style*/, const QString& symbol, int _precision, const QString& _prefix, const QString& _postfix) { QString format; if (_precision == -1) format = '0'; else { QString tmp; for (int i = 0; i < _precision; i++) { tmp += '0'; } format = "0." + tmp; } return KoOdfNumberStyles::saveOdfCurrencyStyle(mainStyles, format, symbol, _prefix, _postfix); } QString Style::saveOdfStyleNumericPercentage(KoGenStyles&mainStyles, Format::Type /*_style*/, int _precision, const QString& _prefix, const QString& _postfix) { // // //% // //TODO add decimal etc. QString format; if (_precision == -1) format = '0'; else { QString tmp; for (int i = 0; i < _precision; i++) { tmp += '0'; } format = "0." + tmp; } return KoOdfNumberStyles::saveOdfPercentageStyle(mainStyles, format, _prefix, _postfix); } QString Style::saveOdfStyleNumericScientific(KoGenStyles&mainStyles, Format::Type /*_style*/, const QString &_prefix, const QString &_suffix, int _precision, bool thousandsSep) { // // // QString format; if (_precision == -1) format = "0E+00"; else { QString tmp; for (int i = 0; i < _precision; i++) { tmp += '0'; } format = "0." + tmp + "E+00"; } return KoOdfNumberStyles::saveOdfScientificStyle(mainStyles, format, _prefix, _suffix, thousandsSep); } QString Style::saveOdfStyleNumericDate(KoGenStyles&mainStyles, Format::Type _style, const QString& _prefix, const QString& _postfix) { QString format; bool locale = false; switch (_style) { //TODO fixme use locale of Calligra Sheets and not kglobal case Format::ShortDate: format = KLocale::global()->dateFormatShort(); locale = true; break; case Format::TextDate: format = KLocale::global()->dateFormat(); locale = true; break; case Format::Date1: format = "dd-MMM-yy"; break; case Format::Date2: format = "dd-MMM-yyyy"; break; case Format::Date3: format = "dd-M"; break; case Format::Date4: format = "dd-MM"; break; case Format::Date5: format = "dd/MM/yy"; break; case Format::Date6: format = "dd/MM/yyyy"; break; case Format::Date7: format = "MMM-yy"; break; case Format::Date8: format = "MMMM-yy"; break; case Format::Date9: format = "MMMM-yyyy"; break; case Format::Date10: format = "MMMMM-yy"; break; case Format::Date11: format = "dd/MMM"; break; case Format::Date12: format = "dd/MM"; break; case Format::Date13: format = "dd/MMM/yyyy"; break; case Format::Date14: format = "yyyy/MMM/dd"; break; case Format::Date15: format = "yyyy-MMM-dd"; break; case Format::Date16: format = "yyyy/MM/dd"; break; case Format::Date17: format = "d MMMM yyyy"; break; case Format::Date18: format = "MM/dd/yyyy"; break; case Format::Date19: format = "MM/dd/yy"; break; case Format::Date20: format = "MMM/dd/yy"; break; case Format::Date21: format = "MMM/dd/yyyy"; break; case Format::Date22: format = "MMM-yyyy"; break; case Format::Date23: format = "yyyy"; break; case Format::Date24: format = "yy"; break; case Format::Date25: format = "yyyy/MM/dd"; break; case Format::Date26: format = "yyyy/MMM/dd"; break; case Format::Date27: format = "MMM/yy"; break; case Format::Date28: format = "MMM/yyyy"; break; case Format::Date29: format = "MMMM/yy"; break; case Format::Date30: format = "MMMM/yyyy"; break; case Format::Date31: format = "dd-MM"; break; case Format::Date32: format = "MM/yy"; break; case Format::Date33: format = "MM-yy"; break; case Format::Date34: format = "ddd d MMM yyyy"; break; case Format::Date35: format = "dddd d MMM yyyy"; break; default: debugSheetsODF << "this date format is not defined ! :" << _style; break; } return KoOdfNumberStyles::saveOdfDateStyle(mainStyles, format, locale, _prefix, _postfix); } QString Style::saveOdfStyleNumericCustom(KoGenStyles& /*mainStyles*/, Format::Type /*_style*/, const QString& /*_prefix*/, const QString& /*_postfix*/) { //TODO // // /// // /// // // // //: // // // // return ""; } QString Style::saveOdfStyleNumericTime(KoGenStyles& mainStyles, Format::Type _style, const QString& _prefix, const QString& _postfix) { // // //: // // // // QString format; bool locale = false; //TODO use format switch (_style) { case Format::Time: //TODO FIXME format = "hh:mm:ss"; break; case Format::SecondeTime: //TODO FIXME format = "hh:mm"; break; case Format::Time1: format = "h:mm AP"; break; case Format::Time2: format = "h:mm:ss AP"; break; case Format::Time3: // 9 h 01 min 28 s format = "hh \\h mm \\m\\i\\n ss \\s"; break; case Format::Time4: format = "hh:mm"; break; case Format::Time5: format = "hh:mm:ss"; break; case Format::Time6: format = "m:ss"; break; case Format::Time7: format = "h:mm:ss"; break; case Format::Time8: format = "h:mm"; break; default: debugSheetsODF << "time format not defined :" << _style; break; } return KoOdfNumberStyles::saveOdfTimeStyle(mainStyles, format, locale, _prefix, _postfix); } QString Style::saveOdfStyleNumericFraction(KoGenStyles &mainStyles, Format::Type formatType, const QString &_prefix, const QString &_suffix) { // // // QString format; switch (formatType) { case Format::fraction_half: format = "# ?/2"; break; case Format::fraction_quarter: format = "# ?/4"; break; case Format::fraction_eighth: format = "# ?/8"; break; case Format::fraction_sixteenth: format = "# ?/16"; break; case Format::fraction_tenth: format = "# ?/10"; break; case Format::fraction_hundredth: format = "# ?/100"; break; case Format::fraction_one_digit: format = "# ?/?"; break; case Format::fraction_two_digits: format = "# \?\?/\?\?"; break; case Format::fraction_three_digits: format = "# \?\?\?/\?\?\?"; break; default: debugSheetsODF << " fraction format not defined :" << formatType; break; } return KoOdfNumberStyles::saveOdfFractionStyle(mainStyles, format, _prefix, _suffix); } QString Style::saveOdf(KoGenStyle& style, KoGenStyles& mainStyles, const StyleManager* manager) const { // list of substyles to store QSet keysToStore; if (isDefault()) { if (style.isEmpty()) { style = KoGenStyle(KoGenStyle::TableCellStyle, "table-cell"); style.setDefaultStyle(true); // don't i18n'ize "Default" in this case return "Default"; // mainStyles.insert( style, "Default", KoGenStyles::DontAddNumberToName ); } // no attributes to store here return mainStyles.insert(style, "ce"); } else if (hasAttribute(NamedStyleKey)) { // it's not really the parent name in this case CustomStyle* namedStyle = manager->style(parentName()); // remove substyles already present in named style if (namedStyle) keysToStore = difference(*namedStyle); // no differences and not an automatic style yet if (style.isEmpty() && (keysToStore.count() == 0 || (keysToStore.count() == 1 && keysToStore.toList().first() == NamedStyleKey))) { return manager->openDocumentName(parentName()); } } else keysToStore = QSet::fromList(d->subStyles.keys()); // Calligra::Sheets::Style is definitly an OASIS auto style, // but don't overwrite it, if it already exists if (style.isEmpty()) style = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell"); // doing the real work saveOdfStyle(keysToStore, style, mainStyles, manager); return mainStyles.insert(style, "ce"); } void Style::saveOdfStyle(const QSet& keysToStore, KoGenStyle &style, KoGenStyles &mainStyles, const StyleManager* manager) const { #ifndef NDEBUG //if (type() == BUILTIN ) // debugSheetsStyle <<"BUILTIN"; //else if (type() == CUSTOM ) // debugSheetsStyle <<"CUSTOM"; //else if (type() == AUTO ) // debugSheetsStyle <<"AUTO"; #endif if (!isDefault() && hasAttribute(NamedStyleKey)) { const QString parentName = manager->openDocumentName(this->parentName()); if (!parentName.isEmpty()) style.addAttribute("style:parent-style-name", parentName); } if (keysToStore.contains(HorizontalAlignment)) { QString value; switch (halign()) { case Center: value = "center"; break; case Right: value = "end"; break; case Left: value = "start"; break; case Justified: value = "justify"; break; case HAlignUndefined: break; } if (!value.isEmpty()) { style.addProperty("style:text-align-source", "fix"); // table-cell-properties style.addProperty("fo:text-align", value, KoGenStyle::ParagraphType); } } if (keysToStore.contains(VerticalAlignment)) { QString value; switch (valign()) { case Top: case VJustified: value = "top"; break; case Middle: case VDistributed: value = "middle"; break; case Bottom: value = "bottom"; break; case VAlignUndefined: default: break; } if (!value.isEmpty()) // sanity style.addProperty("style:vertical-align", value); if (valign() == VJustified || valign() == VDistributed) style.addProperty("calligra:vertical-distributed", "distributed"); } if (keysToStore.contains(BackgroundColor) && backgroundColor().isValid()) style.addProperty("fo:background-color", colorName(backgroundColor())); if (keysToStore.contains(MultiRow) && wrapText()) style.addProperty("fo:wrap-option", "wrap"); if (keysToStore.contains(VerticalText) && verticalText()) { style.addProperty("style:direction", "ttb"); style.addProperty("style:rotation-angle", "0"); style.addProperty("style:rotation-align", "none"); } if (keysToStore.contains(ShrinkToFit) && shrinkToFit()) style.addProperty("style:shrink-to-fit", "true"); #if 0 if (keysToStore.contains(FloatFormat)) format.setAttribute("float", (int) floatFormat()); if (keysToStore.contains(FloatColor)) format.setAttribute("floatcolor", (int)floatColor()); if (keysToStore.contains(CustomFormat) && !customFormat().isEmpty()) format.setAttribute("custom", customFormat()); if (keysToStore.contains(Format::Type) && formatType() == Money) { format.setAttribute("type", (int) currency().type); format.setAttribute("symbol", currency().symbol); } #endif if (keysToStore.contains(Angle) && angle() != 0) { style.addProperty("style:rotation-align", "none"); style.addProperty("style:rotation-angle", QString::number(-1.0 * angle())); } if (keysToStore.contains(Indentation) && indentation() != 0.0) { style.addPropertyPt("fo:margin-left", indentation(), KoGenStyle::ParagraphType); //FIXME //if ( a == HAlignUndefined ) //currentCellStyle.addProperty("fo:text-align", "start" ); } if (keysToStore.contains(DontPrintText) && keysToStore.contains(DontPrintText)) style.addProperty("style:print-content", "false"); // protection bool hideAll = false; bool hideFormula = false; bool isNotProtected = false; if (keysToStore.contains(NotProtected)) isNotProtected = notProtected(); if (keysToStore.contains(HideAll)) hideAll = this->hideAll(); if (keysToStore.contains(HideFormula)) hideFormula = this->hideFormula(); if (hideAll) style.addProperty("style:cell-protect", "hidden-and-protected"); else { if (isNotProtected && !hideFormula) style.addProperty("style:cell-protect", "none"); else if (isNotProtected && hideFormula) style.addProperty("style:cell-protect", "formula-hidden"); else if (hideFormula) style.addProperty("style:cell-protect", "protected formula-hidden"); else if (keysToStore.contains(NotProtected) && !isNotProtected) // write out, only if it is explicitly set style.addProperty("style:cell-protect", "protected"); } // borders // NOTE Stefan: QPen api docs: // A line width of zero indicates a cosmetic pen. This means // that the pen width is always drawn one pixel wide, // independent of the transformation set on the painter. if (keysToStore.contains(LeftPen) && keysToStore.contains(RightPen) && keysToStore.contains(TopPen) && keysToStore.contains(BottomPen) && (leftBorderPen() == topBorderPen()) && (leftBorderPen() == rightBorderPen()) && (leftBorderPen() == bottomBorderPen())) { if (leftBorderPen().style() != Qt::NoPen) - style.addProperty("fo:border", Odf::encodePen(leftBorderPen())); + style.addProperty("fo:border", encodePen(leftBorderPen())); } else { if (keysToStore.contains(LeftPen) && (leftBorderPen().style() != Qt::NoPen)) - style.addProperty("fo:border-left", Odf::encodePen(leftBorderPen())); + style.addProperty("fo:border-left", encodePen(leftBorderPen())); if (keysToStore.contains(RightPen) && (rightBorderPen().style() != Qt::NoPen)) - style.addProperty("fo:border-right", Odf::encodePen(rightBorderPen())); + style.addProperty("fo:border-right", encodePen(rightBorderPen())); if (keysToStore.contains(TopPen) && (topBorderPen().style() != Qt::NoPen)) - style.addProperty("fo:border-top", Odf::encodePen(topBorderPen())); + style.addProperty("fo:border-top", encodePen(topBorderPen())); if (keysToStore.contains(BottomPen) && (bottomBorderPen().style() != Qt::NoPen)) - style.addProperty("fo:border-bottom", Odf::encodePen(bottomBorderPen())); + style.addProperty("fo:border-bottom", encodePen(bottomBorderPen())); } if (keysToStore.contains(FallDiagonalPen) && (fallDiagonalPen().style() != Qt::NoPen)) { - style.addProperty("style:diagonal-tl-br", Odf::encodePen(fallDiagonalPen())); + style.addProperty("style:diagonal-tl-br", encodePen(fallDiagonalPen())); } if (keysToStore.contains(GoUpDiagonalPen) && (goUpDiagonalPen().style() != Qt::NoPen)) { - style.addProperty("style:diagonal-bl-tr", Odf::encodePen(goUpDiagonalPen())); + style.addProperty("style:diagonal-bl-tr", encodePen(goUpDiagonalPen())); } // font if (keysToStore.contains(FontFamily)) { // !fontFamily().isEmpty() == true style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } if (keysToStore.contains(FontSize)) { // fontSize() != 0 style.addPropertyPt("fo:font-size", fontSize(), KoGenStyle::TextType); } if (keysToStore.contains(FontBold) && bold()) style.addProperty("fo:font-weight", "bold", KoGenStyle::TextType); if (keysToStore.contains(FontItalic) && italic()) style.addProperty("fo:font-style", "italic", KoGenStyle::TextType); if (keysToStore.contains(FontUnderline) && underline()) { //style:text-underline-style="solid" style:text-underline-width="auto" style.addProperty("style:text-underline-style", "solid", KoGenStyle::TextType); //copy from oo-129 style.addProperty("style:text-underline-width", "auto", KoGenStyle::TextType); style.addProperty("style:text-underline-color", "font-color", KoGenStyle::TextType); } if (keysToStore.contains(FontStrike) && strikeOut()) style.addProperty("style:text-line-through-style", "solid", KoGenStyle::TextType); if (keysToStore.contains(FontColor) && fontColor().isValid()) { // always save style.addProperty("fo:color", colorName(fontColor()), KoGenStyle::TextType); } //I don't think there is a reason why the background brush should be saved if it is null, //but remove the check if it causes problems. -- Robert Knight if (keysToStore.contains(BackgroundBrush) && (backgroundBrush().style() != Qt::NoBrush)) { QString tmp = saveOdfBackgroundStyle(mainStyles, backgroundBrush()); if (!tmp.isEmpty()) style.addProperty("calligra:fill-style-name", tmp); } QString _prefix; QString _postfix; int _precision = -1; if (keysToStore.contains(Prefix) && !prefix().isEmpty()) _prefix = prefix(); if (keysToStore.contains(Postfix) && !postfix().isEmpty()) _postfix = postfix(); if (keysToStore.contains(Precision) && precision() != -1) _precision = precision(); bool _thousandsSep = false; if (keysToStore.contains(ThousandsSep)) { _thousandsSep = thousandsSep(); } QString currencyCode; if (keysToStore.contains(FormatTypeKey) && formatType() == Format::Money) { currencyCode = currency().code(); } QString numericStyle = saveOdfStyleNumeric(style, mainStyles, formatType(), _prefix, _postfix, _precision, currencyCode, _thousandsSep); if (!numericStyle.isEmpty()) style.addAttribute("style:data-style-name", numericStyle); } QString Style::saveOdfBackgroundStyle(KoGenStyles &mainStyles, const QBrush &brush) { KoGenStyle styleobjectauto = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); KoOdfGraphicStyles::saveOdfFillStyle(styleobjectauto, mainStyles, brush); return mainStyles.insert(styleobjectauto, "gr"); } void Style::saveXML(QDomDocument& doc, QDomElement& format, const StyleManager* styleManager) const { // list of substyles to store QSet keysToStore; if (d->subStyles.contains(NamedStyleKey)) { const CustomStyle* namedStyle = styleManager->style(parentName()); // check, if it's an unmodified named style keysToStore = difference(*namedStyle); if (type() == AUTO) { const QList keys = keysToStore.toList(); if ((keysToStore.count() == 0) || (keysToStore.count() == 1 && keysToStore.toList().first() == NamedStyleKey)) { // just save the name and we are done. format.setAttribute("style-name", parentName()); return; } else format.setAttribute("parent", parentName()); } else { // custom style if (d->subStyles.contains(NamedStyleKey)) format.setAttribute("parent", parentName()); } } else keysToStore = QSet::fromList(d->subStyles.keys()); if (keysToStore.contains(HorizontalAlignment) && halign() != HAlignUndefined) format.setAttribute(type() == AUTO ? "align" : "alignX", (int) halign()); if (keysToStore.contains(VerticalAlignment) && valign() != VAlignUndefined) format.setAttribute("alignY", (int) valign()); if (keysToStore.contains(BackgroundColor) && backgroundColor().isValid()) format.setAttribute("bgcolor", backgroundColor().name()); if (keysToStore.contains(MultiRow) && wrapText()) format.setAttribute("multirow", "yes"); if (keysToStore.contains(VerticalText) && verticalText()) format.setAttribute("verticaltext", "yes"); if (keysToStore.contains(ShrinkToFit) && shrinkToFit()) format.setAttribute("shrinktofit", "yes"); if (keysToStore.contains(Precision)) format.setAttribute("precision", precision()); if (keysToStore.contains(Prefix) && !prefix().isEmpty()) format.setAttribute("prefix", prefix()); if (keysToStore.contains(Postfix) && !postfix().isEmpty()) format.setAttribute("postfix", postfix()); if (keysToStore.contains(FloatFormatKey)) format.setAttribute("float", (int) floatFormat()); if (keysToStore.contains(FloatColorKey)) format.setAttribute("floatcolor", (int)floatColor()); if (keysToStore.contains(FormatTypeKey)) format.setAttribute("format", (int) formatType()); if (keysToStore.contains(CustomFormat) && !customFormat().isEmpty()) format.setAttribute("custom", customFormat()); if (keysToStore.contains(FormatTypeKey) && formatType() == Format::Money) { format.setAttribute("type", (int) currency().index()); format.setAttribute("symbol", currency().symbol()); } if (keysToStore.contains(Angle)) format.setAttribute("angle", angle()); if (keysToStore.contains(Indentation)) format.setAttribute("indent", indentation()); if (keysToStore.contains(DontPrintText)) format.setAttribute("dontprinttext", printText() ? "no" : "yes"); if (keysToStore.contains(NotProtected)) format.setAttribute("noprotection", notProtected() ? "yes" : "no"); if (keysToStore.contains(HideAll)) format.setAttribute("hideall", hideAll() ? "yes" : "no"); if (keysToStore.contains(HideFormula)) format.setAttribute("hideformula", hideFormula() ? "yes" : "no"); if (type() == AUTO) { if (keysToStore.contains(FontFamily) || keysToStore.contains(FontSize) || keysToStore.contains(FontBold) || keysToStore.contains(FontItalic) || keysToStore.contains(FontStrike) || keysToStore.contains(FontUnderline)) { format.appendChild(NativeFormat::createElement("font", font(), doc)); } } else { // custom style if (keysToStore.contains(FontFamily)) format.setAttribute("font-family", fontFamily()); if (keysToStore.contains(FontSize)) format.setAttribute("font-size", fontSize()); if (keysToStore.contains(FontBold) || keysToStore.contains(FontItalic) || keysToStore.contains(FontUnderline) || keysToStore.contains(FontStrike)) { enum FontFlags { FBold = 0x01, FUnderline = 0x02, FItalic = 0x04, FStrike = 0x08 }; int fontFlags = 0; fontFlags |= bold() ? FBold : 0; fontFlags |= italic() ? FItalic : 0; fontFlags |= underline() ? FUnderline : 0; fontFlags |= strikeOut() ? FStrike : 0; format.setAttribute("font-flags", fontFlags); } } if (keysToStore.contains(FontColor) && fontColor().isValid()) format.appendChild(NativeFormat::createElement("pen", fontColor(), doc)); if (keysToStore.contains(BackgroundBrush)) { format.setAttribute("brushcolor", backgroundBrush().color().name()); format.setAttribute("brushstyle", (int) backgroundBrush().style()); } if (keysToStore.contains(LeftPen)) { QDomElement left = doc.createElement("left-border"); left.appendChild(NativeFormat::createElement("pen", leftBorderPen(), doc)); format.appendChild(left); } if (keysToStore.contains(TopPen)) { QDomElement top = doc.createElement("top-border"); top.appendChild(NativeFormat::createElement("pen", topBorderPen(), doc)); format.appendChild(top); } if (keysToStore.contains(RightPen)) { QDomElement right = doc.createElement("right-border"); right.appendChild(NativeFormat::createElement("pen", rightBorderPen(), doc)); format.appendChild(right); } if (keysToStore.contains(BottomPen)) { QDomElement bottom = doc.createElement("bottom-border"); bottom.appendChild(NativeFormat::createElement("pen", bottomBorderPen(), doc)); format.appendChild(bottom); } if (keysToStore.contains(FallDiagonalPen)) { QDomElement fallDiagonal = doc.createElement("fall-diagonal"); fallDiagonal.appendChild(NativeFormat::createElement("pen", fallDiagonalPen(), doc)); format.appendChild(fallDiagonal); } if (keysToStore.contains(GoUpDiagonalPen)) { QDomElement goUpDiagonal = doc.createElement("up-diagonal"); goUpDiagonal.appendChild(NativeFormat::createElement("pen", goUpDiagonalPen(), doc)); format.appendChild(goUpDiagonal); } } bool Style::loadXML(KoXmlElement& format, Paste::Mode mode) { if (format.hasAttribute("style-name")) { // Simply set the style name and we are done. insertSubStyle(NamedStyleKey, format.attribute("style-name")); return true; } else if (format.hasAttribute("parent")) insertSubStyle(NamedStyleKey, format.attribute("parent")); bool ok; if (format.hasAttribute(type() == AUTO ? "align" : "alignX")) { HAlign a = (HAlign) format.attribute(type() == AUTO ? "align" : "alignX").toInt(&ok); if (!ok) return false; if ((unsigned int) a >= 1 && (unsigned int) a <= 4) { setHAlign(a); } } if (format.hasAttribute("alignY")) { VAlign a = (VAlign) format.attribute("alignY").toInt(&ok); if (!ok) return false; if ((unsigned int) a >= 1 && (unsigned int) a < 4) { setVAlign(a); } } if (format.hasAttribute("bgcolor")) { QColor color(format.attribute("bgcolor")); if (color.isValid()) setBackgroundColor(color); } if (format.hasAttribute("multirow")) { setWrapText(true); } if (format.hasAttribute("shrinktofit")) { setShrinkToFit(true); } if (format.hasAttribute("precision")) { int i = format.attribute("precision").toInt(&ok); if (i < -1) { debugSheetsODF << "Value out of range Cell::precision=" << i; return false; } // special handling for precision // The Style default (-1) and the storage default (0) differ. if (type() == AUTO && i == -1) i = 0; // The maximum is 10. Replace the Style value 0 with -11, which always results // in a storage value < 0 and is interpreted as Style value 0. else if (type() == AUTO && i == 0) i = -11; setPrecision(i); } if (format.hasAttribute("float")) { FloatFormat a = (FloatFormat)format.attribute("float").toInt(&ok); if (!ok) return false; if ((unsigned int) a >= 1 && (unsigned int) a <= 3) { setFloatFormat(a); } } if (format.hasAttribute("floatcolor")) { FloatColor a = (FloatColor) format.attribute("floatcolor").toInt(&ok); if (!ok) return false; if ((unsigned int) a >= 1 && (unsigned int) a <= 2) { setFloatColor(a); } } if (format.hasAttribute("format")) { int fo = format.attribute("format").toInt(&ok); if (! ok) return false; setFormatType(static_cast(fo)); } if (format.hasAttribute("custom")) { setCustomFormat(format.attribute("custom")); } if (formatType() == Format::Money) { ok = true; Currency currency; if (format.hasAttribute("type")) { currency = Currency(format.attribute("type").toInt(&ok)); if (!ok) { if (format.hasAttribute("symbol")) currency = Currency(format.attribute("symbol")); } } else if (format.hasAttribute("symbol")) currency = Currency(format.attribute("symbol")); setCurrency(currency); } if (format.hasAttribute("angle")) { setAngle(format.attribute("angle").toInt(&ok)); if (!ok) return false; } if (format.hasAttribute("indent")) { setIndentation(format.attribute("indent").toDouble(&ok)); if (!ok) return false; } if (format.hasAttribute("dontprinttext")) { setDontPrintText(true); } if (format.hasAttribute("noprotection")) { setNotProtected(true); } if (format.hasAttribute("hideall")) { setHideAll(true); } if (format.hasAttribute("hideformula")) { setHideFormula(true); } if (type() == AUTO) { KoXmlElement fontElement = format.namedItem("font").toElement(); if (!fontElement.isNull()) { QFont font(NativeFormat::toFont(fontElement)); setFontFamily(font.family()); setFontSize(font.pointSize()); if (font.italic()) setFontItalic(true); if (font.bold()) setFontBold(true); if (font.underline()) setFontUnderline(true); if (font.strikeOut()) setFontStrikeOut(true); } } else { // custom style if (format.hasAttribute("font-family")) setFontFamily(format.attribute("font-family")); if (format.hasAttribute("font-size")) { setFontSize(format.attribute("font-size").toInt(&ok)); if (!ok) return false; } if (format.hasAttribute("font-flags")) { int fontFlags = format.attribute("font-flags").toInt(&ok); if (!ok) return false; enum FontFlags { FBold = 0x01, FUnderline = 0x02, FItalic = 0x04, FStrike = 0x08 }; setFontBold(fontFlags & FBold); setFontItalic(fontFlags & FItalic); setFontUnderline(fontFlags & FUnderline); setFontStrikeOut(fontFlags & FStrike); } } if (format.hasAttribute("brushcolor")) { QColor color(format.attribute("brushcolor")); if (color.isValid()) { QBrush brush = backgroundBrush(); brush.setColor(color); setBackgroundBrush(brush); } } if (format.hasAttribute("brushstyle")) { QBrush brush = backgroundBrush(); brush.setStyle((Qt::BrushStyle) format.attribute("brushstyle").toInt(&ok)); if (!ok) return false; setBackgroundBrush(brush); } KoXmlElement pen = format.namedItem("pen").toElement(); if (!pen.isNull()) { setFontColor(NativeFormat::toPen(pen).color()); } if (mode != Paste::NoBorder) { KoXmlElement left = format.namedItem("left-border").toElement(); if (!left.isNull()) { KoXmlElement pen = left.namedItem("pen").toElement(); if (!pen.isNull()) setLeftBorderPen(NativeFormat::toPen(pen)); } KoXmlElement top = format.namedItem("top-border").toElement(); if (!top.isNull()) { KoXmlElement pen = top.namedItem("pen").toElement(); if (!pen.isNull()) setTopBorderPen(NativeFormat::toPen(pen)); } KoXmlElement right = format.namedItem("right-border").toElement(); if (!right.isNull()) { KoXmlElement pen = right.namedItem("pen").toElement(); if (!pen.isNull()) setRightBorderPen(NativeFormat::toPen(pen)); } KoXmlElement bottom = format.namedItem("bottom-border").toElement(); if (!bottom.isNull()) { KoXmlElement pen = bottom.namedItem("pen").toElement(); if (!pen.isNull()) setBottomBorderPen(NativeFormat::toPen(pen)); } KoXmlElement fallDiagonal = format.namedItem("fall-diagonal").toElement(); if (!fallDiagonal.isNull()) { KoXmlElement pen = fallDiagonal.namedItem("pen").toElement(); if (!pen.isNull()) setFallDiagonalPen(NativeFormat::toPen(pen)); } KoXmlElement goUpDiagonal = format.namedItem("up-diagonal").toElement(); if (!goUpDiagonal.isNull()) { KoXmlElement pen = goUpDiagonal.namedItem("pen").toElement(); if (!pen.isNull()) setGoUpDiagonalPen(NativeFormat::toPen(pen)); } } if (format.hasAttribute("prefix")) { setPrefix(format.attribute("prefix")); } if (format.hasAttribute("postfix")) { setPostfix(format.attribute("postfix")); } return true; } uint Style::bottomPenValue() const { if (!d->subStyles.contains(BottomPen)) return BorderPenStyle().value; return static_cast*>(d->subStyles[BottomPen].data())->value; } uint Style::rightPenValue() const { if (!d->subStyles.contains(RightPen)) return BorderPenStyle().value; return static_cast*>(d->subStyles[RightPen].data())->value; } uint Style::leftPenValue() const { if (!d->subStyles.contains(LeftPen)) return BorderPenStyle().value; return static_cast*>(d->subStyles[LeftPen].data())->value; } uint Style::topPenValue() const { if (!d->subStyles.contains(TopPen)) return BorderPenStyle().value; return static_cast*>(d->subStyles[TopPen].data())->value; } QColor Style::fontColor() const { if (!d->subStyles.contains(FontColor)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FontColor].data())->value1; } QColor Style::backgroundColor() const { if (!d->subStyles.contains(BackgroundColor)) return SubStyleOne().value1; return static_cast*>(d->subStyles[BackgroundColor].data())->value1; } QPen Style::rightBorderPen() const { if (!d->subStyles.contains(RightPen)) return BorderPenStyle().value1; return static_cast*>(d->subStyles[RightPen].data())->value1; } QPen Style::bottomBorderPen() const { if (!d->subStyles.contains(BottomPen)) return BorderPenStyle().value1; return static_cast*>(d->subStyles[BottomPen].data())->value1; } QPen Style::leftBorderPen() const { if (!d->subStyles.contains(LeftPen)) return BorderPenStyle().value1; return static_cast*>(d->subStyles[LeftPen].data())->value1; } QPen Style::topBorderPen() const { if (!d->subStyles.contains(TopPen)) return BorderPenStyle().value1; return static_cast*>(d->subStyles[TopPen].data())->value1; } QPen Style::fallDiagonalPen() const { if (!d->subStyles.contains(FallDiagonalPen)) return PenStyle().value1; return static_cast*>(d->subStyles[FallDiagonalPen].data())->value1; } QPen Style::goUpDiagonalPen() const { if (!d->subStyles.contains(GoUpDiagonalPen)) return PenStyle().value1; return static_cast*>(d->subStyles[GoUpDiagonalPen].data())->value1; } QBrush Style::backgroundBrush() const { if (!d->subStyles.contains(BackgroundBrush)) return SubStyleOne().value1; return static_cast*>(d->subStyles[BackgroundBrush].data())->value1; } QString Style::customFormat() const { if (!d->subStyles.contains(CustomFormat)) return SubStyleOne().value1; return static_cast*>(d->subStyles[CustomFormat].data())->value1; } QString Style::prefix() const { if (!d->subStyles.contains(Prefix)) return SubStyleOne().value1; return static_cast*>(d->subStyles[Prefix].data())->value1; } QString Style::postfix() const { if (!d->subStyles.contains(Postfix)) return SubStyleOne().value1; return static_cast*>(d->subStyles[Postfix].data())->value1; } QString Style::fontFamily() const { if (!d->subStyles.contains(FontFamily)) return KoGlobal::defaultFont().family(); // SubStyleOne().value1; return static_cast*>(d->subStyles[FontFamily].data())->value1; } Style::HAlign Style::halign() const { if (!d->subStyles.contains(HorizontalAlignment)) return SubStyleOne().value1; return static_cast*>(d->subStyles[HorizontalAlignment].data())->value1; } Style::VAlign Style::valign() const { if (!d->subStyles.contains(VerticalAlignment)) return SubStyleOne().value1; return static_cast*>(d->subStyles[VerticalAlignment].data())->value1; } Style::FloatFormat Style::floatFormat() const { if (!d->subStyles.contains(FloatFormatKey)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FloatFormatKey].data())->value1; } Style::FloatColor Style::floatColor() const { if (!d->subStyles.contains(FloatColorKey)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FloatColorKey].data())->value1; } Format::Type Style::formatType() const { if (!d->subStyles.contains(FormatTypeKey)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FormatTypeKey].data())->value1; } Currency Style::currency() const { if (!d->subStyles.contains(CurrencyFormat)) return Currency(); return static_cast*>(d->subStyles[CurrencyFormat].data())->value1; } QFont Style::font() const { QFont font; font.setFamily(fontFamily()); font.setPointSize(fontSize()); font.setBold(bold()); font.setItalic(italic()); font.setUnderline(underline()); font.setStrikeOut(strikeOut()); return font; } bool Style::bold() const { if (!d->subStyles.contains(FontBold)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FontBold].data())->value1; } bool Style::italic() const { if (!d->subStyles.contains(FontItalic)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FontItalic].data())->value1; } bool Style::underline() const { if (!d->subStyles.contains(FontUnderline)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FontUnderline].data())->value1; } bool Style::strikeOut() const { if (!d->subStyles.contains(FontStrike)) return SubStyleOne().value1; return static_cast*>(d->subStyles[FontStrike].data())->value1; } int Style::fontSize() const { if (!d->subStyles.contains(FontSize)) return KoGlobal::defaultFont().pointSize(); //SubStyleOne().value1; return static_cast*>(d->subStyles[FontSize].data())->value1; } int Style::precision() const { if (!d->subStyles.contains(Precision)) return -1; //SubStyleOne().value1; return static_cast*>(d->subStyles[Precision].data())->value1; } bool Style::thousandsSep() const { if (!d->subStyles.contains(ThousandsSep)) return false; return static_cast*>(d->subStyles[ThousandsSep].data())->value1; } int Style::angle() const { if (!d->subStyles.contains(Angle)) return SubStyleOne().value1; return static_cast*>(d->subStyles[Angle].data())->value1; } double Style::indentation() const { if (!d->subStyles.contains(Indentation)) return SubStyleOne().value1; return static_cast*>(d->subStyles[Indentation].data())->value1; } bool Style::shrinkToFit() const { if (!d->subStyles.contains(ShrinkToFit)) return SubStyleOne().value1; return static_cast*>(d->subStyles[ShrinkToFit].data())->value1; } bool Style::verticalText() const { if (!d->subStyles.contains(VerticalText)) return SubStyleOne().value1; return static_cast*>(d->subStyles[VerticalText].data())->value1; } bool Style::wrapText() const { if (!d->subStyles.contains(MultiRow)) return SubStyleOne().value1; return static_cast*>(d->subStyles[MultiRow].data())->value1; } bool Style::printText() const { if (!d->subStyles.contains(DontPrintText)) return !SubStyleOne().value1; return !static_cast*>(d->subStyles[DontPrintText].data())->value1; } bool Style::hideAll() const { if (!d->subStyles.contains(HideAll)) return SubStyleOne().value1; return static_cast*>(d->subStyles[HideAll].data())->value1; } bool Style::hideFormula() const { if (!d->subStyles.contains(HideFormula)) return SubStyleOne().value1; return static_cast*>(d->subStyles[HideFormula].data())->value1; } bool Style::notProtected() const { if (!d->subStyles.contains(NotProtected)) return SubStyleOne().value1; return static_cast*>(d->subStyles[NotProtected].data())->value1; } bool Style::isDefault() const { return isEmpty() || d->subStyles.contains(DefaultStyleKey); } bool Style::isEmpty() const { return d->subStyles.isEmpty(); } void Style::setHAlign(HAlign align) { insertSubStyle(HorizontalAlignment, align); } void Style::setVAlign(VAlign align) { insertSubStyle(VerticalAlignment, align); } void Style::setFont(QFont const & font) { insertSubStyle(FontFamily, font.family()); insertSubStyle(FontSize, font.pointSize()); insertSubStyle(FontBold, font.bold()); insertSubStyle(FontItalic, font.italic()); insertSubStyle(FontStrike, font.strikeOut()); insertSubStyle(FontUnderline, font.underline()); } void Style::setFontFamily(QString const & family) { insertSubStyle(FontFamily, family); } void Style::setFontBold(bool enabled) { insertSubStyle(FontBold, enabled); } void Style::setFontItalic(bool enabled) { insertSubStyle(FontItalic, enabled); } void Style::setFontUnderline(bool enabled) { insertSubStyle(FontUnderline, enabled); } void Style::setFontStrikeOut(bool enabled) { insertSubStyle(FontStrike, enabled); } void Style::setFontSize(int size) { insertSubStyle(FontSize, size); } void Style::setFontColor(QColor const & color) { insertSubStyle(FontColor, color); } void Style::setBackgroundColor(QColor const & color) { insertSubStyle(BackgroundColor, color); } void Style::setRightBorderPen(QPen const & pen) { insertSubStyle(RightPen, pen); } void Style::setBottomBorderPen(QPen const & pen) { insertSubStyle(BottomPen, pen); } void Style::setLeftBorderPen(QPen const & pen) { insertSubStyle(LeftPen, pen); } void Style::setTopBorderPen(QPen const & pen) { insertSubStyle(TopPen, pen); } void Style::setFallDiagonalPen(QPen const & pen) { insertSubStyle(FallDiagonalPen, pen); } void Style::setGoUpDiagonalPen(QPen const & pen) { insertSubStyle(GoUpDiagonalPen, pen); } void Style::setAngle(int angle) { insertSubStyle(Angle, angle); } void Style::setIndentation(double indent) { insertSubStyle(Indentation, indent); } void Style::setBackgroundBrush(QBrush const & brush) { insertSubStyle(BackgroundBrush, brush); } void Style::setFloatFormat(FloatFormat format) { insertSubStyle(FloatFormatKey, format); } void Style::setFloatColor(FloatColor color) { insertSubStyle(FloatColorKey, color); } void Style::setFormatType(Format::Type format) { insertSubStyle(FormatTypeKey, format); } void Style::setCustomFormat(QString const & strFormat) { insertSubStyle(CustomFormat, strFormat); } void Style::setPrecision(int precision) { insertSubStyle(Precision, precision); } void Style::setThousandsSep(bool thousandsSep) { insertSubStyle(ThousandsSep, thousandsSep); } void Style::setPrefix(QString const & prefix) { insertSubStyle(Prefix, prefix); } void Style::setPostfix(QString const & postfix) { insertSubStyle(Postfix, postfix); } void Style::setCurrency(Currency const & currency) { QVariant variant; variant.setValue(currency); insertSubStyle(CurrencyFormat, variant); } void Style::setWrapText(bool enable) { insertSubStyle(MultiRow, enable); } void Style::setHideAll(bool enable) { insertSubStyle(HideAll, enable); } void Style::setHideFormula(bool enable) { insertSubStyle(HideFormula, enable); } void Style::setNotProtected(bool enable) { insertSubStyle(NotProtected, enable); } void Style::setDontPrintText(bool enable) { insertSubStyle(DontPrintText, enable); } void Style::setVerticalText(bool enable) { insertSubStyle(VerticalText, enable); } void Style::setShrinkToFit(bool enable) { insertSubStyle(ShrinkToFit, enable); } void Style::setDefault() { insertSubStyle(DefaultStyleKey, true); } void Style::clear() { d->subStyles.clear(); } QString Style::colorName(const QColor& color) { static QMap map; QRgb rgb = color.rgb(); if (!map.contains(rgb)) { map[rgb] = color.name(); return map[rgb]; } else { return map[rgb]; } } bool Style::compare(const SubStyle* one, const SubStyle* two) { if (!one || !two) return one == two; if (one->type() != two->type()) return false; switch (one->type()) { case DefaultStyleKey: return true; case NamedStyleKey: return static_cast(one)->name == static_cast(two)->name; // borders case LeftPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case RightPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case TopPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case BottomPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FallDiagonalPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case GoUpDiagonalPen: return static_cast*>(one)->value1 == static_cast*>(two)->value1; // layout case HorizontalAlignment: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case VerticalAlignment: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case MultiRow: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case VerticalText: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case ShrinkToFit: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case Angle: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case Indentation: return static_cast*>(one)->value1 == static_cast*>(two)->value1; // content format case Prefix: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case Postfix: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case Precision: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case ThousandsSep: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FormatTypeKey: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FloatFormatKey: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FloatColorKey: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case CurrencyFormat: { Currency currencyOne = static_cast*>(one)->value1; Currency currencyTwo = static_cast*>(two)->value1; if (currencyOne != currencyTwo) return false; return true; } case CustomFormat: return static_cast*>(one)->value1 == static_cast*>(two)->value1; // background case BackgroundBrush: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case BackgroundColor: return static_cast*>(one)->value1 == static_cast*>(two)->value1; // font case FontColor: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontFamily: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontSize: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontBold: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontItalic: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontStrike: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case FontUnderline: return static_cast*>(one)->value1 == static_cast*>(two)->value1; //misc case DontPrintText: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case NotProtected: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case HideAll: return static_cast*>(one)->value1 == static_cast*>(two)->value1; case HideFormula: return static_cast*>(one)->value1 == static_cast*>(two)->value1; default: return false; } } bool Style::operator==(const Style& other) const { if (other.isEmpty()) return isEmpty() ? true : false; const QSet keys = QSet::fromList(d->subStyles.keys() + other.d->subStyles.keys()); const QSet::ConstIterator end = keys.constEnd(); for (QSet::ConstIterator it = keys.constBegin(); it != end; ++it) { if (!compare(d->subStyles.value(*it).data(), other.d->subStyles.value(*it).data())) return false; } return true; } uint Calligra::Sheets::qHash(const Style& style) { uint hash = 0; foreach (const SharedSubStyle& ss, style.subStyles()) { hash ^= ss->koHash(); } return hash; } void Style::operator=(const Style & other) { d = other.d; } Style Style::operator-(const Style& other) const { Style style; const QSet keys = difference(other); const QSet::ConstIterator end = keys.constEnd(); for (QSet::ConstIterator it = keys.constBegin(); it != end; ++it) style.insertSubStyle(d->subStyles[*it]); return style; } void Style::merge(const Style& style) { const QList subStyles(style.subStyles()); // debugSheetsStyle <<"merging" << subStyles.count() <<" attributes."; for (int i = 0; i < subStyles.count(); ++i) { // debugSheetsStyle << subStyles[i]->debugData(); insertSubStyle(subStyles[i]); } } QSet Style::difference(const Style& other) const { QSet result; const QSet keys = QSet::fromList(d->subStyles.keys() + other.d->subStyles.keys()); const QSet::ConstIterator end = keys.constEnd(); for (QSet::ConstIterator it = keys.constBegin(); it != end; ++it) { if (!other.d->subStyles.contains(*it)) result.insert(*it); else if (d->subStyles.contains(*it)) { // both contain this key if (!compare(d->subStyles.value(*it).data(), other.d->subStyles.value(*it).data())) result.insert(*it); } } return result; } void Style::dump() const { for (int i = 0; i < subStyles().count(); ++i) subStyles()[i]->dump(); } QTextCharFormat Style::asCharFormat() const { QTextCharFormat format; format.setFont(font()); format.setFontWeight(bold() ? QFont::Bold : QFont::Normal); format.setFontItalic(italic()); format.setFontUnderline(underline()); format.setFontStrikeOut(strikeOut()); return format; } QList Style::subStyles() const { return d->subStyles.values(); } SharedSubStyle Style::createSubStyle(Key key, const QVariant& value) { SharedSubStyle newSubStyle; switch (key) { // special cases case DefaultStyleKey: newSubStyle = new SubStyle(); break; case NamedStyleKey: newSubStyle = new NamedStyle(value.value()); break; case LeftPen: newSubStyle = new BorderPenStyle(value.value()); break; case RightPen: newSubStyle = new BorderPenStyle(value.value()); break; case TopPen: newSubStyle = new BorderPenStyle(value.value()); break; case BottomPen: newSubStyle = new BorderPenStyle(value.value()); break; case FallDiagonalPen: newSubStyle = new BorderPenStyle(value.value()); break; case GoUpDiagonalPen: newSubStyle = new BorderPenStyle(value.value()); break; // layout case HorizontalAlignment: newSubStyle = new SubStyleOne((HAlign)value.value()); break; case VerticalAlignment: newSubStyle = new SubStyleOne((VAlign)value.value()); break; case MultiRow: newSubStyle = new SubStyleOne(value.value()); break; case VerticalText: newSubStyle = new SubStyleOne(value.value()); break; case Angle: newSubStyle = new SubStyleOne(value.value()); break; case Indentation: newSubStyle = new SubStyleOne(value.value()); break; case ShrinkToFit: newSubStyle = new SubStyleOne(value.value()); break; // content format case Prefix: newSubStyle = new SubStyleOne(value.value()); break; case Postfix: newSubStyle = new SubStyleOne(value.value()); break; case Precision: newSubStyle = new SubStyleOne(value.value()); break; case ThousandsSep: newSubStyle = new SubStyleOne(value.value()); break; case FormatTypeKey: newSubStyle = new SubStyleOne((Format::Type)value.value()); break; case FloatFormatKey: newSubStyle = new SubStyleOne((FloatFormat)value.value()); break; case FloatColorKey: newSubStyle = new SubStyleOne((FloatColor)value.value()); break; case CurrencyFormat: newSubStyle = new SubStyleOne(value.value()); break; case CustomFormat: newSubStyle = new SubStyleOne(value.value()); break; // background case BackgroundBrush: newSubStyle = new SubStyleOne(value.value()); break; case BackgroundColor: newSubStyle = new SubStyleOne(value.value()); break; // font case FontColor: newSubStyle = new SubStyleOne(value.value()); break; case FontFamily: newSubStyle = new SubStyleOne(value.value()); break; case FontSize: newSubStyle = new SubStyleOne(value.value()); break; case FontBold: newSubStyle = new SubStyleOne(value.value()); break; case FontItalic: newSubStyle = new SubStyleOne(value.value()); break; case FontStrike: newSubStyle = new SubStyleOne(value.value()); break; case FontUnderline: newSubStyle = new SubStyleOne(value.value()); break; //misc case DontPrintText: newSubStyle = new SubStyleOne(value.value()); break; case NotProtected: newSubStyle = new SubStyleOne(value.value()); break; case HideAll: newSubStyle = new SubStyleOne(value.value()); break; case HideFormula: newSubStyle = new SubStyleOne(value.value()); break; } return newSubStyle; } void Style::insertSubStyle(Key key, const QVariant& value) { const SharedSubStyle subStyle = createSubStyle(key, value); Q_ASSERT(!!subStyle); insertSubStyle(subStyle); } void Style::insertSubStyle(const SharedSubStyle& subStyle) { if (!subStyle) return; releaseSubStyle(subStyle->type()); d->subStyles.insert(subStyle->type(), subStyle); } bool Style::releaseSubStyle(Key key) { if (!d->subStyles.contains(key)) return false; d->subStyles.remove(key); return true; } ///////////////////////////////////////////////////////////////////////////// // // CustomStyle::Private // ///////////////////////////////////////////////////////////////////////////// class Q_DECL_HIDDEN CustomStyle::Private : public QSharedData { public: QString name; StyleType type; }; ///////////////////////////////////////////////////////////////////////////// // // CustomStyle // ///////////////////////////////////////////////////////////////////////////// CustomStyle::CustomStyle() : Style() , d(new Private) { d->name = "Default"; d->type = BUILTIN; setDefault(); } CustomStyle::CustomStyle(QString const & name, CustomStyle * parent) : Style() , d(new Private) { d->name = name; d->type = CUSTOM; if (parent) setParentName(parent->name()); } CustomStyle::CustomStyle(const CustomStyle& style) : Style(style), d(style.d) { } CustomStyle::~CustomStyle() { } CustomStyle& CustomStyle::operator=(const CustomStyle& style) { Style::operator=(style); d = style.d; return *this; } Style::StyleType CustomStyle::type() const { return d->type; } void CustomStyle::setType(StyleType type) { Q_ASSERT(type != AUTO); d->type = type; } const QString& CustomStyle::name() const { return d->name; } void CustomStyle::setName(QString const & name) { d->name = name; } QString CustomStyle::saveOdf(KoGenStyle& style, KoGenStyles &mainStyles, const StyleManager* manager) const { Q_ASSERT(!name().isEmpty()); // default style does not need display name if (!isDefault()) style.addAttribute("style:display-name", name()); // doing the real work QSet keysToStore; for (int i = 0; i < subStyles().count(); ++i) keysToStore.insert(subStyles()[i].data()->type()); saveOdfStyle(keysToStore, style, mainStyles, manager); if (isDefault()) { style.setDefaultStyle(true); // don't i18n'ize "Default" in this case return mainStyles.insert(style, "Default", KoGenStyles::DontAddNumberToName); } // this is a custom style return mainStyles.insert(style, "custom-style"); } void CustomStyle::loadOdf(KoOdfStylesReader& stylesReader, const KoXmlElement& style, const QString& name, Conditions& conditions, const StyleManager* styleManager, const ValueParser *parser) { setName(name); if (style.hasAttributeNS(KoXmlNS::style, "parent-style-name")) setParentName(style.attributeNS(KoXmlNS::style, "parent-style-name", QString())); setType(CUSTOM); Style::loadOdfStyle(stylesReader, style, conditions, styleManager, parser); } void CustomStyle::save(QDomDocument& doc, QDomElement& styles, const StyleManager* styleManager) { if (name().isEmpty()) return; QDomElement style(doc.createElement("style")); style.setAttribute("type", (int) type()); if (!parentName().isNull()) style.setAttribute("parent", parentName()); style.setAttribute("name", name()); QDomElement format(doc.createElement("format")); saveXML(doc, format, styleManager); style.appendChild(format); styles.appendChild(style); } bool CustomStyle::loadXML(KoXmlElement const & style, QString const & name) { setName(name); if (style.hasAttribute("parent")) setParentName(style.attribute("parent")); if (!style.hasAttribute("type")) return false; bool ok = true; setType((StyleType) style.attribute("type").toInt(&ok)); if (!ok) return false; KoXmlElement f(style.namedItem("format").toElement()); if (!f.isNull()) if (!Style::loadXML(f)) return false; return true; } int CustomStyle::usage() const { return d->ref; } diff --git a/sheets/Util.cpp b/sheets/Util.cpp index f1257829cf8..903d641dc87 100644 --- a/sheets/Util.cpp +++ b/sheets/Util.cpp @@ -1,846 +1,772 @@ /* This file is part of the KDE project Copyright 2006,2007 Stefan Nikolaus Copyright 1998,1999 Torben Weis 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. */ // Local #include "Util.h" #include #include #include #include #include "SheetsDebug.h" #include "Formula.h" #include "calligra_sheets_limits.h" #include "Localization.h" #include "Map.h" #include "NamedAreaManager.h" #include "Region.h" #include "Sheet.h" #include "Style.h" using namespace Calligra::Sheets; //used in Cell::encodeFormula and // dialogs/kspread_dlg_paperlayout.cc int Calligra::Sheets::Util::decodeColumnLabelText(const QString &labelText) { int col = 0; const int offset = 'a' - 'A'; int counterColumn = 0; const uint totalLength = labelText.length(); uint labelTextLength = 0; for ( ; labelTextLength < totalLength; labelTextLength++) { const char c = labelText[labelTextLength].toLatin1(); if (labelTextLength == 0 && c == '$') continue; // eat an absolute reference char that could be at the beginning only if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) break; } if (labelTextLength == 0) { warnSheets << "No column label text found for col:" << labelText; return 0; } for (uint i = 0; i < labelTextLength; i++) { const char c = labelText[i].toLatin1(); counterColumn = (int) ::pow(26.0 , static_cast(labelTextLength - i - 1)); if (c >= 'A' && c <= 'Z') col += counterColumn * (c - 'A' + 1); // okay here (Werner) else if (c >= 'a' && c <= 'z') col += counterColumn * (c - 'A' - offset + 1); } return col; } int Calligra::Sheets::Util::decodeRowLabelText(const QString &labelText) { QRegExp rx("(|\\$)([A-Za-z]+)(|\\$)([0-9]+)"); if(rx.exactMatch(labelText)) return rx.cap(4).toInt(); return 0; } QString Calligra::Sheets::Util::encodeColumnLabelText(int column) { return Cell::columnName(column); } bool Calligra::Sheets::Util::isCellReference(const QString &text, int startPos) { int length = text.length(); if (length < 1 || startPos >= length) return false; const QChar *data = text.constData(); if (startPos > 0) { data += startPos; } if (*data == QChar('$', 0)) { ++data; } bool letterFound = false; while (1) { if (data->isNull()) { return false; } ushort c = data->unicode(); if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) break; letterFound = true; ++data; } if (!letterFound) { return false; } if (*data == QChar('$', 0)) { ++data; } bool numberFound = false; while (!data->isNull()) { ushort c = data->unicode(); if (c < '0' || c > '9') break; numberFound = true; ++data; } return numberFound && data->isNull(); // we found the number and reached end } QDomElement Calligra::Sheets::NativeFormat::createElement(const QString & tagName, const QFont & font, QDomDocument & doc) { QDomElement e(doc.createElement(tagName)); e.setAttribute("family", font.family()); e.setAttribute("size", font.pointSize()); e.setAttribute("weight", font.weight()); if (font.bold()) e.setAttribute("bold", "yes"); if (font.italic()) e.setAttribute("italic", "yes"); if (font.underline()) e.setAttribute("underline", "yes"); if (font.strikeOut()) e.setAttribute("strikeout", "yes"); //e.setAttribute( "charset", KCharsets::charsets()->name( font ) ); return e; } QDomElement Calligra::Sheets::NativeFormat::createElement(const QString & tagname, const QPen & pen, QDomDocument & doc) { QDomElement e(doc.createElement(tagname)); e.setAttribute("color", pen.color().name()); e.setAttribute("style", (int)pen.style()); e.setAttribute("width", (int)pen.width()); return e; } QFont Calligra::Sheets::NativeFormat::toFont(KoXmlElement & element) { QFont f; f.setFamily(element.attribute("family")); bool ok; const int size = element.attribute("size").toInt(&ok); if (ok) f.setPointSize(size); const int weight = element.attribute("weight").toInt(&ok); if (!ok) f.setWeight(weight); if (element.hasAttribute("italic") && element.attribute("italic") == "yes") f.setItalic(true); if (element.hasAttribute("bold") && element.attribute("bold") == "yes") f.setBold(true); if (element.hasAttribute("underline") && element.attribute("underline") == "yes") f.setUnderline(true); if (element.hasAttribute("strikeout") && element.attribute("strikeout") == "yes") f.setStrikeOut(true); /* Uncomment when charset is added to kspread_dlg_layout + save a document-global charset if ( element.hasAttribute( "charset" ) ) KCharsets::charsets()->setQFont( f, element.attribute("charset") ); else */ // ######## Not needed anymore in 3.0? //KCharsets::charsets()->setQFont( f, KLocale::global()->charset() ); return f; } QPen Calligra::Sheets::NativeFormat::toPen(KoXmlElement & element) { bool ok; QPen p; p.setStyle((Qt::PenStyle)element.attribute("style").toInt(&ok)); if (!ok) return QPen(); p.setWidth(element.attribute("width").toInt(&ok)); if (!ok) return QPen(); p.setColor(QColor(element.attribute("color"))); return p; } bool util_isPointValid(const QPoint& point) { if (point.x() >= 0 && point.y() >= 0 && point.x() <= KS_colMax && point.y() <= KS_rowMax ) return true; else return false; } bool util_isRectValid(const QRect& rect) { if (util_isPointValid(rect.topLeft()) && util_isPointValid(rect.bottomRight()) ) return true; else return false; } //not used anywhere int Calligra::Sheets::Util::penCompare(QPen const & pen1, QPen const & pen2) { if (pen1.style() == Qt::NoPen && pen2.style() == Qt::NoPen) return 0; if (pen1.style() == Qt::NoPen) return -1; if (pen2.style() == Qt::NoPen) return 1; if (pen1.width() < pen2.width()) return -1; if (pen1.width() > pen2.width()) return 1; if (pen1.style() < pen2.style()) return -1; if (pen1.style() > pen2.style()) return 1; if (pen1.color().name() < pen2.color().name()) return -1; if (pen1.color().name() > pen2.color().name()) return 1; return 0; } QString Calligra::Sheets::Odf::convertRefToBase(const QString & sheet, const QRect & rect) { QPoint bottomRight(rect.bottomRight()); QString s = '$' + sheet + ".$" + Cell::columnName(bottomRight.x()) + '$' + QString::number(bottomRight.y()); return s; } QString Calligra::Sheets::Odf::convertRefToRange(const QString & sheet, const QRect & rect) { QPoint topLeft(rect.topLeft()); QPoint bottomRight(rect.bottomRight()); if (topLeft == bottomRight) return Odf::convertRefToBase(sheet, rect); QString s = '$' + sheet + ".$" + /*Util::encodeColumnLabelText*/Cell::columnName(topLeft.x()) + '$' + QString::number(topLeft.y()) + ":.$" + /*Util::encodeColumnLabelText*/Cell::columnName(bottomRight.x()) + '$' + QString::number(bottomRight.y()); return s; } // e.g.: Sheet4.A1:Sheet4.E28 //used in Sheet::saveOdf QString Calligra::Sheets::Odf::convertRangeToRef(const QString & sheetName, const QRect & _area) { return sheetName + '.' + Cell::name(_area.left(), _area.top()) + ':' + sheetName + '.' + Cell::name(_area.right(), _area.bottom()); } -QString Calligra::Sheets::Odf::encodePen(const QPen & pen) -{ -// debugSheets<<"encodePen( const QPen & pen ) :"<decimalSymbol() : "."; const QChar *data = expression.constData(); const QChar *start = data; if (data->isNull()) { return QString(); } int length = expression.length() * 2; QString result(length, QChar()); result.reserve(length); QChar * out = result.data(); QChar * outStart = result.data(); if (*data == QChar('=', 0)) { *out = *data; ++data; ++out; } const QChar *pos = data; while (!data->isNull()) { switch (state) { case Start: { if (data->isDigit()) { // check for number state = InNumber; *out++ = *data++; } else if (*data == QChar('.', 0)) { state = InNumber; *out = decimal[0]; ++out; ++data; } else if (isIdentifier(*data)) { // beginning with alphanumeric ? // could be identifier, cell, range, or function... state = InIdentifier; int i = data - start; const static QString errorTypeReplacement("ERRORTYPE"); const static QString legacyNormsdistReplacement("LEGACYNORMSDIST"); const static QString legacyNormsinvReplacement("LEGACYNORMSINV"); const static QString multipleOperations("MULTIPLE.OPERATIONS"); if (expression.midRef(i,10).compare(QLatin1String("ERROR.TYPE")) == 0) { // replace it int outPos = out - outStart; result.replace(outPos, 9, errorTypeReplacement); data += 10; // number of characters in "ERROR.TYPE" out += 9; } else if (expression.midRef(i, 12).compare(QLatin1String("LEGACY.NORMS")) == 0) { if (expression.midRef(i + 12, 4).compare(QLatin1String("DIST")) == 0) { // replace it int outPos = out - outStart; result.replace(outPos, 15, legacyNormsdistReplacement); data += 16; // number of characters in "LEGACY.NORMSDIST" out += 15; } else if (expression.midRef(i + 12, 3).compare(QLatin1String("INV")) == 0) { // replace it int outPos = out - outStart; result.replace(outPos, 14, legacyNormsinvReplacement); data += 15; // number of characters in "LEGACY.NORMSINV" out += 14; } } else if (namespacePrefix == "oooc:" && expression.midRef(i, 5).compare(QLatin1String("TABLE")) == 0 && !isIdentifier(expression[i+5])) { int outPos = out - outStart; result.replace(outPos, 19, multipleOperations); data += 5; out += 19; } else if (expression.midRef(i, 3).compare(QLatin1String("NEG")) == 0) { *out = QChar('-', 0); data += 3; ++out; } } else { switch (data->unicode()) { case '"': // a string ? state = InString; *out++ = *data++; break; case '[': // [ marks sheet name for 3-d cell, e.g ['Sales Q3'.A4] state = InReference; ++data; // NOTE: As long as Calligra::Sheets does not support fixed sheets eat the dollar sign. if (*data == QChar('$', 0)) { ++data; } pos = data; break; default: const QChar *operatorStart = data; if (!parseOperator(data, out)) { *out++ = *data++; } else if (*operatorStart == QChar('=', 0) && data - operatorStart == 1) { // only one = *out++ = QChar('=', 0); } break; } } } break; case InNumber: if (data->isDigit()) { *out++ = *data++; } else if (*data == QChar('.', 0)) { const QChar *decimalChar = decimal.constData(); while (!decimalChar->isNull()) { *out++ = *decimalChar++; } ++data; } else if (*data == QChar('E', 0) || *data == QChar('e', 0)) { *out++ = QChar('E', 0); ++data; } else { state = Start; } break; case InString: if (*data == QChar('"', 0)) { state = Start; } *out++ = *data++; break; case InIdentifier: { if (isIdentifier(*data) || data->isDigit()) { *out++ = *data++; } else { state = Start; } } break; case InReference: switch (data->unicode()) { case ']': Region::loadOdf(pos, data, out); pos = data; state = Start; break; case '\'': state = InSheetName; break; default: break; } ++data; break; case InSheetName: if (*data == QChar('\'', 0)) { ++data; if (!data->isNull() && *data == QChar('\'', 0)) { ++data; } else { state = InReference; } } else { ++data; } break; } } result.resize(out - outStart); return result; } QString Calligra::Sheets::Odf::encodeFormula(const QString& expr, const KLocale* locale) { // use locale settings const QString decimal = locale ? locale->decimalSymbol() : "."; QString result('='); Formula formula; Tokens tokens = formula.scan(expr, locale); if (!tokens.valid() || tokens.count() == 0) return expr; // no altering on error for (int i = 0; i < tokens.count(); ++i) { const QString tokenText = tokens[i].text(); const Token::Type type = tokens[i].type(); switch (type) { case Token::Cell: case Token::Range: { result.append('['); // FIXME Stefan: Hack to get the apostrophes right. Fix and remove! const int pos = tokenText.lastIndexOf('!'); if (pos != -1 && tokenText.left(pos).contains(' ')) result.append(Region::saveOdf('\'' + tokenText.left(pos) + '\'' + tokenText.mid(pos))); else result.append(Region::saveOdf(tokenText)); result.append(']'); break; } case Token::Float: { QString tmp(tokenText); result.append(tmp.replace(decimal, ".")); break; } case Token::Operator: { if (tokens[i].asOperator() == Token::Equal) result.append('='); else result.append(tokenText); break; } case Token::Identifier: { if (tokenText == "ERRORTYPE") { // need to replace this result.append("ERROR.TYPE"); } else if (tokenText == "LEGACYNORMSDIST") { result.append("LEGACY.NORMSDIST"); } else if (tokenText == "LEGACYNORMSINV") { result.append("LEGACY.NORMSINV"); } else { // dump it out unchanged result.append(tokenText); } break; } case Token::Boolean: case Token::Integer: case Token::String: default: result.append(tokenText); break; } } return result; } static bool isCellnameCharacter(const QChar &c) { return c.isDigit() || c.isLetter() || c == '$'; } static void replaceFormulaReference(int referencedRow, int referencedColumn, int thisRow, int thisColumn, QString &result, int cellReferenceStart, int cellReferenceLength) { const QString ref = result.mid(cellReferenceStart, cellReferenceLength); QRegExp rx("(|\\$)[A-Za-z]+(|\\$)[0-9]+"); if (rx.exactMatch(ref)) { int c = Calligra::Sheets::Util::decodeColumnLabelText(ref); int r = Calligra::Sheets::Util::decodeRowLabelText(ref); if (rx.cap(1) != "$") // absolute or relative column? c += thisColumn - referencedColumn; if (rx.cap(2) != "$") // absolute or relative row? r += thisRow - referencedRow; result.replace(cellReferenceStart, cellReferenceLength, rx.cap(1) + Calligra::Sheets::Util::encodeColumnLabelText(c) + rx.cap(2) + QString::number(r) ); } } QString Calligra::Sheets::Util::adjustFormulaReference(const QString& formula, int referencedRow, int referencedColumn, int thisRow, int thisColumn) { QString result = formula; if (result.isEmpty()) return QString(); enum { InStart, InCellReference, InString, InSheetOrAreaName } state; state = InStart; int cellReferenceStart = 0; for(int i = 1; i < result.length(); ++i) { QChar ch = result[i]; switch (state) { case InStart: if (ch == '"') state = InString; else if (ch.unicode() == '\'') state = InSheetOrAreaName; else if (isCellnameCharacter(ch)) { state = InCellReference; cellReferenceStart = i; } break; case InString: if (ch == '"') state = InStart; break; case InSheetOrAreaName: if (ch == '\'') state = InStart; break; case InCellReference: if (!isCellnameCharacter(ch)) { // We need to update cell-references according to the position of the referenced cell and this // cell. This means that if the referenced cell is for example at C5 and contains the formula // "=SUM(K22)" and if thisCell is at E6 then thisCell will get the formula "=SUM(L23)". if (ch != '(') /* skip formula-names */ { replaceFormulaReference(referencedRow, referencedColumn, thisRow, thisColumn, result, cellReferenceStart, i - cellReferenceStart); } state = InStart; --i; // decrement again to handle the current char in the InStart-switch. } break; }; } if(state == InCellReference) { replaceFormulaReference(referencedRow, referencedColumn, thisRow, thisColumn, result, cellReferenceStart, result.length() - cellReferenceStart); } return result; } QString Calligra::Sheets::MSOOXML::convertFormula(const QString& formula) { if (formula.isEmpty()) return QString(); enum { InStart, InArguments, InParenthesizedArgument, InString, InSheetOrAreaName, InCellReference } state; state = InStart; int cellReferenceStart = 0; int sheetOrAreaNameDelimiterCount = 0; QString result = formula.startsWith('=') ? formula : '=' + formula; for(int i = 1; i < result.length(); ++i) { QChar ch = result[i]; switch (state) { case InStart: if(ch == '(') state = InArguments; break; case InArguments: if (ch == '"') state = InString; else if (ch.unicode() == '\'') { sheetOrAreaNameDelimiterCount = 1; for(int j = i + 1; j < result.length(); ++j) { if (result[j].unicode() != '\'') break; ++sheetOrAreaNameDelimiterCount; } if (sheetOrAreaNameDelimiterCount >= 2) result.remove(i + 1, sheetOrAreaNameDelimiterCount - 1); state = InSheetOrAreaName; } else if (isCellnameCharacter(ch)) { state = InCellReference; cellReferenceStart = i; } else if (ch == ',') result[i] = ';'; // replace argument delimiter else if (ch == '(' && !result[i-1].isLetterOrNumber()) state = InParenthesizedArgument; else if (ch == ' ') { // check if it might be an intersection operator // for it to be an intersection operator the next non-space char must be a cell-name-character or ' // and previous converted char cannot be ';' int firstNonSpace = i+1; while (firstNonSpace < result.length() && result[firstNonSpace] == ' ') { firstNonSpace++; } bool wasDelimeter = (i-1 > 0) && (result[i-1] == ';'); bool isIntersection = !wasDelimeter && firstNonSpace < result.length() && (result[firstNonSpace].isLetter() || result[firstNonSpace] == '$' || result[firstNonSpace] == '\''); if (isIntersection) { result[i] = '!'; i = firstNonSpace-1; } } break; case InParenthesizedArgument: if (ch == ',') result[i] = '~'; // union operator else if (ch == ' ') result[i] = '!'; // intersection operator else if (ch == ')') state = InArguments; break; case InString: if (ch == '"') state = InArguments; break; case InSheetOrAreaName: Q_ASSERT( i >= 1 ); if (ch == '\'' && result[i - 1].unicode() != '\\') { int count = 1; for(int j = i + 1; count < sheetOrAreaNameDelimiterCount && j < result.length(); ++j) { if (result[j].unicode() != '\'') break; ++count; } if (count == sheetOrAreaNameDelimiterCount) { if (sheetOrAreaNameDelimiterCount >= 2) result.remove(i + 1, sheetOrAreaNameDelimiterCount - 1); state = InArguments; } else { result.insert(i, '\''); ++i; } } break; case InCellReference: if (!isCellnameCharacter(ch)) { if (ch != '(') /* skip formula-names */ { // Excel is able to use only the column-name to define a column // where all rows are selected. Since that is not supproted in // ODF we add to such definitions the minimum/maximum row-number. // So, something like "A:B" would become "A$1:B$65536". Note that // such whole column-definitions are only allowed for ranges like // "A:B" but not for single column definitions like "A" or "B". const QString ref = result.mid(qMax(0, cellReferenceStart - 1), i - cellReferenceStart + 2); QRegExp rxStart(".*(|\\$)[A-Za-z]+\\:"); QRegExp rxEnd("\\:(|\\$)[A-Za-z]+(|(|\\$)[0-9]+).*"); if (rxEnd.exactMatch(ref) && rxEnd.cap(2).isEmpty()) { result.insert(i, "$65536"); i += 6; } else if (rxStart.exactMatch(ref)) { result.insert(i, "$1"); i += 2; } } state = InArguments; --i; // decrement again to handle the current char in the InArguments-switch. } break; }; }; return result; } diff --git a/sheets/Util.h b/sheets/Util.h index 0a334e0e1f3..706c4c49cc5 100644 --- a/sheets/Util.h +++ b/sheets/Util.h @@ -1,193 +1,179 @@ /* This file is part of the KDE project Copyright 2006,2007 Stefan Nikolaus Copyright 1998,1999 Torben Weis 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 CALLIGRA_SHEETS_UTIL #define CALLIGRA_SHEETS_UTIL #include #include #include #include "sheets_odf_export.h" #include "Global.h" #include "Value.h" class QFont; class QPen; class QDomElement; class QDomDocument; class KLocale; bool util_isPointValid(const QPoint& point); bool util_isRectValid(const QRect& rect); namespace Calligra { namespace Sheets { class Cell; class Sheet; namespace Util { /** * Call this function to decode the text of a column label to an integer, * e.g. 1 for A and 27 for AA. * Converted are all characters matching [A-Za-z]+ regular expression, the rest is ignored. * 0 is returned if no characters match. */ CALLIGRA_SHEETS_ODF_EXPORT int decodeColumnLabelText(const QString &labelText); /** * Call this function to decode the text of a row label to an integer, * e.g. B7 is translated to 7. */ CALLIGRA_SHEETS_ODF_EXPORT int decodeRowLabelText(const QString &labelText); /** * Call this function to encode an integer to the text of the column label * i.e. 27->AA */ CALLIGRA_SHEETS_ODF_EXPORT QString encodeColumnLabelText(int column); /** * Returns true if the given text is a cell-reference. * * This is an optimized version of QRegExp("^(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$") * to check if the given string is a cell-reference like $A$1 or D17. Note * that this will return false for cell-ranges like A1:B2 or cell-references * given with sheet-name like Sheet1:A1. * * @param text The text to check * @param startPos The position in the string where we should start to check */ CALLIGRA_SHEETS_ODF_EXPORT bool isCellReference(const QString &text, int startPos = 0); /** * Generate and return the ODF formula for this cell (\p thisRow, \p thisColumn) based on the formula in the * defined cell (\p referencedRow, \p referencedColumn ). */ CALLIGRA_SHEETS_ODF_EXPORT QString adjustFormulaReference(const QString& formula, int referencedRow, int referencedColumn, int thisRow, int thisColumn); //Return true when it's a reference to cell from sheet. CALLIGRA_SHEETS_ODF_EXPORT bool localReferenceAnchor(const QString &_ref); // TODO Stefan: used nowhere int penCompare(QPen const & pen1, QPen const & pen2); } /** * \ingroup NativeFormat * This namespace collects methods related to old KSpread file format * encoding/decoding. */ namespace NativeFormat { /** * \ingroup NativeFormat */ QDomElement createElement(const QString & tagName, const QFont & font, QDomDocument & doc); /** * \ingroup NativeFormat */ QDomElement createElement(const QString & tagname, const QPen & pen, QDomDocument & doc); /** * \ingroup NativeFormat */ QFont toFont(KoXmlElement & element); /** * \ingroup NativeFormat */ QPen toPen(KoXmlElement & element); } /** * \ingroup OpenDocument * This namespace collects methods related to OpenDocument * encoding/decoding. */ namespace Odf { -/** - * \ingroup OpenDocument - * Creates OpenDocument pen attributes of the QPen \p pen . - * \return the OpenDocument pen attributes - */ -QString encodePen(const QPen& pen); - -/** - * \ingroup OpenDocument - * Creates a QPen of OpenDocument pen attributes \p str . - * \return the created QPen - */ -QPen decodePen(const QString &str); - /** * \ingroup OpenDocument * Converts an OpenDocument representation of a formula to a localized formula. * @param expr The expression to convert from OpenDocument format. * @param locale The locale to which the expression should be converted. * \note Use Region::loadOdf() for plain cell references. */ // TODO check visibility CALLIGRA_SHEETS_ODF_EXPORT QString decodeFormula(const QString& expression_, const KLocale *locale = 0, const QString &namespacePrefix = QString()); /** * \ingroup OpenDocument * Converts a localized formula to an OpenDocument representation of a formula. * @param expr The expression to convert to OpenDocument format. * @param locale The locale from which the expression should be converted. * \note Use Region::saveOdf() for plain cell references. */ CALLIGRA_SHEETS_ODF_EXPORT QString encodeFormula(const QString& expr, const KLocale* locale = 0); /** * \ingroup OpenDocument */ CALLIGRA_SHEETS_ODF_EXPORT QString convertRefToRange(const QString & sheet, const QRect & rect); /** * \ingroup OpenDocument */ CALLIGRA_SHEETS_ODF_EXPORT QString convertRefToBase(const QString & sheet, const QRect & rect); /** * \ingroup OpenDocument */ CALLIGRA_SHEETS_ODF_EXPORT QString convertRangeToRef(const QString & sheetName, const QRect & _area); } namespace MSOOXML { /** * Convert the MSOOXML \p formula into a ODF formula and return that ODF formula. */ CALLIGRA_SHEETS_ODF_EXPORT QString convertFormula(const QString& formula); } } // namespace Sheets } // namespace Calligra #endif // CALLIGRA_SHEETS_UTIL diff --git a/sheets/odf/SheetsOdfCell.cpp b/sheets/odf/SheetsOdfCell.cpp index ffc428a3921..da16d6922fa 100644 --- a/sheets/odf/SheetsOdfCell.cpp +++ b/sheets/odf/SheetsOdfCell.cpp @@ -1,45 +1,979 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis 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 "SheetsOdf.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Cell.h" +#include "CellStorage.h" +#include "Condition.h" +#include "Map.h" +#include "Sheet.h" +#include "Util.h" +#include "Value.h" +#include "ValueFormatter.h" +#include "GenValidationStyle.h" +#include "OdfLoadingContext.h" +#include "OdfSavingContext.h" +#include "ShapeApplicationData.h" + // This file contains functionality to load/save a Cell namespace Calligra { namespace Sheets { +namespace Odf { + bool loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& tableContext, + const Styles& autoStyles, const QString& cellStyleName, + QList& shapeData); + bool saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext); + + // cell loading - helper functions + void loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName); + QString loadCellTextNodes(Cell *cell, const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace); + void loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& tableContext, QList& shapeData); + ShapeLoadingData loadObject(Cell *cell, const KoXmlElement &element, KoShapeLoadingContext &shapeContext); + + // cell saving - helper functions + QString saveCellStyle(Cell *cell, KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles); + void saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter); + void saveCellValue(Cell *cell, KoXmlWriter &xmlWriter); +} + +// *************** Loading ***************** +bool Odf::loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& tableContext, + const Styles& autoStyles, const QString& cellStyleName, + QList& shapeData) +{ + static const QString sFormula = QString::fromLatin1("formula"); + static const QString sValidationName = QString::fromLatin1("validation-name"); + static const QString sValueType = QString::fromLatin1("value-type"); + static const QString sBoolean = QString::fromLatin1("boolean"); + static const QString sBooleanValue = QString::fromLatin1("boolean-value"); + static const QString sTrue = QString::fromLatin1("true"); + static const QString sFalse = QString::fromLatin1("false"); + static const QString sFloat = QString::fromLatin1("float"); + static const QString sValue = QString::fromLatin1("value"); + static const QString sCurrency = QString::fromLatin1("currency"); + static const QString sPercentage = QString::fromLatin1("percentage"); + static const QString sDate = QString::fromLatin1("date"); + static const QString sDateValue = QString::fromLatin1("date-value"); + static const QString sTime = QString::fromLatin1("time"); + static const QString sTimeValue = QString::fromLatin1("time-value"); + static const QString sString = QString::fromLatin1("string"); + static const QString sStringValue = QString::fromLatin1("string-value"); + static const QString sNumberColumnsSpanned = QString::fromLatin1("number-columns-spanned"); + static const QString sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned"); + static const QString sAnnotation = QString::fromLatin1("annotation"); + static const QString sP = QString::fromLatin1("p"); + + static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" << "of:" << "msoxl:"; + + //Search and load each paragraph of text. Each paragraph is separated by a line break. + loadCellText(cell, element, tableContext, autoStyles, cellStyleName); + + // + // formula + // + bool isFormula = false; + if (element.hasAttributeNS(KoXmlNS::table, sFormula)) { + isFormula = true; + QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, QString())); + // debugSheetsODF << "cell:" << name() << "formula :" << oasisFormula; + // each spreadsheet application likes to safe formulas with a different namespace + // prefix, so remove all of them + QString namespacePrefix; + foreach(const QString &prefix, formulaNSPrefixes) { + if (oasisFormula.startsWith(prefix)) { + oasisFormula.remove(0, prefix.length()); + namespacePrefix = prefix; + break; + } + } + oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix); + cell->setUserInput(oasisFormula); + } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to the text to avoid = to be painted + cell->setUserInput(userInput().prepend('\'')); + + // + // validation + // + if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) { + const QString validationName = element.attributeNS(KoXmlNS::table, sValidationName, QString()); + debugSheetsODF << "cell:" << name() << sValidationName << validationName; + Validity validity; +#warning use new odf + validity.loadOdfValidation(this, validationName, tableContext); + if (!validity.isEmpty()) + setValidity(validity); + } + + // + // value type + // + if (element.hasAttributeNS(KoXmlNS::office, sValueType)) { + const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, QString()); + // debugSheetsODF << "cell:" << name() << "value-type:" << valuetype; + if (valuetype == sBoolean) { + const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, QString()).toLower(); + if ((val == sTrue) || (val == sFalse)) + cell->setValue(Value(val == sTrue)); + } + + // integer and floating-point value + else if (valuetype == sFloat) { + bool ok = false; + Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); + if (ok) { + value.setFormat(Value::fmt_Number); + cell->setValue(value); +#if 0 + Style style; + style.setFormatType(Format::Number); + cell->setStyle(style); +#endif + } + // always set the userInput to the actual value read from the cell, and not whatever happens to be set as text, as the textual representation of a value may be less accurate than the value itself + if (!isFormula) + cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString()); + } + + // currency value + else if (valuetype == sCurrency) { + bool ok = false; + Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); + if (ok) { + value.setFormat(Value::fmt_Money); + cell->setValue(value); + + Currency currency; + if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) { + currency = Currency(element.attributeNS(KoXmlNS::office, sCurrency, QString())); + } + /* TODO: somehow make this work again, all setStyle calls here will be overwritten by cell styles later + if( style.isEmpty() ) { + Style style; + style.setCurrency(currency); + setStyle(style); + } */ + } + } else if (valuetype == sPercentage) { + bool ok = false; + Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); + if (ok) { + value.setFormat(Value::fmt_Percent); + cell->setValue(value); + if (!isFormula && cell->userInput().isEmpty()) + cell->setUserInput(cell->sheet()->map()->converter()->asString(value).asString()); +// FIXME Stefan: Should be handled by Value::Format. Verify and remove! +#if 0 + Style style; + style.setFormatType(Format::Percentage); + setStyle(style); +#endif + } + } else if (valuetype == sDate) { + QString value = element.attributeNS(KoXmlNS::office, sDateValue, QString()); + + // "1980-10-15" or "2001-01-01T19:27:41" + int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0; + bool hasTime = false; + bool ok = false; + + int p1 = value.indexOf('-'); + if (p1 > 0) { + year = value.left(p1).toInt(&ok); + if (ok) { + int p2 = value.indexOf('-', ++p1); + month = value.mid(p1, p2 - p1).toInt(&ok); + if (ok) { + // the date can optionally have a time attached + int p3 = value.indexOf('T', ++p2); + if (p3 > 0) { + hasTime = true; + day = value.mid(p2, p3 - p2).toInt(&ok); + if (ok) { + int p4 = value.indexOf(':', ++p3); + hours = value.mid(p3, p4 - p3).toInt(&ok); + if (ok) { + int p5 = value.indexOf(':', ++p4); + minutes = value.mid(p4, p5 - p4).toInt(&ok); + if (ok) + seconds = value.right(value.length() - p5 - 1).toInt(&ok); + } + } + } else { + day = value.right(value.length() - p2).toInt(&ok); + } + } + } + } + + if (ok) { + if (hasTime) + cell->setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, minutes, seconds)), cell->sheet()->map()->calculationSettings())); + else + cell->setValue(Value(QDate(year, month, day), cell->sheet()->map()->calculationSettings())); +// FIXME Stefan: Should be handled by Value::Format. Verify and remove! +//Sebsauer: Fixed now. Value::Format handles it correct. +#if 0 + Style s; + s.setFormatType(Format::ShortDate); + setStyle(s); +#endif + // debugSheetsODF << "cell:" << name() << "Type: date, value:" << value << "Date:" << year << " -" << month << " -" << day; + } + } else if (valuetype == sTime) { + QString value = element.attributeNS(KoXmlNS::office, sTimeValue, QString()); + + // "PT15H10M12S" + int hours = 0, minutes = 0, seconds = 0; + int l = value.length(); + QString num; + bool ok = false; + for (int i = 0; i < l; ++i) { + if (value[i].isNumber()) { + num += value[i]; + continue; + } else if (value[i] == 'H') + hours = num.toInt(&ok); + else if (value[i] == 'M') + minutes = num.toInt(&ok); + else if (value[i] == 'S') + seconds = num.toInt(&ok); + else + continue; + //debugSheetsODF << "Num:" << num; + num.clear(); + if (!ok) + break; + } + + if (ok) { + // Value kval( timeToNum( hours, minutes, seconds ) ); + // cell->setValue( kval ); + cell->setValue(Value(QTime(hours % 24, minutes, seconds))); +// FIXME Stefan: Should be handled by Value::Format. Verify and remove! +#if 0 + Style style; + style.setFormatType(Format::Time); + setStyle(style); +#endif + // debugSheetsODF << "cell:" << name() << "Type: time:" << value << "Hours:" << hours << "," << minutes << "," << seconds; + } + } else if (valuetype == sString) { + if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) { + QString value = element.attributeNS(KoXmlNS::office, sStringValue, QString()); + cell->setValue(Value(value)); + } else { + // use the paragraph(s) read in before + cell->setValue(Value(cell->userInput())); + } +// FIXME Stefan: Should be handled by Value::Format. Verify and remove! +#if 0 + Style style; + style.setFormatType(Format::Text); + setStyle(style); +#endif + } else { + // debugSheetsODF << "cell:" << name() << " Unknown type. Parsing user input."; + // Set the value by parsing the user input. + cell->parseUserInput(cell->userInput()); + } + } else { // no value-type attribute + // debugSheetsODF << "cell:" << name() << " No value type specified. Parsing user input."; + // Set the value by parsing the user input. + cell->parseUserInput(cell->userInput()); + } + + // + // merged cells ? + // + int colSpan = 1; + int rowSpan = 1; + if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) { + bool ok = false; + int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, QString()).toInt(&ok); + if (ok) colSpan = span; + } + if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) { + bool ok = false; + int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, QString()).toInt(&ok); + if (ok) rowSpan = span; + } + if (colSpan > 1 || rowSpan > 1) + mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1); + + // + // cell comment/annotation + // + KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, sAnnotation); + if (!annotationElement.isNull()) { + QString comment; + KoXmlNode node = annotationElement.firstChild(); + while (!node.isNull()) { + KoXmlElement commentElement = node.toElement(); + if (!commentElement.isNull()) + if (commentElement.localName() == sP && commentElement.namespaceURI() == KoXmlNS::text) { + if (!comment.isEmpty()) comment.append('\n'); + comment.append(commentElement.text()); + } + + node = node.nextSibling(); + } + if (!comment.isEmpty()) + setComment(comment); + } + + loadObjects(cell, element, tableContext, shapeData); + + return true; +} + +bool Odf::saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext) +{ + KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter(); + KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles(); + + int row = cell->row(); + int column = cell->column(); + + // see: OpenDocument, 8.1.3 Table Cell + if (!isPartOfMerged()) + xmlwriter.startElement("table:table-cell"); + else + xmlwriter.startElement("table:covered-table-cell"); +#if 0 + //add font style + QFont font; + Value const value(cell.value()); + if (!cell.isDefault()) { + font = cell.format()->textFont(i, row); + m_styles.addFont(font); + + if (cell.format()->hasProperty(Style::SComment)) + hasComment = true; + } +#endif + // NOTE save the value before the style as long as the Formatter does not work correctly + if (cell->link().isEmpty()) + saveCellValue(cell, xmlwriter); + + const Style cellStyle = cell->style(); + + // Either there's no column and row default and the style's not the default style, + // or the style is different to one of them. The row default takes precedence. + if ((!tableContext.rowDefaultStyles.contains(row) && + !tableContext.columnDefaultStyles.contains(column) && + !(cellStyle.isDefault() && conditions().isEmpty())) || + (tableContext.rowDefaultStyles.contains(row) && tableContext.rowDefaultStyles[row] != cellStyle) || + (tableContext.columnDefaultStyles.contains(column) && tableContext.columnDefaultStyles[column] != cellStyle)) { + KoGenStyle currentCellStyle; // the type determined in saveCellStyle + QString styleName = saveCellStyle(currentCellStyle, mainStyles); + // skip 'table:style-name' attribute for the default style + if (!currentCellStyle.isDefaultStyle()) { + if (!styleName.isEmpty()) + xmlwriter.addAttribute("table:style-name", styleName); + } + } + + // group empty cells with the same style + const QString comment = cell->comment(); + if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() && + !tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) { + bool refCellIsDefault = isDefault(); + int j = column + 1; + Cell nextCell = cell->sheet()->cellStorage()->nextInRow(column, row); + while (!nextCell.isNull()) { + // if + // the next cell is not the adjacent one + // or + // the next cell is not empty + if (nextCell.column() != j || (!nextCell.isEmpty() || tableContext.cellHasAnchoredShapes(cell->sheet(), row, column))) { + if (refCellIsDefault) { + // if the origin cell was a default cell, + // we count the default cells + repeated = nextCell.column() - j + 1; + + // check if any of the empty/default cells we skipped contained anchored shapes + int shapeColumn = tableContext.nextAnchoredShape(cell->sheet(), row, column); + if (shapeColumn) { + repeated = qMin(repeated, shapeColumn - column); + } + } + // otherwise we just stop here to process the adjacent + // cell in the next iteration of the outer loop + // (in saveCells) + break; + } + + if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() || + !nextCell.comment().isEmpty() || tableContext.cellHasAnchoredShapes(cell->sheet(), row, nextCell.column()) || + !(nextCell.style() == cellStyle && nextCell.conditions() == conditions())) { + break; + } + ++repeated; + // get the next cell and set the index to the adjacent cell + nextCell = cell->sheet()->cellStorage()->nextInRow(j++, row); + } + //debugSheetsODF << "Odf::saveCell: empty cell in column" << column + //<< "repeated" << repeated << "time(s)" << endl; + + if (repeated > 1) + xmlwriter.addAttribute("table:number-columns-repeated", QString::number(repeated)); + } + + Validity validity = cell->validity(); + if (!validity.isEmpty()) { + GenValidationStyle styleVal(&validity, cell->sheet()->map()->converter()); + xmlwriter.addAttribute("table:validation-name", tableContext.valStyle.insert(styleVal)); + } + if (cell->isFormula()) { + //debugSheetsODF <<"Formula found"; + QString formula = Odf::encodeFormula(userInput(), locale()); + xmlwriter.addAttribute("table:formula", formula); + } + + if (doesMergeCells()) { + int colSpan = mergedXCells() + 1; + int rowSpan = mergedYCells() + 1; + + if (colSpan > 1) + xmlwriter.addAttribute("table:number-columns-spanned", QString::number(colSpan)); + + if (rowSpan > 1) + xmlwriter.addAttribute("table:number-rows-spanned", QString::number(rowSpan)); + } + + saveCellAnnotation(cell, xmlwriter); + + if (!cell->isFormula() && !link().isEmpty()) { + //debugSheetsODF<<"Link found"; + xmlwriter.startElement("text:p"); + xmlwriter.startElement("text:a"); + const QString url = link(); + //Reference cell is started by '#' + if (Util::localReferenceAnchor(url)) + xmlwriter.addAttribute("xlink:href", ('#' + url)); + else + xmlwriter.addAttribute("xlink:href", url); + xmlwriter.addAttribute("xlink:type", "simple"); + xmlwriter.addTextNode(userInput()); + xmlwriter.endElement(); + xmlwriter.endElement(); + } + + if (!isEmpty() && link().isEmpty()) { + QSharedPointer doc = richText(); + if (doc) { + QTextCharFormat format = style().asCharFormat(); + ((KoCharacterStyle *)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); + + KoTextWriter writer(tableContext.shapeContext); + + writer.write(doc.data(), 0); + } else { + xmlwriter.startElement("text:p"); + xmlwriter.addTextNode(displayText().toUtf8()); + xmlwriter.endElement(); + } + } + + // flake + // Save shapes that are anchored to this cell. + // see: OpenDocument, 2.3.1 Text Documents + // see: OpenDocument, 9.2 Drawing Shapes + if (tableContext.cellHasAnchoredShapes(cell->sheet(), row, column)) { + const QList shapes = tableContext.cellAnchoredShapes(cell->sheet(), row, column); + for (int i = 0; i < shapes.count(); ++i) { + KoShape* const shape = shapes[i]; + const QPointF bottomRight = shape->boundingRect().bottomRight(); + qreal endX = 0.0; + qreal endY = 0.0; + const int scol = cell->sheet()->leftColumn(bottomRight.x(), endX); + const int srow = cell->sheet()->topRow(bottomRight.y(), endY); + qreal offsetX = cell->sheet()->columnPosition(column); + qreal offsetY = cell->sheet()->rowPosition(row); + tableContext.shapeContext.addShapeOffset(shape, QTransform::fromTranslate(-offsetX, -offsetY)); +#warning use new odf + shape->setAdditionalAttribute("table:end-cell-address", Region(QPoint(scol, srow)).saveOdf()); + shape->setAdditionalAttribute("table:end-x", QString::number(bottomRight.x() - endX) + "pt"); + shape->setAdditionalAttribute("table:end-y", QString::number(bottomRight.y() - endY) + "pt"); + shape->saveOdf(tableContext.shapeContext); + shape->removeAdditionalAttribute("table:end-cell-address"); + shape->removeAdditionalAttribute("table:end-x"); + shape->removeAdditionalAttribute("table:end-y"); + tableContext.shapeContext.removeShapeOffset(shape); + } + } + + xmlwriter.endElement(); + return true; +} + + +// loading - helper functions + +QString Odf::loadCellTextNodes(Cell *cell, const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace) +{ + QString cellText; + bool countedOwnFragments = false; + bool prevWasText = false; + for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { + if (n.isText()) { + prevWasText = true; + QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), *stripLeadingSpace); + if (!t.isEmpty()) { + *stripLeadingSpace = t[t.length() - 1].isSpace(); + cellText += t; + if (!countedOwnFragments) { + // We only count the number of different parent elements which have text. That is + // so cause different parent-elements may mean different styles which means + // rich-text while the same parent element means the same style so we can easily + // put them together into one string. + countedOwnFragments = true; + ++(*textFragmentCount); + } + } + } else { + KoXmlElement e = n.toElement(); + if (!e.isNull()) { + if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() - 1].isSpace()) { + // A trailing space of the cellText collected so far needs to be preserved when + // more text-nodes within the same parent follow but if an element like e.g. + // text:s follows then a trailing space needs to be removed. + cellText.chop(1); + } + prevWasText = false; + + // We can optimize some elements like text:s (space), text:tab (tabulator) and + // text:line-break (new-line) to not produce rich-text but add the equivalent + // for them in plain-text. + const bool isTextNs = e.namespaceURI() == KoXmlNS::text; + if (isTextNs && e.localName() == "s") { + const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c", QString()).toInt()); + cellText += QString().fill(32, howmany); + } else if (isTextNs && e.localName() == "tab") { + cellText += '\t'; + } else if (isTextNs && e.localName() == "line-break") { + cellText += '\n'; + ++(*lineCount); + } else if (isTextNs && e.localName() == "span") { + // Nested span-elements means recursive evaluation. + cellText += loadCellTextNodes(cell, e, textFragmentCount, lineCount, hasRichText, stripLeadingSpace); + } else if (!isTextNs || + ( e.localName() != "annotation" && + e.localName() != "bookmark" && + e.localName() != "meta" && + e.localName() != "tag" )) { + // Seems we have an element we cannot easily translate to a string what + // means it's all rich-text now. + *hasRichText = true; + } + } + } + } + return cellText; +} + +// recursively goes through all children of parent and returns true if there is any element +// in the draw: namespace in this subtree +static bool findDrawElements(const KoXmlElement& parent) +{ + KoXmlElement element; + forEachElement(element , parent) { + if (element.namespaceURI() == KoXmlNS::draw) + return true; + if (findDrawElements(element)) + return true; + } + return false; +} + +// Similar to KoXml::namedItemNS except that children of span tags will be evaluated too. +static KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const QString &localName) +{ + KoXmlNode n = node.firstChild(); + for (; !n.isNull(); n = n.nextSibling()) { + if (n.isElement()) { + if (n.localName() == localName && n.namespaceURI() == nsURI) { + return n.toElement(); + } + if (n.localName() == "span" && n.namespaceURI() == nsURI) { + KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not recursive + if (!e.isNull()) { + return e; + } + } + } + } + return KoXmlElement(); +} + +void Odf::loadCellText(Cell *cell, const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName) +{ + //Search and load each paragraph of text. Each paragraph is separated by a line break + KoXmlElement textParagraphElement; + QString cellText; + + int lineCount = 0; + bool hasRichText = false; + bool stripLeadingSpace = true; + + forEachElement(textParagraphElement , parent) { + if (textParagraphElement.localName() == "p" && + textParagraphElement.namespaceURI() == KoXmlNS::text) { + + // the text:a link could be located within a text:span element + KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, KoXmlNS::text, "a"); + if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href")) { + QString link = textA.attributeNS(KoXmlNS::xlink, "href", QString()); + cellText = textA.text(); + cell->setUserInput(cellText); + hasRichText = false; + lineCount = 0; + // The value will be set later in loadOdf(). + if ((!link.isEmpty()) && (link[0] == '#')) + link.remove(0, 1); + cell->setLink(link); + // Abort here cause we can handle only either a link in a cell or (rich-)text but not both. + break; + } + + if (!cellText.isNull()) + cellText += '\n'; + + ++lineCount; + int textFragmentCount = 0; + + // Our text could contain formating for value or result of formula or a mix of + // multiple text:span elements with text-nodes and line-break's. + cellText += loadCellTextNodes(cell, textParagraphElement, &textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace); + + // If we got text from multiple different sources (e.g. from the text:p and a + // child text:span) then we have very likely rich-text. + if (!hasRichText) + hasRichText = textFragmentCount >= 2; + } + } + + if (!cellText.isNull()) { + if (hasRichText && !findDrawElements(parent)) { + // for now we don't support richtext and embedded shapes in the same cell; + // this is because they would currently be loaded twice, once by the KoTextLoader + // and later properly by the cell itself + + Style style; style.setDefault(); + if (!cellStyleName.isEmpty()) { + if (autoStyles.contains(cellStyleName)) + style.merge(autoStyles[cellStyleName]); + else { + const CustomStyle* namedStyle = cell->sheet()->map()->styleManager()->style(cellStyleName); + if (namedStyle) + style.merge(*namedStyle); + } + } + + QTextCharFormat format = style.asCharFormat(); + ((KoCharacterStyle *)cell->sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); + + QSharedPointer doc(new QTextDocument); + KoTextDocument(doc.data()).setStyleManager(cell->sheet()->map()->textStyleManager()); + + Q_ASSERT(tableContext.shapeContext); + KoTextLoader loader(*tableContext.shapeContext); + QTextCursor cursor(doc.data()); + loader.loadBody(parent, cursor); + + cell->setUserInput(doc->toPlainText()); + cell->setRichText(doc); + } else { + cell->setUserInput(cellText); + } + } + + // enable word wrapping if multiple lines of text have been found. + if (lineCount >= 2) { + Style newStyle; + newStyle.setWrapText(true); + setStyle(newStyle); + } +} + +void Odf::loadObjects(Cell *cell, const KoXmlElement &parent, OdfLoadingContext& tableContext, QList& shapeData) +{ + // Register additional attributes, that identify shapes anchored in cells. + // Their dimensions need adjustment after all rows are loaded, + // because the position of the end cell is not always known yet. + KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( + KoXmlNS::table, "end-cell-address", + "table:end-cell-address")); + KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( + KoXmlNS::table, "end-x", + "table:end-x")); + KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( + KoXmlNS::table, "end-y", + "table:end-y")); + + KoXmlElement element; + forEachElement(element, parent) { + if (element.namespaceURI() != KoXmlNS::draw) + continue; + + if (element.localName() == "a") { + // It may the case that the object(s) are embedded into a hyperlink so actions are done on + // clicking it/them but since we do not supported objects-with-hyperlinks yet we just fetch + // the inner elements and use them to at least create and show the objects (see bug 249862). + KoXmlElement e; + forEachElement(e, element) { + if (e.namespaceURI() != KoXmlNS::draw) + continue; + ShapeLoadingData data = loadObject(cell, e, *tableContext.shapeContext); + if (data.shape) { + shapeData.append(data); + } + } + } else { + ShapeLoadingData data = loadObject(cell, element, *tableContext.shapeContext); + if (data.shape) { + shapeData.append(data); + } + } + } +} + +ShapeLoadingData Odf::loadObject(Cell *cell, const KoXmlElement &element, KoShapeLoadingContext &shapeContext) +{ + ShapeLoadingData data; + data.shape = 0; + KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext); + if (!shape) { + debugSheetsODF << "Unable to load shape with localName=" << element.localName(); + return data; + } + + cell->sheet()->addShape(shape); + + // The position is relative to the upper left sheet corner until now. Move it. + QPointF position = shape->position(); + // Remember how far we're off from the top-left corner of this cell + double offsetX = position.x(); + double offsetY = position.y(); + for (int col = 1; col < column(); ++col) + position += QPointF(cell->sheet()->columnFormat(col)->width(), 0.0); + if (cell->row() > 1) + position += QPointF(0.0, cell->sheet()->rowFormats()->totalRowHeight(1, cell->row() - 1)); + shape->setPosition(position); + + dynamic_cast(shape->applicationData())->setAnchoredToCell(true); + + // All three attributes are necessary for cell anchored shapes. + // Otherwise, they are anchored in the sheet. + if (!shape->hasAdditionalAttribute("table:end-cell-address") || + !shape->hasAdditionalAttribute("table:end-x") || + !shape->hasAdditionalAttribute("table:end-y")) { + debugSheetsODF << "Not all attributes found, that are necessary for cell anchoring."; + return data; + } + +#warning use new odf + Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address")), + cell->sheet()->map(), cell->sheet()); + if (!endCell.isValid() || !endCell.isSingular()) + return data; + + QString string = shape->additionalAttribute("table:end-x"); + if (string.isNull()) + return data; + double endX = KoUnit::parseValue(string); + + string = shape->additionalAttribute("table:end-y"); + if (string.isNull()) + return data; + double endY = KoUnit::parseValue(string); + + data.shape = shape; + data.startCell = QPoint(column(), row()); + data.offset = QPointF(offsetX, offsetY); + data.endCell = endCell; + data.endPoint = QPointF(endX, endY); + + // The column dimensions are already the final ones, but not the row dimensions. + // The default height is used for the not yet loaded rows. + // TODO Stefan: Honor non-default row heights later! + // subtract offset because the accumulated width and height we calculate below starts + // at the top-left corner of this cell, but the shape can have an offset to that corner + QSizeF size = QSizeF(endX - offsetX, endY - offsetY); + for (int col = column(); col < endCell.firstRange().left(); ++col) + size += QSizeF(cell->sheet()->columnFormat(col)->width(), 0.0); + if (endCell.firstRange().top() > cell->row()) + size += QSizeF(0.0, cell->sheet()->rowFormats()->totalRowHeight(cell->row(), endCell.firstRange().top() - 1)); + shape->setSize(size); + + return data; +} + + +// saving - helper functions + +void Odf::saveCellAnnotation(Cell *cell, KoXmlWriter &xmlwriter) +{ + const QString comment = cell->comment(); + if (comment.isEmpty()) return; + + // + xmlwriter.startElement("office:annotation"); + const QStringList text = comment.split('\n', QString::SkipEmptyParts); + for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) { + xmlwriter.startElement("text:p"); + xmlwriter.addTextNode(*it); + xmlwriter.endElement(); + } + xmlwriter.endElement(); +} + + +QString Odf::saveCellStyle(Cell *cell, KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles) +{ + const Conditions conditions = cell->conditions(); + if (!conditions.isEmpty()) { + // this has to be an automatic style + currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell"); +#warning use new odf + conditions.saveOdfConditions(currentCellStyle, cell->sheet()->map()->converter()); + } +#warning TODO new style odf + return style().saveOdf(currentCellStyle, mainStyles, cell->sheet()->map()->styleManager()); +} + +void Odf::saveCellValue(Cell *cell, KoXmlWriter &xmlWriter) +{ + Value value = cell->value(); + // Determine the format that we will be storing. + // This is usually the format that is actually shown - doing so mixes style and content, but that's how + // LO does it, so we need to stay compatible + Format::Type shownFormat = style().formatType(); + if (shownFormat == Format::Generic) + shownFormat = cell->sheet()->map()->formatter()->determineFormatting(value, shownFormat); + Value::Format saveFormat = Value::fmt_None; + Value::Format valueFormat = value.format(); + if (valueFormat == Value::fmt_Boolean) + saveFormat = Value::fmt_Boolean; + else if (valueFormat == Value::fmt_String) // if it's a text, it needs to be stored as a text + saveFormat = Value::fmt_String; + else if (Format::isDate(shownFormat)) + saveFormat = Value::fmt_Date; + else if (Format::isTime(shownFormat)) + saveFormat = Value::fmt_Time; + else if (Format::isNumber(shownFormat)) + saveFormat = Value::fmt_Number; + else if (Format::isMoney(shownFormat)) + saveFormat = Value::fmt_Money; + else if (shownFormat == Format::Percentage) + saveFormat = Value::fmt_Percent; + else if (shownFormat == Format::Text) + saveFormat = Value::fmt_String; + else if (shownFormat == Format::Custom) + saveFormat = valueFormat; + + switch (saveFormat) { + case Value::fmt_None: break; //NOTHING HERE + case Value::fmt_Boolean: { + xmlWriter.addAttribute("office:value-type", "boolean"); + xmlWriter.addAttribute("office:boolean-value", (value.asBoolean() ? "true" : "false")); + break; + } + case Value::fmt_Number: { + xmlWriter.addAttribute("office:value-type", "float"); + if (value.isInteger()) + xmlWriter.addAttribute("office:value", QString::number(value.asInteger())); + else + xmlWriter.addAttribute("office:value", QString::number(numToDouble(value.asFloat()), 'g', DBL_DIG)); + break; + } + case Value::fmt_Percent: { + xmlWriter.addAttribute("office:value-type", "percentage"); + xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value.asFloat()))); + break; + } + case Value::fmt_Money: { + xmlWriter.addAttribute("office:value-type", "currency"); + const Style style = cell->style(); + if (style.hasAttribute(Style::CurrencyFormat)) { + Currency currency = style.currency(); + xmlWriter.addAttribute("office:currency", currency.code()); + } + xmlWriter.addAttribute("office:value", QString::number((double) numToDouble(value.asFloat()))); + break; + } + case Value::fmt_DateTime: break; //NOTHING HERE + case Value::fmt_Date: { + xmlWriter.addAttribute("office:value-type", "date"); + xmlWriter.addAttribute("office:date-value", + value.asDate(cell->sheet()->map()->calculationSettings()).toString(Qt::ISODate)); + break; + } + case Value::fmt_Time: { + xmlWriter.addAttribute("office:value-type", "time"); + xmlWriter.addAttribute("office:time-value", + value.asTime().toString("'PT'hh'H'mm'M'ss'S'")); + break; + } + case Value::fmt_String: { + xmlWriter.addAttribute("office:value-type", "string"); + xmlWriter.addAttribute("office:string-value", value.asString()); + break; + } + }; +} + + + + } // Sheets } // Calligra diff --git a/sheets/odf/SheetsOdfSheet.cpp b/sheets/odf/SheetsOdfSheet.cpp index f43758b265d..5254bf66c90 100644 --- a/sheets/odf/SheetsOdfSheet.cpp +++ b/sheets/odf/SheetsOdfSheet.cpp @@ -1,1788 +1,1799 @@ /* This file is part of the KDE project Copyright 1998-2016 The Calligra Team Copyright 2016 Tomas Mecir Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 1998-2000 Torben Weis 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 "SheetsOdf.h" #include #include #include #include #include #include #include #include #include #include "KoStore.h" #include #include "KoUnit.h" #include #include #include #include "CellStorage.h" #include "Condition.h" #include "DocBase.h" #include "Formula.h" #include "HeaderFooter.h" #include "LoadingInfo.h" #include "Map.h" #include "OdfLoadingContext.h" #include "OdfSavingContext.h" #include "PrintSettings.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Sheet.h" #include "SheetPrint.h" #include "ShapeApplicationData.h" #include "StyleManager.h" #include "StyleStorage.h" #include "Validity.h" // This file contains functionality to load/save a Sheet namespace Calligra { namespace Sheets { +class Cell; + template class IntervalMap { public: IntervalMap() {} // from and to are inclusive, assumes no overlapping ranges // even though no checks are done void insert(int from, int to, const T& data) { m_data.insert(to, qMakePair(from, data)); } T get(int idx) const { typename QMap >::ConstIterator it = m_data.lowerBound(idx); if (it != m_data.end() && it.value().first <= idx) { return it.value().second; } return T(); } private: QMap > m_data; }; namespace Odf { + // Sheet loading and saving bool loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles); + bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext); + + // Sheet loading - helper functions /** * Inserts the styles contained in \p styleRegions into the style storage. * Looks automatic styles up in the map of preloaded automatic styles, * \p autoStyles , and custom styles in the StyleManager. * The region is restricted to \p usedArea . */ void loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles, const QHash& styleRegions, const QHash& conditionalStyles, const QRect& usedArea, QList >& outStyleRegions, QList >& outConditionalStyles); bool loadStyleFormat(Sheet *sheet, KoXmlElement *style); void loadMasterLayoutPage(Sheet *sheet, KoStyleStack &styleStack); void loadRowNodes(Sheet *sheet, const KoXmlElement& parent, int& rowIndex, int& maxColumn, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData); void loadColumnNodes(Sheet *sheet, const KoXmlElement& parent, int& indexCol, int& maxColumn, KoOdfLoadingContext& odfContext, QHash& columnStyleRegions, IntervalMap& columnStyles); bool loadColumnFormat(Sheet *sheet, const KoXmlElement& column, const KoOdfStylesReader& stylesReader, int & indexCol, QHash& columnStyleRegions, IntervalMap& columnStyles); int loadRowFormat(Sheet *sheet, const KoXmlElement& row, int &rowIndex, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData); QString getPart(const KoXmlNode & part); void replaceMacro(QString & text, const QString & old, const QString & newS); - bool saveSheet(Sheet *sheet, OdfSavingContext& tableContext); + // Sheet saving - helper functions QString saveSheetStyleName(Sheet *sheet, KoGenStyles &mainStyles); void saveColRowCell(Sheet *sheet, int maxCols, int maxRows, OdfSavingContext& tableContext); void saveCells(Sheet *sheet, int row, int maxCols, OdfSavingContext& tableContext); void saveHeaderFooter(Sheet *sheet, KoXmlWriter &xmlWriter); void saveBackgroundImage(Sheet *sheet, KoXmlWriter& xmlWriter); void addText(const QString & text, KoXmlWriter & writer); void convertPart(Sheet *sheet, const QString & part, KoXmlWriter & xmlWriter); bool compareRows(Sheet *sheet, int row1, int row2, int maxCols, OdfSavingContext& tableContext); + // sheet settings void loadSheetSettings(Sheet *sheet, const KoOasisSettings::NamedMap &settings); void saveSheetSettings(Sheet *sheet, KoXmlWriter &settingsWriter); + + // Cell loading - in SheetsOdfCell + bool loadCell(Cell *cell, const KoXmlElement& element, OdfLoadingContext& tableContext, + const Styles& autoStyles, const QString& cellStyleName, + QList& shapeData); + bool saveCell(Cell *cell, int &repeated, OdfSavingContext& tableContext); } // *************** Loading ***************** bool Odf::loadSheet(Sheet *sheet, const KoXmlElement& sheetElement, OdfLoadingContext& tableContext, const Styles& autoStyles, const QHash& conditionalStyles) { QPointer updater; if (sheet->doc() && sheet->doc()->progressUpdater()) { updater = sheet->doc()->progressUpdater()->startSubtask(1, "Calligra::Sheets::Odf::loadSheet"); updater->setProgress(0); } KoOdfLoadingContext& odfContext = tableContext.odfContext; if (sheetElement.hasAttributeNS(KoXmlNS::table, "style-name")) { QString stylename = sheetElement.attributeNS(KoXmlNS::table, "style-name", QString()); //debugSheetsODF<<" style of table :"<setHidden(!visible); } } if (style->hasAttributeNS(KoXmlNS::style, "master-page-name")) { QString masterPageStyleName = style->attributeNS(KoXmlNS::style, "master-page-name", QString()); //debugSheets<<"style->attribute( style:master-page-name ) :"<hasAttributeNS(KoXmlNS::style, "page-layout-name")) { QString masterPageLayoutStyleName = masterStyle->attributeNS(KoXmlNS::style, "page-layout-name", QString()); //debugSheetsODF<<"masterPageLayoutStyleName :"<hasChildNodes() ) { KoXmlElement element; forEachElement(element, properties) { if (element.nodeName() == "style:background-image") { QString imagePath = element.attributeNS(KoXmlNS::xlink, "href"); KoStore* store = tableContext.odfContext.store(); if (store->hasFile(imagePath)) { QByteArray data; store->extractFile(imagePath, data); QImage image = QImage::fromData(data); if( image.isNull() ) { continue; } sheet->setBackgroundImage(image); Sheet::BackgroundImageProperties bgProperties; if( element.hasAttribute("draw:opacity") ) { QString opacity = element.attribute("draw:opacity", ""); if( opacity.endsWith(QLatin1Char('%')) ) { opacity.chop(1); } bool ok; float opacityFloat = opacity.toFloat( &ok ); if( ok ) { bgProperties.opacity = opacityFloat; } } //TODO //if( element.hasAttribute("style:filterName") ) { //} if( element.hasAttribute("style:position") ) { const QString positionAttribute = element.attribute("style:position",""); const QStringList positionList = positionAttribute.split(' ', QString::SkipEmptyParts); if( positionList.size() == 1) { const QString position = positionList.at(0); if( position == "left" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left; } if( position == "center" ) { //NOTE the standard is too vague to know what center alone means, we assume that it means both centered bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter; bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter; } if( position == "right" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right; } if( position == "top" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top; } if( position == "bottom" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom; } } else if (positionList.size() == 2) { const QString verticalPosition = positionList.at(0); const QString horizontalPosition = positionList.at(1); if( horizontalPosition == "left" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Left; } if( horizontalPosition == "center" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::HorizontalCenter; } if( horizontalPosition == "right" ) { bgProperties.horizontalPosition = Sheet::BackgroundImageProperties::Right; } if( verticalPosition == "top" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Top; } if( verticalPosition == "center" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::VerticalCenter; } if( verticalPosition == "bottom" ) { bgProperties.verticalPosition = Sheet::BackgroundImageProperties::Bottom; } } } if( element.hasAttribute("style:repeat") ) { const QString repeat = element.attribute("style:repeat"); if( repeat == "no-repeat" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::NoRepeat; } if( repeat == "repeat" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::Repeat; } if( repeat == "stretch" ) { bgProperties.repeat = Sheet::BackgroundImageProperties::Stretch; } } sheet->setBackgroundImageProperties(bgProperties); } } } } } } // Cell style regions QHash cellStyleRegions; // Cell style regions (row defaults) QHash rowStyleRegions; // Cell style regions (column defaults) QHash columnStyleRegions; IntervalMap columnStyles; // List of shapes that need to have their size recalculated after loading is complete QList shapeData; int rowIndex = 1; int indexCol = 1; int maxColumn = 1; KoXmlNode rowNode = sheetElement.firstChild(); // Some spreadsheet programs may support more rows than // Calligra Sheets so limit the number of repeated rows. // FIXME POSSIBLE DATA LOSS! // First load all style information for rows, columns and cells while (!rowNode.isNull() && rowIndex <= KS_rowMax) { //debugSheetsODF << " rowIndex :" << rowIndex << " indexCol :" << indexCol; KoXmlElement rowElement = rowNode.toElement(); if (!rowElement.isNull()) { // slightly faster KoXml::load(rowElement); //debugSheetsODF << " Odf::loadSheet rowElement.tagName() :" << rowElement.localName(); if (rowElement.namespaceURI() == KoXmlNS::table) { if (rowElement.localName() == "table-header-columns") { // NOTE Handle header cols as ordinary ones // as long as they're not supported. loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } else if (rowElement.localName() == "table-column-group") { loadColumnNodes(sheet, rowElement, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } else if (rowElement.localName() == "table-column" && indexCol <= KS_colMax) { //debugSheetsODF << " table-column found : index column before" << indexCol; loadColumnFormat(sheet, rowElement, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles); //debugSheetsODF << " table-column found : index column after" << indexCol; maxColumn = qMax(maxColumn, indexCol - 1); } else if (rowElement.localName() == "table-header-rows") { // NOTE Handle header rows as ordinary ones // as long as they're not supported. loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } else if (rowElement.localName() == "table-row-group") { loadRowNodes(sheet, rowElement, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } else if (rowElement.localName() == "table-row") { //debugSheetsODF << " table-row found :index row before" << rowIndex; int columnMaximal = loadRowFormat(sheet, rowElement, rowIndex, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); // allow the row to define more columns then defined via table-column maxColumn = qMax(maxColumn, columnMaximal); //debugSheetsODF << " table-row found :index row after" << rowIndex; } else if (rowElement.localName() == "shapes") { // OpenDocument v1.1, 8.3.4 Shapes: // The element contains all graphic shapes // with an anchor on the table this element is a child of. KoShapeLoadingContext* shapeLoadingContext = tableContext.shapeContext; KoXmlElement element; forEachElement(element, rowElement) { if (element.namespaceURI() != KoXmlNS::draw) continue; loadSheetObject(sheet, element, *shapeLoadingContext); } } } // don't need it anymore KoXml::unload(rowElement); } rowNode = rowNode.nextSibling(); int count = sheet->map()->increaseLoadedRowsCounter(); if (updater && count >= 0) updater->setProgress(count); } // now recalculate the size for embedded shapes that had sizes specified relative to a bottom-right corner cell foreach (const ShapeLoadingData& sd, shapeData) { // subtract offset because the accumulated width and height we calculate below starts // at the top-left corner of this cell, but the shape can have an offset to that corner QSizeF size = QSizeF( sd.endPoint.x() - sd.offset.x(), sd.endPoint.y() - sd.offset.y()); for (int col = sd.startCell.x(); col < sd.endCell.firstRange().left(); ++col) size += QSizeF(sheet->columnFormat(col)->width(), 0.0); if (sd.endCell.firstRange().top() > sd.startCell.y()) size += QSizeF(0.0, sheet->rowFormats()->totalRowHeight(sd.startCell.y(), sd.endCell.firstRange().top() - 1)); sd.shape->setSize(size); } QList > styleRegions; QList > conditionRegions; // insert the styles into the storage (column defaults) debugSheetsODF << "Inserting column default cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, columnStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); // insert the styles into the storage (row defaults) debugSheetsODF << "Inserting row default cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, rowStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); // insert the styles into the storage debugSheetsODF << "Inserting cell styles ..."; loadSheetInsertStyles(sheet, autoStyles, cellStyleRegions, conditionalStyles, QRect(1, 1, maxColumn, rowIndex - 1), styleRegions, conditionRegions); sheet->cellStorage()->loadStyles(styleRegions); sheet->cellStorage()->loadConditions(conditionRegions); if (sheetElement.hasAttributeNS(KoXmlNS::table, "print-ranges")) { // e.g.: Sheet4.A1:Sheet4.E28 QString range = sheetElement.attributeNS(KoXmlNS::table, "print-ranges", QString()); #warning TODO new style odf Region region(Region::loadOdf(range)); if (!region.firstSheet() || sheet->sheetName() == region.firstSheet()->sheetName()) sheet->printSettings()->setPrintRegion(region); } if (sheetElement.attributeNS(KoXmlNS::table, "protected", QString()) == "true") { #warning TODO new style odf loadProtection(sheet, sheetElement); } return true; } void Odf::loadSheetObject(Sheet *sheet, const KoXmlElement& element, KoShapeLoadingContext& shapeContext) { KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext); if (!shape) return; sheet->addShape(shape); dynamic_cast(shape->applicationData())->setAnchoredToCell(false); } void Odf::loadRowNodes(Sheet *sheet, const KoXmlElement& parent, int& rowIndex, int& maxColumn, OdfLoadingContext& tableContext, QHash& rowStyleRegions, QHash& cellStyleRegions, const IntervalMap& columnStyles, const Styles& autoStyles, QList& shapeData ) { KoXmlNode node = parent.firstChild(); while (!node.isNull()) { KoXmlElement elem = node.toElement(); if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) { if (elem.localName() == "table-row") { int columnMaximal = loadRowFormat(sheet, elem, rowIndex, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); // allow the row to define more columns then defined via table-column maxColumn = qMax(maxColumn, columnMaximal); } else if (elem.localName() == "table-row-group") { loadRowNodes(sheet, elem, rowIndex, maxColumn, tableContext, rowStyleRegions, cellStyleRegions, columnStyles, autoStyles, shapeData); } } node = node.nextSibling(); } } void Odf::loadColumnNodes(Sheet *sheet, const KoXmlElement& parent, int& indexCol, int& maxColumn, KoOdfLoadingContext& odfContext, QHash& columnStyleRegions, IntervalMap& columnStyles ) { KoXmlNode node = parent.firstChild(); while (!node.isNull()) { KoXmlElement elem = node.toElement(); if (!elem.isNull() && elem.namespaceURI() == KoXmlNS::table) { if (elem.localName() == "table-column") { loadColumnFormat(sheet, elem, odfContext.stylesReader(), indexCol, columnStyleRegions, columnStyles); maxColumn = qMax(maxColumn, indexCol - 1); } else if (elem.localName() == "table-column-group") { loadColumnNodes(sheet, elem, indexCol, maxColumn, odfContext, columnStyleRegions, columnStyles); } } node = node.nextSibling(); } } void Odf::loadSheetInsertStyles(Sheet *sheet, const Styles& autoStyles, const QHash& styleRegions, const QHash& conditionalStyles, const QRect& usedArea, QList >& outStyleRegions, QList >& outConditionalStyles) { const QList styleNames = styleRegions.keys(); for (int i = 0; i < styleNames.count(); ++i) { if (!autoStyles.contains(styleNames[i]) && !sheet->map()->styleManager()->style(styleNames[i])) { warnSheetsODF << "\t" << styleNames[i] << " not used"; continue; } const bool hasConditions = conditionalStyles.contains(styleNames[i]); const QRegion styleRegion = styleRegions[styleNames[i]] & QRegion(usedArea); if (hasConditions) outConditionalStyles.append(qMakePair(styleRegion, conditionalStyles[styleNames[i]])); if (autoStyles.contains(styleNames[i])) { //debugSheetsODF << "\tautomatic:" << styleNames[i] << " at" << styleRegion.rectCount() << "rects"; Style style; style.setDefault(); // "overwrite" existing style style.merge(autoStyles[styleNames[i]]); outStyleRegions.append(qMakePair(styleRegion, style)); } else { const CustomStyle* namedStyle = sheet->map()->styleManager()->style(styleNames[i]); //debugSheetsODF << "\tcustom:" << namedStyle->name() << " at" << styleRegion.rectCount() << "rects"; Style style; style.setDefault(); // "overwrite" existing style style.merge(*namedStyle); outStyleRegions.append(qMakePair(styleRegion, style)); } } } void Odf::replaceMacro(QString & text, const QString & old, const QString & newS) { int n = text.indexOf(old); if (n != -1) text = text.replace(n, old.length(), newS); } QString Odf::getPart(const KoXmlNode & part) { QString result; KoXmlElement e = KoXml::namedItemNS(part, KoXmlNS::text, "p"); while (!e.isNull()) { QString text = e.text(); KoXmlElement macro = KoXml::namedItemNS(e, KoXmlNS::text, "time"); if (!macro.isNull()) replaceMacro(text, macro.text(), "