File indexing completed on 2024-12-22 03:35:51
0001 /* 0002 File : OriginProjectParser.h 0003 Project : LabPlot 0004 Description : parser for Origin projects 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2017-2024 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2017-2024 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "backend/datasources/projects/OriginProjectParser.h" 0013 #include "backend/core/Project.h" 0014 #include "backend/core/Workbook.h" 0015 #include "backend/core/column/Column.h" 0016 #include "backend/core/datatypes/DateTime2StringFilter.h" 0017 #include "backend/core/datatypes/Double2StringFilter.h" 0018 #include "backend/matrix/Matrix.h" 0019 #include "backend/note/Note.h" 0020 #include "backend/spreadsheet/Spreadsheet.h" 0021 #include "backend/worksheet/Line.h" 0022 #include "backend/worksheet/TextLabel.h" 0023 #include "backend/worksheet/Worksheet.h" 0024 #include "backend/worksheet/WorksheetElement.h" 0025 #include "backend/worksheet/plots/PlotArea.h" 0026 #include "backend/worksheet/plots/cartesian/Axis.h" 0027 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0028 #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" 0029 #include "backend/worksheet/plots/cartesian/Symbol.h" 0030 #include "backend/worksheet/plots/cartesian/XYCurve.h" 0031 #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" 0032 0033 #include <KLocalizedString> 0034 0035 #include <QDateTime> 0036 #include <QDir> 0037 #include <QFontMetrics> 0038 #include <QGraphicsScene> 0039 #include <QRegularExpression> 0040 0041 #include <gsl/gsl_const_cgs.h> 0042 0043 /*! 0044 \class OriginProjectParser 0045 \brief parser for Origin projects. 0046 0047 \ingroup datasources 0048 */ 0049 0050 OriginProjectParser::OriginProjectParser() 0051 : ProjectParser() { 0052 m_topLevelClasses = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::Matrix, AspectType::Worksheet, AspectType::Note}; 0053 } 0054 0055 OriginProjectParser::~OriginProjectParser() { 0056 delete m_originFile; 0057 } 0058 0059 bool OriginProjectParser::isOriginProject(const QString& fileName) { 0060 // TODO add opju later when liborigin supports it 0061 return fileName.endsWith(QLatin1String(".opj"), Qt::CaseInsensitive); 0062 } 0063 0064 void OriginProjectParser::setImportUnusedObjects(bool importUnusedObjects) { 0065 m_importUnusedObjects = importUnusedObjects; 0066 } 0067 0068 void OriginProjectParser::checkContent(bool& hasUnusedObjects, bool& hasMultiLayerGraphs) { 0069 DEBUG(Q_FUNC_INFO) 0070 m_originFile = new OriginFile(qPrintable(m_projectFileName)); 0071 if (!m_originFile->parse()) { 0072 delete m_originFile; 0073 m_originFile = nullptr; 0074 hasUnusedObjects = false; 0075 hasMultiLayerGraphs = false; 0076 return; 0077 } 0078 0079 hasUnusedObjects = this->hasUnusedObjects(); 0080 hasMultiLayerGraphs = this->hasMultiLayerGraphs(); 0081 0082 delete m_originFile; 0083 m_originFile = nullptr; 0084 } 0085 0086 bool OriginProjectParser::hasUnusedObjects() { 0087 if (!m_originFile) 0088 return false; 0089 0090 for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { 0091 const auto& spread = m_originFile->spread(i); 0092 if (spread.objectID < 0) 0093 return true; 0094 } 0095 for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { 0096 const auto& excel = m_originFile->excel(i); 0097 if (excel.objectID < 0) 0098 return true; 0099 } 0100 for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { 0101 const auto& matrix = m_originFile->matrix(i); 0102 if (matrix.objectID < 0) 0103 return true; 0104 } 0105 0106 return false; 0107 } 0108 0109 bool OriginProjectParser::hasMultiLayerGraphs() { 0110 if (!m_originFile) 0111 return false; 0112 0113 for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { 0114 const auto& graph = m_originFile->graph(i); 0115 if (graph.layers.size() > 1) 0116 return true; 0117 } 0118 0119 return false; 0120 } 0121 0122 void OriginProjectParser::setGraphLayerAsPlotArea(bool value) { 0123 m_graphLayerAsPlotArea = value; 0124 } 0125 0126 QString OriginProjectParser::supportedExtensions() { 0127 // TODO add opju later when liborigin supports it 0128 static const QString extensions = QStringLiteral("*.opj *.OPJ"); 0129 return extensions; 0130 } 0131 0132 // sets first found spread of given name 0133 unsigned int OriginProjectParser::findSpreadsheetByName(const QString& name) { 0134 DEBUG(Q_FUNC_INFO << ", name = " << name.toStdString() << ", count = " << m_originFile->spreadCount()) 0135 for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { 0136 const auto& spread = m_originFile->spread(i); 0137 DEBUG(Q_FUNC_INFO << ", spreadsheet name = " << spread.name) 0138 if (spread.name == name.toStdString()) { 0139 m_spreadsheetNameList << name; 0140 m_spreadsheetNameList.removeDuplicates(); 0141 return i; 0142 } 0143 } 0144 return 0; 0145 } 0146 unsigned int OriginProjectParser::findColumnByName(Origin::SpreadSheet& spread, const QString& name) { 0147 for (unsigned int i = 0; i < spread.columns.size(); i++) { 0148 auto column = spread.columns[i]; 0149 if (column.name == name.toStdString()) 0150 return i; 0151 } 0152 return 0; 0153 } 0154 unsigned int OriginProjectParser::findMatrixByName(const QString& name) { 0155 for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { 0156 const auto& originMatrix = m_originFile->matrix(i); 0157 if (originMatrix.name == name.toStdString()) { 0158 m_matrixNameList << name; 0159 m_matrixNameList.removeDuplicates(); 0160 return i; 0161 } 0162 } 0163 return 0; 0164 } 0165 unsigned int OriginProjectParser::findWorkbookByName(const QString& name) { 0166 // QDEBUG("WORKBOOK LIST: " << m_workbookNameList << ", name = " << name) 0167 for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { 0168 const auto& excel = m_originFile->excel(i); 0169 if (excel.name == name.toStdString()) { 0170 m_workbookNameList << name; 0171 m_workbookNameList.removeDuplicates(); 0172 return i; 0173 } 0174 } 0175 return 0; 0176 } 0177 unsigned int OriginProjectParser::findWorksheetByName(const QString& name) { 0178 for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { 0179 const auto& graph = m_originFile->graph(i); 0180 if (graph.name == name.toStdString()) { 0181 m_worksheetNameList << name; 0182 m_worksheetNameList.removeDuplicates(); 0183 return i; 0184 } 0185 } 0186 return 0; 0187 } 0188 unsigned int OriginProjectParser::findNoteByName(const QString& name) { 0189 for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { 0190 const auto& originNote = m_originFile->note(i); 0191 if (originNote.name == name.toStdString()) { 0192 m_noteNameList << name; 0193 m_noteNameList.removeDuplicates(); 0194 return i; 0195 } 0196 } 0197 return 0; 0198 } 0199 0200 // get Origin::Spreadsheet from container name (may be a spreadsheet or workbook) 0201 Origin::SpreadSheet OriginProjectParser::getSpreadsheetByName(QString& containerName) { 0202 DEBUG(Q_FUNC_INFO) 0203 int sheetIndex = 0; // which sheet? "@X" 0204 const int atIndex = containerName.indexOf(QLatin1Char('@')); 0205 if (atIndex != -1) { 0206 sheetIndex = containerName.mid(atIndex + 1).toInt() - 1; 0207 containerName.truncate(atIndex); 0208 } 0209 // DEBUG("CONTAINER = " << STDSTRING(containerName) << ", SHEET = " << sheetIndex) 0210 0211 // check if workbook 0212 int workbookIndex = findWorkbookByName(containerName); 0213 // if workbook not found, findWorkbookByName() returns 0: check this 0214 if (workbookIndex == 0 && (m_originFile->excelCount() == 0 || containerName.toStdString() != m_originFile->excel(0).name)) 0215 workbookIndex = -1; 0216 // DEBUG("WORKBOOK index = " << workbookIndex) 0217 0218 // comment of y column is used in legend (if not empty), else the column name 0219 Origin::SpreadSheet sheet; 0220 if (workbookIndex != -1) { // container is a workbook 0221 sheet = m_originFile->excel(workbookIndex).sheets[sheetIndex]; 0222 } else { // container is a spreadsheet? 0223 int spreadsheetIndex = findSpreadsheetByName(containerName); 0224 // if spreadsheet not found, findSpreadsheetByName() returns 0: check this 0225 if (spreadsheetIndex == 0 && (m_originFile->spreadCount() == 0 || containerName.toStdString() != m_originFile->spread(0).name)) 0226 spreadsheetIndex = -1; 0227 if (spreadsheetIndex != -1) 0228 sheet = m_originFile->spread(spreadsheetIndex); 0229 } 0230 0231 return sheet; 0232 } 0233 0234 // ############################################################################## 0235 // ############## Deserialization from Origin's project tree #################### 0236 // ############################################################################## 0237 bool OriginProjectParser::load(Project* project, bool preview) { 0238 DEBUG(Q_FUNC_INFO); 0239 0240 // read and parse the m_originFile-file 0241 m_originFile = new OriginFile(qPrintable(m_projectFileName)); 0242 if (!m_originFile->parse()) { 0243 delete m_originFile; 0244 m_originFile = nullptr; 0245 return false; 0246 } 0247 0248 DEBUG(Q_FUNC_INFO << ", project file name: " << m_projectFileName.toStdString()); 0249 DEBUG(Q_FUNC_INFO << ", Origin version: " << m_originFile->version()); 0250 0251 // Origin project tree and the iterator pointing to the root node 0252 const auto* projectTree = m_originFile->project(); 0253 auto projectIt = projectTree->begin(projectTree->begin()); 0254 0255 m_spreadsheetNameList.clear(); 0256 m_workbookNameList.clear(); 0257 m_matrixNameList.clear(); 0258 m_worksheetNameList.clear(); 0259 m_noteNameList.clear(); 0260 0261 // convert the project tree from liborigin's representation to LabPlot's project object 0262 project->setIsLoading(true); 0263 if (projectIt.node) { // only opj files from version >= 6.0 have a project tree 0264 DEBUG(Q_FUNC_INFO << ", project tree found"); 0265 QString name(QString::fromLatin1(projectIt->name.c_str())); 0266 project->setName(name); 0267 project->setCreationTime(creationTime(projectIt)); 0268 loadFolder(project, projectIt, preview); 0269 } else { // for older versions put all windows on rootfolder 0270 DEBUG(Q_FUNC_INFO << ", no project tree"); 0271 int pos = m_projectFileName.lastIndexOf(QLatin1Char('/')) + 1; 0272 project->setName(m_projectFileName.mid(pos)); 0273 } 0274 // imports all loose windows (like prior version 6 which has no project tree) 0275 handleLooseWindows(project, preview); 0276 0277 // restore column pointers: 0278 // 1. extend the pathes to contain the parent structures first 0279 // 2. restore the pointers from the pathes 0280 const auto& columns = project->children<Column>(AbstractAspect::ChildIndexFlag::Recursive); 0281 const auto& spreadsheets = project->children<Spreadsheet>(AbstractAspect::ChildIndexFlag::Recursive); 0282 const auto& curves = project->children<XYCurve>(AbstractAspect::ChildIndexFlag::Recursive); 0283 DEBUG(Q_FUNC_INFO << ", NUMBER of spreadsheets/columns = " 0284 << "/" << spreadsheets.count() << "/" << columns.count()) 0285 for (auto* curve : curves) { 0286 DEBUG(Q_FUNC_INFO << ", RESTORE CURVE with x/y column path " << STDSTRING(curve->xColumnPath()) << " " << STDSTRING(curve->yColumnPath())) 0287 curve->setSuppressRetransform(true); 0288 0289 // x-column 0290 QString spreadsheetName = curve->xColumnPath(); 0291 spreadsheetName.truncate(curve->xColumnPath().lastIndexOf(QLatin1Char('/'))); 0292 // DEBUG(Q_FUNC_INFO << ", SPREADSHEET name from column: " << STDSTRING(spreadsheetName)) 0293 for (const auto* spreadsheet : spreadsheets) { 0294 QString container, containerPath = spreadsheet->parentAspect()->path(); 0295 if (spreadsheetName.contains(QLatin1Char('/'))) { // part of a workbook 0296 container = containerPath.mid(containerPath.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char('/'); 0297 containerPath = containerPath.left(containerPath.lastIndexOf(QLatin1Char('/'))); 0298 } 0299 // DEBUG("CONTAINER = " << STDSTRING(container)) 0300 // DEBUG("CONTAINER PATH = " << STDSTRING(containerPath)) 0301 // DEBUG(Q_FUNC_INFO << ", LOOP spreadsheet names = \"" << STDSTRING(container) + 0302 // STDSTRING(spreadsheet->name()) << "\", path = " << STDSTRING(spreadsheetName)) 0303 // DEBUG("SPREADSHEET parent path = " << STDSTRING(spreadsheet->parentAspect()->path())) 0304 if (container + spreadsheet->name() == spreadsheetName) { 0305 const QString& newPath = containerPath + QLatin1Char('/') + curve->xColumnPath(); 0306 // const QString& newPath = QLatin1String("Project") + QLatin1Char('/') + curve->xColumnPath(); 0307 DEBUG(Q_FUNC_INFO << ", SET COLUMN PATH to \"" << STDSTRING(newPath) << "\"") 0308 curve->setXColumnPath(newPath); 0309 0310 for (auto* column : columns) { 0311 if (!column) 0312 continue; 0313 if (column->path() == newPath) { 0314 // DEBUG(Q_FUNC_INFO << ", set X column path = \"" << STDSTRING(column->path()) << "\"") 0315 curve->setXColumn(column); 0316 break; 0317 } 0318 } 0319 break; 0320 } 0321 } 0322 0323 // y-column 0324 spreadsheetName = curve->yColumnPath(); 0325 spreadsheetName.truncate(curve->yColumnPath().lastIndexOf(QLatin1Char('/'))); 0326 for (const auto* spreadsheet : spreadsheets) { 0327 QString container, containerPath = spreadsheet->parentAspect()->path(); 0328 if (spreadsheetName.contains(QLatin1Char('/'))) { // part of a workbook 0329 container = containerPath.mid(containerPath.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char('/'); 0330 containerPath = containerPath.left(containerPath.lastIndexOf(QLatin1Char('/'))); 0331 } 0332 if (container + spreadsheet->name() == spreadsheetName) { 0333 const QString& newPath = containerPath + QLatin1Char('/') + curve->yColumnPath(); 0334 curve->setYColumnPath(newPath); 0335 0336 for (auto* column : columns) { 0337 if (!column) 0338 continue; 0339 // DEBUG(Q_FUNC_INFO << ", column paths = \"" << STDSTRING(column->path()) 0340 // << "\" / \"" << STDSTRING(newPath) << "\"" ) 0341 if (column->path() == newPath) { 0342 curve->setYColumn(column); 0343 break; 0344 } 0345 } 0346 break; 0347 } 0348 } 0349 DEBUG(Q_FUNC_INFO << ", curve x/y COLUMNS = " << curve->xColumn() << "/" << curve->yColumn()) 0350 0351 // TODO: error columns 0352 0353 curve->setSuppressRetransform(false); 0354 } 0355 0356 if (!preview) { 0357 const auto& plots = project->children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive); 0358 for (auto* plot : plots) { 0359 plot->setIsLoading(false); 0360 plot->retransform(); 0361 } 0362 } 0363 0364 project->setIsLoading(false); 0365 0366 delete m_originFile; 0367 m_originFile = nullptr; 0368 0369 return true; 0370 } 0371 0372 bool OriginProjectParser::loadFolder(Folder* folder, tree<Origin::ProjectNode>::iterator baseIt, bool preview) { 0373 DEBUG(Q_FUNC_INFO) 0374 const tree<Origin::ProjectNode>* projectTree = m_originFile->project(); 0375 0376 // do not skip anything if pathesToLoad() contains only root folder 0377 bool containsRootFolder = (folder->pathesToLoad().size() == 1 && folder->pathesToLoad().contains(folder->path())); 0378 if (containsRootFolder) { 0379 DEBUG(" pathesToLoad contains only folder path \"" << STDSTRING(folder->path()) << "\". Clearing pathes to load.") 0380 folder->setPathesToLoad(QStringList()); 0381 } 0382 0383 // load folder's children: logic for reading the selected objects only is similar to Folder::readChildAspectElement 0384 for (auto it = projectTree->begin(baseIt); it != projectTree->end(baseIt); ++it) { 0385 QString name(QString::fromLatin1(it->name.c_str())); // name of the current child 0386 DEBUG(Q_FUNC_INFO << ", folder item name = " << STDSTRING(name)) 0387 0388 // check whether we need to skip the loading of the current child 0389 if (!folder->pathesToLoad().isEmpty()) { 0390 // child's path is not available yet (child not added yet) -> construct the path manually 0391 const QString childPath = folder->path() + QLatin1Char('/') + name; 0392 DEBUG(" path = " << STDSTRING(childPath)) 0393 0394 // skip the current child aspect it is not in the list of aspects to be loaded 0395 if (folder->pathesToLoad().indexOf(childPath) == -1) { 0396 DEBUG(" skip it!") 0397 continue; 0398 } 0399 } 0400 0401 // load top-level children. 0402 // use 'preview' as 'loading'-parameter in the constructors to skip the init() calls in Worksheet, Spreadsheet and Matrix: 0403 //* when doing the preview of the project we don't want to initialize the objects and skip init()'s 0404 //* when loading the project, 'preview' is false and we initialize all objects with our default values 0405 // and set all possible properties from Origin additionally 0406 AbstractAspect* aspect = nullptr; 0407 switch (it->type) { 0408 case Origin::ProjectNode::Folder: { 0409 DEBUG(Q_FUNC_INFO << ", top level FOLDER"); 0410 Folder* f = new Folder(name); 0411 0412 if (!folder->pathesToLoad().isEmpty()) { 0413 // a child folder to be read -> provide the list of aspects to be loaded to the child folder, too. 0414 // since the child folder and all its children are not added yet (path() returns empty string), 0415 // we need to remove the path of the current child folder from the full pathes provided in pathesToLoad. 0416 // E.g. we want to import the path "Project/Folder/Spreadsheet" in the following project 0417 // Project 0418 // \Spreadsheet 0419 // \Folder 0420 // \Spreadsheet 0421 // 0422 // Here, we remove the part "Project/Folder/" and proceed for this child folder with "Spreadsheet" only. 0423 // With this the logic above where it is determined whether to import the child aspect or not works out. 0424 0425 // manually construct the path of the child folder to be read 0426 const QString& curFolderPath = folder->path() + QLatin1Char('/') + name; 0427 0428 // remove the path of the current child folder 0429 QStringList pathesToLoadNew; 0430 for (const auto& path : folder->pathesToLoad()) { 0431 if (path.startsWith(curFolderPath)) 0432 pathesToLoadNew << path.right(path.length() - curFolderPath.length()); 0433 } 0434 0435 f->setPathesToLoad(pathesToLoadNew); 0436 } 0437 0438 loadFolder(f, it, preview); 0439 aspect = f; 0440 break; 0441 } 0442 case Origin::ProjectNode::SpreadSheet: { 0443 DEBUG(Q_FUNC_INFO << ", top level SPREADSHEET"); 0444 auto* spreadsheet = new Spreadsheet(name, preview); 0445 loadSpreadsheet(spreadsheet, preview, name); 0446 aspect = spreadsheet; 0447 break; 0448 } 0449 case Origin::ProjectNode::Graph: { 0450 DEBUG(Q_FUNC_INFO << ", top level GRAPH"); 0451 auto* worksheet = new Worksheet(name, preview); 0452 if (!preview) { 0453 worksheet->setIsLoading(true); 0454 worksheet->setTheme(QString()); 0455 } 0456 loadWorksheet(worksheet, preview); 0457 aspect = worksheet; 0458 break; 0459 } 0460 case Origin::ProjectNode::Matrix: { 0461 DEBUG(Q_FUNC_INFO << ", top level MATRIX"); 0462 const auto& originMatrix = m_originFile->matrix(findMatrixByName(name)); 0463 DEBUG(" matrix name = " << originMatrix.name); 0464 DEBUG(" number of sheets = " << originMatrix.sheets.size()); 0465 if (originMatrix.sheets.size() == 1) { 0466 // single sheet -> load into a matrix 0467 Matrix* matrix = new Matrix(name, preview); 0468 loadMatrix(matrix, preview); 0469 aspect = matrix; 0470 } else { 0471 // multiple sheets -> load into a workbook 0472 Workbook* workbook = new Workbook(name); 0473 loadMatrixWorkbook(workbook, preview); 0474 aspect = workbook; 0475 } 0476 break; 0477 } 0478 case Origin::ProjectNode::Excel: { 0479 DEBUG(Q_FUNC_INFO << ", top level WORKBOOK"); 0480 auto* workbook = new Workbook(name); 0481 loadWorkbook(workbook, preview); 0482 aspect = workbook; 0483 break; 0484 } 0485 case Origin::ProjectNode::Note: { 0486 DEBUG(Q_FUNC_INFO << ", top level NOTE"); 0487 Note* note = new Note(name); 0488 loadNote(note, preview); 0489 aspect = note; 0490 break; 0491 } 0492 case Origin::ProjectNode::Graph3D: 0493 default: 0494 // TODO: add UnsupportedAspect 0495 break; 0496 } 0497 0498 if (aspect) { 0499 folder->addChildFast(aspect); 0500 aspect->setCreationTime(creationTime(it)); 0501 aspect->setIsLoading(false); 0502 } 0503 } 0504 0505 // ResultsLog 0506 QString resultsLog = QString::fromStdString(m_originFile->resultsLogString()); 0507 if (resultsLog.length() > 0) { 0508 DEBUG("Results log:\t\tyes"); 0509 Note* note = new Note(QStringLiteral("ResultsLog")); 0510 0511 if (preview) 0512 folder->addChildFast(note); 0513 else { 0514 // only import the log if it is in the list of aspects to be loaded 0515 const QString childPath = folder->path() + QLatin1Char('/') + note->name(); 0516 if (folder->pathesToLoad().indexOf(childPath) != -1) { 0517 note->setText(resultsLog); 0518 folder->addChildFast(note); 0519 } 0520 } 0521 } else 0522 DEBUG("Results log:\t\tno"); 0523 0524 return folder; 0525 } 0526 0527 void OriginProjectParser::handleLooseWindows(Folder* folder, bool preview) { 0528 QDEBUG(Q_FUNC_INFO << ", paths to load:" << folder->pathesToLoad()); 0529 QDEBUG(" spreads =" << m_spreadsheetNameList); 0530 QDEBUG(" workbooks =" << m_workbookNameList); 0531 QDEBUG(" matrices =" << m_matrixNameList); 0532 QDEBUG(" worksheets =" << m_worksheetNameList); 0533 QDEBUG(" notes =" << m_noteNameList); 0534 0535 DEBUG("Number of spreads loaded:\t" << m_spreadsheetNameList.size() << ", in file: " << m_originFile->spreadCount()); 0536 DEBUG("Number of excels loaded:\t" << m_workbookNameList.size() << ", in file: " << m_originFile->excelCount()); 0537 DEBUG("Number of matrices loaded:\t" << m_matrixNameList.size() << ", in file: " << m_originFile->matrixCount()); 0538 DEBUG("Number of graphs loaded:\t" << m_worksheetNameList.size() << ", in file: " << m_originFile->graphCount()); 0539 DEBUG("Number of notes loaded:\t\t" << m_noteNameList.size() << ", in file: " << m_originFile->noteCount()); 0540 0541 // loop over all spreads to find loose ones 0542 for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { 0543 AbstractAspect* aspect = nullptr; 0544 const auto& spread = m_originFile->spread(i); 0545 QString name = QString::fromStdString(spread.name); 0546 0547 DEBUG(" spread.objectId = " << spread.objectID); 0548 // skip unused spreads if selected 0549 if (spread.objectID < 0 && !m_importUnusedObjects) { 0550 DEBUG(" Dropping unused loose spread: " << STDSTRING(name)); 0551 continue; 0552 } 0553 0554 const QString childPath = folder->path() + QLatin1Char('/') + name; 0555 // we could also use spread.loose 0556 if (!m_spreadsheetNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { 0557 DEBUG(" Adding loose spread: " << STDSTRING(name)); 0558 0559 auto* spreadsheet = new Spreadsheet(name); 0560 loadSpreadsheet(spreadsheet, preview, name); 0561 aspect = spreadsheet; 0562 } 0563 if (aspect) { 0564 folder->addChildFast(aspect); 0565 DEBUG(" creation time as reported by liborigin: " << spread.creationDate); 0566 aspect->setCreationTime(QDateTime::fromSecsSinceEpoch(spread.creationDate)); 0567 } 0568 } 0569 // loop over all workbooks to find loose ones 0570 for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { 0571 AbstractAspect* aspect = nullptr; 0572 const auto& excel = m_originFile->excel(i); 0573 QString name = QString::fromStdString(excel.name); 0574 0575 DEBUG(" excel.objectId = " << excel.objectID); 0576 // skip unused data sets if selected 0577 if (excel.objectID < 0 && !m_importUnusedObjects) { 0578 DEBUG(" Dropping unused loose excel: " << STDSTRING(name)); 0579 continue; 0580 } 0581 0582 const QString childPath = folder->path() + QLatin1Char('/') + name; 0583 // we could also use excel.loose 0584 if (!m_workbookNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { 0585 DEBUG(" Adding loose excel: " << STDSTRING(name)); 0586 DEBUG(" containing number of sheets = " << excel.sheets.size()); 0587 0588 auto* workbook = new Workbook(name); 0589 loadWorkbook(workbook, preview); 0590 aspect = workbook; 0591 } 0592 if (aspect) { 0593 folder->addChildFast(aspect); 0594 DEBUG(" creation time as reported by liborigin: " << excel.creationDate); 0595 aspect->setCreationTime(QDateTime::fromSecsSinceEpoch(excel.creationDate)); 0596 } 0597 } 0598 // loop over all matrices to find loose ones 0599 for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { 0600 AbstractAspect* aspect = nullptr; 0601 const auto& originMatrix = m_originFile->matrix(i); 0602 QString name = QString::fromStdString(originMatrix.name); 0603 0604 DEBUG(" originMatrix.objectId = " << originMatrix.objectID); 0605 // skip unused data sets if selected 0606 if (originMatrix.objectID < 0 && !m_importUnusedObjects) { 0607 DEBUG(" Dropping unused loose matrix: " << STDSTRING(name)); 0608 continue; 0609 } 0610 0611 const QString childPath = folder->path() + QLatin1Char('/') + name; 0612 if (!m_matrixNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { 0613 DEBUG(" Adding loose matrix: " << STDSTRING(name)); 0614 DEBUG(" containing number of sheets = " << originMatrix.sheets.size()); 0615 if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix 0616 auto* matrix = new Matrix(name); 0617 loadMatrix(matrix, preview); 0618 aspect = matrix; 0619 } else { // multiple sheets -> load into a workbook 0620 auto* workbook = new Workbook(name); 0621 loadMatrixWorkbook(workbook, preview); 0622 aspect = workbook; 0623 } 0624 } 0625 if (aspect) { 0626 folder->addChildFast(aspect); 0627 aspect->setCreationTime(QDateTime::fromSecsSinceEpoch(originMatrix.creationDate)); 0628 } 0629 } 0630 // handle loose graphs (is this even possible?) 0631 for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { 0632 AbstractAspect* aspect = nullptr; 0633 const auto& graph = m_originFile->graph(i); 0634 QString name = QString::fromStdString(graph.name); 0635 0636 DEBUG(" graph.objectId = " << graph.objectID); 0637 // skip unused graph if selected 0638 if (graph.objectID < 0 && !m_importUnusedObjects) { 0639 DEBUG(" Dropping unused loose graph: " << STDSTRING(name)); 0640 continue; 0641 } 0642 0643 const QString childPath = folder->path() + QLatin1Char('/') + name; 0644 if (!m_worksheetNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { 0645 DEBUG(" Adding loose graph: " << STDSTRING(name)); 0646 auto* worksheet = new Worksheet(name); 0647 loadWorksheet(worksheet, preview); 0648 aspect = worksheet; 0649 } 0650 if (aspect) { 0651 folder->addChildFast(aspect); 0652 aspect->setCreationTime(QDateTime::fromSecsSinceEpoch(graph.creationDate)); 0653 } 0654 } 0655 // handle loose notes (is this even possible?) 0656 for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { 0657 AbstractAspect* aspect = nullptr; 0658 const auto& originNote = m_originFile->note(i); 0659 QString name = QString::fromStdString(originNote.name); 0660 0661 DEBUG(" originNote.objectId = " << originNote.objectID); 0662 // skip unused notes if selected 0663 if (originNote.objectID < 0 && !m_importUnusedObjects) { 0664 DEBUG(" Dropping unused loose note: " << STDSTRING(name)); 0665 continue; 0666 } 0667 0668 const QString childPath = folder->path() + QLatin1Char('/') + name; 0669 if (!m_noteNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { 0670 DEBUG(" Adding loose note: " << STDSTRING(name)); 0671 Note* note = new Note(name); 0672 loadNote(note, preview); 0673 aspect = note; 0674 } 0675 if (aspect) { 0676 folder->addChildFast(aspect); 0677 aspect->setCreationTime(QDateTime::fromSecsSinceEpoch(originNote.creationDate)); 0678 } 0679 } 0680 } 0681 0682 bool OriginProjectParser::loadWorkbook(Workbook* workbook, bool preview) { 0683 DEBUG(Q_FUNC_INFO); 0684 // load workbook sheets 0685 const auto& excel = m_originFile->excel(findWorkbookByName(workbook->name())); 0686 DEBUG(Q_FUNC_INFO << ", workbook name = " << excel.name); 0687 DEBUG(Q_FUNC_INFO << ", number of sheets = " << excel.sheets.size()); 0688 for (unsigned int s = 0; s < excel.sheets.size(); ++s) { 0689 // DEBUG(Q_FUNC_INFO << ", LOADING SHEET " << excel.sheets[s].name) 0690 auto* spreadsheet = new Spreadsheet(QString::fromStdString(excel.sheets[s].name)); 0691 loadSpreadsheet(spreadsheet, preview, workbook->name(), s); 0692 workbook->addChildFast(spreadsheet); 0693 } 0694 0695 return true; 0696 } 0697 0698 // load spreadsheet from spread (sheetIndex == -1) or from workbook (only sheet sheetIndex) 0699 // name is the spreadsheet name (spread) or the workbook name (if inside a workbook) 0700 bool OriginProjectParser::loadSpreadsheet(Spreadsheet* spreadsheet, bool preview, const QString& name, int sheetIndex) { 0701 DEBUG(Q_FUNC_INFO << ", own/workbook name = " << STDSTRING(name) << ", sheet index = " << sheetIndex); 0702 0703 // load spreadsheet data 0704 Origin::SpreadSheet spread; 0705 Origin::Excel excel; 0706 if (sheetIndex == -1) // spread 0707 spread = m_originFile->spread(findSpreadsheetByName(name)); 0708 else { 0709 excel = m_originFile->excel(findWorkbookByName(name)); 0710 spread = excel.sheets.at(sheetIndex); 0711 } 0712 0713 const size_t cols = spread.columns.size(); 0714 int rows = 0; 0715 for (size_t j = 0; j < cols; ++j) 0716 rows = std::max((int)spread.columns.at(j).data.size(), rows); 0717 // alternative: int rows = excel.maxRows; 0718 DEBUG(Q_FUNC_INFO << ", cols/maxRows = " << cols << "/" << rows); 0719 0720 // TODO QLocale locale = mw->locale(); 0721 0722 spreadsheet->setRowCount(rows); 0723 spreadsheet->setColumnCount((int)cols); 0724 if (sheetIndex == -1) 0725 spreadsheet->setComment(QString::fromStdString(spread.label)); 0726 else // TODO: only first spread should get the comments 0727 spreadsheet->setComment(QString::fromStdString(excel.label)); 0728 0729 // in Origin column width is measured in characters, we need to convert to pixels 0730 // TODO: determine the font used in Origin in order to get the same column width as in Origin 0731 QFont font; 0732 QFontMetrics fm(font); 0733 const int scaling_factor = fm.maxWidth(); 0734 0735 for (size_t j = 0; j < cols; ++j) { 0736 auto column = spread.columns[j]; 0737 auto* col = spreadsheet->column((int)j); 0738 0739 DEBUG(Q_FUNC_INFO << ", column " << j << ", name = " << column.name << ", dataset name = " << column.dataset_name) 0740 QString name(QString::fromStdString(column.name)); 0741 col->setName(name.remove(QRegularExpression(QStringLiteral(".*_")))); 0742 0743 if (preview) 0744 continue; 0745 0746 // TODO: we don't support any formulas for cells yet. 0747 DEBUG(Q_FUNC_INFO << ", column " << j << ", command = " << column.command) 0748 // if (column.command.size() > 0) 0749 // col->setFormula(Interval<int>(0, rows), QString::fromStdString(column.command)); 0750 0751 DEBUG(Q_FUNC_INFO << ", column " << j << ", full comment = " << column.comment) 0752 QString comment; 0753 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 0754 comment = QString::fromLatin1(column.comment.c_str()); 0755 else 0756 comment = QString::fromStdString(column.comment); 0757 if (comment.contains(QLatin1Char('@'))) // remove @ options 0758 comment.truncate(comment.indexOf(QLatin1Char('@'))); 0759 col->setComment(comment); 0760 col->setWidth((int)column.width * scaling_factor); 0761 0762 // plot designation 0763 switch (column.type) { 0764 case Origin::SpreadColumn::X: 0765 col->setPlotDesignation(AbstractColumn::PlotDesignation::X); 0766 break; 0767 case Origin::SpreadColumn::Y: 0768 col->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 0769 break; 0770 case Origin::SpreadColumn::Z: 0771 col->setPlotDesignation(AbstractColumn::PlotDesignation::Z); 0772 break; 0773 case Origin::SpreadColumn::XErr: 0774 col->setPlotDesignation(AbstractColumn::PlotDesignation::XError); 0775 break; 0776 case Origin::SpreadColumn::YErr: 0777 col->setPlotDesignation(AbstractColumn::PlotDesignation::YError); 0778 break; 0779 case Origin::SpreadColumn::Label: 0780 case Origin::SpreadColumn::NONE: 0781 default: 0782 col->setPlotDesignation(AbstractColumn::PlotDesignation::NoDesignation); 0783 } 0784 0785 QString format; 0786 switch (column.valueType) { 0787 case Origin::Numeric: { 0788 for (unsigned int i = column.beginRow; i < column.endRow; ++i) { 0789 const double value = column.data.at(i).as_double(); 0790 if (value != _ONAN) 0791 col->setValueAt(i, value); 0792 } 0793 0794 loadColumnNumericFormat(column, col); 0795 break; 0796 } 0797 case Origin::TextNumeric: { 0798 // A TextNumeric column can contain numeric and string values, there is no equivalent column mode in LabPlot. 0799 // -> Set the column mode as 'Numeric' or 'Text' depending on the type of first non-empty element in column. 0800 for (unsigned int i = column.beginRow; i < column.endRow; ++i) { 0801 const Origin::variant value(column.data.at(i)); 0802 if (value.type() == Origin::Variant::V_DOUBLE) { 0803 if (value.as_double() != _ONAN) 0804 break; 0805 } else { 0806 if (value.as_string() != nullptr) { 0807 col->setColumnMode(AbstractColumn::ColumnMode::Text); 0808 break; 0809 } 0810 } 0811 } 0812 0813 if (col->columnMode() == AbstractColumn::ColumnMode::Double) { 0814 for (unsigned int i = column.beginRow; i < column.endRow; ++i) { 0815 const double value = column.data.at(i).as_double(); 0816 if (column.data.at(i).type() == Origin::Variant::V_DOUBLE && value != _ONAN) 0817 col->setValueAt(i, value); 0818 } 0819 loadColumnNumericFormat(column, col); 0820 } else { 0821 for (unsigned int i = column.beginRow; i < column.endRow; ++i) { 0822 const Origin::variant value(column.data.at(i)); 0823 if (value.type() == Origin::Variant::V_STRING) { 0824 if (value.as_string() != nullptr) 0825 col->setTextAt(i, QLatin1String(value.as_string())); 0826 } else { 0827 if (value.as_double() != _ONAN) 0828 col->setTextAt(i, QString::number(value.as_double())); 0829 } 0830 } 0831 } 0832 break; 0833 } 0834 case Origin::Text: 0835 col->setColumnMode(AbstractColumn::ColumnMode::Text); 0836 for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) 0837 col->setTextAt(i, QLatin1String(column.data[i].as_string())); 0838 break; 0839 case Origin::Time: { 0840 switch (column.valueTypeSpecification + 128) { 0841 case Origin::TIME_HH_MM: 0842 format = QStringLiteral("hh:mm"); 0843 break; 0844 case Origin::TIME_HH: 0845 format = QStringLiteral("hh"); 0846 break; 0847 case Origin::TIME_HH_MM_SS: 0848 format = QStringLiteral("hh:mm:ss"); 0849 break; 0850 case Origin::TIME_HH_MM_SS_ZZ: 0851 format = QStringLiteral("hh:mm:ss.zzz"); 0852 break; 0853 case Origin::TIME_HH_AP: 0854 format = QStringLiteral("hh ap"); 0855 break; 0856 case Origin::TIME_HH_MM_AP: 0857 format = QStringLiteral("hh:mm ap"); 0858 break; 0859 case Origin::TIME_MM_SS: 0860 format = QStringLiteral("mm:ss"); 0861 break; 0862 case Origin::TIME_MM_SS_ZZ: 0863 format = QStringLiteral("mm:ss.zzz"); 0864 break; 0865 case Origin::TIME_HHMM: 0866 format = QStringLiteral("hhmm"); 0867 break; 0868 case Origin::TIME_HHMMSS: 0869 format = QStringLiteral("hhmmss"); 0870 break; 0871 case Origin::TIME_HH_MM_SS_ZZZ: 0872 format = QStringLiteral("hh:mm:ss.zzz"); 0873 break; 0874 } 0875 0876 for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) 0877 col->setValueAt(i, column.data[i].as_double()); 0878 col->setColumnMode(AbstractColumn::ColumnMode::DateTime); 0879 0880 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 0881 filter->setFormat(format); 0882 break; 0883 } 0884 case Origin::Date: { 0885 switch (column.valueTypeSpecification) { 0886 case Origin::DATE_DD_MM_YYYY: 0887 format = QStringLiteral("dd/MM/yyyy"); 0888 break; 0889 case Origin::DATE_DD_MM_YYYY_HH_MM: 0890 format = QStringLiteral("dd/MM/yyyy HH:mm"); 0891 break; 0892 case Origin::DATE_DD_MM_YYYY_HH_MM_SS: 0893 format = QStringLiteral("dd/MM/yyyy HH:mm:ss"); 0894 break; 0895 case Origin::DATE_DDMMYYYY: 0896 case Origin::DATE_DDMMYYYY_HH_MM: 0897 case Origin::DATE_DDMMYYYY_HH_MM_SS: 0898 format = QStringLiteral("dd.MM.yyyy"); 0899 break; 0900 case Origin::DATE_MMM_D: 0901 format = QStringLiteral("MMM d"); 0902 break; 0903 case Origin::DATE_M_D: 0904 format = QStringLiteral("M/d"); 0905 break; 0906 case Origin::DATE_D: 0907 format = QLatin1Char('d'); 0908 break; 0909 case Origin::DATE_DDD: 0910 case Origin::DATE_DAY_LETTER: 0911 format = QStringLiteral("ddd"); 0912 break; 0913 case Origin::DATE_YYYY: 0914 format = QStringLiteral("yyyy"); 0915 break; 0916 case Origin::DATE_YY: 0917 format = QStringLiteral("yy"); 0918 break; 0919 case Origin::DATE_YYMMDD: 0920 case Origin::DATE_YYMMDD_HH_MM: 0921 case Origin::DATE_YYMMDD_HH_MM_SS: 0922 case Origin::DATE_YYMMDD_HHMM: 0923 case Origin::DATE_YYMMDD_HHMMSS: 0924 format = QStringLiteral("yyMMdd"); 0925 break; 0926 case Origin::DATE_MMM: 0927 case Origin::DATE_MONTH_LETTER: 0928 format = QStringLiteral("MMM"); 0929 break; 0930 case Origin::DATE_M_D_YYYY: 0931 format = QStringLiteral("M-d-yyyy"); 0932 break; 0933 default: 0934 format = QStringLiteral("dd.MM.yyyy"); 0935 } 0936 0937 for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) 0938 col->setValueAt(i, column.data[i].as_double()); 0939 col->setColumnMode(AbstractColumn::ColumnMode::DateTime); 0940 0941 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 0942 filter->setFormat(format); 0943 break; 0944 } 0945 case Origin::Month: { 0946 switch (column.valueTypeSpecification) { 0947 case Origin::MONTH_MMM: 0948 format = QStringLiteral("MMM"); 0949 break; 0950 case Origin::MONTH_MMMM: 0951 format = QStringLiteral("MMMM"); 0952 break; 0953 case Origin::MONTH_LETTER: 0954 format = QLatin1Char('M'); 0955 break; 0956 } 0957 0958 for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) 0959 col->setValueAt(i, column.data[i].as_double()); 0960 col->setColumnMode(AbstractColumn::ColumnMode::Month); 0961 0962 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 0963 filter->setFormat(format); 0964 break; 0965 } 0966 case Origin::Day: { 0967 switch (column.valueTypeSpecification) { 0968 case Origin::DAY_DDD: 0969 format = QStringLiteral("ddd"); 0970 break; 0971 case Origin::DAY_DDDD: 0972 format = QStringLiteral("dddd"); 0973 break; 0974 case Origin::DAY_LETTER: 0975 format = QLatin1Char('d'); 0976 break; 0977 } 0978 0979 for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) 0980 col->setValueAt(i, column.data[i].as_double()); 0981 col->setColumnMode(AbstractColumn::ColumnMode::Day); 0982 0983 auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter()); 0984 filter->setFormat(format); 0985 break; 0986 } 0987 case Origin::ColumnHeading: 0988 case Origin::TickIndexedDataset: 0989 case Origin::Categorical: 0990 break; 0991 } 0992 } 0993 0994 // TODO: "hidden" not supported yet 0995 // if (spread.hidden || spread.loose) 0996 // mw->hideWindow(spreadsheet); 0997 0998 return true; 0999 } 1000 1001 void OriginProjectParser::loadColumnNumericFormat(const Origin::SpreadColumn& originColumn, Column* column) const { 1002 if (originColumn.numericDisplayType != 0) { 1003 int fi = 0; 1004 switch (originColumn.valueTypeSpecification) { 1005 case Origin::Decimal: 1006 fi = 1; 1007 break; 1008 case Origin::Scientific: 1009 fi = 2; 1010 break; 1011 case Origin::Engineering: 1012 case Origin::DecimalWithMarks: 1013 break; 1014 } 1015 1016 auto* filter = static_cast<Double2StringFilter*>(column->outputFilter()); 1017 filter->setNumericFormat(fi); 1018 filter->setNumDigits(originColumn.decimalPlaces); 1019 } 1020 } 1021 1022 bool OriginProjectParser::loadMatrixWorkbook(Workbook* workbook, bool preview) { 1023 DEBUG(Q_FUNC_INFO) 1024 // load matrix workbook sheets 1025 const auto& originMatrix = m_originFile->matrix(findMatrixByName(workbook->name())); 1026 for (size_t s = 0; s < originMatrix.sheets.size(); ++s) { 1027 auto* matrix = new Matrix(QString::fromStdString(originMatrix.sheets[s].name)); 1028 loadMatrix(matrix, preview, s, workbook->name()); 1029 workbook->addChildFast(matrix); 1030 } 1031 1032 return true; 1033 } 1034 1035 bool OriginProjectParser::loadMatrix(Matrix* matrix, bool preview, size_t sheetIndex, const QString& mwbName) { 1036 DEBUG(Q_FUNC_INFO) 1037 // import matrix data 1038 const auto& originMatrix = m_originFile->matrix(findMatrixByName(mwbName)); 1039 1040 if (preview) 1041 return true; 1042 1043 // in Origin column width is measured in characters, we need to convert to pixels 1044 // TODO: determine the font used in Origin in order to get the same column width as in Origin 1045 QFont font; 1046 QFontMetrics fm(font); 1047 const int scaling_factor = fm.maxWidth(); 1048 1049 const auto& layer = originMatrix.sheets.at(sheetIndex); 1050 const int colCount = layer.columnCount; 1051 const int rowCount = layer.rowCount; 1052 1053 matrix->setRowCount(rowCount); 1054 matrix->setColumnCount(colCount); 1055 matrix->setFormula(QString::fromStdString(layer.command)); 1056 1057 // TODO: how to handle different widths for different columns? 1058 for (int j = 0; j < colCount; j++) 1059 matrix->setColumnWidth(j, layer.width * scaling_factor); 1060 1061 // TODO: check column major vs. row major to improve the performance here 1062 for (int i = 0; i < rowCount; i++) { 1063 for (int j = 0; j < colCount; j++) 1064 matrix->setCell(i, j, layer.data[j + i * colCount]); 1065 } 1066 1067 char format = 'g'; 1068 // TODO: prec not support by Matrix 1069 // int prec = 6; 1070 switch (layer.valueTypeSpecification) { 1071 case 0: // Decimal 1000 1072 format = 'f'; 1073 // prec = layer.decimalPlaces; 1074 break; 1075 case 1: // Scientific 1076 format = 'e'; 1077 // prec = layer.decimalPlaces; 1078 break; 1079 case 2: // Engineering 1080 case 3: // Decimal 1,000 1081 format = 'g'; 1082 // prec = layer.significantDigits; 1083 break; 1084 } 1085 1086 matrix->setNumericFormat(format); 1087 1088 return true; 1089 } 1090 1091 bool OriginProjectParser::loadWorksheet(Worksheet* worksheet, bool preview) { 1092 DEBUG(Q_FUNC_INFO << ", preview = " << preview) 1093 if (worksheet->parentAspect()) 1094 DEBUG(Q_FUNC_INFO << ", parent PATH " << STDSTRING(worksheet->parentAspect()->path())) 1095 1096 // load worksheet data 1097 const auto& graph = m_originFile->graph(findWorksheetByName(worksheet->name())); 1098 DEBUG(Q_FUNC_INFO << ", worksheet name = " << graph.name); 1099 worksheet->setComment(QLatin1String(graph.label.c_str())); 1100 1101 // TODO: width, height, view mode (print view, page view, window view, draft view) 1102 // Origin allows to freely resize the window and ajusts the size of the plot (layer) automatically 1103 // by keeping a certain width-to-height ratio. It's not clear what the actual size of the plot/layer is and how to handle this. 1104 // For now we simply create a new worksheet here with it's default size and make it using the whole view size. 1105 // Later we can decide to use one of the following properties: 1106 // 1) Window.frameRect gives Rect-corner coordinates (in pixels) of the Window object 1107 // 2) GraphLayer.clientRect gives Rect-corner coordinates (pixels) of the Layer inside the (printer?) page. 1108 // 3) Graph.width, Graph.height give the (printer?) page size in pixels. 1109 // const QRectF size(0, 0, 1110 // Worksheet::convertToSceneUnits(graph.width/600., Worksheet::Inch), 1111 // Worksheet::convertToSceneUnits(graph.height/600., Worksheet::Inch)); 1112 // worksheet->setPageRect(size); 1113 graphSize.rwidth() = graph.width; 1114 graphSize.rheight() = graph.height; 1115 DEBUG(Q_FUNC_INFO << ", GRAPH width/height (px) = " << graphSize.width() << "/" << graphSize.height()) 1116 // Graphic elements in Origin are scaled relative to the dimensions of the page (Format->Page) with 600 DPI (>=9.6), 300 DPI (<9.6) 1117 double dpi = 600.; 1118 if (m_originFile->version() < 9.6) 1119 dpi = 300.; 1120 DEBUG(Q_FUNC_INFO << ", GRAPH width/height (cm) = " << graphSize.width() * GSL_CONST_CGS_INCH / dpi << "/" << graphSize.height() * GSL_CONST_CGS_INCH / dpi) 1121 // Origin scales text and plots with the size of the layer when no fixed size is used (Layer properties->Size) 1122 // so we scale all text and plots with a scaling factor to the whole view height (29.5 cm) used as default 1123 const double fixedHeight = 29.5; // full height [cm] 1124 elementScalingFactor = fixedHeight / (graph.height * GSL_CONST_CGS_INCH / dpi); 1125 // not using the full value for scaling text is better in most cases 1126 textScalingFactor = 1. + (elementScalingFactor - 1.) / 2.; 1127 DEBUG(Q_FUNC_INFO << ", ELEMENT SCALING FACTOR = " << elementScalingFactor) 1128 DEBUG(Q_FUNC_INFO << ", TEXT SCALING FACTOR = " << textScalingFactor) 1129 // default values (1000/1000) 1130 // DEBUG(Q_FUNC_INFO << ", WORKSHEET width/height = " << worksheet->pageRect().width() << "/" << worksheet->pageRect().height()) 1131 1132 worksheet->setUseViewSize(true); 1133 1134 QHash<TextLabel*, QSizeF> textLabelPositions; 1135 1136 // worksheet background color 1137 const Origin::ColorGradientDirection bgColorGradient = graph.windowBackgroundColorGradient; 1138 const Origin::Color bgBaseColor = graph.windowBackgroundColorBase; 1139 const Origin::Color bgEndColor = graph.windowBackgroundColorEnd; 1140 worksheet->background()->setColorStyle(backgroundColorStyle(bgColorGradient)); 1141 switch (bgColorGradient) { 1142 case Origin::ColorGradientDirection::NoGradient: 1143 case Origin::ColorGradientDirection::TopLeft: 1144 case Origin::ColorGradientDirection::Left: 1145 case Origin::ColorGradientDirection::BottomLeft: 1146 case Origin::ColorGradientDirection::Top: 1147 worksheet->background()->setFirstColor(color(bgEndColor)); 1148 worksheet->background()->setSecondColor(color(bgBaseColor)); 1149 break; 1150 case Origin::ColorGradientDirection::Center: 1151 break; 1152 case Origin::ColorGradientDirection::Bottom: 1153 case Origin::ColorGradientDirection::TopRight: 1154 case Origin::ColorGradientDirection::Right: 1155 case Origin::ColorGradientDirection::BottomRight: 1156 worksheet->background()->setFirstColor(color(bgBaseColor)); 1157 worksheet->background()->setSecondColor(color(bgEndColor)); 1158 } 1159 1160 // TODO: do we need changes on the worksheet layout? 1161 1162 // process Origin's graph layers - add new plot areas or new coordinate system in the same plot area, depending on the global setting 1163 // https://www.originlab.com/doc/Origin-Help/MultiLayer-Graph 1164 int layerIndex = 0; // index of the graph layer 1165 CartesianPlot* plot = nullptr; 1166 Origin::Rect layerRect; 1167 for (const auto& layer : graph.layers) { 1168 DEBUG(Q_FUNC_INFO << ", Graph Layer " << layerIndex + 1) 1169 if (layer.is3D()) { 1170 // TODO: add an "UnsupportedAspect" here since we don't support 3D yet 1171 break; 1172 } 1173 1174 layerRect = layer.clientRect; 1175 // DEBUG(Q_FUNC_INFO << ", layer left/right (px) = " << layerRect.left << "/" << layerRect.right) 1176 // DEBUG(Q_FUNC_INFO << ", layer top/bottom (px) = " << layerRect.top << "/" << layerRect.bottom) 1177 // DEBUG(Q_FUNC_INFO << ", layer width/height (px) = " << layerRect.width() << "/" << layerRect.height()) 1178 1179 // create a new plot if we're 1180 // 1. interpreting every layer as a new plot 1181 // 2. interpreting every layer as a new coordinate system in the same and single plot and no plot was created yet 1182 DEBUG(Q_FUNC_INFO << ", layer as plot area = " << m_graphLayerAsPlotArea) 1183 if (m_graphLayerAsPlotArea || (!m_graphLayerAsPlotArea && !plot)) { 1184 plot = new CartesianPlot(i18n("Plot%1", QString::number(layerIndex + 1))); 1185 worksheet->addChildFast(plot); 1186 plot->setIsLoading(true); 1187 } 1188 1189 loadGraphLayer(layer, plot, layerIndex, textLabelPositions, preview); 1190 ++layerIndex; 1191 } 1192 1193 // padding 1194 plot->setSymmetricPadding(false); 1195 int numberOfLayer = layerIndex + 1; 1196 DEBUG(Q_FUNC_INFO << ", number of layer = " << numberOfLayer) 1197 if (numberOfLayer == 1 || !m_graphLayerAsPlotArea) { // use layer clientRect for padding 1198 DEBUG(Q_FUNC_INFO << ", using layer rect for padding") 1199 double aspectRatio = graphSize.width() / graphSize.height(); 1200 1201 double leftPadding = layerRect.left / (double)graphSize.width() * aspectRatio * fixedHeight; 1202 double topPadding = layerRect.top / (double)graphSize.height() * fixedHeight; 1203 double rightPadding = (graphSize.width() - layerRect.right) / (double)graphSize.width() * aspectRatio * fixedHeight; 1204 double bottomPadding = (graphSize.height() - layerRect.bottom) / (double)graphSize.height() * fixedHeight; 1205 plot->setHorizontalPadding(Worksheet::convertToSceneUnits(leftPadding, Worksheet::Unit::Centimeter)); 1206 plot->setVerticalPadding(Worksheet::convertToSceneUnits(topPadding, Worksheet::Unit::Centimeter)); 1207 plot->setRightPadding(Worksheet::convertToSceneUnits(rightPadding, Worksheet::Unit::Centimeter)); 1208 plot->setBottomPadding(Worksheet::convertToSceneUnits(bottomPadding, Worksheet::Unit::Centimeter)); 1209 } else { 1210 plot->setHorizontalPadding(plot->horizontalPadding() * elementScalingFactor); 1211 plot->setVerticalPadding(plot->verticalPadding() * elementScalingFactor); 1212 plot->setRightPadding(plot->rightPadding() * elementScalingFactor); 1213 plot->setBottomPadding(plot->bottomPadding() * elementScalingFactor); 1214 } 1215 DEBUG(Q_FUNC_INFO << ", PADDING (H/V) = " << plot->horizontalPadding() << ", " << plot->verticalPadding()) 1216 DEBUG(Q_FUNC_INFO << ", PADDING (R/B) = " << plot->rightPadding() << ", " << plot->bottomPadding()) 1217 1218 if (!preview) { 1219 worksheet->updateLayout(); 1220 1221 // worksheet and plots got their sizes, 1222 //-> position all text labels inside the plots correctly by converting 1223 // the relative positions determined above to the absolute values 1224 auto it = textLabelPositions.constBegin(); 1225 while (it != textLabelPositions.constEnd()) { 1226 auto* label = it.key(); 1227 const auto& ratios = it.value(); 1228 1229 auto position = label->position(); 1230 position.point.setX(ratios.width()); 1231 position.point.setY(ratios.height()); 1232 position.horizontalPosition = WorksheetElement::HorizontalPosition::Relative; 1233 position.verticalPosition = WorksheetElement::VerticalPosition::Relative; 1234 // achor depending on rotation 1235 auto rotation = label->rotationAngle(); 1236 auto hAlign = WorksheetElement::HorizontalAlignment::Left; 1237 auto vAlign = WorksheetElement::VerticalAlignment::Top; 1238 if (rotation > 45 && rotation <= 135) // left/bottom 1239 vAlign = WorksheetElement::VerticalAlignment::Bottom; 1240 else if (rotation > 135 && rotation <= 225) { // right/bottom 1241 hAlign = WorksheetElement::HorizontalAlignment::Right; 1242 vAlign = WorksheetElement::VerticalAlignment::Bottom; 1243 } else if (rotation > 225) // right/top 1244 hAlign = WorksheetElement::HorizontalAlignment::Right; 1245 1246 label->setHorizontalAlignment(hAlign); 1247 label->setVerticalAlignment(vAlign); 1248 1249 label->setPosition(position); 1250 1251 ++it; 1252 } 1253 } 1254 1255 DEBUG(Q_FUNC_INFO << " DONE"); 1256 return true; 1257 } 1258 1259 void OriginProjectParser::loadGraphLayer(const Origin::GraphLayer& layer, 1260 CartesianPlot* plot, 1261 int layerIndex, 1262 QHash<TextLabel*, QSizeF>& textLabelPositions, 1263 bool preview) { 1264 DEBUG(Q_FUNC_INFO << ", NEW GRAPH LAYER") 1265 1266 // background color 1267 const auto& regColor = layer.backgroundColor; 1268 if (regColor.type == Origin::Color::None) 1269 plot->plotArea()->background()->setOpacity(0); 1270 else 1271 plot->plotArea()->background()->setFirstColor(color(regColor)); 1272 1273 // border 1274 if (layer.borderType == Origin::BorderType::None) 1275 plot->plotArea()->borderLine()->setStyle(Qt::NoPen); 1276 else 1277 plot->plotArea()->borderLine()->setStyle(Qt::SolidLine); 1278 1279 // ranges 1280 const auto& originXAxis = layer.xAxis; 1281 const auto& originYAxis = layer.yAxis; 1282 1283 Range<double> xRange(originXAxis.min, originXAxis.max); 1284 Range<double> yRange(originYAxis.min, originYAxis.max); 1285 xRange.setAutoScale(false); 1286 yRange.setAutoScale(false); 1287 1288 if (m_graphLayerAsPlotArea) { // graph layer is read as a new plot area 1289 // set the ranges for default coordinate system 1290 plot->setRangeDefault(Dimension::X, xRange); 1291 plot->setRangeDefault(Dimension::Y, yRange); 1292 } else { // graph layer is read as a new coordinate system in the same plot area 1293 // create a new coordinate systems and set the ranges for it 1294 if (layerIndex > 0) { 1295 // check if identical range already exists 1296 int selectedXRangeIndex = -1; 1297 for (int i = 0; i < plot->rangeCount(Dimension::X); i++) { 1298 const auto& range = plot->range(Dimension::X, i); 1299 if (range == xRange) { 1300 selectedXRangeIndex = i; 1301 break; 1302 } 1303 } 1304 int selectedYRangeIndex = -1; 1305 for (int i = 0; i < plot->rangeCount(Dimension::Y); i++) { 1306 const auto& range = plot->range(Dimension::Y, i); 1307 if (range == yRange) { 1308 selectedYRangeIndex = i; 1309 break; 1310 } 1311 } 1312 1313 if (selectedXRangeIndex < 0) { 1314 plot->addXRange(); 1315 selectedXRangeIndex = plot->rangeCount(Dimension::X) - 1; 1316 } 1317 if (selectedYRangeIndex < 0) { 1318 plot->addYRange(); 1319 selectedYRangeIndex = plot->rangeCount(Dimension::Y) - 1; 1320 } 1321 1322 plot->addCoordinateSystem(); 1323 // set ranges for new coordinate system 1324 plot->setCoordinateSystemRangeIndex(layerIndex, Dimension::X, selectedXRangeIndex); 1325 plot->setCoordinateSystemRangeIndex(layerIndex, Dimension::Y, selectedYRangeIndex); 1326 } 1327 plot->setRange(Dimension::X, layerIndex, xRange); 1328 plot->setRange(Dimension::Y, layerIndex, yRange); 1329 } 1330 1331 // scales 1332 switch (originXAxis.scale) { 1333 case Origin::GraphAxis::Linear: 1334 plot->setXRangeScale(RangeT::Scale::Linear); 1335 break; 1336 case Origin::GraphAxis::Log10: 1337 plot->setXRangeScale(RangeT::Scale::Log10); 1338 break; 1339 case Origin::GraphAxis::Ln: 1340 plot->setXRangeScale(RangeT::Scale::Ln); 1341 break; 1342 case Origin::GraphAxis::Log2: 1343 plot->setXRangeScale(RangeT::Scale::Log2); 1344 break; 1345 case Origin::GraphAxis::Reciprocal: 1346 plot->setXRangeScale(RangeT::Scale::Inverse); 1347 break; 1348 case Origin::GraphAxis::Probability: 1349 case Origin::GraphAxis::Probit: 1350 case Origin::GraphAxis::OffsetReciprocal: 1351 case Origin::GraphAxis::Logit: 1352 // TODO: 1353 plot->setXRangeScale(RangeT::Scale::Linear); 1354 break; 1355 } 1356 1357 switch (originYAxis.scale) { 1358 case Origin::GraphAxis::Linear: 1359 plot->setYRangeScale(RangeT::Scale::Linear); 1360 break; 1361 case Origin::GraphAxis::Log10: 1362 plot->setYRangeScale(RangeT::Scale::Log10); 1363 break; 1364 case Origin::GraphAxis::Ln: 1365 plot->setYRangeScale(RangeT::Scale::Ln); 1366 break; 1367 case Origin::GraphAxis::Log2: 1368 plot->setYRangeScale(RangeT::Scale::Log2); 1369 break; 1370 case Origin::GraphAxis::Reciprocal: 1371 plot->setYRangeScale(RangeT::Scale::Inverse); 1372 break; 1373 case Origin::GraphAxis::Probability: 1374 case Origin::GraphAxis::Probit: 1375 case Origin::GraphAxis::OffsetReciprocal: 1376 case Origin::GraphAxis::Logit: 1377 // TODO: 1378 plot->setYRangeScale(RangeT::Scale::Linear); 1379 break; 1380 } 1381 1382 // add legend if available 1383 const auto& originLegend = layer.legend; 1384 if (!originLegend.text.empty()) { 1385 QString legendText; 1386 // not using UTF8! (9.85, TO) 1387 legendText = QString::fromLatin1(originLegend.text.c_str()); 1388 DEBUG(Q_FUNC_INFO << ", legend text = \"" << STDSTRING(legendText) << "\""); 1389 1390 auto* legend = new CartesianPlotLegend(i18n("legend")); 1391 1392 plot->addLegend(legend); 1393 1394 // set legend text size 1395 DEBUG(Q_FUNC_INFO << ", legend text size = " << originLegend.fontSize); 1396 auto labelFont = legend->labelFont(); 1397 labelFont.setPointSize(Worksheet::convertToSceneUnits(originLegend.fontSize * textScalingFactor, Worksheet::Unit::Point)); 1398 legend->setLabelFont(labelFont); 1399 1400 // Origin's legend uses "\l(...)" or "\L(...)" string to format the legend symbol 1401 // and "%(...) to format the legend text for each curve 1402 // s. a. https://www.originlab.com/doc/Origin-Help/Legend-ManualControl 1403 // the text before these formatting tags, if available, is interpreted as the legend title 1404 1405 // search for the first occurrence of the legend symbol substring 1406 int index = legendText.indexOf(QLatin1String("\\l("), 0, Qt::CaseInsensitive); 1407 QString legendTitle; 1408 if (index != -1) 1409 legendTitle = legendText.left(index); 1410 else { 1411 // check legend text 1412 index = legendText.indexOf(QLatin1String("%(")); 1413 if (index != -1) 1414 legendTitle = legendText.left(index); 1415 } 1416 1417 legendTitle = legendTitle.trimmed(); 1418 if (!legendTitle.isEmpty()) 1419 legendTitle = parseOriginText(legendTitle); 1420 if (!legendTitle.isEmpty()) { 1421 DEBUG(Q_FUNC_INFO << ", legend title = \"" << STDSTRING(legendTitle) << "\""); 1422 legend->title()->setText(legendTitle); 1423 } else { 1424 DEBUG(Q_FUNC_INFO << ", legend title is empty"); 1425 } 1426 1427 // TODO: text color 1428 // const Origin::Color& originColor = originLegend.color; 1429 1430 // position 1431 // TODO: In Origin the legend can be placed outside of the plot which is not possible in LabPlot. 1432 // To achieve this we'll need to increase padding area in the plot to place the legend outside of the plot area. 1433 // graphSize (% of page), layer.clientRect (% of layer) -> see loadWorksheet() 1434 auto legendRect = originLegend.clientRect; 1435 // auto layerRect = layer.clientRect; // for % of layer 1436 DEBUG(Q_FUNC_INFO << ", LEGEND position (l/t) << " << legendRect.left << "/" << legendRect.top) 1437 DEBUG(Q_FUNC_INFO << ", page size = " << graphSize.width() << "/" << graphSize.height()) 1438 CartesianPlotLegend::PositionWrapper position; 1439 QSizeF relativePosition(legendRect.left / (double)graphSize.width(), legendRect.top / (double)graphSize.height()); 1440 // achor depending on rotation 1441 auto rotation = originLegend.rotation; 1442 if (rotation > 45 && rotation <= 135) // left/bottom 1443 relativePosition.setHeight(legendRect.bottom / (double)graphSize.height()); 1444 else if (rotation > 135 && rotation <= 225) { // right/bottom 1445 relativePosition.setWidth(legendRect.right / (double)graphSize.width()); 1446 relativePosition.setHeight(legendRect.bottom / (double)graphSize.height()); 1447 } else if (rotation > 225) // right/top 1448 relativePosition.setWidth(legendRect.right / (double)graphSize.width()); 1449 1450 DEBUG(Q_FUNC_INFO << ", relative position to page = " << relativePosition.width() << "/" << relativePosition.height()) 1451 1452 position.point.setX(relativePosition.width()); 1453 position.point.setY(relativePosition.height()); 1454 // achor depending on rotation 1455 position.horizontalPosition = WorksheetElement::HorizontalPosition::Relative; 1456 position.verticalPosition = WorksheetElement::VerticalPosition::Relative; 1457 auto hAlign = WorksheetElement::HorizontalAlignment::Left; 1458 auto vAlign = WorksheetElement::VerticalAlignment::Top; 1459 if (rotation > 45 && rotation <= 135) // left/bottom 1460 vAlign = WorksheetElement::VerticalAlignment::Bottom; 1461 else if (rotation > 135 && rotation <= 225) { // right/bottom 1462 hAlign = WorksheetElement::HorizontalAlignment::Right; 1463 vAlign = WorksheetElement::VerticalAlignment::Bottom; 1464 } else if (rotation > 225) // right/top 1465 hAlign = WorksheetElement::HorizontalAlignment::Right; 1466 legend->setHorizontalAlignment(hAlign); 1467 legend->setVerticalAlignment(vAlign); 1468 1469 legend->setPosition(position); 1470 1471 // rotation 1472 legend->setRotationAngle(originLegend.rotation); 1473 1474 // border line 1475 if (originLegend.borderType == Origin::BorderType::None) 1476 legend->borderLine()->setStyle(Qt::NoPen); 1477 else 1478 legend->borderLine()->setStyle(Qt::SolidLine); 1479 1480 // background color, determine it with the help of the border type 1481 if (originLegend.borderType == Origin::BorderType::DarkMarble) 1482 legend->background()->setFirstColor(Qt::darkGray); 1483 else if (originLegend.borderType == Origin::BorderType::BlackOut) 1484 legend->background()->setFirstColor(Qt::black); 1485 else 1486 legend->background()->setFirstColor(Qt::white); 1487 1488 // save current legend text 1489 m_legendText = legendText; 1490 } 1491 1492 // curves 1493 loadCurves(layer, plot, layerIndex, preview); 1494 1495 // reading of other properties is not relevant for the dependency checks in the preview, skip them 1496 if (preview) 1497 return; 1498 1499 // texts 1500 for (const auto& t : layer.texts) { 1501 DEBUG(Q_FUNC_INFO << ", EXTRA TEXT = " << t.text); 1502 auto* label = new TextLabel(QStringLiteral("text label")); 1503 QString text; 1504 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 1505 text = QString::fromLatin1(t.text.c_str()); 1506 else 1507 text = QString::fromStdString(t.text); 1508 QTextEdit te(parseOriginText(text)); 1509 te.selectAll(); 1510 DEBUG(Q_FUNC_INFO << ", font size = " << t.fontSize) 1511 te.setFontPointSize(int(t.fontSize)); // no scaling 1512 te.setTextColor(OriginProjectParser::color(t.color)); 1513 label->setText(te.toHtml()); 1514 // DEBUG(" TEXT = " << STDSTRING(label->text().text)) 1515 1516 plot->addChild(label); 1517 label->setParentGraphicsItem(plot->graphicsItem()); 1518 1519 // position 1520 // determine the relative position to the graph 1521 QSizeF relativePosition(t.clientRect.left / (double)graphSize.width(), t.clientRect.top / (double)graphSize.height()); 1522 // achor depending on rotation 1523 if (t.rotation > 45 && t.rotation <= 135) // left/bottom 1524 relativePosition.setHeight(t.clientRect.bottom / (double)graphSize.height()); 1525 else if (t.rotation > 135 && t.rotation <= 225) { // right/bottom 1526 relativePosition.setWidth(t.clientRect.right / (double)graphSize.width()); 1527 relativePosition.setHeight(t.clientRect.bottom / (double)graphSize.height()); 1528 } else if (t.rotation > 225) // right/top 1529 relativePosition.setWidth(t.clientRect.right / (double)graphSize.height()); 1530 1531 DEBUG(Q_FUNC_INFO << ", relative position to page = " << relativePosition.width() << "/" << relativePosition.height()) 1532 textLabelPositions[label] = relativePosition; 1533 1534 // rotation 1535 label->setRotationAngle(t.rotation); 1536 1537 // TODO: 1538 // int tab; 1539 // BorderType borderType; 1540 // Attach attach; 1541 } 1542 1543 // axes 1544 DEBUG(Q_FUNC_INFO << ", layer.curves.size() = " << layer.curves.size()) 1545 if (layer.curves.empty()) // no curves, just axes 1546 loadAxes(layer, plot, layerIndex, QLatin1String("X Axis Title"), QLatin1String("Y Axis Title")); 1547 else { 1548 auto originCurve = layer.curves.at(0); 1549 // see loadCurves() 1550 QString dataName(QString::fromStdString(originCurve.dataName)); 1551 DEBUG(Q_FUNC_INFO << ", curve data name " << STDSTRING(dataName)) 1552 QString containerName = dataName.right(dataName.length() - 2); // strip "E_" or "T_" 1553 auto sheet = getSpreadsheetByName(containerName); 1554 1555 QString xColumnName; 1556 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 1557 xColumnName = QString::fromLatin1(originCurve.xColumnName.c_str()); 1558 else 1559 xColumnName = QString::fromStdString(originCurve.xColumnName); 1560 1561 auto xColumn = sheet.columns[findColumnByName(sheet, xColumnName)]; 1562 QString xColumnInfo = xColumnName; 1563 if (xColumn.comment.length() > 0) { // long name(, unit(, comment)) 1564 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 1565 xColumnInfo = QString::fromLatin1(xColumn.comment.c_str()); 1566 else 1567 xColumnInfo = QString::fromStdString(xColumn.comment); 1568 if (xColumnInfo.contains(QLatin1Char('@'))) // remove @ options 1569 xColumnInfo.truncate(xColumnInfo.indexOf(QLatin1Char('@'))); 1570 } 1571 DEBUG(Q_FUNC_INFO << ", x column name = " << STDSTRING(xColumnName)); 1572 DEBUG(Q_FUNC_INFO << ", x column info = " << STDSTRING(xColumnInfo)); 1573 1574 // same for y 1575 QString yColumnName; 1576 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 1577 yColumnName = QString::fromLatin1(originCurve.yColumnName.c_str()); 1578 else 1579 yColumnName = QString::fromStdString(originCurve.yColumnName); 1580 1581 auto yColumn = sheet.columns[findColumnByName(sheet, yColumnName)]; 1582 QString yColumnInfo = yColumnName; 1583 if (yColumn.comment.length() > 0) { // long name(, unit(, comment)) 1584 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 1585 yColumnInfo = QString::fromLatin1(yColumn.comment.c_str()); 1586 else 1587 yColumnInfo = QString::fromStdString(yColumn.comment); 1588 if (yColumnInfo.contains(QLatin1Char('@'))) // remove @ options 1589 yColumnInfo.truncate(yColumnInfo.indexOf(QLatin1Char('@'))); 1590 } 1591 DEBUG(Q_FUNC_INFO << ", y column name = " << STDSTRING(yColumnName)); 1592 DEBUG(Q_FUNC_INFO << ", y column info = " << STDSTRING(yColumnInfo)); 1593 1594 loadAxes(layer, plot, layerIndex, xColumnInfo, yColumnInfo); 1595 } 1596 1597 // range breaks 1598 // TODO 1599 } 1600 1601 void OriginProjectParser::loadCurves(const Origin::GraphLayer& layer, CartesianPlot* plot, int layerIndex, bool preview) { 1602 DEBUG(Q_FUNC_INFO) 1603 1604 int curveIndex = 1; 1605 for (const auto& originCurve : layer.curves) { 1606 QString dataName(QString::fromStdString(originCurve.dataName)); 1607 DEBUG(Q_FUNC_INFO << ", NEW CURVE (curve data name) " << STDSTRING(dataName)) 1608 DEBUG(Q_FUNC_INFO << ", curve x column name = " << originCurve.xColumnName) 1609 DEBUG(Q_FUNC_INFO << ", curve y column name = " << originCurve.yColumnName) 1610 DEBUG(Q_FUNC_INFO << ", curve x data name = " << originCurve.xDataName) 1611 1612 switch (dataName.at(0).toLatin1()) { 1613 case 'T': // Spreadsheet 1614 case 'E': { // Workbook 1615 if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::Scatter 1616 || originCurve.type == Origin::GraphCurve::LineSymbol || originCurve.type == Origin::GraphCurve::ErrorBar 1617 || originCurve.type == Origin::GraphCurve::XErrorBar) { 1618 auto containerName = dataName.right(dataName.length() - 2); // strip "E_" or "T_" 1619 auto sheet = getSpreadsheetByName(containerName); 1620 1621 auto columnName(QString::fromStdString(originCurve.yColumnName)); 1622 auto column = sheet.columns[findColumnByName(sheet, columnName)]; 1623 QString shortName = columnName, curveName = columnName; 1624 QString longName, unit, comments; 1625 if (column.comment.length() > 0) { 1626 auto columnInfo = QString::fromStdString(column.comment); // long name(, unit(, comment)) 1627 DEBUG(Q_FUNC_INFO << ", y column full comment = \"" << column.comment << "\"") 1628 if (columnInfo.contains(QLatin1Char('@'))) // remove @ options 1629 columnInfo.truncate(columnInfo.indexOf(QLatin1Char('@'))); 1630 1631 auto infoList = columnInfo.split(QRegularExpression(QLatin1String("[\r\n]")), Qt::SkipEmptyParts); 1632 1633 switch (infoList.size()) { 1634 case 2: // long name, unit 1635 unit = infoList.at(1); 1636 // fallthrough 1637 case 1: // long name 1638 longName = infoList.at(0); 1639 curveName = longName; 1640 break; 1641 default: // long name, unit, comment 1642 longName = infoList.at(0); 1643 unit = infoList.at(1); 1644 comments = infoList.at(2); 1645 curveName = comments; 1646 } 1647 } 1648 if (longName.isEmpty()) 1649 longName = shortName; 1650 if (comments.isEmpty()) 1651 comments = longName; 1652 DEBUG(Q_FUNC_INFO << ", default curve name = \"" << curveName.toStdString() << "\"") 1653 1654 // TODO: custom legend not used yet 1655 // Origin's legend uses "%(...) to format the legend text for each curve 1656 // s. a. https://www.originlab.com/doc/Origin-Help/Legend-ManualControl 1657 1658 // parse and use legend text (not used) 1659 // find substring between %c{curveIndex} and %c{curveIndex+1} 1660 // int pos1 = legendText.indexOf(QStringLiteral("\\c{%1}").arg(curveIndex)) + 5; 1661 // int pos2 = legendText.indexOf(QStringLiteral("\\c{%1}").arg(curveIndex + 1)); 1662 // QString curveText = legendText.mid(pos1, pos2 - pos1); 1663 // replace %(1), %(2), etc. with curve name 1664 // curveText.replace(QStringLiteral("%(%1)").arg(curveIndex), legendText); 1665 1666 // curveText = curveText.trimmed(); 1667 // DEBUG(" curve " << curveIndex << " text = \"" << STDSTRING(curveText) << "\""); 1668 // TODO: curve (legend) does not support HTML text yet. 1669 // auto* curve = new XYCurve(curveText); 1670 1671 // check if curve is in actual legendText 1672 // examples: \l(1) %(1), \l(2) %(2) text, \l(3) %(3,@LG), .. 1673 DEBUG(Q_FUNC_INFO << ", LEGEND TEXT = \"" << m_legendText.toStdString() << "\"") 1674 // DEBUG(Q_FUNC_INFO << ", layer index = " << layerIndex + 1 << ", curve index = " << curveIndex) 1675 bool enableCurveInLegend = false; 1676 QString legendCurveString; 1677 // find \l(C) 1678 int pos1 = m_legendText.indexOf(QStringLiteral("\\l(%1)").arg(curveIndex)); 1679 if (pos1 == -1) // try \l(L.C) 1680 pos1 = m_legendText.indexOf(QStringLiteral("\\l(%1.%2)").arg(layerIndex + 1).arg(curveIndex)); 1681 else // remove symbol string 1682 m_legendText.remove(QStringLiteral("\\l(%1)").arg(curveIndex)); 1683 1684 if (pos1 != -1) { // \l(C) or \l(L.C) found 1685 // remove symbol string 1686 m_legendText.remove(QStringLiteral("\\l(%1.%2)").arg(layerIndex + 1).arg(curveIndex)); 1687 1688 // whole line 1689 int pos2 = m_legendText.indexOf(QRegularExpression(QLatin1String("[\r\n]")), pos1); 1690 if (pos2 == -1) 1691 legendCurveString = m_legendText.mid(pos1, pos2); 1692 else 1693 legendCurveString = m_legendText.mid(pos1, pos2 - pos1); 1694 DEBUG(Q_FUNC_INFO << ", legend curve string = \"" << legendCurveString.toStdString() << "\"") 1695 if (!legendCurveString.isEmpty()) { // don't include empty entries 1696 // replace %(C) and %(L.C) 1697 // see https://www.originlab.com/doc/en/LabTalk/ref/Text-Label-Options#Complete_List_of_.40Options 1698 QString unitString(unit.isEmpty() ? QStringLiteral("") : QStringLiteral(" (") + unit + QStringLiteral(")")); 1699 // TODO: implement more 1700 legendCurveString.replace(QStringLiteral("%(%1)").arg(curveIndex), curveName); 1701 legendCurveString.replace(QStringLiteral("%(%1,@C)").arg(curveIndex), shortName); 1702 legendCurveString.replace(QStringLiteral("%(%1,@L)").arg(curveIndex), longName); 1703 legendCurveString.replace(QStringLiteral("%(%1,@LA)").arg(curveIndex), longName); 1704 legendCurveString.replace(QStringLiteral("%(%1,@LC)").arg(curveIndex), comments); 1705 legendCurveString.replace(QStringLiteral("%(%1,@LG)").arg(curveIndex), longName + unitString); 1706 legendCurveString.replace(QStringLiteral("%(%1,@LL)").arg(curveIndex), longName); 1707 legendCurveString.replace(QStringLiteral("%(%1,@LM)").arg(curveIndex), comments); 1708 legendCurveString.replace(QStringLiteral("%(%1,@LN)").arg(curveIndex), comments + unitString); 1709 legendCurveString.replace(QStringLiteral("%(%1,@LS)").arg(curveIndex), shortName); 1710 legendCurveString.replace(QStringLiteral("%(%1,@LU)").arg(curveIndex), unit); 1711 1712 // same with %(L.C) 1713 legendCurveString.replace(QStringLiteral("%(%1.%2)").arg(layerIndex + 1).arg(curveIndex), curveName); 1714 legendCurveString.replace(QStringLiteral("%(%1.%2,@C)").arg(layerIndex + 1).arg(curveIndex), shortName); 1715 legendCurveString.replace(QStringLiteral("%(%1.%2,@L)").arg(layerIndex + 1).arg(curveIndex), longName); 1716 legendCurveString.replace(QStringLiteral("%(%1.%2,@LA)").arg(layerIndex + 1).arg(curveIndex), longName); 1717 legendCurveString.replace(QStringLiteral("%(%1.%2,@LC)").arg(layerIndex + 1).arg(curveIndex), comments); 1718 legendCurveString.replace(QStringLiteral("%(%1.%2,@LG)").arg(layerIndex + 1).arg(curveIndex), longName + unitString); 1719 legendCurveString.replace(QStringLiteral("%(%1.%2,@LL)").arg(layerIndex + 1).arg(curveIndex), longName); 1720 legendCurveString.replace(QStringLiteral("%(%1.%2,@LM)").arg(layerIndex + 1).arg(curveIndex), comments); 1721 legendCurveString.replace(QStringLiteral("%(%1.%2,@LN)").arg(layerIndex + 1).arg(curveIndex), comments + unitString); 1722 legendCurveString.replace(QStringLiteral("%(%1.%2,@LS)").arg(layerIndex + 1).arg(curveIndex), shortName); 1723 legendCurveString.replace(QStringLiteral("%(%1.%2,@LU)").arg(layerIndex + 1).arg(curveIndex), unit); 1724 1725 DEBUG(Q_FUNC_INFO << ", legend curve string final = \"" << legendCurveString.toStdString() << "\"") 1726 1727 legendCurveString = legendCurveString.trimmed(); // remove leading and trailing whitspaces for curve name 1728 legendCurveString.remove(QRegularExpression(QStringLiteral("\\\\l\\(\\d+\\)"))); // remove left over "\l(X)" (TO) 1729 if (!legendCurveString.isEmpty()) 1730 curveName = legendCurveString; 1731 1732 enableCurveInLegend = true; 1733 } 1734 } 1735 1736 DEBUG(Q_FUNC_INFO << ", curve name = \"" << curveName.toStdString() << "\", in legend = " << enableCurveInLegend) 1737 1738 auto* curve = new XYCurve(curveName); 1739 if (m_graphLayerAsPlotArea) 1740 curve->setCoordinateSystemIndex(plot->defaultCoordinateSystemIndex()); 1741 else 1742 curve->setCoordinateSystemIndex(layerIndex); 1743 // DEBUG("CURVE path = " << STDSTRING(data)) 1744 1745 // set column path 1746 QString tableName = containerName; 1747 if (dataName.startsWith(QStringLiteral("E_"))) // container is a workbook 1748 tableName += QLatin1Char('/') + QString::fromStdString(sheet.name); 1749 curve->setXColumnPath(tableName + QLatin1Char('/') + QString::fromStdString(originCurve.xColumnName)); 1750 curve->setYColumnPath(tableName + QLatin1Char('/') + QString::fromStdString(originCurve.yColumnName)); 1751 DEBUG(Q_FUNC_INFO << ", x/y column path = \"" << STDSTRING(curve->xColumnPath()) << "\" \"" << STDSTRING(curve->yColumnPath()) << "\"") 1752 1753 curve->setLegendVisible(enableCurveInLegend); 1754 1755 curve->setSuppressRetransform(true); 1756 if (!preview) 1757 loadCurve(originCurve, curve); 1758 plot->addChildFast(curve); 1759 curve->setSuppressRetransform(false); 1760 } else if (originCurve.type == Origin::GraphCurve::Column) { 1761 // TODO: vertical bars 1762 1763 } else if (originCurve.type == Origin::GraphCurve::Bar) { 1764 // TODO: horizontal bars 1765 1766 } else if (originCurve.type == Origin::GraphCurve::Histogram) { 1767 // TODO 1768 } 1769 } break; 1770 case 'F': { 1771 Origin::Function function; 1772 const auto funcIndex = m_originFile->functionIndex(dataName.right(dataName.length() - 2).toStdString()); 1773 if (funcIndex < 0) { 1774 ++curveIndex; 1775 continue; 1776 } 1777 1778 function = m_originFile->function(funcIndex); 1779 1780 auto* xyEqCurve = new XYEquationCurve(QString::fromStdString(function.name)); 1781 if (m_graphLayerAsPlotArea) 1782 xyEqCurve->setCoordinateSystemIndex(plot->defaultCoordinateSystemIndex()); 1783 else 1784 xyEqCurve->setCoordinateSystemIndex(layerIndex); 1785 XYEquationCurve::EquationData eqData; 1786 1787 eqData.count = function.totalPoints; 1788 eqData.expression1 = QString::fromStdString(function.formula); 1789 1790 if (function.type == Origin::Function::Polar) { 1791 eqData.type = XYEquationCurve::EquationType::Polar; 1792 1793 // replace 'x' by 'phi' 1794 eqData.expression1 = eqData.expression1.replace(QLatin1Char('x'), QLatin1String("phi")); 1795 1796 // convert from degrees to radians 1797 eqData.min = QString::number(function.begin / 180.) + QLatin1String("*pi"); 1798 eqData.max = QString::number(function.end / 180.) + QLatin1String("*pi"); 1799 } else { 1800 eqData.expression1 = QLatin1String(function.formula.c_str()); 1801 eqData.min = QString::number(function.begin); 1802 eqData.max = QString::number(function.end); 1803 } 1804 1805 xyEqCurve->setSuppressRetransform(true); 1806 xyEqCurve->setEquationData(eqData); 1807 if (!preview) 1808 loadCurve(originCurve, xyEqCurve); 1809 plot->addChildFast(xyEqCurve); 1810 xyEqCurve->setSuppressRetransform(false); 1811 } 1812 // TODO case 'M': Matrix 1813 } 1814 1815 ++curveIndex; 1816 } 1817 } 1818 1819 void OriginProjectParser::loadAxes(const Origin::GraphLayer& layer, 1820 CartesianPlot* plot, 1821 int layerIndex, 1822 const QString& xColumnInfo, 1823 const QString& yColumnInfo) { 1824 const auto& originXAxis = layer.xAxis; 1825 const auto& originYAxis = layer.yAxis; 1826 1827 // x bottom 1828 if (!originXAxis.formatAxis[0].hidden || originXAxis.tickAxis[0].showMajorLabels) { 1829 auto* axis = new Axis(QStringLiteral("x"), Axis::Orientation::Horizontal); 1830 axis->setSuppressRetransform(true); 1831 axis->setPosition(Axis::Position::Bottom); 1832 plot->addChildFast(axis); 1833 1834 // fix padding if label not shown 1835 const auto& axisFormat = originXAxis.formatAxis[0]; 1836 if (!axisFormat.label.shown) 1837 plot->setBottomPadding(plot->bottomPadding() / 2.); 1838 1839 loadAxis(originXAxis, axis, layerIndex, 0, xColumnInfo); 1840 if (!m_graphLayerAsPlotArea) 1841 axis->setCoordinateSystemIndex(layerIndex); 1842 axis->setSuppressRetransform(false); 1843 } 1844 1845 // x top 1846 if (!originXAxis.formatAxis[1].hidden || originXAxis.tickAxis[1].showMajorLabels) { 1847 auto* axis = new Axis(QStringLiteral("x top"), Axis::Orientation::Horizontal); 1848 axis->setPosition(Axis::Position::Top); 1849 axis->setSuppressRetransform(true); 1850 plot->addChildFast(axis); 1851 1852 // fix padding if label not shown 1853 const auto& axisFormat = originXAxis.formatAxis[1]; 1854 if (!axisFormat.label.shown) 1855 plot->setVerticalPadding(plot->verticalPadding() / 2.); 1856 1857 loadAxis(originXAxis, axis, layerIndex, 1, xColumnInfo); 1858 if (!m_graphLayerAsPlotArea) 1859 axis->setCoordinateSystemIndex(layerIndex); 1860 axis->setSuppressRetransform(false); 1861 } 1862 1863 // y left 1864 if (!originYAxis.formatAxis[0].hidden || originYAxis.tickAxis[0].showMajorLabels) { 1865 auto* axis = new Axis(QStringLiteral("y"), Axis::Orientation::Vertical); 1866 axis->setSuppressRetransform(true); 1867 axis->setPosition(Axis::Position::Left); 1868 plot->addChildFast(axis); 1869 1870 // fix padding if label not shown 1871 const auto& axisFormat = originYAxis.formatAxis[0]; 1872 if (!axisFormat.label.shown) 1873 plot->setHorizontalPadding(plot->horizontalPadding() / 2.); 1874 1875 loadAxis(originYAxis, axis, layerIndex, 0, yColumnInfo); 1876 if (!m_graphLayerAsPlotArea) 1877 axis->setCoordinateSystemIndex(layerIndex); 1878 axis->setSuppressRetransform(false); 1879 } 1880 1881 // y right 1882 if (!originYAxis.formatAxis[1].hidden || originYAxis.tickAxis[1].showMajorLabels) { 1883 auto* axis = new Axis(QStringLiteral("y right"), Axis::Orientation::Vertical); 1884 axis->setSuppressRetransform(true); 1885 axis->setPosition(Axis::Position::Right); 1886 plot->addChildFast(axis); 1887 1888 // fix padding if label not shown 1889 const auto& axisFormat = originYAxis.formatAxis[1]; 1890 if (!axisFormat.label.shown) 1891 plot->setRightPadding(plot->rightPadding() / 2.); 1892 1893 loadAxis(originYAxis, axis, layerIndex, 1, yColumnInfo); 1894 if (!m_graphLayerAsPlotArea) 1895 axis->setCoordinateSystemIndex(layerIndex); 1896 axis->setSuppressRetransform(false); 1897 } 1898 } 1899 1900 /* 1901 * sets the axis properties (format and ticks) as defined in \c originAxis in \c axis, 1902 * \c index being 0 or 1 for "bottom" and "top" or "left" and "right" for horizontal or vertical axes, respectively. 1903 */ 1904 void OriginProjectParser::loadAxis(const Origin::GraphAxis& originAxis, Axis* axis, int layerIndex, int index, const QString& columnInfo) const { 1905 // int axisPosition; 1906 // possible values: 1907 // 0: Axis is at default position 1908 // 1: Axis is at (axisPositionValue)% from standard position 1909 // 2: Axis is at (axisPositionValue) position of orthogonal axis 1910 // double axisPositionValue; 1911 1912 // bool zeroLine; 1913 // bool oppositeLine; 1914 1915 DEBUG(Q_FUNC_INFO << ", index = " << index) 1916 1917 // ranges 1918 axis->setRange(originAxis.min, originAxis.max); 1919 1920 // ticks 1921 axis->setMajorTicksType(Axis::TicksType::Spacing); 1922 DEBUG(Q_FUNC_INFO << ", step = " << originAxis.step) 1923 DEBUG(Q_FUNC_INFO << ", position = " << originAxis.position) 1924 DEBUG(Q_FUNC_INFO << ", anchor = " << originAxis.anchor) 1925 axis->setMajorTicksSpacing(originAxis.step); 1926 // set offset from step and min later, when scaling factor is known 1927 axis->setMinorTicksType(Axis::TicksType::TotalNumber); 1928 axis->setMinorTicksNumber(originAxis.minorTicks); 1929 1930 // scale 1931 switch (originAxis.scale) { 1932 case Origin::GraphAxis::Linear: 1933 axis->setScale(RangeT::Scale::Linear); 1934 break; 1935 case Origin::GraphAxis::Log10: 1936 axis->setScale(RangeT::Scale::Log10); 1937 break; 1938 case Origin::GraphAxis::Ln: 1939 axis->setScale(RangeT::Scale::Ln); 1940 break; 1941 case Origin::GraphAxis::Log2: 1942 axis->setScale(RangeT::Scale::Log2); 1943 break; 1944 case Origin::GraphAxis::Reciprocal: 1945 axis->setScale(RangeT::Scale::Inverse); 1946 break; 1947 case Origin::GraphAxis::Probability: 1948 case Origin::GraphAxis::Probit: 1949 case Origin::GraphAxis::OffsetReciprocal: 1950 case Origin::GraphAxis::Logit: 1951 // TODO: set if implemented 1952 axis->setScale(RangeT::Scale::Linear); 1953 break; 1954 } 1955 1956 // major grid 1957 const auto& majorGrid = originAxis.majorGrid; 1958 Qt::PenStyle penStyle(Qt::NoPen); 1959 if (!majorGrid.hidden) { 1960 switch (majorGrid.style) { 1961 case Origin::GraphCurve::Solid: 1962 penStyle = Qt::SolidLine; 1963 break; 1964 case Origin::GraphCurve::Dash: 1965 case Origin::GraphCurve::ShortDash: 1966 penStyle = Qt::DashLine; 1967 break; 1968 case Origin::GraphCurve::Dot: 1969 case Origin::GraphCurve::ShortDot: 1970 penStyle = Qt::DotLine; 1971 break; 1972 case Origin::GraphCurve::DashDot: 1973 case Origin::GraphCurve::ShortDashDot: 1974 penStyle = Qt::DashDotLine; 1975 break; 1976 case Origin::GraphCurve::DashDotDot: 1977 penStyle = Qt::DashDotDotLine; 1978 break; 1979 } 1980 } 1981 axis->majorGridLine()->setStyle(penStyle); 1982 1983 Origin::Color gridColor; 1984 gridColor.type = Origin::Color::ColorType::Regular; 1985 gridColor.regular = majorGrid.color; 1986 axis->majorGridLine()->setColor(OriginProjectParser::color(gridColor)); 1987 axis->majorGridLine()->setWidth(Worksheet::convertToSceneUnits(majorGrid.width, Worksheet::Unit::Point)); 1988 1989 // minor grid 1990 const auto& minorGrid = originAxis.minorGrid; 1991 penStyle = Qt::NoPen; 1992 if (!minorGrid.hidden) { 1993 switch (minorGrid.style) { 1994 case Origin::GraphCurve::Solid: 1995 penStyle = Qt::SolidLine; 1996 break; 1997 case Origin::GraphCurve::Dash: 1998 case Origin::GraphCurve::ShortDash: 1999 penStyle = Qt::DashLine; 2000 break; 2001 case Origin::GraphCurve::Dot: 2002 case Origin::GraphCurve::ShortDot: 2003 penStyle = Qt::DotLine; 2004 break; 2005 case Origin::GraphCurve::DashDot: 2006 case Origin::GraphCurve::ShortDashDot: 2007 penStyle = Qt::DashDotLine; 2008 break; 2009 case Origin::GraphCurve::DashDotDot: 2010 penStyle = Qt::DashDotDotLine; 2011 break; 2012 } 2013 } 2014 axis->minorGridLine()->setStyle(penStyle); 2015 2016 gridColor.regular = minorGrid.color; 2017 axis->minorGridLine()->setColor(OriginProjectParser::color(gridColor)); 2018 axis->minorGridLine()->setWidth(Worksheet::convertToSceneUnits(minorGrid.width, Worksheet::Unit::Point)); 2019 2020 // process Origin::GraphAxisFormat 2021 const auto& axisFormat = originAxis.formatAxis[index]; 2022 2023 Origin::Color color; 2024 color.type = Origin::Color::ColorType::Regular; 2025 color.regular = axisFormat.color; 2026 axis->line()->setColor(OriginProjectParser::color(color)); 2027 // DEBUG("AXIS LINE THICKNESS = " << axisFormat.thickness) 2028 double lineThickness = 1.; 2029 if (layerIndex == 0) // axis line thickness is actually only used for first layer in Origin! 2030 lineThickness = axisFormat.thickness; 2031 axis->line()->setWidth(Worksheet::convertToSceneUnits(lineThickness * elementScalingFactor, Worksheet::Unit::Point)); 2032 2033 if (axisFormat.hidden) 2034 axis->line()->setStyle(Qt::NoPen); 2035 // TODO: read line style properties? (solid line, dashed line, etc.) 2036 2037 axis->setMajorTicksLength(Worksheet::convertToSceneUnits(axisFormat.majorTickLength * elementScalingFactor, Worksheet::Unit::Point)); 2038 axis->setMajorTicksDirection((Axis::TicksFlags)axisFormat.majorTicksType); 2039 axis->majorTicksLine()->setStyle(axis->line()->style()); 2040 axis->majorTicksLine()->setColor(axis->line()->color()); 2041 axis->majorTicksLine()->setWidth(axis->line()->width()); 2042 axis->setMinorTicksLength(axis->majorTicksLength() / 2); // minorTicksLength is half of majorTicksLength 2043 axis->setMinorTicksDirection((Axis::TicksFlags)axisFormat.minorTicksType); 2044 axis->minorTicksLine()->setStyle(axis->line()->style()); 2045 axis->minorTicksLine()->setColor(axis->line()->color()); 2046 axis->minorTicksLine()->setWidth(axis->line()->width()); 2047 2048 // axis title 2049 if (axisFormat.label.shown) { 2050 /*for (int i=0; i<axisFormat.label.text.size(); i++) 2051 printf(" %c ", axisFormat.label.text.at(i)); 2052 printf("\n"); 2053 for (int i=0; i<axisFormat.label.text.size(); i++) 2054 printf(" %02hhx", axisFormat.label.text.at(i)); 2055 printf("\n"); 2056 */ 2057 QString titleText; 2058 if (m_originFile->version() < 9.5) // <= 2017 : pre-Unicode 2059 titleText = parseOriginText(QString::fromLatin1(axisFormat.label.text.c_str())); 2060 else 2061 titleText = parseOriginText(QString::fromStdString(axisFormat.label.text)); 2062 DEBUG(Q_FUNC_INFO << ", axis title string = " << STDSTRING(titleText)); 2063 DEBUG(Q_FUNC_INFO << ", column info = " << STDSTRING(columnInfo)); 2064 2065 // long name(, unit(, comment)) 2066 // if long name not defined: columnInfo contains column name (s.a.) 2067 auto infoList = columnInfo.split(QRegularExpression(QStringLiteral("[\r\n]")), Qt::SkipEmptyParts); 2068 QString longName, unit, comments; 2069 switch (infoList.size()) { 2070 case 2: // long name, unit 2071 unit = infoList.at(1); 2072 // fallthrough 2073 case 1: // long name 2074 longName = infoList.at(0); 2075 // curveName = longName; 2076 break; 2077 default: // long name, unit, comment 2078 longName = infoList.at(0); 2079 unit = infoList.at(1); 2080 comments = infoList.at(2); 2081 // curveName = comments; 2082 } 2083 if (comments.isEmpty()) 2084 comments = longName; 2085 2086 QString unitString(unit.isEmpty() ? QStringLiteral("") : QStringLiteral(" (") + unit + QStringLiteral(")")); 2087 // TODO: more replacements here using column info (see loadCurves()) 2088 titleText.replace(QLatin1String("%(?X)"), longName + unitString); 2089 titleText.replace(QLatin1String("%(?Y)"), longName + unitString); 2090 titleText.replace(QLatin1String("%(?X,@L)"), longName); 2091 titleText.replace(QLatin1String("%(?Y,@L)"), longName); 2092 titleText.replace(QLatin1String("%(?X,@LC)"), comments); 2093 titleText.replace(QLatin1String("%(?Y,@LC)"), comments); 2094 titleText.replace(QLatin1String("%(?X,@LG)"), longName + unitString); 2095 titleText.replace(QLatin1String("%(?Y,@LG)"), longName + unitString); 2096 titleText.replace(QLatin1String("%(?X,@LL)"), longName); 2097 titleText.replace(QLatin1String("%(?Y,@LL)"), longName); 2098 titleText.replace(QLatin1String("%(?X,@LM)"), comments); 2099 titleText.replace(QLatin1String("%(?Y,@LM)"), comments); 2100 titleText.replace(QLatin1String("%(?X,@LN)"), comments + unitString); 2101 titleText.replace(QLatin1String("%(?Y,@LN)"), comments + unitString); 2102 // not available 2103 // titleText.replace(QLatin1String("%(?X,@LS)"), shortName); 2104 // titleText.replace(QLatin1String("%(?Y,@LS)"), shortName); 2105 titleText.replace(QLatin1String("%(?X,@LU)"), unit); 2106 titleText.replace(QLatin1String("%(?Y,@LU)"), unit); 2107 2108 DEBUG(Q_FUNC_INFO << ", axis title final = " << STDSTRING(titleText)); 2109 2110 // use axisFormat.fontSize to override the global font size for the hmtl string 2111 DEBUG(Q_FUNC_INFO << ", axis font size = " << axisFormat.label.fontSize) 2112 QTextEdit te(titleText); 2113 te.selectAll(); 2114 te.setFontPointSize(int(axisFormat.label.fontSize * textScalingFactor)); 2115 // TODO: parseOriginText() returns html formatted string. What is axisFormat.color used for? 2116 // te.setTextColor(OriginProjectParser::color(t.color)); 2117 axis->title()->setText(te.toHtml()); 2118 2119 // axis->title()->setText(titleText); 2120 axis->title()->setRotationAngle(axisFormat.label.rotation); 2121 } else { 2122 axis->title()->setText({}); 2123 } 2124 2125 // handle string factor member in GraphAxisFormat 2126 double scalingFactor = 1.0; 2127 if (!axisFormat.factor.empty()) { 2128 scalingFactor = 1. / std::stod(axisFormat.factor); 2129 DEBUG(Q_FUNC_INFO << ", scaling factor = " << scalingFactor) 2130 axis->setScalingFactor(scalingFactor); 2131 axis->setShowScaleOffset(false); // don't show scale factor 2132 } 2133 // now set axis ticks start offset 2134 double startOffset = nsl_math_ceil_multiple(originAxis.min * scalingFactor, originAxis.step * scalingFactor) - originAxis.min; 2135 DEBUG(Q_FUNC_INFO << ", min = " << originAxis.min << ", step = " << originAxis.step) 2136 DEBUG(Q_FUNC_INFO << ", start offset = " << startOffset) 2137 if (axis->range().contains(startOffset)) 2138 axis->setMajorTickStartOffset(startOffset); 2139 else { 2140 DEBUG(Q_FUNC_INFO << ", WARNING: start offset = " << startOffset << " outside of axis range!") 2141 axis->setMajorTickStartOffset(0.); 2142 } 2143 2144 axis->setLabelsPrefix(QLatin1String(axisFormat.prefix.c_str())); 2145 axis->setLabelsSuffix(QLatin1String(axisFormat.suffix.c_str())); 2146 2147 // process Origin::GraphAxisTick 2148 const auto& tickAxis = originAxis.tickAxis[index]; 2149 if (tickAxis.showMajorLabels) { 2150 color.type = Origin::Color::ColorType::Regular; 2151 color.regular = tickAxis.color; 2152 axis->setLabelsColor(OriginProjectParser::color(color)); 2153 if (index == 0) // left 2154 axis->setLabelsPosition(Axis::LabelsPosition::Out); 2155 else // right 2156 axis->setLabelsPosition(Axis::LabelsPosition::In); 2157 } else { 2158 axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); 2159 } 2160 2161 // TODO: handle ValueType valueType member in GraphAxisTick 2162 // TODO: handle int valueTypeSpecification in GraphAxisTick 2163 2164 // precision 2165 if (tickAxis.decimalPlaces == -1) { 2166 DEBUG(Q_FUNC_INFO << ", SET auto precision") 2167 axis->setLabelsAutoPrecision(true); 2168 } else { 2169 DEBUG(Q_FUNC_INFO << ", DISABLE auto precision. decimalPlaces = " << tickAxis.decimalPlaces) 2170 axis->setLabelsPrecision(tickAxis.decimalPlaces); 2171 axis->setLabelsAutoPrecision(false); 2172 } 2173 2174 QFont font; 2175 // TODO: font family? 2176 DEBUG(Q_FUNC_INFO << ", axis tick label font size = " << tickAxis.fontSize) 2177 DEBUG(Q_FUNC_INFO << ", axis tick label font size in points = " << Worksheet::convertToSceneUnits(tickAxis.fontSize, Worksheet::Unit::Point)) 2178 font.setPointSize(static_cast<int>(Worksheet::convertToSceneUnits(tickAxis.fontSize * textScalingFactor, Worksheet::Unit::Point))); 2179 font.setBold(tickAxis.fontBold); 2180 axis->setLabelsFont(font); 2181 // TODO: handle string dataName member in GraphAxisTick 2182 // TODO: handle string columnName member in GraphAxisTick 2183 axis->setLabelsRotationAngle(tickAxis.rotation); 2184 } 2185 2186 void OriginProjectParser::loadCurve(const Origin::GraphCurve& originCurve, XYCurve* curve) const { 2187 DEBUG(Q_FUNC_INFO) 2188 // line properties 2189 auto penStyle(Qt::NoPen); 2190 if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::LineSymbol) { 2191 switch (originCurve.lineConnect) { 2192 case Origin::GraphCurve::NoLine: 2193 curve->setLineType(XYCurve::LineType::NoLine); 2194 break; 2195 case Origin::GraphCurve::Straight: 2196 curve->setLineType(XYCurve::LineType::Line); 2197 break; 2198 case Origin::GraphCurve::TwoPointSegment: 2199 curve->setLineType(XYCurve::LineType::Segments2); 2200 break; 2201 case Origin::GraphCurve::ThreePointSegment: 2202 curve->setLineType(XYCurve::LineType::Segments3); 2203 break; 2204 case Origin::GraphCurve::BSpline: 2205 case Origin::GraphCurve::Bezier: 2206 case Origin::GraphCurve::Spline: 2207 curve->setLineType(XYCurve::LineType::SplineCubicNatural); 2208 break; 2209 case Origin::GraphCurve::StepHorizontal: 2210 curve->setLineType(XYCurve::LineType::StartHorizontal); 2211 break; 2212 case Origin::GraphCurve::StepVertical: 2213 curve->setLineType(XYCurve::LineType::StartVertical); 2214 break; 2215 case Origin::GraphCurve::StepHCenter: 2216 curve->setLineType(XYCurve::LineType::MidpointHorizontal); 2217 break; 2218 case Origin::GraphCurve::StepVCenter: 2219 curve->setLineType(XYCurve::LineType::MidpointVertical); 2220 break; 2221 } 2222 2223 switch (originCurve.lineStyle) { 2224 case Origin::GraphCurve::Solid: 2225 penStyle = Qt::SolidLine; 2226 break; 2227 case Origin::GraphCurve::Dash: 2228 case Origin::GraphCurve::ShortDash: 2229 penStyle = Qt::DashLine; 2230 break; 2231 case Origin::GraphCurve::Dot: 2232 case Origin::GraphCurve::ShortDot: 2233 penStyle = Qt::DotLine; 2234 break; 2235 case Origin::GraphCurve::DashDot: 2236 case Origin::GraphCurve::ShortDashDot: 2237 penStyle = Qt::DashDotLine; 2238 break; 2239 case Origin::GraphCurve::DashDotDot: 2240 penStyle = Qt::DashDotDotLine; 2241 break; 2242 } 2243 2244 curve->line()->setStyle(penStyle); 2245 auto lineWidth = std::max(originCurve.lineWidth * elementScalingFactor, 1.); // minimum 1 px 2246 curve->line()->setWidth(Worksheet::convertToSceneUnits(lineWidth, Worksheet::Unit::Point)); 2247 curve->line()->setColor(color(originCurve.lineColor)); 2248 curve->line()->setOpacity(1 - originCurve.lineTransparency / 255); 2249 2250 // TODO: handle unsigned char boxWidth of Origin::GraphCurve 2251 } else 2252 curve->line()->setStyle(penStyle); 2253 2254 // symbol properties 2255 auto* symbol = curve->symbol(); 2256 DEBUG(Q_FUNC_INFO << ", curve type = " << (unsigned int)originCurve.type) 2257 if (originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol) { 2258 // try to map the different symbols, mapping is not exact 2259 symbol->setRotationAngle(0); 2260 switch (originCurve.symbolShape) { // see https://www.originlab.com/doc/Labtalk/Ref/List-of-Symbol-Shapes 2261 case 0: // NoSymbol 2262 symbol->setStyle(Symbol::Style::NoSymbols); 2263 break; 2264 case 1: // Square 2265 switch (originCurve.symbolInterior) { 2266 case 0: // solid 2267 case 1: // open 2268 case 3: // hollow 2269 symbol->setStyle(Symbol::Style::Square); 2270 break; 2271 case 2: // dot 2272 symbol->setStyle(Symbol::Style::SquareDot); 2273 break; 2274 case 4: // plus 2275 symbol->setStyle(Symbol::Style::SquarePlus); 2276 break; 2277 case 5: // X 2278 symbol->setStyle(Symbol::Style::SquareX); 2279 break; 2280 case 6: // minus 2281 case 10: // down 2282 symbol->setStyle(Symbol::Style::SquareHalf); 2283 break; 2284 case 7: // pipe 2285 symbol->setStyle(Symbol::Style::SquareHalf); 2286 symbol->setRotationAngle(90); 2287 break; 2288 case 8: // up 2289 symbol->setStyle(Symbol::Style::SquareHalf); 2290 symbol->setRotationAngle(180); 2291 break; 2292 case 9: // right 2293 symbol->setStyle(Symbol::Style::SquareHalf); 2294 symbol->setRotationAngle(-90); 2295 break; 2296 case 11: // left 2297 symbol->setStyle(Symbol::Style::SquareHalf); 2298 symbol->setRotationAngle(90); 2299 break; 2300 } 2301 break; 2302 case 2: // Ellipse 2303 case 20: // Sphere 2304 switch (originCurve.symbolInterior) { 2305 case 0: // solid 2306 case 1: // open 2307 case 3: // hollow 2308 symbol->setStyle(Symbol::Style::Circle); 2309 break; 2310 case 2: // dot 2311 symbol->setStyle(Symbol::Style::CircleDot); 2312 break; 2313 case 4: // plus 2314 symbol->setStyle(Symbol::Style::CircleX); 2315 symbol->setRotationAngle(45); 2316 break; 2317 case 5: // X 2318 symbol->setStyle(Symbol::Style::CircleX); 2319 break; 2320 case 6: // minus 2321 symbol->setStyle(Symbol::Style::CircleHalf); 2322 symbol->setRotationAngle(90); 2323 break; 2324 case 7: // pipe 2325 case 11: // left 2326 symbol->setStyle(Symbol::Style::CircleHalf); 2327 break; 2328 case 8: // up 2329 symbol->setStyle(Symbol::Style::CircleHalf); 2330 symbol->setRotationAngle(90); 2331 break; 2332 case 9: // right 2333 symbol->setStyle(Symbol::Style::CircleHalf); 2334 symbol->setRotationAngle(180); 2335 break; 2336 case 10: // down 2337 symbol->setStyle(Symbol::Style::CircleHalf); 2338 symbol->setRotationAngle(-90); 2339 break; 2340 } 2341 break; 2342 case 3: // UTriangle 2343 switch (originCurve.symbolInterior) { 2344 case 0: // solid 2345 case 1: // open 2346 case 3: // hollow 2347 case 4: // plus TODO 2348 case 5: // X TODO 2349 symbol->setStyle(Symbol::Style::EquilateralTriangle); 2350 break; 2351 case 2: // dot 2352 symbol->setStyle(Symbol::Style::TriangleDot); 2353 break; 2354 case 7: // pipe 2355 case 11: // left 2356 symbol->setStyle(Symbol::Style::TriangleLine); 2357 break; 2358 case 6: // minus 2359 case 8: // up 2360 symbol->setStyle(Symbol::Style::TriangleHalf); 2361 break; 2362 case 9: // right TODO 2363 symbol->setStyle(Symbol::Style::TriangleLine); 2364 // symbol->setRotationAngle(180); 2365 break; 2366 case 10: // down TODO 2367 symbol->setStyle(Symbol::Style::TriangleHalf); 2368 // symbol->setRotationAngle(180); 2369 break; 2370 } 2371 break; 2372 case 4: // DTriangle 2373 switch (originCurve.symbolInterior) { 2374 case 0: // solid 2375 case 1: // open 2376 case 3: // hollow 2377 case 4: // plus TODO 2378 case 5: // X TODO 2379 symbol->setStyle(Symbol::Style::EquilateralTriangle); 2380 symbol->setRotationAngle(180); 2381 break; 2382 case 2: // dot 2383 symbol->setStyle(Symbol::Style::TriangleDot); 2384 symbol->setRotationAngle(180); 2385 break; 2386 case 7: // pipe 2387 case 11: // left 2388 symbol->setStyle(Symbol::Style::TriangleLine); 2389 symbol->setRotationAngle(180); 2390 break; 2391 case 6: // minus 2392 case 8: // up 2393 symbol->setStyle(Symbol::Style::TriangleHalf); 2394 symbol->setRotationAngle(180); 2395 break; 2396 case 9: // right TODO 2397 symbol->setStyle(Symbol::Style::TriangleLine); 2398 symbol->setRotationAngle(180); 2399 break; 2400 case 10: // down TODO 2401 symbol->setStyle(Symbol::Style::TriangleHalf); 2402 symbol->setRotationAngle(180); 2403 break; 2404 } 2405 break; 2406 case 5: // Diamond 2407 symbol->setStyle(Symbol::Style::Diamond); 2408 switch (originCurve.symbolInterior) { 2409 case 0: // solid 2410 case 1: // open 2411 case 3: // hollow 2412 symbol->setStyle(Symbol::Style::Diamond); 2413 break; 2414 case 2: // dot 2415 symbol->setStyle(Symbol::Style::SquareDot); 2416 symbol->setRotationAngle(45); 2417 break; 2418 case 4: // plus 2419 symbol->setStyle(Symbol::Style::SquareX); 2420 symbol->setRotationAngle(45); 2421 break; 2422 case 5: // X 2423 symbol->setStyle(Symbol::Style::SquarePlus); 2424 symbol->setRotationAngle(45); 2425 break; 2426 case 6: // minus 2427 case 10: // down 2428 symbol->setStyle(Symbol::Style::SquareHalf); 2429 break; 2430 case 7: // pipe 2431 symbol->setStyle(Symbol::Style::SquareHalf); 2432 symbol->setRotationAngle(90); 2433 break; 2434 case 8: // up 2435 symbol->setStyle(Symbol::Style::SquareHalf); 2436 symbol->setRotationAngle(180); 2437 break; 2438 case 9: // right 2439 symbol->setStyle(Symbol::Style::SquareHalf); 2440 symbol->setRotationAngle(-90); 2441 break; 2442 case 11: // left 2443 symbol->setStyle(Symbol::Style::SquareHalf); 2444 symbol->setRotationAngle(90); 2445 break; 2446 } 2447 break; 2448 case 6: // Cross + 2449 symbol->setStyle(Symbol::Style::Cross); 2450 break; 2451 case 7: // Cross x 2452 symbol->setStyle(Symbol::Style::Cross); 2453 symbol->setRotationAngle(45); 2454 break; 2455 case 8: // Snow 2456 symbol->setStyle(Symbol::Style::XPlus); 2457 break; 2458 case 9: // Horizontal - 2459 symbol->setStyle(Symbol::Style::Line); 2460 symbol->setRotationAngle(90); 2461 break; 2462 case 10: // Vertical | 2463 symbol->setStyle(Symbol::Style::Line); 2464 break; 2465 case 15: // LTriangle 2466 switch (originCurve.symbolInterior) { 2467 case 0: // solid 2468 case 1: // open 2469 case 3: // hollow 2470 case 4: // plus TODO 2471 case 5: // X TODO 2472 symbol->setStyle(Symbol::Style::EquilateralTriangle); 2473 symbol->setRotationAngle(-90); 2474 break; 2475 case 2: // dot 2476 symbol->setStyle(Symbol::Style::TriangleDot); 2477 symbol->setRotationAngle(-90); 2478 break; 2479 case 7: // pipe 2480 case 11: // left 2481 symbol->setStyle(Symbol::Style::TriangleLine); 2482 symbol->setRotationAngle(-90); 2483 break; 2484 case 6: // minus 2485 case 8: // up 2486 symbol->setStyle(Symbol::Style::TriangleHalf); 2487 symbol->setRotationAngle(-90); 2488 break; 2489 case 9: // right TODO 2490 symbol->setStyle(Symbol::Style::TriangleLine); 2491 symbol->setRotationAngle(-90); 2492 break; 2493 case 10: // down TODO 2494 symbol->setStyle(Symbol::Style::TriangleHalf); 2495 symbol->setRotationAngle(-90); 2496 break; 2497 } 2498 break; 2499 case 16: // RTriangle 2500 switch (originCurve.symbolInterior) { 2501 case 0: // solid 2502 case 1: // open 2503 case 3: // hollow 2504 case 4: // plus TODO 2505 case 5: // X TODO 2506 symbol->setStyle(Symbol::Style::EquilateralTriangle); 2507 symbol->setRotationAngle(90); 2508 break; 2509 case 2: // dot 2510 symbol->setStyle(Symbol::Style::TriangleDot); 2511 symbol->setRotationAngle(90); 2512 break; 2513 case 7: // pipe 2514 case 11: // left 2515 symbol->setStyle(Symbol::Style::TriangleLine); 2516 symbol->setRotationAngle(90); 2517 break; 2518 case 6: // minus 2519 case 8: // up 2520 symbol->setStyle(Symbol::Style::TriangleHalf); 2521 symbol->setRotationAngle(90); 2522 break; 2523 case 9: // right TODO 2524 symbol->setStyle(Symbol::Style::TriangleLine); 2525 symbol->setRotationAngle(90); 2526 break; 2527 case 10: // down TODO 2528 symbol->setStyle(Symbol::Style::TriangleHalf); 2529 symbol->setRotationAngle(90); 2530 break; 2531 } 2532 break; 2533 case 17: // Hexagon 2534 symbol->setStyle(Symbol::Style::Hexagon); 2535 break; 2536 case 18: // Star 2537 symbol->setStyle(Symbol::Style::Star); 2538 break; 2539 case 19: // Pentagon 2540 symbol->setStyle(Symbol::Style::Pentagon); 2541 break; 2542 default: 2543 symbol->setStyle(Symbol::Style::NoSymbols); 2544 } 2545 2546 // symbol size 2547 DEBUG(Q_FUNC_INFO << ", symbol size = " << originCurve.symbolSize) 2548 DEBUG(Q_FUNC_INFO << ", symbol size in points = " << Worksheet::convertToSceneUnits(originCurve.symbolSize, Worksheet::Unit::Point)) 2549 symbol->setSize(Worksheet::convertToSceneUnits(originCurve.symbolSize * elementScalingFactor, Worksheet::Unit::Point)); 2550 2551 // symbol colors 2552 DEBUG(Q_FUNC_INFO << ", symbol fill color = " << originCurve.symbolFillColor.type << "_" 2553 << (Origin::Color::RegularColor)originCurve.symbolFillColor.regular) 2554 DEBUG(Q_FUNC_INFO << ", symbol color = " << originCurve.symbolColor.type << "_" << (Origin::Color::RegularColor)originCurve.symbolColor.regular) 2555 // if plot type == line+symbol 2556 2557 auto brush = symbol->brush(); 2558 auto pen = symbol->pen(); 2559 DEBUG(Q_FUNC_INFO << ", SYMBOL THICKNESS = " << (int)originCurve.symbolThickness) 2560 // border width (edge thickness in Origin) is given as percentage of the symbol radius 2561 const double borderScaleFactor = 5.; // match size 2562 if (originCurve.type == Origin::GraphCurve::LineSymbol) { 2563 // symbol fill color 2564 if (originCurve.symbolFillColor.type == Origin::Color::ColorType::Automatic) { 2565 //"automatic" color -> the color of the line, if available, is used, and black otherwise 2566 if (curve->lineType() != XYCurve::LineType::NoLine) 2567 brush.setColor(curve->line()->pen().color()); 2568 else 2569 brush.setColor(Qt::black); 2570 } else 2571 brush.setColor(color(originCurve.symbolFillColor)); 2572 if (originCurve.symbolInterior > 0 && originCurve.symbolInterior < 8) // unfilled styles 2573 brush.setStyle(Qt::NoBrush); 2574 2575 // symbol border/edge color and width 2576 if (originCurve.symbolColor.type == Origin::Color::ColorType::Automatic) { 2577 //"automatic" color -> the color of the line, if available, has to be used, black otherwise 2578 if (curve->lineType() != XYCurve::LineType::NoLine) 2579 pen.setColor(curve->line()->pen().color()); 2580 else 2581 pen.setColor(Qt::black); 2582 } else 2583 pen.setColor(color(originCurve.symbolColor)); 2584 2585 DEBUG(Q_FUNC_INFO << ", BORDER THICKNESS = " << borderScaleFactor * originCurve.symbolThickness / 100. * symbol->size()) 2586 pen.setWidthF(borderScaleFactor * originCurve.symbolThickness / 100. * symbol->size()); 2587 } else if (originCurve.type == Origin::GraphCurve::Scatter) { 2588 // symbol color (uses originCurve.symbolColor) 2589 if (originCurve.symbolColor.type == Origin::Color::ColorType::Automatic) { 2590 //"automatic" color -> the color of the line, if available, is used, and black otherwise 2591 if (curve->lineType() != XYCurve::LineType::NoLine) 2592 brush.setColor(curve->line()->pen().color()); 2593 else 2594 brush.setColor(Qt::black); 2595 } else 2596 brush.setColor(color(originCurve.symbolColor)); 2597 2598 if (originCurve.symbolInterior > 0 && originCurve.symbolInterior < 8) { // unfilled styles 2599 brush.setStyle(Qt::NoBrush); 2600 DEBUG(Q_FUNC_INFO << ", BORDER THICKNESS = " << borderScaleFactor * originCurve.symbolThickness / 100. * symbol->size()) 2601 pen.setWidthF(borderScaleFactor * originCurve.symbolThickness / 100. * symbol->size()); 2602 2603 // symbol border/edge color and width 2604 if (originCurve.symbolColor.type == Origin::Color::ColorType::Automatic) { 2605 //"automatic" color -> the color of the line, if available, has to be used, black otherwise 2606 if (curve->lineType() != XYCurve::LineType::NoLine) 2607 pen.setColor(curve->line()->pen().color()); 2608 else 2609 pen.setColor(Qt::black); 2610 } else 2611 pen.setColor(color(originCurve.symbolColor)); 2612 } else 2613 pen.setStyle(Qt::NoPen); // no border 2614 } 2615 symbol->setBrush(brush); 2616 symbol->setPen(pen); 2617 2618 // handle unsigned char pointOffset member 2619 // handle bool connectSymbols member 2620 } else { 2621 symbol->setStyle(Symbol::Style::NoSymbols); 2622 } 2623 2624 // filling properties 2625 if (originCurve.fillArea) { 2626 // TODO: handle unsigned char fillAreaType; 2627 // with 'fillAreaType'=0x10 the area between the curve and the x-axis is filled 2628 // with 'fillAreaType'=0x14 the area included inside the curve is filled. First and last curve points are joined by a line to close the otherwise open 2629 // area. with 'fillAreaType'=0x12 the area excluded outside the curve is filled. The inverse of fillAreaType=0x14 is filled. At the moment we only 2630 // support the first type, so set it to XYCurve::FillingBelow 2631 curve->background()->setPosition(Background::Position::Below); 2632 auto* background = curve->background(); 2633 2634 if (originCurve.fillAreaPattern == 0) { 2635 background->setType(Background::Type::Color); 2636 } else { 2637 background->setType(Background::Type::Pattern); 2638 2639 // map different patterns in originCurve.fillAreaPattern (has the values of Origin::FillPattern) to Qt::BrushStyle; 2640 switch (originCurve.fillAreaPattern) { 2641 case 0: 2642 background->setBrushStyle(Qt::NoBrush); 2643 break; 2644 case 1: 2645 case 2: 2646 case 3: 2647 background->setBrushStyle(Qt::BDiagPattern); 2648 break; 2649 case 4: 2650 case 5: 2651 case 6: 2652 background->setBrushStyle(Qt::FDiagPattern); 2653 break; 2654 case 7: 2655 case 8: 2656 case 9: 2657 background->setBrushStyle(Qt::DiagCrossPattern); 2658 break; 2659 case 10: 2660 case 11: 2661 case 12: 2662 background->setBrushStyle(Qt::HorPattern); 2663 break; 2664 case 13: 2665 case 14: 2666 case 15: 2667 background->setBrushStyle(Qt::VerPattern); 2668 break; 2669 case 16: 2670 case 17: 2671 case 18: 2672 background->setBrushStyle(Qt::CrossPattern); 2673 break; 2674 } 2675 } 2676 2677 background->setFirstColor(color(originCurve.fillAreaColor)); 2678 background->setOpacity(1 - originCurve.fillAreaTransparency / 255); 2679 2680 // Color fillAreaPatternColor - color for the pattern lines, not supported 2681 // double fillAreaPatternWidth - width of the pattern lines, not supported 2682 // bool fillAreaWithLineTransparency - transparency of the pattern lines independent of the area transparency, not supported 2683 2684 // TODO: 2685 // unsigned char fillAreaPatternBorderStyle; 2686 // Color fillAreaPatternBorderColor; 2687 // double fillAreaPatternBorderWidth; 2688 // The Border properties are used only in "Column/Bar" (histogram) plots. Those properties are: 2689 // fillAreaPatternBorderStyle for the line style (use enum Origin::LineStyle here) 2690 // fillAreaPatternBorderColor for the line color 2691 // fillAreaPatternBorderWidth for the line width 2692 } else 2693 curve->background()->setPosition(Background::Position::No); 2694 } 2695 2696 bool OriginProjectParser::loadNote(Note* note, bool preview) { 2697 DEBUG(Q_FUNC_INFO); 2698 // load note data 2699 const auto& originNote = m_originFile->note(findNoteByName(note->name())); 2700 2701 if (preview) 2702 return true; 2703 2704 note->setComment(QString::fromStdString(originNote.label)); 2705 note->setNote(QString::fromStdString(originNote.text)); 2706 2707 return true; 2708 } 2709 2710 // ############################################################################## 2711 // ########################### Helper functions ################################ 2712 // ############################################################################## 2713 QDateTime OriginProjectParser::creationTime(tree<Origin::ProjectNode>::iterator it) const { 2714 // this logic seems to be correct only for the first node (project node). For other nodes the current time is returned. 2715 char time_str[21]; 2716 strftime(time_str, sizeof(time_str), "%F %T", gmtime(&(*it).creationDate)); 2717 return QDateTime::fromString(QLatin1String(time_str), Qt::ISODate); 2718 } 2719 2720 QString OriginProjectParser::parseOriginText(const QString& str) const { 2721 DEBUG(Q_FUNC_INFO); 2722 auto lines = str.split(QLatin1Char('\n')); 2723 QString text; 2724 for (int i = 0; i < lines.size(); ++i) { 2725 if (i > 0) 2726 text.append(QLatin1String("<br>")); 2727 text.append(parseOriginTags(lines[i])); 2728 } 2729 2730 DEBUG(Q_FUNC_INFO << ", PARSED TEXT = " << STDSTRING(text)); 2731 2732 return text; 2733 } 2734 2735 QColor OriginProjectParser::color(Origin::Color color) const { 2736 switch (color.type) { 2737 case Origin::Color::ColorType::Regular: 2738 switch (color.regular) { 2739 case Origin::Color::Black: 2740 return QColor{Qt::black}; 2741 case Origin::Color::Red: 2742 return QColor{Qt::red}; 2743 case Origin::Color::Green: 2744 return QColor{Qt::green}; 2745 case Origin::Color::Blue: 2746 return QColor{Qt::blue}; 2747 case Origin::Color::Cyan: 2748 return QColor{Qt::cyan}; 2749 case Origin::Color::Magenta: 2750 return QColor{Qt::magenta}; 2751 case Origin::Color::Yellow: 2752 return QColor{Qt::yellow}; 2753 case Origin::Color::DarkYellow: 2754 return QColor{Qt::darkYellow}; 2755 case Origin::Color::Navy: 2756 return QColor{0, 0, 128}; 2757 case Origin::Color::Purple: 2758 return QColor{128, 0, 128}; 2759 case Origin::Color::Wine: 2760 return QColor{128, 0, 0}; 2761 case Origin::Color::Olive: 2762 return QColor{0, 128, 0}; 2763 case Origin::Color::DarkCyan: 2764 return QColor{Qt::darkCyan}; 2765 case Origin::Color::Royal: 2766 return QColor{0, 0, 160}; 2767 case Origin::Color::Orange: 2768 return QColor{255, 128, 0}; 2769 case Origin::Color::Violet: 2770 return QColor{128, 0, 255}; 2771 case Origin::Color::Pink: 2772 return QColor{255, 0, 128}; 2773 case Origin::Color::White: 2774 return QColor{Qt::white}; 2775 case Origin::Color::LightGray: 2776 return QColor{Qt::lightGray}; 2777 case Origin::Color::Gray: 2778 return QColor{Qt::gray}; 2779 case Origin::Color::LTYellow: 2780 return QColor{255, 0, 128}; 2781 case Origin::Color::LTCyan: 2782 return QColor{128, 255, 255}; 2783 case Origin::Color::LTMagenta: 2784 return QColor{255, 128, 255}; 2785 case Origin::Color::DarkGray: 2786 return QColor{Qt::darkGray}; 2787 case Origin::Color::SpecialV7Axis: 2788 return QColor{Qt::black}; 2789 } 2790 break; 2791 case Origin::Color::ColorType::Custom: 2792 return QColor{color.custom[0], color.custom[1], color.custom[2]}; 2793 case Origin::Color::ColorType::None: 2794 case Origin::Color::ColorType::Automatic: 2795 case Origin::Color::ColorType::Increment: 2796 case Origin::Color::ColorType::Indexing: 2797 case Origin::Color::ColorType::RGB: 2798 case Origin::Color::ColorType::Mapping: 2799 break; 2800 } 2801 2802 return Qt::white; 2803 } 2804 2805 Background::ColorStyle OriginProjectParser::backgroundColorStyle(Origin::ColorGradientDirection colorGradient) const { 2806 switch (colorGradient) { 2807 case Origin::ColorGradientDirection::NoGradient: 2808 return Background::ColorStyle::SingleColor; 2809 case Origin::ColorGradientDirection::TopLeft: 2810 return Background::ColorStyle::TopLeftDiagonalLinearGradient; 2811 case Origin::ColorGradientDirection::Left: 2812 return Background::ColorStyle::HorizontalLinearGradient; 2813 case Origin::ColorGradientDirection::BottomLeft: 2814 return Background::ColorStyle::BottomLeftDiagonalLinearGradient; 2815 case Origin::ColorGradientDirection::Top: 2816 return Background::ColorStyle::VerticalLinearGradient; 2817 case Origin::ColorGradientDirection::Center: 2818 return Background::ColorStyle::RadialGradient; 2819 case Origin::ColorGradientDirection::Bottom: 2820 return Background::ColorStyle::VerticalLinearGradient; 2821 case Origin::ColorGradientDirection::TopRight: 2822 return Background::ColorStyle::BottomLeftDiagonalLinearGradient; 2823 case Origin::ColorGradientDirection::Right: 2824 return Background::ColorStyle::HorizontalLinearGradient; 2825 case Origin::ColorGradientDirection::BottomRight: 2826 return Background::ColorStyle::TopLeftDiagonalLinearGradient; 2827 } 2828 2829 return Background::ColorStyle::SingleColor; 2830 } 2831 2832 QString strreverse(const QString& str) { // QString reversing 2833 auto ba = str.toLocal8Bit(); 2834 std::reverse(ba.begin(), ba.end()); 2835 2836 return QLatin1String(ba); 2837 } 2838 2839 QList<QPair<QString, QString>> OriginProjectParser::charReplacementList() const { 2840 QList<QPair<QString, QString>> replacements; 2841 2842 // TODO: probably missed some. Is there any generic method? 2843 replacements << qMakePair(QStringLiteral("ä"), QStringLiteral("ä")); 2844 replacements << qMakePair(QStringLiteral("ö"), QStringLiteral("ö")); 2845 replacements << qMakePair(QStringLiteral("ü"), QStringLiteral("ü")); 2846 replacements << qMakePair(QStringLiteral("Ä"), QStringLiteral("Ä")); 2847 replacements << qMakePair(QStringLiteral("Ö"), QStringLiteral("Ö")); 2848 replacements << qMakePair(QStringLiteral("Ü"), QStringLiteral("Ü")); 2849 replacements << qMakePair(QStringLiteral("ß"), QStringLiteral("ß")); 2850 replacements << qMakePair(QStringLiteral("€"), QStringLiteral("€")); 2851 replacements << qMakePair(QStringLiteral("£"), QStringLiteral("£")); 2852 replacements << qMakePair(QStringLiteral("¥"), QStringLiteral("¥")); 2853 replacements << qMakePair(QStringLiteral("¤"), QStringLiteral("¤")); // krazy:exclude=spelling 2854 replacements << qMakePair(QStringLiteral("¦"), QStringLiteral("¦")); 2855 replacements << qMakePair(QStringLiteral("§"), QStringLiteral("§")); 2856 replacements << qMakePair(QStringLiteral("µ"), QStringLiteral("µ")); 2857 replacements << qMakePair(QStringLiteral("¹"), QStringLiteral("¹")); 2858 replacements << qMakePair(QStringLiteral("²"), QStringLiteral("²")); 2859 replacements << qMakePair(QStringLiteral("³"), QStringLiteral("³")); 2860 replacements << qMakePair(QStringLiteral("¶"), QStringLiteral("¶")); 2861 replacements << qMakePair(QStringLiteral("ø"), QStringLiteral("ø")); 2862 replacements << qMakePair(QStringLiteral("æ"), QStringLiteral("æ")); 2863 replacements << qMakePair(QStringLiteral("ð"), QStringLiteral("ð")); 2864 replacements << qMakePair(QStringLiteral("ħ"), QStringLiteral("ℏ")); 2865 replacements << qMakePair(QStringLiteral("ĸ"), QStringLiteral("κ")); 2866 replacements << qMakePair(QStringLiteral("¢"), QStringLiteral("¢")); 2867 replacements << qMakePair(QStringLiteral("¼"), QStringLiteral("¼")); 2868 replacements << qMakePair(QStringLiteral("½"), QStringLiteral("½")); 2869 replacements << qMakePair(QStringLiteral("¾"), QStringLiteral("¾")); 2870 replacements << qMakePair(QStringLiteral("¬"), QStringLiteral("¬")); 2871 replacements << qMakePair(QStringLiteral("©"), QStringLiteral("©")); 2872 replacements << qMakePair(QStringLiteral("®"), QStringLiteral("®")); 2873 replacements << qMakePair(QStringLiteral("ª"), QStringLiteral("ª")); 2874 replacements << qMakePair(QStringLiteral("º"), QStringLiteral("º")); 2875 replacements << qMakePair(QStringLiteral("±"), QStringLiteral("±")); 2876 replacements << qMakePair(QStringLiteral("¿"), QStringLiteral("¿")); 2877 replacements << qMakePair(QStringLiteral("×"), QStringLiteral("×")); 2878 replacements << qMakePair(QStringLiteral("°"), QStringLiteral("°")); 2879 replacements << qMakePair(QStringLiteral("«"), QStringLiteral("«")); 2880 replacements << qMakePair(QStringLiteral("»"), QStringLiteral("»")); 2881 replacements << qMakePair(QStringLiteral("¯"), QStringLiteral("¯")); 2882 replacements << qMakePair(QStringLiteral("¸"), QStringLiteral("¸")); 2883 replacements << qMakePair(QStringLiteral("À"), QStringLiteral("À")); 2884 replacements << qMakePair(QStringLiteral("Á"), QStringLiteral("Á")); 2885 replacements << qMakePair(QStringLiteral("Â"), QStringLiteral("Â")); 2886 replacements << qMakePair(QStringLiteral("Ã"), QStringLiteral("Ã")); 2887 replacements << qMakePair(QStringLiteral("Å"), QStringLiteral("Å")); 2888 replacements << qMakePair(QStringLiteral("Æ"), QStringLiteral("Æ")); 2889 replacements << qMakePair(QStringLiteral("Ç"), QStringLiteral("Ç")); 2890 replacements << qMakePair(QStringLiteral("È"), QStringLiteral("È")); 2891 replacements << qMakePair(QStringLiteral("É"), QStringLiteral("É")); 2892 replacements << qMakePair(QStringLiteral("Ê"), QStringLiteral("Ê")); 2893 replacements << qMakePair(QStringLiteral("Ë"), QStringLiteral("Ë")); 2894 replacements << qMakePair(QStringLiteral("Ì"), QStringLiteral("Ì")); 2895 replacements << qMakePair(QStringLiteral("Í"), QStringLiteral("Í")); 2896 replacements << qMakePair(QStringLiteral("Î"), QStringLiteral("Î")); 2897 replacements << qMakePair(QStringLiteral("Ï"), QStringLiteral("Ï")); 2898 replacements << qMakePair(QStringLiteral("Ð"), QStringLiteral("Ð")); 2899 replacements << qMakePair(QStringLiteral("Ñ"), QStringLiteral("Ñ")); 2900 replacements << qMakePair(QStringLiteral("Ò"), QStringLiteral("Ò")); 2901 replacements << qMakePair(QStringLiteral("Ó"), QStringLiteral("Ó")); 2902 replacements << qMakePair(QStringLiteral("Ô"), QStringLiteral("Ô")); 2903 replacements << qMakePair(QStringLiteral("Õ"), QStringLiteral("Õ")); 2904 replacements << qMakePair(QStringLiteral("Ù"), QStringLiteral("Ù")); 2905 replacements << qMakePair(QStringLiteral("Ú"), QStringLiteral("Ú")); 2906 replacements << qMakePair(QStringLiteral("Û"), QStringLiteral("Û")); 2907 replacements << qMakePair(QStringLiteral("Ý"), QStringLiteral("Ý")); 2908 replacements << qMakePair(QStringLiteral("Þ"), QStringLiteral("Þ")); 2909 replacements << qMakePair(QStringLiteral("à"), QStringLiteral("à")); 2910 replacements << qMakePair(QStringLiteral("á"), QStringLiteral("á")); 2911 replacements << qMakePair(QStringLiteral("â"), QStringLiteral("â")); 2912 replacements << qMakePair(QStringLiteral("ã"), QStringLiteral("ã")); 2913 replacements << qMakePair(QStringLiteral("å"), QStringLiteral("å")); 2914 replacements << qMakePair(QStringLiteral("ç"), QStringLiteral("ç")); 2915 replacements << qMakePair(QStringLiteral("è"), QStringLiteral("è")); 2916 replacements << qMakePair(QStringLiteral("é"), QStringLiteral("é")); 2917 replacements << qMakePair(QStringLiteral("ê"), QStringLiteral("ê")); 2918 replacements << qMakePair(QStringLiteral("ë"), QStringLiteral("ë")); 2919 replacements << qMakePair(QStringLiteral("ì"), QStringLiteral("ì")); 2920 replacements << qMakePair(QStringLiteral("í"), QStringLiteral("í")); 2921 replacements << qMakePair(QStringLiteral("î"), QStringLiteral("î")); 2922 replacements << qMakePair(QStringLiteral("ï"), QStringLiteral("ï")); 2923 replacements << qMakePair(QStringLiteral("ñ"), QStringLiteral("ñ")); 2924 replacements << qMakePair(QStringLiteral("ò"), QStringLiteral("ò")); 2925 replacements << qMakePair(QStringLiteral("ó"), QStringLiteral("ó")); 2926 replacements << qMakePair(QStringLiteral("ô"), QStringLiteral("ô")); 2927 replacements << qMakePair(QStringLiteral("õ"), QStringLiteral("õ")); 2928 replacements << qMakePair(QStringLiteral("÷"), QStringLiteral("÷")); 2929 replacements << qMakePair(QStringLiteral("ù"), QStringLiteral("ù")); 2930 replacements << qMakePair(QStringLiteral("ú"), QStringLiteral("ú")); 2931 replacements << qMakePair(QStringLiteral("û"), QStringLiteral("û")); 2932 replacements << qMakePair(QStringLiteral("ý"), QStringLiteral("ý")); 2933 replacements << qMakePair(QStringLiteral("þ"), QStringLiteral("þ")); 2934 replacements << qMakePair(QStringLiteral("ÿ"), QStringLiteral("ÿ")); 2935 replacements << qMakePair(QStringLiteral("Œ"), QStringLiteral("Œ")); 2936 replacements << qMakePair(QStringLiteral("œ"), QStringLiteral("œ")); 2937 replacements << qMakePair(QStringLiteral("Š"), QStringLiteral("Š")); 2938 replacements << qMakePair(QStringLiteral("š"), QStringLiteral("š")); 2939 replacements << qMakePair(QStringLiteral("Ÿ"), QStringLiteral("Ÿ")); 2940 replacements << qMakePair(QStringLiteral("†"), QStringLiteral("†")); 2941 replacements << qMakePair(QStringLiteral("‡"), QStringLiteral("‡")); 2942 replacements << qMakePair(QStringLiteral("…"), QStringLiteral("…")); 2943 replacements << qMakePair(QStringLiteral("‰"), QStringLiteral("‰")); 2944 replacements << qMakePair(QStringLiteral("™"), QStringLiteral("™")); 2945 2946 return replacements; 2947 } 2948 2949 QString OriginProjectParser::replaceSpecialChars(const QString& text) const { 2950 QString t = text; 2951 DEBUG(Q_FUNC_INFO << ", got " << t.toStdString()) 2952 for (const auto& r : charReplacementList()) 2953 t.replace(r.first, r.second); 2954 DEBUG(Q_FUNC_INFO << ", now " << t.toStdString()) 2955 return t; 2956 } 2957 2958 /*! 2959 * helper function mapping the characters from the Symbol font (outdated and shouldn't be used for html) 2960 * to Unicode characters, s.a. https://www.alanwood.net/demos/symbol.html 2961 */ 2962 QString greekSymbol(const QString& symbol) { 2963 // characters in the Symbol-font 2964 static QStringList symbols{// letters 2965 QStringLiteral("A"), 2966 QStringLiteral("a"), 2967 QStringLiteral("B"), 2968 QStringLiteral("b"), 2969 QStringLiteral("G"), 2970 QStringLiteral("g"), 2971 QStringLiteral("D"), 2972 QStringLiteral("d"), 2973 QStringLiteral("E"), 2974 QStringLiteral("e"), 2975 QStringLiteral("Z"), 2976 QStringLiteral("z"), 2977 QStringLiteral("H"), 2978 QStringLiteral("h"), 2979 QStringLiteral("Q"), 2980 QStringLiteral("q"), 2981 QStringLiteral("I"), 2982 QStringLiteral("i"), 2983 QStringLiteral("K"), 2984 QStringLiteral("k"), 2985 QStringLiteral("L"), 2986 QStringLiteral("l"), 2987 QStringLiteral("M"), 2988 QStringLiteral("m"), 2989 QStringLiteral("N"), 2990 QStringLiteral("n"), 2991 QStringLiteral("X"), 2992 QStringLiteral("x"), 2993 QStringLiteral("O"), 2994 QStringLiteral("o"), 2995 QStringLiteral("P"), 2996 QStringLiteral("p"), 2997 QStringLiteral("R"), 2998 QStringLiteral("r"), 2999 QStringLiteral("S"), 3000 QStringLiteral("s"), 3001 QStringLiteral("T"), 3002 QStringLiteral("t"), 3003 QStringLiteral("U"), 3004 QStringLiteral("u"), 3005 QStringLiteral("F"), 3006 QStringLiteral("f"), 3007 QStringLiteral("C"), 3008 QStringLiteral("c"), 3009 QStringLiteral("Y"), 3010 QStringLiteral("y"), 3011 QStringLiteral("W"), 3012 QStringLiteral("w"), 3013 3014 // extra symbols 3015 QStringLiteral("V"), 3016 QStringLiteral("J"), 3017 QStringLiteral("j"), 3018 QStringLiteral("v"), 3019 QStringLiteral("i")}; 3020 3021 // Unicode friendy codes for greek letters and symbols 3022 static QStringList unicodeFriendlyCode{// letters 3023 QStringLiteral("Α"), 3024 QStringLiteral("α"), 3025 QStringLiteral("Β"), 3026 QStringLiteral("β"), 3027 QStringLiteral("Γ"), 3028 QStringLiteral("γ"), 3029 QStringLiteral("Δ"), 3030 QStringLiteral("δ"), 3031 QStringLiteral("Ε"), 3032 QStringLiteral("ε"), 3033 QStringLiteral("Ζ"), 3034 QStringLiteral("ζ"), 3035 QStringLiteral("Η"), 3036 QStringLiteral("η"), 3037 QStringLiteral("Θ"), 3038 QStringLiteral("θ"), 3039 QStringLiteral("Ι"), 3040 QStringLiteral("Iota;"), 3041 QStringLiteral("Κ"), 3042 QStringLiteral("κ"), 3043 QStringLiteral("Λ"), 3044 QStringLiteral("λ"), 3045 QStringLiteral("Μ"), 3046 QStringLiteral("μ"), 3047 QStringLiteral("Ν"), 3048 QStringLiteral("ν"), 3049 QStringLiteral("Ξ"), 3050 QStringLiteral("ξ"), 3051 QStringLiteral("Ο"), 3052 QStringLiteral("ο"), 3053 QStringLiteral("Π"), 3054 QStringLiteral("π"), 3055 QStringLiteral("Ρ"), 3056 QStringLiteral("ρ"), 3057 QStringLiteral("Σ"), 3058 QStringLiteral("σ"), 3059 QStringLiteral("&Tua;"), 3060 QStringLiteral("τ"), 3061 QStringLiteral("Υ"), 3062 QStringLiteral("υ"), 3063 QStringLiteral("Φ"), 3064 QStringLiteral("φ"), 3065 QStringLiteral("Χ"), 3066 QStringLiteral("χ"), 3067 QStringLiteral("Ψ"), 3068 QStringLiteral("ψ"), 3069 QStringLiteral("Ω"), 3070 QStringLiteral("ω"), 3071 3072 // extra symbols 3073 QStringLiteral("ς"), 3074 QStringLiteral("ϑ"), 3075 QStringLiteral("ϕ;") /* phi symbol, no friendly code */, 3076 QStringLiteral("ϖ"), 3077 QStringLiteral("ϒ")}; 3078 3079 int index = symbols.indexOf(symbol); 3080 if (index != -1) 3081 return unicodeFriendlyCode.at(index); 3082 else 3083 return QString(); 3084 } 3085 3086 /*! 3087 * converts the string with Origin's syntax for text formatting/highlighting 3088 * to a string in the richtext/html format supported by Qt. 3089 * For the supported syntax, see: 3090 * https://www.originlab.com/doc/LabTalk/ref/Label-cmd 3091 * https://www.originlab.com/doc/Origin-Help/TextOb-Prop-Text-tab 3092 * https://doc.qt.io/qt-5/richtext-html-subset.html 3093 */ 3094 QString OriginProjectParser::parseOriginTags(const QString& str) const { 3095 DEBUG(Q_FUNC_INFO << ", string = " << STDSTRING(str)); 3096 QDEBUG(" UTF8 string: " << str.toUtf8()); 3097 QString line = str; 3098 3099 // replace %(...) tags 3100 // QRegExp rxcol("\\%\\(\\d+\\)"); 3101 3102 // replace \l(x) (plot legend tags) with \\c{x}, where x is a digit 3103 line.replace(QRegularExpression(QStringLiteral("\\\\\\s*l\\s*\\(\\s*(\\d+)\\s*\\)")), QStringLiteral("\\c{\\1}")); 3104 3105 // replace umlauts etc. 3106 line = replaceSpecialChars(line); 3107 3108 // replace tabs (not really supported) 3109 line.replace(QLatin1Char('\t'), QLatin1String(" ")); 3110 3111 // In PCRE2 (which is what QRegularExpression uses) variable-length lookbehind is supposed to be 3112 // exprimental in Perl 5.30; which means it doesn't work at the moment, i.e. using a variable-length 3113 // negative lookbehind isn't valid syntax from QRegularExpression POV. 3114 // Ultimately we have to reverse the string and use a negative _lookahead_ instead. 3115 // The goal is to temporatily replace '(' and ')' that don't denote tags; this is so that we 3116 // can handle parenthesis that are inside the tag, e.g. '\b(bold (cf))', we want the '(cf)' part 3117 // to remain as is. 3118 const QRegularExpression nonTagsRe(QLatin1String(R"(\)([^)(]*)\((?!\s*([buigs\+\-]|\d{1,3}\s*[pc]|[\w ]+\s*:\s*f)\s*\\))")); 3119 QString linerev = strreverse(line); 3120 const QString lBracket = strreverse(QStringLiteral("&lbracket;")); 3121 const QString rBracket = strreverse(QStringLiteral("&rbracket;")); 3122 linerev.replace(nonTagsRe, rBracket + QStringLiteral("\\1") + lBracket); 3123 3124 // change the line back to normal 3125 line = strreverse(linerev); 3126 3127 // replace \-(...), \+(...), \b(...), \i(...), \u(...), \s(....), \g(...), \f:font(...), 3128 // \c'number'(...), \p'size'(...) tags with equivalent supported HTML syntax 3129 const QRegularExpression tagsRe(QStringLiteral("\\\\\\s*([-+bgisu]|f:(\\w[\\w ]+)|[pc]\\s*(\\d+))\\s*\\(([^()]+?)\\)")); 3130 QRegularExpressionMatch rmatch; 3131 while (line.contains(tagsRe, &rmatch)) { 3132 QString rep; 3133 const QString tagText = rmatch.captured(4); 3134 const QString marker = rmatch.captured(1); 3135 if (marker.startsWith(QLatin1Char('-'))) { 3136 rep = QStringLiteral("<sub>%1</sub>").arg(tagText); 3137 } else if (marker.startsWith(QLatin1Char('+'))) { 3138 rep = QStringLiteral("<sup>%1</sup>").arg(tagText); 3139 } else if (marker.startsWith(QLatin1Char('b'))) { 3140 rep = QStringLiteral("<b>%1</b>").arg(tagText); 3141 } else if (marker.startsWith(QLatin1Char('g'))) { // greek symbols e.g. α φ 3142 rep = greekSymbol(tagText); 3143 } else if (marker.startsWith(QLatin1Char('i'))) { 3144 rep = QStringLiteral("<i>%1</i>").arg(tagText); 3145 } else if (marker.startsWith(QLatin1Char('s'))) { 3146 rep = QStringLiteral("<s>%1</s>").arg(tagText); 3147 } else if (marker.startsWith(QLatin1Char('u'))) { 3148 rep = QStringLiteral("<u>%1</u>").arg(tagText); 3149 } else if (marker.startsWith(QLatin1Char('f'))) { 3150 rep = QStringLiteral("<font face=\"%1\">%2</font>").arg(rmatch.captured(2).trimmed(), tagText); 3151 } else if (marker.startsWith(QLatin1Char('p'))) { // e.g. \p200(...), means use font-size 200% 3152 rep = QStringLiteral("<span style=\"font-size: %1%\">%2</span>").arg(rmatch.captured(3), tagText); 3153 } else if (marker.startsWith(QLatin1Char('c'))) { 3154 // e.g. \c12(...), set the text color to the corresponding color from 3155 // the color drop-down list in OriginLab 3156 const int colorIndex = rmatch.captured(3).toInt(); 3157 Origin::Color c; 3158 c.type = Origin::Color::ColorType::Regular; 3159 c.regular = colorIndex <= 23 ? static_cast<Origin::Color::RegularColor>(colorIndex) : Origin::Color::RegularColor::Black; 3160 QColor color = OriginProjectParser::color(c); 3161 rep = QStringLiteral("<span style=\"color: %1\">%2</span>").arg(color.name(), tagText); 3162 } 3163 line.replace(rmatch.capturedStart(0), rmatch.capturedLength(0), rep); 3164 } 3165 3166 // put non-tag '(' and ')' back in their places 3167 line.replace(QLatin1String("&lbracket;"), QLatin1String("(")); 3168 line.replace(QLatin1String("&rbracket;"), QLatin1String(")")); 3169 3170 // special characters 3171 line.replace(QRegularExpression(QStringLiteral("\\\\\\((\\d+)\\)")), QLatin1String("&#\\1;")); 3172 3173 DEBUG(" result: " << STDSTRING(line)); 3174 3175 return line; 3176 }