diff --git a/liborigin/OriginAnyParser.cpp b/liborigin/OriginAnyParser.cpp index a284082a0..600aecb4e 100644 --- a/liborigin/OriginAnyParser.cpp +++ b/liborigin/OriginAnyParser.cpp @@ -1,2931 +1,2941 @@ /* * OriginAnyParser.cpp * * Copyright 2017 Miquel Garriga * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Parser for all versions. Based mainly on Origin750Parser.cpp */ #include "OriginAnyParser.h" #include /* define a macro to get an int (or uint) from a istringstream in binary mode */ #define GET_INT(iss, ovalue) {iss.read(reinterpret_cast(&ovalue), 4);}; #define GET_SHORT(iss, ovalue) {iss.read(reinterpret_cast(&ovalue), 2);}; #define GET_FLOAT(iss, ovalue) {iss.read(reinterpret_cast(&ovalue), 4);}; #define GET_DOUBLE(iss, ovalue) {iss.read(reinterpret_cast(&ovalue), 8);}; OriginAnyParser::OriginAnyParser(const string& fileName) : file(fileName.c_str(),ios::binary) { objectIndex = 0; + parseError = 0; } bool OriginAnyParser::parse() { #ifdef GENERATE_CODE_FOR_LOG // append progress in log file logfile = fopen("opjfile.log","a"); #endif // GENERATE_CODE_FOR_LOG // get length of file: file.seekg (0, ios_base::end); d_file_size = (unsigned long)file.tellg(); file.seekg(0, ios_base::beg); LOG_PRINT(logfile, "File size: %d\n", d_file_size) // get file and program version, check it is a valid file readFileVersion(); + if (parseError > 1) return false; unsigned long curpos = 0; curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX]\n", curpos, curpos) // get global header readGlobalHeader(); + if (parseError > 1) return false; curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX]\n", curpos, curpos) // get dataset list unsigned int dataset_list_size = 0; LOG_PRINT(logfile, "Reading Data sets ...\n") while (true) { if (!readDataSetElement()) break; dataset_list_size++; } + if (parseError > 1) return false; LOG_PRINT(logfile, " ... done. Data sets: %d\n", dataset_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) for(unsigned int i = 0; i < spreadSheets.size(); ++i){ #ifdef LVERSION // LABPLOT wants all sheets converted and not loose order if(spreadSheets[i].sheets > 0){ #else if(spreadSheets[i].sheets > 1){ #endif LOG_PRINT(logfile, " CONVERT SPREADSHEET \"%s\" to EXCEL\n", spreadSheets[i].name.c_str()); convertSpreadToExcel(i); --i; } } // get window list unsigned int window_list_size = 0; LOG_PRINT(logfile, "Reading Windows ...\n") while (true) { if (!readWindowElement()) break; window_list_size++; } LOG_PRINT(logfile, " ... done. Windows: %d\n", window_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) // get parameter list unsigned int parameter_list_size = 0; LOG_PRINT(logfile, "Reading Parameters ...\n") while (true) { if (!readParameterElement()) break; parameter_list_size++; } LOG_PRINT(logfile, " ... done. Parameters: %d\n", parameter_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) // Note windows were added between version >4.141 and 4.210, // i.e., with Release 5.0 if (curpos < d_file_size) { // get note windows list unsigned int note_list_size = 0; LOG_PRINT(logfile, "Reading Note windows ...\n") // Note windows have an independent index objectIndex = 0; while (true) { if (!readNoteElement()) break; note_list_size++; } LOG_PRINT(logfile, " ... done. Note windows: %d\n", note_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) } // Project Tree was added between version >4.210 and 4.2616, // i.e., with Release 6.0 if (curpos < d_file_size) { // get project tree readProjectTree(); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) } // Attachments were added between version >4.2673_558 and 4.2764_623, // i.e., with Release 7.0 if (curpos < d_file_size) { readAttachmentList(); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Now at %ld [0x%lX], filesize %d\n", curpos, curpos, d_file_size) } if (curpos >= d_file_size) LOG_PRINT(logfile, "Now at end of file\n") // drop unused matrix datasets vector validMatrices; for(unsigned int i = 0; i < matrixes.size(); ++i){ Matrix m = matrixes[i]; if (m.objectID >= 0) { validMatrices.push_back(m); } else { LOG_PRINT(logfile, "Matrix data set %d: %s is not used.\n", i, m.name.c_str()) } } matrixes.clear(); matrixes = validMatrices; #ifdef GENERATE_CODE_FOR_LOG fclose(logfile); #endif // GENERATE_CODE_FOR_LOG return true; } string toLowerCase(string str){ for (unsigned int i = 0; i < str.length(); i++) if (str[i] >= 0x41 && str[i] <= 0x5A) str[i] = str[i] + 0x20; return str; } OriginParser* createOriginAnyParser(const string& fileName) { return new OriginAnyParser(fileName); } unsigned int OriginAnyParser::readObjectSize() { unsigned int obj_size = 0; unsigned long curpos; (void) curpos; char c = 0; file >> obj_size; file >> c; if (c != '\n') { curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Wrong delimiter %c at %ld [0x%lX]\n", c, curpos, curpos) - exit(2); + parseError = 3; + return 0; } return obj_size; } string OriginAnyParser::readObjectAsString(unsigned int size) { char c; unsigned long curpos; (void) curpos; // read a size-byte blob of data followed by '\n' if (size > 0) { // get a string large enough to hold the result, initialize it to all 0's string blob = string(size, '\0'); // read data into that string // cannot use '>>' operator because iendianfstream truncates it at first '\0' file.read(&blob[0], size); // read the '\n' file >> c; if (c != '\n') { curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Wrong delimiter %c at %ld [0x%lX]\n", c, curpos, curpos) - exit(3); + parseError = 2; + return string(); } return blob; } return string(); } void OriginAnyParser::readFileVersion() { // get file and program version, check it is a valid file string sFileVersion; getline(file, sFileVersion); - if ((sFileVersion.substr(0,4) != "CPYA") || (*sFileVersion.rbegin() != '#')) { + if ((sFileVersion.substr(0,4) != "CPYA")) { LOG_PRINT(logfile, "File, is not a valid opj file\n") - exit(1); + parseError = 2; + return; } + if (*sFileVersion.rbegin() != '#') parseError = 1; LOG_PRINT(logfile, "File version string: %s\n", sFileVersion.c_str()) } void OriginAnyParser::readGlobalHeader() { // get global header size unsigned int gh_size = 0, gh_endmark = 0; gh_size = readObjectSize(); unsigned long curpos = (unsigned long)file.tellg(); (void) curpos; LOG_PRINT(logfile, "Global header size: %d [0x%X], starts at %ld [0x%lX],", gh_size, gh_size, curpos, curpos) // get global header data string gh_data; gh_data = readObjectAsString(gh_size); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " ends at %ld [0x%lX]\n", curpos, curpos) // when gh_size > 0x1B, a double with fileVersion/100 can be read at gh_data[0x1B:0x23] if (gh_size > 0x1B) { istringstream stmp; stmp.str(gh_data.substr(0x1B)); double dFileVersion; GET_DOUBLE(stmp, dFileVersion) if (dFileVersion > 8.5) { fileVersion = (unsigned int)trunc(dFileVersion*100.); } else { fileVersion = 10*(unsigned int)trunc(dFileVersion*10.); } LOG_PRINT(logfile, "Project version as read from header: %.2f (%.6f)\n", fileVersion/100.0, dFileVersion) } // now read a zero size end mark gh_endmark = readObjectSize(); if (gh_endmark != 0) { curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Wrong end of list mark %d at %ld [0x%lX]\n", gh_endmark, curpos, curpos) - exit(4); + parseError = 5; + return; } } bool OriginAnyParser::readDataSetElement() { /* get info and values of a DataSet (worksheet column, matrix sheet, ...) * return true if a DataSet is found, otherwise return false */ unsigned int dse_header_size = 0, dse_data_size = 0, dse_mask_size = 0; unsigned long curpos = 0, dsh_start = 0, dsd_start = 0, dsm_start = 0; string dse_header; // get dataset header size dse_header_size = readObjectSize(); if (dse_header_size == 0) return false; curpos = (unsigned long)file.tellg(); dsh_start = curpos; LOG_PRINT(logfile, "Column: header size %d [0x%X], starts at %ld [0x%lX], ", dse_header_size, dse_header_size, curpos, curpos) dse_header = readObjectAsString(dse_header_size); // get known info string name(25,0); name = dse_header.substr(0x58,25); // go to end of dataset header, get data size file.seekg(dsh_start+dse_header_size+1, ios_base::beg); dse_data_size = readObjectSize(); dsd_start = (unsigned long)file.tellg(); string dse_data = readObjectAsString(dse_data_size); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "data size %d [0x%X], from %ld [0x%lX] to %ld [0x%lX],", dse_data_size, dse_data_size, dsd_start, dsd_start, curpos, curpos) // get data values getColumnInfoAndData(dse_header, dse_header_size, dse_data, dse_data_size); // go to end of data values, get mask size (often zero) file.seekg(dsd_start+dse_data_size, ios_base::beg); // dse_data_size can be zero if (dse_data_size > 0) file.seekg(1, ios_base::cur); dse_mask_size = readObjectSize(); dsm_start = (unsigned long)file.tellg(); if (dse_mask_size > 0) LOG_PRINT(logfile, "\nmask size %d [0x%X], starts at %ld [0x%lX]", dse_mask_size, dse_mask_size, dsm_start, dsm_start) string dse_mask = readObjectAsString(dse_mask_size); // get mask values if (dse_mask_size > 0) { curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, ", ends at %ld [0x%lX]\n", curpos, curpos) // TODO: extract mask values from dse_mask // go to end of dataset mask file.seekg(dsm_start+dse_mask_size+1, ios_base::beg); } curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " ends at %ld [0x%lX]: ", curpos, curpos) LOG_PRINT(logfile, "%s\n", name.c_str()) return true; } bool OriginAnyParser::readWindowElement() { /* get general info and details of a window * return true if a Window is found, otherwise return false */ unsigned int wde_header_size = 0; unsigned long curpos = 0, wdh_start = 0; // get window header size wde_header_size = readObjectSize(); if (wde_header_size == 0) return false; curpos = (unsigned long)file.tellg(); wdh_start = curpos; LOG_PRINT(logfile, "Window found: header size %d [0x%X], starts at %ld [0x%lX]: ", wde_header_size, wde_header_size, curpos, curpos) string wde_header = readObjectAsString(wde_header_size); // get known info string name(25,0); name = wde_header.substr(0x02,25).c_str(); LOG_PRINT(logfile, "%s\n", name.c_str()) // classify type of window ispread = findSpreadByName(name); imatrix = findMatrixByName(name); iexcel = findExcelByName(name); igraph = -1; if (ispread != -1) { LOG_PRINT(logfile, "\n Window is a Worksheet book\n") getWindowProperties(spreadSheets[ispread], wde_header, wde_header_size); } else if (imatrix != -1) { LOG_PRINT(logfile, "\n Window is a Matrix book\n") getWindowProperties(matrixes[imatrix], wde_header, wde_header_size); } else if (iexcel != -1) { LOG_PRINT(logfile, "\n Window is an Excel book\n") getWindowProperties(excels[iexcel], wde_header, wde_header_size); } else { LOG_PRINT(logfile, "\n Window is a Graph\n") graphs.push_back(Graph(name)); igraph = graphs.size()-1; getWindowProperties(graphs[igraph], wde_header, wde_header_size); } // go to end of window header file.seekg(wdh_start+wde_header_size+1, ios_base::beg); // get layer list unsigned int layer_list_size = 0; LOG_PRINT(logfile, " Reading Layers ...\n") while (true) { ilayer = layer_list_size; if (!readLayerElement()) break; layer_list_size++; } LOG_PRINT(logfile, " ... done. Layers: %d\n", layer_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "window ends at %ld [0x%lX]\n", curpos, curpos) return true; } bool OriginAnyParser::readLayerElement() { /* get general info and details of a layer * return true if a Layer is found, otherwise return false */ unsigned int lye_header_size = 0; unsigned long curpos = 0, lyh_start = 0; // get layer header size lye_header_size = readObjectSize(); if (lye_header_size == 0) return false; curpos = (unsigned long)file.tellg(); lyh_start = curpos; LOG_PRINT(logfile, " Layer found: header size %d [0x%X], starts at %ld [0x%lX]\n", lye_header_size, lye_header_size, curpos, curpos) string lye_header = readObjectAsString(lye_header_size); // get known info getLayerProperties(lye_header, lye_header_size); // go to end of layer header file.seekg(lyh_start+lye_header_size+1, ios_base::beg); // get annotation list unsigned int annotation_list_size = 0; (void) annotation_list_size; LOG_PRINT(logfile, " Reading Annotations ...\n") /* Some annotations can be groups of annotations. We need a recursive function for those cases */ annotation_list_size = readAnnotationList(); LOG_PRINT(logfile, " ... done. Annotations: %d\n", annotation_list_size) // get curve list unsigned int curve_list_size = 0; LOG_PRINT(logfile, " Reading Curves ...\n") while (true) { if (!readCurveElement()) break; curve_list_size++; } LOG_PRINT(logfile, " ... done. Curves: %d\n", curve_list_size) // get axisbreak list unsigned int axisbreak_list_size = 0; LOG_PRINT(logfile, " Reading Axis breaks ...\n") while (true) { if (!readAxisBreakElement()) break; axisbreak_list_size++; } LOG_PRINT(logfile, " ... done. Axis breaks: %d\n", axisbreak_list_size) // get x axisparameter list unsigned int axispar_x_list_size = 0; LOG_PRINT(logfile, " Reading x-Axis parameters ...\n") while (true) { if (!readAxisParameterElement(1)) break; axispar_x_list_size++; } LOG_PRINT(logfile, " ... done. x-Axis parameters: %d\n", axispar_x_list_size) // get y axisparameter list unsigned int axispar_y_list_size = 0; LOG_PRINT(logfile, " Reading y-Axis parameters ...\n") while (true) { if (!readAxisParameterElement(2)) break; axispar_y_list_size++; } LOG_PRINT(logfile, " ... done. y-Axis parameters: %d\n", axispar_y_list_size) // get z axisparameter list unsigned int axispar_z_list_size = 0; LOG_PRINT(logfile, " Reading z-Axis parameters ...\n") while (true) { if (!readAxisParameterElement(3)) break; axispar_z_list_size++; } LOG_PRINT(logfile, " ... done. z-Axis parameters: %d\n", axispar_z_list_size) curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " layer ends at %ld [0x%lX]\n", curpos, curpos) return true; } unsigned int OriginAnyParser::readAnnotationList() { /* Purpose of this function is to allow recursive call for groups of annotation elements. */ unsigned int annotation_list_size = 0; while (true) { if (!readAnnotationElement()) break; annotation_list_size++; } return annotation_list_size; } bool OriginAnyParser::readAnnotationElement() { /* get general info and details of an Annotation * return true if an Annotation is found, otherwise return false */ unsigned int ane_header_size = 0; unsigned long curpos = 0, anh_start = 0; // get annotation header size ane_header_size = readObjectSize(); if (ane_header_size == 0) return false; curpos = (unsigned long)file.tellg(); anh_start = curpos; LOG_PRINT(logfile, " Annotation found: header size %d [0x%X], starts at %ld [0x%lX]: ", ane_header_size, ane_header_size, curpos, curpos) string ane_header = readObjectAsString(ane_header_size); // get known info string name(41,0); name = ane_header.substr(0x46,41); LOG_PRINT(logfile, "%s\n", name.c_str()) // go to end of annotation header file.seekg(anh_start+ane_header_size+1, ios_base::beg); // data of an annotation element is divided in three blocks // first block unsigned int ane_data_1_size = 0; unsigned long andt1_start = 0; ane_data_1_size = readObjectSize(); andt1_start = (unsigned long)file.tellg(); LOG_PRINT(logfile, " block 1 size %d [0x%X] at %ld [0x%lX]\n", ane_data_1_size, ane_data_1_size, andt1_start, andt1_start) string andt1_data = readObjectAsString(ane_data_1_size); // TODO: get known info // go to end of first data block file.seekg(andt1_start+ane_data_1_size+1, ios_base::beg); // second block unsigned int ane_data_2_size = 0; unsigned long andt2_start = 0; ane_data_2_size = readObjectSize(); andt2_start = (unsigned long)file.tellg(); LOG_PRINT(logfile, " block 2 size %d [0x%X] at %ld [0x%lX]\n", ane_data_2_size, ane_data_2_size, andt2_start, andt2_start) string andt2_data; // check for group of annotations if ((ane_data_1_size == 0x5e) && (ane_data_2_size == 0x04)) { unsigned int angroup_size = 0; (void) angroup_size; curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " Annotation group found at %ld [0x%lX] ...\n", curpos, curpos) angroup_size = readAnnotationList(); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " ... group end at %ld [0x%lX]. Annotations: %d\n", curpos, curpos, angroup_size) andt2_data = string(""); } else { andt2_data = readObjectAsString(ane_data_2_size); // TODO: get known info // go to end of second data block file.seekg(andt2_start+ane_data_2_size, ios_base::beg); if (ane_data_2_size > 0) file.seekg(1, ios_base::cur); } // third block unsigned int ane_data_3_size = 0; unsigned long andt3_start = 0; (void) andt3_start; ane_data_3_size = readObjectSize(); andt3_start = (unsigned long)file.tellg(); LOG_PRINT(logfile, " block 3 size %d [0x%X] at %ld [0x%lX]\n", ane_data_3_size, ane_data_3_size, andt3_start, andt3_start) string andt3_data = readObjectAsString(ane_data_3_size); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, " annotation ends at %ld [0x%lX]\n", curpos, curpos) // get annotation info getAnnotationProperties(ane_header, ane_header_size, andt1_data, ane_data_1_size, andt2_data, ane_data_2_size, andt3_data, ane_data_3_size); return true; } bool OriginAnyParser::readCurveElement() { /* get general info and details of a Curve * return true if a Curve is found, otherwise return false */ unsigned int cve_header_size = 0, cve_data_size = 0; unsigned long curpos = 0, cvh_start = 0, cvd_start = 0; // get curve header size cve_header_size = readObjectSize(); if (cve_header_size == 0) return false; curpos = (unsigned long)file.tellg(); cvh_start = curpos; LOG_PRINT(logfile, " Curve: header size %d [0x%X], starts at %ld [0x%lX], ", cve_header_size, cve_header_size, curpos, curpos) string cve_header = readObjectAsString(cve_header_size); // TODO: get known info from curve header string name = cve_header.substr(0x12,12); // go to end of header, get curve data size file.seekg(cvh_start+cve_header_size+1, ios_base::beg); cve_data_size = readObjectSize(); cvd_start = (unsigned long)file.tellg(); LOG_PRINT(logfile, "data size %d [0x%X], from %ld [0x%lX]", cve_data_size, cve_data_size, cvd_start, cvd_start) string cve_data = readObjectAsString(cve_data_size); // TODO: get known info from curve data // go to end of data file.seekg(cvd_start+cve_data_size, ios_base::beg); if (cve_data_size > 0) file.seekg(1, ios_base::cur); curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "to %ld [0x%lX]: %s\n", curpos, curpos, name.c_str()) // get curve (or column) info getCurveProperties(cve_header, cve_header_size, cve_data, cve_data_size); return true; } bool OriginAnyParser::readAxisBreakElement() { /* get info of Axis breaks * return true if an Axis break, otherwise return false */ unsigned int abe_data_size = 0; unsigned long curpos = 0, abd_start = 0; // get axis break data size abe_data_size = readObjectSize(); if (abe_data_size == 0) return false; curpos = (unsigned long)file.tellg(); abd_start = curpos; string abd_data = readObjectAsString(abe_data_size); // get known info // go to end of axis break data file.seekg(abd_start+abe_data_size+1, ios_base::beg); // get axis break info getAxisBreakProperties(abd_data, abe_data_size); return true; } bool OriginAnyParser::readAxisParameterElement(unsigned int naxis) { /* get info of Axis parameters for naxis-axis (x,y,z) = (1,2,3) * return true if an Axis break is found, otherwise return false */ unsigned int ape_data_size = 0; unsigned long curpos = 0, apd_start = 0; // get axis break data size ape_data_size = readObjectSize(); if (ape_data_size == 0) return false; curpos = (unsigned long)file.tellg(); apd_start = curpos; string apd_data = readObjectAsString(ape_data_size); // get known info // go to end of axis break data file.seekg(apd_start+ape_data_size+1, ios_base::beg); // get axis parameter info getAxisParameterProperties(apd_data, ape_data_size, naxis); return true; } bool OriginAnyParser::readParameterElement() { // get parameter name unsigned long curpos = 0; (void) curpos; string par_name; char c; getline(file, par_name); if (par_name[0] == '\0') { unsigned int eof_parameters_mark = readObjectSize(); (void) eof_parameters_mark; // supress compiler warning return false; } LOG_PRINT(logfile, " %s:", par_name.c_str()) // get value double value; file >> value; LOG_PRINT(logfile, " %g\n", value) // read the '\n' file >> c; if (c != '\n') { curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Wrong delimiter %c at %ld [0x%lX]\n", c, curpos, curpos) - exit(3); + parseError = 6; + return false; } return true; } bool OriginAnyParser::readNoteElement() { /* get info of Note windows, including "Results Log" * return true if a Note window is found, otherwise return false */ unsigned int nwe_header_size = 0, nwe_label_size = 0, nwe_contents_size = 0; unsigned long curpos = 0, nwh_start = 0, nwl_start = 0, nwc_start = 0; (void) nwc_start; // get note header size nwe_header_size = readObjectSize(); if (nwe_header_size == 0) return false; curpos = (unsigned long)file.tellg(); nwh_start = curpos; LOG_PRINT(logfile, " Note window found: header size %d [0x%X], starts at %ld [0x%lX]\n", nwe_header_size, nwe_header_size, curpos, curpos) string nwe_header = readObjectAsString(nwe_header_size); // TODO: get known info from header // go to end of header file.seekg(nwh_start+nwe_header_size+1, ios_base::beg); // get label size nwe_label_size = readObjectSize(); nwl_start = (unsigned long)file.tellg(); string nwe_label = readObjectAsString(nwe_label_size); LOG_PRINT(logfile, " label at %ld [0x%lX]: %s\n", nwl_start, nwl_start, nwe_label.c_str()) // go to end of label file.seekg(nwl_start+nwe_label_size, ios_base::beg); if (nwe_label_size > 0) file.seekg(1, ios_base::cur); // get contents size nwe_contents_size = readObjectSize(); nwc_start = (unsigned long)file.tellg(); string nwe_contents = readObjectAsString(nwe_contents_size); LOG_PRINT(logfile, " contents at %ld [0x%lX]: \n%s\n", nwc_start, nwc_start, nwe_contents.c_str()) // get note window info getNoteProperties(nwe_header, nwe_header_size, nwe_label, nwe_label_size, nwe_contents, nwe_contents_size); return true; } void OriginAnyParser::readProjectTree() { unsigned int pte_depth = 0; // first preamble size and data (usually 4) unsigned int pte_pre1_size = readObjectSize(); string pte_pre1 = readObjectAsString(pte_pre1_size); // second preamble size and data (usually 16) unsigned int pte_pre2_size = readObjectSize(); string pte_pre2 = readObjectAsString(pte_pre2_size); // root element and children unsigned int rootfolder = readFolderTree(projectTree.begin(), pte_depth); (void) rootfolder; // supress compiler warning // epilogue (should be zero) unsigned int pte_post_size = readObjectSize(); (void) pte_post_size; // supress compiler warning // log info on project tree #ifdef GENERATE_CODE_FOR_LOG outputProjectTree(); #endif // GENERATE_CODE_FOR_LOG return; } unsigned int OriginAnyParser::readFolderTree(tree::iterator parent, unsigned int depth) { unsigned int fle_header_size = 0, fle_eofh_size = 0, fle_name_size = 0, fle_prop_size = 0; unsigned long curpos = 0; (void) curpos; // folder header size, data, end mark fle_header_size = readObjectSize(); string fle_header = readObjectAsString(fle_header_size); fle_eofh_size = readObjectSize(); // (usually 0) (void) fle_eofh_size; // supress compiler warning // folder name size fle_name_size = readObjectSize(); curpos = (unsigned long)file.tellg(); string fle_name = readObjectAsString(fle_name_size); LOG_PRINT(logfile, "Folder name at %ld [0x%lX]: %s\n", curpos, curpos, fle_name.c_str()); // additional properties fle_prop_size = readObjectSize(); for (unsigned int i = 0; i < fle_prop_size; i++) { unsigned int obj_size = readObjectSize(); string obj_data = readObjectAsString(obj_size); } // get project folder properties tree::iterator current_folder = projectTree.append_child(parent, ProjectNode(fle_name, ProjectNode::Folder)); getProjectFolderProperties(current_folder, fle_header, fle_header_size); // file entries unsigned int number_of_files_size = 0; number_of_files_size = readObjectSize(); // should be 4 as number_of_files is an integer curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Number of files at %ld [0x%lX] ", curpos, curpos); string fle_nfiles = readObjectAsString(number_of_files_size); istringstream stmp(ios_base::binary); stmp.str(fle_nfiles); unsigned int number_of_files = 0; GET_INT(stmp, number_of_files) LOG_PRINT(logfile, "%d\n", number_of_files) for (unsigned int i=0; i < number_of_files; i++) { readProjectLeaf(current_folder); } // subfolder entries unsigned int number_of_folders_size = 0; number_of_folders_size = readObjectSize(); // should be 4 as number_of_subfolders is an integer curpos = (unsigned long)file.tellg(); LOG_PRINT(logfile, "Number of subfolders at %ld [0x%lX] ", curpos, curpos); string fle_nfolders = readObjectAsString(number_of_folders_size); stmp.str(fle_nfolders); unsigned int number_of_folders = 0; GET_INT(stmp, number_of_folders) LOG_PRINT(logfile, "%d\n", number_of_folders) for (unsigned int i=0; i < number_of_folders; i++) { depth++; unsigned int files_in_subfolder = readFolderTree(current_folder, depth); (void) files_in_subfolder; // supress compiler warning depth--; } return number_of_files; } void OriginAnyParser::readProjectLeaf(tree::iterator current_folder) { unsigned long curpos = 0; (void) curpos; // preamble size (usually 0) and data unsigned int ptl_pre_size = readObjectSize(); string ptl_pre = readObjectAsString(ptl_pre_size); // file data size (usually 8) and data unsigned int ptl_data_size = readObjectSize(); curpos = (unsigned long)file.tellg(); string ptl_data = readObjectAsString(ptl_data_size); LOG_PRINT(logfile, "File at %ld [0x%lX]\n", curpos, curpos) // epilogue (should be zero) unsigned int ptl_post_size = readObjectSize(); (void) ptl_post_size; // supress compiler warning // get project node properties getProjectLeafProperties(current_folder, ptl_data, ptl_data_size); return; } void OriginAnyParser::readAttachmentList() { /* Attachments are divided in two groups (which can be empty) first group is preceeded by two integers: 4096 (0x1000) and number_of_attachments followed as usual by a '\n' mark second group is a series of (header, name, data) triplets without the '\n' mark. */ // figure out if first group is not empty. In this case we will read integer=8 at current file position unsigned int att_1st_empty = 0; file >> att_1st_empty; file.seekg(-4, ios_base::cur); istringstream stmp(ios_base::binary); string att_header; unsigned long curpos = 0; (void) curpos; if (att_1st_empty == 8) { // first group unsigned int att_list1_size = 0; // get two integers // next line fails if first attachment group is empty: readObjectSize exits as there is no '\n' after 4 bytes for uint att_list1_size = readObjectSize(); // should be 8 as we expect two integer values curpos = (unsigned long)file.tellg(); string att_list1 = readObjectAsString(att_list1_size); LOG_PRINT(logfile, "First attachment group at %ld [0x%lX]", curpos, curpos) stmp.str(att_list1); unsigned int att_mark = 0, number_of_atts = 0, iattno = 0, att_data_size = 0; GET_INT(stmp, att_mark) // should be 4096 GET_INT(stmp, number_of_atts) LOG_PRINT(logfile, " with %d attachments.\n", number_of_atts) for (unsigned int i=0; i < number_of_atts; i++) { /* Header is a group of 7 integers followed by \n 1st attachment mark (4096: 0x00 0x10 0x00 0x00) 2nd attachment number ( (&att_header[0]), 12); if (file.gcount() != 12) break; // get header size, type and data size unsigned int att_header_size=0, att_type=0, att_size=0; stmp.str(att_header); GET_INT(stmp, att_header_size) GET_INT(stmp, att_type) GET_INT(stmp, att_size) // get name and data unsigned int name_size = att_header_size - 3*4; string att_name = string(name_size, 0); file.read(&att_name[0], name_size); curpos = (unsigned long)file.tellg(); string att_data = string(att_size, 0); file.read(&att_data[0], att_size); LOG_PRINT(logfile, "attachment at %ld [0x%lX], type 0x%X, size %d [0x%X]: %s\n", curpos, curpos, att_type, att_size, att_size, att_name.c_str()) } return; } bool OriginAnyParser::getColumnInfoAndData(string col_header, unsigned int col_header_size, string col_data, unsigned int col_data_size) { istringstream stmp(ios_base::binary); static unsigned int dataIndex=0; short data_type; char data_type_u; unsigned char valuesize; string name(25,0), column_name; stmp.str(col_header.substr(0x16)); GET_SHORT(stmp, data_type); data_type_u = col_header[0x3F]; if (fileVersion == 350) { valuesize = col_header[0x36]; } else { valuesize = col_header[0x3D]; } if(valuesize == 0) { LOG_PRINT(logfile, " WARNING : found strange valuesize of %d\n", (int)valuesize); valuesize = 8; } if (fileVersion == 350) { name = col_header.substr(0x57,25).c_str(); } else { name = col_header.substr(0x58,25).c_str(); } string dataset_name = name; string::size_type colpos = name.find_last_of("_"); if(colpos != string::npos){ column_name = name.substr(colpos + 1); name.resize(colpos); } LOG_PRINT(logfile, "\n data_type 0x%.4X, data_type_u 0x%.2X, valuesize %d [0x%X], %s [%s]\n", data_type, data_type_u, valuesize, valuesize, name.c_str(), column_name.c_str()); unsigned short signature; if (col_header_size > 0x72) { stmp.str(col_header.substr(0x71)); GET_SHORT(stmp, signature); int total_rows, first_row, last_row; stmp.str(col_header.substr(0x19)); GET_INT(stmp, total_rows); GET_INT(stmp, first_row); GET_INT(stmp, last_row); LOG_PRINT(logfile, " total %d, first %d, last %d rows\n", total_rows, first_row, last_row) } else { LOG_PRINT(logfile, " NOTE: alternative signature determination\n") signature = col_header[0x18]; } LOG_PRINT(logfile, " signature %d [0x%X], valuesize %d size %d ", signature, signature, valuesize, col_data_size) unsigned int current_col = 1;//, nr = 0, nbytes = 0; static unsigned int col_index = 0; unsigned int current_sheet = 0; int spread = 0; if (column_name.empty()) { // Matrix or function if (data_type == 0x6081) { // Function functions.push_back(Function(name, dataIndex)); ++dataIndex; Origin::Function &f = functions.back(); f.formula = toLowerCase(col_data.c_str()); stmp.str(col_header.substr(0x0A)); short t; GET_SHORT(stmp, t) if (t == 0x1194) f.type = Function::Polar; stmp.str(col_header.substr(0x21)); GET_INT(stmp, f.totalPoints) GET_DOUBLE(stmp, f.begin) double d; GET_DOUBLE(stmp, d) f.end = f.begin + d*(f.totalPoints - 1); LOG_PRINT(logfile, "\n NEW FUNCTION: %s = %s", f.name.c_str(), f.formula.c_str()); LOG_PRINT(logfile, ". Range [%g : %g], number of points: %d\n", f.begin, f.end, f.totalPoints); } else { // Matrix int mIndex = -1; string::size_type pos = name.find_first_of("@"); if (pos != string::npos){ string sheetName = name; name.resize(pos); mIndex = findMatrixByName(name); if (mIndex != -1){ LOG_PRINT(logfile, "\n NEW MATRIX SHEET\n"); matrixes[mIndex].sheets.push_back(MatrixSheet(sheetName, dataIndex)); } } else { LOG_PRINT(logfile, "\n NEW MATRIX\n"); matrixes.push_back(Matrix(name)); matrixes.back().sheets.push_back(MatrixSheet(name, dataIndex)); } ++dataIndex; getMatrixValues(col_data, col_data_size, data_type, data_type_u, valuesize, mIndex); } } else { if(spreadSheets.size() == 0 || findSpreadByName(name) == -1) { LOG_PRINT(logfile, "\n NEW SPREADSHEET\n"); current_col = 1; spreadSheets.push_back(SpreadSheet(name)); spread = spreadSheets.size() - 1; spreadSheets.back().maxRows = 0; current_sheet = 0; } else { spread = findSpreadByName(name); current_col = spreadSheets[spread].columns.size(); if(!current_col) current_col = 1; ++current_col; } spreadSheets[spread].columns.push_back(SpreadColumn(column_name, dataIndex)); spreadSheets[spread].columns.back().colIndex = ++col_index; spreadSheets[spread].columns.back().dataset_name = dataset_name; string::size_type sheetpos = spreadSheets[spread].columns.back().name.find_last_of("@"); if(sheetpos != string::npos){ unsigned int sheet = strtol(column_name.substr(sheetpos + 1).c_str(), 0, 10); if( sheet > 1){ spreadSheets[spread].columns.back().name = column_name; if (current_sheet != (sheet - 1)) current_sheet = sheet - 1; spreadSheets[spread].columns.back().sheet = current_sheet; if (spreadSheets[spread].sheets < sheet) spreadSheets[spread].sheets = sheet; } } ++dataIndex; LOG_PRINT(logfile, " data index %d, valuesize %d, ", dataIndex, valuesize) unsigned int nr = col_data_size / valuesize; LOG_PRINT(logfile, "n. of rows = %d\n\n", nr) spreadSheets[spread].maxRows (nr-5))) { LOG_PRINT(logfile, "%g ", value) } else if (i == 5) { LOG_PRINT(logfile, "... ") } spreadSheets[spread].columns[(current_col-1)].data.push_back(value); } else if((data_type & 0x100) == 0x100) // Text&Numeric { unsigned char c = col_data[i*valuesize]; stmp.seekg(i*valuesize+2, ios_base::beg); if(c != 1) //value { GET_DOUBLE(stmp, value); if ((i < 5) || (i > (nr-5))) { LOG_PRINT(logfile, "%g ", value) } else if (i == 5) { LOG_PRINT(logfile, "... ") } spreadSheets[spread].columns[(current_col-1)].data.push_back(value); } else //text { string svaltmp = col_data.substr(i*valuesize+2, valuesize-2); // TODO: check if this test is still needed if(svaltmp.find(0x0E) != string::npos) { // try find non-printable symbol - garbage test svaltmp = string(); LOG_PRINT(logfile, "Non printable symbol found, place 1 for i=%d\n", i) } if ((i < 5) || (i > (nr-5))) { LOG_PRINT(logfile, "\"%s\" ", svaltmp.c_str()) } else if (i == 5) { LOG_PRINT(logfile, "... ") } spreadSheets[spread].columns[(current_col-1)].data.push_back(svaltmp); } } else //text { string svaltmp = col_data.substr(i*valuesize, valuesize).c_str(); // TODO: check if this test is still needed if(svaltmp.find(0x0E) != string::npos) { // try find non-printable symbol - garbage test svaltmp = string(); LOG_PRINT(logfile, "Non printable symbol found, place 2 for i=%d\n", i) } if ((i < 5) || (i > (nr-5))) { LOG_PRINT(logfile, "\"%s\" ", svaltmp.c_str()) } else if (i == 5) { LOG_PRINT(logfile, "... ") } spreadSheets[spread].columns[(current_col-1)].data.push_back(svaltmp); } } LOG_PRINT(logfile, "\n\n") datasets.push_back(spreadSheets[spread].columns.back()); } return true; } void OriginAnyParser::getMatrixValues(string col_data, unsigned int col_data_size, short data_type, char data_type_u, char valuesize, int mIndex) { if (matrixes.empty()) return; istringstream stmp; stmp.str(col_data); if (mIndex < 0) mIndex = matrixes.size() - 1; unsigned int size = col_data_size/valuesize; bool logValues = true; switch(data_type){ case 0x6001://double for(unsigned int i = 0; i < size; ++i){ double value; GET_DOUBLE(stmp, value) matrixes[mIndex].sheets.back().data.push_back(value); } break; case 0x6003://float for(unsigned int i = 0; i < size; ++i){ float value; GET_FLOAT(stmp, value) matrixes[mIndex].sheets.back().data.push_back((double)value); } break; case 0x6801://int if (data_type_u == 8){//unsigned for(unsigned int i = 0; i < size; ++i){ unsigned int value; GET_INT(stmp, value) matrixes[mIndex].sheets.back().data.push_back((double)value); } } else { for(unsigned int i = 0; i < size; ++i){ int value; GET_INT(stmp, value) matrixes[mIndex].sheets.back().data.push_back((double)value); } } break; case 0x6803://short if (data_type_u == 8){//unsigned for(unsigned int i = 0; i < size; ++i){ unsigned short value; GET_SHORT(stmp, value) matrixes[mIndex].sheets.back().data.push_back((double)value); } } else { for(unsigned int i = 0; i < size; ++i){ short value; GET_SHORT(stmp, value) matrixes[mIndex].sheets.back().data.push_back((double)value); } } break; case 0x6821://char if (data_type_u == 8){//unsigned for(unsigned int i = 0; i < size; ++i){ unsigned char value; value = col_data[i]; matrixes[mIndex].sheets.back().data.push_back((double)value); } } else { for(unsigned int i = 0; i < size; ++i){ char value; value = col_data[i]; matrixes[mIndex].sheets.back().data.push_back((double)value); } } break; default: LOG_PRINT(logfile, " UNKNOWN MATRIX DATATYPE: %02X SKIP DATA\n", data_type); matrixes.pop_back(); logValues = false; } if (logValues){ LOG_PRINT(logfile, " FIRST 10 CELL VALUES: "); for(unsigned int i = 0; i < 10 && i < matrixes[mIndex].sheets.back().data.size(); ++i) LOG_PRINT(logfile, "%g\t", matrixes[mIndex].sheets.back().data[i]); } } void OriginAnyParser::getWindowProperties(Origin::Window& window, string wde_header, unsigned int wde_header_size) { window.objectID = objectIndex; ++objectIndex; istringstream stmp; stmp.str(wde_header.substr(0x1B)); GET_SHORT(stmp, window.frameRect.left) GET_SHORT(stmp, window.frameRect.top) GET_SHORT(stmp, window.frameRect.right) GET_SHORT(stmp, window.frameRect.bottom) char c = wde_header[0x32]; if(c & 0x01) window.state = Window::Minimized; else if(c & 0x02) window.state = Window::Maximized; c = wde_header[0x69]; if(c & 0x01) window.title = Window::Label; else if(c & 0x02) window.title = Window::Name; else window.title = Window::Both; window.hidden = (c & 0x08); if (window.hidden) { LOG_PRINT(logfile, " WINDOW %d NAME : %s is hidden\n", objectIndex, window.name.c_str()); } else { LOG_PRINT(logfile, " WINDOW %d NAME : %s is not hidden\n", objectIndex, window.name.c_str()); } if (wde_header_size > 0x82) { // only projects of version 6.0 and higher have these double creationDate, modificationDate; stmp.str(wde_header.substr(0x73)); GET_DOUBLE(stmp, creationDate); window.creationDate = doubleToPosixTime(creationDate); GET_DOUBLE(stmp, modificationDate) window.modificationDate = doubleToPosixTime(modificationDate); } if(wde_header_size > 0xC3){ window.label = wde_header.substr(0xC3).c_str(); window.label = window.label.substr(0,window.label.find("@${")); LOG_PRINT(logfile, " WINDOW %d LABEL: %s\n", objectIndex, window.label.c_str()); } if (imatrix != -1) { // additional properties for matrix windows unsigned char h = wde_header[0x29]; matrixes[imatrix].activeSheet = h; if (wde_header_size > 0x86) { h = wde_header[0x87]; matrixes[imatrix].header = (h == 194) ? Matrix::XY : Matrix::ColumnRow; } } if (igraph != -1) { // additional properties for graph/layout windows stmp.str(wde_header.substr(0x23)); GET_SHORT(stmp, graphs[igraph].width) GET_SHORT(stmp, graphs[igraph].height) unsigned char c = wde_header[0x38]; graphs[igraph].connectMissingData = (c & 0x40); string templateName = wde_header.substr(0x45,20).c_str(); graphs[igraph].templateName = templateName; if (templateName == "LAYOUT") graphs[igraph].isLayout = true; } } void OriginAnyParser::getLayerProperties(string lye_header, unsigned int lye_header_size) { istringstream stmp; if (ispread != -1) { // spreadsheet spreadSheets[ispread].loose = false; } else if (imatrix != -1) { // matrix MatrixSheet& sheet = matrixes[imatrix].sheets[ilayer]; unsigned short width = 8; stmp.str(lye_header.substr(0x27)); GET_SHORT(stmp, width) if (width == 0) width = 8; sheet.width = width; stmp.str(lye_header.substr(0x2B)); GET_SHORT(stmp, sheet.columnCount) stmp.str(lye_header.substr(0x52)); GET_SHORT(stmp, sheet.rowCount) unsigned char view = lye_header[0x71]; if (view != 0x32 && view != 0x28){ sheet.view = MatrixSheet::ImageView; } else { sheet.view = MatrixSheet::DataView; } if (lye_header_size > 0xD2) { sheet.name = lye_header.substr(0xD2,32).c_str(); } } else if (iexcel != -1) { // excel excels[iexcel].loose = false; } else { // graph graphs[igraph].layers.push_back(GraphLayer()); GraphLayer& glayer = graphs[igraph].layers[ilayer]; stmp.str(lye_header.substr(0x0F)); GET_DOUBLE(stmp, glayer.xAxis.min); GET_DOUBLE(stmp, glayer.xAxis.max); GET_DOUBLE(stmp, glayer.xAxis.step); glayer.xAxis.majorTicks = lye_header[0x2B]; unsigned char g = lye_header[0x2D]; glayer.xAxis.zeroLine = (g & 0x80); glayer.xAxis.oppositeLine = (g & 0x40); glayer.xAxis.minorTicks = lye_header[0x37]; glayer.xAxis.scale = lye_header[0x38]; stmp.str(lye_header.substr(0x3A)); GET_DOUBLE(stmp, glayer.yAxis.min); GET_DOUBLE(stmp, glayer.yAxis.max); GET_DOUBLE(stmp, glayer.yAxis.step); glayer.yAxis.majorTicks = lye_header[0x56]; g = lye_header[0x58]; glayer.yAxis.zeroLine = (g & 0x80); glayer.yAxis.oppositeLine = (g & 0x40); glayer.yAxis.minorTicks = lye_header[0x62]; glayer.yAxis.scale = lye_header[0x63]; g = lye_header[0x68]; glayer.gridOnTop = (g & 0x04); glayer.exchangedAxes = (g & 0x40); stmp.str(lye_header.substr(0x71)); GET_SHORT(stmp, glayer.clientRect.left) GET_SHORT(stmp, glayer.clientRect.top) GET_SHORT(stmp, glayer.clientRect.right) GET_SHORT(stmp, glayer.clientRect.bottom) unsigned char border = lye_header[0x89]; glayer.borderType = (BorderType)(border >= 0x80 ? border-0x80 : None); if (lye_header_size > 0x107) glayer.backgroundColor = getColor(lye_header.substr(0x105,4)); } } Origin::Color OriginAnyParser::getColor(string strbincolor) { /* decode a color value from a 4 byte binary string */ Origin::Color result; unsigned char sbincolor[4]; for (int i=0; i < 4; i++) { sbincolor[i] = strbincolor[i]; } switch(sbincolor[3]) { case 0: if(sbincolor[0] < 0x64) { result.type = Origin::Color::Regular; result.regular = sbincolor[0]; } else { switch(sbincolor[2]) { case 0: result.type = Origin::Color::Indexing; break; case 0x40: result.type = Origin::Color::Mapping; break; case 0x80: result.type = Origin::Color::RGB; break; } result.column = sbincolor[0] - 0x64; } break; case 1: result.type = Origin::Color::Custom; for(int i = 0; i < 3; ++i) result.custom[i] = sbincolor[i]; break; case 0x20: result.type = Origin::Color::Increment; result.starting = sbincolor[1]; break; case 0xFF: if(sbincolor[0] == 0xFC) result.type = Origin::Color::None; else if(sbincolor[0] == 0xF7) result.type = Origin::Color::Automatic; else { result.type = Origin::Color::Regular; result.regular = sbincolor[0]; } break; default: result.type = Origin::Color::Regular; result.regular = sbincolor[0]; break; } return result; } void OriginAnyParser::getAnnotationProperties(string anhd, unsigned int anhdsz, string andt1, unsigned int andt1sz, string andt2, unsigned int andt2sz, string andt3, unsigned int andt3sz) { istringstream stmp; (void) anhdsz; (void) andt3; (void) andt3sz; if (ispread != -1) { string sec_name = anhd.substr(0x46,41).c_str(); int col_index = findColumnByName(ispread, sec_name); if (col_index != -1){ //check if it is a formula spreadSheets[ispread].columns[col_index].command = andt1.c_str(); LOG_PRINT(logfile, " Column: %s has formula: %s\n", sec_name.c_str(), spreadSheets[ispread].columns[col_index].command.c_str()) } } else if (imatrix != -1) { MatrixSheet& sheet = matrixes[imatrix].sheets[ilayer]; string sec_name = anhd.substr(0x46,41).c_str(); stmp.str(andt1.c_str()); if (sec_name == "MV") { sheet.command = andt1.c_str(); } else if (sec_name == "Y2") { stmp >> sheet.coordinates[0]; } else if (sec_name == "X2") { stmp >> sheet.coordinates[1]; } else if (sec_name == "Y1") { stmp >> sheet.coordinates[2]; } else if (sec_name == "X1") { stmp >> sheet.coordinates[3]; } else if (sec_name == "COLORMAP") { // Color maps for matrix annotations are similar to color maps for graph curves (3D). // They differ only in the start offset to the data string. getColorMap(sheet.colorMap, andt2, andt2sz); } } else if (iexcel != -1) { string sec_name = anhd.substr(0x46,41).c_str(); int col_index = findExcelColumnByName(iexcel, ilayer, sec_name); if (col_index != -1){ //check if it is a formula excels[iexcel].sheets[ilayer].columns[col_index].command = andt1.c_str(); } } else { GraphLayer& glayer = graphs[igraph].layers[ilayer]; string sec_name = anhd.substr(0x46,41).c_str(); Rect r; stmp.str(anhd.substr(0x03)); GET_SHORT(stmp, r.left) GET_SHORT(stmp, r.top) GET_SHORT(stmp, r.right) GET_SHORT(stmp, r.bottom) unsigned char attach = anhd[0x28]; unsigned char border = anhd[0x29]; Color color = getColor(anhd.substr(0x33,4)); if (sec_name == "PL") glayer.yAxis.formatAxis[0].prefix = andt1.c_str(); if (sec_name == "PR") glayer.yAxis.formatAxis[1].prefix = andt1.c_str(); if (sec_name == "PB") glayer.xAxis.formatAxis[0].prefix = andt1.c_str(); if (sec_name == "PT") glayer.xAxis.formatAxis[1].prefix = andt1.c_str(); if (sec_name == "SL") glayer.yAxis.formatAxis[0].suffix = andt1.c_str(); if (sec_name == "SR") glayer.yAxis.formatAxis[1].suffix = andt1.c_str(); if (sec_name == "SB") glayer.xAxis.formatAxis[0].suffix = andt1.c_str(); if (sec_name == "ST") glayer.xAxis.formatAxis[1].suffix = andt1.c_str(); if (sec_name == "OL") glayer.yAxis.formatAxis[0].factor = andt1.c_str(); if (sec_name == "OR") glayer.yAxis.formatAxis[1].factor = andt1.c_str(); if (sec_name == "OB") glayer.xAxis.formatAxis[0].factor = andt1.c_str(); if (sec_name == "OT") glayer.xAxis.formatAxis[1].factor = andt1.c_str(); unsigned char type = andt1[0x00]; LineVertex begin, end; /* OriginNNNParser identify line/arrow annotation by checking size of andt1 Origin410: 21||24; Origin 500: 24; Origin 610: 24||96; Origin700 and higher: 120; An alternative is to look at anhd[0x02]: (0x21 for Circle/Rect, 0x22 for Line/Arrow, 0x23 for Polygon/Polyline) */ unsigned char ankind = anhd[0x02]; if (ankind == 0x22) {//Line/Arrow if ((attach == Origin::Scale) && (andt1sz > 0x5F)) { if (type == 2) { stmp.str(andt1.substr(0x20)); GET_DOUBLE(stmp, begin.x) GET_DOUBLE(stmp, end.x) stmp.str(andt1.substr(0x40)); GET_DOUBLE(stmp, begin.y) GET_DOUBLE(stmp, end.y) } else if (type == 4) {//curved arrow: start point, 2 middle points and end point stmp.str(andt1.substr(0x20)); GET_DOUBLE(stmp, begin.x) GET_DOUBLE(stmp, end.x) GET_DOUBLE(stmp, end.x) GET_DOUBLE(stmp, end.x) GET_DOUBLE(stmp, begin.y) GET_DOUBLE(stmp, end.y) GET_DOUBLE(stmp, end.y) GET_DOUBLE(stmp, end.y) } } else { - short x1, x2, y1, y2; + short x1=0, x2=0, y1=0, y2=0; if (type == 2) {//straight line/arrow stmp.str(andt1.substr(0x01)); GET_SHORT(stmp, x1) GET_SHORT(stmp, x2) stmp.seekg(4, ios_base::cur); GET_SHORT(stmp, y1) GET_SHORT(stmp, y2) } else if (type == 4) {//curved line/arrow has 4 points stmp.str(andt1.substr(0x01)); GET_SHORT(stmp, x1) stmp.seekg(4, ios_base::cur); GET_SHORT(stmp, x2) GET_SHORT(stmp, y1) stmp.seekg(4, ios_base::cur); GET_SHORT(stmp, y2) } double dx = fabs(x2 - x1); double dy = fabs(y2 - y1); double minx = (x1 <= x2) ? x1 : x2; double miny = (y1 <= y2) ? y1 : y2; begin.x = (x1 == x2) ? r.left + 0.5*r.width() : r.left + (x1 - minx)/dx*r.width(); end.x = (x1 == x2) ? r.left + 0.5*r.width() : r.left + (x2 - minx)/dx*r.width(); begin.y = (y1 == y2) ? r.top + 0.5*r.height(): r.top + (y1 - miny)/dy*r.height(); end.y = (y1 == y2) ? r.top + 0.5*r.height(): r.top + (y2 - miny)/dy*r.height(); } unsigned char arrows = andt1[0x11]; switch (arrows) { case 0: begin.shapeType = 0; end.shapeType = 0; break; case 1: begin.shapeType = 1; end.shapeType = 0; break; case 2: begin.shapeType = 0; end.shapeType = 1; break; case 3: begin.shapeType = 1; end.shapeType = 1; break; } if (andt1sz > 0x77) { begin.shapeType = andt1[0x60]; unsigned int w = 0; stmp.str(andt1.substr(0x64)); GET_INT(stmp, w) begin.shapeWidth = (double)w/500.0; GET_INT(stmp, w) begin.shapeLength = (double)w/500.0; end.shapeType = andt1[0x6C]; stmp.str(andt1.substr(0x70)); GET_INT(stmp, w) end.shapeWidth = (double)w/500.0; GET_INT(stmp, w) end.shapeLength = (double)w/500.0; } } //text properties short rotation; stmp.str(andt1.substr(0x02)); GET_SHORT(stmp, rotation) unsigned char fontSize = andt1[0x4]; unsigned char tab = andt1[0x0A]; //line properties unsigned char lineStyle = andt1[0x12]; unsigned short w1 = 0; if (andt1sz > 0x14) { stmp.str(andt1.substr(0x13)); GET_SHORT(stmp, w1) } double width = (double)w1/500.0; Figure figure; stmp.str(andt1.substr(0x05)); GET_SHORT(stmp, w1) figure.width = (double)w1/500.0; figure.style = andt1[0x08]; if (andt1sz > 0x4D) { figure.fillAreaColor = getColor(andt1.substr(0x42,4)); stmp.str(andt1.substr(0x46)); GET_SHORT(stmp, w1) figure.fillAreaPatternWidth = (double)w1/500.0; figure.fillAreaPatternColor = getColor(andt1.substr(0x4A,4)); figure.fillAreaPattern = andt1[0x4E]; } if (andt1sz > 0x56) { unsigned char h = andt1[0x57]; figure.useBorderColor = (h == 0x10); } if (sec_name == "XB") { string text = andt2.c_str(); glayer.xAxis.position = GraphAxis::Bottom; glayer.xAxis.formatAxis[0].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "XT") { string text = andt2.c_str(); glayer.xAxis.position = GraphAxis::Top; glayer.xAxis.formatAxis[1].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "YL") { string text = andt2.c_str(); glayer.yAxis.position = GraphAxis::Left; glayer.yAxis.formatAxis[0].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "YR") { string text = andt2.c_str(); glayer.yAxis.position = GraphAxis::Right; glayer.yAxis.formatAxis[1].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "ZF") { string text = andt2.c_str(); glayer.zAxis.position = GraphAxis::Front; glayer.zAxis.formatAxis[0].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "ZB") { string text = andt2.c_str(); glayer.zAxis.position = GraphAxis::Back; glayer.zAxis.formatAxis[1].label = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "3D") { stmp.str(andt2); GET_DOUBLE(stmp, glayer.zAxis.min) GET_DOUBLE(stmp, glayer.zAxis.max) GET_DOUBLE(stmp, glayer.zAxis.step) glayer.zAxis.majorTicks = andt2[0x1C]; glayer.zAxis.minorTicks = andt2[0x28]; glayer.zAxis.scale = andt2[0x29]; stmp.str(andt2.substr(0x5A)); GET_FLOAT(stmp, glayer.xAngle) GET_FLOAT(stmp, glayer.yAngle) GET_FLOAT(stmp, glayer.zAngle) stmp.str(andt2.substr(0x218)); GET_FLOAT(stmp, glayer.xLength) GET_FLOAT(stmp, glayer.yLength) GET_FLOAT(stmp, glayer.zLength) glayer.xLength /= 23.0; glayer.yLength /= 23.0; glayer.zLength /= 23.0; glayer.orthographic3D = (andt2[0x240] != 0); } else if (sec_name == "Legend") { string text = andt2.c_str(); glayer.legend = TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach); } else if (sec_name == "__BCO2") { // histogram stmp.str(andt2.substr(0x10)); GET_DOUBLE(stmp, glayer.histogramBin) stmp.str(andt2.substr(0x20)); GET_DOUBLE(stmp, glayer.histogramEnd) GET_DOUBLE(stmp, glayer.histogramBegin) // TODO: check if 0x5E is right (obtained from anhdsz-0x46+93-andt1sz = 111-70+93-40 = 94) glayer.percentile.p1SymbolType = andt2[0x5E]; glayer.percentile.p99SymbolType = andt2[0x5F]; glayer.percentile.meanSymbolType = andt2[0x60]; glayer.percentile.maxSymbolType = andt2[0x61]; glayer.percentile.minSymbolType = andt2[0x62]; // 0x9F = 0x5E+65 glayer.percentile.labels = andt2[0x9F]; // 0x6B = 0x5E+106-93 = 107 glayer.percentile.whiskersRange = andt2[0x6B]; glayer.percentile.boxRange = andt2[0x6C]; // 0x8e = 0x5E+141-93 = 142 glayer.percentile.whiskersCoeff = andt2[0x8e]; glayer.percentile.boxCoeff = andt2[0x8f]; unsigned char h = andt2[0x90]; glayer.percentile.diamondBox = (h == 0x82) ? true : false; // 0xCB = 0x5E+109 = 203 stmp.str(andt2.substr(0xCB)); GET_SHORT(stmp, glayer.percentile.symbolSize) glayer.percentile.symbolSize = glayer.percentile.symbolSize/2 + 1; // 0x101 = 0x5E+163 glayer.percentile.symbolColor = getColor(andt2.substr(0x101,4)); glayer.percentile.symbolFillColor = getColor(andt2.substr(0x105,4)); } else if (sec_name == "_206") { // box plot labels } else if (sec_name == "VLine") { stmp.str(andt1.substr(0x0A)); double start; GET_DOUBLE(stmp, start) stmp.str(andt1.substr(0x1A)); double width; GET_DOUBLE(stmp, width) glayer.vLine = start + 0.5*width; glayer.imageProfileTool = 2; } else if (sec_name == "HLine") { stmp.str(andt1.substr(0x12)); double start; GET_DOUBLE(stmp, start) stmp.str(andt1.substr(0x22)); double width; GET_DOUBLE(stmp, width) glayer.hLine = start + 0.5*width; glayer.imageProfileTool = 2; } else if (sec_name == "vline") { stmp.str(andt1.substr(0x20)); GET_DOUBLE(stmp, glayer.vLine) glayer.imageProfileTool = 1; } else if (sec_name == "hline") { stmp.str(andt1.substr(0x40)); GET_DOUBLE(stmp, glayer.hLine) glayer.imageProfileTool = 1; } else if (sec_name == "ZCOLORS") { glayer.isXYY3D = true; if (fileVersion < 600) { ColorMap& colorMap = glayer.colorMap; getZcolorsMap(colorMap, andt2, andt2sz); } } else if (sec_name == "SPECTRUM1") { glayer.isXYY3D = false; glayer.colorScale.visible = true; glayer.colorScale.reverseOrder = andt2[0x18]; stmp.str(andt2.substr(0x20)); GET_SHORT(stmp, glayer.colorScale.colorBarThickness) GET_SHORT(stmp, glayer.colorScale.labelGap) glayer.colorScale.labelsColor = getColor(andt2.substr(0x5C,4)); } else if (sec_name == "&0") { glayer.isWaterfall = true; string text = andt1.c_str(); string::size_type commaPos = text.find_first_of(","); stmp.str(text.substr(0,commaPos)); stmp >> glayer.xOffset; stmp.str(text.substr(commaPos+1)); stmp >> glayer.yOffset; } /* OriginNNNParser identify text, circle, rectangle and bitmap annotation by checking size of andt1: text/pie text rectangle/circle line bitmap Origin410: 22 0xA(10) 21/24 38 Origin500: 22 0xA(10) 24 40 Origin610: 22 0xA(10) 24/96 40 Origin700: 0x5E(94) 120 0x28(40) Origin750: 0x3E(62)/78 0x5E(94) 0x78(120) 0x28(40) Origin850: 0x3E(62)/78 0x5E(94) 0x78(120) 0x28(40) An alternative is to look at anhd[0x02]: (0x00 for Text, 0x21 for Circle/Rect, 0x22 for Line/Arrow, 0x23 for Polygon/Polyline) */ else if ((ankind == 0x0) && (sec_name != "DelData")) { // text string text = andt2.c_str(); if (sec_name.substr(0,3) == "PIE") glayer.pieTexts.push_back(TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach)); else glayer.texts.push_back(TextBox(text, r, color, fontSize, rotation/10, tab, (BorderType)(border >= 0x80 ? border-0x80 : None), (Attach)attach)); } else if (ankind == 0x21) { // rectangle & circle switch (type) { // type = andt1[0x00] case 0: case 1: figure.type = Figure::Rectangle; break; case 2: case 3: figure.type = Figure::Circle; break; } figure.clientRect = r; figure.attach = (Attach)attach; figure.color = color; glayer.figures.push_back(figure); } else if ((ankind == 0x22) && (sec_name != "sLine") && (sec_name != "sline")) { // line/arrow glayer.lines.push_back(Line()); Line& line(glayer.lines.back()); line.color = color; line.clientRect = r; line.attach = (Attach)attach; line.width = width; line.style = lineStyle; line.begin = begin; line.end = end; } else if (andt1sz == 40) { // bitmap if (type == 4) { // type = andt1[0x00] unsigned long filesize = andt2sz + 14; glayer.bitmaps.push_back(Bitmap()); Bitmap& bitmap(glayer.bitmaps.back()); bitmap.clientRect = r; bitmap.attach = (Attach)attach; bitmap.size = filesize; bitmap.borderType = (BorderType)(border >= 0x80 ? border-0x80 : None); bitmap.data = new unsigned char[filesize]; unsigned char* data = bitmap.data; //add Bitmap header memcpy(data, "BM", 2); data += 2; memcpy(data, &filesize, 4); data += 4; unsigned int d = 0; memcpy(data, &d, 4); data += 4; d = 0x36; memcpy(data, &d, 4); data += 4; memcpy(data, andt2.c_str(), andt2sz); } else if (type == 6) { // TODO check if 0x5E is right (obtained from anhdsz-0x46+93-andt1sz = 111-70+93-40 = 94) string gname = andt2.substr(0x5E).c_str(); glayer.bitmaps.push_back(Bitmap(gname)); Bitmap& bitmap(glayer.bitmaps.back()); bitmap.clientRect = r; bitmap.attach = (Attach)attach; bitmap.size = 0; bitmap.borderType = (BorderType)(border >= 0x80 ? border-0x80 : None); } } } return; } void OriginAnyParser::getCurveProperties(string cvehd, unsigned int cvehdsz, string cvedt, unsigned int cvedtsz) { istringstream stmp; if (ispread != -1) { // spreadsheet: curves are columns // TODO: check that spreadsheet columns are stored in proper order // vector header; unsigned char c = cvehd[0x11]; string name = cvehd.substr(0x12).c_str(); unsigned short width = 0; if (cvehdsz > 0x4B) { stmp.str(cvehd.substr(0x4A)); GET_SHORT(stmp, width) } int col_index = findColumnByName(ispread, name); if (col_index != -1) { if (spreadSheets[ispread].columns[col_index].name != name) spreadSheets[ispread].columns[col_index].name = name; SpreadColumn::ColumnType type; switch(c){ case 3: type = SpreadColumn::X; break; case 0: type = SpreadColumn::Y; break; case 5: type = SpreadColumn::Z; break; case 6: type = SpreadColumn::XErr; break; case 2: type = SpreadColumn::YErr; break; case 4: type = SpreadColumn::Label; break; default: type = SpreadColumn::NONE; break; } spreadSheets[ispread].columns[col_index].type = type; width /= 0xA; if(width == 0) width = 8; spreadSheets[ispread].columns[col_index].width = width; unsigned char c1 = cvehd[0x1E]; unsigned char c2 = cvehd[0x1F]; switch (c1) { case 0x00: // Numeric - Dec1000 case 0x09: // Text&Numeric - Dec1000 case 0x10: // Numeric - Scientific case 0x19: // Text&Numeric - Scientific case 0x20: // Numeric - Engineering case 0x29: // Text&Numeric - Engineering case 0x30: // Numeric - Dec1,000 case 0x39: // Text&Numeric - Dec1,000 spreadSheets[ispread].columns[col_index].valueType = (c1%0x10 == 0x9) ? TextNumeric : Numeric; spreadSheets[ispread].columns[col_index].valueTypeSpecification = c1 / 0x10; if (c2 >= 0x80) { spreadSheets[ispread].columns[col_index].significantDigits = c2 - 0x80; spreadSheets[ispread].columns[col_index].numericDisplayType = SignificantDigits; } else if (c2 > 0) { spreadSheets[ispread].columns[col_index].decimalPlaces = c2 - 0x03; spreadSheets[ispread].columns[col_index].numericDisplayType = DecimalPlaces; } break; case 0x02: // Time spreadSheets[ispread].columns[col_index].valueType = Time; spreadSheets[ispread].columns[col_index].valueTypeSpecification = c2 - 0x80; break; case 0x03: // Date case 0x33: spreadSheets[ispread].columns[col_index].valueType = Date; spreadSheets[ispread].columns[col_index].valueTypeSpecification= c2 - 0x80; break; case 0x31: // Text spreadSheets[ispread].columns[col_index].valueType = Text; break; case 0x4: // Month case 0x34: spreadSheets[ispread].columns[col_index].valueType = Month; spreadSheets[ispread].columns[col_index].valueTypeSpecification = c2; break; case 0x5: // Day case 0x35: spreadSheets[ispread].columns[col_index].valueType = Day; spreadSheets[ispread].columns[col_index].valueTypeSpecification = c2; break; default: // Text spreadSheets[ispread].columns[col_index].valueType = Text; break; } if (cvedtsz > 0) { spreadSheets[ispread].columns[col_index].comment = cvedt.c_str(); } // TODO: check that spreadsheet columns are stored in proper order // header.push_back(spreadSheets[ispread].columns[col_index]); } // TODO: check that spreadsheet columns are stored in proper order // for (unsigned int i = 0; i < header.size(); i++) // spreadSheets[spread].columns[i] = header[i]; } else if (imatrix != -1) { MatrixSheet sheet = matrixes[imatrix].sheets[ilayer]; unsigned char c1 = cvehd[0x1E]; unsigned char c2 = cvehd[0x1F]; sheet.valueTypeSpecification = c1/0x10; if (c2 >= 0x80) { sheet.significantDigits = c2-0x80; sheet.numericDisplayType = SignificantDigits; } else if (c2 > 0) { sheet.decimalPlaces = c2-0x03; sheet.numericDisplayType = DecimalPlaces; } matrixes[imatrix].sheets[ilayer] = sheet; } else if (iexcel != -1) { unsigned char c = cvehd[0x11]; string name = cvehd.substr(0x12).c_str(); unsigned short width = 0; stmp.str(cvehd.substr(0x4A)); GET_SHORT(stmp, width) unsigned short dataID = 0; stmp.str(cvehd.substr(0x04)); GET_SHORT(stmp, dataID) unsigned int isheet = datasets[dataID-1].sheet; int col_index = findExcelColumnByName(iexcel, isheet, name); if (col_index != -1) { SpreadColumn::ColumnType type; switch(c){ case 3: type = SpreadColumn::X; break; case 0: type = SpreadColumn::Y; break; case 5: type = SpreadColumn::Z; break; case 6: type = SpreadColumn::XErr; break; case 2: type = SpreadColumn::YErr; break; case 4: type = SpreadColumn::Label; break; default: type = SpreadColumn::NONE; break; } excels[iexcel].sheets[isheet].columns[col_index].type = type; width /= 0xA; if (width == 0) width = 8; excels[iexcel].sheets[isheet].columns[col_index].width = width; unsigned char c1 = cvehd[0x1E]; unsigned char c2 = cvehd[0x1F]; switch (c1) { case 0x00: // Numeric - Dec1000 case 0x09: // Text&Numeric - Dec1000 case 0x10: // Numeric - Scientific case 0x19: // Text&Numeric - Scientific case 0x20: // Numeric - Engineering case 0x29: // Text&Numeric - Engineering case 0x30: // Numeric - Dec1,000 case 0x39: // Text&Numeric - Dec1,000 excels[iexcel].sheets[isheet].columns[col_index].valueType = (c1%0x10 == 0x9) ? TextNumeric : Numeric; excels[iexcel].sheets[isheet].columns[col_index].valueTypeSpecification = c1 / 0x10; if (c2 >= 0x80) { excels[iexcel].sheets[isheet].columns[col_index].significantDigits = c2 - 0x80; excels[iexcel].sheets[isheet].columns[col_index].numericDisplayType = SignificantDigits; } else if (c2 > 0) { excels[iexcel].sheets[isheet].columns[col_index].decimalPlaces = c2 - 0x03; excels[iexcel].sheets[isheet].columns[col_index].numericDisplayType = DecimalPlaces; } break; case 0x02: // Time excels[iexcel].sheets[isheet].columns[col_index].valueType = Time; excels[iexcel].sheets[isheet].columns[col_index].valueTypeSpecification = c2 - 0x80; break; case 0x03: // Date excels[iexcel].sheets[isheet].columns[col_index].valueType = Date; excels[iexcel].sheets[isheet].columns[col_index].valueTypeSpecification = c2 - 0x80; break; case 0x31: // Text excels[iexcel].sheets[isheet].columns[col_index].valueType = Text; break; case 0x04: // Month case 0x34: excels[iexcel].sheets[isheet].columns[col_index].valueType = Month; excels[iexcel].sheets[isheet].columns[col_index].valueTypeSpecification = c2; break; case 0x05: // Day case 0x35: excels[iexcel].sheets[isheet].columns[col_index].valueType = Day; excels[iexcel].sheets[isheet].columns[col_index].valueTypeSpecification = c2; break; default: // Text excels[iexcel].sheets[isheet].columns[col_index].valueType = Text; break; } if (cvedtsz > 0) { excels[iexcel].sheets[isheet].columns[col_index].comment = cvedt.c_str(); } } } else { GraphLayer& glayer = graphs[igraph].layers[ilayer]; glayer.curves.push_back(GraphCurve()); GraphCurve& curve(glayer.curves.back()); unsigned char h = cvehd[0x26]; curve.hidden = (h == 33); curve.type = cvehd[0x4C]; if (curve.type == GraphCurve::XYZContour || curve.type == GraphCurve::Contour) glayer.isXYY3D = false; unsigned short w; stmp.str(cvehd.substr(0x04)); GET_SHORT(stmp, w) pair column = findDataByIndex(w-1); short nColY = w; if (column.first.size() > 0) { curve.dataName = column.first; if (glayer.is3D() || (curve.type == GraphCurve::XYZContour)) { curve.zColumnName = column.second; } else { curve.yColumnName = column.second; } } stmp.str(cvehd.substr(0x23)); GET_SHORT(stmp, w) column = findDataByIndex(w-1); if (column.first.size() > 0) { curve.xDataName = (curve.dataName != column.first) ? column.first : ""; if (glayer.is3D() || (curve.type == GraphCurve::XYZContour)) { curve.yColumnName = column.second; } else if (glayer.isXYY3D){ curve.xColumnName = column.second; } else { curve.xColumnName = column.second; } } if (cvehdsz > 0x4E) { stmp.str(cvehd.substr(0x4D)); GET_SHORT(stmp, w) column = findDataByIndex(w-1); if (column.first.size() > 0 && (glayer.is3D() || (curve.type == GraphCurve::XYZContour))) { curve.xColumnName = column.second; if (curve.dataName != column.first) { // graph X and Y from different tables } } } if (glayer.is3D() || glayer.isXYY3D) graphs[igraph].is3D = true; curve.lineConnect = cvehd[0x11]; curve.lineStyle = cvehd[0x12]; curve.boxWidth = cvehd[0x14]; stmp.str(cvehd.substr(0x15)); GET_SHORT(stmp, w) curve.lineWidth = (double)w/500.0; stmp.str(cvehd.substr(0x17)); GET_SHORT(stmp, curve.symbolType) stmp.str(cvehd.substr(0x19)); GET_SHORT(stmp, w) curve.symbolSize = (double)w/500.0; h = cvehd[0x1C]; curve.fillArea = (h==2); curve.fillAreaType = cvehd[0x1E]; //text if (curve.type == GraphCurve::TextPlot) { stmp.str(cvehd.substr(0x13)); GET_SHORT(stmp, curve.text.rotation) curve.text.rotation /= 10; GET_SHORT(stmp, curve.text.fontSize) h = cvehd[0x19]; switch (h) { case 26: curve.text.justify = TextProperties::Center; break; case 2: curve.text.justify = TextProperties::Right; break; default: curve.text.justify = TextProperties::Left; break; } h = cvehd[0x20]; curve.text.fontUnderline = (h & 0x1); curve.text.fontItalic = (h & 0x2); curve.text.fontBold = (h & 0x8); curve.text.whiteOut = (h & 0x20); char offset = cvehd[0x37]; curve.text.xOffset = offset * 5; offset = cvehd[0x38]; curve.text.yOffset = offset * 5; } //vector if (curve.type == GraphCurve::FlowVector || curve.type == GraphCurve::Vector) { stmp.str(cvehd.substr(0x56)); GET_FLOAT(stmp, curve.vector.multiplier) h = cvehd[0x5E]; column = findDataByIndex(nColY - 1 + h - 0x64); if (column.first.size() > 0) curve.vector.endXColumnName = column.second; h = cvehd[0x62]; column = findDataByIndex(nColY - 1 + h - 0x64); if (column.first.size() > 0) curve.vector.endYColumnName = column.second; h = cvehd[0x18]; if (h >= 0x64) { column = findDataByIndex(nColY - 1 + h - 0x64); if (column.first.size() > 0) curve.vector.angleColumnName = column.second; } else if (h <= 0x08) curve.vector.constAngle = 45*h; h = cvehd[0x19]; if (h >= 0x64){ column = findDataByIndex(nColY - 1 + h - 0x64); if (column.first.size() > 0) curve.vector.magnitudeColumnName = column.second; } else curve.vector.constMagnitude = (int)curve.symbolSize; stmp.str(cvehd.substr(0x66)); GET_SHORT(stmp, curve.vector.arrowLenght) curve.vector.arrowAngle = cvehd[0x68]; h = cvehd[0x69]; curve.vector.arrowClosed = !(h & 0x1); stmp.str(cvehd.substr(0x70)); GET_SHORT(stmp, w) curve.vector.width = (double)w/500.0; h = cvehd[0x142]; switch (h) { case 2: curve.vector.position = VectorProperties::Midpoint; break; case 4: curve.vector.position = VectorProperties::Head; break; default: curve.vector.position = VectorProperties::Tail; break; } } //pie if (curve.type == GraphCurve::Pie) { // code from Origin410/500Parser h = cvehd[0x14]; curve.pie.formatPercentages = (h & 0x08); curve.pie.formatValues = !curve.pie.formatPercentages; curve.pie.positionAssociate = (h & 0x80); curve.pie.formatCategories = (h & 0x20); h = cvehd[0x19]; curve.pie.radius = 100 - h; h = cvehd[0x1A]; curve.pie.distance = h; curve.pie.formatAutomatic = true; curve.pie.viewAngle = 90; curve.pie.thickness = 33; curve.pie.rotation = 0; curve.pie.horizontalOffset = 0; if (cvehdsz > 0xA9) { // code from Origin750Parser.cpp h = cvehd[0x92]; curve.pie.formatPercentages = (h & 0x01); curve.pie.formatValues = (h & 0x02); curve.pie.positionAssociate = (h & 0x08); curve.pie.clockwiseRotation = (h & 0x20); curve.pie.formatCategories = (h & 0x80); curve.pie.formatAutomatic = cvehd[0x93]; stmp.str(cvehd.substr(0x94)); GET_SHORT(stmp, curve.pie.distance) curve.pie.viewAngle = cvehd[0x96]; curve.pie.thickness = cvehd[0x98]; stmp.str(cvehd.substr(0x9A)); GET_SHORT(stmp, curve.pie.rotation) stmp.str(cvehd.substr(0x9E)); GET_SHORT(stmp, curve.pie.displacement) stmp.str(cvehd.substr(0xA0)); GET_SHORT(stmp, curve.pie.radius) GET_SHORT(stmp, curve.pie.horizontalOffset) stmp.str(cvehd.substr(0xA6)); GET_INT(stmp, curve.pie.displacedSectionCount) } } //surface if (glayer.isXYY3D || curve.type == GraphCurve::Mesh3D) { curve.surface.type = cvehd[0x17]; h = cvehd[0x1C]; if ((h & 0x60) == 0x60) curve.surface.grids = SurfaceProperties::X; else if (h & 0x20) curve.surface.grids = SurfaceProperties::Y; else if (h & 0x40) curve.surface.grids = SurfaceProperties::None; else curve.surface.grids = SurfaceProperties::XY; curve.surface.sideWallEnabled = (h & 0x10); curve.surface.frontColor = getColor(cvehd.substr(0x1D,4)); h = cvehd[0x13]; curve.surface.backColorEnabled = (h & 0x08); curve.surface.surface.fill = (h & 0x10); curve.surface.surface.contour = (h & 0x40); curve.surface.topContour.fill = (h & 0x02); curve.surface.topContour.contour = (h & 0x04); curve.surface.bottomContour.fill = (h & 0x80); curve.surface.bottomContour.contour = (h & 0x01); if (cvehdsz > 0x165) { stmp.str(cvehd.substr(0x14C)); GET_SHORT(stmp, w) curve.surface.gridLineWidth = (double)w/500.0; curve.surface.gridColor = getColor(cvehd.substr(0x14E,4)); curve.surface.backColor = getColor(cvehd.substr(0x15A,4)); curve.surface.xSideWallColor = getColor(cvehd.substr(0x15E,4)); curve.surface.ySideWallColor = getColor(cvehd.substr(0x162,4)); } if (cvehdsz > 0xA9) { stmp.str(cvehd.substr(0x94)); GET_SHORT(stmp, w) curve.surface.surface.lineWidth = (double)w/500.0; curve.surface.surface.lineColor = getColor(cvehd.substr(0x96,4)); stmp.str(cvehd.substr(0xB4)); GET_SHORT(stmp, w) curve.surface.topContour.lineWidth = (double)w/500.0; curve.surface.topContour.lineColor = getColor(cvehd.substr(0xB6,4)); stmp.str(cvehd.substr(0xA4)); GET_SHORT(stmp, w) curve.surface.bottomContour.lineWidth = (double)w/500.0; curve.surface.bottomContour.lineColor = getColor(cvehd.substr(0xA6,4)); } } if (curve.type == GraphCurve::Mesh3D || curve.type == GraphCurve::Contour || curve.type == GraphCurve::XYZContour) { if (curve.type == GraphCurve::Contour || curve.type == GraphCurve::XYZContour) glayer.isXYY3D = false; ColorMap& colorMap = (curve.type == GraphCurve::Mesh3D ? curve.surface.colorMap : curve.colorMap); h = cvehd[0x13]; colorMap.fillEnabled = (h & 0x82); if ((curve.type == GraphCurve::Contour) && (cvehdsz > 0x89)) { stmp.str(cvehd.substr(0x7A)); GET_SHORT(stmp, curve.text.fontSize) h = cvehd[0x83]; curve.text.fontUnderline = (h & 0x1); curve.text.fontItalic = (h & 0x2); curve.text.fontBold = (h & 0x8); curve.text.whiteOut = (h & 0x20); curve.text.color = getColor(cvehd.substr(0x86,4)); } if (cvedtsz > 0x6C) { getColorMap(colorMap, cvedt, cvedtsz); } else { colorMap = glayer.colorMap; } } if (fileVersion >= 850) { curve.lineTransparency = cvehd[0x9C]; h = cvehd[0x9D]; curve.fillAreaWithLineTransparency = !h; curve.fillAreaTransparency = cvehd[0x11E]; } if (cvehdsz > 0x143) { curve.fillAreaColor = getColor(cvehd.substr(0xC2,4)); stmp.str(cvehd.substr(0xC6)); GET_SHORT(stmp, w) curve.fillAreaPatternWidth = (double)w/500.0; curve.fillAreaPatternColor = getColor(cvehd.substr(0xCA,4)); curve.fillAreaPattern = cvehd[0xCE]; curve.fillAreaPatternBorderStyle = cvehd[0xCF]; stmp.str(cvehd.substr(0xD0)); GET_SHORT(stmp, w) curve.fillAreaPatternBorderWidth = (double)w/500.0; curve.fillAreaPatternBorderColor = getColor(cvehd.substr(0xD2,4)); curve.fillAreaTransparency = cvehd[0x11E]; curve.lineColor = getColor(cvehd.substr(0x16A,4)); if (curve.type != GraphCurve::Contour) curve.text.color = curve.lineColor; curve.symbolFillColor = getColor(cvehd.substr(0x12E,4)); curve.symbolColor = getColor(cvehd.substr(0x132,4)); curve.vector.color = curve.symbolColor; h = cvehd[0x136]; curve.symbolThickness = (h == 255 ? 1 : h); curve.pointOffset = cvehd[0x137]; h = cvehd[0x138]; curve.symbolFillTransparency = cvehd[0x139]; h = cvehd[0x143]; curve.connectSymbols = (h&0x8); } } } void OriginAnyParser::getAxisBreakProperties(string abdata, unsigned int abdatasz) { istringstream stmp; (void) abdatasz; if (ispread != -1) { // spreadsheet } else if (imatrix != -1) { // matrix } else if (iexcel != -1) { // excel } else { // graph GraphLayer& glayer = graphs[igraph].layers[ilayer]; unsigned char h = abdata[0x02]; if (h == 2) { glayer.xAxisBreak.minorTicksBefore = glayer.xAxis.minorTicks; glayer.xAxisBreak.scaleIncrementBefore = glayer.xAxis.step; glayer.xAxisBreak.show = true; stmp.str(abdata.substr(0x0B)); GET_DOUBLE(stmp, glayer.xAxisBreak.from) GET_DOUBLE(stmp, glayer.xAxisBreak.to) GET_DOUBLE(stmp, glayer.xAxisBreak.scaleIncrementAfter) GET_DOUBLE(stmp, glayer.xAxisBreak.position) h = abdata[0x2B]; glayer.xAxisBreak.log10 = (h == 1); glayer.xAxisBreak.minorTicksAfter = abdata[0x2C]; } else if (h == 4) { glayer.yAxisBreak.minorTicksBefore = glayer.yAxis.minorTicks; glayer.yAxisBreak.scaleIncrementBefore = glayer.yAxis.step; glayer.yAxisBreak.show = true; stmp.str(abdata.substr(0x0B)); GET_DOUBLE(stmp, glayer.yAxisBreak.from) GET_DOUBLE(stmp, glayer.yAxisBreak.to) GET_DOUBLE(stmp, glayer.yAxisBreak.scaleIncrementAfter) GET_DOUBLE(stmp, glayer.yAxisBreak.position) h = abdata[0x2B]; glayer.yAxisBreak.log10 = (h == 1); glayer.yAxisBreak.minorTicksAfter = abdata[0x2C]; } } } void OriginAnyParser::getAxisParameterProperties(string apdata, unsigned int apdatasz, int naxis) { istringstream stmp; static int iaxispar = 0; if (igraph != -1) { unsigned char h = 0; unsigned short w = 0; GraphLayer& glayer = graphs[igraph].layers[ilayer]; GraphAxis axis = glayer.xAxis; if (naxis == 1) { axis = glayer.xAxis; } else if (naxis == 2) { axis = glayer.yAxis; } else if (naxis == 3) { axis = glayer.zAxis; } if (iaxispar == 0) { // minor Grid h = apdata[0x26]; axis.minorGrid.hidden = (h==0); axis.minorGrid.color = apdata[0x0F]; axis.minorGrid.style = apdata[0x12]; stmp.str(apdata.substr(0x15)); GET_SHORT(stmp, w) axis.minorGrid.width = (double)w/500.0; } else if (iaxispar == 1) { // major Grid h = apdata[0x26]; axis.majorGrid.hidden = (h==0); axis.majorGrid.color = apdata[0x0F]; axis.majorGrid.style = apdata[0x12]; stmp.str(apdata.substr(0x15)); GET_SHORT(stmp, w) axis.majorGrid.width = (double)w/500.0; } else if (iaxispar == 2) { // tickaxis 0 h = apdata[0x26]; axis.tickAxis[0].showMajorLabels = (h & 0x40); axis.tickAxis[0].color = apdata[0x0F]; stmp.str(apdata.substr(0x13)); GET_SHORT(stmp, w) axis.tickAxis[0].rotation = w/10; GET_SHORT(stmp, w) axis.tickAxis[0].fontSize = w; h = apdata[0x1A]; axis.tickAxis[0].fontBold = (h & 0x08); stmp.str(apdata.substr(0x23)); GET_SHORT(stmp, w) h = apdata[0x25]; unsigned char h1 = apdata[0x26]; axis.tickAxis[0].valueType = (ValueType)(h & 0x0F); pair column; switch (axis.tickAxis[0].valueType) { case Numeric: /*switch ((h>>4)) { case 0x9: axis.tickAxis[0].valueTypeSpecification=1; break; case 0xA: axis.tickAxis[0].valueTypeSpecification=2; break; case 0xB: axis.tickAxis[0].valueTypeSpecification=3; break; default: axis.tickAxis[0].valueTypeSpecification=0; }*/ if ((h>>4) > 7) { axis.tickAxis[0].valueTypeSpecification = (h>>4) - 8; axis.tickAxis[0].decimalPlaces = h1 - 0x40; } else { axis.tickAxis[0].valueTypeSpecification = (h>>4); axis.tickAxis[0].decimalPlaces = -1; } break; case Time: case Date: case Month: case Day: case ColumnHeading: axis.tickAxis[0].valueTypeSpecification = h1 - 0x40; break; case Text: case TickIndexedDataset: case Categorical: column = findDataByIndex(w-1); if (column.first.size() > 0) { axis.tickAxis[0].dataName = column.first; axis.tickAxis[0].columnName = column.second; } break; case TextNumeric: // Numeric Decimal 1.000 axis.tickAxis[0].valueType = Numeric; axis.tickAxis[0].valueTypeSpecification = 0; break; } } else if (iaxispar == 3) { // formataxis 0 h = apdata[0x26]; axis.formatAxis[0].hidden = (h == 0); axis.formatAxis[0].color = apdata[0x0F]; if (apdatasz > 0x4B) { stmp.str(apdata.substr(0x4A)); GET_SHORT(stmp, w) axis.formatAxis[0].majorTickLength = (double)w/10.0; } stmp.str(apdata.substr(0x15)); GET_SHORT(stmp, w) axis.formatAxis[0].thickness = (double)w/500.0; h = apdata[0x25]; axis.formatAxis[0].minorTicksType = (h>>6); axis.formatAxis[0].majorTicksType = ((h>>4) & 3); axis.formatAxis[0].axisPosition = (h & 0x0F); switch (axis.formatAxis[0].axisPosition) { // TODO: check if correct case 1: h = apdata[0x37]; axis.formatAxis[0].axisPositionValue = (double)h; break; case 2: stmp.str(apdata.substr(0x2F)); GET_DOUBLE(stmp, axis.formatAxis[0].axisPositionValue) break; } } else if (iaxispar == 4) { // tickaxis 1 h = apdata[0x26]; axis.tickAxis[1].showMajorLabels = (h & 0x40); axis.tickAxis[1].color = apdata[0x0F]; stmp.str(apdata.substr(0x13)); GET_SHORT(stmp, w) axis.tickAxis[1].rotation = w/10; GET_SHORT(stmp, w) axis.tickAxis[1].fontSize = w; h = apdata[0x1A]; axis.tickAxis[1].fontBold = (h & 0x08); stmp.str(apdata.substr(0x23)); GET_SHORT(stmp, w) h = apdata[0x25]; unsigned char h1 = apdata[0x26]; axis.tickAxis[1].valueType = (ValueType)(h & 0x0F); pair column; switch (axis.tickAxis[1].valueType) { case Numeric: /*switch ((h>>4)) { case 0x9: axis.tickAxis[1].valueTypeSpecification=1; break; case 0xA: axis.tickAxis[1].valueTypeSpecification=2; break; case 0xB: axis.tickAxis[1].valueTypeSpecification=3; break; default: axis.tickAxis[1].valueTypeSpecification=0; }*/ if ((h>>4) > 7) { axis.tickAxis[1].valueTypeSpecification = (h>>4) - 8; axis.tickAxis[1].decimalPlaces = h1 - 0x40; } else { axis.tickAxis[1].valueTypeSpecification = (h>>4); axis.tickAxis[1].decimalPlaces = -1; } break; case Time: case Date: case Month: case Day: case ColumnHeading: axis.tickAxis[1].valueTypeSpecification = h1 - 0x40; break; case Text: case TickIndexedDataset: case Categorical: column = findDataByIndex(w-1); if (column.first.size() > 0) { axis.tickAxis[1].dataName = column.first; axis.tickAxis[1].columnName = column.second; } break; case TextNumeric: // Numeric Decimal 1.000 axis.tickAxis[1].valueType = Numeric; axis.tickAxis[1].valueTypeSpecification = 0; break; } } else if (iaxispar == 5) { // formataxis 1 h = apdata[0x26]; axis.formatAxis[1].hidden = (h == 0); axis.formatAxis[1].color = apdata[0x0F]; if (apdatasz > 0x4B) { stmp.str(apdata.substr(0x4A)); GET_SHORT(stmp, w) axis.formatAxis[1].majorTickLength = (double)w/10.0; } stmp.str(apdata.substr(0x15)); GET_SHORT(stmp, w) axis.formatAxis[1].thickness = (double)w/500.0; h = apdata[0x25]; axis.formatAxis[1].minorTicksType = (h>>6); axis.formatAxis[1].majorTicksType = ((h>>4) & 3); axis.formatAxis[1].axisPosition = (h & 0x0F); switch (axis.formatAxis[1].axisPosition) { // TODO: check if correct case 1: h = apdata[0x37]; axis.formatAxis[1].axisPositionValue = (double)h; break; case 2: stmp.str(apdata.substr(0x2F)); GET_DOUBLE(stmp, axis.formatAxis[1].axisPositionValue) break; } } if (naxis == 1) { glayer.xAxis = axis; } else if (naxis == 2) { glayer.yAxis = axis; } else if (naxis == 3) { glayer.zAxis = axis; } iaxispar++; iaxispar %= 6; } } void OriginAnyParser::getNoteProperties(string nwehd, unsigned int nwehdsz, string nwelb, unsigned int nwelbsz, string nwect, unsigned int nwectsz) { istringstream stmp; (void) nwehdsz; (void) nwelbsz; (void) nwectsz; // note window position and size Rect rect; unsigned int coord; stmp.str(nwehd); GET_INT(stmp, coord) rect.left = coord; GET_INT(stmp, coord) rect.top = coord; GET_INT(stmp, coord) rect.right = coord; GET_INT(stmp, coord) rect.bottom = coord; string name = nwelb.c_str(); // ResultsLog note window has left, top, right, bottom all zero. // All other parameters are also zero, except "name" and "text". if (!rect.bottom || !rect.right) { resultsLog = nwect.c_str(); return; } unsigned char state = nwehd[0x18]; double creationDate, modificationDate; stmp.str(nwehd.substr(0x20)); GET_DOUBLE(stmp, creationDate) GET_DOUBLE(stmp, modificationDate) unsigned char c = nwehd[0x38]; unsigned int labellen = 0; stmp.str(nwehd.substr(0x3C)); GET_INT(stmp, labellen) notes.push_back(Note(name)); notes.back().objectID = objectIndex; ++objectIndex; notes.back().frameRect = rect; notes.back().creationDate = doubleToPosixTime(creationDate); notes.back().modificationDate = doubleToPosixTime(modificationDate); if (c == 0x01) notes.back().title = Window::Label; else if (c == 0x02) notes.back().title = Window::Name; else notes.back().title = Window::Both; if (state == 0x07) notes.back().state = Window::Minimized; else if (state == 0x0b) notes.back().state = Window::Maximized; notes.back().hidden = (state & 0x40); if (labellen > 1) { notes.back().label = nwect.substr(0,labellen); notes.back().text = nwect.substr(labellen).c_str(); } else { notes.back().text = nwect.c_str(); } } void OriginAnyParser::getColorMap(ColorMap& cmap, string cmapdata, unsigned int cmapdatasz) { istringstream stmp; unsigned int cmoffset = 0; // color maps for matrix annotations have a different offset than graph curve's colormaps if (imatrix != -1) { cmoffset = 0x14; } else if (igraph != -1) { cmoffset = 0x6C; } else { return; } stmp.str(cmapdata.substr(cmoffset)); unsigned int colorMapSize = 0; GET_INT(stmp, colorMapSize) // check we have enough data to fill the map unsigned int minDataSize = cmoffset + 0x114 + (colorMapSize+2)*0x38; if (minDataSize > cmapdatasz) { cerr << "WARNING: Too few data while getting ColorMap. Needed: at least " << minDataSize << " bytes. Have: " << cmapdatasz << " bytes." << endl; LOG_PRINT(logfile, "WARNING: Too few data while getting ColorMap. Needed: at least %d bytes. Have: %d bytes.\n", minDataSize, cmapdatasz) return; } unsigned int lvl_offset = 0; for (unsigned int i = 0; i < colorMapSize + 3; ++i) { lvl_offset = cmoffset + 0x114 + i*0x38; ColorMapLevel level; level.fillPattern = cmapdata[lvl_offset]; level.fillPatternColor = getColor(cmapdata.substr(lvl_offset+0x04, 4)); stmp.str(cmapdata.substr(lvl_offset+0x08)); unsigned short w; GET_SHORT(stmp, w) level.fillPatternLineWidth = (double)w/500.0; level.lineStyle = cmapdata[lvl_offset+0x10]; stmp.str(cmapdata.substr(lvl_offset+0x12)); GET_SHORT(stmp, w) level.lineWidth = (double)w/500.0; level.lineColor = getColor(cmapdata.substr(lvl_offset+0x14, 4)); unsigned char h = cmapdata[lvl_offset+0x1A]; level.labelVisible = (h & 0x1); level.lineVisible = !(h & 0x2); level.fillColor = getColor(cmapdata.substr(lvl_offset+0x28, 4)); double value = 0.0; stmp.str(cmapdata.substr(lvl_offset+0x30)); GET_DOUBLE(stmp, value) cmap.levels.push_back(make_pair(value, level)); } } void OriginAnyParser::getZcolorsMap(ColorMap& colorMap, string cmapdata, unsigned int cmapdatasz) { istringstream stmp; (void) cmapdatasz; Color lowColor;//color below lowColor.type = Origin::Color::Custom; lowColor.custom[0] = cmapdata[0x0E]; lowColor.custom[1] = cmapdata[0x0F]; lowColor.custom[2] = cmapdata[0x10]; // skip an unsigned char at 0x11 Color highColor;//color above highColor.type = Origin::Color::Custom; highColor.custom[0] = cmapdata[0x12]; highColor.custom[1] = cmapdata[0x13]; highColor.custom[2] = cmapdata[0x14]; // skip an unsigned char at 0x15 unsigned short colorMapSize; stmp.str(cmapdata.substr(0x16)); GET_SHORT(stmp, colorMapSize) // skip a short at 0x18-0x19 for (int i = 0; i < 4; ++i) {//low, high, middle and missing data colors Color color; (void) color; color.type = Origin::Color::Custom; color.custom[0] = cmapdata[0x1A+4*i]; color.custom[1] = cmapdata[0x1B+4*i]; color.custom[2] = cmapdata[0x1C+4*i]; } double zmin, zmax, zmissing; stmp.str(cmapdata.substr(0x2A)); GET_DOUBLE(stmp, zmin); GET_DOUBLE(stmp, zmax); GET_DOUBLE(stmp, zmissing); short val; for (int i = 0; i < 2; ++i) { Color color; (void) color; color.type = Origin::Color::Custom; color.custom[0] = cmapdata[0x66+10*i]; color.custom[1] = cmapdata[0x67+10*i]; color.custom[2] = cmapdata[0x68+10*i]; // skip an unsigned char at 0x69+10*i stmp.str(cmapdata.substr(0x6A+10*i)); GET_SHORT(stmp, val) } ColorMapLevel level; level.fillColor = lowColor; colorMap.levels.push_back(make_pair(zmin, level)); for (int i = 0; i < (colorMapSize + 1); ++i) { Color color; (void) color; color.type = Origin::Color::Custom; color.custom[0] = cmapdata[0x7A+10*i]; color.custom[1] = cmapdata[0x7B+10*i]; color.custom[2] = cmapdata[0x7C+10*i]; // skip an unsigned char at 0x7D+10*i stmp.str(cmapdata.substr((0x7E)+10*i)); GET_SHORT(stmp, val) level.fillColor = color; colorMap.levels.push_back(make_pair(val, level)); } level.fillColor = highColor; colorMap.levels.push_back(make_pair(zmax, level)); } void OriginAnyParser::getProjectLeafProperties(tree::iterator current_folder, string ptldt, unsigned int ptldtsz) { istringstream stmp; (void) ptldtsz; stmp.str(ptldt); unsigned int file_type = 0, file_object_id = 0; GET_INT(stmp, file_type); GET_INT(stmp, file_object_id); if (file_type == 0x100000) { // Note window if ((file_object_id <= notes.size()) && (notes.size()>0)) { projectTree.append_child(current_folder, ProjectNode(notes[file_object_id].name, ProjectNode::Note)); } } else { // other windows pair object = findObjectByIndex(file_object_id); projectTree.append_child(current_folder, ProjectNode(object.second, object.first)); } } void OriginAnyParser::getProjectFolderProperties(tree::iterator current_folder, string flehd, unsigned int flehdsz) { istringstream stmp; (void) flehdsz; unsigned char a = flehd[0x02]; (*current_folder).active = (a == 1); double creationDate, modificationDate; stmp.str(flehd.substr(0x10)); GET_DOUBLE(stmp, creationDate); GET_DOUBLE(stmp, modificationDate); (*current_folder).creationDate = doubleToPosixTime(creationDate); (*current_folder).modificationDate = doubleToPosixTime(modificationDate); } void OriginAnyParser::outputProjectTree() { unsigned int windowsCount = spreadSheets.size()+matrixes.size()+excels.size()+graphs.size()+notes.size(); cout << "Project has " << windowsCount << " windows." << endl; cout << "Origin project Tree" << endl; char cdsz[21]; for (tree::iterator it = projectTree.begin(projectTree.begin()); it != projectTree.end(projectTree.begin()); ++it) { strftime(cdsz, sizeof(cdsz), "%F %T", gmtime(&(*it).creationDate)); cout << string(projectTree.depth(it) - 1, ' ') << (*it).name.c_str() << "\t" << cdsz << endl; } } diff --git a/liborigin/OriginAnyParser.h b/liborigin/OriginAnyParser.h index 33e8d6dfb..6b988cac3 100644 --- a/liborigin/OriginAnyParser.h +++ b/liborigin/OriginAnyParser.h @@ -1,88 +1,88 @@ /* * OriginAnyParser.h * * Copyright 2017 Miquel Garriga * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Parser for all versions. Based mainly on Origin750Parser.h */ #ifndef ORIGIN_ANY_PARSER_H #define ORIGIN_ANY_PARSER_H #include "OriginParser.h" #include "endianfstream.hh" #include #include // for floor() using namespace std; using namespace Origin; class OriginAnyParser : public OriginParser { public: OriginAnyParser(const string& fileName); bool parse(); protected: unsigned int readObjectSize(); string readObjectAsString(unsigned int); void readFileVersion(); void readGlobalHeader(); bool readDataSetElement(); bool readWindowElement(); bool readLayerElement(); unsigned int readAnnotationList(); bool readAnnotationElement(); bool readCurveElement(); bool readAxisBreakElement(); bool readAxisParameterElement(unsigned int); bool readParameterElement(); bool readNoteElement(); void readProjectTree(); unsigned int readFolderTree(tree::iterator, unsigned int); void readProjectLeaf(tree::iterator); void readAttachmentList(); bool getColumnInfoAndData(string, unsigned int, string, unsigned int); void getMatrixValues(string, unsigned int, short, char, char, int); void getWindowProperties(Origin::Window&, string, unsigned int); void getLayerProperties(string, unsigned int); Origin::Color getColor(string); void getAnnotationProperties(string, unsigned int, string, unsigned int, string, unsigned int, string, unsigned int); void getCurveProperties(string, unsigned int, string, unsigned int); void getAxisBreakProperties(string, unsigned int); void getAxisParameterProperties(string, unsigned int, int); void getNoteProperties(string, unsigned int, string, unsigned int, string, unsigned int); void getColorMap(ColorMap&, string, unsigned int); void getZcolorsMap(ColorMap&, string, unsigned int); void getProjectLeafProperties(tree::iterator, string, unsigned int); void getProjectFolderProperties(tree::iterator, string, unsigned int); void outputProjectTree(); inline time_t doubleToPosixTime(double jdt) { /* 2440587.5 is julian date for the unixtime epoch */ return (time_t) floor((jdt - 2440587) * 86400. + 0.5); } iendianfstream file; FILE *logfile; unsigned long d_file_size; - unsigned int objectIndex; + unsigned int objectIndex, parseError; int ispread, imatrix, iexcel, igraph; int ilayer; }; #endif // ORIGIN_ANY_PARSER_H diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h index 252fc2db6..0e32a16e3 100644 --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -1,146 +1,147 @@ /*************************************************************************** File : Column.h Project : LabPlot Description : Aspect that manages a column -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef COLUMN_H #define COLUMN_H #include "backend/core/AbstractSimpleFilter.h" #include "backend/lib/XmlStreamReader.h" #include "backend/core/column/ColumnPrivate.h" class ColumnStringIO; class QActionGroup; class Column : public AbstractColumn { Q_OBJECT public: explicit Column(const QString& name, AbstractColumn::ColumnMode = AbstractColumn::Numeric); // template constructor for all supported data types (AbstractColumn::ColumnMode) must be defined in header template explicit Column(const QString& name, QVector data, AbstractColumn::ColumnMode mode = AbstractColumn::Numeric) : AbstractColumn(name), d(new ColumnPrivate(this, mode, new QVector(data))) { init(); }; void init(); ~Column(); virtual QIcon icon() const override; virtual QMenu* createContextMenu() override; AbstractColumn::ColumnMode columnMode() const override; void setColumnMode(AbstractColumn::ColumnMode) override; void setColumnModeFast(AbstractColumn::ColumnMode); bool copy(const AbstractColumn*) override; bool copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) override; AbstractColumn::PlotDesignation plotDesignation() const override; void setPlotDesignation(AbstractColumn::PlotDesignation) override; bool isReadOnly() const override; int rowCount() const override; int width() const; void setWidth(const int); void clear() override; AbstractSimpleFilter* outputFilter() const; ColumnStringIO* asStringColumn() const; void setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPathes); QString formula() const; const QStringList& formulaVariableNames() const; const QStringList& formulaVariableColumnPathes() const; - QString formula(const int) const override; + + QString formula(int) const override; QList< Interval > formulaIntervals() const override; void setFormula(Interval, QString) override; void setFormula(int, QString) override; void clearFormulas() override; const AbstractColumn::ColumnStatistics& statistics(); void* data() const; - QString textAt(const int) const override; - void setTextAt(const int, const QString&) override; - void replaceTexts(const int, const QVector&) override; - QDate dateAt(const int) const override; - void setDateAt(const int, const QDate&) override; - QTime timeAt(const int) const override; - void setTimeAt(const int, const QTime&) override; - QDateTime dateTimeAt(const int) const override; - void setDateTimeAt(const int, const QDateTime&) override; - void replaceDateTimes(const int, const QVector&) override; - double valueAt(const int) const override; - void setValueAt(const int, const double) override; - virtual void replaceValues(const int, const QVector&) override; - int integerAt(const int) const override; - void setIntegerAt(const int, const int) override; - virtual void replaceInteger(const int, const QVector&) override; + QString textAt(int) const override; + void setTextAt(int, const QString&) override; + void replaceTexts(int, const QVector&) override; + QDate dateAt(int) const override; + void setDateAt(int, const QDate&) override; + QTime timeAt(int) const override; + void setTimeAt(int, const QTime&) override; + QDateTime dateTimeAt(int) const override; + void setDateTimeAt(int, const QDateTime&) override; + void replaceDateTimes(int, const QVector&) override; + double valueAt(int) const override; + void setValueAt(int, const double) override; + void replaceValues(int, const QVector&) override; + int integerAt(int) const override; + void setIntegerAt(int, const int) override; + void replaceInteger(int, const QVector&) override; virtual double maximum(int count = 0) const override; virtual double minimum(int count = 0) const override; void setChanged(); void setSuppressDataChangedSignal(const bool); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; private: bool XmlReadInputFilter(XmlStreamReader*); bool XmlReadOutputFilter(XmlStreamReader*); bool XmlReadFormula(XmlStreamReader*); bool XmlReadRow(XmlStreamReader*); void handleRowInsertion(int before, int count) override; void handleRowRemoval(int first, int count) override; void calculateStatistics(); void setStatisticsAvailable(bool); bool statisticsAvailable() const; bool m_suppressDataChangedSignal; QActionGroup* m_usedInActionGroup; ColumnPrivate* d; ColumnStringIO* m_string_io; signals: void requestProjectContextMenu(QMenu*); private slots: void navigateTo(QAction*); void handleFormatChange(); friend class ColumnPrivate; friend class ColumnStringIO; }; #endif diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp index a055a07e6..ab947473f 100644 --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -1,1475 +1,1481 @@ /*************************************************************************** File : AsciiFilter.cpp Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2017 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/AsciiFilterPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include #include #include #include #include /*! \class AsciiFilter \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file. \ingroup datasources */ AsciiFilter::AsciiFilter() : AbstractFileFilter(), d(new AsciiFilterPrivate(this)) {} AsciiFilter::~AsciiFilter() {} /*! reads the content of the device \c device. */ void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } void AsciiFilter::readFromLiveDeviceNotFile(QIODevice &device, AbstractDataSource * dataSource) { d->readFromLiveDevice(device, dataSource); } qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { return d->readFromLiveDevice(device, dataSource, from); } /*! reads the content of the file \c fileName. */ QVector AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromFile(fileName, dataSource, importMode, lines); return QVector(); //TODO: remove this later once all read*-functions in the filter classes don't return any preview strings anymore } QVector AsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } QVector AsciiFilter::preview(QIODevice &device) { return d->preview(device); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ //void AsciiFilter::read(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { // d->read(fileName, dataSource, importMode); //} /*! writes the content of the data source \c dataSource to the file \c fileName. */ void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! loads the predefined filter settings for \c filterName */ void AsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void AsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list with the names of all saved (system wide or user defined) filter settings. */ QStringList AsciiFilter::predefinedFilters() { return QStringList(); } /*! returns the list of all predefined separator characters. */ QStringList AsciiFilter::separatorCharacters() { return (QStringList() << "auto" << "TAB" << "SPACE" << "," << ";" << ":" << ",TAB" << ";TAB" << ":TAB" << ",SPACE" << ";SPACE" << ":SPACE"); } /*! returns the list of all predefined comment characters. */ QStringList AsciiFilter::commentCharacters() { return (QStringList() << "#" << "!" << "//" << "+" << "c" << ":" << ";"); } /*! returns the list of all predefined data types. */ QStringList AsciiFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } /*! returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of columns"); return -1; } QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); QStringList lineStringList; if (separator.length() > 0) lineStringList = line.split(separator); else lineStringList = line.split(QRegExp("\\s+")); DEBUG("number of columns : " << lineStringList.size()); return lineStringList.size(); } size_t AsciiFilter::lineNumber(const QString& fileName) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of lines"); return 0; } size_t lineCount = 0; while (!device.atEnd()) { device.readLine(); lineCount++; } //TODO: wc is much faster but not portable /* QElapsedTimer myTimer; myTimer.start(); QProcess wc; wc.start(QString("wc"), QStringList() << "-l" << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) lineCount = wc.readLine().split(' ')[0].toInt(); lineCount++; // last line not counted DEBUG(" Elapsed time counting lines : " << myTimer.elapsed() << " ms"); */ return lineCount; } /*! returns the number of lines in the device \c device or 0 if not available. resets the position to 0! */ size_t AsciiFilter::lineNumber(QIODevice &device) { // device.hasReadLine() always returns 0 for KFilterDev! if (device.isSequential()) return 0; size_t lineCount = 0; device.seek(0); while (!device.atEnd()) { device.readLine(); lineCount++; } device.seek(0); return lineCount; } void AsciiFilter::setCommentCharacter(const QString& s) { d->commentCharacter = s; } QString AsciiFilter::commentCharacter() const { return d->commentCharacter; } void AsciiFilter::setSeparatingCharacter(const QString& s) { d->separatingCharacter = s; } QString AsciiFilter::separatingCharacter() const { return d->separatingCharacter; } void AsciiFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString AsciiFilter::dateTimeFormat() const { return d->dateTimeFormat; } void AsciiFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language AsciiFilter::numberFormat() const { return d->numberFormat; } void AsciiFilter::setAutoModeEnabled(const bool b) { d->autoModeEnabled = b; } bool AsciiFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } void AsciiFilter::setHeaderEnabled(const bool b) { d->headerEnabled = b; } bool AsciiFilter::isHeaderEnabled() const { return d->headerEnabled; } void AsciiFilter::setSkipEmptyParts(const bool b) { d->skipEmptyParts = b; } bool AsciiFilter::skipEmptyParts() const { return d->skipEmptyParts; } void AsciiFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } bool AsciiFilter::simplifyWhitespacesEnabled() const { return d->simplifyWhitespacesEnabled; } void AsciiFilter::setNaNValueToZero(bool b) { if (b) d->nanValue = 0; else d->nanValue = NAN; } bool AsciiFilter::NaNValueToZeroEnabled() const { if (d->nanValue == 0) return true; return false; } void AsciiFilter::setRemoveQuotesEnabled(bool b) { d->removeQuotesEnabled = b; } bool AsciiFilter::removeQuotesEnabled() const { return d->removeQuotesEnabled; } void AsciiFilter::setVectorNames(const QString s) { d->vectorNames.clear(); if (!s.simplified().isEmpty()) d->vectorNames = s.simplified().split(' '); } QStringList AsciiFilter::vectorNames() const { return d->vectorNames; } QVector AsciiFilter::columnModes() { return d->columnModes; } void AsciiFilter::setStartRow(const int r) { d->startRow = r; } int AsciiFilter::startRow() const { return d->startRow; } void AsciiFilter::setEndRow(const int r) { d->endRow = r; } int AsciiFilter::endRow() const { return d->endRow; } void AsciiFilter::setStartColumn(const int c) { d->startColumn = c; } int AsciiFilter::startColumn() const { return d->startColumn; } void AsciiFilter::setEndColumn(const int c) { d->endColumn = c; } int AsciiFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner), commentCharacter("#"), separatingCharacter("auto"), numberFormat(QLocale::C), autoModeEnabled(true), headerEnabled(true), skipEmptyParts(false), simplifyWhitespacesEnabled(true), nanValue(NAN), removeQuotesEnabled(false), createIndexEnabled(false), startRow(1), endRow(-1), startColumn(1), endColumn(-1), m_actualRows(0), m_actualCols(0), m_prepared(false), m_columnOffset(0) { } QStringList AsciiFilterPrivate::getLineString(QIODevice& device) { QString line; do { // skip comment lines in data lines line = device.readLine(); } while (line.startsWith(commentCharacter)); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); DEBUG("data line : \'" << line.toStdString() << '\''); QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //TODO: remove quotes here? QDEBUG("data line, parsed: " << lineStringList); return lineStringList; } /*! * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise. */ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { DEBUG("device is sequential = " << device.isSequential()); if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd() && !device.isSequential()) // empty file return 1; ///////////////////////////////////////////////////////////////// // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine; do { // skip comment lines firstLine = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } } while (firstLine.startsWith(commentCharacter)); DEBUG(" device position after first line and comments = " << device.pos()); firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("First line: \'" << firstLine.toStdString() << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); QRegExp regExp("(\\s+)|(,\\s+)|(;\\s+)|(:\\s+)"); firstLineStringList = firstLine.split(regExp, QString::SkipEmptyParts); if (!firstLineStringList.isEmpty()) { int length1 = firstLineStringList.at(0).length(); if (firstLineStringList.size() > 1) { int pos2 = firstLine.indexOf(firstLineStringList.at(1), length1); m_separator = firstLine.mid(length1, pos2 - length1); } else { //old: separator = line.right(line.length() - length1); m_separator = ' '; } } } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, QString::SkipEmptyParts); } DEBUG("separator: \'" << m_separator.toStdString() << '\''); DEBUG("number of columns: " << firstLineStringList.size()); DEBUG("headerEnabled = " << headerEnabled); + //optionally, remove potential spaces in the first line + if (simplifyWhitespacesEnabled) { + for (int i = 0; i < firstLineStringList.size(); ++i) + firstLineStringList[i] = firstLineStringList[i].simplified(); + } + if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; QDEBUG("vector names =" << vectorNames); startRow++; } // set range to read if (endColumn == -1) { if (headerEnabled || vectorNames.size() == 0) endColumn = firstLineStringList.size(); // last column else //number of vector names provided in the import dialog (not more than the maximal number of columns in the file) endColumn = qMin(vectorNames.size(), firstLineStringList.size()); } if (createIndexEnabled) { vectorNames.prepend("index"); endColumn++; } m_actualCols = endColumn - startColumn + 1; //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); DEBUG("read data line @ pos " << testpos << " : " << device.readLine().toStdString()); device.seek(testpos); testpos = device.pos(); DEBUG("read data line again @ pos " << testpos << " : " << device.readLine().toStdString()); */ // ATTENTION: This resets the position in the device to 0 m_actualRows = AsciiFilter::lineNumber(device); ///////////////////////////////////////////////////////////////// // Find first data line (ignoring comment lines) DEBUG("Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { QString line = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } if (line.startsWith(commentCharacter)) // ignore commented lines before startRow i--; } // parse first data line to determine data type for each column if (!device.isSequential()) firstLineStringList = getLineString(device); columnModes.resize(m_actualCols); int col = 0; if (createIndexEnabled) { columnModes[0] = AbstractColumn::Integer; col = 1; } for (const auto& valueString: firstLineStringList) { // parse columns available in first data line if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } // parsing more lines to better determine data types for (unsigned int i = 0; i < m_dataTypeLines; ++i) { firstLineStringList = getLineString(device); if (createIndexEnabled) col = 1; else col = 0; for (const auto& valueString: firstLineStringList) { if (col == m_actualCols) break; AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); // numeric: integer -> numeric if (mode == AbstractColumn::Numeric && columnModes[col] == AbstractColumn::Integer) columnModes[col] = mode; // text: non text -> text if (mode == AbstractColumn::Text && columnModes[col] != AbstractColumn::Text) columnModes[col] = mode; col++; } } QDEBUG("column modes = " << columnModes); // reset to start of file if (!device.isSequential()) device.seek(0); ///////////////////////////////////////////////////////////////// int actualEndRow = endRow; DEBUG("endRow = " << endRow); if (endRow == -1 || endRow > m_actualRows) actualEndRow = m_actualRows; if (m_actualRows > actualEndRow) m_actualRows = actualEndRow; DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << startRow << ' ' << actualEndRow); DEBUG("actual cols/rows (w/o header incl. start rows): " << m_actualCols << ' ' << m_actualRows); if (m_actualRows == 0 && !device.isSequential()) return 1; return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode, lines); } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { if (!(device.bytesAvailable() > 0)) { DEBUG("No new data available"); return 0; } Q_ASSERT(dataSource != nullptr); LiveDataSource* spreadsheet = dynamic_cast(dataSource); if (spreadsheet->sourceType() != LiveDataSource::SourceType::FileOrPipe) if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return 0; if (!m_prepared) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return 0; } // prepare import for spreadsheet spreadsheet->setUndoAware(false); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); qDebug() << "fds resized to col: " << m_actualCols; qDebug() << "fds rowCount: " << spreadsheet->rowCount(); //columns in a file data source don't have any manual changes. //make the available columns undo unaware and suppress the "data changed" signal. //data changes will be propagated via an explicit Column::setChanged() call once new data was read. for (int i = 0; i < spreadsheet->childCount(); i++) { spreadsheet->child(i)->setUndoAware(false); spreadsheet->child(i)->setSuppressDataChangedSignal(true); } //also here we need a cheaper version of this if (!spreadsheet->keepLastValues()) spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { spreadsheet->setRowCount(spreadsheet->keepNvalues()); m_actualRows = spreadsheet->keepNvalues(); } m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } qDebug() << "prepared!"; } qint64 bytesread = 0; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif LiveDataSource::ReadingType readingType; if (!m_prepared) readingType = LiveDataSource::ReadingType::TillEnd; else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = LiveDataSource::ReadingType::TillEnd; else readingType = spreadsheet->readingType(); } //move to the last read position, from == total bytes read //since the other source types are sequencial we cannot seek on them if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); qDebug() <<"available bytes: " << device.bytesAvailable(); //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleRateNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != LiveDataSource::ReadingType::TillEnd) { newData.reserve(spreadsheet->sampleRate()); newData.resize(spreadsheet->sampleRate()); } int newDataIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif while (!device.atEnd()) { if (readingType != LiveDataSource::ReadingType::TillEnd) newData[newDataIdx++] = device.readLine(); else newData.push_back(device.readLine()); newLinesTillEnd++; if (readingType != LiveDataSource::ReadingType::TillEnd) { newLinesForSampleRateNotTillEnd++; //for Continous reading and FromEnd we read sample rate number of lines if possible if (newLinesForSampleRateNotTillEnd == spreadsheet->sampleRate()) break; } } } //now we reset the readingType if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = spreadsheet->readingType(); //we had less new lines than the sample rate specified if (readingType != LiveDataSource::ReadingType::TillEnd) qDebug() << "Removed empty lines: " << newData.removeAll(""); //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); int currentRow; // indexes the position in the vector(column) int linesToRead; if (!m_prepared) linesToRead = newLinesTillEnd; if (m_prepared) { //increase row count if we don't have a fixed size //but only after the preparation step if (!spreadsheet->keepLastValues()) { if (readingType != LiveDataSource::ReadingType::TillEnd) m_actualRows += qMin(newData.size(), spreadsheet->sampleRate()); else m_actualRows += newData.size(); } //fixed size if (spreadsheet->keepLastValues()) { if (readingType == LiveDataSource::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; //TODO after reading we should skip the next data lines //because it's TillEnd actually } else linesToRead = newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinouslyFixed or FromEnd linesToRead = qMin(spreadsheet->sampleRate(), newLinesTillEnd); } } else { //appending linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; } } if (m_prepared && (linesToRead == 0)) return 0; //new rows/resize columns if we don't have a fixed size //TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize if (!spreadsheet->keepLastValues()) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } else { //when we have a fixed size we have to pop sampleRate number of lines if specified //here popping, setting currentRow if (!m_prepared) currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); else { if (readingType == LiveDataSource::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinouslyFixed or FromEnd currentRow = m_actualRows - qMin(spreadsheet->sampleRate(), newLinesTillEnd); } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } } // from the last row we read the new data in the spreadsheet qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd; qDebug() << "Lines to read: " << linesToRead <<" actual rows: " << m_actualRows; newDataIdx = 0; if (readingType == LiveDataSource::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->sampleRate()) newDataIdx = newData.size() - spreadsheet->sampleRate(); //since we skip a couple of lines, we need to count those bytes too for (int i = 0; i < newDataIdx; ++i) bytesread += newData.at(i).size(); } } qDebug() << "newDataIdx: " << newDataIdx; //TODO static int indexColumnIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif for (int i = 0; i < linesToRead; ++i) { QString line; if (readingType == LiveDataSource::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(i); if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) bytesread += line.size(); //qDebug() << "line bytes: " << line.size() << " line: " << line; //qDebug() << "reading in row: " << currentRow; if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; QLocale locale(numberFormat); QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); if (createIndexEnabled) { if (spreadsheet->keepLastValues()) lineStringList.prepend(QString::number(indexColumnIdx++)); else lineStringList.prepend(QString::number(currentRow)); } for (int n = 0; n < m_actualCols; ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); //qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); //qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QRegExp("[\"\']")); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = ""; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } } currentRow++; } } if (m_prepared) { //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); const Project* project = spreadsheet->project(); QVector curves = project->children(AbstractAspect::Recursive); QVector plots; for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); //determine the plots where the column is consumed for (const auto* curve: curves) { if (curve->xColumn() == column || curve->yColumn() == column) { CartesianPlot* plot = dynamic_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) { plots << plot; plot->setSuppressDataChangedSignal(true); } } } column->setChanged(); } //loop over all affected plots and retransform them for (auto* plot: plots) { //TODO setting this back to true triggers again a lot of retransforms in the plot (one for each curve). // plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } m_prepared = true; return bytesread; } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromDevice(): dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); Q_ASSERT(dataSource != nullptr); if (!m_prepared) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return; } // matrix data has only one column mode (which is not text) if (dynamic_cast(dataSource)) { auto mode = columnModes[0]; (mode == AbstractColumn::Text) ? mode = AbstractColumn::Numeric : 0; for (auto& c: columnModes) (c != mode) ? c = mode : 0; } m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows - startRow + 1, m_actualCols, vectorNames, columnModes); m_prepared = true; } DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data int currentRow = 0; // indexes the position in the vector(column) if (lines == -1) lines = m_actualRows; DEBUG("reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); ++i) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend the index if required //TODO: come up maybe with a solution with adding the index inside of the loop below, //without conversion to string, prepending to the list and then conversion back to integer. if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); for (int n = 0; n < m_actualCols; ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QRegExp("[\"\']")); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = ""; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } } currentRow++; emit q->completed(100 * currentRow/m_actualRows); } dataSource->finalizeImport(m_columnOffset, startColumn, endColumn, dateTimeFormat, importMode); } QVector AsciiFilterPrivate::preview(QIODevice &device) { QVector dataStrings; if (!(device.bytesAvailable() > 0)) { DEBUG("No new data available"); return dataStrings; } if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return dataStrings; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif int linesToRead = 0; QVector newData; while (!device.atEnd()) { newData.push_back(device.readLine()); linesToRead++; } if (linesToRead == 0) return dataStrings; int col = 0; int colMax = newData.at(0).size(); if (createIndexEnabled) colMax++; columnModes.resize(colMax); if (createIndexEnabled) { columnModes[0] = AbstractColumn::ColumnMode::Integer; col = 1; vectorNames.prepend("index"); } if (headerEnabled) { int i = 0; if (createIndexEnabled) i = 1; for (; i < vectorNames.size(); ++i) vectorNames[i] = "Column " + QString::number(i); } for (const auto& valueString: newData.at(0).split(' ', QString::SkipEmptyParts)) { if (col == colMax) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } for (int i = 0; i < linesToRead; ++i) { QString line = newData.at(i); if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; QLocale locale(numberFormat); QStringList lineStringList = line.split(' ', QString::SkipEmptyParts); if (createIndexEnabled) lineStringList.prepend(QString::number(i)); QStringList lineString; for (int n = 0; n < lineStringList.size(); ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QRegExp("[\"\']")); lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QLatin1String(""); } dataStrings << lineString; } return dataStrings; } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; } //number formatting DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data if (lines == -1) lines = m_actualRows; DEBUG("generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); ++i) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend index if required if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); QStringList lineString; for (int n = 0; n < m_actualCols; ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QRegExp("[\"\']")); lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QLatin1String(""); } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void AsciiFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: save data to ascii file } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void AsciiFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "asciiFilter"); writer->writeAttribute( "commentCharacter", d->commentCharacter); writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); writer->writeAttribute( "simplifyWhitespaces", QString::number(d->simplifyWhitespacesEnabled)); writer->writeAttribute( "nanValue", QString::number(d->nanValue)); writer->writeAttribute( "removeQuotes", QString::number(d->removeQuotesEnabled)); writer->writeAttribute( "startRow", QString::number(d->startRow)); writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); writer->writeEndElement(); } /*! Loads from XML. */ bool AsciiFilter::load(XmlStreamReader* reader) { if (!reader->isStartElement() || reader->name() != "asciiFilter") { reader->raiseError(i18n("no ascii filter element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("commentCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'commentCharacter'")); else d->commentCharacter = str; str = attribs.value("separatingCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'separatingCharacter'")); else d->separatingCharacter = str; str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'createIndex'")); else d->createIndexEnabled = str.toInt(); str = attribs.value("autoMode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoMode'")); else d->autoModeEnabled = str.toInt(); str = attribs.value("header").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'header'")); else d->headerEnabled = str.toInt(); str = attribs.value("vectorNames").toString(); d->vectorNames = str.split(' '); //may be empty str = attribs.value("simplifyWhitespaces").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'simplifyWhitespaces'")); else d->simplifyWhitespacesEnabled = str.toInt(); str = attribs.value("nanValue").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'nanValue'")); else d->nanValue = str.toDouble(); str = attribs.value("removeQuotes").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'removeQuotes'")); else d->removeQuotesEnabled = str.toInt(); str = attribs.value("skipEmptyParts").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'skipEmptyParts'")); else d->skipEmptyParts = str.toInt(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startRow'")); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endRow'")); else d->endRow = str.toInt(); str = attribs.value("startColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startColumn'")); else d->startColumn = str.toInt(); str = attribs.value("endColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endColumn'")); else d->endColumn = str.toInt(); return true; } diff --git a/src/backend/datasources/filters/HDFFilter.cpp b/src/backend/datasources/filters/HDFFilter.cpp index 2ecd2d5b5..ac8a87d46 100644 --- a/src/backend/datasources/filters/HDFFilter.cpp +++ b/src/backend/datasources/filters/HDFFilter.cpp @@ -1,1692 +1,1695 @@ /*************************************************************************** File : HDFFilter.cpp Project : LabPlot Description : HDF I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /* TODO: * Feature: implement missing data types and ranks * Performance: only fill dataPointer or dataStrings (not both) */ #include "backend/datasources/filters/HDFFilter.h" #include "backend/datasources/filters/HDFFilterPrivate.h" #include "backend/datasources/LiveDataSource.h" #include "backend/core/column/Column.h" #include #include #include /*! \class HDFFilter \brief Manages the import/export of data from/to a HDF file. \ingroup datasources */ HDFFilter::HDFFilter():AbstractFileFilter(), d(new HDFFilterPrivate(this)) {} HDFFilter::~HDFFilter() {} /*! parses the content of the file \c fileName. */ void HDFFilter::parse(const QString & fileName, QTreeWidgetItem* rootItem) { d->parse(fileName, rootItem); } /*! reads the content of the data set \c dataSet from file \c fileName. */ QVector HDFFilter::readCurrentDataSet(const QString& fileName, AbstractDataSource* dataSource, bool &ok, AbstractFileFilter::ImportMode importMode, int lines) { return d->readCurrentDataSet(fileName, dataSource, ok, importMode, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ QVector HDFFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { return d->readDataFromFile(fileName, dataSource, mode, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void HDFFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void HDFFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void HDFFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void HDFFilter::setCurrentDataSetName(QString ds) { d->currentDataSetName = ds; } const QString HDFFilter::currentDataSetName() const { return d->currentDataSetName; } void HDFFilter::setStartRow(const int s) { d->startRow = s; } int HDFFilter::startRow() const { return d->startRow; } void HDFFilter::setEndRow(const int e) { d->endRow = e; } int HDFFilter::endRow() const { return d->endRow; } void HDFFilter::setStartColumn(const int c) { d->startColumn = c; } int HDFFilter::startColumn() const { return d->startColumn; } void HDFFilter::setEndColumn(const int c) { d->endColumn = c; } int HDFFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### HDFFilterPrivate::HDFFilterPrivate(HDFFilter* owner) : - q(owner),currentDataSetName(""),startRow(1), endRow(-1), startColumn(1), endColumn(-1), m_status(0) { + q(owner),currentDataSetName(""),startRow(1), endRow(-1), startColumn(1), endColumn(-1) { +#ifdef HAVE_HDF5 + m_status = 0; +#endif } #ifdef HAVE_HDF5 void HDFFilterPrivate::handleError(int err, QString function, QString arg) { if (err < 0) DEBUG("ERROR " << err << ":" << function.toStdString() << "() - " << arg.toStdString()); } QString HDFFilterPrivate::translateHDFOrder(H5T_order_t o) { QString order; switch (o) { case H5T_ORDER_LE: order = "LE"; break; case H5T_ORDER_BE: order = "BE"; break; case H5T_ORDER_VAX: order = "VAX"; break; case H5T_ORDER_MIXED: order = "MIXED"; break; case H5T_ORDER_NONE: order = "NONE"; break; case H5T_ORDER_ERROR: order = "ERROR"; break; } return order; } QString HDFFilterPrivate::translateHDFType(hid_t t) { QString type; if (H5Tequal(t, H5T_STD_I8LE) || H5Tequal(t, H5T_STD_I8BE)) type = "CHAR"; else if (H5Tequal(t, H5T_STD_U8LE) || H5Tequal(t, H5T_STD_U8BE)) type = "UCHAR"; else if (H5Tequal(t, H5T_STD_I16LE) || H5Tequal(t, H5T_STD_I16BE)) type = "SHORT"; else if (H5Tequal(t, H5T_STD_U16LE) || H5Tequal(t, H5T_STD_U16BE)) type = "USHORT"; else if (H5Tequal(t, H5T_STD_I32LE) || H5Tequal(t, H5T_STD_I32BE)) type = "INT"; else if (H5Tequal(t, H5T_STD_U32LE) || H5Tequal(t, H5T_STD_U32BE)) type = "UINT"; else if (H5Tequal(t, H5T_NATIVE_LONG)) type = "LONG"; else if (H5Tequal(t, H5T_NATIVE_ULONG)) type = "ULONG"; else if (H5Tequal(t, H5T_STD_I64LE) || H5Tequal(t, H5T_STD_I64BE)) type = "LLONG"; else if (H5Tequal(t, H5T_STD_U64LE) || H5Tequal(t, H5T_STD_U64BE)) type = "ULLONG"; else if (H5Tequal(t, H5T_IEEE_F32LE) || H5Tequal(t, H5T_IEEE_F32BE)) type = "FLOAT"; else if (H5Tequal(t, H5T_IEEE_F64LE) || H5Tequal(t, H5T_IEEE_F64BE)) type = "DOUBLE"; else if (H5Tequal(t, H5T_NATIVE_LDOUBLE)) type = "LDOUBLE"; else type = "UNKNOWN"; return type; } QString HDFFilterPrivate::translateHDFClass(H5T_class_t c) { QString dclass; switch (c) { case H5T_INTEGER: dclass = "INTEGER"; break; case H5T_FLOAT: dclass = "FLOAT"; break; case H5T_STRING: dclass = "STRING"; break; case H5T_BITFIELD: dclass = "BITFIELD"; break; case H5T_OPAQUE: dclass = "OPAQUE"; break; case H5T_COMPOUND: dclass = "COMPOUND"; break; case H5T_ARRAY: dclass = "ARRAY"; break; case H5T_ENUM: dclass = "ENUM"; break; case H5T_REFERENCE: dclass = "REFERENCE"; break; case H5T_VLEN: dclass = "VLEN"; break; case H5T_TIME: dclass = "TIME"; break; case H5T_NCLASSES: dclass = "NCLASSES"; break; case H5T_NO_CLASS: dclass = "NOCLASS"; break; } return dclass; } QStringList HDFFilterPrivate::readHDFCompound(hid_t tid) { size_t typeSize = H5Tget_size(tid); QString line; line += QLatin1String("COMPOUND(") + QString::number(typeSize) + QLatin1String(") : ("); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); for (int i=0; i < members; ++i) { H5T_class_t mclass = H5Tget_member_class(tid, i); handleError((int)mclass, "H5Tget_member_class"); hid_t mtype = H5Tget_member_type(tid, i); handleError((int)mtype, "H5Tget_member_type"); size_t size = H5Tget_size(mtype); handleError((int)size, "H5Tget_size"); QString typeString = translateHDFClass(mclass); if (mclass == H5T_INTEGER || mclass == H5T_FLOAT) typeString = translateHDFType(mtype); line += H5Tget_member_name(tid, i) + QLatin1String("[") + typeString + QLatin1String("(") + QString::number(size) + QLatin1String(")]"); if (i == members-1) line += QLatin1String(")"); else line += QLatin1String(","); m_status = H5Tclose(mtype); handleError(m_status, "H5Tclose"); } QStringList dataString; dataString << line; return dataString; } template QStringList HDFFilterPrivate::readHDFData1D(hid_t dataset, hid_t type, int rows, int lines, void* dataContainer) { DEBUG("readHDFData1D() rows =" << rows << "lines =" << lines); QStringList dataString; // we read all rows of data T* data = new T[rows]; m_status = H5Dread(dataset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Dread"); DEBUG(" startRow =" << startRow << "endRow =" << endRow); // DEBUG("dataContainer =" << dataContainer); for (int i = startRow-1; i < qMin(endRow, lines+startRow-1); ++i) { if (dataContainer) // read to data source static_cast*>(dataContainer)->operator[](i-startRow+1) = data[i]; else // for preview dataString << QString::number(static_cast(data[i])); } delete[] data; return dataString; } QStringList HDFFilterPrivate::readHDFCompoundData1D(hid_t dataset, hid_t tid, int rows, int lines, QVector& dataContainer) { DEBUG("HDFFilterPrivate::readHDFCompoundData1D()"); DEBUG(" dataContainer size = " << dataContainer.size()); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); DEBUG(" # members = " << members); QStringList dataString; if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) dataString << QLatin1String("("); dataContainer.resize(members); // avoid "index out of range" for preview } for (int m = 0; m < members; ++m) { hid_t mtype = H5Tget_member_type(tid, m); handleError((int)mtype, "H5Tget_member_type"); size_t msize = H5Tget_size(mtype); handleError((int)msize, "H5Tget_size"); hid_t ctype = H5Tcreate(H5T_COMPOUND, msize); handleError((int)ctype, "H5Tcreate"); m_status = H5Tinsert(ctype, H5Tget_member_name(tid, m), 0, mtype); handleError(m_status, "H5Tinsert"); QStringList mdataString; if (H5Tequal(mtype, H5T_STD_I8LE) || H5Tequal(mtype, H5T_STD_I8BE)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 2: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 4: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 8: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; } } else if (H5Tequal(mtype, H5T_STD_U8LE) || H5Tequal(mtype, H5T_STD_U8BE)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 2: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 4: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 8: mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; } } else if (H5Tequal(mtype, H5T_STD_I16LE) || H5Tequal(mtype, H5T_STD_I16BE) || H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U16LE) || H5Tequal(mtype, H5T_STD_U16BE) || H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_I32LE) || H5Tequal(mtype, H5T_STD_I32BE) || H5Tequal(mtype, H5T_NATIVE_INT)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U32LE) || H5Tequal(mtype, H5T_STD_U32BE) || H5Tequal(mtype, H5T_NATIVE_UINT)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_LONG)) mdataString = readHDFData1D(dataset, ctype, rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_ULONG)) mdataString = readHDFData1D(dataset, ctype, rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_I64LE) || H5Tequal(mtype, H5T_STD_I64BE) || H5Tequal(mtype, H5T_NATIVE_LLONG)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U64LE) || H5Tequal(mtype, H5T_STD_U64BE) || H5Tequal(mtype, H5T_NATIVE_ULLONG)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_IEEE_F32LE) || H5Tequal(mtype, H5T_IEEE_F32BE)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_IEEE_F64LE) || H5Tequal(mtype, H5T_IEEE_F64BE)) mdataString = readHDFData1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_LDOUBLE)) mdataString = readHDFData1D(dataset, ctype, rows, lines, dataContainer[m]); else { if (dataContainer[m]) { for (int i = startRow-1; i < qMin(endRow, lines+startRow-1); ++i) static_cast*>(dataContainer[m])->operator[](i-startRow+1) = 0; } else { for (int i = 0; i < qMin(rows, lines); ++i) mdataString << QLatin1String("_"); } H5T_class_t mclass = H5Tget_member_class(tid, m); handleError((int)mclass, "H5Tget_member_class"); DEBUG("unsupported type of class " << translateHDFClass(mclass).toStdString()); } if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) { dataString[i] += mdataString[i]; if (m < members-1) dataString[i] += QLatin1String(","); } } H5Tclose(ctype); } if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) dataString[i] += QLatin1String(")"); } return dataString; } template QVector HDFFilterPrivate::readHDFData2D(hid_t dataset, hid_t type, int rows, int cols, int lines, QVector& dataPointer) { DEBUG("readHDFData2D() rows = " << rows << ", cols =" << cols << ", lines =" << lines); QVector dataStrings; T** data = (T**) malloc(rows*sizeof(T*)); data[0] = (T*) malloc(cols*rows*sizeof(T)); for (int i = 1; i < rows; ++i) data[i] = data[0]+i*cols; m_status = H5Dread(dataset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, &data[0][0]); handleError(m_status,"H5Dread"); for (int i = 0; i < qMin(rows, lines); ++i) { QStringList line; line.reserve(cols); for (int j = 0; j < cols; ++j) { if (dataPointer[0]) static_cast*>(dataPointer[j-startColumn+1])->operator[](i-startRow+1) = data[i][j]; else line << QString::number(static_cast(data[i][j])); } dataStrings << line; } free(data[0]); free(data); QDEBUG(dataStrings); return dataStrings; } QVector HDFFilterPrivate::readHDFCompoundData2D(hid_t dataset, hid_t tid, int rows, int cols, int lines) { DEBUG("readHDFCompoundData2D() rows =" << rows << "cols =" << cols << "lines =" << lines); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); DEBUG(" # members =" << members); QVector dataStrings; for (int i = 0; i < qMin(rows, lines); ++i) { QStringList lineStrings; for (int j = 0; j < cols; ++j) lineStrings << QLatin1String("("); dataStrings << lineStrings; } //QStringList* data = new QStringList[members]; for (int m = 0; m < members; ++m) { hid_t mtype = H5Tget_member_type(tid, m); handleError((int)mtype, "H5Tget_member_type"); size_t msize = H5Tget_size(mtype); handleError((int)msize, "H5Tget_size"); hid_t ctype = H5Tcreate(H5T_COMPOUND, msize); handleError((int)ctype, "H5Tcreate"); m_status = H5Tinsert(ctype, H5Tget_member_name(tid, m), 0, mtype); handleError(m_status, "H5Tinsert"); // dummy container for all data columns // initially contains one pointer set to NULL QVector dummy(1, nullptr); QVector mdataStrings; if (H5Tequal(mtype, H5T_STD_I8LE) || H5Tequal(mtype, H5T_STD_I8BE)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 2: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 4: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 8: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; } } else if (H5Tequal(mtype, H5T_STD_U8LE) || H5Tequal(mtype, H5T_STD_U8BE)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 2: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 4: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 8: mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; } } else if (H5Tequal(mtype, H5T_STD_I16LE) || H5Tequal(mtype, H5T_STD_I16BE)|| H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U16LE) || H5Tequal(mtype, H5T_STD_U16BE) || H5Tequal(mtype, H5T_NATIVE_USHORT)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_I32LE) || H5Tequal(mtype, H5T_STD_I32BE) || H5Tequal(mtype, H5T_NATIVE_INT)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U32LE) || H5Tequal(mtype, H5T_STD_U32BE) || H5Tequal(mtype, H5T_NATIVE_UINT)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_LONG)) mdataStrings = readHDFData2D(dataset, ctype, rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_ULONG)) mdataStrings = readHDFData2D(dataset, ctype, rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_I64LE) || H5Tequal(mtype, H5T_STD_I64BE) || H5Tequal(mtype, H5T_NATIVE_LLONG)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U64LE) || H5Tequal(mtype, H5T_STD_U64BE) || H5Tequal(mtype, H5T_NATIVE_ULLONG)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_IEEE_F32LE) || H5Tequal(mtype, H5T_IEEE_F32BE)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_IEEE_F64LE) || H5Tequal(mtype, H5T_IEEE_F64BE)) mdataStrings = readHDFData2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_LDOUBLE)) mdataStrings = readHDFData2D(dataset, ctype, rows, cols, lines, dummy); else { for (int i = 0; i < qMin(rows, lines); ++i) { QStringList lineString; for (int j = 0; j < cols; ++j) lineString << QLatin1String("_"); mdataStrings << lineString; } H5T_class_t mclass = H5Tget_member_class(tid, m); DEBUG("unsupported class " << translateHDFClass(mclass).toStdString()); } m_status = H5Tclose(ctype); handleError(m_status, "H5Tclose"); for (int i = 0; i < qMin(rows, lines); i++) { for (int j = 0; j < cols; j++) { dataStrings[i][j] += mdataStrings[i][j]; if (m < members-1) dataStrings[i][j] += QLatin1String(","); } } } for (int i = 0; i < qMin(rows, lines); ++i) { for (int j = 0; j < cols; ++j) dataStrings[i][j] += QLatin1String(")"); } QDEBUG("dataStrings =" << dataStrings); return dataStrings; } QStringList HDFFilterPrivate::readHDFAttr(hid_t aid) { QStringList attr; char name[MAXNAMELENGTH]; m_status = H5Aget_name(aid, MAXNAMELENGTH, name); handleError(m_status, "H5Aget_name"); attr << QString(name); // DEBUG(" name =" << QString(name)); hid_t aspace = H5Aget_space(aid); // the dimensions of the attribute data handleError((int)aspace, "H5Aget_space"); hid_t atype = H5Aget_type(aid); handleError((int)atype, "H5Aget_type"); hid_t aclass = H5Tget_class(atype); handleError((int)aclass, "H5Aget_class"); if (aclass == H5T_STRING) { char buf[MAXSTRINGLENGTH]; // buffer to read attr value hid_t amem = H5Tget_native_type(atype, H5T_DIR_ASCEND); handleError((int)amem, "H5Tget_native_type"); m_status = H5Aread(aid, amem, buf); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString(buf); m_status = H5Tclose(amem); handleError(m_status, "H5Tclose"); } else if (aclass == H5T_INTEGER) { if (H5Tequal(atype, H5T_STD_I8LE)) { qint8 value; m_status = H5Aread(aid, H5T_STD_I8LE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I8BE)) { qint8 value; m_status = H5Aread(aid, H5T_STD_I8BE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: { qint8 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 2: { qint16 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 4: { qint32 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 8: { qint64 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_CHAR) << " of H5T_NATIVE_CHAR"); return QStringList(""); } } else if (H5Tequal(atype, H5T_STD_U8LE)) { uint8_t value; m_status = H5Aread(aid, H5T_STD_U8LE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U8BE)) { uint8_t value; m_status = H5Aread(aid, H5T_STD_U8BE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: { uint8_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 2: { uint16_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 4: { uint32_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 8: { uint64_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_UCHAR) << " of H5T_NATIVE_UCHAR"); return QStringList(""); } } else if (H5Tequal(atype, H5T_STD_I16LE) || H5Tequal(atype, H5T_STD_I16BE) || H5Tequal(atype, H5T_NATIVE_SHORT)) { short value; m_status = H5Aread(aid, H5T_NATIVE_SHORT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U16LE) || H5Tequal(atype, H5T_STD_U16BE) || H5Tequal(atype, H5T_NATIVE_USHORT)) { unsigned short value; m_status = H5Aread(aid, H5T_NATIVE_USHORT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I32LE) || H5Tequal(atype, H5T_STD_I32BE) || H5Tequal(atype, H5T_NATIVE_INT)) { int value; m_status = H5Aread(aid, H5T_NATIVE_INT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U32LE) || H5Tequal(atype, H5T_STD_U32BE) || H5Tequal(atype, H5T_NATIVE_UINT)) { unsigned int value; m_status = H5Aread(aid, H5T_NATIVE_UINT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_LONG)) { long value; m_status = H5Aread(aid, H5T_NATIVE_LONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_ULONG)) { unsigned long value; m_status = H5Aread(aid, H5T_NATIVE_ULONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I64LE) || H5Tequal(atype, H5T_STD_I64BE) || H5Tequal(atype, H5T_NATIVE_LLONG)) { long long value; m_status = H5Aread(aid, H5T_NATIVE_LLONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U64LE) || H5Tequal(atype, H5T_STD_U64BE) || H5Tequal(atype, H5T_NATIVE_ULLONG)) { unsigned long long value; m_status = H5Aread(aid, H5T_NATIVE_ULLONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else attr<<" (unknown integer)"; } else if (aclass == H5T_FLOAT) { if (H5Tequal(atype, H5T_IEEE_F32LE) || H5Tequal(atype, H5T_IEEE_F32BE)) { float value; m_status = H5Aread(aid, H5T_NATIVE_FLOAT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_IEEE_F64LE) || H5Tequal(atype, H5T_IEEE_F64BE)) { double value; m_status = H5Aread(aid, H5T_NATIVE_DOUBLE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_LDOUBLE)) { long double value; m_status = H5Aread(aid, H5T_NATIVE_LDOUBLE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number((double)value); } else attr<<" (unknown float)"; } m_status = H5Tclose(atype); handleError(m_status, "H5Tclose"); m_status = H5Sclose(aspace); handleError(m_status, "H5Sclose"); return attr; } QStringList HDFFilterPrivate::scanHDFAttrs(hid_t oid) { QStringList attrList; int numAttr = H5Aget_num_attrs(oid); handleError(numAttr, "H5Aget_num_attrs"); DEBUG("number of attr =" << numAttr); for (int i = 0; i < numAttr; ++i) { hid_t aid = H5Aopen_idx(oid, i); handleError((int)aid, "H5Aopen_idx"); attrList << readHDFAttr(aid); if (i < numAttr-1) attrList << QLatin1String(", "); m_status = H5Aclose(aid); handleError(m_status, "H5Aclose"); } return attrList; } QStringList HDFFilterPrivate::readHDFDataType(hid_t tid) { H5T_class_t typeClass = H5Tget_class(tid); handleError((int)typeClass, "H5Tget_class"); QStringList typeProps; QString typeString = translateHDFClass(typeClass); if (typeClass == H5T_INTEGER || typeClass == H5T_FLOAT) typeString = translateHDFType(tid); typeProps<setIcon(0, QIcon::fromTheme("accessories-calculator")); dataTypeItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(dataTypeItem); } void HDFFilterPrivate::scanHDFDataSet(hid_t did, char *dataSetName, QTreeWidgetItem* parentItem) { QString attr = scanHDFAttrs(did).join(""); char link[MAXNAMELENGTH]; m_status = H5Iget_name(did, link, MAXNAMELENGTH); handleError(m_status, "H5Iget_name"); QStringList dataSetProps; hsize_t size = H5Dget_storage_size(did); handleError((int)size, "H5Dget_storage_size"); hid_t datatype = H5Dget_type(did); handleError((int)datatype, "H5Dget_type"); size_t typeSize = H5Tget_size(datatype); handleError((int)typeSize, "H5Dget_size"); dataSetProps << readHDFDataType(datatype); hid_t dataspace = H5Dget_space(did); int rank = H5Sget_simple_extent_ndims(dataspace); handleError(rank, "H5Sget_simple_extent_ndims"); unsigned int rows = 1, cols = 1, regs = 1; if (rank == 1) { hsize_t dims_out[1]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, NULL); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else if (rank == 2) { hsize_t dims_out[2]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, NULL); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; cols = dims_out[1]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String("x") << QString::number(cols) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else if (rank == 3) { hsize_t dims_out[3]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, NULL); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; cols = dims_out[1]; regs = dims_out[2]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String("x") << QString::number(cols) << QLatin1String("x") << QString::number(regs) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else dataSetProps << QLatin1String(", ") << i18n("rank %1 not supported yet").arg(rank); hid_t pid = H5Dget_create_plist(did); handleError((int)pid, "H5Dget_create_plist"); dataSetProps << ", " << readHDFPropertyList(pid).join(""); QTreeWidgetItem* dataSetItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); for (int i = 0; i < dataSetItem->columnCount(); ++i) { if (rows > 0 && cols > 0 && regs > 0) { dataSetItem->setBackground(i, QColor(192,255,192)); dataSetItem->setForeground(i, Qt::black); dataSetItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } else dataSetItem->setFlags(Qt::NoItemFlags); } parentItem->addChild(dataSetItem); } void HDFFilterPrivate::scanHDFLink(hid_t gid, char *linkName, QTreeWidgetItem* parentItem) { char target[MAXNAMELENGTH]; m_status = H5Gget_linkval(gid, linkName, MAXNAMELENGTH, target) ; handleError(m_status, "H5Gget_linkval"); QTreeWidgetItem* linkItem = new QTreeWidgetItem(QStringList() << QString(linkName) << i18n("symbolic link") << i18n("link to") + QString(target)); linkItem->setIcon(0, QIcon::fromTheme("emblem-symbolic-link")); linkItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(linkItem); } void HDFFilterPrivate::scanHDFGroup(hid_t gid, char *groupName, QTreeWidgetItem* parentItem) { DEBUG("HDFFilterPrivate::scanHDFGroup()"); //check for hard link H5G_stat_t statbuf; m_status = H5Gget_objinfo(gid, ".", true, &statbuf); handleError(m_status, "H5Gget_objinfo"); if (statbuf.nlink > 1) { if (m_multiLinkList.contains(statbuf.objno[0])) { QTreeWidgetItem* objectItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("link")); objectItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(objectItem); return; } else { m_multiLinkList.append(statbuf.objno[0]); DEBUG(" group multiple links: "<setIcon(0, QIcon::fromTheme("folder")); groupItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(groupItem); hsize_t numObj; m_status = H5Gget_num_objs(gid, &numObj); handleError(m_status, "H5Gget_num_objs"); for (unsigned int i = 0; i < numObj; ++i) { char memberName[MAXNAMELENGTH]; m_status = H5Gget_objname_by_idx(gid, (hsize_t)i, memberName, (size_t)MAXNAMELENGTH ); handleError(m_status, "H5Gget_objname_by_idx"); int otype = H5Gget_objtype_by_idx(gid, (size_t)i ); handleError(otype, "H5Gget_objtype_by_idx"); switch (otype) { case H5G_LINK: { scanHDFLink(gid, memberName, groupItem); break; } case H5G_GROUP: { hid_t grpid = H5Gopen(gid, memberName, H5P_DEFAULT); handleError((int)grpid, "H5Gopen"); scanHDFGroup(grpid, memberName, groupItem); m_status = H5Gclose(grpid); handleError(m_status, "H5Gclose"); break; } case H5G_DATASET: { hid_t dsid = H5Dopen(gid, memberName, H5P_DEFAULT); handleError((int)dsid, "H5Dopen"); scanHDFDataSet(dsid, memberName, groupItem); m_status = H5Dclose(dsid); handleError(m_status, "H5Dclose"); break; } case H5G_TYPE: { hid_t tid = H5Topen(gid, memberName, H5P_DEFAULT); handleError((int)tid, "H5Topen"); scanHDFDataType(tid, memberName, groupItem); m_status = H5Tclose(tid); handleError(m_status, "H5Tclose"); break; } default: QTreeWidgetItem* objectItem = new QTreeWidgetItem(QStringList() << QString(memberName) << i18n("unknown")); objectItem->setFlags(Qt::ItemIsEnabled); groupItem->addChild(objectItem); break; } } } #endif /*! parses the content of the file \c fileName and fill the tree using rootItem. */ void HDFFilterPrivate::parse(const QString & fileName, QTreeWidgetItem* rootItem) { DEBUG("HDFFilterPrivate::parse()"); #ifdef HAVE_HDF5 QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); hid_t file = H5Fopen(bafileName.data(), H5F_ACC_RDONLY, H5P_DEFAULT); handleError((int)file, "H5Fopen", fileName); char rootName[] = "/"; hid_t group = H5Gopen(file, rootName, H5P_DEFAULT); handleError((int)group, "H5Gopen", rootName); // CRASHES multiLinkList.clear(); scanHDFGroup(group, rootName, rootItem); m_status = H5Gclose(group); handleError(m_status, "H5Gclose", ""); m_status = H5Fclose(file); handleError(m_status, "H5Fclose", ""); #else DEBUG("HDF not available"); Q_UNUSED(fileName) Q_UNUSED(rootItem) #endif } /*! reads the content of the date set in the file \c fileName to a string (for preview) or to the data source. */ QVector HDFFilterPrivate::readCurrentDataSet(const QString& fileName, AbstractDataSource* dataSource, bool &ok, AbstractFileFilter::ImportMode mode, int lines) { DEBUG("HDFFilter::readCurrentDataSet()"); QVector dataStrings; if (currentDataSetName.isEmpty()) { //return QString("No data set selected").replace(' ',QChar::Nbsp); ok = false; return dataStrings << (QStringList() << i18n("No data set selected")); } DEBUG(" current data set =" << currentDataSetName.toStdString()); #ifdef HAVE_HDF5 QByteArray bafileName = fileName.toLatin1(); hid_t file = H5Fopen(bafileName.data(), H5F_ACC_RDONLY, H5P_DEFAULT); handleError((int)file, "H5Fopen", fileName); QByteArray badataSet = currentDataSetName.toLatin1(); hid_t dataset = H5Dopen2(file, badataSet.data(), H5P_DEFAULT); handleError((int)file, "H5Dopen2", currentDataSetName); // Get datatype and dataspace hid_t dtype = H5Dget_type(dataset); handleError((int)dtype, "H5Dget_type"); H5T_class_t dclass = H5Tget_class(dtype); handleError((int)dclass, "H5Dget_class"); size_t typeSize = H5Tget_size(dtype); handleError((int)(typeSize-1), "H5Dget_size"); hid_t dataspace = H5Dget_space(dataset); handleError((int)dataspace, "H5Dget_space"); int rank = H5Sget_simple_extent_ndims(dataspace); handleError(rank, "H5Dget_simple_extent_ndims"); DEBUG(" rank =" << rank); int columnOffset = 0; // offset to import data int actualRows = 0, actualCols = 0; // rows and cols to read // dataContainer is used to store the data read from the dataSource // it contains the pointers of all columns // initially there is one pointer set to nullptr // check for dataContainer[0] != nullptr to decide if dataSource can be used QVector dataContainer(1, nullptr); // rank= 0: single value, 1: vector, 2: matrix, 3: 3D data, ... switch (rank) { case 0: { // single value actualRows = 1; actualCols = 1; switch (dclass) { case H5T_STRING: { char* data = (char *) malloc(typeSize * sizeof(char)); hid_t memtype = H5Tcopy(H5T_C_S1); handleError((int)memtype, "H5Tcopy"); m_status = H5Tset_size(memtype, typeSize); handleError(m_status, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Tread"); dataStrings << (QStringList() << data); free(data); break; } case H5T_INTEGER: case H5T_FLOAT: case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_COMPOUND: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataStrings << (QStringList() << i18n("rank 0 not implemented yet for type %1").arg(translateHDFClass(dclass))); qDebug() << dataStrings; } default: break; } break; } case 1: { // vector hsize_t size, maxSize; m_status = H5Sget_simple_extent_dims(dataspace, &size, &maxSize); handleError(m_status, "H5Sget_simple_extent_dims"); int rows = size; if (endRow == -1) endRow = rows; if (lines == -1) lines = endRow; actualRows = endRow-startRow+1; actualCols = 1; #ifndef NDEBUG H5T_order_t order = H5Tget_order(dtype); handleError((int)order, "H5Sget_order"); qDebug() << translateHDFClass(dclass) << "(" << typeSize << ")" << translateHDFOrder(order) << ", rows:" << rows << " max:" << maxSize; #endif //TODO: support other modes QVector columnModes; columnModes.resize(actualCols); //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); QStringList dataString; // data saved in a list switch (dclass) { case H5T_STRING: { DEBUG("rank 1 H5T_STRING"); hid_t memtype = H5Tcopy(H5T_C_S1); handleError((int)memtype, "H5Tcopy"); char** data = (char **) malloc(rows * sizeof (char *)); if (H5Tis_variable_str(dtype)) { m_status = H5Tset_size(memtype, H5T_VARIABLE); handleError((int)memtype, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Dread"); } else { data[0] = (char *) malloc(rows * typeSize * sizeof (char)); for (int i = 1; i < rows; ++i) data[i] = data[0] + i * typeSize; m_status = H5Tset_size(memtype, typeSize); handleError((int)memtype, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data[0]); handleError(m_status, "H5Dread"); } for (int i = startRow-1; i < qMin(endRow, lines+startRow-1); ++i) dataString << data[i]; free(data); break; } case H5T_INTEGER: { if (H5Tequal(dtype, H5T_STD_I8LE)) dataString = readHDFData1D(dataset, H5T_STD_I8LE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I8BE)) dataString = readHDFData1D(dataset, H5T_STD_I8BE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: dataString = readHDFData1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 2: dataString = readHDFData1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 4: dataString = readHDFData1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 8: dataString = readHDFData1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; } } else if (H5Tequal(dtype, H5T_STD_U8LE)) dataString = readHDFData1D(dataset, H5T_STD_U8LE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U8BE)) dataString = readHDFData1D(dataset, H5T_STD_U8BE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: dataString = readHDFData1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 2: dataString = readHDFData1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 4: dataString = readHDFData1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 8: dataString = readHDFData1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; } } else if (H5Tequal(dtype, H5T_STD_I16LE) || H5Tequal(dtype, H5T_STD_I16BE) || H5Tequal(dtype, H5T_NATIVE_SHORT)) dataString = readHDFData1D(dataset, H5T_NATIVE_SHORT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U16LE) || H5Tequal(dtype, H5T_STD_U16BE) || H5Tequal(dtype, H5T_NATIVE_USHORT)) dataString = readHDFData1D(dataset, H5T_NATIVE_USHORT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I32LE) || H5Tequal(dtype, H5T_STD_I32BE) || H5Tequal(dtype, H5T_NATIVE_INT)) dataString = readHDFData1D(dataset, H5T_NATIVE_INT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U32LE) || H5Tequal(dtype, H5T_STD_U32BE) || H5Tequal(dtype, H5T_NATIVE_UINT)) dataString = readHDFData1D(dataset, H5T_NATIVE_UINT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_LONG)) dataString = readHDFData1D(dataset, H5T_NATIVE_LONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_ULONG)) dataString = readHDFData1D(dataset, H5T_NATIVE_ULONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I64LE) || H5Tequal(dtype, H5T_STD_I64BE) || H5Tequal(dtype, H5T_NATIVE_LLONG)) dataString = readHDFData1D(dataset, H5T_NATIVE_LLONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U64LE) || H5Tequal(dtype, H5T_STD_U64BE) || H5Tequal(dtype, H5T_NATIVE_ULLONG)) dataString = readHDFData1D(dataset, H5T_NATIVE_ULLONG, rows, lines, dataContainer[0]); else { ok = false; dataString = (QStringList() << i18n("unsupported integer type for rank 1")); qDebug() << dataString; } break; } case H5T_FLOAT: { if (H5Tequal(dtype, H5T_IEEE_F32LE) || H5Tequal(dtype, H5T_IEEE_F32BE)) dataString = readHDFData1D(dataset, H5T_NATIVE_FLOAT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_IEEE_F64LE) || H5Tequal(dtype, H5T_IEEE_F64BE)) dataString = readHDFData1D(dataset, H5T_NATIVE_DOUBLE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_LDOUBLE)) dataString = readHDFData1D(dataset, H5T_NATIVE_LDOUBLE, rows, lines, dataContainer[0]); else { ok = false; dataString = (QStringList() << i18n("unsupported float type for rank 1")); qDebug() << dataString; } break; } case H5T_COMPOUND: { int members = H5Tget_nmembers(dtype); handleError(members, "H5Tget_nmembers"); if (dataSource) { // re-create data pointer dataContainer.clear(); dataSource->prepareImport(dataContainer, mode, actualRows, members, vectorNames, columnModes); } else dataStrings << readHDFCompound(dtype); dataString = readHDFCompoundData1D(dataset, dtype, rows, lines, dataContainer); break; } case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataString = (QStringList() << i18n("rank 1 not implemented yet for type %1").arg(translateHDFClass(dclass))); qDebug() << dataString; } default: break; } if (dataSource == NULL) { QDEBUG("dataString =" << dataString); for (int i = 0; i < qMin(rows, lines); ++i) dataStrings << (QStringList() << dataString[i]); } break; } case 2: { // matrix hsize_t dims_out[2]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, NULL); handleError(m_status, "H5Sget_simple_extent_dims"); int rows = dims_out[0]; int cols = dims_out[1]; if (endRow == -1) endRow=rows; if (lines == -1) lines = endRow; if (endColumn == -1) endColumn = cols; actualRows = endRow-startRow+1; actualCols = endColumn-startColumn+1; #ifndef NDEBUG H5T_order_t order = H5Tget_order(dtype); handleError((int)order, "H5Tget_order"); qDebug()< columnModes; columnModes.resize(actualCols); //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); // read data switch (dclass) { case H5T_INTEGER: { if (H5Tequal(dtype, H5T_STD_I8LE)) dataStrings << readHDFData2D(dataset, H5T_STD_I8LE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I8BE)) dataStrings << readHDFData2D(dataset, H5T_STD_I8BE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: dataStrings << readHDFData2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 2: dataStrings << readHDFData2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 4: dataStrings << readHDFData2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 8: dataStrings << readHDFData2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; } } else if (H5Tequal(dtype, H5T_STD_U8LE)) dataStrings << readHDFData2D(dataset, H5T_STD_U8LE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U8BE)) dataStrings << readHDFData2D(dataset, H5T_STD_U8BE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: dataStrings << readHDFData2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 2: dataStrings << readHDFData2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 4: dataStrings << readHDFData2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 8: dataStrings << readHDFData2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; } } else if (H5Tequal(dtype, H5T_STD_I16LE) || H5Tequal(dtype, H5T_STD_I16BE) || H5Tequal(dtype, H5T_NATIVE_SHORT)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_SHORT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U16LE) || H5Tequal(dtype, H5T_STD_U16BE) || H5Tequal(dtype, H5T_NATIVE_USHORT)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_USHORT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I32LE) || H5Tequal(dtype, H5T_STD_I32BE) || H5Tequal(dtype, H5T_NATIVE_INT)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_INT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U32LE) || H5Tequal(dtype, H5T_STD_U32BE) || H5Tequal(dtype, H5T_NATIVE_UINT)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_UINT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_LONG)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_LONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_ULONG)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_ULONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I64LE) || H5Tequal(dtype, H5T_STD_I64BE) || H5Tequal(dtype, H5T_NATIVE_LLONG)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_LLONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U64LE) || H5Tequal(dtype, H5T_STD_U64BE) || H5Tequal(dtype, H5T_NATIVE_ULLONG)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_ULLONG, rows, cols, lines, dataContainer); else { ok=false; dataStrings << (QStringList() << i18n("unsupported integer type for rank 2")); qDebug() << dataStrings; } break; } case H5T_FLOAT: { if (H5Tequal(dtype, H5T_IEEE_F32LE) || H5Tequal(dtype, H5T_IEEE_F32BE)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_FLOAT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_IEEE_F64LE) || H5Tequal(dtype, H5T_IEEE_F64BE)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_DOUBLE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_LDOUBLE)) dataStrings << readHDFData2D(dataset, H5T_NATIVE_LDOUBLE, rows, cols, lines, dataContainer); else { ok = false; dataStrings << (QStringList() << i18n("unsupported float type for rank 2")); qDebug() << dataStrings; } break; } case H5T_COMPOUND: { dataStrings << readHDFCompound(dtype); qDebug() << dataStrings; dataStrings << readHDFCompoundData2D(dataset,dtype,rows,cols,lines); break; } case H5T_STRING: { // TODO: implement this ok = false; dataStrings << (QStringList() << i18n("rank 2 not implemented yet for type %1").arg(translateHDFClass(dclass)) + ", " + i18n("size = %1").arg(typeSize)); qDebug() << dataStrings; break; } case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataStrings << (QStringList() << i18n("rank 2 not implemented yet for type %1").arg(translateHDFClass(dclass))); qDebug() << dataStrings; } default: break; } break; } default: { // 3D or more data ok = false; dataStrings << (QStringList() << i18n("rank %1 not implemented yet for type %2").arg(rank).arg(translateHDFClass(dclass))); qDebug() << dataStrings; } } m_status = H5Sclose(dataspace); handleError(m_status, "H5Sclose"); m_status = H5Tclose(dtype); handleError(m_status, "H5Tclose"); m_status = H5Dclose(dataset); handleError(m_status, "H5Dclose"); m_status = H5Fclose(file); handleError(m_status, "H5Fclose"); if (!dataSource) return dataStrings; dataSource->finalizeImport(columnOffset, 1, actualCols, "", mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(mode) Q_UNUSED(lines) #endif return dataStrings; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ QVector HDFFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { Q_UNUSED(lines); DEBUG("HDFFilter::read()"); QVector dataStrings; if (currentDataSetName.isEmpty()) { DEBUG("No data set selected"); return dataStrings; } bool ok = true; return readCurrentDataSet(fileName, dataSource, ok, mode); } /*! writes the content of \c dataSource to the file \c fileName. */ void HDFFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: writing HDF5 not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void HDFFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("hdfFilter"); writer->writeEndElement(); } /*! Loads from XML. */ bool HDFFilter::load(XmlStreamReader* reader) { if (!reader->isStartElement() || reader->name() != "hdfFilter") { reader->raiseError(i18n("no hdf filter element found")); return false; } // QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); // QXmlStreamAttributes attribs = reader->attributes(); return true; } diff --git a/src/backend/datasources/filters/HDFFilterPrivate.h b/src/backend/datasources/filters/HDFFilterPrivate.h index 339b2b7bc..75babef71 100644 --- a/src/backend/datasources/filters/HDFFilterPrivate.h +++ b/src/backend/datasources/filters/HDFFilterPrivate.h @@ -1,86 +1,88 @@ /*************************************************************************** File : HDFFilterPrivate.h Project : LabPlot Description : Private implementation class for HDFFilter. -------------------------------------------------------------------- Copyright : (C) 2015-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef HDFFILTERPRIVATE_H #define HDFFILTERPRIVATE_H #include #ifdef HAVE_HDF5 #include #endif class AbstractDataSource; class HDFFilterPrivate { public: explicit HDFFilterPrivate(HDFFilter*); void parse(const QString & fileName, QTreeWidgetItem* rootItem); QVector readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); QVector readCurrentDataSet(const QString& fileName, AbstractDataSource*, bool &ok, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); void write(const QString& fileName, AbstractDataSource*); const HDFFilter* q; QString currentDataSetName; int startRow; int endRow; int startColumn; int endColumn; private: +#ifdef HAVE_HDF5 + int m_status; +#endif const static int MAXNAMELENGTH = 1024; const static int MAXSTRINGLENGTH = 1024*1024; - int m_status; QList m_multiLinkList; // used to find hard links #ifdef HAVE_HDF5 void handleError(int err, QString function, QString arg=QString()); QString translateHDFOrder(H5T_order_t); QString translateHDFType(hid_t); QString translateHDFClass(H5T_class_t); QStringList readHDFCompound(hid_t tid); template QStringList readHDFData1D(hid_t dataset, hid_t type, int rows, int lines, void* dataPointer = nullptr); QStringList readHDFCompoundData1D(hid_t dataset, hid_t tid, int rows, int lines, QVector& dataPointer); template QVector readHDFData2D(hid_t dataset, hid_t ctype, int rows, int cols, int lines, QVector& dataPointer); QVector readHDFCompoundData2D(hid_t dataset, hid_t tid, int rows, int cols, int lines); QStringList readHDFAttr(hid_t aid); QStringList scanHDFAttrs(hid_t oid); QStringList readHDFDataType(hid_t tid); QStringList readHDFPropertyList(hid_t pid); void scanHDFDataType(hid_t tid, char* dataTypeName, QTreeWidgetItem* parentItem); void scanHDFLink(hid_t gid, char* linkName, QTreeWidgetItem* parentItem); void scanHDFDataSet(hid_t dsid, char* dataSetName, QTreeWidgetItem* parentItem); void scanHDFGroup(hid_t gid, char* groupName, QTreeWidgetItem* parentItem); #endif }; #endif diff --git a/src/backend/datasources/filters/NetCDFFilter.cpp b/src/backend/datasources/filters/NetCDFFilter.cpp index f34c71235..8916cc12c 100644 --- a/src/backend/datasources/filters/NetCDFFilter.cpp +++ b/src/backend/datasources/filters/NetCDFFilter.cpp @@ -1,694 +1,697 @@ /*************************************************************************** File : NetCDFFilter.cpp Project : LabPlot Description : NetCDF I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/NetCDFFilterPrivate.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include #include /*! \class NetCDFFilter \brief Manages the import/export of data from/to a NetCDF file. \ingroup datasources */ NetCDFFilter::NetCDFFilter():AbstractFileFilter(), d(new NetCDFFilterPrivate(this)) {} NetCDFFilter::~NetCDFFilter() {} /*! parses the content of the file \c ileName. */ void NetCDFFilter::parse(const QString & fileName, QTreeWidgetItem* rootItem) { d->parse(fileName, rootItem); } /*! reads the content of the selected attribute from file \c fileName. */ QString NetCDFFilter::readAttribute(const QString & fileName, const QString & name, const QString & varName) { return d->readAttribute(fileName, name, varName); } /*! reads the content of the current variable from file \c fileName. */ QVector NetCDFFilter::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { return d->readCurrentVar(fileName, dataSource, importMode, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ QVector NetCDFFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { return d->readDataFromFile(fileName, dataSource, mode, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NetCDFFilter::write(const QString & fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void NetCDFFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NetCDFFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void NetCDFFilter::setCurrentVarName(QString ds) { d->currentVarName = ds; } const QString NetCDFFilter::currentVarName() const { return d->currentVarName; } void NetCDFFilter::setStartRow(const int s) { d->startRow = s; } int NetCDFFilter::startRow() const { return d->startRow; } void NetCDFFilter::setEndRow(const int e) { d->endRow = e; } int NetCDFFilter::endRow() const { return d->endRow; } void NetCDFFilter::setStartColumn(const int c) { d->startColumn=c; } int NetCDFFilter::startColumn() const { return d->startColumn; } void NetCDFFilter::setEndColumn(const int c) { d->endColumn = c; } int NetCDFFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### NetCDFFilterPrivate::NetCDFFilterPrivate(NetCDFFilter* owner) : - q(owner), startRow(1), endRow(-1), startColumn(1), endColumn(-1), status(0) { + q(owner), startRow(1), endRow(-1), startColumn(1), endColumn(-1) { +#ifdef HAVE_NETCDF + m_status = 0; +#endif } #ifdef HAVE_NETCDF void NetCDFFilterPrivate::handleError(int err, QString function) { if (err != NC_NOERR) - qDebug() << "NETCDF ERROR:" << function << "() - " << nc_strerror(status); + qDebug() << "NETCDF ERROR:" << function << "() - " << nc_strerror(m_status); } QString NetCDFFilterPrivate::translateDataType(nc_type type) { QString typeString; switch (type) { case NC_BYTE: typeString = "BYTE"; break; case NC_UBYTE: typeString = "UBYTE"; break; case NC_CHAR: typeString = "CHAR"; break; case NC_SHORT: typeString = "SHORT"; break; case NC_USHORT: typeString = "USHORT"; break; case NC_INT: typeString = "INT"; break; case NC_UINT: typeString = "UINT"; break; case NC_INT64: typeString = "INT64"; break; case NC_UINT64: typeString = "UINT64"; break; case NC_FLOAT: typeString = "FLOAT"; break; case NC_DOUBLE: typeString = "DOUBLE"; break; case NC_STRING: typeString = "STRING"; break; default: typeString = "UNKNOWN"; } return typeString; } QString NetCDFFilterPrivate::scanAttrs(int ncid, int varid, int attid, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; int nattr, nstart = 0; if (attid == -1) { - status = nc_inq_varnatts(ncid, varid, &nattr); - handleError(status, "nc_inq_varnatts"); + m_status = nc_inq_varnatts(ncid, varid, &nattr); + handleError(m_status, "nc_inq_varnatts"); } else { nstart = attid; nattr = attid+1; } nc_type type; size_t len; QStringList valueString; for (int i = nstart; i < nattr; i++) { valueString.clear(); - status = nc_inq_attname(ncid, varid, i, name); - handleError(status, "nc_inq_attname"); + m_status = nc_inq_attname(ncid, varid, i, name); + handleError(m_status, "nc_inq_attname"); - status = nc_inq_att(ncid, varid, name, &type, &len); - handleError(status, "nc_inq_att"); + m_status = nc_inq_att(ncid, varid, name, &type, &len); + handleError(m_status, "nc_inq_att"); QDEBUG(" attr" << i+1 << "name/type/len =" << name << translateDataType(type) << len); //read attribute switch (type) { case NC_BYTE: { signed char *value = (signed char *)malloc(len*sizeof(signed char)); - status = nc_get_att_schar(ncid, varid, name, value); - handleError(status, "nc_get_att_schar"); + m_status = nc_get_att_schar(ncid, varid, name, value); + handleError(m_status, "nc_get_att_schar"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_UBYTE: { unsigned char *value = (unsigned char *)malloc(len*sizeof(unsigned char)); - status = nc_get_att_uchar(ncid, varid, name, value); - handleError(status, "nc_get_att_uchar"); + m_status = nc_get_att_uchar(ncid, varid, name, value); + handleError(m_status, "nc_get_att_uchar"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_CHAR: { char *value = (char *)malloc((len+1)*sizeof(char)); - status = nc_get_att_text(ncid, varid, name, value); - handleError(status, "nc_get_att_text"); + m_status = nc_get_att_text(ncid, varid, name, value); + handleError(m_status, "nc_get_att_text"); value[len] = 0; valueString << value; free(value); break; } case NC_SHORT: { short *value = (short *)malloc(len*sizeof(short)); - status = nc_get_att_short(ncid, varid, name, value); - handleError(status, "nc_get_att_short"); + m_status = nc_get_att_short(ncid, varid, name, value); + handleError(m_status, "nc_get_att_short"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_USHORT: { unsigned short *value = (unsigned short *)malloc(len*sizeof(unsigned short)); - status = nc_get_att_ushort(ncid, varid, name, value); - handleError(status, "nc_get_att_ushort"); + m_status = nc_get_att_ushort(ncid, varid, name, value); + handleError(m_status, "nc_get_att_ushort"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_INT: { int *value = (int *)malloc(len*sizeof(int)); - status = nc_get_att_int(ncid, varid, name, value); - handleError(status, "nc_get_att_int"); + m_status = nc_get_att_int(ncid, varid, name, value); + handleError(m_status, "nc_get_att_int"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_UINT: { unsigned int *value = (unsigned int *)malloc(len*sizeof(unsigned int)); - status = nc_get_att_uint(ncid, varid, name, value); - handleError(status, "nc_get_att_uint"); + m_status = nc_get_att_uint(ncid, varid, name, value); + handleError(m_status, "nc_get_att_uint"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_INT64: { long long *value = (long long *)malloc(len*sizeof(long long)); - status = nc_get_att_longlong(ncid, varid, name, value); - handleError(status, "nc_get_att_longlong"); + m_status = nc_get_att_longlong(ncid, varid, name, value); + handleError(m_status, "nc_get_att_longlong"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_UINT64: { unsigned long long *value = (unsigned long long *)malloc(len*sizeof(unsigned long long)); - status = nc_get_att_ulonglong(ncid, varid, name, value); - handleError(status, "nc_get_att_ulonglong"); + m_status = nc_get_att_ulonglong(ncid, varid, name, value); + handleError(m_status, "nc_get_att_ulonglong"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_FLOAT: { float *value = (float *)malloc(len*sizeof(float)); - status = nc_get_att_float(ncid, varid, name, value); - handleError(status, "nc_get_att_float"); + m_status = nc_get_att_float(ncid, varid, name, value); + handleError(m_status, "nc_get_att_float"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } case NC_DOUBLE: { double *value = (double *)malloc(len*sizeof(double)); - status = nc_get_att_double(ncid, varid, name, value); - handleError(status, "nc_get_att_double"); + m_status = nc_get_att_double(ncid, varid, name, value); + handleError(m_status, "nc_get_att_double"); for (unsigned int l = 0; l < len; l++) valueString << QString::number(value[l]); free(value); break; } default: valueString << "not supported"; } if (parentItem != NULL) { QString typeName; if (varid == NC_GLOBAL) typeName = i18n("global attribute"); else { char varName[NC_MAX_NAME + 1]; - status = nc_inq_varname(ncid, varid, varName); + m_status = nc_inq_varname(ncid, varid, varName); typeName = QString(varName) + ' ' + i18n("attribute"); } QStringList props; props << translateDataType(type) << " (" << QString::number(len) << ")"; QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << typeName << props.join("") << valueString.join(", ")); attrItem->setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } return valueString.join("\n"); } void NetCDFFilterPrivate::scanDims(int ncid, int ndims, QTreeWidgetItem* parentItem) { int ulid; - status = nc_inq_unlimdim(ncid, &ulid); - handleError(status, "nc_inq_att"); + m_status = nc_inq_unlimdim(ncid, &ulid); + handleError(m_status, "nc_inq_att"); char name[NC_MAX_NAME + 1]; size_t len; for (int i = 0; i < ndims; i++) { - status = nc_inq_dim(ncid, i, name, &len); - handleError(status, "nc_inq_att"); + m_status = nc_inq_dim(ncid, i, name, &len); + handleError(m_status, "nc_inq_att"); DEBUG(" dim" << i+1 << ": name/len =" << name << len); QStringList props; props<setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } void NetCDFFilterPrivate::scanVars(int ncid, int nvars, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; nc_type type; int ndims, nattrs; int dimids[NC_MAX_VAR_DIMS]; for (int i = 0; i < nvars; i++) { - status = nc_inq_var(ncid, i, name, &type, &ndims, dimids, &nattrs); - handleError(status, "nc_inq_att"); + m_status = nc_inq_var(ncid, i, name, &type, &ndims, dimids, &nattrs); + handleError(m_status, "nc_inq_att"); QDEBUG(" var" << i+1 << ": name/type=" << name << translateDataType(type)); DEBUG(" ndims/nattr" << ndims << nattrs); QStringList props; props << translateDataType(type); char dname[NC_MAX_NAME + 1]; size_t dlen; props << "("; for (int j = 0; j < ndims; j++) { - status = nc_inq_dim(ncid, dimids[j], dname, &dlen); + m_status = nc_inq_dim(ncid, dimids[j], dname, &dlen); if (j != 0) props << "x"; props << QString::number(dlen); } props << ")"; QTreeWidgetItem *varItem = new QTreeWidgetItem(QStringList() << QString(name) << i18n("variable") << props.join("")<<""); varItem->setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); varItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // highlight item for (int c = 0; c < varItem->columnCount(); c++) { varItem->setBackground(c, QColor(192, 255, 192)); varItem->setForeground(c, Qt::black); } parentItem->addChild(varItem); scanAttrs(ncid, i, -1, varItem); } } #endif /*! parses the content of the file \c fileName and fill the tree using rootItem. */ void NetCDFFilterPrivate::parse(const QString & fileName, QTreeWidgetItem* rootItem) { #ifdef HAVE_NETCDF QByteArray bafileName = fileName.toLatin1(); int ncid; - status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); - handleError(status, "nc_open"); + m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); + handleError(m_status, "nc_open"); int ndims, nvars, nattr, uldid; - status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); - handleError(status, "nc_inq"); + m_status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); + handleError(m_status, "nc_inq"); DEBUG(" nattr/ndims/nvars =" << nattr << ndims << nvars); QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(i18n("Attributes"))); attrItem->setIcon(0,QIcon::fromTheme("folder")); attrItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(attrItem); scanAttrs(ncid, NC_GLOBAL, -1, attrItem); QTreeWidgetItem *dimItem = new QTreeWidgetItem(QStringList() << QString(i18n("Dimensions"))); dimItem->setIcon(0, QIcon::fromTheme("folder")); dimItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(dimItem); scanDims(ncid, ndims, dimItem); QTreeWidgetItem *varItem = new QTreeWidgetItem(QStringList() << QString(i18n("Variables"))); varItem->setIcon(0, QIcon::fromTheme("folder")); varItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(varItem); scanVars(ncid, nvars, varItem); #else Q_UNUSED(fileName) Q_UNUSED(rootItem) #endif } QString NetCDFFilterPrivate::readAttribute(const QString & fileName, const QString & name, const QString & varName) { #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); - status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); - handleError(status, "nc_open"); + m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); + handleError(m_status, "nc_open"); // get varid int varid; if (varName == "global") varid = NC_GLOBAL; else { QByteArray bavarName = varName.toLatin1(); - status = nc_inq_varid(ncid, bavarName.data(), &varid); - handleError(status, "nc_inq_varid"); + m_status = nc_inq_varid(ncid, bavarName.data(), &varid); + handleError(m_status, "nc_inq_varid"); } // attribute 'name' int attid; QByteArray baName = name.toLatin1(); - status = nc_inq_attid(ncid, varid, baName.data(), &attid); - handleError(status, "nc_inq_attid"); + m_status = nc_inq_attid(ncid, varid, baName.data(), &attid); + handleError(m_status, "nc_inq_attid"); return scanAttrs(ncid, varid, attid); #else Q_UNUSED(fileName) Q_UNUSED(name) Q_UNUSED(varName) return QString(); #endif } /*! reads the content of the variable in the file \c fileName to a string (for preview) or to the data source. */ QVector NetCDFFilterPrivate::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { QVector dataStrings; if (currentVarName.isEmpty()) return dataStrings << (QStringList() << i18n("No variable selected")); QDEBUG(" current variable =" << currentVarName); #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); - status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); - handleError(status, "nc_open"); + m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); + handleError(m_status, "nc_open"); int varid; QByteArray baVarName = currentVarName.toLatin1(); - status = nc_inq_varid(ncid, baVarName.data(), &varid); - handleError(status, "nc_inq_varid"); + m_status = nc_inq_varid(ncid, baVarName.data(), &varid); + handleError(m_status, "nc_inq_varid"); int ndims; nc_type type; - status = nc_inq_varndims(ncid, varid, &ndims); - handleError(status, "nc_inq_varndims"); - status = nc_inq_vartype(ncid, varid, &type); - handleError(status, "nc_inq_type"); + m_status = nc_inq_varndims(ncid, varid, &ndims); + handleError(m_status, "nc_inq_varndims"); + m_status = nc_inq_vartype(ncid, varid, &type); + handleError(m_status, "nc_inq_type"); int* dimids = (int *) malloc(ndims * sizeof(int)); - status = nc_inq_vardimid(ncid, varid, dimids); - handleError(status, "nc_inq_vardimid"); + m_status = nc_inq_vardimid(ncid, varid, dimids); + handleError(m_status, "nc_inq_vardimid"); int actualRows = 0, actualCols = 0; int columnOffset = 0; QVector dataContainer; switch (ndims) { case 0: dataStrings << (QStringList() << i18n("zero dimensions")); qDebug() << dataStrings; break; case 1: { size_t size; - status = nc_inq_dimlen(ncid, dimids[0], &size); - handleError(status, "nc_inq_dimlen"); + m_status = nc_inq_dimlen(ncid, dimids[0], &size); + handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = size; if (lines == -1) lines = endRow; actualRows = endRow-startRow+1; actualCols = 1; DEBUG("start/end row" << startRow << endRow); DEBUG("act rows/cols" << actualRows << actualCols); //TODO: support other modes QVector columnModes; columnModes.resize(actualCols); //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); double* data = nullptr; if (dataSource) data = static_cast*>(dataContainer[0])->data(); else data = new double[actualRows]; size_t start = startRow-1, count = actualRows; - status = nc_get_vara_double(ncid, varid, &start, &count, data); - handleError(status, "nc_get_vara_double"); + m_status = nc_get_vara_double(ncid, varid, &start, &count, data); + handleError(m_status, "nc_get_vara_double"); if (!dataSource) { for (int i = 0; i < qMin(actualRows, lines); i++) dataStrings << (QStringList() << QString::number(data[i])); delete[] data; } break; } case 2: { size_t rows, cols; - status = nc_inq_dimlen(ncid, dimids[0], &rows); - handleError(status, "nc_inq_dimlen"); - status = nc_inq_dimlen(ncid, dimids[1], &cols); - handleError(status, "nc_inq_dimlen"); + m_status = nc_inq_dimlen(ncid, dimids[0], &rows); + handleError(m_status, "nc_inq_dimlen"); + m_status = nc_inq_dimlen(ncid, dimids[1], &cols); + handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = rows; if (lines == -1) lines = endRow; if (endColumn == -1) endColumn = cols; actualRows = endRow-startRow+1; actualCols = endColumn-startColumn+1; DEBUG("dim =" << rows << "x" << cols); DEBUG("startRow/endRow:" << startRow << endRow); DEBUG("startColumn/endColumn:" << startColumn << endColumn); DEBUG("actual rows/cols:" << actualRows << actualCols); DEBUG("lines:" << lines); //TODO: support other modes QVector columnModes; columnModes.resize(actualCols); //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); double** data = (double**) malloc(rows * sizeof(double*)); data[0] = (double*)malloc( cols * rows * sizeof(double) ); for (unsigned int i = 1; i < rows; i++) data[i] = data[0] + i*cols; - status = nc_get_var_double(ncid, varid, &data[0][0]); - handleError(status, "nc_get_var_double"); + m_status = nc_get_var_double(ncid, varid, &data[0][0]); + handleError(m_status, "nc_get_var_double"); for (int i = 0; i < qMin((int)rows, lines); i++) { QStringList line; for (unsigned int j = 0; j < cols; j++) { if (dataContainer[0]) static_cast*>(dataContainer[j-startColumn+1])->operator[](i-startRow+1) = data[i][j]; else line << QString::number(data[i][j]); } dataStrings << line; emit q->completed(100*i/actualRows); } free(data[0]); free(data); break; } default: dataStrings << (QStringList() << i18n("%1 dimensional data of type %2 not supported yet").arg(ndims).arg(translateDataType(type))); qDebug() << dataStrings; } free(dimids); if (!dataSource) return dataStrings; dataSource->finalizeImport(columnOffset, 1, actualCols, "", mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(mode) Q_UNUSED(lines) #endif return dataStrings; } /*! reads the content of the current selected variable from file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ QVector NetCDFFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { Q_UNUSED(lines); QVector dataStrings; if (currentVarName.isEmpty()) { DEBUG(" No variable selected"); return dataStrings; } DEBUG(" current variable =" << currentVarName.toStdString()); return readCurrentVar(fileName, dataSource, mode); } /*! writes the content of \c dataSource to the file \c fileName. */ void NetCDFFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NetCDFFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("netcdfFilter"); writer->writeEndElement(); } /*! Loads from XML. */ bool NetCDFFilter::load(XmlStreamReader* reader) { if (!reader->isStartElement() || reader->name() != "netcdfFilter") { reader->raiseError(i18n("no netcdf filter element found")); return false; } // QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); // QXmlStreamAttributes attribs = reader->attributes(); return true; } diff --git a/src/backend/datasources/filters/NetCDFFilterPrivate.h b/src/backend/datasources/filters/NetCDFFilterPrivate.h index 324f4bbcd..3c0ba19c9 100644 --- a/src/backend/datasources/filters/NetCDFFilterPrivate.h +++ b/src/backend/datasources/filters/NetCDFFilterPrivate.h @@ -1,68 +1,69 @@ /*************************************************************************** File : NetCDFFilterPrivate.h Project : LabPlot Description : Private implementation class for NetCDFFilter. -------------------------------------------------------------------- Copyright : (C) 2015 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef NETCDFFILTERPRIVATE_H #define NETCDFFILTERPRIVATE_H #ifdef HAVE_NETCDF #include #endif class AbstractDataSource; class NetCDFFilterPrivate { public: explicit NetCDFFilterPrivate(NetCDFFilter*); void parse(const QString & fileName, QTreeWidgetItem* rootItem); QVector readDataFromFile(const QString& fileName, AbstractDataSource* = nullptr, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); QString readAttribute(const QString& fileName, const QString& name, const QString& varName); QVector readCurrentVar(const QString& fileName, AbstractDataSource* = nullptr, AbstractFileFilter::ImportMode = AbstractFileFilter::Replace, int lines = -1); void write(const QString& fileName, AbstractDataSource*); const NetCDFFilter* q; QString currentVarName; int startRow; int endRow; int startColumn; int endColumn; private: - int status; #ifdef HAVE_NETCDF + int m_status; + void handleError(int status, QString function); QString translateDataType(nc_type type); QString scanAttrs(int ncid, int varid, int attid, QTreeWidgetItem* parentItem = nullptr); void scanDims(int ncid, int ndims, QTreeWidgetItem* parentItem); void scanVars(int ncid, int nvars, QTreeWidgetItem* parentItem); #endif }; #endif diff --git a/src/backend/datasources/projects/LabPlotProjectParser.cpp b/src/backend/datasources/projects/LabPlotProjectParser.cpp index 8f8d14021..72055132b 100644 --- a/src/backend/datasources/projects/LabPlotProjectParser.cpp +++ b/src/backend/datasources/projects/LabPlotProjectParser.cpp @@ -1,68 +1,81 @@ /*************************************************************************** File : LabPlotProjectParser.h Project : LabPlot Description : parser for LabPlot projects -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/projects/LabPlotProjectParser.h" -#include "backend/core/Project.h" #include "backend/core/AspectTreeModel.h" +#include "backend/core/Project.h" /*! \class LabPlotProjectParser \brief parser for LabPlot projects. \ingroup datasources */ LabPlotProjectParser::LabPlotProjectParser() : ProjectParser() { m_topLevelClasses<<"Folder"<<"Workbook"<<"Spreadsheet"<<"Matrix"<<"Worksheet"<<"CantorWorksheet"<<"Datapicker"<<"LiveDataSource"; } LabPlotProjectParser::~LabPlotProjectParser() { if (m_project != nullptr) delete m_project; } QAbstractItemModel* LabPlotProjectParser::model() { WAIT_CURSOR; if (m_project == nullptr) m_project = new Project(); AspectTreeModel* model = nullptr; bool rc = m_project->load(m_projectFileName, true); if (rc) { model = new AspectTreeModel(m_project); model->setReadOnly(true); } RESET_CURSOR; return model; } -void LabPlotProjectParser::importTo(Folder* folder) { - Q_UNUSED(folder); +void LabPlotProjectParser::importTo(Folder* folder, const QStringList& selectedPathes) { + QDEBUG("Starting the import of " + m_projectFileName); + Q_UNUSED(selectedPathes); + + //import the selected objects into a temporary project + Project* project = new Project(); + project->load(m_projectFileName); + + //move all children from the temp project to the target folder + for (auto* child : project->children()) { + project->removeChild(child); + folder->addChild(child); + } + delete project; + QDEBUG("Import of " + m_projectFileName + " done."); } diff --git a/src/backend/datasources/projects/LabPlotProjectParser.h b/src/backend/datasources/projects/LabPlotProjectParser.h index 8c96965f6..f21855809 100644 --- a/src/backend/datasources/projects/LabPlotProjectParser.h +++ b/src/backend/datasources/projects/LabPlotProjectParser.h @@ -1,44 +1,44 @@ /*************************************************************************** File : LabPlotProjectParser.h Project : LabPlot Description : parser for LabPlot projects -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef LABPLOTPROJECTPARSER_H #define LABPLOTPROJECTPARSER_H #include "backend/datasources/projects/ProjectParser.h" class LabPlotProjectParser : public ProjectParser { Q_OBJECT public: LabPlotProjectParser(); virtual ~LabPlotProjectParser() override; virtual QAbstractItemModel* model() override; - virtual void importTo(Folder*) override; + virtual void importTo(Folder*, const QStringList&) override; }; #endif // LABPLOTPROJECTPARSER_H diff --git a/src/backend/datasources/projects/OriginProjectParser.cpp b/src/backend/datasources/projects/OriginProjectParser.cpp index d6e8b55ac..405741113 100644 --- a/src/backend/datasources/projects/OriginProjectParser.cpp +++ b/src/backend/datasources/projects/OriginProjectParser.cpp @@ -1,71 +1,72 @@ /*************************************************************************** File : OriginProjectParser.h Project : LabPlot Description : parser for Origin projects -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/projects/OriginProjectParser.h" #include "backend/core/Project.h" #include "backend/core/AspectTreeModel.h" #include #include "kdefrontend/datasources/ImportOpj.h" /*! \class OriginProjectParser \brief parser for Origin projects. \ingroup datasources */ OriginProjectParser::OriginProjectParser() : ProjectParser() { m_topLevelClasses << "Folder" << "Workbook" << "Spreadsheet" << "Matrix" << "Worksheet"; } QAbstractItemModel* OriginProjectParser::model() { WAIT_CURSOR; if (m_project == nullptr) m_project = new Project(); //parse the OPJ file and create a Project object for the preview ImportOpj(m_project, m_projectFileName, true); bool rc = true; //TODO // m_project->setName("Test"); RESET_CURSOR; AspectTreeModel* model = nullptr; if (rc) { model = new AspectTreeModel(m_project); model->setReadOnly(true); } return model; } -void OriginProjectParser::importTo(Folder* folder) { +void OriginProjectParser::importTo(Folder* folder, const QStringList& selectedPathes) { + Q_UNUSED(selectedPathes); ImportOpj(folder, m_projectFileName, true); } diff --git a/src/backend/datasources/projects/OriginProjectParser.h b/src/backend/datasources/projects/OriginProjectParser.h index 8e7435061..b85b6ff09 100644 --- a/src/backend/datasources/projects/OriginProjectParser.h +++ b/src/backend/datasources/projects/OriginProjectParser.h @@ -1,43 +1,43 @@ /*************************************************************************** File : OriginProjectParser.h Project : LabPlot Description : parser for Origin projects -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef ORIGINPROJECTPARSER_H #define ORIGINPROJECTPARSER_H #include "backend/datasources/projects/ProjectParser.h" class OriginProjectParser : public ProjectParser { Q_OBJECT public: OriginProjectParser(); virtual QAbstractItemModel* model() override; - virtual void importTo(Folder*) override; + virtual void importTo(Folder*, const QStringList&) override; }; #endif // ORIGINPROJECTPARSER_H diff --git a/src/backend/datasources/projects/ProjectParser.h b/src/backend/datasources/projects/ProjectParser.h index 89a353ab8..96771e3b3 100644 --- a/src/backend/datasources/projects/ProjectParser.h +++ b/src/backend/datasources/projects/ProjectParser.h @@ -1,62 +1,62 @@ /*************************************************************************** File : ProjectParser.h Project : LabPlot Description : base class for project parsers -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef PROJECTPARSER_H #define PROJECTPARSER_H #include class QAbstractItemModel; class QString; class Folder; class Project; class ProjectParser : public QObject { Q_OBJECT public: ProjectParser(); virtual ~ProjectParser() {}; void setProjectFileName(const QString&); const QString& projectFileName() const; virtual QAbstractItemModel* model() = 0; - virtual void importTo(Folder*) = 0; + virtual void importTo(Folder*, const QStringList&) = 0; QList topLevelClasses() const ; protected: QString m_projectFileName; Project* m_project; QList m_topLevelClasses; signals: void completed(int); }; #endif // PROJECTPARSER_H diff --git a/src/backend/nsl/nsl_fit.c b/src/backend/nsl/nsl_fit.c index 3509c7d66..713517dfe 100644 --- a/src/backend/nsl/nsl_fit.c +++ b/src/backend/nsl/nsl_fit.c @@ -1,646 +1,646 @@ /*************************************************************************** File : nsl_fit.c Project : LabPlot Description : NSL (non)linear fit functions -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "nsl_fit.h" #include "nsl_common.h" #include #include #include #include #include const char* nsl_fit_model_category_name[] = {i18n("Basic functions"), i18n("Peak functions"), i18n("Growth (sigmoidal)"), i18n("Statistics (distributions)"), i18n("Custom")}; const char* nsl_fit_model_basic_name[] = {i18n("Polynomial"), i18n("Power"), i18n("Exponential"), i18n("Inverse exponential"), i18n("Fourier")}; const char* nsl_fit_model_basic_equation[] = {"c0 + c1*x", "a*x^b", "a*exp(b*x)", "a*(1-exp(b*x)) + c", "a0 + (a1*cos(w*x) + b1*sin(w*x))"}; const char* nsl_fit_model_basic_pic_name[] = {"polynom", "power", "exponential", "inv_exponential", "fourier"}; const char* nsl_fit_model_peak_name[] = {i18n("Gaussian (normal)"), i18n("Cauchy-Lorentz"), i18n("Hyperbolic secant (sech)"), i18n("Logistic (sech squared)")}; const char* nsl_fit_model_peak_equation[] = {"a/sqrt(2*pi)/s * exp(-((x-mu)/s)^2/2)", "a/pi * g/(g^2+(x-mu)^2)", "a/pi/s * sech((x-mu)/s)", "a/4/s * sech((x-mu)/2/s)**2"}; const char* nsl_fit_model_peak_pic_name[] = {"gaussian", "cauchy_lorentz", "sech", "logistic"}; const char* nsl_fit_model_growth_name[] = {i18n("Inverse tangent"), i18n("Hyperbolic tangent"), i18n("Algebraic sigmoid"), i18n("Logistic function"), i18n("Error function (erf)"), i18n("Hill"), i18n("Gompertz"), i18n("Gudermann (gd)")}; const char* nsl_fit_model_growth_equation[] = {"a * atan((x-mu)/s)", "a * tanh((x-mu)/s)", "a * (x-mu)/s/sqrt(1+((x-mu)/s)^2)", "a/(1+exp(-k*(x-mu)))", "a/2 * erf((x-mu)/s/sqrt(2))", "a * x^n/(s^n + x^n)", "a*exp(-b*exp(-c*x))", "a * asin(tanh((x-mu)/s))"}; const char* nsl_fit_model_growth_pic_name[] = {"atan", "tanh", "alg_sigmoid", "logistic_function", "erf", "hill", "gompertz", "gd"}; -const char* nsl_fit_weight_type_name[] = {"No", "Instrumental", "Direct", "Inverse", "Statistical", "Statistical (Fit)", "Relative", "Relative (Fit)"}; +const char* nsl_fit_weight_type_name[] = {"No", "Instrumental (1/col^2)", "Direct (col)", "Inverse (1/col)", "Statistical (1/data)", "Statistical (Fit)", "Relative (1/data^2)", "Relative (Fit)"}; /* see http://seal.web.cern.ch/seal/documents/minuit/mnusersguide.pdf and https://lmfit.github.io/lmfit-py/bounds.html */ double nsl_fit_map_bound(double x, double min, double max) { if (max <= min) { printf("given bounds must fulfill max > min (min = %g, max = %g)! Giving up.\n", min, max); return DBL_MAX; } /* not bounded */ if (min == -DBL_MAX && max == DBL_MAX) return x; /* open bounds */ if (min == -DBL_MAX) return max + 1. - sqrt(x*x + 1.); if (max == DBL_MAX) return min - 1. + sqrt(x*x + 1.); return min + sin(x + 1.) * (max - min)/2.; /* alternative transformation for closed bounds return min + (max - min)/(1. + exp(-x)); */ } /* see http://seal.web.cern.ch/seal/documents/minuit/mnusersguide.pdf and https://lmfit.github.io/lmfit-py/bounds.html */ double nsl_fit_map_unbound(double x, double min, double max) { if (max <= min) { printf("given bounds must fulfill max > min (min = %g, max = %g)! Giving up.\n", min, max); return DBL_MAX; } if (x < min || x > max) { printf("given value must be within bounds! Giving up.\n"); return -DBL_MAX; } /* not bounded */ if (min == -DBL_MAX && max == DBL_MAX) return x; /* open bounds */ if (min == -DBL_MAX) return sqrt(gsl_pow_2(max - x + 1.) - 1.); if (max == DBL_MAX) return sqrt(gsl_pow_2(x - min + 1.) - 1.); return asin(2. * (x - min)/(max - min) - 1.); /* alternative transformation for closed bounds return -log((max - x)/(x - min)); */ } /********************** parameter derivatives ******************/ /* basic */ double nsl_fit_model_polynomial_param_deriv(double x, int j, double weight) { return weight*pow(x, j); } double nsl_fit_model_power1_param_deriv(int param, double x, double a, double b, double weight) { if (param == 0) return weight*pow(x, b); if (param == 1) return weight*a*pow(x, b)*log(x); return 0; } double nsl_fit_model_power2_param_deriv(int param, double x, double b, double c, double weight) { if (param == 0) return weight; if (param == 1) return weight*pow(x, c); if (param == 2) return weight*b*pow(x, c)*log(x); return 0; } double nsl_fit_model_exponentialn_param_deriv(int param, double x, double *p, double weight) { if (param % 2 == 0) return weight*exp(p[param+1]*x); else return weight*p[param-1]*x*exp(p[param]*x); } double nsl_fit_model_inverse_exponential_param_deriv(int param, double x, double a, double b, double weight) { if (param == 0) return weight*(1. - exp(b*x)); if (param == 1) return -weight*a*x*exp(b*x); if (param == 2) return weight; return 0; } double nsl_fit_model_fourier_param_deriv(int param, int degree, double x, double w, double weight) { if (param == 0) return weight*cos(degree*w*x); if (param == 1) return weight*sin(degree*w*x); return 0; } /* peak */ double nsl_fit_model_gaussian_param_deriv(int param, double x, double s, double mu, double A, double weight) { double s2 = s*s, norm = weight/sqrt(2.*M_PI)/s, efactor = exp(-(x-mu)*(x-mu)/(2.*s2)); if (param == 0) return A * norm/(s*s2) * ((x-mu)*(x-mu) - s2) * efactor; if (param == 1) return A * norm/s2 * (x-mu) * efactor; if (param == 2) return norm * efactor; return 0; } double nsl_fit_model_lorentz_param_deriv(int param, double x, double s, double t, double A, double weight) { double norm = weight/M_PI, denom = s*s+(x-t)*(x-t); if (param == 0) return A * norm * ((x-t)*(x-t) - s*s)/(denom*denom); if (param == 1) return A * norm * 2.*s*(x-t)/(denom*denom); if (param == 2) return norm * s/denom; return 0; } double nsl_fit_model_sech_param_deriv(int param, double x, double s, double mu, double A, double weight) { double y = (x-mu)/s, norm = weight/M_PI/s; if (param == 0) return A/s * norm * (y*tanh(y)-1.)/cosh(y); if (param == 1) return A/s * norm * tanh(y)/cosh(y); if (param == 2) return norm/cosh(y); return 0; } double nsl_fit_model_logistic_param_deriv(int param, double x, double s, double mu, double A, double weight) { double y = (x-mu)/2./s, norm = weight/4./s; if (param == 0) return A/s * norm * (2.*y*tanh(y)-1.)/cosh(y); if (param == 1) return A/s * norm * tanh(y)/cosh(y)/cosh(y); if (param == 2) return norm/cosh(y)/cosh(y); return 0; } /* growth */ double nsl_fit_model_atan_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight, y = (x-mu)/s; if (param == 0) return -A/s * norm * y/(1.+y*y); if (param == 1) return -A/s * norm * 1./(1+y*y); if (param == 2) return norm * atan(y); return 0; } double nsl_fit_model_tanh_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight, y = (x-mu)/s; if (param == 0) return -A/s * norm * y/cosh(y)/cosh(y); if (param == 1) return -A/s * norm * 1./cosh(y)/cosh(y); if (param == 2) return norm * tanh(y); return 0; } double nsl_fit_model_algebraic_sigmoid_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight, y = (x-mu)/s, y2 = y*y; if (param == 0) return -A/s * norm * y/pow(1.+y2, 1.5); if (param == 1) return -A/s * norm * 1./pow(1.+y2, 1.5); if (param == 2) return norm * y/sqrt(1.+y2); return 0; } double nsl_fit_model_sigmoid_param_deriv(int param, double x, double k, double mu, double A, double weight) { double norm = weight, y = k*(x-mu); if (param == 0) return A/k * norm * y*exp(-y)/gsl_pow_2(1. + exp(-y)); if (param == 1) return -A*k * norm * exp(-y)/gsl_pow_2(1. + exp(-y)); if (param == 2) return norm/(1. + exp(-y)); return 0; } double nsl_fit_model_erf_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight, y = (x-mu)/(sqrt(2.)*s); if (param == 0) return -A/sqrt(M_PI)/s * norm * y*exp(-y*y); if (param == 1) return -A/sqrt(2.*M_PI)/s * norm * exp(-y*y); if (param == 2) return norm/2. * gsl_sf_erf(y); return 0; } double nsl_fit_model_hill_param_deriv(int param, double x, double s, double n, double A, double weight) { double norm = weight, y = x/s; if (param == 0) return -A*n/s * norm * pow(y, n)/gsl_pow_2(1.+pow(y, n)); if (param == 1) return A * norm * log(y)*pow(y, n)/gsl_pow_2(1.+pow(y, n)); if (param == 2) return norm * pow(y, n)/(1.+pow(y, n)); return 0; } double nsl_fit_model_gompertz_param_deriv(int param, double x, double a, double b, double c, double weight) { if (param == 0) return weight*exp(-b*exp(-c*x)); if (param == 1) return -weight*a*exp(-c*x-b*exp(-c*x)); if (param == 2) return weight*a*b*x*exp(-c*x-b*exp(-c*x)); return 0; } double nsl_fit_model_gudermann_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight, y = (x-mu)/s; if (param == 0) return -A/s * norm * y/cosh(y); if (param == 1) return -A/s * norm * 1./cosh(y); if (param == 2) return -asin(tanh(y)); return 0; } /* distributions */ double nsl_fit_model_gaussian_tail_param_deriv(int param, double x, double s, double mu, double A, double a, double weight) { if (x < a) return 0; double s2 = s*s, N = erfc(a/s/M_SQRT2)/2., norm = weight/sqrt(2.*M_PI)/s/N, efactor = exp(-(x-mu)*(x-mu)/(2.*s2)); if (param == 0) return A * norm/(s*s2) * ((x-mu)*(x-mu) - s2) * efactor; if (param == 1) return A * norm/s2 * (x-mu) * efactor; if (param == 2) return norm * efactor; if (param == 3) return A/norm/norm * efactor * exp(-a*a/(2.*s2)); return 0; } double nsl_fit_model_exponential_param_deriv(int param, double x, double l, double mu, double A, double weight) { if (x < mu) return 0; double y = l*(x-mu), efactor = exp(-y); if (param == 0) return weight * A * (1. - y) * efactor; if (param == 1) return weight * A * gsl_pow_2(l) * efactor; if (param == 2) return weight * l * efactor; return 0; } double nsl_fit_model_laplace_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight/(2.*s), y = fabs((x-mu)/s), efactor = exp(-y); if (param == 0) return A/s*norm * (y-1.) * efactor; if (param == 1) return A/(s*s)*norm * (x-mu)/y * efactor; if (param == 2) return norm * efactor; return 0; } double nsl_fit_model_exp_pow_param_deriv(int param, double x, double s, double mu, double b, double a, double weight) { double norm = weight/2./s/gsl_sf_gamma(1.+1./b), y = (x-mu)/s, efactor = exp(-pow(fabs(y), b)); if (param == 0) return norm * a/s * efactor * (b * y * pow(fabs(1./y), 1.-b) * GSL_SIGN(y) - 1.); if (param == 1) return norm * a*b/s * efactor * pow(fabs(y), b-1.) * GSL_SIGN(y); if (param == 2) return norm * a/b * gsl_sf_gamma(1.+1./b)/gsl_sf_gamma(1./b) * efactor * (gsl_sf_psi(1.+1./b) - gsl_pow_2(b) * pow(fabs(y), b) * log(fabs(y))); if (param == 3) return norm * efactor; return 0; } double nsl_fit_model_maxwell_param_deriv(int param, double x, double a, double c, double weight) { double a2 = a*a, a3 = a*a2, norm = weight*sqrt(2./M_PI)/a3, x2 = x*x, efactor = exp(-x2/2./a2); if (param == 0) return c * norm * x2*(x2-3.*a2)/a3 * efactor; if (param == 1) return norm * x2 * efactor; return 0; } double nsl_fit_model_poisson_param_deriv(int param, double x, double l, double A, double weight) { double norm = weight*pow(l, x)/gsl_sf_gamma(x+1.); if (param == 0) return A/l * norm *(x-l)*exp(-l); if (param == 1) return norm * exp(-l); return 0; } double nsl_fit_model_lognormal_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight/sqrt(2.*M_PI)/(x*s), y = log(x)-mu, efactor = exp(-(y/s)*(y/s)/2.); if (param == 0) return A * norm * (y*y - s*s) * efactor; if (param == 1) return A * norm * y/(s*s) * efactor; if (param == 2) return norm * efactor; return 0; } double nsl_fit_model_gamma_param_deriv(int param, double x, double t, double k, double A, double weight) { double factor = weight*pow(x, k-1.)/pow(t, k)/gsl_sf_gamma(k), efactor = exp(-x/t); if (param == 0) return A * factor/t * (x/t-k) * efactor; if (param == 1) return A * factor * (log(x/t) - gsl_sf_psi(k)) * efactor; if (param == 2) return factor * efactor; return 0; } double nsl_fit_model_flat_param_deriv(int param, double x, double a, double b, double A, double weight) { if (x < a || x > b) return 0; if (param == 0) return weight * A/gsl_pow_2(a-b); if (param == 1) return - weight * A/gsl_pow_2(a-b); if (param == 2) return weight/(b-a); return 0; } double nsl_fit_model_rayleigh_param_deriv(int param, double x, double s, double A, double weight) { double y=x/s, norm = weight*y/s, efactor = exp(-y*y/2.); if (param == 0) return A*y/(s*s) * (y*y-2.)*efactor; if (param == 1) return norm * efactor; return 0; } double nsl_fit_model_rayleigh_tail_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight*x/(s*s), y = (mu*mu - x*x)/2./(s*s); if (param == 0) return -2. * A * norm/s * (1. + y) * exp(y); if (param == 1) return A * mu * norm/(s*s) * exp(y); if (param == 2) return norm * exp(y); return 0; } double nsl_fit_model_levy_param_deriv(int param, double x, double g, double mu, double A, double weight) { double y=x-mu, norm = weight*sqrt(g/(2.*M_PI))/pow(y, 1.5), efactor = exp(-g/2./y); if (param == 0) return A/2.*norm/g/y * (y - g) * efactor; if (param == 1) return A/2.*norm/y/y * (3.*y - g) * efactor; if (param == 2) return norm * efactor; return 0; } double nsl_fit_model_landau_param_deriv(int param, double x, double weight) { if (param == 0) return weight * gsl_ran_landau_pdf(x); return 0; } double nsl_fit_model_chi_square_param_deriv(int param, double x, double n, double A, double weight) { double y=n/2., norm = weight*pow(x, y-1.)/pow(2., y)/gsl_sf_gamma(y), efactor = exp(-x/2.); if (param == 0) return A/2. * norm * (log(x/2.) - gsl_sf_psi(y)) * efactor; if (param == 1) return norm * efactor; return 0; } double nsl_fit_model_students_t_param_deriv(int param, double x, double n, double A, double weight) { if (param == 0) return weight * A * gsl_sf_gamma((n+1.)/2.)/2./pow(n, 1.5)/sqrt(M_PI)/gsl_sf_gamma(n/2.) * pow(1.+x*x/n, - (n+3.)/2.) * (x*x - 1. - (n+x*x)*log(1.+x*x/n) + (n+x*x)*(gsl_sf_psi((n+1.)/2.) - gsl_sf_psi(n/2.)) ) ; if (param == 1) return weight * gsl_ran_tdist_pdf(x, n); return 0; } double nsl_fit_model_fdist_param_deriv(int param, double x, double n1, double n2, double A, double weight) { double norm = weight * gsl_sf_gamma((n1+n2)/2.)/gsl_sf_gamma(n1/2.)/gsl_sf_gamma(n2/2.) * pow(n1, n1/2.) * pow(n2, n2/2.) * pow(x, n1/2.-1.); double y = n2+n1*x; if (param == 0) return A/2. * norm * pow(y, -(n1+n2+2.)/2.) * (n2*(1.-x) + y*(log(n1) + log(x) - log(y) + gsl_sf_psi((n1+n2)/2.) - gsl_sf_psi(n1/2.))); if (param == 1) return A/2. * norm * pow(y, -(n1+n2+2.)/2.) * (n1*(x-1.) + y*(log(n2) - log(y) + gsl_sf_psi((n1+n2)/2.) - gsl_sf_psi(n2/2.))); if (param == 2) return weight * gsl_ran_fdist_pdf(x, n1, n2); return 0; } double nsl_fit_model_beta_param_deriv(int param, double x, double a, double b, double A, double weight) { double norm = weight * A * gsl_sf_gamma(a+b)/gsl_sf_gamma(a)/gsl_sf_gamma(b) * pow(x, a-1.) * pow(1.-x, b-1.); if (param == 0) return norm * (log(x) - gsl_sf_psi(a) + gsl_sf_psi(a+b)); if (param == 1) return norm * (log(1.-x) - gsl_sf_psi(b) + gsl_sf_psi(a+b)); if (param == 2) return weight * gsl_ran_beta_pdf(x, a, b); return 0; } double nsl_fit_model_pareto_param_deriv(int param, double x, double a, double b, double A, double weight) { if (x < b) return 0; double norm = weight * A; if (param == 0) return norm * pow(b/x, a) * (1. + a * log(b/x))/x; if (param == 1) return norm * a*a * pow(b/x, a-1.)/x/x; if (param == 2) return weight * gsl_ran_pareto_pdf(x, a, b); return 0; } double nsl_fit_model_weibull_param_deriv(int param, double x, double k, double l, double mu, double A, double weight) { double y = (x-mu)/l, z = pow(y, k), efactor = exp(-z); if (param == 0) return weight * A/l * z/y*(k*log(y)*(1.-z) + 1.) * efactor; if (param == 1) return weight * A*k*k/l/l * z/y*(z-1.) * efactor; if (param == 2) return weight * A*k/l/l * z/y/y*(k*z + 1. - k) * efactor; if (param == 3) return weight * k/l * z/y * efactor; return 0; } double nsl_fit_model_frechet_param_deriv(int param, double x, double g, double mu, double s, double A, double weight) { double y = (x-mu)/s, efactor = exp(-pow(y, -g)); if (param == 0) return weight * A/s * pow(y, -2.*g-1.) * (g*log(y)*(1.-pow(y, g))+pow(y, g)) * efactor; if (param == 1) return A * weight * g/(s*s)*pow(y, -g-2.) * (g+1.-g*pow(y, -g)) * efactor; if (param == 2) return A * weight * gsl_pow_2(g/s)*pow(y, -2.*g-1.) * (pow(y, g)-1.) * efactor; if (param == 3) return g * weight/s * pow(y, -g-1.) * efactor; return 0; } double nsl_fit_model_gumbel1_param_deriv(int param, double x, double s, double b, double mu, double A, double weight) { double norm = weight/s, y = (x-mu)/s, efactor = exp(-y - b*exp(-y)); if (param == 0) return A/s * norm * (y - 1. - b*exp(-y)) * efactor; if (param == 1) return -A * norm * exp(-y) * efactor; if (param == 2) return A/s * norm * (1. - b*exp(-y)) * efactor; if (param == 3) return norm * efactor; return 0; } double nsl_fit_model_gumbel2_param_deriv(int param, double x, double a, double b, double mu, double A, double weight) { double y = x - mu, norm = A * weight * exp(-b * pow(y, -a)); if (param == 0) return norm * b * pow(y, -1. -2.*a) * (pow(y, a) -a*(pow(y, a)-b)*log(y)); if (param == 1) return norm * a * pow(y, -1. -2.*a) * (pow(y, a) - b); if (param == 2) return norm * a * b * pow(y, -2.*(a + 1.)) * ((1. + a)*pow(y, a) - a*b); if (param == 3) return weight * gsl_ran_gumbel2_pdf(y, a, b); return 0; } double nsl_fit_model_binomial_param_deriv(int param, double k, double p, double n, double A, double weight) { if (k < 0 || k > n || n < 0 || p < 0 || p > 1.) return 0; k = round(k); n = round(n); double norm = weight * gsl_sf_fact(n)/gsl_sf_fact(n-k)/gsl_sf_fact(k); if (param == 0) return A * norm * pow(p, k-1.) * pow(1.-p, n-k-1.) * (k-n*p); if (param == 1) return A * norm * pow(p, k) * pow(1.-p, n-k) * (log(1.-p) + gsl_sf_psi(n+1.) - gsl_sf_psi(n-k+1.)); if (param == 2) return weight * gsl_ran_binomial_pdf(k, p, n); return 0; } double nsl_fit_model_negative_binomial_param_deriv(int param, double k, double p, double n, double A, double weight) { if (k < 0 || k > n || n < 0 || p < 0 || p > 1.) return 0; double norm = A * weight * gsl_sf_gamma(n+k)/gsl_sf_gamma(k+1.)/gsl_sf_gamma(n); if (param == 0) return - norm * pow(p, n-1.) * pow(1.-p, k-1.) * (n*(p-1.) + k*p); if (param == 1) return norm * pow(p, n) * pow(1.-p, k) * (log(p) - gsl_sf_psi(n) + gsl_sf_psi(n+k)); if (param == 2) return weight * gsl_ran_negative_binomial_pdf(k, p, n); return 0; } double nsl_fit_model_pascal_param_deriv(int param, double k, double p, double n, double A, double weight) { return nsl_fit_model_negative_binomial_param_deriv(param, k, p, round(n), A, weight); } double nsl_fit_model_geometric_param_deriv(int param, double k, double p, double A, double weight) { if (param == 0) return A * weight * pow(1.-p, k-2.) * (1.-k*p); if (param == 1) return weight * gsl_ran_geometric_pdf(k, p); return 0; } double nsl_fit_model_hypergeometric_param_deriv(int param, double k, double n1, double n2, double t, double A, double weight) { if (t > n1 + n2) return 0; double norm = weight * gsl_ran_hypergeometric_pdf(k, n1, n2, t); if (param == 0) return A * norm * (gsl_sf_psi(n1+1.) - gsl_sf_psi(n1-k+1.) - gsl_sf_psi(n1+n2+1.) + gsl_sf_psi(n1+n2-t+1.)); if (param == 1) return A * norm * (gsl_sf_psi(n2+1.) - gsl_sf_psi(n2+k-t+1.) - gsl_sf_psi(n1+n2+1.) + gsl_sf_psi(n1+n2-t+1.)); if (param == 2) return A * norm * (gsl_sf_psi(n2+k-t+1.) - gsl_sf_psi(n1+n2-t+1.) - gsl_sf_psi(t-k+1.) + gsl_sf_psi(t+1.)); if (param == 3) return norm; return 0; } double nsl_fit_model_logarithmic_param_deriv(int param, double k, double p, double A, double weight) { if (param == 0) return A * weight * pow(1.-p, k-2.) * (1.-k*p); if (param == 1) return weight * gsl_ran_logarithmic_pdf(k, p); return 0; } double nsl_fit_model_sech_dist_param_deriv(int param, double x, double s, double mu, double A, double weight) { double norm = weight/2./s, y = M_PI/2.*(x-mu)/s; if (param == 0) return -A/s * norm * (y*tanh(y)+1.)/cosh(y); if (param == 1) return A*M_PI/2./s * norm * tanh(y)/cosh(y); if (param == 2) return norm * 1./cosh(y); return 0; } diff --git a/src/backend/spreadsheet/SpreadsheetModel.cpp b/src/backend/spreadsheet/SpreadsheetModel.cpp index a0923f4b0..1abdff301 100644 --- a/src/backend/spreadsheet/SpreadsheetModel.cpp +++ b/src/backend/spreadsheet/SpreadsheetModel.cpp @@ -1,455 +1,470 @@ /*************************************************************************** File : SpreadsheetModel.cpp Project : LabPlot Description : Model for the access to a Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2009 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/spreadsheet/Spreadsheet.h" #include "backend/spreadsheet/SpreadsheetModel.h" +#include "backend/core/datatypes/Double2StringFilter.h" #include #include #include #include /*! \class SpreadsheetModel \brief Model for the access to a Spreadsheet This is a model in the sense of Qt4 model/view framework which is used to access a Spreadsheet object from any of Qt4s view classes, typically a QTableView. Its main purposes are translating Spreadsheet signals into QAbstractItemModel signals and translating calls to the QAbstractItemModel read/write API into calls in the public API of Spreadsheet. In many cases a pointer to the addressed column is obtained by calling Spreadsheet::column() and the manipulation is done using the public API of column. \ingroup backend */ SpreadsheetModel::SpreadsheetModel(Spreadsheet* spreadsheet) : QAbstractItemModel(0), m_spreadsheet(spreadsheet), m_formula_mode(false) { updateVerticalHeader(); updateHorizontalHeader(); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeAdded, this, &SpreadsheetModel::handleAspectAboutToBeAdded); connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetModel::handleAspectAdded); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved, this, &SpreadsheetModel::handleAspectAboutToBeRemoved); connect(m_spreadsheet, &Spreadsheet::aspectRemoved, this, &SpreadsheetModel::handleAspectRemoved); connect(m_spreadsheet, &Spreadsheet::aspectDescriptionChanged, this, &SpreadsheetModel::handleDescriptionChange); for (int i = 0; i < spreadsheet->columnCount(); ++i) { beginInsertColumns(QModelIndex(), i, i); handleAspectAdded(spreadsheet->column(i)); } } Qt::ItemFlags SpreadsheetModel::flags(const QModelIndex& index) const { if (index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; else return Qt::ItemIsEnabled; } QVariant SpreadsheetModel::data(const QModelIndex& index, int role) const { if( !index.isValid() ) return QVariant(); const int row = index.row(); const int col = index.column(); Column* col_ptr = m_spreadsheet->column(col); if(!col_ptr) return QVariant(); switch(role) { case Qt::ToolTipRole: if(col_ptr->isValid(row)) { if(col_ptr->isMasked(row)) return QVariant(i18n("%1, masked (ignored in all operations)").arg(col_ptr->asStringColumn()->textAt(row))); else return QVariant(col_ptr->asStringColumn()->textAt(row)); } else { if(col_ptr->isMasked(row)) return QVariant(i18n("invalid cell, masked (ignored in all operations)")); else return QVariant(i18n("invalid cell (ignored in all operations)")); } case Qt::EditRole: if(col_ptr->isValid(row)) return QVariant(col_ptr->asStringColumn()->textAt(row)); //m_formula_mode is not used at the moment //if(m_formula_mode) // return QVariant(col_ptr->formula(row)); break; case Qt::DisplayRole: if(!col_ptr->isValid(row)) return QVariant("-"); //m_formula_mode is not used at the moment //if(m_formula_mode) // return QVariant(col_ptr->formula(row)); return QVariant(col_ptr->asStringColumn()->textAt(row)); case Qt::ForegroundRole: if(!col_ptr->isValid(index.row())) return QVariant(QBrush(Qt::red)); break; case MaskingRole: return QVariant(col_ptr->isMasked(row)); case FormulaRole: return QVariant(col_ptr->formula(row)); // case Qt::DecorationRole: // if(m_formula_mode) // return QIcon(QPixmap(":/equals.png")); //TODO } return QVariant(); } QVariant SpreadsheetModel::headerData(int section, Qt::Orientation orientation, int role) const { if ( (orientation == Qt::Horizontal && section > m_spreadsheet->columnCount()-1) || (orientation == Qt::Vertical && section > m_spreadsheet->rowCount()-1) ) return QVariant(); switch(orientation) { case Qt::Horizontal: switch(role) { case Qt::DisplayRole: case Qt::ToolTipRole: case Qt::EditRole: return m_horizontal_header_data.at(section); case Qt::DecorationRole: return m_spreadsheet->child(section)->icon(); case SpreadsheetModel::CommentRole: return m_spreadsheet->child(section)->comment(); } break; case Qt::Vertical: switch(role) { case Qt::DisplayRole: case Qt::ToolTipRole: return m_vertical_header_data.at(section); } } return QVariant(); } int SpreadsheetModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_spreadsheet->rowCount(); } int SpreadsheetModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_spreadsheet->columnCount(); } bool SpreadsheetModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; int row = index.row(); Column* column = m_spreadsheet->column(index.column()); //don't do anything if no new value was provided if (column->columnMode() == AbstractColumn::Numeric) { bool ok; double new_value = value.toDouble(&ok); if (ok) { if (column->valueAt(row) == new_value ) return false; } else { //an empty (non-numeric value) was provided if (std::isnan(column->valueAt(row))) return false; } } else { if (column->asStringColumn()->textAt(row) == value.toString()) return false; } switch(role) { case Qt::EditRole: { // remark: the validity of the cell is determined by the input filter if (m_formula_mode) column->setFormula(row, value.toString()); else column->asStringColumn()->setTextAt(row, value.toString()); return true; } case MaskingRole: { m_spreadsheet->column(index.column())->setMasked(row, value.toBool()); return true; } case FormulaRole: { m_spreadsheet->column(index.column())->setFormula(row, value.toString()); return true; } } return false; } QModelIndex SpreadsheetModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent) return createIndex(row, column); } QModelIndex SpreadsheetModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex(); } bool SpreadsheetModel::hasChildren(const QModelIndex& parent) const { Q_UNUSED(parent) return false; } void SpreadsheetModel::handleAspectAboutToBeAdded(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* new_child) { const Column* col = qobject_cast(new_child); if (!col || parent != static_cast(m_spreadsheet)) return; //TODO: breaks undo/redo Q_UNUSED(before); // int index = before ? m_spreadsheet->indexOfChild(before) : 0; //beginInsertColumns(QModelIndex(), index, index); } void SpreadsheetModel::handleAspectAdded(const AbstractAspect * aspect) { const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; updateVerticalHeader(); updateHorizontalHeader(); connect(col, &Column::plotDesignationChanged, this, &SpreadsheetModel::handlePlotDesignationChange); - connect(col, SIGNAL(modeChanged(const AbstractColumn*)), this, - SLOT(handleDataChange(const AbstractColumn*))); + connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::dataChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleModeChange); connect(col, &Column::rowsInserted, this, &SpreadsheetModel::handleRowsInserted); connect(col, &Column::rowsRemoved, this, &SpreadsheetModel::handleRowsRemoved); connect(col, &Column::maskingChanged, this, &SpreadsheetModel::handleDataChange); + connect(col->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); beginResetModel(); //TODO: breaks undo/redo //endInsertColumns(); endResetModel(); m_spreadsheet->emitColumnCountChanged(); } void SpreadsheetModel::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; int index = m_spreadsheet->indexOfChild(col); beginRemoveColumns(QModelIndex(), index, index); disconnect(col, 0, this, 0); } void SpreadsheetModel::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(before) const Column* col = qobject_cast(child); if (!col || parent != static_cast(m_spreadsheet)) return; updateVerticalHeader(); updateHorizontalHeader(); beginResetModel(); endRemoveColumns(); endResetModel(); m_spreadsheet->emitColumnCountChanged(); } void SpreadsheetModel::handleDescriptionChange(const AbstractAspect* aspect) { const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); } void SpreadsheetModel::handleModeChange(const AbstractColumn* col) { updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); + handleDataChange(col); + + //output filter was changed after the mode change, update the signal-slot connection + disconnect(0, SIGNAL(digitsChanged()), this, SLOT(handledigitsChange())); + connect(dynamic_cast(col)->outputFilter(), SIGNAL(digitsChanged()), this, SLOT(handleDigitsChange())); +} + +void SpreadsheetModel::handleDigitsChange() { + const Double2StringFilter* filter = dynamic_cast(QObject::sender()); + if (!filter) + return; + + const AbstractColumn* col = filter->output(0); + handleDataChange(col); } void SpreadsheetModel::handlePlotDesignationChange(const AbstractColumn* col) { updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, m_spreadsheet->columnCount()-1); } void SpreadsheetModel::handleDataChange(const AbstractColumn* col) { int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); } void SpreadsheetModel::handleRowsInserted(const AbstractColumn* col, int before, int count) { Q_UNUSED(before) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::handleRowsRemoved(const AbstractColumn* col, int first, int count) { Q_UNUSED(first) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::updateVerticalHeader() { int old_rows = m_vertical_header_data.size(); int new_rows = m_spreadsheet->rowCount(); if (new_rows > old_rows) { beginInsertRows(QModelIndex(), old_rows, new_rows-1); for(int i=old_rows+1; i<=new_rows; i++) m_vertical_header_data << i; endInsertRows(); } else if (new_rows < old_rows) { beginRemoveRows(QModelIndex(), new_rows, old_rows-1); while (m_vertical_header_data.size() > new_rows) m_vertical_header_data.removeLast(); endRemoveRows(); } } void SpreadsheetModel::updateHorizontalHeader() { int column_count = m_spreadsheet->childCount(); while(m_horizontal_header_data.size() < column_count) m_horizontal_header_data << QString(); while(m_horizontal_header_data.size() > column_count) m_horizontal_header_data.removeLast(); for (int i = 0; i < column_count; i++) { Column* col = m_spreadsheet->child(i); QString type; switch(col->columnMode()) { case AbstractColumn::Numeric: type = QLatin1String(" {") + i18n("Numeric") + QLatin1Char('}'); break; case AbstractColumn::Integer: type = QLatin1String(" {") + i18n("Integer") + QLatin1Char('}'); break; case AbstractColumn::Text: type = QLatin1String(" {") + i18n("Text") + QLatin1Char('}'); break; case AbstractColumn::Month: type = QLatin1String(" {") + i18n("Month names") + QLatin1Char('}'); break; case AbstractColumn::Day: type = QLatin1String(" {") + i18n("Day names") + QLatin1Char('}'); break; case AbstractColumn::DateTime: type = QLatin1String(" {") + i18n("Date and time") + QLatin1Char('}'); break; } QString designation; switch(col->plotDesignation()) { case AbstractColumn::NoDesignation: break; case AbstractColumn::X: designation = QLatin1String(" [X]"); break; case AbstractColumn::Y: designation = QLatin1String(" [Y]"); break; case AbstractColumn::Z: designation = QLatin1String(" [Z]"); break; case AbstractColumn::XError: designation = QLatin1String(" [") + i18n("X-error") + QLatin1Char(']'); break; case AbstractColumn::XErrorPlus: designation = QLatin1String(" [") + i18n("X-error +") + QLatin1Char(']'); break; case AbstractColumn::XErrorMinus: designation = QLatin1String(" [") + i18n("X-error -") + QLatin1Char(']'); break; case AbstractColumn::YError: designation = QLatin1String(" [") + i18n("Y-error") + QLatin1Char(']'); break; case AbstractColumn::YErrorPlus: designation = QLatin1String(" [") + i18n("Y-error +") + QLatin1Char(']'); break; case AbstractColumn::YErrorMinus: designation = QLatin1String(" [") + i18n("Y-error -") + QLatin1Char(']'); break; } m_horizontal_header_data.replace(i, col->name() + type + designation); } } Column* SpreadsheetModel::column(int index) { return m_spreadsheet->column(index); } void SpreadsheetModel::activateFormulaMode(bool on) { if (m_formula_mode == on) return; m_formula_mode = on; int rows = m_spreadsheet->rowCount(); int cols = m_spreadsheet->columnCount(); if (rows > 0 && cols > 0) emit dataChanged(index(0,0), index(rows-1,cols-1)); } bool SpreadsheetModel::formulaModeActive() const { return m_formula_mode; } diff --git a/src/backend/spreadsheet/SpreadsheetModel.h b/src/backend/spreadsheet/SpreadsheetModel.h index 90bf548c2..97d3cce5a 100644 --- a/src/backend/spreadsheet/SpreadsheetModel.h +++ b/src/backend/spreadsheet/SpreadsheetModel.h @@ -1,94 +1,95 @@ /*************************************************************************** File : SpreadsheetModel.h Project : LabPlot Description : Model for the access to a Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2009 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2013-2016 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef SPREADSHEETMODEL_H #define SPREADSHEETMODEL_H #include #include class Column; class Spreadsheet; class AbstractAspect; class AbstractColumn; class SpreadsheetModel : public QAbstractItemModel { Q_OBJECT public: explicit SpreadsheetModel(Spreadsheet*); enum CustomDataRole { MaskingRole = Qt::UserRole, //!< bool determining whether the cell is masked FormulaRole = Qt::UserRole+1, //!< the cells formula CommentRole = Qt::UserRole+2, //!< the column comment (for headerData()) }; Qt::ItemFlags flags( const QModelIndex & index ) const; QVariant data(const QModelIndex& index, int role) const; QVariant headerData(int section, Qt::Orientation orientation,int role) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; bool setData(const QModelIndex& index, const QVariant& value, int role); QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; QModelIndex parent(const QModelIndex& child) const; bool hasChildren (const QModelIndex& parent = QModelIndex() ) const; Column* column(int index); void activateFormulaMode(bool on); bool formulaModeActive() const; private slots: void handleAspectAboutToBeAdded(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); void handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void handleDescriptionChange(const AbstractAspect*); void handleModeChange(const AbstractColumn*); + void handleDigitsChange(); void handlePlotDesignationChange(const AbstractColumn*); void handleDataChange(const AbstractColumn*); void handleRowsInserted(const AbstractColumn* col, int before, int count); void handleRowsRemoved(const AbstractColumn* col, int first, int count); protected: void updateVerticalHeader(); void updateHorizontalHeader(); private: Spreadsheet* m_spreadsheet; bool m_formula_mode; QList m_vertical_header_data; QStringList m_horizontal_header_data; int m_defaultHeaderHeight; }; #endif diff --git a/src/backend/worksheet/plots/cartesian/Axis.cpp b/src/backend/worksheet/plots/cartesian/Axis.cpp index 193299d1f..63f2241f8 100644 --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -1,2333 +1,2331 @@ /*************************************************************************** File : Axis.cpp Project : LabPlot Description : Axis for cartesian coordinate systems. -------------------------------------------------------------------- Copyright : (C) 2011-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/AxisPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/core/AbstractColumn.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" // #include "backend/lib/trace.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include -#include - /** * \class AxisGrid * \brief Helper class to get the axis grid drawn with the z-Value=0. * * The painting of the grid lines is separated from the painting of the axis itself. * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects ) * and for the axis (z=FLT_MAX, drawn on top of all other objects) * * \ingroup worksheet */ class AxisGrid : public QGraphicsItem { public: AxisGrid(AxisPrivate* a) { axis = a; setFlag(QGraphicsItem::ItemIsSelectable, false); setFlag(QGraphicsItem::ItemIsFocusable, false); setAcceptHoverEvents(false); } QRectF boundingRect() const { QPainterPath gridShape; gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridPen)); gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridPen)); QRectF boundingRectangle = gridShape.boundingRect(); return boundingRectangle; } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!axis->isVisible()) return; if (axis->linePath.isEmpty()) return; //draw major grid if (axis->majorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->majorGridOpacity); painter->setPen(axis->majorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->majorGridPath); } //draw minor grid if (axis->minorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->minorGridOpacity); painter->setPen(axis->minorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->minorGridPath); } } private: AxisPrivate* axis; }; /** * \class Axis * \brief Axis for cartesian coordinate systems. * * \ingroup worksheet */ Axis::Axis(const QString& name, CartesianPlot* plot, const AxisOrientation& orientation) : WorksheetElement(name), d_ptr(new AxisPrivate(this, plot)) { d_ptr->orientation = orientation; init(); } Axis::Axis(const QString& name, const AxisOrientation& orientation, AxisPrivate* dd) : WorksheetElement(name), d_ptr(dd) { d_ptr->orientation = orientation; init(); } void Axis::init() { Q_D(Axis); KConfig config; KConfigGroup group = config.group( "Axis" ); d->autoScale = true; d->position = Axis::AxisCustom; d->offset = group.readEntry("PositionOffset", 0); d->scale = (Axis::AxisScale) group.readEntry("Scale", (int) Axis::ScaleLinear); d->autoScale = group.readEntry("AutoScale", true); d->start = group.readEntry("Start", 0); d->end = group.readEntry("End", 10); d->zeroOffset = group.readEntry("ZeroOffset", 0); d->scalingFactor = group.readEntry("ScalingFactor", 1.0); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int) Qt::SolidLine) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->arrowType = (Axis::ArrowType) group.readEntry("ArrowType", (int)Axis::NoArrow); d->arrowPosition = (Axis::ArrowPosition) group.readEntry("ArrowPosition", (int)Axis::ArrowRight); d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Point)); // axis title d->title = new TextLabel(this->name(), TextLabel::AxisTitle); connect( d->title, &TextLabel::changed, this, &Axis::labelChanged); addChild(d->title); d->title->setHidden(true); d->title->graphicsItem()->setParentItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); d->title->graphicsItem()->setAcceptHoverEvents(false); d->title->setText(this->name()); if (d->orientation == AxisVertical) d->title->setRotationAngle(90); d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut); d->majorTicksType = (Axis::TicksType) group.readEntry("MajorTicksType", (int) Axis::TicksTotalNumber); d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11); d->majorTicksIncrement = group.readEntry("MajorTicksIncrement", 1.0); d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) ); d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) ); d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Point)); d->majorTicksOpacity = group.readEntry("MajorTicksOpacity", 1.0); d->minorTicksDirection = (Axis::TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut); d->minorTicksType = (Axis::TicksType) group.readEntry("MinorTicksType", (int) Axis::TicksTotalNumber); d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1); d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", 0.5); d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) ); d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) ); d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Point)); d->minorTicksOpacity = group.readEntry("MinorTicksOpacity", 1.0); //Labels d->labelsFormat = (Axis::LabelsFormat) group.readEntry("LabelsFormat", (int)Axis::FormatDecimal); d->labelsAutoPrecision = group.readEntry("LabelsAutoPrecision", true); d->labelsPrecision = group.readEntry("LabelsPrecision", 1); d->labelsPosition = (Axis::LabelsPosition) group.readEntry("LabelsPosition", (int) Axis::LabelsOut); d->labelsOffset= group.readEntry("LabelsOffset", Worksheet::convertToSceneUnits( 5.0, Worksheet::Point )); d->labelsRotationAngle = group.readEntry("LabelsRotation", 0); d->labelsFont = group.readEntry("LabelsFont", QFont()); d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Point ) ); d->labelsColor = group.readEntry("LabelsFontColor", QColor(Qt::black)); d->labelsPrefix = group.readEntry("LabelsPrefix", "" ); d->labelsSuffix = group.readEntry("LabelsSuffix", "" ); d->labelsOpacity = group.readEntry("LabelsOpacity", 1.0); //major grid d->majorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MajorGridStyle", (int) Qt::NoPen) ); d->majorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->majorGridOpacity = group.readEntry("MajorGridOpacity", 1.0); //minor grid d->minorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MinorGridStyle", (int) Qt::NoPen) ); d->minorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->minorGridOpacity = group.readEntry("MinorGridOpacity", 1.0); this->initActions(); this->initMenus(); } /*! * For the most frequently edited properties, create Actions and ActionGroups for the context menu. * For some ActionGroups the actual actions are created in \c GuiTool, */ void Axis::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Axis::visibilityChanged); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot); orientationHorizontalAction = new QAction(i18n("horizontal"), orientationActionGroup); orientationHorizontalAction->setCheckable(true); orientationVerticalAction = new QAction(i18n("vertical"), orientationActionGroup); orientationVerticalAction->setCheckable(true); //Line lineStyleActionGroup = new QActionGroup(this); lineStyleActionGroup->setExclusive(true); connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged); //Ticks //TODO } void Axis::initMenus() { //Orientation orientationMenu = new QMenu(i18n("Orientation")); orientationMenu->addAction(orientationHorizontalAction); orientationMenu->addAction(orientationVerticalAction); //Line lineMenu = new QMenu(i18n("Line")); lineStyleMenu = new QMenu(i18n("style"), lineMenu); lineMenu->addMenu( lineStyleMenu ); lineColorMenu = new QMenu(i18n("color"), lineMenu); GuiTools::fillColorMenu( lineColorMenu, lineColorActionGroup ); lineMenu->addMenu( lineColorMenu ); } QMenu* Axis::createContextMenu() { Q_D(const Axis); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //Orientation if ( d->orientation == AxisHorizontal ) orientationHorizontalAction->setChecked(true); else orientationVerticalAction->setChecked(true); menu->insertMenu(firstAction, orientationMenu); //Line styles GuiTools::updatePenStyles( lineStyleMenu, lineStyleActionGroup, d->linePen.color() ); GuiTools::selectPenStyleAction(lineStyleActionGroup, d->linePen.style() ); GuiTools::selectColorAction(lineColorActionGroup, d->linePen.color() ); menu->insertMenu(firstAction, lineMenu); menu->insertSeparator(firstAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Axis::icon() const{ Q_D(const Axis); QIcon ico; if (d->orientation == Axis::AxisHorizontal) ico = QIcon::fromTheme("labplot-axis-horizontal"); else ico = QIcon::fromTheme("labplot-axis-vertical"); return ico; } Axis::~Axis() { if (orientationMenu) delete orientationMenu; if (lineMenu) delete lineMenu; //no need to delete d->title, since it was added with addChild in init(); //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem *Axis::graphicsItem() const { return d_ptr; } /*! - * overrides the implementation in WorkhseetElement and sets the z-value to the maximal possible, + * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible, * axes are drawn on top of all other object in the plot. */ void Axis::setZValue(qreal) { Q_D(Axis); - d->setZValue(FLT_MAX); + d->setZValue(std::numeric_limits::max()); //TODO: Why float? d->gridItem->setParentItem(d->parentItem()); d->gridItem->setZValue(0); } void Axis::retransform() { Q_D(Axis); d->retransform(); } void Axis::setSuppressRetransform(bool value) { Q_D(Axis); d->suppressRetransform = value; } void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("Axis::handleResize()"); Q_D(Axis); Q_UNUSED(pageResize); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); QPen pen = d->linePen; pen.setWidthF(pen.widthF() * ratio); d->linePen = pen; d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant d->minorTicksLength *= ratio; d->labelsFont.setPixelSize( d->labelsFont.pixelSize() * ratio ); //TODO: take into account rotated labels d->labelsOffset *= ratio; d->title->handleResize(horizontalRatio, verticalRatio, pageResize); } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(Axis, bool, autoScale, autoScale) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisOrientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisPosition, position, position) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisScale, scale, scale) BASIC_SHARED_D_READER_IMPL(Axis, float, offset, offset) BASIC_SHARED_D_READER_IMPL(Axis, float, start, start) BASIC_SHARED_D_READER_IMPL(Axis, float, end, end) BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor) BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset) BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title) BASIC_SHARED_D_READER_IMPL(Axis, float, titleOffsetX, titleOffsetX) BASIC_SHARED_D_READER_IMPL(Axis, float, titleOffsetY, titleOffsetY) CLASS_SHARED_D_READER_IMPL(Axis, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, lineOpacity, lineOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition) BASIC_SHARED_D_READER_IMPL(Axis, float, arrowSize, arrowSize) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksIncrement, majorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn) QString& Axis::majorTicksColumnPath() const { return d_ptr->majorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorTicksPen, majorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksOpacity, majorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksIncrement, minorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn) QString& Axis::minorTicksColumnPath() const { return d_ptr->minorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorTicksPen, minorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksOpacity, minorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat); BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision); BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision); BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition); BASIC_SHARED_D_READER_IMPL(Axis, float, labelsOffset, labelsOffset); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle); CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor); CLASS_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity); CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorGridPen, majorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorGridOpacity, majorGridOpacity) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorGridPen, minorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorGridOpacity, minorGridOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(Axis, SetAutoScale, bool, autoScale, retransform); void Axis::setAutoScale(bool autoScale) { Q_D(Axis); if (autoScale != d->autoScale) { exec(new AxisSetAutoScaleCmd(d, autoScale, i18n("%1: set axis auto scaling"))); if (autoScale) { CartesianPlot *plot = qobject_cast(parentAspect()); if (!plot) return; if (d->orientation == Axis::AxisHorizontal) { d->end = plot->xMax(); d->start = plot->xMin(); } else { d->end = plot->yMax(); d->start = plot->yMin(); } retransform(); emit endChanged(d->end); emit startChanged(d->start); } } } STD_SWAP_METHOD_SETTER_CMD_IMPL(Axis, SetVisible, bool, swapVisible); void Axis::setVisible(bool on) { Q_D(Axis); exec(new AxisSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool Axis::isVisible() const { Q_D(const Axis); return d->isVisible(); } void Axis::setPrinting(bool on) { Q_D(Axis); d->setPrinting(on); } STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::AxisOrientation, orientation, retransform); void Axis::setOrientation( AxisOrientation orientation) { Q_D(Axis); if (orientation != d->orientation) exec(new AxisSetOrientationCmd(d, orientation, i18n("%1: set axis orientation"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::AxisPosition, position, retransform); void Axis::setPosition(AxisPosition position) { Q_D(Axis); if (position != d->position) exec(new AxisSetPositionCmd(d, position, i18n("%1: set axis position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScaling, Axis::AxisScale, scale, retransformTicks); void Axis::setScale(AxisScale scale) { Q_D(Axis); if (scale != d->scale) exec(new AxisSetScalingCmd(d, scale, i18n("%1: set axis scale"))); } STD_SETTER_CMD_IMPL_F(Axis, SetOffset, float, offset, retransform); void Axis::setOffset(float offset, bool undo) { Q_D(Axis); if (offset != d->offset) { if (undo) { exec(new AxisSetOffsetCmd(d, offset, i18n("%1: set axis offset"))); } else { d->offset = offset; //don't need to call retransform() afterward //since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway. } emit positionChanged(offset); } } STD_SETTER_CMD_IMPL_F_S(Axis, SetStart, float, start, retransform); void Axis::setStart(float start) { Q_D(Axis); if (start != d->start) exec(new AxisSetStartCmd(d, start, i18n("%1: set axis start"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, float, end, retransform); void Axis::setEnd(float end) { Q_D(Axis); if (end != d->end) exec(new AxisSetEndCmd(d, end, i18n("%1: set axis end"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform); void Axis::setZeroOffset(qreal zeroOffset) { Q_D(Axis); if (zeroOffset != d->zeroOffset) exec(new AxisSetZeroOffsetCmd(d, zeroOffset, i18n("%1: set axis zero offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform); void Axis::setScalingFactor(qreal scalingFactor) { Q_D(Axis); if (scalingFactor != d->scalingFactor) exec(new AxisSetScalingFactorCmd(d, scalingFactor, i18n("%1: set axis scaling factor"))); } //Title STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, float, titleOffsetX, retransform); void Axis::setTitleOffsetX(float offset) { Q_D(Axis); if (offset != d->titleOffsetX) exec(new AxisSetTitleOffsetXCmd(d, offset, i18n("%1: set title offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, float, titleOffsetY, retransform); void Axis::setTitleOffsetY(float offset) { Q_D(Axis); if (offset != d->titleOffsetY) exec(new AxisSetTitleOffsetYCmd(d, offset, i18n("%1: set title offset"))); } //Line STD_SETTER_CMD_IMPL_F_S(Axis, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect); void Axis::setLinePen(const QPen &pen) { Q_D(Axis); if (pen != d->linePen) exec(new AxisSetLinePenCmd(d, pen, i18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLineOpacity, qreal, lineOpacity, update); void Axis::setLineOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->lineOpacity) exec(new AxisSetLineOpacityCmd(d, opacity, i18n("%1: set line opacity"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow); void Axis::setArrowType(ArrowType type) { Q_D(Axis); if (type != d->arrowType) exec(new AxisSetArrowTypeCmd(d, type, i18n("%1: set arrow type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow); void Axis::setArrowPosition(ArrowPosition position) { Q_D(Axis); if (position != d->arrowPosition) exec(new AxisSetArrowPositionCmd(d, position, i18n("%1: set arrow position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, float, arrowSize, retransformArrow); void Axis::setArrowSize(float arrowSize) { Q_D(Axis); if (arrowSize != d->arrowSize) exec(new AxisSetArrowSizeCmd(d, arrowSize, i18n("%1: set arrow size"))); } //Major ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks); void Axis::setMajorTicksDirection(const TicksDirection majorTicksDirection) { Q_D(Axis); if (majorTicksDirection != d->majorTicksDirection) exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, i18n("%1: set major ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks); void Axis::setMajorTicksType(const TicksType majorTicksType) { Q_D(Axis); if (majorTicksType!= d->majorTicksType) exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, i18n("%1: set major ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks); void Axis::setMajorTicksNumber(int majorTicksNumber) { Q_D(Axis); if (majorTicksNumber != d->majorTicksNumber) exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, i18n("%1: set the total number of the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksIncrement, qreal, majorTicksIncrement, retransformTicks); void Axis::setMajorTicksIncrement(qreal majorTicksIncrement) { Q_D(Axis); if (majorTicksIncrement != d->majorTicksIncrement) exec(new AxisSetMajorTicksIncrementCmd(d, majorTicksIncrement, i18n("%1: set the increment for the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks) void Axis::setMajorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->majorTicksColumn) { exec(new AxisSetMajorTicksColumnCmd(d, column, i18n("%1: assign major ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::majorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksPen, QPen, majorTicksPen, recalcShapeAndBoundingRect); void Axis::setMajorTicksPen(const QPen &pen) { Q_D(Axis); if (pen != d->majorTicksPen) exec(new AxisSetMajorTicksPenCmd(d, pen, i18n("%1: set major ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks); void Axis::setMajorTicksLength(qreal majorTicksLength) { Q_D(Axis); if (majorTicksLength != d->majorTicksLength) exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, i18n("%1: set major ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksOpacity, qreal, majorTicksOpacity, update); void Axis::setMajorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorTicksOpacity) exec(new AxisSetMajorTicksOpacityCmd(d, opacity, i18n("%1: set major ticks opacity"))); } //Minor ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks); void Axis::setMinorTicksDirection(const TicksDirection minorTicksDirection) { Q_D(Axis); if (minorTicksDirection != d->minorTicksDirection) exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, i18n("%1: set minor ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks); void Axis::setMinorTicksType(const TicksType minorTicksType) { Q_D(Axis); if (minorTicksType!= d->minorTicksType) exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, i18n("%1: set minor ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks); void Axis::setMinorTicksNumber(int minorTicksNumber) { Q_D(Axis); if (minorTicksNumber != d->minorTicksNumber) exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, i18n("%1: set the total number of the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksIncrement, qreal, minorTicksIncrement, retransformTicks); void Axis::setMinorTicksIncrement(qreal minorTicksIncrement) { Q_D(Axis); if (minorTicksIncrement != d->minorTicksIncrement) exec(new AxisSetMinorTicksIncrementCmd(d, minorTicksIncrement, i18n("%1: set the increment for the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks) void Axis::setMinorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->minorTicksColumn) { exec(new AxisSetMinorTicksColumnCmd(d, column, i18n("%1: assign minor ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::minorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksPen, QPen, minorTicksPen, recalcShapeAndBoundingRect); void Axis::setMinorTicksPen(const QPen &pen) { Q_D(Axis); if (pen != d->minorTicksPen) exec(new AxisSetMinorTicksPenCmd(d, pen, i18n("%1: set minor ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks); void Axis::setMinorTicksLength(qreal minorTicksLength) { Q_D(Axis); if (minorTicksLength != d->minorTicksLength) exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, i18n("%1: set minor ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksOpacity, qreal, minorTicksOpacity, update); void Axis::setMinorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorTicksOpacity) exec(new AxisSetMinorTicksOpacityCmd(d, opacity, i18n("%1: set minor ticks opacity"))); } //Labels STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks); void Axis::setLabelsFormat(const LabelsFormat labelsFormat) { Q_D(Axis); if (labelsFormat != d->labelsFormat) exec(new AxisSetLabelsFormatCmd(d, labelsFormat, i18n("%1: set labels format"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings); void Axis::setLabelsAutoPrecision(const bool labelsAutoPrecision) { Q_D(Axis); if (labelsAutoPrecision != d->labelsAutoPrecision) exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, i18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings); void Axis::setLabelsPrecision(const int labelsPrecision) { Q_D(Axis); if (labelsPrecision != d->labelsPrecision) exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, i18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions); void Axis::setLabelsPosition(const LabelsPosition labelsPosition) { Q_D(Axis); if (labelsPosition != d->labelsPosition) exec(new AxisSetLabelsPositionCmd(d, labelsPosition, i18n("%1: set labels position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, float, labelsOffset, retransformTickLabelPositions); void Axis::setLabelsOffset(float offset) { Q_D(Axis); if (offset != d->labelsOffset) exec(new AxisSetLabelsOffsetCmd(d, offset, i18n("%1: set label offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, recalcShapeAndBoundingRect); void Axis::setLabelsRotationAngle(qreal angle) { Q_D(Axis); if (angle != d->labelsRotationAngle) exec(new AxisSetLabelsRotationAngleCmd(d, angle, i18n("%1: set label rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update); void Axis::setLabelsColor(const QColor &color) { Q_D(Axis); if (color != d->labelsColor) exec(new AxisSetLabelsColorCmd(d, color, i18n("%1: set label color"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings); void Axis::setLabelsFont(const QFont &font) { Q_D(Axis); if (font != d->labelsFont) exec(new AxisSetLabelsFontCmd(d, font, i18n("%1: set label font"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings); void Axis::setLabelsPrefix(const QString& prefix) { Q_D(Axis); if (prefix != d->labelsPrefix) exec(new AxisSetLabelsPrefixCmd(d, prefix, i18n("%1: set label prefix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings); void Axis::setLabelsSuffix(const QString& suffix) { Q_D(Axis); if (suffix != d->labelsSuffix) exec(new AxisSetLabelsSuffixCmd(d, suffix, i18n("%1: set label suffix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update); void Axis::setLabelsOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->labelsOpacity) exec(new AxisSetLabelsOpacityCmd(d, opacity, i18n("%1: set labels opacity"))); } //Major grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridPen, QPen, majorGridPen, retransformMajorGrid); void Axis::setMajorGridPen(const QPen &pen) { Q_D(Axis); if (pen != d->majorGridPen) exec(new AxisSetMajorGridPenCmd(d, pen, i18n("%1: set major grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, update); void Axis::setMajorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorGridOpacity) exec(new AxisSetMajorGridOpacityCmd(d, opacity, i18n("%1: set major grid opacity"))); } //Minor grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridPen, QPen, minorGridPen, retransformMinorGrid); void Axis::setMinorGridPen(const QPen &pen) { Q_D(Axis); if (pen != d->minorGridPen) exec(new AxisSetMinorGridPenCmd(d, pen, i18n("%1: set minor grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, update); void Axis::setMinorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorGridOpacity) exec(new AxisSetMinorGridOpacityCmd(d, opacity, i18n("%1: set minor grid opacity"))); } //############################################################################## //#################################### SLOTs ################################ //############################################################################## void Axis::labelChanged() { Q_D(Axis); d->recalcShapeAndBoundingRect(); } void Axis::retransformTicks() { Q_D(Axis); d->retransformTicks(); } void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->majorTicksColumn) { d->majorTicksColumn = 0; d->retransformTicks(); } } void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect==d->minorTicksColumn) { d->minorTicksColumn = 0; d->retransformTicks(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Axis::orientationChangedSlot(QAction* action) { if (action == orientationHorizontalAction) this->setOrientation(AxisHorizontal); else this->setOrientation(AxisVertical); } void Axis::lineStyleChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); this->setLinePen(pen); } void Axis::lineColorChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); this->setLinePen(pen); } void Axis::visibilityChangedSlot() { Q_D(const Axis); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### AxisPrivate::AxisPrivate(Axis* owner, CartesianPlot* plot) : majorTicksColumn(0), minorTicksColumn(0), gridItem(new AxisGrid(this)), q(owner), suppressRetransform(false), m_plot(plot), m_cSystem(dynamic_cast(plot->coordinateSystem())), m_hovered(false), m_suppressRecalc(false), m_printing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setAcceptHoverEvents(true); } QString AxisPrivate::name() const{ return q->name(); } bool AxisPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } QRectF AxisPrivate::boundingRect() const{ return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath AxisPrivate::shape() const{ return axisShape; } /*! recalculates the position of the axis on the worksheet */ void AxisPrivate::retransform() { if (suppressRetransform) return; // PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()"); m_suppressRecalc = true; retransformLine(); m_suppressRecalc = false; recalcShapeAndBoundingRect(); } void AxisPrivate::retransformLine() { if (suppressRetransform) return; linePath = QPainterPath(); lines.clear(); QPointF startPoint; QPointF endPoint; if (orientation == Axis::AxisHorizontal) { if (position == Axis::AxisTop) offset = m_plot->yMax(); else if (position == Axis::AxisBottom) offset = m_plot->yMin(); else if (position == Axis::AxisCentered) offset = m_plot->yMin() + (m_plot->yMax()-m_plot->yMin())/2; startPoint.setX(start); startPoint.setY(offset); endPoint.setX(end); endPoint.setY(offset); } else { // vertical if (position == Axis::AxisLeft) offset = m_plot->xMin(); else if (position == Axis::AxisRight) offset = m_plot->xMax(); else if (position == Axis::AxisCentered) offset = m_plot->xMin() + (m_plot->xMax()-m_plot->xMin())/2; startPoint.setX(offset); startPoint.setY(start); endPoint.setY(end); endPoint.setX(offset); } lines.append(QLineF(startPoint, endPoint)); lines = m_cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MarkGaps); foreach (const QLineF& line, lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } if (linePath.isEmpty()) { recalcShapeAndBoundingRect(); return; } else { retransformArrow(); retransformTicks(); } } void AxisPrivate::retransformArrow() { if (suppressRetransform) return; arrowPath = QPainterPath(); if (arrowType == Axis::NoArrow || lines.isEmpty()) { recalcShapeAndBoundingRect(); return; } if (arrowPosition == Axis::ArrowRight || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(lines.size()-1).p2(); this->addArrow(endPoint, 1); } if (arrowPosition == Axis::ArrowLeft || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(0).p1(); this->addArrow(endPoint, -1); } recalcShapeAndBoundingRect(); } void AxisPrivate::addArrow(const QPointF& startPoint, int direction) { static const float cos_phi = cos(3.14159/6); if (orientation == Axis::AxisHorizontal) { QPointF endPoint = QPointF(startPoint.x() + direction*arrowSize, startPoint.y()); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/8, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; } } else { //vertical orientation QPointF endPoint = QPointF(startPoint.x(), startPoint.y()-direction*arrowSize); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/8)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; } } } //! helper function for retransformTicks() bool AxisPrivate::transformAnchor(QPointF* anchorPoint) { QVector points; points.append(*anchorPoint); points = m_cSystem->mapLogicalToScene(points); if (points.count() != 1) { // point is not mappable or in a coordinate gap return false; } else { *anchorPoint = points.at(0); return true; } } /*! recalculates the position of the axis ticks. */ void AxisPrivate::retransformTicks() { if (suppressRetransform) return; //TODO: check that start and end are > 0 for log and >=0 for sqrt, etc. majorTicksPath = QPainterPath(); minorTicksPath = QPainterPath(); majorTickPoints.clear(); minorTickPoints.clear(); tickLabelValues.clear(); if ( majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks) ) { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } //determine the spacing for the major ticks double majorTicksSpacing = 0; int tmpMajorTicksNumber = 0; if (majorTicksType == Axis::TicksTotalNumber) { //the total number of the major ticks is given - > determine the spacing tmpMajorTicksNumber = majorTicksNumber; switch (scale) { case Axis::ScaleLinear: majorTicksSpacing = (end-start)/(majorTicksNumber-1); break; case Axis::ScaleLog10: majorTicksSpacing = (log10(end)-log10(start))/(majorTicksNumber-1); break; case Axis::ScaleLog2: majorTicksSpacing = (log(end)-log(start))/log(2)/(majorTicksNumber-1); break; case Axis::ScaleLn: majorTicksSpacing = (log(end)-log(start))/(majorTicksNumber-1); break; case Axis::ScaleSqrt: majorTicksSpacing = (sqrt(end)-sqrt(start))/(majorTicksNumber-1); break; case Axis::ScaleX2: majorTicksSpacing = (pow(end,2)-pow(start,2))/(majorTicksNumber-1); } } else if (majorTicksType == Axis::TicksIncrement) { //the spacing (increment) of the major ticks is given - > determine the number majorTicksSpacing = majorTicksIncrement; switch (scale) { case Axis::ScaleLinear: tmpMajorTicksNumber = qRound((end-start)/majorTicksSpacing + 1); break; case Axis::ScaleLog10: tmpMajorTicksNumber = qRound((log10(end)-log10(start))/majorTicksSpacing + 1); break; case Axis::ScaleLog2: tmpMajorTicksNumber = qRound((log(end)-log(start))/log(2)/majorTicksSpacing + 1); break; case Axis::ScaleLn: tmpMajorTicksNumber = qRound((log(end)-log(start))/majorTicksSpacing + 1); break; case Axis::ScaleSqrt: tmpMajorTicksNumber = qRound((sqrt(end)-sqrt(start))/majorTicksSpacing + 1); break; case Axis::ScaleX2: tmpMajorTicksNumber = qRound((pow(end,2)-pow(start,2))/majorTicksSpacing + 1); } } else { //custom column was provided if (majorTicksColumn) { tmpMajorTicksNumber = majorTicksColumn->rowCount(); } else { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } } int tmpMinorTicksNumber; if (minorTicksType == Axis::TicksTotalNumber) tmpMinorTicksNumber = minorTicksNumber; else if (minorTicksType == Axis::TicksIncrement) tmpMinorTicksNumber = (end - start)/ (majorTicksNumber - 1)/minorTicksIncrement - 1; else (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0; QPointF anchorPoint; QPointF startPoint; QPointF endPoint; qreal majorTickPos=0.0; qreal minorTickPos; qreal nextMajorTickPos = 0.0; int xDirection = m_cSystem->xDirection(); int yDirection = m_cSystem->yDirection(); float middleX = m_plot->xMin() + (m_plot->xMax() - m_plot->xMin())/2; float middleY = m_plot->yMin() + (m_plot->yMax() - m_plot->yMin())/2; bool valid; for (int iMajor = 0; iMajor < tmpMajorTicksNumber; iMajor++) { //calculate major tick's position if (majorTicksType != Axis::TicksCustomColumn) { switch (scale) { case Axis::ScaleLinear: majorTickPos = start + majorTicksSpacing*iMajor; nextMajorTickPos = start + majorTicksSpacing*(iMajor+1); break; case Axis::ScaleLog10: majorTickPos = pow(10, log10(start) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(10, log10(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLog2: majorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLn: majorTickPos = exp(log(start) + majorTicksSpacing*iMajor); nextMajorTickPos = exp(log(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleSqrt: majorTickPos = pow(sqrt(start) + majorTicksSpacing*iMajor, 2); nextMajorTickPos = pow(sqrt(start) + majorTicksSpacing*(iMajor+1), 2); break; case Axis::ScaleX2: majorTickPos = sqrt(sqrt(start) + majorTicksSpacing*iMajor); nextMajorTickPos = sqrt(sqrt(start) + majorTicksSpacing*(iMajor+1)); break; } } else { majorTickPos = majorTicksColumn->valueAt(iMajor); if (std::isnan(majorTickPos)) break; //stop iterating after the first non numerical value in the column } //calculate start and end points for major tick's line if (majorTicksDirection != Axis::noTicks ) { if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(majorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } } } else { // vertical anchorPoint.setY(majorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } } } //add major tick's line to the painter path if (valid) { majorTicksPath.moveTo(startPoint); majorTicksPath.lineTo(endPoint); majorTickPoints << anchorPoint; tickLabelValues<< scalingFactor*majorTickPos+zeroOffset; } } //minor ticks if ((Axis::noTicks != minorTicksDirection) && (tmpMajorTicksNumber > 1) && (tmpMinorTicksNumber > 0) && (iMajorvalueAt(iMinor); if (std::isnan(minorTickPos)) break; //stop iterating after the first non numerical value in the column //in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis. //execute the minor ticks loop only once. if (iMajor > 0) break; } //calculate start and end points for minor tick's line if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(minorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? -yDirection * minorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? -yDirection * minorTicksLength : 0); } } } else { // vertical anchorPoint.setY(minorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? -xDirection * minorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? -xDirection * minorTicksLength : 0, 0); } } } //add minor tick's line to the painter path if (valid) { minorTicksPath.moveTo(startPoint); minorTicksPath.lineTo(endPoint); minorTickPoints << anchorPoint; } } } } //tick positions where changed -> update the position of the tick labels and grid lines retransformTickLabelStrings(); retransformMajorGrid(); retransformMinorGrid(); } /*! creates the tick label strings starting with the most optimal (=the smallest possible number of float digits) precision for the floats */ void AxisPrivate::retransformTickLabelStrings() { if (suppressRetransform) return; // DEBUG("AxisPrivate::retransformTickLabelStrings()"); if (labelsAutoPrecision) { //check, whether we need to increase the current precision int newPrecision = upperLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } else { //check, whether we can reduce the current precision newPrecision = lowerLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } } } // DEBUG("labelsPrecision =" << labelsPrecision); tickLabelStrings.clear(); QString str; if (labelsFormat == Axis::FormatDecimal) { QString nullStr = QString::number(0, 'f', labelsPrecision); foreach (float value, tickLabelValues) { str = QString::number(value, 'f', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatScientificE) { QString nullStr = QString::number(0, 'e', labelsPrecision); foreach (float value, tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { foreach (float value, tickLabelValues) { str = "10" + QString::number(log10(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers2) { foreach (float value, tickLabelValues) { str = "2" + QString::number(log2(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowersE) { foreach (float value, tickLabelValues) { str = "e" + QString::number(log(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatMultipliesPi) { foreach (float value, tickLabelValues) { str = "" + QString::number(value / M_PI, 'f', labelsPrecision) + "" + QChar(0x03C0); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } //recalculate the position of the tick labels retransformTickLabelPositions(); } /*! returns the smallest upper limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::upperLabelsPrecision(int precision) { // DEBUG("AxisPrivate::upperLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, increase the precision. QVector tempValues; for (int i = 0; i < tickLabelValues.size(); ++i) tempValues.append( round(tickLabelValues[i], precision) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate for the current precision found, increase the precision and check again return upperLabelsPrecision(precision + 1); } } } //no duplicates for the current precision found: return the current value return precision; } /*! returns highest lower limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::lowerLabelsPrecision(int precision) { // DEBUG("AxisPrivate::lowerLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, decrease the precision. QVector tempValues; for (int i = 0; i < tickLabelValues.size(); ++i) tempValues.append( round(tickLabelValues[i], precision-1) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate found for the reduced precision //-> current precision cannot be reduced, return the current value return precision; } } } //no duplicates found, reduce further, and check again if (precision == 0) return 0; else return lowerLabelsPrecision(precision - 1); } double AxisPrivate::round(double value, int precision) { //DEBUG("AxisPrivate::round() value =" << value << "precision =" << precision); double result = roundf(value * pow(10, precision)) / pow(10, precision); //DEBUG(" result =" << result); return result; } /*! recalculates the position of the tick labels. Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed. */ void AxisPrivate::retransformTickLabelPositions() { tickLabelPoints.clear(); if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::NoLabels) { recalcShapeAndBoundingRect(); return; } QFontMetrics fm(labelsFont); float width = 0; float height = fm.ascent(); QString label; QPointF pos; float middleX = m_plot->xMin() + (m_plot->xMax() - m_plot->xMin())/2; float middleY = m_plot->yMin() + (m_plot->yMax() - m_plot->yMin())/2; int xDirection = m_cSystem->xDirection(); int yDirection = m_cSystem->yDirection(); QPointF startPoint, endPoint, anchorPoint; QTextDocument td; td.setDefaultFont(labelsFont); for ( int i=0; i logicalMajorTickPoints = m_cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); if (logicalMajorTickPoints.isEmpty()) return; //TODO: //when iterating over all grid lines, skip the first and the last points for auto scaled axes, //since we don't want to paint any grid lines at the plot boundaries bool skipLowestTick, skipUpperTick; if (orientation == Axis::AxisHorizontal) { //horizontal axis skipLowestTick = qFuzzyCompare((float)logicalMajorTickPoints.at(0).x(), m_plot->xMin()); skipUpperTick = qFuzzyCompare((float)logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), m_plot->xMax()); } else { skipLowestTick = qFuzzyCompare((float)logicalMajorTickPoints.at(0).y(), m_plot->yMin()); skipUpperTick = qFuzzyCompare((float)logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), m_plot->yMax()); } int start, end; if (skipLowestTick) { if (logicalMajorTickPoints.size()>1) start = 1; else start = 0; } else { start = 0; } if ( skipUpperTick ) { if (logicalMajorTickPoints.size()>1) end = logicalMajorTickPoints.size()-1; else end = 0; } else { end = logicalMajorTickPoints.size(); } QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis float yMin = m_plot->yMin(); float yMax = m_plot->yMax(); for (int i=start; ixMin(); float xMax = m_plot->xMax(); //skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries for (int i=start; imapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); foreach (const QLineF& line, lines) { majorGridPath.moveTo(line.p1()); majorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMinorGrid() { if (suppressRetransform) return; minorGridPath = QPainterPath(); if (minorGridPen.style() == Qt::NoPen) { recalcShapeAndBoundingRect(); return; } //minor tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMinorTickPoints = m_cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis float yMin = m_plot->yMin(); float yMax = m_plot->yMax(); for (int i=0; ixMin(); float xMax = m_plot->xMax(); for (int i=0; imapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); foreach (const QLineF& line, lines) { minorGridPath.moveTo(line.p1()); minorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::recalcShapeAndBoundingRect() { if (m_suppressRecalc) return; prepareGeometryChange(); if (linePath.isEmpty()) { axisShape = QPainterPath(); boundingRectangle = QRectF(); title->setPositionInvalid(true); if (m_plot) m_plot->prepareGeometryChange(); return; } else { title->setPositionInvalid(false); } axisShape = WorksheetElement::shapeFromPath(linePath, linePen); axisShape.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen)); axisShape.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksPen)); axisShape.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksPen)); QPainterPath tickLabelsPath = QPainterPath(); if (labelsPosition != Axis::NoLabels) { QTransform trafo; QPainterPath tempPath; QFontMetrics fm(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i=0; iisVisible() && !title->text().text.isEmpty() ) { //determine the new position of the title label: //we calculate the new position here and not in retransform(), //since it depends on the size and position of the tick labels, tickLabelsPath, available here. QRectF rect=linePath.boundingRect(); float offsetX = titleOffsetX - labelsOffset; //the distance to the axis line float offsetY = titleOffsetY - labelsOffset; //the distance to the axis line if (orientation == Axis::AxisHorizontal) { offsetY -= title->graphicsItem()->boundingRect().height()/2 + tickLabelsPath.boundingRect().height(); title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + offsetX, rect.bottomLeft().y() - offsetY ) ); } else { offsetX -= title->graphicsItem()->boundingRect().width()/2 + tickLabelsPath.boundingRect().width(); title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - offsetY) ); } axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen)); } boundingRectangle = axisShape.boundingRect(); //if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.) //request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes if (m_plot) m_plot->prepareGeometryChange(); } /*! paints the content of the axis. Reimplemented from \c QGraphicsItem. \sa QGraphicsItem::paint() */ void AxisPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (linePath.isEmpty()) return; //draw the line if (linePen.style() != Qt::NoPen) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::SolidPattern); painter->drawPath(linePath); //draw the arrow if (arrowType != Axis::NoArrow) painter->drawPath(arrowPath); } //draw the major ticks if (majorTicksDirection != Axis::noTicks) { painter->setOpacity(majorTicksOpacity); painter->setPen(majorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(majorTicksPath); } //draw the minor ticks if (minorTicksDirection != Axis::noTicks) { painter->setOpacity(minorTicksOpacity); painter->setPen(minorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(minorTicksPath); } // draw tick labels if (labelsPosition != Axis::NoLabels) { painter->setOpacity(labelsOpacity); painter->setPen(QPen(labelsColor)); painter->setFont(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); } else { td.setHtml(tickLabelStrings.at(i)); painter->translate(0, -td.size().height()); td.drawContents(painter); } painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(axisShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(axisShape); } } void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; q->hovered(); update(axisShape.boundingRect()); } } void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; q->unhovered(); update(axisShape.boundingRect()); } } void AxisPrivate::setPrinting(bool on) { m_printing = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Axis::save(QXmlStreamWriter* writer) const{ Q_D(const Axis); writer->writeStartElement( "axis" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); writer->writeAttribute( "autoScale", QString::number(d->autoScale) ); writer->writeAttribute( "orientation", QString::number(d->orientation) ); writer->writeAttribute( "position", QString::number(d->position) ); writer->writeAttribute( "scale", QString::number(d->scale) ); writer->writeAttribute( "offset", QString::number(d->offset) ); writer->writeAttribute( "start", QString::number(d->start) ); writer->writeAttribute( "end", QString::number(d->end) ); writer->writeAttribute( "scalingFactor", QString::number(d->scalingFactor) ); writer->writeAttribute( "zeroOffset", QString::number(d->zeroOffset) ); writer->writeAttribute( "titleOffsetX", QString::number(d->titleOffsetX) ); writer->writeAttribute( "titleOffsetY", QString::number(d->titleOffsetY) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //label d->title->save( writer ); //line writer->writeStartElement( "line" ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeAttribute( "arrowType", QString::number(d->arrowType) ); writer->writeAttribute( "arrowPosition", QString::number(d->arrowPosition) ); writer->writeAttribute( "arrowSize", QString::number(d->arrowSize) ); writer->writeEndElement(); //major ticks writer->writeStartElement( "majorTicks" ); writer->writeAttribute( "direction", QString::number(d->majorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->majorTicksType) ); writer->writeAttribute( "number", QString::number(d->majorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->majorTicksIncrement) ); WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn); writer->writeAttribute( "length", QString::number(d->majorTicksLength) ); WRITE_QPEN(d->majorTicksPen); writer->writeAttribute( "opacity", QString::number(d->majorTicksOpacity) ); writer->writeEndElement(); //minor ticks writer->writeStartElement( "minorTicks" ); writer->writeAttribute( "direction", QString::number(d->minorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->minorTicksType) ); writer->writeAttribute( "number", QString::number(d->minorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->minorTicksIncrement) ); WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn); writer->writeAttribute( "length", QString::number(d->minorTicksLength) ); WRITE_QPEN(d->minorTicksPen); writer->writeAttribute( "opacity", QString::number(d->minorTicksOpacity) ); writer->writeEndElement(); //extra ticks //labels writer->writeStartElement( "labels" ); writer->writeAttribute( "position", QString::number(d->labelsPosition) ); writer->writeAttribute( "offset", QString::number(d->labelsOffset) ); writer->writeAttribute( "rotation", QString::number(d->labelsRotationAngle) ); writer->writeAttribute( "format", QString::number(d->labelsFormat) ); writer->writeAttribute( "precision", QString::number(d->labelsPrecision) ); writer->writeAttribute( "autoPrecision", QString::number(d->labelsAutoPrecision) ); WRITE_QCOLOR(d->labelsColor); WRITE_QFONT(d->labelsFont); writer->writeAttribute( "prefix", d->labelsPrefix ); writer->writeAttribute( "suffix", d->labelsSuffix ); writer->writeAttribute( "opacity", QString::number(d->labelsOpacity) ); writer->writeEndElement(); //grid writer->writeStartElement( "majorGrid" ); WRITE_QPEN(d->majorGridPen); writer->writeAttribute( "opacity", QString::number(d->majorGridOpacity) ); writer->writeEndElement(); writer->writeStartElement( "minorGrid" ); WRITE_QPEN(d->minorGridPen); writer->writeAttribute( "opacity", QString::number(d->minorGridOpacity) ); writer->writeEndElement(); writer->writeEndElement(); // close "axis" section } //! Load from XML bool Axis::load(XmlStreamReader* reader, bool preview) { Q_D(Axis); if (!reader->isStartElement() || reader->name() != "axis") { reader->raiseError(i18n("no axis element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; QRectF rect; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "axis") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("autoScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScale'")); else d->autoScale = (bool)str.toInt(); str = attribs.value("orientation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'orientation'")); else d->orientation = (Axis::AxisOrientation)str.toInt(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->position = (Axis::AxisPosition)str.toInt(); str = attribs.value("scale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'scale'")); else d->scale = (Axis::AxisScale)str.toInt(); str = attribs.value("offset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'offset'")); else d->offset = str.toDouble(); str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else d->start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else d->end = str.toDouble(); str = attribs.value("scalingFactor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'scalingFactor'")); else d->scalingFactor = str.toDouble(); str = attribs.value("zeroOffset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'zeroOffset'")); else d->zeroOffset = str.toDouble(); str = attribs.value("titleOffsetX").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'titleOffsetX'")); else d->titleOffsetX = str.toDouble(); str = attribs.value("titleOffsetY").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'titleOffsetY'")); else d->titleOffsetY = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (reader->name() == "textLabel") { d->title->load(reader, preview); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_QPEN(d->linePen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->lineOpacity = str.toDouble(); str = attribs.value("arrowType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowType'")); else d->arrowType = (Axis::ArrowType)str.toInt(); str = attribs.value("arrowPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowPosition'")); else d->arrowPosition = (Axis::ArrowPosition)str.toInt(); str = attribs.value("arrowSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowSize'")); else d->arrowSize = str.toDouble(); } else if (!preview && reader->name() == "majorTicks") { attribs = reader->attributes(); str = attribs.value("direction").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'direction'")); else d->majorTicksDirection = (Axis::TicksDirection)str.toInt(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->majorTicksType = (Axis::TicksType)str.toInt(); str = attribs.value("number").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'number'")); else d->majorTicksNumber = str.toInt(); str = attribs.value("increment").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'increment'")); else d->majorTicksIncrement = str.toDouble(); READ_COLUMN(majorTicksColumn); str = attribs.value("length").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'length'")); else d->majorTicksLength = str.toDouble(); READ_QPEN(d->majorTicksPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->majorTicksOpacity = str.toDouble(); } else if (!preview && reader->name() == "minorTicks") { attribs = reader->attributes(); str = attribs.value("direction").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'direction'")); else d->minorTicksDirection = (Axis::TicksDirection)str.toInt(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->minorTicksType = (Axis::TicksType)str.toInt(); str = attribs.value("number").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'number'")); else d->minorTicksNumber = str.toInt(); str = attribs.value("increment").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'increment'")); else d->minorTicksIncrement = str.toDouble(); READ_COLUMN(minorTicksColumn); str = attribs.value("length").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'length'")); else d->minorTicksLength = str.toDouble(); READ_QPEN(d->minorTicksPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->minorTicksOpacity = str.toDouble(); } else if (!preview && reader->name() == "labels") { attribs = reader->attributes(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->labelsPosition = (Axis::LabelsPosition)str.toInt(); str = attribs.value("offset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'offset'")); else d->labelsOffset = str.toDouble(); str = attribs.value("rotation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->labelsRotationAngle = str.toDouble(); str = attribs.value("format").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'format'")); else d->labelsFormat = (Axis::LabelsFormat)str.toInt(); str = attribs.value("precision").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'precision'")); else d->labelsPrecision = str.toInt(); str = attribs.value("autoPrecision").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoPrecision'")); else d->labelsAutoPrecision = str.toInt(); READ_QCOLOR(d->labelsColor); READ_QFONT(d->labelsFont); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->labelsPrefix = attribs.value("prefix").toString(); d->labelsSuffix = attribs.value("suffix").toString(); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->labelsOpacity = str.toDouble(); } else if (!preview && reader->name() == "majorGrid") { attribs = reader->attributes(); READ_QPEN(d->majorGridPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->majorGridOpacity = str.toDouble(); } else if (!preview && reader->name() == "minorGrid") { attribs = reader->attributes(); READ_QPEN(d->minorGridPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->minorGridOpacity = str.toDouble(); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Axis::loadThemeConfig(const KConfig& config) { const KConfigGroup group = config.group("Axis"); QPen p; // Tick label this->setLabelsColor(group.readEntry("LabelsFontColor",(QColor) this->labelsColor())); this->setLabelsOpacity(group.readEntry("LabelsOpacity",this->labelsOpacity())); //Line this->setLineOpacity(group.readEntry("LineOpacity",this->lineOpacity())); p.setColor(group.readEntry("LineColor", (QColor) this->linePen().color())); p.setStyle((Qt::PenStyle)group.readEntry("LineStyle",(int) this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); this->setLinePen(p); //Major ticks this->setMajorGridOpacity(group.readEntry("MajorGridOpacity", this->majorGridOpacity())); p.setColor(group.readEntry("MajorGridColor",(QColor) this->majorGridPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MajorGridStyle",(int) this->majorGridPen().style())); p.setWidthF(group.readEntry("MajorGridWidth", this->majorGridPen().widthF())); this->setMajorGridPen(p); p.setColor(group.readEntry("MajorTicksColor",(QColor)this->majorTicksPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MajorTicksLineStyle",(int) this->majorTicksPen().style())); p.setWidthF(group.readEntry("MajorTicksWidth", this->majorTicksPen().widthF())); this->setMajorTicksPen(p); this->setMajorTicksOpacity(group.readEntry("MajorTicksOpacity",this->majorTicksOpacity())); this->setMajorTicksType((Axis::TicksType)group.readEntry("MajorTicksType",(int)this->majorTicksType())); //Minor ticks this->setMinorGridOpacity(group.readEntry("MinorGridOpacity", this->minorGridOpacity())); p.setColor(group.readEntry("MinorGridColor",(QColor) this->minorGridPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MinorGridStyle",(int) this->minorGridPen().style())); p.setWidthF(group.readEntry("MinorGridWidth", this->minorGridPen().widthF())); this->setMinorGridPen(p); p.setColor(group.readEntry("MinorTicksColor",(QColor) this->minorTicksPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MinorTicksLineStyle",(int) this->minorTicksPen().style())); p.setWidthF(group.readEntry("MinorTicksWidth", this->minorTicksPen().widthF())); this->setMinorTicksPen(p); this->setMinorTicksOpacity(group.readEntry("MinorTicksOpacity",this->minorTicksOpacity())); this->setMinorTicksType((Axis::TicksType)group.readEntry("MinorTicksType",(int)this->minorTicksType())); const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } void Axis::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Axis"); // Tick label group.writeEntry("LabelsFontColor", (QColor) this->labelsColor()); group.writeEntry("LabelsOpacity", this->labelsOpacity()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineColor", (QColor) this->linePen().color()); group.writeEntry("LineStyle", (int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Major ticks group.writeEntry("MajorGridOpacity", this->majorGridOpacity()); group.writeEntry("MajorGridColor", (QColor) this->majorGridPen().color()); group.writeEntry("MajorGridStyle", (int) this->majorGridPen().style()); group.writeEntry("MajorGridWidth", this->majorGridPen().widthF()); group.writeEntry("MajorTicksColor", (QColor)this->majorTicksPen().color()); group.writeEntry("MajorTicksLineStyle", (int) this->majorTicksPen().style()); group.writeEntry("MajorTicksWidth", this->majorTicksPen().widthF()); group.writeEntry("MajorTicksOpacity", this->majorTicksOpacity()); group.writeEntry("MajorTicksType", (int)this->majorTicksType()); //Minor ticks group.writeEntry("MinorGridOpacity", this->minorGridOpacity()); group.writeEntry("MinorGridColor",(QColor) this->minorGridPen().color()); group.writeEntry("MinorGridStyle", (int) this->minorGridPen().style()); group.writeEntry("MinorGridWidth", this->minorGridPen().widthF()); group.writeEntry("MinorTicksColor", (QColor) this->minorTicksPen().color()); group.writeEntry("MinorTicksLineStyle",( int) this->minorTicksPen().style()); group.writeEntry("MinorTicksWidth", this->minorTicksPen().widthF()); group.writeEntry("MinorTicksOpacity", this->minorTicksOpacity()); group.writeEntry("MinorTicksType", (int)this->minorTicksType()); const QVector& childElements = children(AbstractAspect::IncludeHidden); childElements.at(0)->saveThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index 779cc5235..0d535a52e 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,2907 +1,2905 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include #include #include -#include // DBL_MAX - /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name):AbstractPlot(name, new CartesianPlotPrivate(this)), m_legend(0), m_zoomFactor(1.2), m_menusInitialized(false), addNewMenu(nullptr), zoomMenu(nullptr), dataAnalysisMenu(nullptr), themeMenu(nullptr) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd):AbstractPlot(name, dd), m_legend(0), m_zoomFactor(1.2), addNewMenu(nullptr), zoomMenu(nullptr), dataAnalysisMenu(nullptr), themeMenu(nullptr) { init(); } CartesianPlot::~CartesianPlot() { if (m_menusInitialized) { delete addNewMenu; delete zoomMenu; delete themeMenu; } delete m_coordinateSystem; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; d->rangeType = CartesianPlot::RangeFree; d->rangeLastValues = 1000; d->rangeFirstValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 1 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. d->autoScaleOffsetFactor = 0.05; //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option m_plotArea = new PlotArea(name() + " plot area"); addChildFast(m_plotArea); //offset between the plot area and the area defining the coordinate system, in scene units. d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); connect(this, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(childAdded(const AbstractAspect*))); connect(this, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SLOT(childRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; //Axes Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("x axis 2", this, Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", this, Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("y axis 2", this, Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", this, Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", this, Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", this, Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Plot title m_title = new TextLabel(this->name(), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramPlot = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical equation"), this); // no icons yet addDataReductionCurveAction = new QAction(i18n("xy-curve from a data reduction"), this); addDifferentiationCurveAction = new QAction(i18n("xy-curve from a differentiation"), this); addIntegrationCurveAction = new QAction(i18n("xy-curve from an integration"), this); addInterpolationCurveAction = new QAction(i18n("xy-curve from an interpolation"), this); addSmoothCurveAction = new QAction(i18n("xy-curve from a smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a fit to data"), this); addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier filter"), this); addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier transform"), this); // addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve from an interpolation"), this); // addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smooth-curve"), i18n("xy-curve from a smooth"), this); // addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("xy-curve from a Fourier filter"), this); // addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_transform-curve"), i18n("xy-curve from a Fourier transform"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("horizontal axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("vertical axis"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("custom point"), this); connect(addCurveAction, SIGNAL(triggered()), SLOT(addCurve())); connect(addHistogramPlot,SIGNAL(triggered()), SLOT(addHistogram())); connect(addEquationCurveAction, SIGNAL(triggered()), SLOT(addEquationCurve())); connect(addDataReductionCurveAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationCurveAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationCurveAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationCurveAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothCurveAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); connect(addFitCurveAction, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterCurveAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); connect(addFourierTransformCurveAction, SIGNAL(triggered()), SLOT(addFourierTransformCurve())); connect(addLegendAction, SIGNAL(triggered()), SLOT(addLegend())); connect(addHorizontalAxisAction, SIGNAL(triggered()), SLOT(addHorizontalAxis())); connect(addVerticalAxisAction, SIGNAL(triggered()), SLOT(addVerticalAxis())); connect(addCustomPointAction, SIGNAL(triggered()), SLOT(addCustomPoint())); //Analysis menu actions addDataOperationAction = new QAction(i18n("Data operation"), this); addDataReductionAction = new QAction(i18n("Reduce data"), this); addDifferentiationAction = new QAction(i18n("Differentiate"), this); addIntegrationAction = new QAction(i18n("Integrate"), this); addInterpolationAction = new QAction(i18n("Interpolate"), this); addSmoothAction = new QAction(i18n("Smooth"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(i18n("Inverse exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(i18n("Hyperbolic tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(i18n("Error function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(i18n("Fourier filter"), this); connect(addDataReductionAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); for (const auto& action: addFitAction) connect(action, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("auto scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("auto scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("auto scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("zoom in"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("zoom out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("zoom in X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("zoom out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("zoom in Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("zoom out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("shift left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("shift right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("shift up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("shift down Y"), this); connect(scaleAutoAction, SIGNAL(triggered()), SLOT(scaleAuto())); connect(scaleAutoXAction, SIGNAL(triggered()), SLOT(scaleAutoX())); connect(scaleAutoYAction, SIGNAL(triggered()), SLOT(scaleAutoY())); connect(zoomInAction, SIGNAL(triggered()), SLOT(zoomIn())); connect(zoomOutAction, SIGNAL(triggered()), SLOT(zoomOut())); connect(zoomInXAction, SIGNAL(triggered()), SLOT(zoomInX())); connect(zoomOutXAction, SIGNAL(triggered()), SLOT(zoomOutX())); connect(zoomInYAction, SIGNAL(triggered()), SLOT(zoomInY())); connect(zoomOutYAction, SIGNAL(triggered()), SLOT(zoomOutY())); connect(shiftLeftXAction, SIGNAL(triggered()), SLOT(shiftLeftX())); connect(shiftRightXAction, SIGNAL(triggered()), SLOT(shiftRightX())); connect(shiftUpYAction, SIGNAL(triggered()), SLOT(shiftUpY())); connect(shiftDownYAction, SIGNAL(triggered()), SLOT(shiftDownY())); //visibility action visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add new")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramPlot); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addDataReductionCurveAction); addNewMenu->addAction(addDifferentiationCurveAction); addNewMenu->addAction(addIntegrationCurveAction); addNewMenu->addAction(addInterpolationCurveAction); addNewMenu->addAction(addSmoothCurveAction); addNewMenu->addAction(addFitCurveAction); addNewMenu->addAction(addFourierFilterCurveAction); addNewMenu->addAction(addFourierTransformCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); zoomMenu = new QMenu(i18n("Zoom")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataOperationAction); dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->insertMenu(0, dataManipulationMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addMenu(dataFitMenu); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); ThemesWidget* themeWidget = new ThemesWidget(0); connect(themeWidget, SIGNAL(themeSelected(QString)), this, SLOT(loadTheme(QString))); connect(themeWidget, SIGNAL(themeSelected(QString)), themeMenu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); m_menusInitialized = true; } QMenu* CartesianPlot::createContextMenu() { if (!m_menusInitialized) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertMenu(firstAction, addNewMenu); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); return menu; } QMenu* CartesianPlot::analysisMenu() { if (!m_menusInitialized) initMenus(); return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { if (op == ScaleAuto) scaleAuto(); else if (op == ScaleAutoX) scaleAutoX(); else if (op == ScaleAutoY) scaleAutoY(); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } void CartesianPlot::setSuppressDataChangedSignal(bool value) { Q_D(CartesianPlot); d->suppressRetransform = value; } void CartesianPlot::processDropEvent(QDropEvent* event) { PERFTRACE("CartesianPlot::processDropEvent"); const QMimeData* mimeData = event->mimeData(); if (!mimeData) return; //deserialize the mime data to the vector of aspect pointers QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; QVector columns; for (auto i : vec) { AbstractAspect* aspect = (AbstractAspect*)i; AbstractColumn* column = dynamic_cast(aspect); if (column) columns << column; } //return if there are no columns being dropped. //TODO: extend this later when we allow to drag&drop plots, etc. if (columns.isEmpty()) return; //determine the first column with "x plot designation" as the x-data column for all curves to be created const AbstractColumn* xColumn = nullptr; for (const auto* column : columns) { if (column->plotDesignation() == AbstractColumn::X) { xColumn = column; break; } } //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, if (xColumn == nullptr) { QVector curves = children(); if (!curves.isEmpty()) xColumn = curves.at(0)->xColumn(); } //use the first dropped column if no column with "x plot designation" nor curves are available if (xColumn == nullptr) xColumn = columns.at(0); //create curves bool curvesAdded = false; for (const auto* column : columns) { if (column == xColumn) continue; XYCurve* curve = new XYCurve(column->name()); curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end curve->setXColumn(xColumn); curve->setYColumn(column); addChild(curve); curve->suppressRetransform(false); curvesAdded = true; } if (curvesAdded) dataChanged(); } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! return the actual bounding rectangular of the plot (plot's rectangular minus padding) in plot's coordinates */ //TODO: return here a private variable only, update this variable on rect and padding changes. QRectF CartesianPlot::plotRect() { Q_D(const CartesianPlot); QRectF rect = d->mapRectFromScene(d->rect); rect.setX(rect.x() + d->horizontalPadding); rect.setY(rect.y() + d->verticalPadding); rect.setWidth(rect.width() - d->horizontalPadding); rect.setHeight(rect.height()-d->verticalPadding); return rect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; virtual void redo() { QRectF tmp = m_private->rect; const double horizontalRatio = m_rect.width() / m_private->rect.width(); const double verticalRatio = m_rect.height() / m_private->rect.height(); m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->rect = m_rect; m_rect = tmp; m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; virtual void undo() { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged); void CartesianPlot::setRangeType(RangeType type) { Q_D(CartesianPlot); if (type != d->rangeType) exec(new CartesianPlotSetRangeTypeCmd(d, type, i18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged); void CartesianPlot::setRangeLastValues(int values) { Q_D(CartesianPlot); if (values != d->rangeLastValues) exec(new CartesianPlotSetRangeLastValuesCmd(d, values, i18n("%1: set range"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged); void CartesianPlot::setRangeFirstValues(int values) { Q_D(CartesianPlot); if (values != d->rangeFirstValues) exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, i18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; virtual void redo() { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; virtual void undo() { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; float m_minOld; float m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, float, xMin, retransformScales) void CartesianPlot::setXMin(float xMin) { Q_D(CartesianPlot); if (xMin != d->xMin) exec(new CartesianPlotSetXMinCmd(d, xMin, i18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, float, xMax, retransformScales) void CartesianPlot::setXMax(float xMax) { Q_D(CartesianPlot); if (xMax != d->xMax) exec(new CartesianPlotSetXMaxCmd(d, xMax, i18n("%1: set max x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) exec(new CartesianPlotSetXScaleCmd(d, scale, i18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, i18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales) void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, i18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; virtual void redo() { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; virtual void undo() { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; float m_minOld; float m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, float, yMin, retransformScales) void CartesianPlot::setYMin(float yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) exec(new CartesianPlotSetYMinCmd(d, yMin, i18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, float, yMax, retransformScales) void CartesianPlot::setYMax(float yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) exec(new CartesianPlotSetYMaxCmd(d, yMax, i18n("%1: set max y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) exec(new CartesianPlotSetYScaleCmd(d, scale, i18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, i18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales) void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, i18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: set theme"))); loadTheme(theme); endMacro(); } else exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", this, Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", this, Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addCurve() { addChild(new XYCurve("xy-curve")); } void CartesianPlot::addEquationCurve() { addChild(new XYEquationCurve("f(x)")); } void CartesianPlot::addHistogram() { addChild(new Histogram("Histogram")); } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve: this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return 0; } void CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) ); curve->setName( i18n("Reduction of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->dataReductionDataChanged(curve->dataReductionData()); } else { beginMacro(i18n("%1: add data reduction curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); curve->recalculate(); this->addChild(curve); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFitCurve() { XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const QAction* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); this->addChild(curve); curve->recalculate(); emit curve->fitDataChanged(curve->fitData()); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action if (m_menusInitialized) addLegendAction->setEnabled(false); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const XYCurve* curve = qobject_cast(child); if (curve) { connect(curve, SIGNAL(dataChanged()), this, SLOT(dataChanged())); connect(curve, SIGNAL(xDataChanged()), this, SLOT(xDataChanged())); connect(curve, SIGNAL(yDataChanged()), this, SLOT(yDataChanged())); connect(curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); //update the legend on changes of the name, line and symbol styles connect(curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(updateLegend())); connect(curve, SIGNAL(linePenChanged(QPen)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(updateLegend())); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; } else { const Histogram* histo = qobject_cast(child); if (histo) { connect(histo, SIGNAL(HistogramdataChanged()), this, SLOT(HistogramdataChanged())); connect(histo, SIGNAL(xHistogramDataChanged()), this, SLOT(xHistogramDataChanged())); connect(histo, SIGNAL(yHistogramDataChanged()), this, SLOT(yHistogramDataChanged())); connect(histo, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); } } //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty() && !isLoading()) { const WorksheetElement* el = dynamic_cast(child); if (el) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(el)->loadThemeConfig(config); } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { if (m_menusInitialized) addLegendAction->setEnabled(true); m_legend = nullptr; } else { const XYCurve* curve = qobject_cast(child); if (curve) updateLegend(); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); else { //free ranges -> rentransform the curve that sent XYCurve* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive qDebug()<<"HERE"; for (auto curve : children()) QtConcurrent::run(curve, &XYCurve::retransform); // curve->retransform(); } } } void CartesianPlot::HistogramdataChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoY(); else if (d->autoScaleY) this->scaleAutoY(); else { Histogram* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto curve : children()) curve->retransform(); } } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAutoX(); else { XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); curve->retransform(); } } void CartesianPlot::xHistogramDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAutoX(); else { Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); curve->retransform(); } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else { XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); curve->retransform(); } } void CartesianPlot::yHistogramDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else { Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); curve->retransform(); } } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const Worksheet* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { if (worksheet->layout() != Worksheet::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } } void CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { float offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; const double min = curve->yColumn()->minimum(count); if (min < d->curvesYMin) d->curvesYMin = min; const double max = curve->yColumn()->maximum(count); if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (d->curvesYMin > 0.0) d->curvesYMin = 0.0; if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } d->curvesYMinMaxIsDirty = false; } bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAuto() { Q_D(CartesianPlot); int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } if (d->curvesXMinMaxIsDirty) { d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; const double min = curve->yColumn()->minimum(count); if (min < d->curvesYMin) d->curvesYMin = min; const double max = curve->yColumn()->maximum(count); if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (d->curvesYMin > 0.0) d->curvesYMin = 0.0; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { float offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } } d->retransformScales(); } } void CartesianPlot::zoomIn() { DEBUG("CartesianPlot::zoomIn()"); Q_D(CartesianPlot); float oldRange = (d->xMax - d->xMin); float newRange = (d->xMax - d->xMin) / m_zoomFactor; d->xMax = d->xMax + (newRange - oldRange) / 2; d->xMin = d->xMin - (newRange - oldRange) / 2; oldRange = (d->yMax - d->yMin); newRange = (d->yMax - d->yMin) / m_zoomFactor; d->yMax = d->yMax + (newRange - oldRange) / 2; d->yMin = d->yMin - (newRange - oldRange) / 2; d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; oldRange = (d->yMax-d->yMin); newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)/m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); float oldRange = (d->yMax-d->yMin); float newRange = (d->yMax-d->yMin)/m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); float oldRange = (d->yMax-d->yMin); float newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); float offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); float offsetY = (d->yMax-d->yMin)*0.1; d->yMax -= offsetY; d->yMin -= offsetY; d->retransformScales(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), curvesXMinMaxIsDirty(false), curvesYMinMaxIsDirty(false), curvesXMin(INFINITY), curvesXMax(-INFINITY), curvesYMin(INFINITY), curvesYMax(-INFINITY), q(plot), mouseMode(CartesianPlot::SelectionMode), cSystem(nullptr), suppressRetransform(false), // m_printing(false), m_selectionBandIsShown(false) { setData(0, WorksheetElement::NameCartesianPlot); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { if (suppressRetransform) return; PERFTRACE("CartesianPlotPrivate::retransform()"); prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { PERFTRACE("CartesianPlotPrivate::retransformScales()"); CartesianPlot* plot = dynamic_cast(q); QVector scales; //perform the mapping from the scene coordinates to the plot's coordinates here. QRectF itemRect = mapRectFromScene(rect); //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = itemRect.x() + horizontalPadding; int plotSceneEnd = itemRect.x() + itemRect.width() - horizontalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb: xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = itemRect.y()+itemRect.height()-verticalPadding; plotSceneEnd = itemRect.y()+verticalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb: yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax float deltaXMin = 0; float deltaXMax = 0; float deltaYMin = 0; float deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit plot->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit plot->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit plot->yMinChanged(yMin); } if (yMax!=yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit plot->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis: q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(xMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(xMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(yMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(yMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curvesю //no need to do this on load since all plots are retransformed again after the project is loaded. if (!q->isLoading()) q->retransform(); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (-1E15, 1E15); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else { float base; if (type == CartesianPlot::ScaleLog10) base = 10.0; else if (type == CartesianPlot::ScaleLog2) base = 2.0; else base = M_E; return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, base); } } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; float w = rect.width(); float h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { if (mouseMode == CartesianPlot::ZoomSelectionMode) m_selectionStart = event->pos(); else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionStart.setX(event->pos().x()); m_selectionStart.setY(q->plotRect().height()/2); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(-q->plotRect().width()/2); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else QGraphicsItem::mousePressEvent(event); } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) QGraphicsItem::mouseMoveEvent(event); else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(""); return; } QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = event->pos(); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()) + QString::fromUtf8(", Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionEnd.setX(event->pos().x()); m_selectionEnd.setY(-q->plotRect().height()/2); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(q->plotRect().width()/2); m_selectionEnd.setY(event->pos().y()); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } q->info(info); update(); } //TODO: implement the navigation in plot on mouse move events, //calculate the position changes and call shift*()-functions } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { const QPointF& itemPos = pos();//item's center point in parent's coordinates; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and set it QRectF newRect; float w = rect.width(); float h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); suppressRetransform = true; q->setRect(newRect); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } m_selectionBandIsShown = false; retransformScales(); } } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis: q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (q->plotRect().contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) info = "x=" + QString::number(logicalPoint.x()) + ", y=" + QString::number(logicalPoint.y()); else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicalPoint.x(), yMin); QPointF p2(logicalPoint.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "x=" + QString::number(logicalPoint.x()); update(); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicalPoint.y()); QPointF p2(xMax, logicalPoint.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "y=" + QString::number(logicalPoint.y()); update(); } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) { if (!isVisible()) return; painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown)) painter->drawLine(m_selectionStartLine); if (m_selectionBandIsShown) { painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->restore(); } WorksheetElementContainerPrivate::paint(painter, option, widget); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin) ); writer->writeAttribute( "xMax", QString::number(d->xMax) ); writer->writeAttribute( "yMin", QString::number(d->yMin) ); writer->writeAttribute( "yMax", QString::number(d->yMax) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb: d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb: d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto *elem: children(IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlot); if (!reader->isStartElement() || reader->name() != "cartesianPlot") { reader->raiseError(i18n("no cartesianPlot element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'height'")); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); str = attribs.value("autoScaleX").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleX'")); else d->autoScaleX = bool(str.toInt()); str = attribs.value("autoScaleY").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleY'")); else d->autoScaleY = bool(str.toInt()); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xMin'")); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xMax'")); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yMin'")); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yMax'")); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } str = attribs.value("xScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xScale'")); else d->xScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("yScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yScale'")); else d->yScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("horizontalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPadding'")); else d->horizontalPadding = str.toDouble(); str = attribs.value("verticalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPadding'")); else d->verticalPadding = str.toDouble(); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->xRangeBreakingEnabled = str.toInt(); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (!preview && reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->yRangeBreakingEnabled = str.toInt(); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { m_title = new TextLabel(""); if (m_title->load(reader, preview)) addChildFast(m_title); else { delete m_title; m_title=0; return false; } } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis("", this); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, ""); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { delete m_legend; return false; } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, ""); if (point->load(reader, preview)) addChildFast(point); else { delete point; return false; } } else if(reader->name() == "Histogram") { Histogram* curve = new Histogram("Histogram"); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; d->retransform(); if (m_title) { m_title->setHidden(true); m_title->graphicsItem()->setParentItem(m_plotArea->graphicsItem()); } //if a theme was used, initialize the color palette if (!d->theme.isEmpty()) { //TODO: check whether the theme config really exists KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); this->setColorPalette(config); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } void CartesianPlot::loadThemeConfig(const KConfig& config) { QString str = config.name(); str = str.right(str.length() - str.lastIndexOf(QDir::separator()) - 1); this->setTheme(str); //load the color palettes for the curves this->setColorPalette(config); //load the theme for all the children for (auto* child: children(AbstractAspect::IncludeHidden)) child->loadThemeConfig(config); Q_D(CartesianPlot); d->update(this->rect()); } void CartesianPlot::saveTheme(KConfig &config) { const QVector& axisElements = children(AbstractAspect::IncludeHidden); const QVector& plotAreaElements = children(AbstractAspect::IncludeHidden); const QVector& textLabelElements = children(AbstractAspect::IncludeHidden); axisElements.at(0)->saveThemeConfig(config); plotAreaElements.at(0)->saveThemeConfig(config); textLabelElements.at(0)->saveThemeConfig(config); for (auto *child: children(AbstractAspect::IncludeHidden)) child->saveThemeConfig(config); } //Generating colors from 5-color theme palette void CartesianPlot::setColorPalette(const KConfig& config) { KConfigGroup group = config.group("Theme"); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette float fac[3] = {0.25,0.45,0.65}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp index 0797b3dc7..87e437d9d 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp @@ -1,1150 +1,1148 @@ /*************************************************************************** File : CartesianPlotLegend.cpp Project : LabPlot Description : Legend for the cartesian plot -------------------------------------------------------------------- Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class CartesianPlotLegend \brief Legend for the cartesian plot. \ingroup kdefrontend */ #include "CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegendPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include #include #include #include #include #include #include #include -#include - CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name) : WorksheetElement(name), d_ptr(new CartesianPlotLegendPrivate(this)), m_plot(plot) { init(); } CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name, CartesianPlotLegendPrivate *dd) : WorksheetElement(name), d_ptr(dd), m_plot(plot) { init(); } CartesianPlotLegend::~CartesianPlotLegend() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void CartesianPlotLegend::init() { Q_D(CartesianPlotLegend); KConfig config; KConfigGroup group = config.group( "CartesianPlotLegend" ); d->labelFont = group.readEntry("LabelsFont", QFont()); d->labelFont.setPixelSize( Worksheet::convertToSceneUnits( 10, Worksheet::Point ) ); d->labelColor = Qt::black; d->labelColumnMajor = true; d->lineSymbolWidth = group.readEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)); d->rowCount = 0; d->columnCount = 0; d->position.horizontalPosition = CartesianPlotLegend::hPositionRight; d->position.verticalPosition = CartesianPlotLegend::vPositionBottom; //Title d->title = new TextLabel(this->name(), TextLabel::PlotLegendTitle); d->title->setText(this->name()); addChild(d->title); d->title->setHidden(true); d->title->setParentGraphicsItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); connect(d->title, &TextLabel::changed, this, &CartesianPlotLegend::retransform); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); //Layout d->layoutTopMargin = group.readEntry("LayoutTopMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1, Worksheet::Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1, Worksheet::Centimeter)); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 1); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges); this->initActions(); } void CartesianPlotLegend::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlotLegend::visibilityChanged); } QMenu* CartesianPlotLegend::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlotLegend::icon() const{ return QIcon::fromTheme("text-field"); } STD_SWAP_METHOD_SETTER_CMD_IMPL(CartesianPlotLegend, SetVisible, bool, swapVisible) void CartesianPlotLegend::setVisible(bool on) { Q_D(CartesianPlotLegend); exec(new CartesianPlotLegendSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool CartesianPlotLegend::isVisible() const{ Q_D(const CartesianPlotLegend); return d->isVisible(); } void CartesianPlotLegend::setPrinting(bool on) { Q_D(CartesianPlotLegend); d->m_printing = on; } QGraphicsItem *CartesianPlotLegend::graphicsItem() const{ return d_ptr; } void CartesianPlotLegend::retransform() { d_ptr->retransform(); } void CartesianPlotLegend::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); //TODO // Q_D(const CartesianPlotLegend); } //############################################################################## //################################ getter methods ############################ //############################################################################## CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QFont, labelFont, labelFont) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, labelColor, labelColor) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, bool, labelColumnMajor, labelColumnMajor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, CartesianPlotLegend::PositionWrapper, position, position) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, lineSymbolWidth, lineSymbolWidth) //Title TextLabel* CartesianPlotLegend::title() { return d_ptr->title; } //Background BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, backgroundOpacity, backgroundOpacity) //Border CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderOpacity, borderOpacity) //Layout BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutTopMargin, layoutTopMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutBottomMargin, layoutBottomMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutLeftMargin, layoutLeftMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutRightMargin, layoutRightMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, int, layoutColumnCount, layoutColumnCount) //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelFont, QFont, labelFont, retransform) void CartesianPlotLegend::setLabelFont(const QFont& font) { Q_D(CartesianPlotLegend); if (font!= d->labelFont) exec(new CartesianPlotLegendSetLabelFontCmd(d, font, i18n("%1: set font"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColor, QColor, labelColor, update) void CartesianPlotLegend::setLabelColor(const QColor& color) { Q_D(CartesianPlotLegend); if (color!= d->labelColor) exec(new CartesianPlotLegendSetLabelColorCmd(d, color, i18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColumnMajor, bool, labelColumnMajor, retransform) void CartesianPlotLegend::setLabelColumnMajor(bool columnMajor) { Q_D(CartesianPlotLegend); if (columnMajor != d->labelColumnMajor) exec(new CartesianPlotLegendSetLabelColumnMajorCmd(d, columnMajor, i18n("%1: change column order"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLineSymbolWidth, float, lineSymbolWidth, retransform) void CartesianPlotLegend::setLineSymbolWidth(float width) { Q_D(CartesianPlotLegend); if (width != d->lineSymbolWidth) exec(new CartesianPlotLegendSetLineSymbolWidthCmd(d, width, i18n("%1: change line+symbol width"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetPosition, CartesianPlotLegend::PositionWrapper, position, updatePosition); void CartesianPlotLegend::setPosition(const PositionWrapper& pos) { Q_D(CartesianPlotLegend); if (pos.point!=d->position.point || pos.horizontalPosition!=d->position.horizontalPosition || pos.verticalPosition!=d->position.verticalPosition) exec(new CartesianPlotLegendSetPositionCmd(d, pos, i18n("%1: set position"))); } //Background STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void CartesianPlotLegend::setBackgroundType(PlotArea::BackgroundType type) { Q_D(CartesianPlotLegend); if (type != d->backgroundType) exec(new CartesianPlotLegendSetBackgroundTypeCmd(d, type, i18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void CartesianPlotLegend::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundColorStyle) exec(new CartesianPlotLegendSetBackgroundColorStyleCmd(d, style, i18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void CartesianPlotLegend::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundImageStyle) exec(new CartesianPlotLegendSetBackgroundImageStyleCmd(d, style, i18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void CartesianPlotLegend::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundBrushStyle) exec(new CartesianPlotLegendSetBackgroundBrushStyleCmd(d, style, i18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void CartesianPlotLegend::setBackgroundFirstColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundFirstColor) exec(new CartesianPlotLegendSetBackgroundFirstColorCmd(d, color, i18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void CartesianPlotLegend::setBackgroundSecondColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundSecondColor) exec(new CartesianPlotLegendSetBackgroundSecondColorCmd(d, color, i18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFileName, QString, backgroundFileName, update) void CartesianPlotLegend::setBackgroundFileName(const QString& fileName) { Q_D(CartesianPlotLegend); if (fileName!= d->backgroundFileName) exec(new CartesianPlotLegendSetBackgroundFileNameCmd(d, fileName, i18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundOpacity, float, backgroundOpacity, update) void CartesianPlotLegend::setBackgroundOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->backgroundOpacity) exec(new CartesianPlotLegendSetBackgroundOpacityCmd(d, opacity, i18n("%1: set opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderPen, QPen, borderPen, update) void CartesianPlotLegend::setBorderPen(const QPen &pen) { Q_D(CartesianPlotLegend); if (pen != d->borderPen) exec(new CartesianPlotLegendSetBorderPenCmd(d, pen, i18n("%1: set border style"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderCornerRadius, qreal, borderCornerRadius, update) void CartesianPlotLegend::setBorderCornerRadius(float radius) { Q_D(CartesianPlotLegend); if (radius != d->borderCornerRadius) exec(new CartesianPlotLegendSetBorderCornerRadiusCmd(d, radius, i18n("%1: set border corner radius"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderOpacity, qreal, borderOpacity, update) void CartesianPlotLegend::setBorderOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->borderOpacity) exec(new CartesianPlotLegendSetBorderOpacityCmd(d, opacity, i18n("%1: set border opacity"))); } //Layout STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutTopMargin, float, layoutTopMargin, retransform) void CartesianPlotLegend::setLayoutTopMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutTopMargin) exec(new CartesianPlotLegendSetLayoutTopMarginCmd(d, margin, i18n("%1: set layout top margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutBottomMargin, float, layoutBottomMargin, retransform) void CartesianPlotLegend::setLayoutBottomMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutBottomMargin) exec(new CartesianPlotLegendSetLayoutBottomMarginCmd(d, margin, i18n("%1: set layout bottom margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutLeftMargin, float, layoutLeftMargin, retransform) void CartesianPlotLegend::setLayoutLeftMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutLeftMargin) exec(new CartesianPlotLegendSetLayoutLeftMarginCmd(d, margin, i18n("%1: set layout left margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutRightMargin, float, layoutRightMargin, retransform) void CartesianPlotLegend::setLayoutRightMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutRightMargin) exec(new CartesianPlotLegendSetLayoutRightMarginCmd(d, margin, i18n("%1: set layout right margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, retransform) void CartesianPlotLegend::setLayoutVerticalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutVerticalSpacing) exec(new CartesianPlotLegendSetLayoutVerticalSpacingCmd(d, spacing, i18n("%1: set layout vertical spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, retransform) void CartesianPlotLegend::setLayoutHorizontalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutHorizontalSpacing) exec(new CartesianPlotLegendSetLayoutHorizontalSpacingCmd(d, spacing, i18n("%1: set layout horizontal spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutColumnCount, int, layoutColumnCount, retransform) void CartesianPlotLegend::setLayoutColumnCount(int count) { Q_D(CartesianPlotLegend); if (count != d->layoutColumnCount) exec(new CartesianPlotLegendSetLayoutColumnCountCmd(d, count, i18n("%1: set layout column count"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlotLegend::visibilityChangedSlot() { Q_D(const CartesianPlotLegend); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## CartesianPlotLegendPrivate::CartesianPlotLegendPrivate(CartesianPlotLegend *owner):q(owner), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false) { setAcceptHoverEvents(true); } QString CartesianPlotLegendPrivate::name() const { return q->name(); } QRectF CartesianPlotLegendPrivate::boundingRect() const { return rect; } void CartesianPlotLegendPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } /*! Returns the shape of the CartesianPlotLegend as a QPainterPath in local coordinates */ QPainterPath CartesianPlotLegendPrivate::shape() const { QPainterPath path; if ( qFuzzyIsNull(borderCornerRadius) ) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); return path; } bool CartesianPlotLegendPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the rectangular of the legend. */ void CartesianPlotLegendPrivate::retransform() { if (suppressRetransform) return; prepareGeometryChange(); curvesList.clear(); QVector children = q->m_plot->children(); foreach(XYCurve* curve, children) { if (curve && curve->isVisible()) curvesList.push_back(curve); } int curveCount = curvesList.size(); columnCount = (curveCount= curveCount ) break; curve = curvesList.at(index); if (curve) { if (!curve->isVisible()) continue; w = fm.width(curve->name()); if (w>maxTextWidth) maxTextWidth = w; } } maxColumnTextWidths.append(maxTextWidth); legendWidth += maxTextWidth; } legendWidth += layoutLeftMargin + layoutRightMargin; //margins legendWidth += columnCount*lineSymbolWidth + layoutHorizontalSpacing; //width of the columns without the text legendWidth += (columnCount-1)*2*layoutHorizontalSpacing; //spacings between the columns if (title->isVisible() && !title->text().text.isEmpty()) { float titleWidth = title->graphicsItem()->boundingRect().width(); if (titleWidth > legendWidth) legendWidth = titleWidth; } //determine the height of the legend float legendHeight = layoutTopMargin + layoutBottomMargin; //margins legendHeight += rowCount*h; //height of the rows legendHeight += (rowCount-1)*layoutVerticalSpacing; //spacing between the rows if (title->isVisible() && !title->text().text.isEmpty()) legendHeight += title->graphicsItem()->boundingRect().height(); //legend title rect.setX(-legendWidth/2); rect.setY(-legendHeight/2); rect.setWidth(legendWidth); rect.setHeight(legendHeight); updatePosition(); } /*! calculates the position of the legend, when the position relative to the parent was specified (left, right, etc.) */ void CartesianPlotLegendPrivate::updatePosition() { //position the legend relative to the actual plot size minus small offset //TODO: make the offset dependent on the size of axis ticks. const QRectF parentRect = q->m_plot->plotRect(); float hOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); float vOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (position.horizontalPosition != CartesianPlotLegend::hPositionCustom) { if (position.horizontalPosition == CartesianPlotLegend::hPositionLeft) position.point.setX(parentRect.x() + rect.width()/2 + hOffset); else if (position.horizontalPosition == CartesianPlotLegend::hPositionCenter) position.point.setX(parentRect.x() + parentRect.width()/2); else if (position.horizontalPosition == CartesianPlotLegend::hPositionRight) position.point.setX(parentRect.x() + parentRect.width() - rect.width()/2 - hOffset); } if (position.verticalPosition != CartesianPlotLegend::vPositionCustom) { if (position.verticalPosition == CartesianPlotLegend::vPositionTop) position.point.setY(parentRect.y() + rect.height()/2 + vOffset); else if (position.verticalPosition == CartesianPlotLegend::vPositionCenter) position.point.setY(parentRect.y() + parentRect.height()/2); else if (position.verticalPosition == CartesianPlotLegend::vPositionBottom) position.point.setY(parentRect.y() + parentRect.height() - rect.height()/2 -vOffset); } suppressItemChangeEvent=true; setPos(position.point); suppressItemChangeEvent=false; emit q->positionChanged(position); suppressRetransform = true; title->retransform(); suppressRetransform = false; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the legend. \sa QGraphicsItem::paint(). */ void CartesianPlotLegendPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->save(); //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor:{ painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient:{ QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->drawTiledPixmap(rect,pix); break; case PlotArea::CenterTiled: painter->drawTiledPixmap(rect,pix,QPoint(rect.size().width()/2,rect.size().height()/2)); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } //draw curve's line+symbol and the names int curveCount = curvesList.size(); QFontMetrics fm(labelFont); float h=fm.ascent(); XYCurve* curve; painter->setFont(labelFont); //translate to left upper conner of the bounding rect plus the layout offset and the height of the title painter->translate(-rect.width()/2+layoutLeftMargin, -rect.height()/2+layoutTopMargin); if (title->isVisible() && !title->text().text.isEmpty()) painter->translate(0, title->graphicsItem()->boundingRect().height()); painter->save(); int index; for (int c=0; c= curveCount ) break; curve = curvesList.at(index); //curve's line (painted at the half of the ascent size) if (curve->lineType() != XYCurve::NoLine) { painter->setPen(curve->linePen()); painter->setOpacity(curve->lineOpacity()); painter->drawLine(0, h/2, lineSymbolWidth, h/2); } //error bars if ( (curve->xErrorType() != XYCurve::NoError) || (curve->yErrorType() != XYCurve::NoError) ) { painter->setOpacity(curve->errorBarsOpacity()); painter->setPen(curve->errorBarsPen()); //curve's error bars for x float errorBarsSize = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (curve->symbolsStyle()!=Symbol::NoSymbols && errorBarsSizesymbolsSize()*1.4) errorBarsSize = curve->symbolsSize()*1.4; switch (curve->errorBarsType()) { case XYCurve::ErrorBarsSimple: //horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); break; case XYCurve::ErrorBarsWithEnds: //horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); //caps for the horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2-errorBarsSize/2, h/2+errorBarsSize/4); painter->drawLine(lineSymbolWidth/2+errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2+errorBarsSize/2, h/2+errorBarsSize/4); //caps for the vert. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2-errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2-errorBarsSize/2); painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2+errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2+errorBarsSize/2); break; } } //curve's symbol if (curve->symbolsStyle()!=Symbol::NoSymbols) { painter->setOpacity(curve->symbolsOpacity()); painter->setBrush(curve->symbolsBrush()); painter->setPen(curve->symbolsPen()); QPainterPath path = Symbol::pathFromStyle(curve->symbolsStyle()); QTransform trafo; trafo.scale(curve->symbolsSize(), curve->symbolsSize()); path = trafo.map(path); if (curve->symbolsRotationAngle() != 0) { trafo.reset(); trafo.rotate(curve->symbolsRotationAngle()); path = trafo.map(path); } painter->translate(QPointF(lineSymbolWidth/2, h/2)); painter->drawPath(path); painter->translate(-QPointF(lineSymbolWidth/2, h/2)); } //curve's name painter->setPen(QPen(labelColor)); painter->setOpacity(1.0); painter->drawText(QPoint(lineSymbolWidth+layoutHorizontalSpacing, h), curve->name()); painter->translate(0,layoutVerticalSpacing+h); } //translate to the beginning of the next column painter->restore(); int deltaX = lineSymbolWidth+layoutHorizontalSpacing+maxColumnTextWidths.at(c); //the width of the current columns deltaX += 2*layoutHorizontalSpacing; //spacing between two columns painter->translate(deltaX,0); painter->save(); } painter->restore(); painter->restore(); if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(shape()); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(shape()); } } QVariant CartesianPlotLegendPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = value.toPointF(); tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotLegendPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = pos(); if (point!=position.point) { //position was changed -> set the position related member variables suppressRetransform = true; CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void CartesianPlotLegendPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; q->hovered(); update(); } } void CartesianPlotLegendPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlotLegend::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlotLegend); writer->writeStartElement( "cartesianPlotLegend" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_QCOLOR(d->labelColor); WRITE_QFONT(d->labelFont); writer->writeAttribute( "columnMajor", QString::number(d->labelColumnMajor) ); writer->writeAttribute( "lineSymbolWidth", QString::number(d->lineSymbolWidth) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeEndElement(); //title d->title->save(writer); //background writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeEndElement(); writer->writeEndElement(); // close "cartesianPlotLegend" section } //! Load from XML bool CartesianPlotLegend::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlotLegend); if (!reader->isStartElement() || reader->name() != "cartesianPlotLegend") { reader->raiseError(i18n("no cartesian plot legend element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlotLegend") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_QCOLOR(d->labelColor); READ_QFONT(d->labelFont); str = attribs.value("columnMajor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'columnMajor'")); else d->labelColumnMajor = str.toInt(); str = attribs.value("lineSymbolWidth").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'lineSymbolWidth'")); else d->lineSymbolWidth = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPosition'")); else d->position.horizontalPosition = (CartesianPlotLegend::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPosition'")); else d->position.verticalPosition = (CartesianPlotLegend::VerticalPosition)str.toInt(); } else if (reader->name() == "textLabel") { if (!d->title->load(reader, preview)) { delete d->title; d->title=0; return false; } } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("borderOpacity")); else d->borderOpacity = str.toDouble(); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); str = attribs.value("topMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("topMargin")); else d->layoutTopMargin = str.toDouble(); str = attribs.value("bottomMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("bottomMargin")); else d->layoutBottomMargin = str.toDouble(); str = attribs.value("leftMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("leftMargin")); else d->layoutLeftMargin = str.toDouble(); str = attribs.value("rightMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rightMargin")); else d->layoutRightMargin = str.toDouble(); str = attribs.value("verticalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("verticalSpacing")); else d->layoutVerticalSpacing = str.toDouble(); str = attribs.value("horizontalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("horizontalSpacing")); else d->layoutHorizontalSpacing = str.toDouble(); str = attribs.value("columnCount").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("columnCount")); else d->layoutColumnCount = str.toInt(); } } return true; } void CartesianPlotLegend::loadThemeConfig(const KConfig& config) { KConfigGroup groupLabel = config.group("Label"); this->setLabelColor(groupLabel.readEntry("FontColor", QColor(Qt::white))); const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); this->borderPen().setColor(group.readEntry("BorderColor",(QColor) this->borderPen().color())); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", this->borderCornerRadius())); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); this->borderPen().setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) this->borderPen().style()))); this->borderPen().setWidthF(group.readEntry("BorderWidth", this->borderPen().widthF())); title()->loadThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/Histogram.cpp b/src/backend/worksheet/plots/cartesian/Histogram.cpp index f2abc4a26..aed48b91a 100644 --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -1,1594 +1,1592 @@ /*************************************************************************** File : Histogram.cpp Project : LabPlot Description : Histogram -------------------------------------------------------------------- Copyright : (C) 2016 Anu Mittal (anu22mittal@gmail.com) Copyright : (C) 2016-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class Histogram \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "Histogram.h" #include "HistogramPrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/AbstractCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "tools/ImageTools.h" #include #include #include #include #include -#include -#include extern "C" { #include #include #include } Histogram::Histogram(const QString &name) : WorksheetElement(name), d_ptr(new HistogramPrivate(this)) { init(); } Histogram::Histogram(const QString &name, HistogramPrivate *dd) : WorksheetElement(name), d_ptr(dd) { init(); } void Histogram::init() { Q_D(Histogram); KConfig config; KConfigGroup group = config.group("Histogram"); d->xColumn = NULL; d->histogramType = (Histogram::HistogramType) group.readEntry("histogramType", (int)Histogram::Ordinary); d->binsOption = (Histogram::BinsOption) group.readEntry("binOption", (int)Histogram::Number); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->valuesType = (Histogram::ValuesType) group.readEntry("ValuesType", (int)Histogram::NoValues); d->valuesColumn = NULL; d->valuesPosition = (Histogram::ValuesPosition) group.readEntry("ValuesPosition", (int)Histogram::ValuesAbove); d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (Histogram::FillingPosition) group.readEntry("FillingPosition", (int)Histogram::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); this->initActions(); } void Histogram::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Histogram::visibilityChangedSlot); } QMenu* Histogram::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Histogram::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* Histogram::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(Histogram, SetVisible, bool, swapVisible) void Histogram::setVisible(bool on) { Q_D(Histogram); exec(new HistogramSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool Histogram::isVisible() const { Q_D(const Histogram); return d->isVisible(); } void Histogram::setPrinting(bool on) { Q_D(Histogram); d->m_printing = on; } void Histogram::setHistrogramType(Histogram::HistogramType histogramType) { d_ptr->histogramType = histogramType; DEBUG(histogramType); } Histogram::HistogramType Histogram::getHistrogramType() { return d_ptr->histogramType; } void Histogram::setbinsOption(Histogram::BinsOption binsOption) { d_ptr->histogramData.binsOption = binsOption; } void Histogram::setBinValue(int binValue) { d_ptr->histogramData.binValue= binValue; } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn*, xColumn, xColumn) QString& Histogram::xColumnPath() const { return d_ptr->xColumnPath; } CLASS_SHARED_D_READER_IMPL(Histogram, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::HistogramData, histogramData, histogramData) //values BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn *, valuesColumn, valuesColumn) QString& Histogram::valuesColumnPath() const { return d_ptr->valuesColumnPath; } BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(Histogram, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(Histogram, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(Histogram, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, fillingOpacity, fillingOpacity) double Histogram::getYMaximum() const { return d_ptr->getYMaximum(); } bool Histogram::isSourceDataChangedSinceLastPlot() const { Q_D(const Histogram); return d->sourceDataChangedSinceLastPlot; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(Histogram, SetHistogramData, Histogram::HistogramData, histogramData, recalculate); void Histogram::setHistogramData(const Histogram::HistogramData& histogramData) { Q_D(Histogram); if ((histogramData.binValue != d->histogramData.binValue) || (histogramData.binsOption != d->histogramData.binsOption) ) exec(new HistogramSetHistogramDataCmd(d, histogramData, i18n("%1: set equation"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetXColumn, const AbstractColumn*, xColumn, retransform) void Histogram::setXColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->xColumn) { exec(new HistogramSetXColumnCmd(d, column, i18n("%1: assign x values"))); emit sourceDataChangedSinceLastPlot(); //emit xHistogramDataChanged() in order to notify the plot about the changes emit xHistogramDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::xHistogramDataChanged); connect(column, &AbstractColumn::dataChanged, this, &Histogram::handleSourceDataChanged); //update the curve itself on changes connect(column, &AbstractColumn::dataChanged, this, &Histogram::retransform); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::xColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void Histogram::setLinePen(const QPen &pen) { Q_D(Histogram); if (pen != d->linePen) exec(new HistogramSetLinePenCmd(d, pen, i18n("%1: set line style"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesType, Histogram::ValuesType, valuesType, updateValues) void Histogram::setValuesType(Histogram::ValuesType type) { Q_D(Histogram); if (type != d->valuesType) exec(new HistogramSetValuesTypeCmd(d, type, i18n("%1: set values type"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColumn, const AbstractColumn*, valuesColumn, updateValues) void Histogram::setValuesColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->valuesColumn) { exec(new HistogramSetValuesColumnCmd(d, column, i18n("%1: set values column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::updateValues); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::valuesColumnAboutToBeRemoved); } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPosition, Histogram::ValuesPosition, valuesPosition, updateValues) void Histogram::setValuesPosition(ValuesPosition position) { Q_D(Histogram); if (position != d->valuesPosition) exec(new HistogramSetValuesPositionCmd(d, position, i18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesDistance, qreal, valuesDistance, updateValues) void Histogram::setValuesDistance(qreal distance) { Q_D(Histogram); if (distance != d->valuesDistance) exec(new HistogramSetValuesDistanceCmd(d, distance, i18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void Histogram::setValuesRotationAngle(qreal angle) { Q_D(Histogram); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) exec(new HistogramSetValuesRotationAngleCmd(d, angle, i18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void Histogram::setValuesOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->valuesOpacity) exec(new HistogramSetValuesOpacityCmd(d, opacity, i18n("%1: set values opacity"))); } //TODO: Format, Precision STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPrefix, QString, valuesPrefix, updateValues) void Histogram::setValuesPrefix(const QString& prefix) { Q_D(Histogram); if (prefix!= d->valuesPrefix) exec(new HistogramSetValuesPrefixCmd(d, prefix, i18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesSuffix, QString, valuesSuffix, updateValues) void Histogram::setValuesSuffix(const QString& suffix) { Q_D(Histogram); if (suffix!= d->valuesSuffix) exec(new HistogramSetValuesSuffixCmd(d, suffix, i18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesFont, QFont, valuesFont, updateValues) void Histogram::setValuesFont(const QFont& font) { Q_D(Histogram); if (font!= d->valuesFont) exec(new HistogramSetValuesFontCmd(d, font, i18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColor, QColor, valuesColor, updatePixmap) void Histogram::setValuesColor(const QColor& color) { Q_D(Histogram); if (color != d->valuesColor) exec(new HistogramSetValuesColorCmd(d, color, i18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingPosition, Histogram::FillingPosition, fillingPosition, updateFilling) void Histogram::setFillingPosition(FillingPosition position) { Q_D(Histogram); if (position != d->fillingPosition) exec(new HistogramSetFillingPositionCmd(d, position, i18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void Histogram::setFillingType(PlotArea::BackgroundType type) { Q_D(Histogram); if (type != d->fillingType) exec(new HistogramSetFillingTypeCmd(d, type, i18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void Histogram::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(Histogram); if (style != d->fillingColorStyle) exec(new HistogramSetFillingColorStyleCmd(d, style, i18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void Histogram::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(Histogram); if (style != d->fillingImageStyle) exec(new HistogramSetFillingImageStyleCmd(d, style, i18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void Histogram::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(Histogram); if (style != d->fillingBrushStyle) exec(new HistogramSetFillingBrushStyleCmd(d, style, i18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void Histogram::setFillingFirstColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingFirstColor) exec(new HistogramSetFillingFirstColorCmd(d, color, i18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void Histogram::setFillingSecondColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingSecondColor) exec(new HistogramSetFillingSecondColorCmd(d, color, i18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFileName, QString, fillingFileName, updatePixmap) void Histogram::setFillingFileName(const QString& fileName) { Q_D(Histogram); if (fileName!= d->fillingFileName) exec(new HistogramSetFillingFileNameCmd(d, fileName, i18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void Histogram::setFillingOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->fillingOpacity) exec(new HistogramSetFillingOpacityCmd(d, opacity, i18n("%1: set filling opacity"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void Histogram::retransform() { d_ptr->retransform(); } void Histogram::handleSourceDataChanged() { Q_D(Histogram); d->sourceDataChangedSinceLastPlot = true; emit sourceDataChangedSinceLastPlot(); } //TODO void Histogram::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_UNUSED(verticalRatio); Q_D(const Histogram); //setValuesDistance(d->distance*); QFont font=d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); retransform(); } void Histogram::updateValues() { d_ptr->updateValues(); } void Histogram::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->xColumn) { d->xColumn = 0; d->retransform(); } } void Histogram::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->valuesColumn) { d->valuesColumn = 0; d->updateValues(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Histogram::visibilityChangedSlot() { Q_D(const Histogram); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## HistogramPrivate::HistogramPrivate(Histogram *owner) : m_printing(false), m_hovered(false), m_suppressRecalc(false), m_suppressRetransform(false), m_hoverEffectImageIsDirty(false), m_selectionEffectImageIsDirty(false), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); } QString HistogramPrivate::name() const { return q->name(); } QRectF HistogramPrivate::boundingRect() const { return boundingRectangle; } double HistogramPrivate::getYMaximum() { if (histogram) { double yMaxRange = 0.0; switch(histogramType) { case Histogram::Ordinary: { size_t maxYAddes = gsl_histogram_max_bin(histogram); yMaxRange = gsl_histogram_get(histogram, maxYAddes); break; } case Histogram::Cumulative: { yMaxRange = xColumn->rowCount(); break; } case Histogram::AvgShift: { //TODO } } return yMaxRange; } return -INFINITY; } /*! Returns the shape of the Histogram as a QPainterPath in local coordinates */ QPainterPath HistogramPrivate::shape() const { return curveShape; } void HistogramPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } bool HistogramPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the position of the points to be drawn. Called when the data was changed. Triggers the update of lines, drop lines, symbols etc. */ void HistogramPrivate::retransform() { if (m_suppressRetransform) return; //qDebug()<<"HistogramPrivate::retransform() " << q->name(); symbolPointsLogical.clear(); symbolPointsScene.clear(); connectedPointsLogical.clear(); if (NULL == xColumn) { linePath = QPainterPath(); valuesPath = QPainterPath(); // dropLinePath = QPainterPath(); recalcShapeAndBoundingRect(); return; } int startRow = 0; int endRow = xColumn->rowCount() - 1; QPointF tempPoint; AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); //take over only valid and non masked points. for (int row = startRow; row <= endRow; row++ ) { if (xColumn->isValid(row) && !xColumn->isMasked(row)) { switch(xColMode) { case AbstractColumn::Numeric: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::Integer: //TODO case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); } else { if (connectedPointsLogical.size()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } //calculate the scene coordinates const AbstractPlot* plot = dynamic_cast(q->parentAspect()); if (!plot) return; const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); Q_ASSERT(cSystem); visiblePoints = std::vector(symbolPointsLogical.count(), false); cSystem->mapLogicalToScene(symbolPointsLogical, symbolPointsScene, visiblePoints); m_suppressRecalc = true; updateLines(); updateValues(); m_suppressRecalc = false; } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. */ void HistogramPrivate::updateLines() { linePath = QPainterPath(); lines.clear(); const int count=symbolPointsLogical.count(); //nothing to do, if no data points available if (count<=1) { recalcShapeAndBoundingRect(); return; } int startRow = 0; int endRow = xColumn->rowCount() - 1; QPointF tempPoint,tempPoint1; double xAxisMin = xColumn->minimum(); double xAxisMax = xColumn->maximum(); switch (histogramData.binsOption) { case Histogram::Number: bins = (size_t)histogramData.binValue; break; case Histogram::SquareRoot: bins = (size_t)sqrt(histogramData.binValue); break; case Histogram::RiceRule: bins = (size_t)2*cbrt(histogramData.binValue); break; case Histogram::Width: bins = (size_t) (xAxisMax-xAxisMin)/histogramData.binValue; break; case Histogram::SturgisRule: bins =(size_t) 1 + 3.33*log(histogramData.binValue); break; } double width = (xAxisMax-xAxisMin)/bins; histogram = gsl_histogram_alloc (bins); // demo- number of bins gsl_histogram_set_ranges_uniform (histogram, xAxisMin,xAxisMax+1); //checking height of each column /*for(int i=0;i < bins; ++i) { qDebug() <isValid(row) && !xColumn->isMasked(row) ) gsl_histogram_increment(histogram,xColumn->valueAt(row)); } for(size_t i=0; i < bins; ++i) { tempPoint.setX(xAxisMin); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(gsl_histogram_get(histogram,i)); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin); tempPoint.setY(gsl_histogram_get(histogram,i)); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(gsl_histogram_get(histogram,i)); lines.append(QLineF(tempPoint,tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(gsl_histogram_get(histogram,i)); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); xAxisMin+= width; } break; } case Histogram::Cumulative: { double point =0.0; for (int row = startRow; row <= endRow; row++ ) { if ( xColumn->isValid(row) && !xColumn->isMasked(row)) gsl_histogram_increment(histogram,xColumn->valueAt(row)); } for(size_t i=0; i < bins; ++i) { point+= gsl_histogram_get(histogram,i); tempPoint.setX(xAxisMin); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(point); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin); tempPoint.setY(point); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(point); lines.append(QLineF(tempPoint,tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(point); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); xAxisMin+= width; } break; } case Histogram::AvgShift: { //TODO break; } } //calculate the lines connecting the data points for (int i = 0; i(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); lines = cSystem->mapLogicalToScene(lines); //new line path for (const auto& line: lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } updateFilling(); recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void HistogramPrivate::updateValues() { valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == Histogram::NoValues) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot if (valuesType == Histogram::ValuesY || valuesType == Histogram::ValuesYBracketed) { switch(histogramType) { case Histogram::Ordinary: for(size_t i=0; irowCount()); const AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::Numeric: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; case AbstractColumn::Integer: case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h=fm.ascent(); double xAxisMin=xColumn->minimum(); double xAxisMax=xColumn->maximum(); double width = (xAxisMax-xAxisMin)/bins; switch(valuesPosition) { case Histogram::ValuesAbove: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() -w/2 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y() - valuesDistance ); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesUnder: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() -w/2+xAxisMin ); tempPoint.setY( symbolPointsScene.at(i).y() + valuesDistance + h/2); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesLeft: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() - valuesDistance - w - 1 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y()); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesRight: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() + valuesDistance - 1 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y() ); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; } QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle!=0) trafo.rotate( -valuesRotationAngle ); valuesPath.addPath(trafo.map(path)); } recalcShapeAndBoundingRect(); } void HistogramPrivate::updateFilling() { fillPolygons.clear(); if (fillingPosition==Histogram::NoFilling) { recalcShapeAndBoundingRect(); return; } QVector fillLines; const CartesianPlot* plot = dynamic_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); //if there're no interpolation lines available (Histogram::NoLine selected), create line-interpolation, //use already available lines otherwise. if (lines.size()) fillLines = lines; else { for (int i=0; imapLogicalToScene(fillLines); } //no lines available (no points), nothing to do if (!fillLines.size()) return; //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //starting point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//first point of the curve, may not be visible currently QPointF edge; float xEnd=0, yEnd=0; if (fillingPosition == Histogram::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == Histogram::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == Histogram::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax()>0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax()>0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == Histogram::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i=0; icheck whether we have a break in between. bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon add it to the polygon list and start a new polygon if (fillingPosition==Histogram::FillingAbove || fillingPosition==Histogram::FillingBelow || fillingPosition==Histogram::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2!=end) pol << end; //close the last polygon if (fillingPosition==Histogram::FillingAbove || fillingPosition==Histogram::FillingBelow || fillingPosition==Histogram::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void HistogramPrivate::recalcShapeAndBoundingRect() { //if (m_suppressRecalc) // return; prepareGeometryChange(); curveShape = QPainterPath(); curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (valuesType != Histogram::NoValues) curveShape.addPath(valuesPath); boundingRectangle = curveShape.boundingRect(); foreach(const QPolygonF& pol, fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void HistogramPrivate::draw(QPainter *painter) { //drawing line painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); //draw filling if (fillingPosition != Histogram::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw values if (valuesType != Histogram::NoValues) { painter->setOpacity(valuesOpacity); painter->setPen(valuesColor); painter->setBrush(Qt::SolidPattern); drawValues(painter); } } void HistogramPrivate::updatePixmap() { QPixmap pixmap(boundingRectangle.width(), boundingRectangle.height()); if (boundingRectangle.width()==0 || boundingRectangle.width()==0) { m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; return; } pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void HistogramPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); draw(painter); if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Shadow)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Highlight)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); return; } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which us much faster (factor 10) */ void HistogramPrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i=0; idrawPath(trafo.map(path)); } } void HistogramPrivate::drawFilling(QPainter* painter) { foreach(const QPolygonF& pol, fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0,0),pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2,-pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor,fillingBrushStyle)); painter->drawPolygon(pol); } } void HistogramPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { m_hovered = true; q->hovered(); update(); } } void HistogramPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; q->unhovered(); update(); } } void HistogramPrivate::recalculate() { emit (q->HistogramdataChanged()); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Histogram::save(QXmlStreamWriter* writer) const { Q_D(const Histogram); writer->writeStartElement( "Histogram" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->xColumn, xColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); WRITE_QPEN(d->linePen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //write Histogram specific information writer->writeStartElement( "typeChanged" ); writer->writeAttribute( "Histogramtype", QString::number(d->histogramData.type) ); writer->writeAttribute( "BinsOption", QString::number(d->histogramData.binsOption) ); writer->writeAttribute( "binValue", QString::number(d->histogramData.binValue)); writer->writeEndElement(); if (d->xColumn) d->xColumn->save(writer); writer->writeEndElement(); //close "Histogram" section } //! Load from XML bool Histogram::load(XmlStreamReader* reader, bool preview) { Q_D(Histogram); if(!reader->isStartElement() || reader->name() != "Histogram") { reader->raiseError(i18n("no histogram element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "Histogram") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(xColumn); str = attribs.value("visible").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "typeChanged") { attribs = reader->attributes(); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->histogramType = (Histogram::HistogramType)str.toInt(); str = attribs.value("BinsOption").toString(); d->binsOption = (Histogram::BinsOption)str.toInt(); str = attribs.value("binValue").toString(); d->histogramData.binValue = str.toInt(); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->valuesType = (Histogram::ValuesType)str.toInt(); READ_COLUMN(valuesColumn); str = attribs.value("position").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->valuesPosition = (Histogram::ValuesPosition)str.toInt(); str = attribs.value("distance").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'distance'")); else d->valuesDistance = str.toDouble(); str = attribs.value("rotation").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->valuesRotationAngle = str.toDouble(); str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->valuesOpacity = str.toDouble(); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->valuesOpacity = str.toDouble(); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); str = attribs.value("position").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("position")); else d->fillingPosition = Histogram::FillingPosition(str.toInt()); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->fillingType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->fillingColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->fillingImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->fillingBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->fillingSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->fillingFileName = str; str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->fillingOpacity = str.toDouble(); } else if(reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } d->xColumn = column; } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp index e223dfdd8..bac2d42a3 100644 --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -1,2485 +1,2483 @@ /*************************************************************************** File : XYCurve.cpp Project : LabPlot Description : A xy-curve -------------------------------------------------------------------- Copyright : (C) 2010-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYCurve \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "XYCurve.h" #include "XYCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "tools/ImageTools.h" #include #include #include #include #include #include -#include -#include extern "C" { #include #include } XYCurve::XYCurve(const QString &name) : WorksheetElement(name), d_ptr(new XYCurvePrivate(this)) { init(); } XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd) : WorksheetElement(name), d_ptr(dd) { init(); } XYCurve::~XYCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYCurve::init() { Q_D(XYCurve); KConfig config; KConfigGroup group = config.group("XYCurve"); d->dataSourceType = (XYCurve::DataSourceType) group.readEntry("DataSourceType", (int)XYCurve::DataSourceSpreadsheet); d->xColumn = NULL; d->yColumn = NULL; d->dataSourceCurve = NULL; d->lineType = (XYCurve::LineType) group.readEntry("LineType", (int)XYCurve::Line); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->dropLineType = (XYCurve::DropLineType) group.readEntry("DropLineType", (int)XYCurve::NoLine); d->dropLinePen.setStyle( (Qt::PenStyle) group.readEntry("DropLineStyle", (int)Qt::SolidLine) ); d->dropLinePen.setColor( group.readEntry("DropLineColor", QColor(Qt::black))); d->dropLinePen.setWidthF( group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->dropLineOpacity = group.readEntry("DropLineOpacity", 1.0); d->symbolsStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::NoSymbols); d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->symbolsRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolsOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolsBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolsBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::black)) ); d->symbolsPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolsPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); d->valuesType = (XYCurve::ValuesType) group.readEntry("ValuesType", (int)XYCurve::NoValues); d->valuesColumn = NULL; d->valuesPosition = (XYCurve::ValuesPosition) group.readEntry("ValuesPosition", (int)XYCurve::ValuesAbove); d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (XYCurve::FillingPosition) group.readEntry("FillingPosition", (int)XYCurve::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); d->xErrorType = (XYCurve::ErrorType) group.readEntry("XErrorType", (int)XYCurve::NoError); d->xErrorPlusColumn = NULL; d->xErrorMinusColumn = NULL; d->yErrorType = (XYCurve::ErrorType) group.readEntry("YErrorType", (int)XYCurve::NoError); d->yErrorPlusColumn = NULL; d->yErrorMinusColumn = NULL; d->errorBarsType = (XYCurve::ErrorBarsType) group.readEntry("ErrorBarsType", (int)XYCurve::ErrorBarsSimple); d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Point) ); d->errorBarsPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine) ); d->errorBarsPen.setColor( group.readEntry("ErrorBarsColor", QColor(Qt::black)) ); d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->errorBarsOpacity = group.readEntry("ErrorBarsOpacity", 1.0); this->initActions(); } void XYCurve::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); navigateToAction = new QAction(QIcon::fromTheme("go-next-view"), "", this); connect(navigateToAction, SIGNAL(triggered()), this, SLOT(navigateTo())); } QMenu* XYCurve::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //"data analysis" menu CartesianPlot* plot = dynamic_cast(parentAspect()); menu->insertMenu(visibilityAction, plot->analysisMenu()); //"Navigate to spreadsheet"-action, show only if x- or y-columns have data from a spreadsheet AbstractAspect* parentSpreadsheet = 0; if (xColumn() && dynamic_cast(xColumn()->parentAspect()) ) parentSpreadsheet = xColumn()->parentAspect(); else if (yColumn() && dynamic_cast(yColumn()->parentAspect()) ) parentSpreadsheet = yColumn()->parentAspect(); if (parentSpreadsheet) { navigateToAction->setText(i18n("Navigate to \"%1\"", parentSpreadsheet->name())); navigateToAction->setData(parentSpreadsheet->path()); menu->insertAction(visibilityAction, navigateToAction); menu->insertSeparator(visibilityAction); } return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon XYCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* XYCurve::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(XYCurve, SetVisible, bool, swapVisible) void XYCurve::setVisible(bool on) { Q_D(XYCurve); exec(new XYCurveSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); } bool XYCurve::isVisible() const { Q_D(const XYCurve); return d->isVisible(); } void XYCurve::setPrinting(bool on) { Q_D(XYCurve); d->setPrinting(on); } //############################################################################## //########################## getter methods ################################## //############################################################################## //data source BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::DataSourceType, dataSourceType, dataSourceType) BASIC_SHARED_D_READER_IMPL(XYCurve, const XYCurve*, dataSourceCurve, dataSourceCurve) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xColumn, xColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yColumn, yColumn) const QString& XYCurve::dataSourceCurvePath() const { return d_ptr->dataSourceCurvePath; } const QString& XYCurve::xColumnPath() const { return d_ptr->xColumnPath; } const QString& XYCurve::yColumnPath() const { return d_ptr->yColumnPath; } //line BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::LineType, lineType, lineType) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineSkipGaps, lineSkipGaps) BASIC_SHARED_D_READER_IMPL(XYCurve, int, lineInterpolationPointsCount, lineInterpolationPointsCount) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, lineOpacity, lineOpacity) //droplines BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::DropLineType, dropLineType, dropLineType) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, dropLinePen, dropLinePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, dropLineOpacity, dropLineOpacity) //symbols BASIC_SHARED_D_READER_IMPL(XYCurve, Symbol::Style, symbolsStyle, symbolsStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsOpacity, symbolsOpacity) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsRotationAngle, symbolsRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsSize, symbolsSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QBrush, symbolsBrush, symbolsBrush) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, symbolsPen, symbolsPen) //values BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn *, valuesColumn, valuesColumn) const QString& XYCurve::valuesColumnPath() const { return d_ptr->valuesColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, fillingOpacity, fillingOpacity) //error bars BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, xErrorType, xErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorPlusColumn, xErrorPlusColumn) const QString& XYCurve::xErrorPlusColumnPath() const { return d_ptr->xErrorPlusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorMinusColumn, xErrorMinusColumn) const QString& XYCurve::xErrorMinusColumnPath() const { return d_ptr->xErrorMinusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, yErrorType, yErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorPlusColumn, yErrorPlusColumn) const QString& XYCurve::yErrorPlusColumnPath() const { return d_ptr->yErrorPlusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorMinusColumn, yErrorMinusColumn) const QString& XYCurve::yErrorMinusColumnPath() const { return d_ptr->yErrorMinusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorBarsType, errorBarsType, errorBarsType) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsCapSize, errorBarsCapSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, errorBarsPen, errorBarsPen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsOpacity, errorBarsOpacity) /*! * return \c true if the data in the source columns (x, y) used in the analysis curves, \c false otherwise */ bool XYCurve::isSourceDataChangedSinceLastRecalc() const { Q_D(const XYCurve); return d->sourceDataChangedSinceLastRecalc; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## //data source STD_SETTER_CMD_IMPL_S(XYCurve, SetDataSourceType, XYCurve::DataSourceType, dataSourceType) void XYCurve::setDataSourceType(DataSourceType type) { Q_D(XYCurve); if (type != d->dataSourceType) exec(new XYCurveSetDataSourceTypeCmd(d, type, i18n("%1: data source type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDataSourceCurve, const XYCurve*, dataSourceCurve, retransform) void XYCurve::setDataSourceCurve(const XYCurve* curve) { Q_D(XYCurve); if (curve != d->dataSourceCurve) { exec(new XYCurveSetDataSourceCurveCmd(d, curve, i18n("%1: data source curve changed"))); handleSourceDataChanged(); //handle the changes when different columns were provided for the source curve connect(curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //handle the changes when the data inside of the source curve columns connect(curve, SIGNAL(xDataChanged()), this, SLOT(handleSourceDataChanged())); connect(curve, SIGNAL(yDataChanged()), this, SLOT(handleSourceDataChanged())); //TODO: add disconnect in the undo-function } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXColumn, const AbstractColumn*, xColumn, retransform) void XYCurve::setXColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xColumn) { exec(new XYCurveSetXColumnCmd(d, column, i18n("%1: x-data source changed"))); //emit xDataChanged() in order to notify the plot about the changes emit xDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SIGNAL(xDataChanged())); //update the curve itself on changes connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransform())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xColumnAboutToBeRemoved(const AbstractAspect*))); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYColumn, const AbstractColumn*, yColumn, retransform) void XYCurve::setYColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yColumn) { exec(new XYCurveSetYColumnCmd(d, column, i18n("%1: y-data source changed"))); //emit yDataChanged() in order to notify the plot about the changes emit yDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SIGNAL(yDataChanged())); //update the curve itself on changes connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransform())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yColumnAboutToBeRemoved(const AbstractAspect*))); //TODO: add disconnect in the undo-function } } } //Line STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineType, XYCurve::LineType, lineType, updateLines) void XYCurve::setLineType(LineType type) { Q_D(XYCurve); if (type != d->lineType) exec(new XYCurveSetLineTypeCmd(d, type, i18n("%1: line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineSkipGaps, bool, lineSkipGaps, updateLines) void XYCurve::setLineSkipGaps(bool skip) { Q_D(XYCurve); if (skip != d->lineSkipGaps) exec(new XYCurveSetLineSkipGapsCmd(d, skip, i18n("%1: set skip line gaps"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineInterpolationPointsCount, int, lineInterpolationPointsCount, updateLines) void XYCurve::setLineInterpolationPointsCount(int count) { Q_D(XYCurve); if (count != d->lineInterpolationPointsCount) exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, i18n("%1: set the number of interpolation points"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void XYCurve::setLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->linePen) exec(new XYCurveSetLinePenCmd(d, pen, i18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineOpacity, qreal, lineOpacity, updatePixmap); void XYCurve::setLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->lineOpacity) exec(new XYCurveSetLineOpacityCmd(d, opacity, i18n("%1: set line opacity"))); } //Drop lines STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineType, XYCurve::DropLineType, dropLineType, updateDropLines) void XYCurve::setDropLineType(DropLineType type) { Q_D(XYCurve); if (type != d->dropLineType) exec(new XYCurveSetDropLineTypeCmd(d, type, i18n("%1: drop line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLinePen, QPen, dropLinePen, recalcShapeAndBoundingRect) void XYCurve::setDropLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->dropLinePen) exec(new XYCurveSetDropLinePenCmd(d, pen, i18n("%1: set drop line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineOpacity, qreal, dropLineOpacity, updatePixmap) void XYCurve::setDropLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->dropLineOpacity) exec(new XYCurveSetDropLineOpacityCmd(d, opacity, i18n("%1: set drop line opacity"))); } // Symbols-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, updateSymbols) void XYCurve::setSymbolsStyle(Symbol::Style style) { Q_D(XYCurve); if (style != d->symbolsStyle) exec(new XYCurveSetSymbolsStyleCmd(d, style, i18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsSize, qreal, symbolsSize, updateSymbols) void XYCurve::setSymbolsSize(qreal size) { Q_D(XYCurve); if (!qFuzzyCompare(1 + size, 1 + d->symbolsSize)) exec(new XYCurveSetSymbolsSizeCmd(d, size, i18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsRotationAngle, qreal, symbolsRotationAngle, updateSymbols) void XYCurve::setSymbolsRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->symbolsRotationAngle)) exec(new XYCurveSetSymbolsRotationAngleCmd(d, angle, i18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsBrush, QBrush, symbolsBrush, updatePixmap) void XYCurve::setSymbolsBrush(const QBrush &brush) { Q_D(XYCurve); if (brush != d->symbolsBrush) exec(new XYCurveSetSymbolsBrushCmd(d, brush, i18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsPen, QPen, symbolsPen, updateSymbols) void XYCurve::setSymbolsPen(const QPen &pen) { Q_D(XYCurve); if (pen != d->symbolsPen) exec(new XYCurveSetSymbolsPenCmd(d, pen, i18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsOpacity, qreal, symbolsOpacity, updatePixmap) void XYCurve::setSymbolsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->symbolsOpacity) exec(new XYCurveSetSymbolsOpacityCmd(d, opacity, i18n("%1: set symbols opacity"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesType, XYCurve::ValuesType, valuesType, updateValues) void XYCurve::setValuesType(XYCurve::ValuesType type) { Q_D(XYCurve); if (type != d->valuesType) exec(new XYCurveSetValuesTypeCmd(d, type, i18n("%1: set values type"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColumn, const AbstractColumn*, valuesColumn, updateValues) void XYCurve::setValuesColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->valuesColumn) { exec(new XYCurveSetValuesColumnCmd(d, column, i18n("%1: set values column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateValues())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(valuesColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPosition, XYCurve::ValuesPosition, valuesPosition, updateValues) void XYCurve::setValuesPosition(ValuesPosition position) { Q_D(XYCurve); if (position != d->valuesPosition) exec(new XYCurveSetValuesPositionCmd(d, position, i18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDistance, qreal, valuesDistance, updateValues) void XYCurve::setValuesDistance(qreal distance) { Q_D(XYCurve); if (distance != d->valuesDistance) exec(new XYCurveSetValuesDistanceCmd(d, distance, i18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void XYCurve::setValuesRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) exec(new XYCurveSetValuesRotationAngleCmd(d, angle, i18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void XYCurve::setValuesOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->valuesOpacity) exec(new XYCurveSetValuesOpacityCmd(d, opacity, i18n("%1: set values opacity"))); } //TODO: Format, Precision STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrefix, QString, valuesPrefix, updateValues) void XYCurve::setValuesPrefix(const QString& prefix) { Q_D(XYCurve); if (prefix != d->valuesPrefix) exec(new XYCurveSetValuesPrefixCmd(d, prefix, i18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesSuffix, QString, valuesSuffix, updateValues) void XYCurve::setValuesSuffix(const QString& suffix) { Q_D(XYCurve); if (suffix != d->valuesSuffix) exec(new XYCurveSetValuesSuffixCmd(d, suffix, i18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesFont, QFont, valuesFont, updateValues) void XYCurve::setValuesFont(const QFont& font) { Q_D(XYCurve); if (font != d->valuesFont) exec(new XYCurveSetValuesFontCmd(d, font, i18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColor, QColor, valuesColor, updatePixmap) void XYCurve::setValuesColor(const QColor& color) { Q_D(XYCurve); if (color != d->valuesColor) exec(new XYCurveSetValuesColorCmd(d, color, i18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingPosition, XYCurve::FillingPosition, fillingPosition, updateFilling) void XYCurve::setFillingPosition(FillingPosition position) { Q_D(XYCurve); if (position != d->fillingPosition) exec(new XYCurveSetFillingPositionCmd(d, position, i18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void XYCurve::setFillingType(PlotArea::BackgroundType type) { Q_D(XYCurve); if (type != d->fillingType) exec(new XYCurveSetFillingTypeCmd(d, type, i18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void XYCurve::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(XYCurve); if (style != d->fillingColorStyle) exec(new XYCurveSetFillingColorStyleCmd(d, style, i18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void XYCurve::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(XYCurve); if (style != d->fillingImageStyle) exec(new XYCurveSetFillingImageStyleCmd(d, style, i18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void XYCurve::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(XYCurve); if (style != d->fillingBrushStyle) exec(new XYCurveSetFillingBrushStyleCmd(d, style, i18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void XYCurve::setFillingFirstColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingFirstColor) exec(new XYCurveSetFillingFirstColorCmd(d, color, i18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void XYCurve::setFillingSecondColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingSecondColor) exec(new XYCurveSetFillingSecondColorCmd(d, color, i18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFileName, QString, fillingFileName, updatePixmap) void XYCurve::setFillingFileName(const QString& fileName) { Q_D(XYCurve); if (fileName != d->fillingFileName) exec(new XYCurveSetFillingFileNameCmd(d, fileName, i18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void XYCurve::setFillingOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->fillingOpacity) exec(new XYCurveSetFillingOpacityCmd(d, opacity, i18n("%1: set filling opacity"))); } //Error bars STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorType, XYCurve::ErrorType, xErrorType, updateErrorBars) void XYCurve::setXErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->xErrorType) exec(new XYCurveSetXErrorTypeCmd(d, type, i18n("%1: x-error type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorPlusColumn, const AbstractColumn*, xErrorPlusColumn, updateErrorBars) void XYCurve::setXErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorPlusColumn) { exec(new XYCurveSetXErrorPlusColumnCmd(d, column, i18n("%1: set x-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xErrorPlusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorMinusColumn, const AbstractColumn*, xErrorMinusColumn, updateErrorBars) void XYCurve::setXErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorMinusColumn) { exec(new XYCurveSetXErrorMinusColumnCmd(d, column, i18n("%1: set x-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xErrorMinusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorType, XYCurve::ErrorType, yErrorType, updateErrorBars) void XYCurve::setYErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->yErrorType) exec(new XYCurveSetYErrorTypeCmd(d, type, i18n("%1: y-error type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorPlusColumn, const AbstractColumn*, yErrorPlusColumn, updateErrorBars) void XYCurve::setYErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorPlusColumn) { exec(new XYCurveSetYErrorPlusColumnCmd(d, column, i18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yErrorPlusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorMinusColumn, const AbstractColumn*, yErrorMinusColumn, updateErrorBars) void XYCurve::setYErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorMinusColumn) { exec(new XYCurveSetYErrorMinusColumnCmd(d, column, i18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yErrorMinusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsCapSize, qreal, errorBarsCapSize, updateErrorBars) void XYCurve::setErrorBarsCapSize(qreal size) { Q_D(XYCurve); if (size != d->errorBarsCapSize) exec(new XYCurveSetErrorBarsCapSizeCmd(d, size, i18n("%1: set error bar cap size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsType, XYCurve::ErrorBarsType, errorBarsType, updateErrorBars) void XYCurve::setErrorBarsType(ErrorBarsType type) { Q_D(XYCurve); if (type != d->errorBarsType) exec(new XYCurveSetErrorBarsTypeCmd(d, type, i18n("%1: error bar type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsPen, QPen, errorBarsPen, recalcShapeAndBoundingRect) void XYCurve::setErrorBarsPen(const QPen& pen) { Q_D(XYCurve); if (pen != d->errorBarsPen) exec(new XYCurveSetErrorBarsPenCmd(d, pen, i18n("%1: set error bar style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsOpacity, qreal, errorBarsOpacity, updatePixmap) void XYCurve::setErrorBarsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->errorBarsOpacity) exec(new XYCurveSetErrorBarsOpacityCmd(d, opacity, i18n("%1: set error bar opacity"))); } void XYCurve::suppressRetransform(bool b) { Q_D(XYCurve); d->suppressRetransform(b); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYCurve::retransform() { Q_D(XYCurve); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 0); d->retransform(); RESET_CURSOR; } void XYCurve::updateValues() { Q_D(XYCurve); d->updateValues(); } void XYCurve::updateErrorBars() { Q_D(XYCurve); d->updateErrorBars(); } //TODO void XYCurve::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_D(const XYCurve); setSymbolsSize(d->symbolsSize * horizontalRatio); QPen pen = d->symbolsPen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setSymbolsPen(pen); pen = d->linePen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setLinePen(pen); //setValuesDistance(d->distance*); QFont font=d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); } void XYCurve::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xColumn) { d->xColumn = 0; d->retransform(); } } void XYCurve::yColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yColumn) { d->yColumn = 0; d->retransform(); } } void XYCurve::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->valuesColumn) { d->valuesColumn = 0; d->updateValues(); } } void XYCurve::xErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xErrorPlusColumn) { d->xErrorPlusColumn = 0; d->updateErrorBars(); } } void XYCurve::xErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xErrorMinusColumn) { d->xErrorMinusColumn = 0; d->updateErrorBars(); } } void XYCurve::yErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yErrorPlusColumn) { d->yErrorPlusColumn = 0; d->updateErrorBars(); } } void XYCurve::yErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yErrorMinusColumn) { d->yErrorMinusColumn = 0; d->updateErrorBars(); } } void XYCurve::handleSourceDataChanged() { Q_D(XYCurve); d->sourceDataChangedSinceLastRecalc = true; emit sourceDataChanged(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void XYCurve::visibilityChanged() { Q_D(const XYCurve); this->setVisible(!d->isVisible()); } void XYCurve::navigateTo() { project()->navigateTo(navigateToAction->data().toString()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYCurvePrivate::XYCurvePrivate(XYCurve *owner) : sourceDataChangedSinceLastRecalc(false), q(owner), m_hoverEffectImageIsDirty(false), m_selectionEffectImageIsDirty(false), m_hovered(false), m_suppressRecalc(false), m_suppressRetransform(false), m_printing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); } QString XYCurvePrivate::name() const { return q->name(); } QRectF XYCurvePrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath XYCurvePrivate::shape() const { return curveShape; } void XYCurvePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } bool XYCurvePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the position of the points to be drawn. Called when the data was changed. Triggers the update of lines, drop lines, symbols etc. */ void XYCurvePrivate::retransform() { if (m_suppressRetransform) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform()"); #endif symbolPointsLogical.clear(); symbolPointsScene.clear(); connectedPointsLogical.clear(); if ( (NULL == xColumn) || (NULL == yColumn) ) { linePath = QPainterPath(); dropLinePath = QPainterPath(); symbolsPath = QPainterPath(); valuesPath = QPainterPath(); errorBarsPath = QPainterPath(); recalcShapeAndBoundingRect(); return; } int startRow = 0; int endRow = xColumn->rowCount() - 1; QPointF tempPoint; AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); AbstractColumn::ColumnMode yColMode = yColumn->columnMode(); //take over only valid and non masked points. for (int row = startRow; row <= endRow; row++) { if ( xColumn->isValid(row) && yColumn->isValid(row) && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } switch (yColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: tempPoint.setY(yColumn->valueAt(row)); break; case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); } else { if (!connectedPointsLogical.empty()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } //calculate the scene coordinates const AbstractPlot* plot = dynamic_cast(q->parentAspect()); if (!plot) return; const CartesianCoordinateSystem *cSystem = dynamic_cast(plot->coordinateSystem()); Q_ASSERT(cSystem); visiblePoints = std::vector(symbolPointsLogical.count(), false); { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); #endif cSystem->mapLogicalToScene(symbolPointsLogical, symbolPointsScene, visiblePoints); } m_suppressRecalc = true; updateLines(); updateDropLines(); updateSymbols(); updateValues(); m_suppressRecalc = false; updateErrorBars(); } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. */ void XYCurvePrivate::updateLines() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines()"); #endif linePath = QPainterPath(); lines.clear(); if (lineType == XYCurve::NoLine) { updateFilling(); recalcShapeAndBoundingRect(); return; } int count = symbolPointsLogical.count(); if (count <= 1) { //nothing to do, if no data points available recalcShapeAndBoundingRect(); return; } //calculate the lines connecting the data points { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate the lines connecting the data points"); #endif QPointF tempPoint1, tempPoint2; QPointF curPoint, nextPoint; switch (lineType) { case XYCurve::NoLine: break; case XYCurve::Line: for (int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); } break; case XYCurve::StartHorizontal: for (int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(nextPoint.x(), curPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, nextPoint)); } break; case XYCurve::StartVertical: for (int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x(), nextPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1,nextPoint)); } break; case XYCurve::MidpointHorizontal: for (int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, curPoint.y()); tempPoint2 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, nextPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, tempPoint2)); lines.append(QLineF(tempPoint2, nextPoint)); } break; case XYCurve::MidpointVertical: for (int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); tempPoint2 = QPointF(nextPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, tempPoint2)); lines.append(QLineF(tempPoint2, nextPoint)); } break; case XYCurve::Segments2: { int skip=0; for (int i = 0; i < count - 1; i++) { if (skip != 1) { if (!lineSkipGaps && !connectedPointsLogical[i]) { skip = 0; continue; } lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); skip++; } else skip = 0; } break; } case XYCurve::Segments3: { int skip = 0; for (int i = 0; i < count - 1; i++) { if (skip != 2) { if (!lineSkipGaps && !connectedPointsLogical[i]) { skip = 0; continue; } lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); skip++; } else skip = 0; } break; } case XYCurve::SplineCubicNatural: case XYCurve::SplineCubicPeriodic: case XYCurve::SplineAkimaNatural: case XYCurve::SplineAkimaPeriodic: { //TODO: optimize! try to omit the copying from the column to the arrays of doubles. //TODO: forward the error message to the UI. gsl_interp_accel *acc = gsl_interp_accel_alloc(); gsl_spline *spline = 0; double* x = new double[count]; double* y = new double[count]; for (int i = 0; i < count; i++) { x[i] = symbolPointsLogical.at(i).x(); y[i] = symbolPointsLogical.at(i).y(); } gsl_set_error_handler_off(); if (lineType == XYCurve::SplineCubicNatural) spline = gsl_spline_alloc(gsl_interp_cspline, count); else if (lineType == XYCurve::SplineCubicPeriodic) spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); else if (lineType == XYCurve::SplineAkimaNatural) spline = gsl_spline_alloc(gsl_interp_akima, count); else if (lineType == XYCurve::SplineAkimaPeriodic) spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); if (!spline) { QString msg; if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); else msg = i18n("Couldn't initialize spline function"); QDEBUG(msg); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_interp_accel_free (acc); return; } int status = gsl_spline_init (spline, x, y, count); if (status) { //TODO: check in gsl/interp.c when GSL_EINVAL is thrown QString gslError; if (status == GSL_EINVAL) gslError = "x values must be monotonically increasing."; else gslError = gsl_strerror (status); QDEBUG("Error in spline calculation. " << gslError); recalcShapeAndBoundingRect(); return; } //create interpolating points std::vector xinterp, yinterp; double step; double xi, yi, x1, x2; for (int i = 0; i < count - 1; i++) { x1 = x[i]; x2 = x[i+1]; step=fabs(x2 - x1)/(lineInterpolationPointsCount + 1); for (xi = x1; xi < x2; xi += step) { yi = gsl_spline_eval(spline, xi, acc); xinterp.push_back(xi); yinterp.push_back(yi); } } for (unsigned int i = 0; i < xinterp.size() - 1; i++) lines.append(QLineF(xinterp[i], yinterp[i], xinterp[i+1], yinterp[i+1])); lines.append(QLineF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1], x[count-1], y[count-1])); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); break; } } } //map the lines to scene coordinates const CartesianPlot* plot = dynamic_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), map lines to scene coordinates"); #endif lines = cSystem->mapLogicalToScene(lines); } { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate new line path"); #endif //new line path foreach (const QLineF& line, lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } } updateFilling(); recalcShapeAndBoundingRect(); } /*! recalculates the painter path for the drop lines. Called each time when the type of the drop lines is changed. */ void XYCurvePrivate::updateDropLines() { dropLinePath = QPainterPath(); if (dropLineType == XYCurve::NoDropLine) { recalcShapeAndBoundingRect(); return; } //calculate drop lines const CartesianPlot* plot = dynamic_cast(q->parentAspect()); QVector lines; float xMin = 0; float yMin = 0; xMin = plot->xMin(); yMin = plot->yMin(); switch (dropLineType) { case XYCurve::NoDropLine: break; case XYCurve::DropLineX: for(int i=0; i(yColumn)->minimum())) ); } break; case XYCurve::DropLineXMaxBaseline: for(int i=0; i(yColumn)->maximum())) ); } break; } //map the drop lines to scene coordinates const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines foreach (const QLineF& line, lines) { dropLinePath.moveTo(line.p1()); dropLinePath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateSymbols() { symbolsPath = QPainterPath(); if (symbolsStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } foreach (const QPointF& point, symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); symbolsPath.addPath(trafo.map(path)); } } recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void XYCurvePrivate::updateValues() { valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == XYCurve::NoValues) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot switch (valuesType) { case XYCurve::NoValues: case XYCurve::ValuesX: { for(int i=0; ivaluesColumn->rowCount()) endRow = valuesColumn->rowCount(); else endRow = symbolPointsLogical.size(); AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h=fm.ascent(); for (int i=0; i1000) { recalcShapeAndBoundingRect(); return; } QVector fillLines; const CartesianPlot* plot = dynamic_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); //if there're no interpolation lines available (XYCurve::NoLine selected), create line-interpolation, //use already available lines otherwise. if (!lines.isEmpty()) fillLines = lines; else { for (int i=0; imapLogicalToScene(fillLines); //no lines available (no points), nothing to do if (fillLines.isEmpty()) return; } //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //starting point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//first point of the curve, may not be visible currently QPointF edge; float xEnd=0, yEnd=0; if (fillingPosition == XYCurve::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == XYCurve::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == XYCurve::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax()>0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax()>0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == XYCurve::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i=0; icheck whether we have a break in between. bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon add it to the polygon list and start a new polygon if (fillingPosition==XYCurve::FillingAbove || fillingPosition==XYCurve::FillingBelow || fillingPosition==XYCurve::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2!=end) pol << end; //close the last polygon if (fillingPosition==XYCurve::FillingAbove || fillingPosition==XYCurve::FillingBelow || fillingPosition==XYCurve::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateErrorBars() { errorBarsPath = QPainterPath(); if (xErrorType==XYCurve::NoError && yErrorType==XYCurve::NoError) { recalcShapeAndBoundingRect(); return; } QVector lines; float errorPlus, errorMinus; const CartesianPlot* plot = dynamic_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); //the cap size for the errorbars is given in scene units. //determine first the (half of the) cap size in logical units: // * take the first visible point in logical units // * convert it to scene units // * add to this point an offset corresponding to the cap size in scene units // * convert this point back to logical units // * subtract from this point the original coordinates (without the new offset) // to determine the cap size in logical units. float capSizeX = 0; float capSizeY = 0; if (errorBarsType != XYCurve::ErrorBarsSimple && !symbolPointsLogical.isEmpty()) { //determine the index of the first visible point size_t i = 0; while (i no error bars to draw //cap size for x-error bars QPointF pointScene = cSystem->mapLogicalToScene(symbolPointsLogical.at(i)); pointScene.setY(pointScene.y()-errorBarsCapSize); QPointF pointLogical = cSystem->mapSceneToLogical(pointScene); capSizeX = (pointLogical.y() - symbolPointsLogical.at(i).y())/2; //cap size for y-error bars pointScene = cSystem->mapLogicalToScene(symbolPointsLogical.at(i)); pointScene.setX(pointScene.x()+errorBarsCapSize); pointLogical = cSystem->mapSceneToLogical(pointScene); capSizeY = (pointLogical.x() - symbolPointsLogical.at(i).x())/2; } for (int i=0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); //error bars for x if (xErrorType != XYCurve::NoError) { //determine the values for the errors if (xErrorPlusColumn && xErrorPlusColumn->isValid(i) && !xErrorPlusColumn->isMasked(i)) errorPlus = xErrorPlusColumn->valueAt(i); else errorPlus = 0; if (xErrorType==XYCurve::SymmetricError) errorMinus = errorPlus; else { if (xErrorMinusColumn && xErrorMinusColumn->isValid(i) && !xErrorMinusColumn->isMasked(i)) errorMinus = xErrorMinusColumn->valueAt(i); else errorMinus = 0; } //draw the error bars switch (errorBarsType) { case XYCurve::ErrorBarsSimple: lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); break; case XYCurve::ErrorBarsWithEnds: lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); if (errorMinus!=0) { lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()-capSizeX), QPointF(point.x()-errorMinus, point.y()+capSizeX))); } if (errorPlus!=0) { lines.append(QLineF(QPointF(point.x()+errorPlus, point.y()-capSizeX), QPointF(point.x()+errorPlus, point.y()+capSizeX))); } break; } } //error bars for y if (yErrorType != XYCurve::NoError) { //determine the values for the errors if (yErrorPlusColumn && yErrorPlusColumn->isValid(i) && !yErrorPlusColumn->isMasked(i)) errorPlus = yErrorPlusColumn->valueAt(i); else errorPlus = 0; if (yErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (yErrorMinusColumn && yErrorMinusColumn->isValid(i) && !yErrorMinusColumn->isMasked(i) ) errorMinus = yErrorMinusColumn->valueAt(i); else errorMinus = 0; } //draw the error bars switch (errorBarsType) { case XYCurve::ErrorBarsSimple: lines.append(QLineF(QPointF(point.x(), point.y()-errorMinus), QPointF(point.x(), point.y()+errorPlus))); break; case XYCurve::ErrorBarsWithEnds: lines.append(QLineF(QPointF(point.x(), point.y()-errorMinus), QPointF(point.x(), point.y()+errorPlus))); if (errorMinus != 0) lines.append(QLineF(QPointF(point.x()-capSizeY, point.y()-errorMinus), QPointF(point.x()+capSizeY, point.y()-errorMinus))); if (errorPlus != 0) lines.append(QLineF(QPointF(point.x()-capSizeY, point.y()+errorPlus), QPointF(point.x()+capSizeY, point.y()+errorPlus))); break; } } } //map the error bars to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines foreach (const QLineF& line, lines) { errorBarsPath.moveTo(line.p1()); errorBarsPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void XYCurvePrivate::recalcShapeAndBoundingRect() { if (m_suppressRecalc) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcShapeAndBoundingRect()"); #endif prepareGeometryChange(); curveShape = QPainterPath(); if (lineType != XYCurve::NoLine) curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (dropLineType != XYCurve::NoDropLine) curveShape.addPath(WorksheetElement::shapeFromPath(dropLinePath, dropLinePen)); if (symbolsStyle != Symbol::NoSymbols) curveShape.addPath(symbolsPath); if (valuesType != XYCurve::NoValues) curveShape.addPath(valuesPath); if (xErrorType != XYCurve::NoError || yErrorType != XYCurve::NoError) curveShape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarsPen)); boundingRectangle = curveShape.boundingRect(); foreach (const QPolygonF& pol, fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void XYCurvePrivate::draw(QPainter* painter) { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::draw()"); #endif //draw filling if (fillingPosition != XYCurve::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw lines if (lineType != XYCurve::NoLine) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); } //draw drop lines if (dropLineType != XYCurve::NoDropLine) { painter->setOpacity(dropLineOpacity); painter->setPen(dropLinePen); painter->setBrush(Qt::NoBrush); painter->drawPath(dropLinePath); } //draw error bars if ( (xErrorType != XYCurve::NoError) || (yErrorType != XYCurve::NoError) ) { painter->setOpacity(errorBarsOpacity); painter->setPen(errorBarsPen); painter->setBrush(Qt::NoBrush); painter->drawPath(errorBarsPath); } //draw symbols if (symbolsStyle != Symbol::NoSymbols) { painter->setOpacity(symbolsOpacity); painter->setPen(symbolsPen); painter->setBrush(symbolsBrush); drawSymbols(painter); } //draw values if (valuesType != XYCurve::NoValues) { painter->setOpacity(valuesOpacity); //don't use any painter pen, since this will force QPainter to render the text outline which is expensive painter->setPen(Qt::NoPen); painter->setBrush(valuesColor); drawValues(painter); } } void XYCurvePrivate::updatePixmap() { if (m_suppressRecalc) return; WAIT_CURSOR; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; if (boundingRectangle.width() == 0 || boundingRectangle.width() == 0) { m_pixmap = QPixmap(); RESET_CURSOR; return; } QPixmap pixmap(ceil(boundingRectangle.width()), ceil(boundingRectangle.height())); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; update(); RESET_CURSOR; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void XYCurvePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if ( KSharedConfig::openConfig()->group("Settings_Worksheet").readEntry("DoubleBuffering", true) ) painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); //draw the cached pixmap (fast) else draw(painter); //draw directly again (slow) if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Shadow)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Highlight)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); return; } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which is much faster (factor 10) */ void XYCurvePrivate::drawSymbols(QPainter* painter) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } foreach (const QPointF& point, symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i=0; idrawPath(trafo.map(path)); } } void XYCurvePrivate::drawFilling(QPainter* painter) { foreach (const QPolygonF& pol, fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0, 0), pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2, -pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor, fillingBrushStyle)); painter->drawPolygon(pol); } } void XYCurvePrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { m_hovered = true; q->hovered(); update(); } } void XYCurvePrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; q->unhovered(); update(); } } void XYCurvePrivate::setPrinting(bool on) { m_printing = on; } void XYCurvePrivate::suppressRetransform(bool on) { m_suppressRetransform = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYCurve); writer->writeStartElement( "xyCurve" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); writer->writeAttribute( "dataSourceType", QString::number(d->dataSourceType) ); WRITE_PATH(d->dataSourceCurve, dataSourceCurve); WRITE_COLUMN(d->xColumn, xColumn); WRITE_COLUMN(d->yColumn, yColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); writer->writeAttribute( "type", QString::number(d->lineType) ); writer->writeAttribute( "skipGaps", QString::number(d->lineSkipGaps) ); writer->writeAttribute( "interpolationPointsCount", QString::number(d->lineInterpolationPointsCount) ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeEndElement(); //Drop lines writer->writeStartElement( "dropLines" ); writer->writeAttribute( "type", QString::number(d->dropLineType) ); WRITE_QPEN(d->dropLinePen); writer->writeAttribute( "opacity", QString::number(d->dropLineOpacity) ); writer->writeEndElement(); //Symbols writer->writeStartElement( "symbols" ); writer->writeAttribute( "symbolsStyle", QString::number(d->symbolsStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolsOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolsRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolsSize) ); WRITE_QBRUSH(d->symbolsBrush); WRITE_QPEN(d->symbolsPen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //Error bars writer->writeStartElement( "errorBars" ); writer->writeAttribute( "xErrorType", QString::number(d->xErrorType) ); WRITE_COLUMN(d->xErrorPlusColumn, xErrorPlusColumn); WRITE_COLUMN(d->xErrorMinusColumn, xErrorMinusColumn); writer->writeAttribute( "yErrorType", QString::number(d->yErrorType) ); WRITE_COLUMN(d->yErrorPlusColumn, yErrorPlusColumn); WRITE_COLUMN(d->yErrorMinusColumn, yErrorMinusColumn); writer->writeAttribute( "type", QString::number(d->errorBarsType) ); writer->writeAttribute( "capSize", QString::number(d->errorBarsCapSize) ); WRITE_QPEN(d->errorBarsPen); writer->writeAttribute( "opacity", QString::number(d->errorBarsOpacity) ); writer->writeEndElement(); writer->writeEndElement(); //close "xyCurve" section } //! Load from XML bool XYCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYCurve); if (!reader->isStartElement() || reader->name() != "xyCurve") { reader->raiseError(i18n("no xy-curve element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_INT_VALUE("dataSourceType", dataSourceType, XYCurve::DataSourceType); READ_PATH(dataSourceCurve); READ_COLUMN(xColumn); READ_COLUMN(yColumn); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "lines") { attribs = reader->attributes(); READ_INT_VALUE("type", lineType, XYCurve::LineType); READ_INT_VALUE("skipGaps", lineSkipGaps, int); READ_INT_VALUE("interpolationPointsCount", lineInterpolationPointsCount, int); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); } else if (!preview && reader->name() == "dropLines") { attribs = reader->attributes(); READ_INT_VALUE("type", dropLineType, XYCurve::DropLineType); READ_QPEN(d->dropLinePen); READ_DOUBLE_VALUE("opacity", dropLineOpacity); } else if (!preview && reader->name() == "symbols") { attribs = reader->attributes(); READ_INT_VALUE("symbolsStyle", symbolsStyle, Symbol::Style); READ_DOUBLE_VALUE("opacity", symbolsOpacity); READ_DOUBLE_VALUE("rotation", symbolsRotationAngle); READ_DOUBLE_VALUE("size", symbolsSize); READ_QBRUSH(d->symbolsBrush); READ_QPEN(d->symbolsPen); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); READ_INT_VALUE("type", valuesType, XYCurve::ValuesType); READ_COLUMN(valuesColumn); READ_INT_VALUE("position", valuesPosition, XYCurve::ValuesPosition); READ_DOUBLE_VALUE("distance", valuesDistance); READ_DOUBLE_VALUE("rotation", valuesRotationAngle); READ_DOUBLE_VALUE("opacity", valuesOpacity); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); READ_INT_VALUE("position", fillingPosition, XYCurve::FillingPosition); READ_INT_VALUE("type", fillingType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", fillingColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", fillingImageStyle, PlotArea::BackgroundImageStyle ); READ_INT_VALUE("brushStyle", fillingBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->fillingSecondColor.setBlue(str.toInt()); READ_STRING_VALUE("fileName", fillingFileName); READ_DOUBLE_VALUE("opacity", fillingOpacity); } else if (!preview && reader->name() == "errorBars") { attribs = reader->attributes(); READ_INT_VALUE("xErrorType", xErrorType, XYCurve::ErrorType); READ_COLUMN(xErrorPlusColumn); READ_COLUMN(xErrorMinusColumn); READ_INT_VALUE("yErrorType", yErrorType, XYCurve::ErrorType); READ_COLUMN(yErrorPlusColumn); READ_COLUMN(yErrorMinusColumn); READ_INT_VALUE("type", errorBarsType, XYCurve::ErrorBarsType); READ_DOUBLE_VALUE("capSize", errorBarsCapSize); READ_QPEN(d->errorBarsPen); READ_DOUBLE_VALUE("opacity", errorBarsOpacity); } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void XYCurve::loadThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); int index = parentAspect()->indexOfChild(this); const CartesianPlot* plot = dynamic_cast(parentAspect()); QColor themeColor; if (indexthemeColorPalette().size()) themeColor = plot->themeColorPalette().at(index); else { if (plot->themeColorPalette().size()) themeColor = plot->themeColorPalette().last(); } QPen p; Q_D(XYCurve); d->m_suppressRecalc = true; //Line p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); p.setColor(themeColor); this->setLinePen(p); this->setLineOpacity(group.readEntry("LineOpacity", this->lineOpacity())); //Drop line p.setStyle((Qt::PenStyle)group.readEntry("DropLineStyle",(int) this->dropLinePen().style())); p.setWidthF(group.readEntry("DropLineWidth", this->dropLinePen().widthF())); p.setColor(themeColor); this->setDropLinePen(p); this->setDropLineOpacity(group.readEntry("DropLineOpacity", this->dropLineOpacity())); //Symbol this->setSymbolsOpacity(group.readEntry("SymbolOpacity", this->symbolsOpacity())); QBrush brush = symbolsBrush(); brush.setColor(themeColor); this->setSymbolsBrush(brush); p = symbolsPen(); p.setColor(themeColor); this->setSymbolsPen(p); //Values this->setValuesOpacity(group.readEntry("ValuesOpacity", this->valuesOpacity())); this->setValuesColor(group.readEntry("ValuesColor", this->valuesColor())); //Filling this->setFillingBrushStyle((Qt::BrushStyle)group.readEntry("FillingBrushStyle",(int) this->fillingBrushStyle())); this->setFillingColorStyle((PlotArea::BackgroundColorStyle)group.readEntry("FillingColorStyle",(int) this->fillingColorStyle())); this->setFillingOpacity(group.readEntry("FillingOpacity", this->fillingOpacity())); this->setFillingPosition((XYCurve::FillingPosition)group.readEntry("FillingPosition",(int) this->fillingPosition())); this->setFillingSecondColor(group.readEntry("FillingSecondColor",(QColor) this->fillingSecondColor())); this->setFillingFirstColor(themeColor); this->setFillingType((PlotArea::BackgroundType)group.readEntry("FillingType",(int) this->fillingType())); //Error Bars p.setStyle((Qt::PenStyle)group.readEntry("ErrorBarsStyle",(int) this->errorBarsPen().style())); p.setWidthF(group.readEntry("ErrorBarsWidth", this->errorBarsPen().widthF())); p.setColor(themeColor); this->setErrorBarsPen(p); this->setErrorBarsOpacity(group.readEntry("ErrorBarsOpacity",this->errorBarsOpacity())); d->m_suppressRecalc = false; d->recalcShapeAndBoundingRect(); } void XYCurve::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); //Drop line group.writeEntry("DropLineColor",(QColor) this->dropLinePen().color()); group.writeEntry("DropLineStyle",(int) this->dropLinePen().style()); group.writeEntry("DropLineWidth", this->dropLinePen().widthF()); group.writeEntry("DropLineOpacity",this->dropLineOpacity()); //Error Bars group.writeEntry("ErrorBarsCapSize",this->errorBarsCapSize()); group.writeEntry("ErrorBarsOpacity",this->errorBarsOpacity()); group.writeEntry("ErrorBarsColor",(QColor) this->errorBarsPen().color()); group.writeEntry("ErrorBarsStyle",(int) this->errorBarsPen().style()); group.writeEntry("ErrorBarsWidth", this->errorBarsPen().widthF()); //Filling group.writeEntry("FillingBrushStyle",(int) this->fillingBrushStyle()); group.writeEntry("FillingColorStyle",(int) this->fillingColorStyle()); group.writeEntry("FillingOpacity", this->fillingOpacity()); group.writeEntry("FillingPosition",(int) this->fillingPosition()); group.writeEntry("FillingSecondColor",(QColor) this->fillingSecondColor()); group.writeEntry("FillingType",(int) this->fillingType()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineStyle",(int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Symbol group.writeEntry("SymbolOpacity", this->symbolsOpacity()); //Values group.writeEntry("ValuesOpacity", this->valuesOpacity()); group.writeEntry("ValuesColor", (QColor) this->valuesColor()); group.writeEntry("ValuesFont", this->valuesFont()); int index = parentAspect()->indexOfChild(this); if(index<5) { KConfigGroup themeGroup = config.group("Theme"); for(int i = index; i<5; i++) { QString s = "ThemePaletteColor" + QString::number(i+1); themeGroup.writeEntry(s,(QColor) this->linePen().color()); } } } diff --git a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp index 5497da21b..e570a9bd5 100644 --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -1,477 +1,475 @@ /*************************************************************************** File : XYDataReductionCurve.cpp Project : LabPlot Description : A xy-curve defined by a data reduction -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYDataReductionCurve \brief A xy-curve defined by a data reduction \ingroup worksheet */ #include "XYDataReductionCurve.h" #include "XYDataReductionCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" -#include // isnan - #include #include #include #include XYDataReductionCurve::XYDataReductionCurve(const QString& name) : XYCurve(name, new XYDataReductionCurvePrivate(this)) { init(); } XYDataReductionCurve::XYDataReductionCurve(const QString& name, XYDataReductionCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYDataReductionCurve::~XYDataReductionCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDataReductionCurve::init() { Q_D(XYDataReductionCurve); //TODO: read from the saved settings for XYDataReductionCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYDataReductionCurve::recalculate() { Q_D(XYDataReductionCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDataReductionCurve::icon() const { return QIcon::fromTheme("labplot-xy-data-reduction-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYDataReductionCurve::xDataColumnPath() const { Q_D(const XYDataReductionCurve); return d->xDataColumnPath; } const QString& XYDataReductionCurve::yDataColumnPath() const { Q_D(const XYDataReductionCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, XYDataReductionCurve::DataReductionData, dataReductionData, dataReductionData) const XYDataReductionCurve::DataReductionResult& XYDataReductionCurve::dataReductionResult() const { Q_D(const XYDataReductionCurve); return d->dataReductionResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYDataReductionCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYDataReductionCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYDataReductionCurve); if (column != d->xDataColumn) { exec(new XYDataReductionCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYDataReductionCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYDataReductionCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYDataReductionCurve); if (column != d->yDataColumn) { exec(new XYDataReductionCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYDataReductionCurve, SetDataReductionData, XYDataReductionCurve::DataReductionData, dataReductionData, recalculate); void XYDataReductionCurve::setDataReductionData(const XYDataReductionCurve::DataReductionData& reductionData) { Q_D(XYDataReductionCurve); exec(new XYDataReductionCurveSetDataReductionDataCmd(d, reductionData, i18n("%1: set options and perform the data reduction"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDataReductionCurvePrivate::XYDataReductionCurvePrivate(XYDataReductionCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYDataReductionCurvePrivate::~XYDataReductionCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYDataReductionCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create dataReduction result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result dataReductionResult = XYDataReductionCurve::DataReductionResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the data reduction to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (dataReductionData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = dataReductionData.xRange.first(); xmax = dataReductionData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to use const unsigned int n = xdataVector.size(); if (n < 2) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Not enough data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // dataReduction settings const nsl_geom_linesim_type type = dataReductionData.type; const double tol = dataReductionData.tolerance; const double tol2 = dataReductionData.tolerance2; DEBUG("n =" << n); DEBUG("type:" << nsl_geom_linesim_type_name[type]); DEBUG("tolerance/step:" << tol); DEBUG("tolerance2/repeat/maxtol/region:" << tol2); /////////////////////////////////////////////////////////// emit q->completed(10); size_t npoints = 0; double calcTolerance = 0; // calculated tolerance from Douglas-Peucker variant size_t *index = (size_t *) malloc(n*sizeof(size_t)); switch (type) { case nsl_geom_linesim_type_douglas_peucker_variant: // tol used as number of points npoints = tol; calcTolerance = nsl_geom_linesim_douglas_peucker_variant(xdata, ydata, n, npoints, index); break; case nsl_geom_linesim_type_douglas_peucker: npoints = nsl_geom_linesim_douglas_peucker(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_nthpoint: // tol used as step npoints = nsl_geom_linesim_nthpoint(n, (int)tol, index); break; case nsl_geom_linesim_type_raddist: npoints = nsl_geom_linesim_raddist(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_perpdist: // tol2 used as repeat npoints = nsl_geom_linesim_perpdist_repeat(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_interp: npoints = nsl_geom_linesim_interp(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_visvalingam_whyatt: npoints = nsl_geom_linesim_visvalingam_whyatt(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_reumann_witkam: npoints = nsl_geom_linesim_reumann_witkam(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_opheim: npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_lang: // tol2 used as region npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; } DEBUG("npoints =" << npoints); if (type == nsl_geom_linesim_type_douglas_peucker_variant) { DEBUG("calculated tolerance =" << calcTolerance); } else Q_UNUSED(calcTolerance); emit q->completed(80); xVector->resize(npoints); yVector->resize(npoints); for (unsigned int i = 0; i < npoints; i++) { (*xVector)[i] = xdata[index[i]]; (*yVector)[i] = ydata[index[i]]; } emit q->completed(90); const double posError = nsl_geom_linesim_positional_squared_error(xdata, ydata, n, index); const double areaError = nsl_geom_linesim_area_error(xdata, ydata, n, index); free(index); /////////////////////////////////////////////////////////// //write the result dataReductionResult.available = true; dataReductionResult.valid = true; if (npoints > 0) dataReductionResult.status = QString("OK"); else dataReductionResult.status = QString("FAILURE"); dataReductionResult.elapsedTime = timer.elapsed(); dataReductionResult.npoints = npoints; dataReductionResult.posError = posError; dataReductionResult.areaError = areaError; //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; emit q->completed(100); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDataReductionCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDataReductionCurve); writer->writeStartElement("xyDataReductionCurve"); //write xy-curve information XYCurve::save(writer); //write xy-dataReduction-curve specific information // dataReduction data writer->writeStartElement("dataReductionData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "autoRange", QString::number(d->dataReductionData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->dataReductionData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->dataReductionData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->dataReductionData.type) ); writer->writeAttribute( "autoTolerance", QString::number(d->dataReductionData.autoTolerance) ); writer->writeAttribute( "tolerance", QString::number(d->dataReductionData.tolerance) ); writer->writeAttribute( "autoTolerance2", QString::number(d->dataReductionData.autoTolerance2) ); writer->writeAttribute( "tolerance2", QString::number(d->dataReductionData.tolerance2) ); writer->writeEndElement();// dataReductionData // dataReduction results (generated columns) writer->writeStartElement("dataReductionResult"); writer->writeAttribute( "available", QString::number(d->dataReductionResult.available) ); writer->writeAttribute( "valid", QString::number(d->dataReductionResult.valid) ); writer->writeAttribute( "status", d->dataReductionResult.status ); writer->writeAttribute( "time", QString::number(d->dataReductionResult.elapsedTime) ); writer->writeAttribute( "npoints", QString::number(d->dataReductionResult.npoints) ); writer->writeAttribute( "posError", QString::number(d->dataReductionResult.posError) ); writer->writeAttribute( "areaError", QString::number(d->dataReductionResult.areaError) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"dataReductionResult" writer->writeEndElement(); //"xyDataReductionCurve" } //! Load from XML bool XYDataReductionCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDataReductionCurve); if (!reader->isStartElement() || reader->name() != "xyDataReductionCurve") { reader->raiseError(i18n("no xy dataReduction curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyDataReductionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "dataReductionData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", dataReductionData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", dataReductionData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", dataReductionData.xRange.last()); READ_INT_VALUE("type", dataReductionData.type, nsl_geom_linesim_type); READ_INT_VALUE("autoTolerance", dataReductionData.autoTolerance, int); READ_DOUBLE_VALUE("tolerance", dataReductionData.tolerance); READ_INT_VALUE("autoTolerance2", dataReductionData.autoTolerance2, int); READ_DOUBLE_VALUE("tolerance2", dataReductionData.tolerance2); } else if (!preview && reader->name() == "dataReductionResult") { attribs = reader->attributes(); READ_INT_VALUE("available", dataReductionResult.available, int); READ_INT_VALUE("valid", dataReductionResult.valid, int); READ_STRING_VALUE("status", dataReductionResult.status); READ_INT_VALUE("time", dataReductionResult.elapsedTime, int); READ_INT_VALUE("npoints", dataReductionResult.npoints, int); READ_DOUBLE_VALUE("posError", dataReductionResult.posError); READ_DOUBLE_VALUE("areaError", dataReductionResult.areaError); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp index de4a8918f..8be9981f1 100644 --- a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp @@ -1,424 +1,422 @@ /*************************************************************************** File : XYDifferentiationCurve.cpp Project : LabPlot Description : A xy-curve defined by an differentiation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYDifferentiationCurve \brief A xy-curve defined by an differentiation \ingroup worksheet */ #include "XYDifferentiationCurve.h" #include "XYDifferentiationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" -#include // isnan -#include // DBL_MIN extern "C" { #include } #include #include #include #include XYDifferentiationCurve::XYDifferentiationCurve(const QString& name) : XYCurve(name, new XYDifferentiationCurvePrivate(this)) { init(); } XYDifferentiationCurve::XYDifferentiationCurve(const QString& name, XYDifferentiationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYDifferentiationCurve::~XYDifferentiationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDifferentiationCurve::init() { Q_D(XYDifferentiationCurve); //TODO: read from the saved settings for XYDifferentiationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYDifferentiationCurve::recalculate() { Q_D(XYDifferentiationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDifferentiationCurve::icon() const { return QIcon::fromTheme("labplot-xy-differentiation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYDifferentiationCurve::xDataColumnPath() const { Q_D(const XYDifferentiationCurve); return d->xDataColumnPath; } const QString& XYDifferentiationCurve::yDataColumnPath() const { Q_D(const XYDifferentiationCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, XYDifferentiationCurve::DifferentiationData, differentiationData, differentiationData) const XYDifferentiationCurve::DifferentiationResult& XYDifferentiationCurve::differentiationResult() const { Q_D(const XYDifferentiationCurve); return d->differentiationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYDifferentiationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYDifferentiationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYDifferentiationCurve); if (column != d->xDataColumn) { exec(new XYDifferentiationCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYDifferentiationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYDifferentiationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYDifferentiationCurve); if (column != d->yDataColumn) { exec(new XYDifferentiationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYDifferentiationCurve, SetDifferentiationData, XYDifferentiationCurve::DifferentiationData, differentiationData, recalculate); void XYDifferentiationCurve::setDifferentiationData(const XYDifferentiationCurve::DifferentiationData& differentiationData) { Q_D(XYDifferentiationCurve); exec(new XYDifferentiationCurveSetDifferentiationDataCmd(d, differentiationData, i18n("%1: set options and perform the differentiation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDifferentiationCurvePrivate::XYDifferentiationCurvePrivate(XYDifferentiationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYDifferentiationCurvePrivate::~XYDifferentiationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYDifferentiationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create differentiation result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result differentiationResult = XYDifferentiationCurve::DifferentiationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { differentiationResult.available = true; differentiationResult.valid = false; differentiationResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (differentiationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = differentiationData.xRange.first(); xmax = differentiationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to differentiate const unsigned int n = xdataVector.size(); if (n < 3) { differentiationResult.available = true; differentiationResult.valid = false; differentiationResult.status = i18n("Not enough data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // differentiation settings const nsl_diff_deriv_order_type derivOrder = differentiationData.derivOrder; const int accOrder = differentiationData.accOrder; DEBUG(nsl_diff_deriv_order_name[derivOrder] << "derivative"); DEBUG("accuracy order:" << accOrder); /////////////////////////////////////////////////////////// int status=0; switch (derivOrder) { case nsl_diff_deriv_order_first: status = nsl_diff_first_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_second: status = nsl_diff_second_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_third: status = nsl_diff_third_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fourth: status = nsl_diff_fourth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fifth: status = nsl_diff_fifth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_sixth: status = nsl_diff_sixth_deriv(xdata, ydata, n, accOrder); break; } xVector->resize(n); yVector->resize(n); memcpy(xVector->data(), xdata, n*sizeof(double)); memcpy(yVector->data(), ydata, n*sizeof(double)); /////////////////////////////////////////////////////////// //write the result differentiationResult.available = true; differentiationResult.valid = true; differentiationResult.status = QString::number(status); differentiationResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDifferentiationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDifferentiationCurve); writer->writeStartElement("xyDifferentiationCurve"); //write xy-curve information XYCurve::save(writer); //write xy-differentiation-curve specific information // differentiation data writer->writeStartElement("differentiationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "derivOrder", QString::number(d->differentiationData.derivOrder) ); writer->writeAttribute( "accOrder", QString::number(d->differentiationData.accOrder) ); writer->writeAttribute( "autoRange", QString::number(d->differentiationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->differentiationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->differentiationData.xRange.last()) ); writer->writeEndElement();// differentiationData // differentiation results (generated columns) writer->writeStartElement("differentiationResult"); writer->writeAttribute( "available", QString::number(d->differentiationResult.available) ); writer->writeAttribute( "valid", QString::number(d->differentiationResult.valid) ); writer->writeAttribute( "status", d->differentiationResult.status ); writer->writeAttribute( "time", QString::number(d->differentiationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"differentiationResult" writer->writeEndElement(); //"xyDifferentiationCurve" } //! Load from XML bool XYDifferentiationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDifferentiationCurve); if (!reader->isStartElement() || reader->name() != "xyDifferentiationCurve") { reader->raiseError(i18n("no xy differentiation curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyDifferentiationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "differentiationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", differentiationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", differentiationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", differentiationData.xRange.last()); READ_INT_VALUE("derivOrder", differentiationData.derivOrder, nsl_diff_deriv_order_type); READ_INT_VALUE("accOrder", differentiationData.accOrder, int); } else if (!preview && reader->name() == "differentiationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", differentiationResult.available, int); READ_INT_VALUE("valid", differentiationResult.valid, int); READ_STRING_VALUE("status", differentiationResult.status); READ_INT_VALUE("time", differentiationResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index 7dd828112..850c295d2 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2146 +1,2171 @@ /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } -#include #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYCurve(name, new XYFitCurvePrivate(this)) { init(); } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFitCurve::~XYFitCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFitCurve::init() { Q_D(XYFitCurve); //TODO: read from the saved settings for XYFitCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; unsigned int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; DEBUG("XYFitCurve::initFitData() for model category = " << modelCategory << ", model type = " << modelType << ", degree = " << degree); if (modelCategory != nsl_fit_model_custom) paramNames.clear(); paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = {QString::fromUtf8("\u2081"), QString::fromUtf8("\u2082"), QString::fromUtf8("\u2083"), QString::fromUtf8("\u2084"), QString::fromUtf8("\u2085"), QString::fromUtf8("\u2086"), QString::fromUtf8("\u2087"), QString::fromUtf8("\u2088"), QString::fromUtf8("\u2089"), QString::fromUtf8("\u2081") + QString::fromUtf8("\u2080")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << QString::fromUtf8("c\u2080") << QString::fromUtf8("c\u2081"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << QString::fromUtf8("c\u2082"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << "c" + numStr; paramNamesUtf8 << "c" + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << "a" + numStr << "b" + numStr; paramNamesUtf8 << "a" + indices[i-1] << "b" + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: degree = 1; paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << QString::fromUtf8("\u03c9") << QString::fromUtf8("a\u2080") << QString::fromUtf8("a\u2081") << QString::fromUtf8("b\u2081"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << "a" + numStr << "b" + numStr; paramNamesUtf8 << "a" + indices[i-1] << "b" + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2) + a3/s3 * exp(-((x-mu3)/s3)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2) + a3 * g3/(g3^2+(x-mu3)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2" << "g3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03b3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << "g" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03b3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2) + a3/s3 * sech((x-mu3)/s3))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ")"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2 + a3/s3 * sech((x-mu3)/2/s3)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_sigmoid: paramNames << "k" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_hill: paramNames << "s" << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "n" << "A"; break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gaussian_tail: paramNames << "s" << "mu" << "A" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A" << "a"; break; case nsl_sf_stats_exponential: paramNames << "l" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_exponential_power: paramNames << "s" << "mu" << "b" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "b" << "A"; break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_rayleigh: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "t" << "k" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b8") << "k" << "A"; break; case nsl_sf_stats_flat: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_chi_squared: paramNames << "n" << "a"; paramNamesUtf8 << "n" << "A"; break; case nsl_sf_stats_fdist: paramNames << "n1" << "n2" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2081") << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2082") << "A"; break; case nsl_sf_stats_tdist: paramNames << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") << "A"; break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_weibull: paramNames << "k" << "l" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel1: paramNames << "s" << "b" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03b2") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel2: paramNames << "a" << "b" << "mu" << "A"; paramNamesUtf8 << "a" << "b" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_poisson: paramNames << "l" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << "A"; break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "p" << "n" << "a"; paramNamesUtf8 << "p" << "n" << "A"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "p" << "a"; paramNamesUtf8 << "p" << "A"; break; case nsl_sf_stats_hypergeometric: paramNames << "n1" << "n2" << "t" << "a"; paramNamesUtf8 << "n" + QString::fromUtf8("\u2081") << "n" + QString::fromUtf8("\u2082") << "t" << "A"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; break; case nsl_sf_stats_frechet: paramNames << "g" << "mu" << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << QString::fromUtf8("\u03c3") << "A"; break; } break; case nsl_fit_model_custom: break; } if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; - paramLowerLimits[i] = -DBL_MAX; - paramUpperLimits[i] = DBL_MAX; + paramLowerLimits[i] = -std::numeric_limits::max(); + paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values if (modelCategory == nsl_fit_model_distribution) { nsl_sf_stats_distribution type = (nsl_sf_stats_distribution)modelType; if (type == nsl_sf_stats_flat) paramStartValues[0] = -1.0; else if (type == nsl_sf_stats_frechet || type == nsl_sf_stats_levy || type == nsl_sf_stats_exponential_power) paramStartValues[1] = 0.0; else if (type == nsl_sf_stats_weibull || type == nsl_sf_stats_gumbel2) paramStartValues[2] = 0.0; else if (type == nsl_sf_stats_binomial || type == nsl_sf_stats_negative_binomial || type == nsl_sf_stats_pascal || type == nsl_sf_stats_geometric || type == nsl_sf_stats_logarithmic) paramStartValues[0] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yDataColumn, yDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xDataColumnPath() const { Q_D(const XYFitCurve); return d->xDataColumnPath; } const QString& XYFitCurve::yDataColumnPath() const { Q_D(const XYFitCurve); return d->yDataColumnPath; } const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve);return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve);return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFitCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xDataColumn) { exec(new XYFitCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFitCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yDataColumn) { exec(new XYFitCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, i18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, i18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate); void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, i18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xErrorColumn(0), yErrorColumn(0), xColumn(0), yColumn(0), residualsColumn(0), xVector(0), yVector(0), residualsVector(0), q(owner) { } XYFitCurvePrivate::~XYFitCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; unsigned int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double x = gsl_vector_get(paramValues, i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(x, min[i], max[i])); QDEBUG("Parameter"<at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); - const double eps = 1.e-9 * fabs(f_p); // adapt step size to value + const double eps = 1.e-9 * std::abs(f_p); // adapt step size to value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // qDebug()<<"evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); residualsVector->clear(); } // clear the previous result fitResult = XYFitCurve::FitResult(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < xDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.xRange.first(); xmax = fitData.xRange.last(); } for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y and errors, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { if (dataSourceType == XYCurve::DataSourceCurve || (!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } else if (!xErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } else { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG("number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG("x errors: " << xerrorVector.size()); DEBUG("y errors: " << yerrorVector.size()); double* weight = new double[n]; + switch(fitData.xWeightsType) { + case nsl_fit_weight_no: + case nsl_fit_weight_statistical_fit: + case nsl_fit_weight_relative_fit: + case nsl_fit_weight_direct: + break; + case nsl_fit_weight_instrumental: + for(int i = 0; i < xerrorVector.size(); i++) + xerror[i] = 1./gsl_pow_2(xerror[i]); + break; + case nsl_fit_weight_inverse: + for(int i = 0; i < xerrorVector.size(); i++) + xerror[i] = 1./xerror[i]; + break; + case nsl_fit_weight_statistical: + for(int i = 0; i < xerrorVector.size(); i++) + xerror[i] = 1./xdata[i]; + break; + case nsl_fit_weight_relative: + for(int i = 0; i < xerrorVector.size(); i++) + xerror[i] = 1./gsl_pow_2(xdata[i]); + break; + } + for (size_t i = 0; i < n; i++) weight[i] = 1.; - switch (fitData.weightsType) { + switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(yerror[i]); break; case nsl_fit_weight_direct: for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./yerror[i]; break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./ydata[i]; break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./gsl_pow_2(ydata[i]); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" for (unsigned int i = 0; i < np; i++) DEBUG("parameter " << i << " fixed: " << fitData.paramFixed.data()[i]); //function to fit gsl_multifit_function_fdf f; DEBUG("model = " << fitData.model.toStdString()); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; // initialize the derivative solver (using Levenberg-Marquardt robust solver) const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); // set start values double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); // scale start values if limits are set for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); gsl_vector_view x = gsl_vector_view_array(x_init, np); // initialize solver with function f and initial guess x gsl_multifit_fdfsolver_set(s, &f, &x.vector); // iterate int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; // update weights for Y-depending weights - if (fitData.weightsType == nsl_fit_weight_statistical_fit) { + if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i - } else if (fitData.weightsType == nsl_fit_weight_relative_fit) { + } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i^2 } status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG("Rerun fit with x errors"); // y'(x) double *yd = new double[n]; for (size_t i = 0; i < n; i++) { size_t index = i; if (index == n-1) index = n-2; yd[i] = gsl_vector_get(s->f, index+1) + ydata[index+1] - gsl_vector_get(s->f, index) - ydata[index]; yd[i] /= (xdata[index+1] - xdata[index]); } - switch (fitData.weightsType) { + switch (fitData.yWeightsType) { case nsl_fit_weight_no: break; case nsl_fit_weight_instrumental: for (size_t i = 0; i < n; i++) { double sigma; if (yerrorVector.size() > 0) // x- and y-error // sigma = sqrt(sigma_y^2 + (y'(x)*sigma_x)^2) sigma = sqrt(gsl_pow_2(yerror[i]) + gsl_pow_2(yd[i] * xerror[i])); else // only x-error sigma = yd[i] * xerror[i]; weight[i] = 1./gsl_pow_2(sigma); } break; // other weight types: y'(x) considered correctly? case nsl_fit_weight_direct: for (size_t i = 0; i < n; i++) { weight[i] = xerror[i]/yd[i]; if (yerrorVector.size() > 0) weight[i] += yerror[i]; } break; case nsl_fit_weight_inverse: for (size_t i = 0; i < n; i++) { weight[i] = yd[i]/xerror[i]; if (yerrorVector.size() > 0) weight[i] += 1./yerror[i]; } break; case nsl_fit_weight_statistical: case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i break; case nsl_fit_weight_relative_fit: for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i^2 break; } delete[] yd; do { iter++; status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) break; status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); } delete[] weight; // unscale start values for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - np; //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; //needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np); //parameter values const double c = GSL_MIN_DBL(1., sqrt(fitResult.rms)); //limit error for poor fit fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG("saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); if (fitData.evaluateFullRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*xVector)[i] = tmpXDataColumn->valueAt(i); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; if (!rc) residualsVector->clear(); } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) ExpressionParser* parser = ExpressionParser::getInstance(); if (fitData.evaluateFullRange) { // evaluate fit on full data range if selected xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } xVector->resize(fitData.evaluatedPoints); yVector->resize(fitData.evaluatedPoints); bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, fitResult.paramValues); if (!rc) { xVector->clear(); yVector->clear(); } fitResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write xy-curve information XYCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("xRangeMin", QString::number(d->fitData.xRange.first(), 'g', 15)); writer->writeAttribute("xRangeMax", QString::number(d->fitData.xRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); - writer->writeAttribute("weightsType", QString::number(d->fitData.weightsType)); + writer->writeAttribute("xWeightsType", QString::number(d->fitData.xWeightsType)); + writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("evaluateFullRange", QString::number(d->fitData.evaluateFullRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFitCurve); if (!reader->isStartElement() || reader->name() != "xyFitCurve") { reader->raiseError(i18n("no xy fit curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", fitData.xRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); - READ_INT_VALUE("weightsType", fitData.weightsType, nsl_fit_weight_type); + READ_INT_VALUE("xWeightsType", fitData.xWeightsType, nsl_fit_weight_type); + READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << d->fitData.model.toStdString()); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.evaluateFullRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else - d->fitData.paramLowerLimits << -DBL_MAX; + d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else - d->fitData.paramUpperLimits << DBL_MAX; + d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("reset old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(d->fitResult.paramValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.h b/src/backend/worksheet/plots/cartesian/XYFitCurve.h index e95ad3894..89606386d 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.h +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.h @@ -1,162 +1,164 @@ /*************************************************************************** File : XYFitCurve.h Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2016 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef XYFITCURVE_H #define XYFITCURVE_H #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. extern "C" { #include "backend/nsl/nsl_fit.h" } class XYFitCurvePrivate; class XYFitCurve : public XYCurve { Q_OBJECT public: struct FitData { FitData() : modelCategory(nsl_fit_model_basic), modelType(0), - weightsType(nsl_fit_weight_no), + xWeightsType(nsl_fit_weight_no), + yWeightsType(nsl_fit_weight_no), degree(1), maxIterations(500), eps(1e-4), evaluatedPoints(100), evaluateFullRange(true), useDataErrors(true), useResults(true), autoRange(true), xRange(2) {}; nsl_fit_model_category modelCategory; unsigned int modelType; - nsl_fit_weight_type weightsType; + nsl_fit_weight_type xWeightsType; + nsl_fit_weight_type yWeightsType; int degree; QString model; QStringList paramNames; QStringList paramNamesUtf8; // Utf8 version of paramNames QVector paramStartValues; QVector paramLowerLimits; QVector paramUpperLimits; QVector paramFixed; int maxIterations; double eps; size_t evaluatedPoints; bool evaluateFullRange; // evaluate fit function on full data range (default) bool useDataErrors; // use given data errors when fitting (default) bool useResults; // use results as new start values (default) bool autoRange; // use all data? QVector xRange; // x range for integration }; struct FitResult { FitResult() : available(false), valid(false), iterations(0), elapsedTime(0), dof(0), sse(0), sst(0), rms(0), rsd(0), mse(0), rmse(0), mae(0), rsquare(0), rsquareAdj(0), chisq_p(0), fdist_F(0), fdist_p(0), aic(0), bic(0) {}; bool available; bool valid; QString status; int iterations; qint64 elapsedTime; double dof; //degrees of freedom // residuals: r_i = y_i - Y_i double sse; // sum of squared errors (SSE) / residual sum of squares (RSS) / sum of sq. residuals (SSR) / S = chi^2 = \sum_i^n r_i^2 double sst; // total sum of squares (SST) = \sum_i^n (y_i - )^2 double rms; // residual mean square / reduced chi^2 = SSE/dof double rsd; // residual standard deviation = sqrt(SSE/dof) double mse; // mean squared error = SSE/n double rmse; // root-mean squared error = \sqrt(mse) double mae; // mean absolute error = \sum_i^n |r_i| double rsquare; double rsquareAdj; double chisq_p; // chi^2 distribution p-value double fdist_F; // F distribution F-value double fdist_p; // F distribution p-value double aic; // Akaike information criterion double bic; // Schwarz Bayesian information criterion // see also http://www.originlab.com/doc/Origin-Help/NLFit-Algorithm QVector paramValues; QVector errorValues; QVector tdist_tValues; QVector tdist_pValues; QVector tdist_marginValues; QString solverOutput; }; explicit XYFitCurve(const QString& name); virtual ~XYFitCurve(); void recalculate(); void initFitData(PlotDataDialog::AnalysisAction); static void initFitData(XYFitCurve::FitData&); virtual QIcon icon() const override; virtual void save(QXmlStreamWriter*) const override; virtual bool load(XmlStreamReader*, bool preview) override; POINTER_D_ACCESSOR_DECL(const AbstractColumn, xDataColumn, XDataColumn) POINTER_D_ACCESSOR_DECL(const AbstractColumn, yDataColumn, YDataColumn) POINTER_D_ACCESSOR_DECL(const AbstractColumn, xErrorColumn, XErrorColumn) POINTER_D_ACCESSOR_DECL(const AbstractColumn, yErrorColumn, YErrorColumn) const QString& xDataColumnPath() const; const QString& yDataColumnPath() const; const QString& xErrorColumnPath() const; const QString& yErrorColumnPath() const; CLASS_D_ACCESSOR_DECL(FitData, fitData, FitData) const FitResult& fitResult() const; typedef XYFitCurvePrivate Private; protected: XYFitCurve(const QString& name, XYFitCurvePrivate* dd); private: Q_DECLARE_PRIVATE(XYFitCurve) void init(); signals: friend class XYFitCurveSetXDataColumnCmd; friend class XYFitCurveSetYDataColumnCmd; friend class XYFitCurveSetXErrorColumnCmd; friend class XYFitCurveSetYErrorColumnCmd; void xDataColumnChanged(const AbstractColumn*); void yDataColumnChanged(const AbstractColumn*); void xErrorColumnChanged(const AbstractColumn*); void yErrorColumnChanged(const AbstractColumn*); friend class XYFitCurveSetFitDataCmd; void fitDataChanged(const XYFitCurve::FitData&); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp index ca22c6a38..1b6c963b6 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -1,463 +1,463 @@ /*************************************************************************** File : XYFourierFilterCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier filter -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierFilterCurve \brief A xy-curve defined by a Fourier filter \ingroup worksheet */ #include "XYFourierFilterCurve.h" #include "XYFourierFilterCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" -#include // isnan extern "C" { #include #ifdef HAVE_FFTW3 #include #endif #include "backend/nsl/nsl_sf_poly.h" } #include #include #include +#include // qWarning() XYFourierFilterCurve::XYFourierFilterCurve(const QString& name) : XYCurve(name, new XYFourierFilterCurvePrivate(this)) { init(); } XYFourierFilterCurve::XYFourierFilterCurve(const QString& name, XYFourierFilterCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFourierFilterCurve::~XYFourierFilterCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierFilterCurve::init() { Q_D(XYFourierFilterCurve); //TODO: read from the saved settings for XYFourierFilterCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYFourierFilterCurve::recalculate() { Q_D(XYFourierFilterCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierFilterCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_filter-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYFourierFilterCurve::xDataColumnPath() const { Q_D(const XYFourierFilterCurve); return d->xDataColumnPath; } const QString& XYFourierFilterCurve::yDataColumnPath() const { Q_D(const XYFourierFilterCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, XYFourierFilterCurve::FilterData, filterData, filterData) const XYFourierFilterCurve::FilterResult& XYFourierFilterCurve::filterResult() const { Q_D(const XYFourierFilterCurve); return d->filterResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFourierFilterCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFourierFilterCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFourierFilterCurve); if (column != d->xDataColumn) { exec(new XYFourierFilterCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFourierFilterCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFourierFilterCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFourierFilterCurve); if (column != d->yDataColumn) { exec(new XYFourierFilterCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYFourierFilterCurve, SetFilterData, XYFourierFilterCurve::FilterData, filterData, recalculate); void XYFourierFilterCurve::setFilterData(const XYFourierFilterCurve::FilterData& filterData) { Q_D(XYFourierFilterCurve); exec(new XYFourierFilterCurveSetFilterDataCmd(d, filterData, i18n("%1: set filter options and perform the Fourier filter"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierFilterCurvePrivate::XYFourierFilterCurvePrivate(XYFourierFilterCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYFourierFilterCurvePrivate::~XYFourierFilterCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYFourierFilterCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create filter result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result filterResult = XYFourierFilterCurve::FilterResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (filterData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = filterData.xRange.first(); xmax = filterData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to filter unsigned int n = xdataVector.size(); if (n == 0) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("No data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // filter settings const nsl_filter_type type = filterData.type; const nsl_filter_form form = filterData.form; const unsigned int order = filterData.order; const double cutoff = filterData.cutoff, cutoff2 = filterData.cutoff2; const nsl_filter_cutoff_unit unit = filterData.unit, unit2 = filterData.unit2; DEBUG("n ="< 0. Giving up."; return; } DEBUG("cut off @" << cutindex << cutindex2); DEBUG("bandwidth =" << bandwidth); // run filter int status = nsl_filter_fourier(ydata, n, type, form, order, cutindex, bandwidth); xVector->resize(n); yVector->resize(n); memcpy(xVector->data(), xdataVector.data(), n*sizeof(double)); memcpy(yVector->data(), ydata, n*sizeof(double)); /////////////////////////////////////////////////////////// //write the result filterResult.available = true; filterResult.valid = true; filterResult.status = gslErrorToString(status); filterResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierFilterCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierFilterCurve); writer->writeStartElement("xyFourierFilterCurve"); //write xy-curve information XYCurve::save(writer); //write xy-fourier_filter-curve specific information //filter data writer->writeStartElement("filterData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "autoRange", QString::number(d->filterData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->filterData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->filterData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->filterData.type) ); writer->writeAttribute( "form", QString::number(d->filterData.form) ); writer->writeAttribute( "order", QString::number(d->filterData.order) ); writer->writeAttribute( "cutoff", QString::number(d->filterData.cutoff) ); writer->writeAttribute( "unit", QString::number(d->filterData.unit) ); writer->writeAttribute( "cutoff2", QString::number(d->filterData.cutoff2) ); writer->writeAttribute( "unit2", QString::number(d->filterData.unit2) ); writer->writeEndElement();// filterData //filter results (generated columns) writer->writeStartElement("filterResult"); writer->writeAttribute( "available", QString::number(d->filterResult.available) ); writer->writeAttribute( "valid", QString::number(d->filterResult.valid) ); writer->writeAttribute( "status", d->filterResult.status ); writer->writeAttribute( "time", QString::number(d->filterResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"filterResult" writer->writeEndElement(); //"xyFourierFilterCurve" } //! Load from XML bool XYFourierFilterCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierFilterCurve); if (!reader->isStartElement() || reader->name() != "xyFourierFilterCurve") { reader->raiseError(i18n("no xy Fourier filter curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierFilterCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "filterData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", filterData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", filterData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", filterData.xRange.last()); READ_INT_VALUE("type", filterData.type, nsl_filter_type); READ_INT_VALUE("form", filterData.form, nsl_filter_form); READ_INT_VALUE("order", filterData.order, int); READ_DOUBLE_VALUE("cutoff", filterData.cutoff); READ_INT_VALUE("unit", filterData.unit, nsl_filter_cutoff_unit); READ_DOUBLE_VALUE("cutoff2", filterData.cutoff2); READ_INT_VALUE("unit2", filterData.unit2, nsl_filter_cutoff_unit); } else if (!preview && reader->name() == "filterResult") { attribs = reader->attributes(); READ_INT_VALUE("available", filterResult.available, int); READ_INT_VALUE("valid", filterResult.valid, int); READ_STRING_VALUE("status", filterResult.status); READ_INT_VALUE("time", filterResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurvePrivate.h b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurvePrivate.h index cc4a78641..6baf50c99 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurvePrivate.h +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurvePrivate.h @@ -1,66 +1,63 @@ /*************************************************************************** File : XYFourierFilterCurvePrivate.h Project : LabPlot Description : Private members of XYFourierFilterCurve -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef XYFOURIERFILTERCURVEPRIVATE_H #define XYFOURIERFILTERCURVEPRIVATE_H #include "backend/worksheet/plots/cartesian/XYCurvePrivate.h" #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h" -#include -#include - class XYFourierFilterCurve; class Column; class XYFourierFilterCurvePrivate: public XYCurvePrivate { public: explicit XYFourierFilterCurvePrivate(XYFourierFilterCurve*); ~XYFourierFilterCurvePrivate(); void recalculate(); const AbstractColumn* xDataColumn; //* xVector; QVector* yVector; XYFourierFilterCurve* const q; // private: // void writeSolverState(gsl_multifit_fdfsolver* s); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp index ae72c7c3c..271ad1cfa 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -1,449 +1,449 @@ /*************************************************************************** File : XYFourierTransformCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier transform -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierTransformCurve \brief A xy-curve defined by a Fourier transform \ingroup worksheet */ #include "XYFourierTransformCurve.h" #include "XYFourierTransformCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" -#include // isnan extern "C" { #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include +#include // qWarning() XYFourierTransformCurve::XYFourierTransformCurve(const QString& name) : XYCurve(name, new XYFourierTransformCurvePrivate(this)) { init(); } XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFourierTransformCurve::~XYFourierTransformCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierTransformCurve::init() { Q_D(XYFourierTransformCurve); //TODO: read from the saved settings for XYFourierTransformCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYFourierTransformCurve::recalculate() { Q_D(XYFourierTransformCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierTransformCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_transform-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYFourierTransformCurve::xDataColumnPath() const { Q_D(const XYFourierTransformCurve); return d->xDataColumnPath; } const QString& XYFourierTransformCurve::yDataColumnPath() const { Q_D(const XYFourierTransformCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, XYFourierTransformCurve::TransformData, transformData, transformData) const XYFourierTransformCurve::TransformResult& XYFourierTransformCurve::transformResult() const { Q_D(const XYFourierTransformCurve); return d->transformResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFourierTransformCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFourierTransformCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFourierTransformCurve); if (column != d->xDataColumn) { exec(new XYFourierTransformCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFourierTransformCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFourierTransformCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFourierTransformCurve); if (column != d->yDataColumn) { exec(new XYFourierTransformCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYFourierTransformCurve, SetTransformData, XYFourierTransformCurve::TransformData, transformData, recalculate); void XYFourierTransformCurve::setTransformData(const XYFourierTransformCurve::TransformData& transformData) { Q_D(XYFourierTransformCurve); exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, i18n("%1: set transform options and perform the Fourier transform"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYFourierTransformCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create transform result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result transformResult = XYFourierTransformCurve::TransformResult(); if (!xDataColumn || !yDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (xDataColumn->rowCount()!=yDataColumn->rowCount()) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the transform to temporary vectors QVector xdataVector; QVector ydataVector; const double xmin = transformData.xRange.first(); const double xmax = transformData.xRange.last(); for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(xDataColumn->valueAt(row)) && !std::isnan(yDataColumn->valueAt(row)) && !xDataColumn->isMasked(row) && !yDataColumn->isMasked(row)) { // only when inside given range if (xDataColumn->valueAt(row) >= xmin && xDataColumn->valueAt(row) <= xmax) { xdataVector.append(xDataColumn->valueAt(row)); ydataVector.append(yDataColumn->valueAt(row)); } } } //number of data points to transform unsigned int n = ydataVector.size(); if (n == 0) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("No data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // transform settings const nsl_sf_window_type windowType = transformData.windowType; const nsl_dft_result_type type = transformData.type; const bool twoSided = transformData.twoSided; const bool shifted = transformData.shifted; const nsl_dft_xscale xScale = transformData.xScale; DEBUG("n =" << n); DEBUG("window type:" << nsl_sf_window_type_name[windowType]); DEBUG("type:" << nsl_dft_result_type_name[type]); DEBUG("scale:" << nsl_dft_xscale_name[xScale]); DEBUG("two sided:" << twoSided); DEBUG("shifted:" << shifted); #ifndef NDEBUG QDebug out = qDebug(); for (unsigned int i=0; i < n; i++) out<= n/2 && shifted) xdata[i] = (n-1)/(xmax-xmin)*(i/(double)n-1.); else xdata[i] = (n-1)*i/(xmax-xmin)/n; } break; case nsl_dft_xscale_index: for (unsigned int i=0; i < N; i++) { if (i >= n/2 && shifted) xdata[i] = (int)i-(int) N; else xdata[i] = i; } break; case nsl_dft_xscale_period: { double f0 = (n-1)/(xmax-xmin)/n; for (unsigned int i=0; i < N; i++) { double f = (n-1)*i/(xmax-xmin)/n; xdata[i] = 1/(f+f0); } break; } } #ifndef NDEBUG out = qDebug(); for (unsigned int i=0; i < N; i++) out << ydata[i] << '(' << xdata[i] << ')'; #endif xVector->resize(N); yVector->resize(N); if(shifted) { memcpy(xVector->data(), &xdata[n/2], n/2*sizeof(double)); memcpy(&xVector->data()[n/2], xdata, n/2*sizeof(double)); memcpy(yVector->data(), &ydata[n/2], n/2*sizeof(double)); memcpy(&yVector->data()[n/2], ydata, n/2*sizeof(double)); } else { memcpy(xVector->data(), xdata, N*sizeof(double)); memcpy(yVector->data(), ydata, N*sizeof(double)); } /////////////////////////////////////////////////////////// //write the result transformResult.available = true; transformResult.valid = true; transformResult.status = gslErrorToString(status); transformResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierTransformCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierTransformCurve); writer->writeStartElement("xyFourierTransformCurve"); //write xy-curve information XYCurve::save(writer); //write xy-fourier_transform-curve specific information //transform data writer->writeStartElement("transformData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "autoRange", QString::number(d->transformData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->transformData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->transformData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->transformData.type) ); writer->writeAttribute( "twoSided", QString::number(d->transformData.twoSided) ); writer->writeAttribute( "shifted", QString::number(d->transformData.shifted) ); writer->writeAttribute( "xScale", QString::number(d->transformData.xScale) ); writer->writeAttribute( "windowType", QString::number(d->transformData.windowType) ); writer->writeEndElement();// transformData //transform results (generated columns) writer->writeStartElement("transformResult"); writer->writeAttribute( "available", QString::number(d->transformResult.available) ); writer->writeAttribute( "valid", QString::number(d->transformResult.valid) ); writer->writeAttribute( "status", d->transformResult.status ); writer->writeAttribute( "time", QString::number(d->transformResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"transformResult" writer->writeEndElement(); //"xyFourierTransformCurve" } //! Load from XML bool XYFourierTransformCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierTransformCurve); if (!reader->isStartElement() || reader->name() != "xyFourierTransformCurve") { reader->raiseError(i18n("no xy Fourier transform curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierTransformCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "transformData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", transformData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last()); READ_INT_VALUE("type", transformData.type, nsl_dft_result_type); READ_INT_VALUE("twoSided", transformData.twoSided, bool); READ_INT_VALUE("shifted", transformData.shifted, bool); READ_INT_VALUE("xScale", transformData.xScale, nsl_dft_xscale); READ_INT_VALUE("windowType", transformData.windowType, nsl_sf_window_type); } else if (!preview && reader->name() == "transformResult") { attribs = reader->attributes(); READ_INT_VALUE("available", transformResult.available, int); READ_INT_VALUE("valid", transformResult.valid, int); READ_STRING_VALUE("status", transformResult.status); READ_INT_VALUE("time", transformResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurvePrivate.h b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurvePrivate.h index 5f1089b39..8d1687d5c 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurvePrivate.h +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurvePrivate.h @@ -1,66 +1,63 @@ /*************************************************************************** File : XYFourierTransformCurvePrivate.h Project : LabPlot Description : Private members of XYFourierTransformCurve -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef XYFOURIERTRANSFORMCURVEPRIVATE_H #define XYFOURIERTRANSFORMCURVEPRIVATE_H #include "backend/worksheet/plots/cartesian/XYCurvePrivate.h" #include "backend/worksheet/plots/cartesian/XYFourierTransformCurve.h" -#include -#include - class XYFourierTransformCurve; class Column; class XYFourierTransformCurvePrivate: public XYCurvePrivate { public: explicit XYFourierTransformCurvePrivate(XYFourierTransformCurve*); ~XYFourierTransformCurvePrivate(); void recalculate(); const AbstractColumn* xDataColumn; //* xVector; QVector* yVector; XYFourierTransformCurve* const q; // private: // void writeSolverState(gsl_multifit_fdfsolver* s); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp index 771c5e853..96258ad05 100644 --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -1,421 +1,419 @@ /*************************************************************************** File : XYIntegrationCurve.cpp Project : LabPlot Description : A xy-curve defined by an integration -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYIntegrationCurve \brief A xy-curve defined by an integration \ingroup worksheet */ #include "XYIntegrationCurve.h" #include "XYIntegrationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" -#include // isnan -#include // DBL_MIN extern "C" { #include } #include #include #include #include XYIntegrationCurve::XYIntegrationCurve(const QString& name) : XYCurve(name, new XYIntegrationCurvePrivate(this)) { init(); } XYIntegrationCurve::XYIntegrationCurve(const QString& name, XYIntegrationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYIntegrationCurve::~XYIntegrationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYIntegrationCurve::init() { Q_D(XYIntegrationCurve); //TODO: read from the saved settings for XYIntegrationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYIntegrationCurve::recalculate() { Q_D(XYIntegrationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYIntegrationCurve::icon() const { return QIcon::fromTheme("labplot-xy-integration-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYIntegrationCurve::xDataColumnPath() const { Q_D(const XYIntegrationCurve); return d->xDataColumnPath; } const QString& XYIntegrationCurve::yDataColumnPath() const { Q_D(const XYIntegrationCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, XYIntegrationCurve::IntegrationData, integrationData, integrationData) const XYIntegrationCurve::IntegrationResult& XYIntegrationCurve::integrationResult() const { Q_D(const XYIntegrationCurve); return d->integrationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYIntegrationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYIntegrationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYIntegrationCurve); if (column != d->xDataColumn) { exec(new XYIntegrationCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYIntegrationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYIntegrationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYIntegrationCurve); if (column != d->yDataColumn) { exec(new XYIntegrationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYIntegrationCurve, SetIntegrationData, XYIntegrationCurve::IntegrationData, integrationData, recalculate); void XYIntegrationCurve::setIntegrationData(const XYIntegrationCurve::IntegrationData& integrationData) { Q_D(XYIntegrationCurve); exec(new XYIntegrationCurveSetIntegrationDataCmd(d, integrationData, i18n("%1: set options and perform the integration"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYIntegrationCurvePrivate::XYIntegrationCurvePrivate(XYIntegrationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYIntegrationCurvePrivate::~XYIntegrationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYIntegrationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create integration result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result integrationResult = XYIntegrationCurve::IntegrationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the integration to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (integrationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = integrationData.xRange.first(); xmax = integrationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } const size_t n = xdataVector.size(); // number of data points to integrate if (n < 2) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Not enough data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // integration settings const nsl_int_method_type method = integrationData.method; const bool absolute = integrationData.absolute; DEBUG("method:"<resize(np); yVector->resize(np); memcpy(xVector->data(), xdata, np*sizeof(double)); memcpy(yVector->data(), ydata, np*sizeof(double)); /////////////////////////////////////////////////////////// //write the result integrationResult.available = true; integrationResult.valid = true; integrationResult.status = QString::number(status); integrationResult.elapsedTime = timer.elapsed(); integrationResult.value = ydata[np-1]; //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYIntegrationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYIntegrationCurve); writer->writeStartElement("xyIntegrationCurve"); //write xy-curve information XYCurve::save(writer); //write xy-integration-curve specific information // integration data writer->writeStartElement("integrationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "autoRange", QString::number(d->integrationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->integrationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->integrationData.xRange.last()) ); writer->writeAttribute( "method", QString::number(d->integrationData.method) ); writer->writeAttribute( "absolute", QString::number(d->integrationData.absolute) ); writer->writeEndElement();// integrationData // integration results (generated columns) writer->writeStartElement("integrationResult"); writer->writeAttribute( "available", QString::number(d->integrationResult.available) ); writer->writeAttribute( "valid", QString::number(d->integrationResult.valid) ); writer->writeAttribute( "status", d->integrationResult.status ); writer->writeAttribute( "time", QString::number(d->integrationResult.elapsedTime) ); writer->writeAttribute( "value", QString::number(d->integrationResult.value) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"integrationResult" writer->writeEndElement(); //"xyIntegrationCurve" } //! Load from XML bool XYIntegrationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYIntegrationCurve); if (!reader->isStartElement() || reader->name() != "xyIntegrationCurve") { reader->raiseError(i18n("no xy integration curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyIntegrationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "integrationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", integrationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", integrationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", integrationData.xRange.last()); READ_INT_VALUE("method", integrationData.method, nsl_int_method_type); READ_INT_VALUE("absolute", integrationData.absolute, bool); } else if (!preview && reader->name() == "integrationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", integrationResult.available, int); READ_INT_VALUE("valid", integrationResult.valid, int); READ_STRING_VALUE("status", integrationResult.status); READ_INT_VALUE("time", integrationResult.elapsedTime, int); READ_DOUBLE_VALUE("value", integrationResult.value); } else if (!preview && reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp index 70bbf2eee..a83f8e273 100644 --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -1,621 +1,619 @@ /*************************************************************************** File : XYInterpolationCurve.cpp Project : LabPlot Description : A xy-curve defined by an interpolation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 20016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYInterpolationCurve \brief A xy-curve defined by an interpolation \ingroup worksheet */ #include "XYInterpolationCurve.h" #include "XYInterpolationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" -#include // isnan -#include // DBL_MIN extern "C" { #include #include #include "backend/nsl/nsl_diff.h" #include "backend/nsl/nsl_int.h" } #include #include #include XYInterpolationCurve::XYInterpolationCurve(const QString& name) : XYCurve(name, new XYInterpolationCurvePrivate(this)) { init(); } XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYInterpolationCurve::~XYInterpolationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYInterpolationCurve::init() { Q_D(XYInterpolationCurve); //TODO: read from the saved settings for XYInterpolationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYInterpolationCurve::recalculate() { Q_D(XYInterpolationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYInterpolationCurve::icon() const { return QIcon::fromTheme("labplot-xy-interpolation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYInterpolationCurve::xDataColumnPath() const { Q_D(const XYInterpolationCurve); return d->xDataColumnPath; } const QString& XYInterpolationCurve::yDataColumnPath() const { Q_D(const XYInterpolationCurve); return d->yDataColumnPath; } BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, XYInterpolationCurve::InterpolationData, interpolationData, interpolationData) const XYInterpolationCurve::InterpolationResult& XYInterpolationCurve::interpolationResult() const { Q_D(const XYInterpolationCurve); return d->interpolationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYInterpolationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYInterpolationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYInterpolationCurve); if (column != d->xDataColumn) { exec(new XYInterpolationCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYInterpolationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYInterpolationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYInterpolationCurve); if (column != d->yDataColumn) { exec(new XYInterpolationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYInterpolationCurve, SetInterpolationData, XYInterpolationCurve::InterpolationData, interpolationData, recalculate); void XYInterpolationCurve::setInterpolationData(const XYInterpolationCurve::InterpolationData& interpolationData) { Q_D(XYInterpolationCurve); exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, i18n("%1: set options and perform the interpolation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYInterpolationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create interpolation result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result interpolationResult = XYInterpolationCurve::InterpolationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the interpolation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (interpolationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = interpolationData.xRange.first(); xmax = interpolationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to interpolate const unsigned int n = xdataVector.size(); if (n < 2) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Not enough data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // interpolation settings const nsl_interp_type type = interpolationData.type; const nsl_interp_pch_variant variant = interpolationData.variant; const double tension = interpolationData.tension; const double continuity = interpolationData.continuity; const double bias = interpolationData.bias; const nsl_interp_evaluate evaluate = interpolationData.evaluate; const unsigned int npoints = interpolationData.npoints; DEBUG("type:"<data(), yVector->data(), npoints); break; case nsl_interp_evaluate_second_derivative: nsl_diff_second_deriv_second_order(xVector->data(), yVector->data(), npoints); break; case nsl_interp_evaluate_integral: nsl_int_trapezoid(xVector->data(), yVector->data(), npoints, 0); break; } } // check values for (unsigned int i = 0; i < npoints; i++) { if ((*yVector)[i] > CartesianScale::LIMIT_MAX) (*yVector)[i] = CartesianScale::LIMIT_MAX; else if ((*yVector)[i] < CartesianScale::LIMIT_MIN) (*yVector)[i] = CartesianScale::LIMIT_MIN; } gsl_spline_free(spline); gsl_interp_accel_free(acc); /////////////////////////////////////////////////////////// //write the result interpolationResult.available = true; interpolationResult.valid = true; interpolationResult.status = gslErrorToString(status); interpolationResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYInterpolationCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYInterpolationCurve); writer->writeStartElement("xyInterpolationCurve"); //write xy-curve information XYCurve::save(writer); //write xy-interpolation-curve specific information // interpolation data writer->writeStartElement("interpolationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeAttribute( "autoRange", QString::number(d->interpolationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->interpolationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->interpolationData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->interpolationData.type) ); writer->writeAttribute( "variant", QString::number(d->interpolationData.variant) ); writer->writeAttribute( "tension", QString::number(d->interpolationData.tension) ); writer->writeAttribute( "continuity", QString::number(d->interpolationData.continuity) ); writer->writeAttribute( "bias", QString::number(d->interpolationData.bias) ); writer->writeAttribute( "npoints", QString::number(d->interpolationData.npoints) ); writer->writeAttribute( "pointsMode", QString::number(d->interpolationData.pointsMode) ); writer->writeAttribute( "evaluate", QString::number(d->interpolationData.evaluate) ); writer->writeEndElement();// interpolationData // interpolation results (generated columns) writer->writeStartElement("interpolationResult"); writer->writeAttribute( "available", QString::number(d->interpolationResult.available) ); writer->writeAttribute( "valid", QString::number(d->interpolationResult.valid) ); writer->writeAttribute( "status", d->interpolationResult.status ); writer->writeAttribute( "time", QString::number(d->interpolationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"interpolationResult" writer->writeEndElement(); //"xyInterpolationCurve" } //! Load from XML bool XYInterpolationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYInterpolationCurve); if (!reader->isStartElement() || reader->name() != "xyInterpolationCurve") { reader->raiseError(i18n("no xy interpolation curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyInterpolationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "interpolationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_INT_VALUE("autoRange", interpolationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", interpolationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", interpolationData.xRange.last()); READ_INT_VALUE("type", interpolationData.type, nsl_interp_type); READ_INT_VALUE("variant", interpolationData.variant, nsl_interp_pch_variant); READ_DOUBLE_VALUE("tension", interpolationData.tension); READ_DOUBLE_VALUE("continuity", interpolationData.continuity); READ_DOUBLE_VALUE("bias", interpolationData.bias); READ_INT_VALUE("npoints", interpolationData.npoints, int); READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); } else if (!preview && reader->name() == "interpolationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", interpolationResult.available, int); READ_INT_VALUE("valid", interpolationResult.valid, int); READ_STRING_VALUE("status", interpolationResult.status); READ_INT_VALUE("time", interpolationResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/doc/coding_style.dox b/src/doc/coding_style.dox index 9bd8aa7c5..da5cd5882 100644 --- a/src/doc/coding_style.dox +++ b/src/doc/coding_style.dox @@ -1,133 +1,132 @@ /**\page coding_style Coding Style The following rules are not used everywhere (yet), but are intended as guidelines for new code and eventually old code should be adapted as well. They apply to C++ and C code. The standards are C++11 and C99. \section files Files - Files use Unix-style line endings ('\\n'). - C++ source files use “.cpp” as extension, C source code use "*.c" and header files use “.h”. - The code is documented using Doxygen comments which are placed in the source files, not the header files. - Every file should be named exactly like the class inside and there should be only one class per file, with the exception of really short classes. Very short classes can be bundled in one file which then is named using all lower case letters. \section identifier Identifier names - Class names start with a capital letter and use CamelCase, acronyms in class names are use like normal words. Example: MySuperHtmlToPdfConverter - Function/method names start with a lower case letter and use CamelCase Example: doSomethingImportant() - Variable/object names start with a lower case letter and use CamelCase, underscores are used for special prefixes only. - Only private class member variables are prefixed with “m_” to distinguish them easily. d-pointer and UI-widgets are called d and ui, respectively, i.e. without prefix. - Property access methods use Qt style: property() and setProperty(), except for boolean properties (isVisible(), hasChanged()). Accessor functions (getter/setter) can be done using macros. - Avoid abbreviations, except for local counters and temporaries whose purpose is obvious. \section indent Indentation, spacing and line breaks - Tabs are used for indentation because they allow everyone to choose the indentation depth for him/herself. - Try to keep lines shorter than 100 characters, inserting line breaks as necessary and indent the following lines to improved readability. - included headers should be in order: own header, local header, Qt/KDE header, system header, extern header - Opening braces (‘{‘) are placed behind the statement and are preceded by a space. This also goes for function implementations, class, struct and namespace declarations, which are exceptions in other coding styles. Example: @code void MyClass::doSomething() { if (condition) { ... } ... } @endcode - Opening brackets (‘(‘) are preceded by a space in for/switch/if/while statements, but not for function calls. Example: @code if (condition) { doSomething(myData); ... } @endcode - For pointers or references, use a single space after ‘*’ or ‘&’ (i.e. specifier is bound to the data type not the name). Example: @code void doSomething(int* dataPointer, const QString& name); ... = static_cast(...) @endcode “public” and namespace enclosures are not indented. Example: @code class MyClass: public QObject { public: void doSomething(); @endcode “case” of switch is not indented. “default” should be present if type of condition is not an enum. Example: @code switch (condition) { case 1: handleCaseOne(); break; case 2: { int i=0; ... break; } ... default: ... } @endcode - Each comma in a function call or semicolon in a for statement is followed by a space character; no space before the first and after the last argument. Example: @code for (int i = 0; i < 10; i++) { ... doSomething(arg1, arg2, arg3); } @endcode "else" (and "catch" if it is ever used) is put after the closing brace like this: "} else {" - Use as many brackets in conditions/math terms as you see fit for optimum readability. All operators ('=', '==', '<', '+', '-', '<<', etc.) and castings should always be surrounded by spaces. Examples: @code foo/2 + bar/4 + baz/3 for (int i = 0; i < bar+1; i++) var = (foo - 1) + (bar - 2) + (baz - 3) char *s = (char*) malloc(LENGTH * sizeof(char)); @endcode - enum and structs should be defined first in a class - parameter names in a method definition should only be used to explains the usage of the parameter - In SIGNAL() and SLOT() macros, use as little whitespace as possible. This gives a little speed up since Qt does not have to normalize the signal/slot name. \section constructs Usage of specific constructs * Use C++ casting (static_cast, const_cast, dynamic_cast) in C++ and qobject_cast on Qt classes since they include checks see https://en.wikibooks.org/wiki/C%2B%2B_Programming/Programming_Languages/C%2B%2B/Code/Statements/Variables/Type_Casting * In C++ use Qt container instead of STL container https://marcmutz.wordpress.com/effective-qt/containers/ * In C++ use range-based loops instead of foreach/Q_FOREACH https://www.kdab.com/goodbye-q_foreach/ * The keyword 'auto' can be used in range-based loops but should be avoided else to maintain readability * use smart pointers unique_ptr when possible and shared_ptr otherwise. * Use the 'override' specifier when overriding virtual functions from the base class http://en.cppreference.com/w/cpp/language/override * Use braces to enclose a single statement only for readability * In C++ nullptr should be used instead of bug-prone NULL and 0. * #include <...> vs. #include "...": Include headers from external libraries using angle brackets (as in #include ) and headers from LabPlot/SciDAVis using double quotes (as in #include "core/AbstractAspect.h"). Rationale: Headers of external libraries are never in the same directory as the including file, so it makes sense to use the angle bracket form (which searches only in directories specified using -I). If you work with a build system that does not include the current source directory, or disable CMAKE_INCLUDE_CURRENT_DIR, then all angle-bracket-includes referencing LabPlot/SciDAVis headers will break. Excluding the current directory from -I dirs may be desirable one day when using a new library or a new version of a library containing a header file with the same name as one of our headers. * Use DEBUG() macro for debugging code when possible and QDEBUG() only for special Qt data types. DEBUG() works on all supported systems. @code QString string; DEBUG(" string : " << string.toStdString()); @endcode * Use Qt functions for user messages: qDebug(), qWarning(), qCritical(), qFatal(). Check conditions with Q_ASSERT(cond) or Q_ASSERT_X(cond, where, what) and pointers with Q_CHECK_PTR(ptr). -* Import C header (from GSL etc.) with extern statement. We use cmath instead of math.h and "std::" prefix (C++11). Example: +* Import C header (from GSL etc.) with extern statement. We use "std::" prefix (C++11) and try to avoid C header like cmath, cfloat etc. by using corresponding C++ constructs (fabs() -> std::abs(), DBL_MAX -> std::numeric_limits::max(), round() -> qRound()). Example: @code extern "C" { #include } - #include if (std::isnan(x)) { ... } @endcode \section links Links Apart from that, the following links are recommended as guidelines for the coding style: http://techbase.kde.org/index.php?title=Policies/Library_Code_Policy http://doc.trolltech.com/qq/qq13-apis.html http://techbase.kde.org/Policies/Kdelibs_Coding_Style */ diff --git a/src/kdefrontend/datasources/ImportOpj.cpp b/src/kdefrontend/datasources/ImportOpj.cpp index 21661e517..974681a9c 100644 --- a/src/kdefrontend/datasources/ImportOpj.cpp +++ b/src/kdefrontend/datasources/ImportOpj.cpp @@ -1,717 +1,718 @@ /*************************************************************************** File : ImportOpj.cpp Project : LabPlot Description : Import Origin project -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) adapted from SciDAVis (importOPJ.cpp) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportOpj.h" #include "backend/lib/macros.h" #include "backend/core/Project.h" #include "backend/core/Workbook.h" #include "backend/core/column/Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/note/Note.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/TextLabel.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include #include #include #include /*! \class ImportOpj \brief Importing an Origin project. \ingroup kdefrontend */ ImportOpj::ImportOpj(Folder *folder, const QString& filename, bool preview) : p(folder) { + Q_UNUSED(preview); DEBUG("Opj import started ..."); OriginFile opj((const char *)filename.toLocal8Bit()); int status = opj.parse(); DEBUG("Parsing done with status " << status); // if (status != 0) // return; DEBUG("Starting conversion ..."); importTables(opj); //TODO importGraphs(opj); //TODO importNotes(opj); // if(filename.endsWith(".opj", Qt::CaseInsensitive)) // createProjectTree(opj); } int ImportOpj::importTables(const OriginFile &opj) { // excels (origin workbook with one or more sheets) for (unsigned int e = 0; e < opj.excelCount(); ++e) { DEBUG("Reading Spreadsheet " << e); Origin::Excel excelwb = opj.excel(e); if (excelwb.sheets.size() == 1) { // single sheet -> spreadsheet Origin::SpreadSheet spread = excelwb.sheets[0]; spread.name = excelwb.name; spread.label = excelwb.label; importSpreadsheet(0, opj, spread); } else { // multiple sheets -> workbook Workbook *workbook = new Workbook(0, excelwb.name.c_str() + QString(" - ") + excelwb.label.c_str()); for (unsigned int s = 0; s < excelwb.sheets.size(); ++s) { Origin::SpreadSheet spread = excelwb.sheets[s]; importSpreadsheet(workbook, opj, spread); } p->addChildFast(workbook); } } // matrices for (unsigned int m = 0; m < opj.matrixCount(); ++m) { Origin::Matrix matrix = opj.matrix(m); importMatrix(opj, matrix); } return 0; } int ImportOpj::importSpreadsheet(Workbook* workbook, const OriginFile &opj, const Origin::SpreadSheet &spread) { Q_UNUSED(opj); int cols = spread.columns.size(); int rows = spread.maxRows; //TODO if (rows > 1000) rows = 1000; if (!cols) // do not create spreadsheet without columns return -1; //TODO QLocale locale = mw->locale(); Spreadsheet* spreadsheet; if (workbook == 0 && spread.label.length() > 0) // single sheet with label (long name) spreadsheet = new Spreadsheet(0, spread.name.c_str() + QString(" - ") + spread.label.c_str()); else // multiple sheets (TODO: name of sheets are not saved in liborigin: "Sheet1", "Sheet2", ...) spreadsheet = new Spreadsheet(0, spread.name.c_str()); DEBUG("OK rows = " << rows); spreadsheet->setRowCount(rows); DEBUG("OK DONE"); spreadsheet->setColumnCount(cols); int scaling_factor = 10; //in Origin width is measured in characters while here in pixels --- need to be accurate for (int j = 0; j < cols; ++j) { Origin::SpreadColumn column = spread.columns[j]; Column *col = spreadsheet->column(j); QString name(column.name.c_str()); col->setName(name.replace(QRegExp(".*_"),"")); if (column.command.size() > 0) col->setFormula(Interval(0, rows), QString(column.command.c_str())); col->setComment(QString(column.comment.c_str())); col->setWidth((int)column.width * scaling_factor); switch (column.type) { case Origin::SpreadColumn::X: col->setPlotDesignation(AbstractColumn::X); break; case Origin::SpreadColumn::Y: col->setPlotDesignation(AbstractColumn::Y); break; case Origin::SpreadColumn::Z: col->setPlotDesignation(AbstractColumn::Z); break; case Origin::SpreadColumn::XErr: col->setPlotDesignation(AbstractColumn::XError); break; case Origin::SpreadColumn::YErr: col->setPlotDesignation(AbstractColumn::YError); break; case Origin::SpreadColumn::Label: case Origin::SpreadColumn::NONE: default: col->setPlotDesignation(AbstractColumn::NoDesignation); } QString format; switch(column.valueType) { case Origin::Numeric: case Origin::TextNumeric: { /* TODO: check this A TextNumeric column in Origin is a column whose filled cells contain either a double or a string. Here there is no equivalent column type. Set the column type as 'Numeric' or 'Text' depending on the type of first element in column. IDEA: Add a "per column" flag, settable at import dialog, to choose between both types. */ double datavalue; bool setAsText = false; col->setColumnMode(AbstractColumn::Numeric); //printf("column has %ld rows\n", column.data.size()); for (int i = 0; i < std::min((int)column.data.size(), rows); ++i) { Origin::variant v(column.data.at(i)); //printf("i=%d type = %d\n", i, v.type); if (v.type() == Origin::Variant::V_DOUBLE) { //printf("DOUBLE !\n"); datavalue = v.as_double(); //printf("datavalue = %g\n", datavalue); if (datavalue == _ONAN) continue; // mark for empty cell if (!setAsText) col->setValueAt(i, datavalue); //TODO else // convert double to string for Text columns // col->setTextAt(i, locale.toString(datavalue, 'g', 16)); } else if (v.type() == Origin::Variant::V_STRING) { // string //printf("STRING !\n"); if (!setAsText && i == 0) { col->setColumnMode(AbstractColumn::Text); setAsText = true; } col->setTextAt(i, v.as_string()); } else { printf("ERROR: data type = %d unknown!\n", v.type()); } } if (column.numericDisplayType != 0) { int f = 0; switch(column.valueTypeSpecification) { case Origin::Decimal: f=1; break; case Origin::Scientific: f=2; break; case Origin::Engineering: case Origin::DecimalWithMarks: break; } Double2StringFilter *filter = static_cast(col->outputFilter()); filter->setNumericFormat(f); filter->setNumDigits(column.decimalPlaces); } break; } case Origin::Text: col->setColumnMode(AbstractColumn::Text); for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setTextAt(i, column.data[i].as_string()); break; case Origin::Time: { switch(column.valueTypeSpecification + 128) { case Origin::TIME_HH_MM: format="hh:mm"; break; case Origin::TIME_HH: format="hh"; break; case Origin::TIME_HH_MM_SS: format="hh:mm:ss"; break; case Origin::TIME_HH_MM_SS_ZZ: format="hh:mm:ss.zzz"; break; case Origin::TIME_HH_AP: format="hh ap"; break; case Origin::TIME_HH_MM_AP: format="hh:mm ap"; break; case Origin::TIME_MM_SS: format="mm:ss"; break; case Origin::TIME_MM_SS_ZZ: format="mm:ss.zzz"; break; case Origin::TIME_HHMM: format="hhmm"; break; case Origin::TIME_HHMMSS: format="hhmmss"; break; case Origin::TIME_HH_MM_SS_ZZZ: format="hh:mm:ss.zzz"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Date: { switch(column.valueTypeSpecification) { case Origin::DATE_DD_MM_YYYY: format="dd/MM/yyyy"; break; case Origin::DATE_DD_MM_YYYY_HH_MM: format="dd/MM/yyyy HH:mm"; break; case Origin::DATE_DD_MM_YYYY_HH_MM_SS: format="dd/MM/yyyy HH:mm:ss"; break; case Origin::DATE_DDMMYYYY: case Origin::DATE_DDMMYYYY_HH_MM: case Origin::DATE_DDMMYYYY_HH_MM_SS: format="dd.MM.yyyy"; break; case Origin::DATE_MMM_D: format="MMM d"; break; case Origin::DATE_M_D: format="M/d"; break; case Origin::DATE_D: format="d"; break; case Origin::DATE_DDD: case Origin::DATE_DAY_LETTER: format="ddd"; break; case Origin::DATE_YYYY: format="yyyy"; break; case Origin::DATE_YY: format="yy"; break; case Origin::DATE_YYMMDD: case Origin::DATE_YYMMDD_HH_MM: case Origin::DATE_YYMMDD_HH_MM_SS: case Origin::DATE_YYMMDD_HHMM: case Origin::DATE_YYMMDD_HHMMSS: format="yyMMdd"; break; case Origin::DATE_MMM: case Origin::DATE_MONTH_LETTER: format="MMM"; break; case Origin::DATE_M_D_YYYY: format="M-d-yyyy"; break; default: format="dd.MM.yyyy"; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Month: { switch (column.valueTypeSpecification) { case Origin::MONTH_MMM: format = "MMM"; break; case Origin::MONTH_MMMM: format = "MMMM"; break; case Origin::MONTH_LETTER: format = "M"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Month); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Day: { switch(column.valueTypeSpecification) { case Origin::DAY_DDD: format = "ddd"; break; case Origin::DAY_DDDD: format = "dddd"; break; case Origin::DAY_LETTER: format = "d"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Day); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::ColumnHeading: case Origin::TickIndexedDataset: case Origin::Categorical: break; } } //TODO // if (spread.hidden || spread.loose) // mw->hideWindow(spreadsheet); if (workbook == 0) // single sheet p->addChildFast(spreadsheet); else // multiple sheets workbook->addChild(spreadsheet); return 0; } int ImportOpj::importMatrix(const OriginFile &opj, const Origin::Matrix &matrix) { Q_UNUSED(opj); unsigned int layers = matrix.sheets.size(); int scaling_factor = 10; //in Origin width is measured in characters while here in pixels --- need to be accurate for (unsigned int l = 0; l < layers; ++l) { Origin::MatrixSheet layer = matrix.sheets[l]; int colCount = layer.columnCount; int rowCount = layer.rowCount; Matrix* m = new Matrix(0, matrix.name.c_str()); m->setRowCount(rowCount); m->setColumnCount(colCount); if (!m) return false; m->setFormula(layer.command.c_str()); for (int j = 0; j < colCount; j++) m->setColumnWidth(j, layer.width * scaling_factor); for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) { m->setCell(i, j, layer.data[j + i*colCount]); } } char format = 'g'; int prec = 6; switch (layer.valueTypeSpecification) { case 0: //Decimal 1000 format='f'; prec = layer.decimalPlaces; break; case 1: //Scientific format='e'; prec = layer.decimalPlaces; break; case 2: //Engineering case 3: //Decimal 1,000 format='g'; prec = layer.significantDigits; break; } //TODO: prec not support by Matrix Q_UNUSED(prec); m->setNumericFormat(format); p->addChildFast(m); } return 0; } int ImportOpj::importNotes(const OriginFile &opj) { // int visible_count = 0; for(unsigned int n = 0; n < opj.noteCount(); ++n) { Origin::Note _note = opj.note(n); QString name = _note.name.c_str(); QRegExp rx("^@(\\S+)$"); if(rx.indexIn(name) == 0) name = name.mid(2, name.length() - 3); Note *note = new Note(name); if(!note) return -1; //note->setWindowLabel(_note.label.c_str()); note->setNote(QString(_note.text.c_str())); // TODO //cascade the notes //int dx = 20; //int dy = note->parentWidget()->frameGeometry().height() - note->height(); //note->parentWidget()->move(QPoint(visible_count*dx+xoffset*OBJECTXOFFSET, visible_count*dy)); p->addChildFast(note); // visible_count++; } // if(visible_count > 0) // xoffset++; return 0; } int ImportOpj::importGraphs(const OriginFile &opj) { for(unsigned int g = 0; g < opj.graphCount(); ++g) { Origin::Graph graph = opj.graph(g); Worksheet *worksheet = new Worksheet(0, graph.name.c_str()); if (!worksheet) return -1; // worksheet->hide();//!hack used in order to avoid resize and repaint events worksheet->setComment(graph.label.c_str()); for (const auto& layer: graph.layers) { CartesianPlot* plot = new CartesianPlot(""); if (!plot) return -2; if (!layer.legend.text.empty()) { CartesianPlotLegend* legend = new CartesianPlotLegend(plot, ""); TextLabel* title = new TextLabel(legend->name(), TextLabel::PlotLegendTitle); DEBUG("TEXT =" << layer.legend.text.c_str()); QDEBUG("PARSED TEXT =" << parseOriginText(QString::fromLocal8Bit(layer.legend.text.c_str()))); title->setText(parseOriginText(QString::fromLocal8Bit(layer.legend.text.c_str()))); //legend->title() = title; legend->addChild(title); plot->addChild(legend); } // TODO: we only support one legend //add texts for (const auto &s: layer.texts) DEBUG("EXTRA TEXT =" << s.text.c_str()); // plot->newLegend(parseOriginText(QString::fromLocal8Bit(layer.texts[i].text.c_str()))); // int auto_color = 0; // int style = 0; for (const auto& curve: layer.curves) { QString data(curve.dataName.c_str()); // int color = 0; switch(curve.type) { case Origin::GraphCurve::Line: // style = Graph::Line; break; case Origin::GraphCurve::Scatter: // style = Graph::Scatter; break; case Origin::GraphCurve::LineSymbol: // style = Graph::LineSymbols; break; case Origin::GraphCurve::ErrorBar: case Origin::GraphCurve::XErrorBar: // style = Graph::ErrorBars; break; case Origin::GraphCurve::Column: // style = Graph::VerticalBars; break; case Origin::GraphCurve::Bar: // style = Graph::HorizontalBars; break; case Origin::GraphCurve::Histogram: // style = Graph::Histogram; break; default: continue; } /* QString tableName; switch(data[0].toAscii()) { case 'T': case 'E': { tableName = data.right(data.length() - 2); Table* table = mw->table(tableName); if (!table) break; if(style == Graph::ErrorBars) { int flags=_curve.symbolType; graph->addErrorBars(QString("%1_%2").arg(tableName, _curve.xColumnName.c_str()), table, QString("%1_%2").arg(tableName, _curve.yColumnName.c_str()), ((flags&0x10)==0x10?0:1), ceil(_curve.lineWidth), ceil(_curve.symbolSize), QColor(Qt::black), (flags&0x40)==0x40, (flags&2)==2, (flags&1)==1); } else if(style == Graph::Histogram) { graph->insertCurve(table, QString("%1_%2").arg(tableName, _curve.yColumnName.c_str()), style); } else { graph->insertCurve(table, QString("%1_%2").arg(tableName, _curve.xColumnName.c_str()), QString("%1_%2").arg(tableName, _curve.yColumnName.c_str()), style); } break; } //TODO } */ /* CurveLayout cl = graph->initCurveLayout(style, layer.curves.size()); cl.sSize = ceil(_curve.symbolSize*0.5); cl.penWidth = _curve.symbolThickness; color = _curve.symbolColor.regular; if((style == Graph::Scatter || style == Graph::LineSymbols) && color == 0xF7) // 0xF7 -Automatic color color = auto_color++; cl.symCol = color; switch(_curve.symbolType & 0xFF) { case 0: //NoSymbol cl.sType = 0; break; //TODO } */ //TODO } worksheet->addChild(plot); } p->addChildFast(worksheet); } return 0; } QString ImportOpj::parseOriginText(const QString &str) { QStringList lines = str.split("\n"); QString text = ""; for (int i = 0; i < lines.size(); ++i) { if(i > 0) text.append("\n"); text.append(parseOriginTags(lines[i])); } return text; } QString strreverse(const QString &str) { //QString reversing QByteArray ba = str.toLocal8Bit(); std::reverse(ba.begin(), ba.end()); return QString(ba); } // taken from SciDAVis QString ImportOpj::parseOriginTags(const QString &str) { QString line = str; //replace \l(...) and %(...) tags QRegExp rxline("\\\\\\s*l\\s*\\(\\s*\\d+\\s*\\)"); QRegExp rxcol("\\%\\(\\d+\\)"); int pos = rxline.indexIn(line); while (pos > -1) { QString value = rxline.cap(0); int len=value.length(); value.replace(QRegExp(" "),""); value="\\c{"+value.mid(3,value.length()-4)+"}"; line.replace(pos, len, value); pos = rxline.indexIn(line); } //Lookbehind conditions are not supported - so need to reverse string QRegExp rx("\\)[^\\)\\(]*\\((?!\\s*[buig\\+\\-]\\s*\\\\)"); QRegExp rxfont("\\)[^\\)\\(]*\\((?![^\\:]*\\:f\\s*\\\\)"); QString linerev = strreverse(line); QString lBracket=strreverse("&lbracket;"); QString rBracket=strreverse("&rbracket;"); QString ltagBracket=strreverse("<agbracket;"); QString rtagBracket=strreverse("&rtagbracket;"); int pos1 = rx.indexIn(linerev); int pos2 = rxfont.indexIn(linerev); while (pos1>-1 || pos2>-1) { if(pos1==pos2) { QString value = rx.cap(0); int len=value.length(); value=rBracket+value.mid(1,len-2)+lBracket; linerev.replace(pos1, len, value); } else if ((pos1>pos2&&pos2!=-1)||pos1==-1) { QString value = rxfont.cap(0); int len=value.length(); value=rtagBracket+value.mid(1,len-2)+ltagBracket; linerev.replace(pos2, len, value); } else if ((pos2>pos1&&pos1!=-1)||pos2==-1) { QString value = rx.cap(0); int len=value.length(); value=rtagBracket+value.mid(1,len-2)+ltagBracket; linerev.replace(pos1, len, value); } pos1=rx.indexIn(linerev); pos2=rxfont.indexIn(linerev); } linerev.replace(ltagBracket, "("); linerev.replace(rtagBracket, ")"); line = strreverse(linerev); //replace \b(...), \i(...), \u(...), \g(...), \+(...), \-(...), \f:font(...) tags const QString rxstr[] = { "\\\\\\s*b\\s*\\(", "\\\\\\s*i\\s*\\(", "\\\\\\s*u\\s*\\(", "\\\\\\s*g\\s*\\(", "\\\\\\s*\\+\\s*\\(", "\\\\\\s*\\-\\s*\\(", "\\\\\\s*f\\:[^\\(]*\\("}; int postag[]={0,0,0,0,0,0,0}; QString ltag[]={"","","","","","",""}; QString rtag[]={"","","","","","",""}; QRegExp rxtags[7]; for(int i=0; i<7; ++i) rxtags[i].setPattern(rxstr[i]+"[^\\(\\)]*\\)"); bool flag=true; while(flag) { for(int i=0; i<7; ++i) { postag[i] = rxtags[i].indexIn(line); while (postag[i] > -1) { QString value = rxtags[i].cap(0); int len=value.length(); int pos2=value.indexOf("("); if(i<6) value=ltag[i]+value.mid(pos2+1,len-pos2-2)+rtag[i]; else { int posfont=value.indexOf("f:"); value=ltag[i].arg(value.mid(posfont+2,pos2-posfont-2))+value.mid(pos2+1,len-pos2-2)+rtag[i]; } line.replace(postag[i], len, value); postag[i] = rxtags[i].indexIn(line); } } flag=false; for(int i=0; i<7; ++i) { if(rxtags[i].indexIn(line)>-1) { flag=true; break; } } } //replace unclosed tags for(int i=0; i<6; ++i) line.replace(QRegExp(rxstr[i]), ltag[i]); rxfont.setPattern(rxstr[6]); pos = rxfont.indexIn(line); while (pos > -1) { QString value = rxfont.cap(0); int len=value.length(); int posfont=value.indexOf("f:"); value=ltag[6].arg(value.mid(posfont+2,len-posfont-3)); line.replace(pos, len, value); pos = rxfont.indexIn(line); } line.replace("&lbracket;", "("); line.replace("&rbracket;", ")"); return line; } diff --git a/src/kdefrontend/datasources/ImportProjectDialog.cpp b/src/kdefrontend/datasources/ImportProjectDialog.cpp index 7603a09ff..14e1ddb63 100644 --- a/src/kdefrontend/datasources/ImportProjectDialog.cpp +++ b/src/kdefrontend/datasources/ImportProjectDialog.cpp @@ -1,314 +1,337 @@ /*************************************************************************** File : ImportProjectDialog.cpp Project : LabPlot Description : import project dialog -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportProjectDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/datasources/projects/LabPlotProjectParser.h" #include "backend/datasources/projects/OriginProjectParser.h" #include "kdefrontend/MainWin.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #include #include #include /*! \class ImportProjectDialog \brief Dialog for importing project files. \ingroup kdefrontend */ ImportProjectDialog::ImportProjectDialog(MainWin* parent, ProjectType type) : QDialog(parent), m_mainWin(parent), m_projectParser(nullptr), m_projectType(type), m_aspectTreeModel(new AspectTreeModel(parent->project())) { QVBoxLayout* vLayout = new QVBoxLayout(this); //main widget QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); vLayout->addWidget(mainWidget); + ui.tvPreview->setAnimated(true); + ui.tvPreview->setAlternatingRowColors(true); + ui.tvPreview->setSelectionBehavior(QAbstractItemView::SelectRows); + ui.tvPreview->setSelectionMode(QAbstractItemView::ExtendedSelection); + ui.tvPreview->setUniformRowHeights(true); + ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); m_cbAddTo = new TreeViewComboBox(ui.gbImportTo); m_cbAddTo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); ui.gbImportTo->layout()->addWidget(m_cbAddTo); QList list; list << "Folder"; m_cbAddTo->setTopLevelClasses(list); m_aspectTreeModel->setSelectableAspects(list); m_cbAddTo->setModel(m_aspectTreeModel); m_bNewFolder = new QPushButton(ui.gbImportTo); m_bNewFolder->setIcon(QIcon::fromTheme("list-add")); m_bNewFolder->setToolTip(i18n("Add new folder")); ui.gbImportTo->layout()->addWidget(m_bNewFolder); //dialog buttons m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); //TODO: ok is only available if some project objects were selected vLayout->addWidget(m_buttonBox); + //ok-button is only enabled if some project objects were selected (s.a. ImportProjectDialog::selectionChanged()) + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + //Signals/Slots connect(ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString))); connect(ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile())); connect(m_bNewFolder, SIGNAL(clicked()), this, SLOT(newFolder())); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QString title; switch (m_projectType) { case (ProjectLabPlot): m_projectParser = new LabPlotProjectParser(); title = i18n("Import LabPlot Project"); break; case (ProjectOrigin): m_projectParser = new OriginProjectParser(); title = i18n("Import Origin Project"); break; } //dialog title and icon setWindowTitle(title); setWindowIcon(QIcon::fromTheme("document-import")); QTimer::singleShot(0, this, &ImportProjectDialog::loadSettings); } void ImportProjectDialog::loadSettings() { //restore saved settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); KWindowConfig::restoreWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } QApplication::processEvents(QEventLoop::AllEvents, 100); ui.leFileName->setText(conf.readEntry(lastImportedFile, "")); } ImportProjectDialog::~ImportProjectDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); QString lastImportedFile; switch (m_projectType) { case (ProjectLabPlot): lastImportedFile = QLatin1String("LastImportedLabPlotProject"); break; case (ProjectOrigin): lastImportedFile = QLatin1String("LastImportedOriginProject"); break; } conf.writeEntry(lastImportedFile, ui.leFileName->text()); } void ImportProjectDialog::setCurrentFolder(const Folder* folder) { m_cbAddTo->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(folder)); } void ImportProjectDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportProjectDialog::importTo()"); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(100); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::processEvents(QEventLoop::AllEvents, 100); + //determine the selected objects, convert the model indexes to string pathes + const QModelIndexList& indexes = ui.tvPreview->selectionModel()->selectedIndexes(); + QStringList selectedPathes; + for (int i=0; i(index.internalPointer()); + selectedPathes << aspect->path(); + } + QDEBUG("project objects to be imported: " << selectedPathes); + //import the selected project objects into the specified folder QTime timer; timer.start(); Folder* folder = static_cast(m_cbAddTo->currentModelIndex().internalPointer()); connect(m_projectParser, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); - m_projectParser->importTo(folder); + m_projectParser->importTo(folder ,selectedPathes); statusBar->showMessage( i18n("Project data imported in %1 seconds.", (float)timer.elapsed()/1000) ); QApplication::restoreOverrideCursor(); statusBar->removeWidget(progressBar); } /*! * show the content of the project in the tree view */ void ImportProjectDialog::refreshPreview() { QString project = ui.leFileName->text(); m_projectParser->setProjectFileName(project); ui.tvPreview->setModel(m_projectParser->model()); + connect(ui.tvPreview->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); + //show top-level containers only if (ui.tvPreview->model()) { QModelIndex root = ui.tvPreview->model()->index(0,0); showTopLevelOnly(root); } - ui.tvPreview->header()->resizeSection(0,0); - ui.tvPreview->header()->resizeSections(QHeaderView::ResizeToContents); + //extand the tree to show all available top-level objects and adjust the header sizes ui.tvPreview->expandAll(); + ui.tvPreview->header()->resizeSections(QHeaderView::ResizeToContents); } /*! Hides the non-toplevel items of the model used in the tree view. */ void ImportProjectDialog::showTopLevelOnly(const QModelIndex& index) { int rows = index.model()->rowCount(index); for (int i = 0; i < rows; ++i) { QModelIndex child = index.child(i, 0); showTopLevelOnly(child); const AbstractAspect* aspect = static_cast(child.internalPointer()); ui.tvPreview->setRowHidden(i, index, !isTopLevel(aspect)); } } /*! checks whether \c aspect is one of the allowed top level types */ bool ImportProjectDialog::isTopLevel(const AbstractAspect* aspect) const { foreach (const char* classString, m_projectParser->topLevelClasses()) { if (aspect->inherits(classString)) return true; } return false; } //############################################################################## //################################# SLOTS #################################### //############################################################################## -void ImportProjectDialog::selectionChanged() { - //TODO: +void ImportProjectDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { + Q_UNUSED(deselected); + //determine the dependent objects and select/deselect them too + //TODO: - //Ok-button is only available if some project objects were selected - bool objectsSelected = true; //TODO + //Ok-button is only enabled if some project objects were selected + bool objectsSelected = (selected.indexes().size() != 0); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(objectsSelected); } /*! opens a file dialog and lets the user select the project file. */ void ImportProjectDialog::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog"); QString title; QString lastDir; QString supportedFormats; QString lastDirConfEntryName; switch (m_projectType) { case (ProjectLabPlot): title = i18n("Open LabPlot Project"); lastDirConfEntryName = QLatin1String("LastImportLabPlotProjectDir"); supportedFormats = i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)"); break; case (ProjectOrigin): title = i18n("Open Origin Project"); lastDirConfEntryName = QLatin1String("LastImportOriginProjecttDir"); supportedFormats = i18n("Origin Projects (*.opj *.OPJ)"); break; } lastDir = conf.readEntry(lastDirConfEntryName, ""); QString path = QFileDialog::getOpenFileName(this, title, lastDir, supportedFormats); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != lastDir) conf.writeEntry(lastDirConfEntryName, newDir); } ui.leFileName->setText(path); refreshPreview(); } void ImportProjectDialog::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path if ( !fileName.isEmpty() && fileName.left(1) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tvPreview->setModel(nullptr); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } refreshPreview(); } void ImportProjectDialog::newFolder() { QString path = ui.leFileName->text(); QString name = path.right( path.length()-path.lastIndexOf(QDir::separator())-1 ); bool ok; QInputDialog* dlg = new QInputDialog(this); name = dlg->getText(this, i18n("Add new folder"), i18n("Folder name:"), QLineEdit::Normal, name, &ok); if (ok) { Folder* folder = new Folder(name); m_mainWin->addAspectToProject(folder); m_cbAddTo->setCurrentModelIndex(m_mainWin->model()->modelIndexOfAspect(folder)); } delete dlg; } diff --git a/src/kdefrontend/datasources/ImportProjectDialog.h b/src/kdefrontend/datasources/ImportProjectDialog.h index bf4eb12d0..f7cb074e8 100644 --- a/src/kdefrontend/datasources/ImportProjectDialog.h +++ b/src/kdefrontend/datasources/ImportProjectDialog.h @@ -1,78 +1,78 @@ /*************************************************************************** File : ImportProjectDialog.h Project : LabPlot Description : import project dialog -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef IMPORTPROJECTDIALOG_H #define IMPORTPROJECTDIALOG_H #include #include "ui_importprojectwidget.h" class AbstractAspect; class AspectTreeModel; class Folder; class ProjectParser; class TreeViewComboBox; class MainWin; class QDialogButtonBox; class QStatusBar; class ImportProjectDialog : public QDialog { Q_OBJECT public: enum ProjectType {ProjectLabPlot, ProjectOrigin}; explicit ImportProjectDialog(MainWin*, ProjectType); ~ImportProjectDialog(); void setCurrentFolder(const Folder*); void importTo(QStatusBar*) const; private: Ui::ImportProjectWidget ui; MainWin* m_mainWin; ProjectParser* m_projectParser; ProjectType m_projectType; AspectTreeModel* m_aspectTreeModel; TreeViewComboBox* m_cbAddTo; QPushButton* m_bNewFolder; QDialogButtonBox* m_buttonBox; void refreshPreview(); void showTopLevelOnly(const QModelIndex&); bool isTopLevel(const AbstractAspect*) const; private slots: void loadSettings(); void fileNameChanged(const QString&); - void selectionChanged(); + void selectionChanged(const QItemSelection&, const QItemSelection&); void selectFile(); void newFolder(); }; #endif //IMPORTPROJECTDIALOG_H diff --git a/src/kdefrontend/dockwidgets/AxisDock.cpp b/src/kdefrontend/dockwidgets/AxisDock.cpp index 20ea3eb68..2074ad90d 100644 --- a/src/kdefrontend/dockwidgets/AxisDock.cpp +++ b/src/kdefrontend/dockwidgets/AxisDock.cpp @@ -1,1911 +1,1909 @@ /*************************************************************************** File : AxisDock.cpp Project : LabPlot Description : axes widget class -------------------------------------------------------------------- Copyright : (C) 2011-2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "AxisDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/worksheet/Worksheet.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/widgets/LabelWidget.h" #include #include #include #include #include -#include - /*! \class AxisDock \brief Provides a widget for editing the properties of the axes currently selected in the project explorer. \ingroup kdefrontend */ AxisDock::AxisDock(QWidget* parent):QWidget(parent), m_axis(0), m_aspectTreeModel(0), m_dataChanged(0), m_initializing(false) { ui.setupUi(this); //"Title"-tab QHBoxLayout* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); labelWidget->setFixedLabelMode(true); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //"Ticks"-tab QGridLayout* layout = static_cast(ui.tabTicks->layout()); cbMajorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMajorTicksColumn, 5, 2); cbMinorTicksColumn = new TreeViewComboBox(ui.tabTicks); layout->addWidget(cbMinorTicksColumn, 18, 2); //adjust layouts in the tabs for (int i=0; icount(); ++i) { layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //********************************** Slots ********************************************** //"General"-tab connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.cbOrientation, SIGNAL(currentIndexChanged(int)), this, SLOT(orientationChanged(int)) ); connect( ui.cbPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(positionChanged(int)) ); connect( ui.lePosition, SIGNAL(returnPressed()), this, SLOT(positionChanged()) ); connect( ui.cbScale, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleChanged(int)) ); connect( ui.chkAutoScale, SIGNAL(stateChanged(int)), this, SLOT(autoScaleChanged(int)) ); connect( ui.leStart, SIGNAL(returnPressed()), this, SLOT(startChanged()) ); connect( ui.leEnd, SIGNAL(returnPressed()), this, SLOT(endChanged()) ); connect( ui.leZeroOffset, SIGNAL(returnPressed()), this, SLOT(zeroOffsetChanged()) ); connect( ui.leScalingFactor, SIGNAL(returnPressed()), this, SLOT(scalingFactorChanged()) ); //"Line"-tab connect( ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(lineStyleChanged(int)) ); connect( ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(lineWidthChanged(double)) ); connect( ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(lineOpacityChanged(int)) ); connect( ui.cbArrowPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(arrowPositionChanged(int)) ); connect( ui.cbArrowType, SIGNAL(currentIndexChanged(int)), this, SLOT(arrowTypeChanged(int)) ); connect( ui.sbArrowSize, SIGNAL(valueChanged(int)), this, SLOT(arrowSizeChanged(int)) ); //"Major ticks"-tab connect( ui.cbMajorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksDirectionChanged(int)) ); connect( ui.cbMajorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksTypeChanged(int)) ); connect( ui.sbMajorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(majorTicksNumberChanged(int)) ); connect( ui.leMajorTicksIncrement, SIGNAL(returnPressed()), this, SLOT(majorTicksIncrementChanged()) ); connect( cbMajorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(majorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMajorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(majorTicksLineStyleChanged(int)) ); connect( ui.kcbMajorTicksColor, SIGNAL(changed(QColor)), this, SLOT(majorTicksColorChanged(QColor)) ); connect( ui.sbMajorTicksWidth, SIGNAL(valueChanged(double)), this, SLOT(majorTicksWidthChanged(double)) ); connect( ui.sbMajorTicksLength, SIGNAL(valueChanged(double)), this, SLOT(majorTicksLengthChanged(double)) ); connect( ui.sbMajorTicksOpacity, SIGNAL(valueChanged(int)), this, SLOT(majorTicksOpacityChanged(int)) ); //"Minor ticks"-tab connect( ui.cbMinorTicksDirection, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksDirectionChanged(int)) ); connect( ui.cbMinorTicksType, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksTypeChanged(int)) ); connect( ui.sbMinorTicksNumber, SIGNAL(valueChanged(int)), this, SLOT(minorTicksNumberChanged(int)) ); connect( ui.leMinorTicksIncrement, SIGNAL(returnPressed()), this, SLOT(minorTicksIncrementChanged()) ); connect( cbMinorTicksColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(minorTicksColumnChanged(QModelIndex)) ); connect( ui.cbMinorTicksLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(minorTicksLineStyleChanged(int)) ); connect( ui.kcbMinorTicksColor, SIGNAL(changed(QColor)), this, SLOT(minorTicksColorChanged(QColor)) ); connect( ui.sbMinorTicksWidth, SIGNAL(valueChanged(double)), this, SLOT(minorTicksWidthChanged(double)) ); connect( ui.sbMinorTicksLength, SIGNAL(valueChanged(double)), this, SLOT(minorTicksLengthChanged(double)) ); connect( ui.sbMinorTicksOpacity, SIGNAL(valueChanged(int)), this, SLOT(minorTicksOpacityChanged(int)) ); //"Extra ticks"-tab //"Tick labels"-tab connect( ui.cbLabelsFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(labelsFormatChanged(int)) ); connect( ui.sbLabelsPrecision, SIGNAL(valueChanged(int)), this, SLOT(labelsPrecisionChanged(int)) ); connect( ui.chkLabelsAutoPrecision, SIGNAL(stateChanged(int)), this, SLOT(labelsAutoPrecisionChanged(int)) ); connect( ui.cbLabelsPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(labelsPositionChanged(int)) ); connect( ui.sbLabelsOffset, SIGNAL(valueChanged(double)), this, SLOT(labelsOffsetChanged(double)) ); connect( ui.sbLabelsRotation, SIGNAL(valueChanged(int)), this, SLOT(labelsRotationChanged(int)) ); connect( ui.kfrLabelsFont, SIGNAL(fontSelected(QFont)), this, SLOT(labelsFontChanged(QFont)) ); connect( ui.kcbLabelsFontColor, SIGNAL(changed(QColor)), this, SLOT(labelsFontColorChanged(QColor)) ); connect( ui.leLabelsPrefix, SIGNAL(returnPressed()), this, SLOT(labelsPrefixChanged()) ); connect( ui.leLabelsSuffix, SIGNAL(returnPressed()), this, SLOT(labelsSuffixChanged()) ); connect( ui.sbLabelsOpacity, SIGNAL(valueChanged(int)), this, SLOT(labelsOpacityChanged(int)) ); /* connect( ui.sbLabelsPrecision, SIGNAL(valueChanged(int)), this, SLOT(slotDataChanged()) ); connect( ui.cbLabelsFormat, SIGNAL(currentIndexChanged(QString)), this, SLOT(labelFormatChanged(QString)) ); connect( ui.leLabelsDateFormat, SIGNAL(textChanged(QString)), this, SLOT(slotDataChanged()) ); */ //"Grid"-tab connect( ui.cbMajorGridStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(majorGridStyleChanged(int)) ); connect( ui.kcbMajorGridColor, SIGNAL(changed(QColor)), this, SLOT(majorGridColorChanged(QColor)) ); connect( ui.sbMajorGridWidth, SIGNAL(valueChanged(double)), this, SLOT(majorGridWidthChanged(double)) ); connect( ui.sbMajorGridOpacity, SIGNAL(valueChanged(int)), this, SLOT(majorGridOpacityChanged(int)) ); connect( ui.cbMinorGridStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(minorGridStyleChanged(int)) ); connect( ui.kcbMinorGridColor, SIGNAL(changed(QColor)), this, SLOT(minorGridColorChanged(QColor)) ); connect( ui.sbMinorGridWidth, SIGNAL(valueChanged(double)), this, SLOT(minorGridWidthChanged(double)) ); connect( ui.sbMinorGridOpacity, SIGNAL(valueChanged(int)), this, SLOT(minorGridOpacityChanged(int)) ); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::Axis); ui.verticalLayout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); init(); } AxisDock::~AxisDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void AxisDock::init() { m_initializing=true; //Validators ui.lePosition->setValidator( new QDoubleValidator(ui.lePosition) ); ui.leStart->setValidator( new QDoubleValidator(ui.leStart) ); ui.leEnd->setValidator( new QDoubleValidator(ui.leEnd) ); ui.leZeroOffset->setValidator( new QDoubleValidator(ui.leZeroOffset) ); ui.leScalingFactor->setValidator( new QDoubleValidator(ui.leScalingFactor) ); ui.leMajorTicksIncrement->setValidator( new QDoubleValidator(ui.leMajorTicksIncrement) ); ui.leMinorTicksIncrement->setValidator( new QDoubleValidator(ui.leMinorTicksIncrement) ); //TODO move this stuff to retranslateUI() ui.cbPosition->addItem(i18n("top")); ui.cbPosition->addItem(i18n("bottom")); ui.cbPosition->addItem(i18n("centered")); ui.cbPosition->addItem(i18n("custom")); ui.cbScale->addItem( i18n("linear") ); ui.cbScale->addItem( QLatin1String("log(x)") ); ui.cbScale->addItem( QLatin1String("log2(x)") ); ui.cbScale->addItem( QLatin1String("ln(x)") ); ui.cbScale->addItem( QLatin1String("sqrt(x)") ); ui.cbScale->addItem( QLatin1String("x^2") ); ui.cbOrientation->addItem( i18n("horizontal") ); ui.cbOrientation->addItem( i18n("vertical") ); //Arrows ui.cbArrowType->addItem( i18n("no arrow") ); ui.cbArrowType->addItem( i18n("simple, small") ); ui.cbArrowType->addItem( i18n("simple, big") ); ui.cbArrowType->addItem( i18n("filled, small") ); ui.cbArrowType->addItem( i18n("filled, big") ); ui.cbArrowType->addItem( i18n("semi-filled, small") ); ui.cbArrowType->addItem( i18n("semi-filled, big") ); QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbArrowType->setIconSize( QSize(20,20) ); //no arrow pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); pa.end(); ui.cbArrowType->setItemIcon(0, pm); //simple, small float cos_phi = cos(3.14159/6); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10); pa.drawLine(17,10, 10, 10-5*cos_phi); pa.drawLine(17,10, 10, 10+5*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(1, pm); //simple, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10); pa.drawLine(17,10, 10, 10-10*cos_phi); pa.drawLine(17,10, 10, 10+10*cos_phi); pa.end(); ui.cbArrowType->setItemIcon(2, pm); //filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points3[3] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points3, 3); pa.end(); ui.cbArrowType->setItemIcon(3, pm); //filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points4[3] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points4, 3); pa.end(); ui.cbArrowType->setItemIcon(4, pm); //semi-filled, small pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points5[4] = {QPointF(17, 10), QPointF(10, 10-4*cos_phi), QPointF(13, 10), QPointF(10, 10+4*cos_phi) }; pa.drawPolygon(points5, 4); pa.end(); ui.cbArrowType->setItemIcon(5, pm); //semi-filled, big pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); QPointF points6[4] = {QPointF(17, 10), QPointF(10, 10-10*cos_phi), QPointF(13, 10), QPointF(10, 10+10*cos_phi) }; pa.drawPolygon(points6, 4); pa.end(); ui.cbArrowType->setItemIcon(6, pm); ui.cbArrowPosition->addItem( i18n("left") ); ui.cbArrowPosition->addItem( i18n("right") ); ui.cbArrowPosition->addItem( i18n("both") ); ui.cbMajorTicksDirection->addItem( i18n("none") ); ui.cbMajorTicksDirection->addItem( i18n("in") ); ui.cbMajorTicksDirection->addItem( i18n("out") ); ui.cbMajorTicksDirection->addItem( i18n("in and out") ); ui.cbMajorTicksType->addItem( i18n("Number") ); ui.cbMajorTicksType->addItem( i18n("Increment") ); ui.cbMajorTicksType->addItem( i18n("Custom column") ); ui.cbMinorTicksDirection->addItem( i18n("none") ); ui.cbMinorTicksDirection->addItem( i18n("in") ); ui.cbMinorTicksDirection->addItem( i18n("out") ); ui.cbMinorTicksDirection->addItem( i18n("in and out") ); ui.cbMinorTicksType->addItem( i18n("Number") ); ui.cbMinorTicksType->addItem( i18n("Increment") ); ui.cbMinorTicksType->addItem( i18n("Custom column") ); GuiTools::updatePenStyles(ui.cbLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, QColor(Qt::black)); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, QColor(Qt::black)); //labels ui.cbLabelsPosition->addItem(i18n("no labels")); ui.cbLabelsPosition->addItem(i18n("top")); ui.cbLabelsPosition->addItem(i18n("bottom")); ui.cbLabelsFormat->addItem( i18n("Decimal notation") ); ui.cbLabelsFormat->addItem( i18n("Scientific notation") ); ui.cbLabelsFormat->addItem( i18n("Powers of 10") ); ui.cbLabelsFormat->addItem( i18n("Powers of 2") ); ui.cbLabelsFormat->addItem( i18n("Powers of e") ); ui.cbLabelsFormat->addItem( i18n("Multiples of \u03C0") ); m_initializing=false; } void AxisDock::setModel() { QList list; list<<"Folder"<<"Spreadsheet"<<"FileDataSource"<<"Column"; cbMajorTicksColumn->setTopLevelClasses(list); cbMinorTicksColumn->setTopLevelClasses(list); list.clear(); list<<"Column"; m_aspectTreeModel->setSelectableAspects(list); cbMajorTicksColumn->setModel(m_aspectTreeModel); cbMinorTicksColumn->setModel(m_aspectTreeModel); } /*! sets the axes. The properties of the axes in the list \c list can be edited in this widget. */ void AxisDock::setAxes(QList list) { m_initializing=true; m_axesList=list; m_axis=list.first(); Q_ASSERT(m_axis); m_aspectTreeModel = new AspectTreeModel(m_axis->project()); this->setModel(); labelWidget->setAxes(list); //if there are more then one axis in the list, disable the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_axis->name()); ui.leComment->setText(m_axis->comment()); this->setModelIndexFromColumn(cbMajorTicksColumn, m_axis->majorTicksColumn()); this->setModelIndexFromColumn(cbMinorTicksColumn, m_axis->minorTicksColumn()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); cbMajorTicksColumn->setCurrentModelIndex(QModelIndex()); cbMinorTicksColumn->setCurrentModelIndex(QModelIndex()); } //show the properties of the first axis this->load(); // general connect(m_axis, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(axisDescriptionChanged(const AbstractAspect*))); connect(m_axis, SIGNAL(orientationChanged(Axis::AxisOrientation)), this, SLOT(axisOrientationChanged(Axis::AxisOrientation))); connect(m_axis, SIGNAL(positionChanged(Axis::AxisPosition)), this, SLOT(axisPositionChanged(Axis::AxisPosition))); connect(m_axis, SIGNAL(scaleChanged(Axis::AxisScale)), this, SLOT(axisScaleChanged(Axis::AxisScale))); connect(m_axis, SIGNAL(autoScaleChanged(bool)), this, SLOT(axisAutoScaleChanged(bool))); connect(m_axis, SIGNAL(startChanged(float)), this, SLOT(axisStartChanged(float))); connect(m_axis, SIGNAL(endChanged(float)), this, SLOT(axisEndChanged(float))); connect(m_axis, SIGNAL(zeroOffsetChanged(qreal)), this, SLOT(axisZeroOffsetChanged(qreal))); connect(m_axis, SIGNAL(scalingFactorChanged(qreal)), this, SLOT(axisScalingFactorChanged(qreal))); // line connect(m_axis, SIGNAL(linePenChanged(QPen)), this, SLOT(axisLinePenChanged(QPen))); connect(m_axis, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(axisLineOpacityChanged(qreal))); connect(m_axis, SIGNAL(arrowTypeChanged(Axis::ArrowType)), this, SLOT(axisArrowTypeChanged(Axis::ArrowType))); connect(m_axis, SIGNAL(arrowPositionChanged(Axis::ArrowPosition)), this, SLOT(axisArrowPositionChanged(Axis::ArrowPosition))); connect(m_axis, SIGNAL(arrowSizeChanged(float)), this, SLOT(axisArrowSizeChanged(float))); // ticks connect(m_axis, SIGNAL(majorTicksDirectionChanged(Axis::TicksDirection)), this, SLOT(axisMajorTicksDirectionChanged(Axis::TicksDirection))); connect(m_axis, SIGNAL(majorTicksTypeChanged(Axis::TicksType)), this, SLOT(axisMajorTicksTypeChanged(Axis::TicksType))); connect(m_axis, SIGNAL(majorTicksNumberChanged(int)), this, SLOT(axisMajorTicksNumberChanged(int))); connect(m_axis, SIGNAL(majorTicksIncrementChanged(qreal)), this, SLOT(axisMajorTicksIncrementChanged(qreal))); connect(m_axis, SIGNAL(majorTicksPenChanged(QPen)), this, SLOT(axisMajorTicksPenChanged(QPen))); connect(m_axis, SIGNAL(majorTicksLengthChanged(qreal)), this, SLOT(axisMajorTicksLengthChanged(qreal))); connect(m_axis, SIGNAL(majorTicksOpacityChanged(qreal)), this, SLOT(axisMajorTicksOpacityChanged(qreal))); connect(m_axis, SIGNAL(minorTicksDirectionChanged(Axis::TicksDirection)), this, SLOT(axisMinorTicksDirectionChanged(Axis::TicksDirection))); connect(m_axis, SIGNAL(minorTicksTypeChanged(Axis::TicksType)), this, SLOT(axisMinorTicksTypeChanged(Axis::TicksType))); connect(m_axis, SIGNAL(minorTicksNumberChanged(int)), this, SLOT(axisMinorTicksNumberChanged(int))); connect(m_axis, SIGNAL(minorTicksIncrementChanged(qreal)), this, SLOT(axisMinorTicksIncrementChanged(qreal))); connect(m_axis, SIGNAL(minorTicksPenChanged(QPen)), this, SLOT(axisMinorTicksPenChanged(QPen))); connect(m_axis, SIGNAL(minorTicksLengthChanged(qreal)), this, SLOT(axisMinorTicksLengthChanged(qreal))); connect(m_axis, SIGNAL(minorTicksOpacityChanged(qreal)), this, SLOT(axisMinorTicksOpacityChanged(qreal))); // labels connect(m_axis, SIGNAL(labelsFormatChanged(Axis::LabelsFormat)), this, SLOT(axisLabelsFormatChanged(Axis::LabelsFormat))); connect(m_axis, SIGNAL(labelsAutoPrecisionChanged(bool)), this, SLOT(axisLabelsAutoPrecisionChanged(bool))); connect(m_axis, SIGNAL(labelsPrecisionChanged(int)), this, SLOT(axisLabelsPrecisionChanged(int))); connect(m_axis, SIGNAL(labelsPositionChanged(Axis::LabelsPosition)), this, SLOT(axisLabelsPositionChanged(Axis::LabelsPosition))); connect(m_axis, SIGNAL(labelsOffsetChanged(float)), this, SLOT(axisLabelsOffsetChanged(float))); connect(m_axis, SIGNAL(labelsRotationAngleChanged(qreal)), this, SLOT(axisLabelsRotationAngleChanged(qreal))); connect(m_axis, SIGNAL(labelsFontChanged(QFont)), this, SLOT(axisLabelsFontChanged(QFont))); connect(m_axis, SIGNAL(labelsColorChanged(QColor)), this, SLOT(axisLabelsFontColorChanged(QColor))); connect(m_axis, SIGNAL(labelsPrefixChanged(QString)), this, SLOT(axisLabelsPrefixChanged(QString))); connect(m_axis, SIGNAL(labelsSuffixChanged(QString)), this, SLOT(axisLabelsSuffixChanged(QString))); connect(m_axis, SIGNAL(labelsOpacityChanged(qreal)), this, SLOT(axisLabelsOpacityChanged(qreal))); // grids connect(m_axis, SIGNAL(majorGridPenChanged(QPen)), this, SLOT(axisMajorGridPenChanged(QPen))); connect(m_axis, SIGNAL(majorGridOpacityChanged(qreal)), this, SLOT(axisMajorGridOpacityChanged(qreal))); connect(m_axis, SIGNAL(minorGridPenChanged(QPen)), this, SLOT(axisMinorGridPenChanged(QPen))); connect(m_axis, SIGNAL(minorGridOpacityChanged(qreal)), this, SLOT(axisMinorGridOpacityChanged(qreal))); connect(m_axis, SIGNAL(visibilityChanged(bool)), this, SLOT(axisVisibilityChanged(bool))); m_initializing = false; } void AxisDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } void AxisDock::setModelIndexFromColumn(TreeViewComboBox* cb, const AbstractColumn* column) { if (column) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in AxisDock ********** //************************************************************* //"General"-tab void AxisDock::nameChanged() { if (m_initializing) return; m_axis->setName(ui.leName->text()); } void AxisDock::commentChanged() { if (m_initializing) return; m_axis->setComment(ui.leComment->text()); } void AxisDock::visibilityChanged(bool state) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setVisible(state); } /*! called if the orientation (horizontal or vertical) of the current axis is changed. */ void AxisDock::orientationChanged(int index) { Axis::AxisOrientation orientation = (Axis::AxisOrientation)index; if (orientation == Axis::AxisHorizontal) { ui.cbPosition->setItemText(0, i18n("top") ); ui.cbPosition->setItemText(1, i18n("bottom") ); ui.cbLabelsPosition->setItemText(1, i18n("top") ); ui.cbLabelsPosition->setItemText(2, i18n("bottom") ); ui.cbScale->setItemText(1, QLatin1String("log(x)") ); ui.cbScale->setItemText(2, QLatin1String("log2(x)") ); ui.cbScale->setItemText(3, QLatin1String("ln(x)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(x)") ); ui.cbScale->setItemText(5, QLatin1String("x^2") ); } else { //vertical ui.cbPosition->setItemText(0, i18n("left") ); ui.cbPosition->setItemText(1, i18n("right") ); ui.cbLabelsPosition->setItemText(1, i18n("right") ); ui.cbLabelsPosition->setItemText(2, i18n("left") ); ui.cbScale->setItemText(1, QLatin1String("log(y)") ); ui.cbScale->setItemText(2, QLatin1String("log2(y)") ); ui.cbScale->setItemText(3, QLatin1String("ln(y)") ); ui.cbScale->setItemText(4, QLatin1String("sqrt(y)") ); ui.cbScale->setItemText(5, QLatin1String("y^2") ); } if (m_initializing) return; //depending on the current orientation we need to update axis possition and labels position //axis position, map from the current index in the combobox to the enum value in Axis::AxisPosition Axis::AxisPosition axisPosition; int posIndex = ui.cbPosition->currentIndex(); if (orientation == Axis::AxisHorizontal) { if (posIndex>1) posIndex += 2; axisPosition = Axis::AxisPosition(posIndex); } else { axisPosition = Axis::AxisPosition(posIndex+2); } //labels position posIndex = ui.cbLabelsPosition->currentIndex(); Axis::LabelsPosition labelsPosition = Axis::LabelsPosition(posIndex); foreach(Axis* axis, m_axesList) { axis->beginMacro(i18n("%1: set axis orientation", axis->name())); axis->setOrientation(orientation); axis->setPosition(axisPosition); axis->setLabelsPosition(labelsPosition); axis->endMacro(); } } /*! called if one of the predefined axis positions (top, bottom, left, right, center or custom) was changed. */ void AxisDock::positionChanged(int index) { if (index == -1) return; //we occasionally get -1 here, nothing to do in this case if (index == 3) ui.lePosition->setVisible(true); else ui.lePosition->setVisible(false); if (m_initializing) return; //map from the current index in the combo box to the enum value in Axis::AxisPosition, //depends on the current orientation Axis::AxisPosition position; if ( ui.cbOrientation->currentIndex() == 0 ) { if (index>1) index += 2; position = Axis::AxisPosition(index); } else { position = Axis::AxisPosition(index+2); } foreach(Axis* axis, m_axesList) axis->setPosition(position); } /*! called when the custom position of the axis in the corresponding LineEdit is changed. */ void AxisDock::positionChanged() { if (m_initializing) return; double offset = ui.lePosition->text().toDouble(); foreach(Axis* axis, m_axesList) axis->setOffset(offset); } void AxisDock::scaleChanged(int index) { if (m_initializing) return; Axis::AxisScale scale = (Axis::AxisScale)index; foreach(Axis* axis, m_axesList) axis->setScale(scale); } void AxisDock::autoScaleChanged(int index) { bool autoScale = index == Qt::Checked; ui.leStart->setEnabled(!autoScale); ui.leEnd->setEnabled(!autoScale); if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setAutoScale(autoScale); } void AxisDock::startChanged() { if (m_initializing) return; double value = ui.leStart->text().toDouble(); //check first, whether the value for the lower limit is valid for the log- and square root scaling. If not, set the default values. Axis::AxisScale scale = Axis::AxisScale(ui.cbScale->currentIndex()); if (scale == Axis::ScaleLog10 || scale == Axis::ScaleLog2 || scale == Axis::ScaleLn) { if (value <= 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a non-positive value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0.01" ); value=0.01; } } else if (scale == Axis::ScaleSqrt) { if (value < 0) { KMessageBox::sorry(this, i18n("The axes lower limit has a negative value. Default minimal value will be used."), i18n("Wrong lower limit value") ); ui.leStart->setText( "0" ); value=0; } } foreach (Axis* axis, m_axesList) axis->setStart(value); } void AxisDock::endChanged() { if (m_initializing) return; double value = ui.leEnd->text().toDouble(); foreach(Axis* axis, m_axesList) axis->setEnd(value); } void AxisDock::zeroOffsetChanged() { if (m_initializing) return; double offset = ui.leZeroOffset->text().toDouble(); foreach(Axis* axis, m_axesList) axis->setZeroOffset(offset); } void AxisDock::scalingFactorChanged() { if (m_initializing) return; double scalingFactor = ui.leScalingFactor->text().toDouble(); foreach(Axis* axis, m_axesList) axis->setScalingFactor(scalingFactor); } // "Line"-tab void AxisDock::lineStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lLineColor->setEnabled(b); ui.kcbLineColor->setEnabled(b); ui.lLineWidth->setEnabled(b); ui.sbLineWidth->setEnabled(b); ui.lLineOpacity->setEnabled(b); ui.sbLineOpacity->setEnabled(b); if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->linePen(); pen.setStyle(penStyle); axis->setLinePen(pen); } } void AxisDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->linePen(); pen.setColor(color); axis->setLinePen(pen); } m_initializing=true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing=false; } void AxisDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->linePen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setLinePen(pen); } } void AxisDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setLineOpacity(opacity); } void AxisDock::arrowTypeChanged(int index) { Axis::ArrowType type = (Axis::ArrowType)index; if (type == Axis::NoArrow) { ui.cbArrowPosition->setEnabled(false); ui.sbArrowSize->setEnabled(false); } else { ui.cbArrowPosition->setEnabled(true); ui.sbArrowSize->setEnabled(true); } if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setArrowType(type); } void AxisDock::arrowPositionChanged(int index) { if (m_initializing) return; Axis::ArrowPosition position = (Axis::ArrowPosition)index; foreach(Axis* axis, m_axesList) axis->setArrowPosition(position); } void AxisDock::arrowSizeChanged(int value) { if (m_initializing) return; float v = Worksheet::convertToSceneUnits(value, Worksheet::Point); foreach(Axis* axis, m_axesList) axis->setArrowSize(v); } //"Major ticks" tab void AxisDock::majorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksType->setEnabled(b); ui.cbMajorTicksType->setEnabled(b); ui.lMajorTicksNumber->setEnabled(b); ui.sbMajorTicksNumber->setEnabled(b); ui.lMajorTicksIncrement->setEnabled(b); ui.leMajorTicksIncrement->setEnabled(b); ui.lMajorTicksLineStyle->setEnabled(b); ui.cbMajorTicksLineStyle->setEnabled(b); if (b) { Qt::PenStyle penStyle=Qt::PenStyle(ui.cbMajorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMajorTicksDirection(direction); } /*! called if the current style of the ticks (Number or Increment) is changed. Shows/hides the corresponding widgets. */ void AxisDock::majorTicksTypeChanged(int index) { Axis::TicksType type = Axis::TicksType(index); if ( type == Axis::TicksTotalNumber) { ui.lMajorTicksNumber->show(); ui.sbMajorTicksNumber->show(); ui.lMajorTicksIncrement->hide(); ui.leMajorTicksIncrement->hide(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); } else if ( type == Axis::TicksIncrement) { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksIncrement->show(); ui.leMajorTicksIncrement->show(); ui.lMajorTicksColumn->hide(); cbMajorTicksColumn->hide(); } else { ui.lMajorTicksNumber->hide(); ui.sbMajorTicksNumber->hide(); ui.lMajorTicksIncrement->hide(); ui.leMajorTicksIncrement->hide(); ui.lMajorTicksColumn->show(); cbMajorTicksColumn->show(); } if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMajorTicksType(type); } void AxisDock::majorTicksNumberChanged(int value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMajorTicksNumber(value); } void AxisDock::majorTicksIncrementChanged() { if (m_initializing) return; double value = ui.leMajorTicksIncrement->text().toDouble(); if (value<0) value = -1*value; //don't allow negative values foreach(Axis* axis, m_axesList) axis->setMajorTicksIncrement(value); } void AxisDock::majorTicksLineStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); bool b=(penStyle != Qt::NoPen); ui.lMajorTicksColor->setEnabled(b); ui.kcbMajorTicksColor->setEnabled(b); ui.lMajorTicksWidth->setEnabled(b); ui.sbMajorTicksWidth->setEnabled(b); ui.lMajorTicksLength->setEnabled(b); ui.sbMajorTicksLength->setEnabled(b); ui.lMajorTicksOpacity->setEnabled(b); ui.sbMajorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorTicksPen(); pen.setStyle(penStyle); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(Axis* axis, m_axesList) axis->setMajorTicksColumn(column); } void AxisDock::majorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorTicksPen(); pen.setColor(color); axis->setMajorTicksPen(pen); } m_initializing=true; GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, color); m_initializing=false; } void AxisDock::majorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorTicksPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); axis->setMajorTicksPen(pen); } } void AxisDock::majorTicksLengthChanged(double value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMajorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::majorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setMajorTicksOpacity(opacity); } //"Minor ticks" tab void AxisDock::minorTicksDirectionChanged(int index) { Axis::TicksDirection direction = Axis::TicksDirection(index); bool b = (direction != Axis::noTicks); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksType->setEnabled(b); ui.cbMinorTicksType->setEnabled(b); ui.lMinorTicksNumber->setEnabled(b); ui.sbMinorTicksNumber->setEnabled(b); ui.lMinorTicksIncrement->setEnabled(b); ui.leMinorTicksIncrement->setEnabled(b); ui.lMinorTicksLineStyle->setEnabled(b); ui.cbMinorTicksLineStyle->setEnabled(b); if (b) { Qt::PenStyle penStyle=Qt::PenStyle(ui.cbMinorTicksLineStyle->currentIndex()); b = (penStyle != Qt::NoPen); } ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMinorTicksDirection(direction); } void AxisDock::minorTicksTypeChanged(int index) { Axis::TicksType type = Axis::TicksType(index); if ( type == Axis::TicksTotalNumber) { ui.lMinorTicksNumber->show(); ui.sbMinorTicksNumber->show(); ui.lMinorTicksIncrement->hide(); ui.leMinorTicksIncrement->hide(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); } else if ( type == Axis::TicksIncrement) { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); ui.lMinorTicksIncrement->show(); ui.leMinorTicksIncrement->show(); ui.lMinorTicksColumn->hide(); cbMinorTicksColumn->hide(); } else { ui.lMinorTicksNumber->hide(); ui.sbMinorTicksNumber->hide(); ui.lMinorTicksIncrement->hide(); ui.leMinorTicksIncrement->hide(); ui.lMinorTicksColumn->show(); cbMinorTicksColumn->show(); } if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMinorTicksType(type); } void AxisDock::minorTicksNumberChanged(int value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMinorTicksNumber(value); } void AxisDock::minorTicksIncrementChanged() { if (m_initializing) return; double value = ui.leMinorTicksIncrement->text().toDouble(); if (value<0) value = -1*value; //don't allow negative values foreach(Axis* axis, m_axesList) axis->setMinorTicksIncrement(value); } void AxisDock::minorTicksColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = dynamic_cast(aspect); Q_ASSERT(column); foreach(Axis* axis, m_axesList) axis->setMinorTicksColumn(column); } void AxisDock::minorTicksLineStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); bool b=(penStyle != Qt::NoPen); ui.lMinorTicksColor->setEnabled(b); ui.kcbMinorTicksColor->setEnabled(b); ui.lMinorTicksWidth->setEnabled(b); ui.sbMinorTicksWidth->setEnabled(b); ui.lMinorTicksLength->setEnabled(b); ui.sbMinorTicksLength->setEnabled(b); ui.lMinorTicksOpacity->setEnabled(b); ui.sbMinorTicksOpacity->setEnabled(b); if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorTicksPen(); pen.setStyle(penStyle); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorTicksPen(); pen.setColor(color); axis->setMinorTicksPen(pen); } m_initializing=true; GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, color); m_initializing=false; } void AxisDock::minorTicksWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorTicksPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); axis->setMinorTicksPen(pen); } } void AxisDock::minorTicksLengthChanged(double value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setMinorTicksLength( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::minorTicksOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setMinorTicksOpacity(opacity); } //"Tick labels"-tab void AxisDock::labelsFormatChanged(int index) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsFormat(Axis::LabelsFormat(index)); } void AxisDock::labelsPrecisionChanged(int value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsPrecision(value); } void AxisDock::labelsAutoPrecisionChanged(int state) { bool checked = (state == Qt::Checked); ui.sbLabelsPrecision->setEnabled(!checked); if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsAutoPrecision(checked); } void AxisDock::labelsPositionChanged(int index) { Axis::LabelsPosition position = Axis::LabelsPosition(index); bool b = (position != Axis::NoLabels); ui.lLabelsOffset->setEnabled(b); ui.sbLabelsOffset->setEnabled(b); ui.lLabelsRotation->setEnabled(b); ui.sbLabelsRotation->setEnabled(b); ui.lLabelsFont->setEnabled(b); ui.kfrLabelsFont->setEnabled(b); ui.lLabelsColor->setEnabled(b); ui.kcbLabelsFontColor->setEnabled(b); ui.lLabelsPrefix->setEnabled(b); ui.leLabelsPrefix->setEnabled(b); ui.lLabelsSuffix->setEnabled(b); ui.leLabelsSuffix->setEnabled(b); ui.lLabelsOpacity->setEnabled(b); ui.sbLabelsOpacity->setEnabled(b); if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsPosition(position); } void AxisDock::labelsOffsetChanged(double value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsOffset( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void AxisDock::labelsRotationChanged(int value) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsRotationAngle(value); } void AxisDock::labelsPrefixChanged() { if (m_initializing) return; QString prefix = ui.leLabelsPrefix->text(); foreach(Axis* axis, m_axesList) axis->setLabelsPrefix(prefix); } void AxisDock::labelsSuffixChanged() { if (m_initializing) return; QString suffix = ui.leLabelsSuffix->text(); foreach(Axis* axis, m_axesList) axis->setLabelsSuffix(suffix); } void AxisDock::labelsFontChanged(const QFont& font) { if (m_initializing) return; QFont labelsFont = font; labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); foreach(Axis* axis, m_axesList) axis->setLabelsFont( labelsFont ); } void AxisDock::labelsFontColorChanged(const QColor& color) { if (m_initializing) return; foreach(Axis* axis, m_axesList) axis->setLabelsColor(color); } void AxisDock::labelsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setLabelsOpacity(opacity); } // "Grid"-tab //major grid void AxisDock::majorGridStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMajorGridColor->setEnabled(b); ui.kcbMajorGridColor->setEnabled(b); ui.lMajorGridWidth->setEnabled(b); ui.sbMajorGridWidth->setEnabled(b); ui.lMajorGridOpacity->setEnabled(b); ui.sbMajorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorGridPen(); pen.setStyle(penStyle); axis->setMajorGridPen(pen); } } void AxisDock::majorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorGridPen(); pen.setColor(color); axis->setMajorGridPen(pen); } m_initializing=true; GuiTools::updatePenStyles(ui.cbMajorGridStyle, color); m_initializing=false; } void AxisDock::majorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->majorGridPen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setMajorGridPen(pen); } } void AxisDock::majorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setMajorGridOpacity(opacity); } //minor grid void AxisDock::minorGridStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); bool b = (penStyle != Qt::NoPen); ui.lMinorGridColor->setEnabled(b); ui.kcbMinorGridColor->setEnabled(b); ui.lMinorGridWidth->setEnabled(b); ui.sbMinorGridWidth->setEnabled(b); ui.lMinorGridOpacity->setEnabled(b); ui.sbMinorGridOpacity->setEnabled(b); if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorGridPen(); pen.setStyle(penStyle); axis->setMinorGridPen(pen); } } void AxisDock::minorGridColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorGridPen(); pen.setColor(color); axis->setMinorGridPen(pen); } m_initializing=true; GuiTools::updatePenStyles(ui.cbMinorGridStyle, color); m_initializing=false; } void AxisDock::minorGridWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(Axis* axis, m_axesList) { pen=axis->minorGridPen(); pen.setWidthF(Worksheet::convertToSceneUnits(value, Worksheet::Point)); axis->setMinorGridPen(pen); } } void AxisDock::minorGridOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(Axis* axis, m_axesList) axis->setMinorGridOpacity(opacity); } //************************************************************* //************ SLOTs for changes triggered in Axis ************ //************************************************************* void AxisDock::axisDescriptionChanged(const AbstractAspect* aspect) { if (m_axis != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void AxisDock::axisOrientationChanged(Axis::AxisOrientation orientation) { m_initializing = true; ui.cbOrientation->setCurrentIndex( (int)orientation ); m_initializing = false; } void AxisDock::axisPositionChanged(Axis::AxisPosition position) { m_initializing = true; //map from the enum Axis::AxisOrientation to the index in the combo box int index(position); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); m_initializing = false; } void AxisDock::axisPositionChanged(float value) { m_initializing = true; ui.lePosition->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisScaleChanged(Axis::AxisScale scale) { m_initializing = true; ui.cbScale->setCurrentIndex( (int)scale ); m_initializing = false; } void AxisDock::axisAutoScaleChanged(bool on) { m_initializing = true; ui.chkAutoScale->setChecked(on); m_initializing = false; } void AxisDock::axisStartChanged(float value) { m_initializing = true; ui.leStart->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisEndChanged(float value) { m_initializing = true; ui.leEnd->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisZeroOffsetChanged(qreal value) { m_initializing = true; ui.leZeroOffset->setText( QString::number(value) ); m_initializing = false; } void AxisDock::axisScalingFactorChanged(qreal value) { m_initializing = true; ui.leScalingFactor->setText( QString::number(value) ); m_initializing = false; } //line void AxisDock::axisLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( pen.style() ); ui.kcbLineColor->setColor( pen.color() ); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point) ); m_initializing = false; } void AxisDock::axisArrowTypeChanged(Axis::ArrowType type) { m_initializing = true; ui.cbArrowType->setCurrentIndex( (int)type); m_initializing = false; } void AxisDock::axisLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisArrowPositionChanged(Axis::ArrowPosition position) { m_initializing = true; ui.cbArrowPosition->setCurrentIndex( (int)position ); m_initializing = false; } void AxisDock::axisArrowSizeChanged(float size) { m_initializing = true; ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } //major ticks void AxisDock::axisMajorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMajorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMajorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMajorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMajorTicksNumberChanged(int number) { m_initializing = true; ui.sbMajorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMajorTicksIncrementChanged(qreal increment) { m_initializing = true; ui.leMajorTicksIncrement->setText( QString::number(increment)); m_initializing = false; } void AxisDock::axisMajorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMajorTicksColor->setColor(pen.color()); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksLengthChanged(qreal length) { m_initializing = true; ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMajorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorTicksOpacity->setValue( round(opacity*100.0)); m_initializing = false; } //minor ticks void AxisDock::axisMinorTicksDirectionChanged(Axis::TicksDirection direction) { m_initializing = true; ui.cbMinorTicksDirection->setCurrentIndex(direction); m_initializing = false; } void AxisDock::axisMinorTicksTypeChanged(Axis::TicksType type) { m_initializing = true; ui.cbMinorTicksType->setCurrentIndex(type); m_initializing = false; } void AxisDock::axisMinorTicksNumberChanged(int number) { m_initializing = true; ui.sbMinorTicksNumber->setValue(number); m_initializing = false; } void AxisDock::axisMinorTicksIncrementChanged(qreal increment) { m_initializing = true; ui.leMinorTicksIncrement->setText( QString::number(increment)); m_initializing = false; } void AxisDock::axisMinorTicksPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorTicksLineStyle->setCurrentIndex(pen.style()); ui.kcbMinorTicksColor->setColor(pen.color()); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksLengthChanged(qreal length) { m_initializing = true; ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(length,Worksheet::Point) ); m_initializing = false; } void AxisDock::axisMinorTicksOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorTicksOpacity->setValue(round(opacity*100.0)); m_initializing = false; } //labels void AxisDock::axisLabelsFormatChanged(Axis::LabelsFormat format) { m_initializing = true; ui.cbLabelsFormat->setCurrentIndex(format); m_initializing = false; } void AxisDock::axisLabelsAutoPrecisionChanged(bool on) { m_initializing = true; ui.chkLabelsAutoPrecision->setChecked((int) on); m_initializing = false; } void AxisDock::axisLabelsPrecisionChanged(int precision) { m_initializing = true; ui.sbLabelsPrecision->setValue(precision); m_initializing = false; } void AxisDock::axisLabelsPositionChanged(Axis::LabelsPosition position) { m_initializing = true; ui.cbLabelsPosition->setCurrentIndex(position); m_initializing = false; } void AxisDock::axisLabelsOffsetChanged(float offset) { m_initializing = true; ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(offset, Worksheet::Point) ); m_initializing = false; } void AxisDock::axisLabelsRotationAngleChanged(qreal rotation) { m_initializing = true; ui.sbLabelsRotation->setValue(rotation); m_initializing = false; } void AxisDock::axisLabelsFontChanged(const QFont& font) { m_initializing = true; //we need to set the font size in points for KFontRequester QFont newFont(font); newFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont(newFont); m_initializing = false; } void AxisDock::axisLabelsFontColorChanged(const QColor& color) { m_initializing = true; ui.kcbLabelsFontColor->setColor(color); m_initializing = false; } void AxisDock::axisLabelsPrefixChanged(const QString& prefix) { m_initializing = true; ui.leLabelsPrefix->setText(prefix); m_initializing = false; } void AxisDock::axisLabelsSuffixChanged(const QString& suffix) { m_initializing = true; ui.leLabelsSuffix->setText(suffix); m_initializing = false; } void AxisDock::axisLabelsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLabelsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //grid void AxisDock::axisMajorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMajorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMajorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, pen.color()); ui.sbMajorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void AxisDock::axisMajorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMajorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisMinorGridPenChanged(const QPen& pen) { m_initializing = true; ui.cbMinorGridStyle->setCurrentIndex((int) pen.style()); ui.kcbMinorGridColor->setColor(pen.color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, pen.color()); ui.sbMinorGridWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void AxisDock::axisMinorGridOpacityChanged(qreal opacity) { m_initializing = true; ui.sbMinorGridOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void AxisDock::axisVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void AxisDock::load() { //General ui.chkVisible->setChecked( m_axis->isVisible() ); ui.cbOrientation->setCurrentIndex( (int) m_axis->orientation() ); int index = (int) m_axis->position(); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( m_axis->offset()) ); ui.cbScale->setCurrentIndex( (int) m_axis->scale() ); ui.chkAutoScale->setChecked( m_axis->autoScale() ); ui.leStart->setText( QString::number(m_axis->start()) ); ui.leEnd->setText( QString::number(m_axis->end()) ); ui.leZeroOffset->setText( QString::number(m_axis->zeroOffset()) ); ui.leScalingFactor->setText( QString::number(m_axis->scalingFactor()) ); //Line ui.cbLineStyle->setCurrentIndex( (int) m_axis->linePen().style() ); ui.kcbLineColor->setColor( m_axis->linePen().color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->linePen().widthF(),Worksheet::Point) ); ui.sbLineOpacity->setValue( round(m_axis->lineOpacity()*100.0) ); ui.cbArrowType->setCurrentIndex( (int)m_axis->arrowType() ); ui.cbArrowPosition->setCurrentIndex( (int)m_axis->arrowPosition() ); ui.sbArrowSize->setValue( (int)Worksheet::convertFromSceneUnits(m_axis->arrowSize(), Worksheet::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( (int) m_axis->majorTicksDirection() ); ui.cbMajorTicksType->setCurrentIndex( (int) m_axis->majorTicksType() ); ui.sbMajorTicksNumber->setValue( m_axis->majorTicksNumber() ); ui.leMajorTicksIncrement->setText( QString::number(m_axis->majorTicksIncrement()) ); ui.cbMajorTicksLineStyle->setCurrentIndex( (int) m_axis->majorTicksPen().style() ); ui.kcbMajorTicksColor->setColor( m_axis->majorTicksPen().color() ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksPen().widthF(),Worksheet::Point) ); ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits( m_axis->majorTicksLength(),Worksheet::Point) ); ui.sbMajorTicksOpacity->setValue( round(m_axis->majorTicksOpacity()*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( (int) m_axis->minorTicksDirection() ); ui.cbMinorTicksType->setCurrentIndex( (int) m_axis->minorTicksType() ); ui.sbMinorTicksNumber->setValue( m_axis->minorTicksNumber() ); ui.leMinorTicksIncrement->setText( QString::number( m_axis->minorTicksIncrement()) ); ui.cbMinorTicksLineStyle->setCurrentIndex( (int) m_axis->minorTicksPen().style() ); ui.kcbMinorTicksColor->setColor( m_axis->minorTicksPen().color() ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksPen().widthF(),Worksheet::Point) ); ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(m_axis->minorTicksLength(),Worksheet::Point) ); ui.sbMinorTicksOpacity->setValue( round(m_axis->minorTicksOpacity()*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsFormat->setCurrentIndex( (int) m_axis->labelsFormat() ); ui.chkLabelsAutoPrecision->setChecked( (int) m_axis->labelsAutoPrecision() ); ui.sbLabelsPrecision->setValue( (int)m_axis->labelsPrecision() ); ui.cbLabelsPosition->setCurrentIndex( (int) m_axis->labelsPosition() ); ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(m_axis->labelsOffset(),Worksheet::Point) ); ui.sbLabelsRotation->setValue( m_axis->labelsRotationAngle() ); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont( font ); ui.kcbLabelsFontColor->setColor( m_axis->labelsColor() ); ui.leLabelsPrefix->setText( m_axis->labelsPrefix() ); ui.leLabelsSuffix->setText( m_axis->labelsSuffix() ); ui.sbLabelsOpacity->setValue( round(m_axis->labelsOpacity()*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( (int) m_axis->majorGridPen().style() ); ui.kcbMajorGridColor->setColor( m_axis->majorGridPen().color() ); ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->majorGridPen().widthF(),Worksheet::Point) ); ui.sbMajorGridOpacity->setValue( round(m_axis->majorGridOpacity()*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( (int) m_axis->minorGridPen().style() ); ui.kcbMinorGridColor->setColor( m_axis->minorGridPen().color() ); ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(m_axis->minorGridPen().widthF(),Worksheet::Point) ); ui.sbMinorGridOpacity->setValue( round(m_axis->minorGridOpacity()*100.0) ); m_initializing=true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); m_initializing=false; } void AxisDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_axesList.size(); if (size>1) m_axis->beginMacro(i18n("%1 axes: template \"%2\" loaded", size, name)); else m_axis->beginMacro(i18n("%1: template \"%2\" loaded", m_axis->name(), name)); this->loadConfig(config); m_axis->endMacro(); } void AxisDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Axis" ); //General ui.cbOrientation->setCurrentIndex( group.readEntry("Orientation", (int) m_axis->orientation()) ); int index = group.readEntry("Position", (int) m_axis->position()); if (index > 1) ui.cbPosition->setCurrentIndex(index-2); else ui.cbPosition->setCurrentIndex(index); ui.lePosition->setText( QString::number( group.readEntry("PositionOffset", m_axis->offset())) ); ui.cbScale->setCurrentIndex( group.readEntry("Scale", (int) m_axis->scale()) ); ui.chkAutoScale->setChecked(group.readEntry("AutoScale", m_axis->autoScale())); ui.leStart->setText( QString::number( group.readEntry("Start", m_axis->start())) ); ui.leEnd->setText( QString::number( group.readEntry("End", m_axis->end())) ); ui.leZeroOffset->setText( QString::number( group.readEntry("ZeroOffset", m_axis->zeroOffset())) ); ui.leScalingFactor->setText( QString::number( group.readEntry("ScalingFactor", m_axis->scalingFactor())) ); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->loadConfig(axisLabelGroup); //Line ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_axis->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_axis->linePen().color()) ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_axis->linePen().widthF()),Worksheet::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_axis->lineOpacity())*100.0) ); ui.cbArrowType->setCurrentIndex( group.readEntry("ArrowType", (int) m_axis->arrowType()) ); ui.cbArrowPosition->setCurrentIndex( group.readEntry("ArrowPosition", (int) m_axis->arrowPosition()) ); ui.sbArrowSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ArrowSize", m_axis->arrowSize()), Worksheet::Point) ); //Major ticks ui.cbMajorTicksDirection->setCurrentIndex( group.readEntry("MajorTicksDirection", (int) m_axis->majorTicksDirection()) ); ui.cbMajorTicksType->setCurrentIndex( group.readEntry("MajorTicksType", (int) m_axis->majorTicksType()) ); ui.sbMajorTicksNumber->setValue( group.readEntry("MajorTicksNumber", m_axis->majorTicksNumber()) ); ui.leMajorTicksIncrement->setText( QString::number( group.readEntry("MajorTicksIncrement", m_axis->majorTicksIncrement())) ); ui.cbMajorTicksLineStyle->setCurrentIndex( group.readEntry("MajorTicksLineStyle", (int) m_axis->majorTicksPen().style()) ); ui.kcbMajorTicksColor->setColor( group.readEntry("MajorTicksColor", m_axis->majorTicksPen().color()) ); ui.sbMajorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksWidth", m_axis->majorTicksPen().widthF()),Worksheet::Point) ); ui.sbMajorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorTicksLength", m_axis->majorTicksLength()),Worksheet::Point) ); ui.sbMajorTicksOpacity->setValue( round(group.readEntry("MajorTicksOpacity", m_axis->majorTicksOpacity())*100.0) ); //Minor ticks ui.cbMinorTicksDirection->setCurrentIndex( group.readEntry("MinorTicksDirection", (int) m_axis->minorTicksDirection()) ); ui.cbMinorTicksType->setCurrentIndex( group.readEntry("MinorTicksType", (int) m_axis->minorTicksType()) ); ui.sbMinorTicksNumber->setValue( group.readEntry("MinorTicksNumber", m_axis->minorTicksNumber()) ); ui.leMinorTicksIncrement->setText( QString::number( group.readEntry("MinorTicksIncrement", m_axis->minorTicksIncrement())) ); ui.cbMinorTicksLineStyle->setCurrentIndex( group.readEntry("MinorTicksLineStyle", (int) m_axis->minorTicksPen().style()) ); ui.kcbMinorTicksColor->setColor( group.readEntry("MinorTicksColor", m_axis->minorTicksPen().color()) ); ui.sbMinorTicksWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksWidth", m_axis->minorTicksPen().widthF()),Worksheet::Point) ); ui.sbMinorTicksLength->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorTicksLength", m_axis->minorTicksLength()),Worksheet::Point) ); ui.sbMinorTicksOpacity->setValue( round(group.readEntry("MinorTicksOpacity", m_axis->minorTicksOpacity())*100.0) ); //Extra ticks //TODO // Tick label ui.cbLabelsFormat->setCurrentIndex( group.readEntry("LabelsFormat", (int) m_axis->labelsFormat()) ); ui.chkLabelsAutoPrecision->setChecked( group.readEntry("LabelsAutoPrecision", (int) m_axis->labelsAutoPrecision()) ); ui.sbLabelsPrecision->setValue( group.readEntry("LabelsPrecision", (int)m_axis->labelsPrecision()) ); ui.cbLabelsPosition->setCurrentIndex( group.readEntry("LabelsPosition", (int) m_axis->labelsPosition()) ); ui.sbLabelsOffset->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LabelsOffset", m_axis->labelsOffset()), Worksheet::Point) ); ui.sbLabelsRotation->setValue( group.readEntry("LabelsRotation", m_axis->labelsRotationAngle()) ); //we need to set the font size in points for KFontRequester QFont font = m_axis->labelsFont(); font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelsFont->setFont( group.readEntry("LabelsFont", font) ); ui.kcbLabelsFontColor->setColor( group.readEntry("LabelsFontColor", m_axis->labelsColor()) ); ui.leLabelsPrefix->setText( group.readEntry("LabelsPrefix", m_axis->labelsPrefix()) ); ui.leLabelsSuffix->setText( group.readEntry("LabelsSuffix", m_axis->labelsSuffix()) ); ui.sbLabelsOpacity->setValue( round(group.readEntry("LabelsOpacity", m_axis->labelsOpacity())*100.0) ); //Grid ui.cbMajorGridStyle->setCurrentIndex( group.readEntry("MajorGridStyle", (int) m_axis->majorGridPen().style()) ); ui.kcbMajorGridColor->setColor( group.readEntry("MajorGridColor", m_axis->majorGridPen().color()) ); ui.sbMajorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MajorGridWidth", m_axis->majorGridPen().widthF()),Worksheet::Point) ); ui.sbMajorGridOpacity->setValue( round(group.readEntry("MajorGridOpacity", m_axis->majorGridOpacity())*100.0) ); ui.cbMinorGridStyle->setCurrentIndex( group.readEntry("MinorGridStyle", (int) m_axis->minorGridPen().style()) ); ui.kcbMinorGridColor->setColor( group.readEntry("MinorGridColor", m_axis->minorGridPen().color()) ); ui.sbMinorGridWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("MinorGridWidth", m_axis->minorGridPen().widthF()),Worksheet::Point) ); ui.sbMinorGridOpacity->setValue( round(group.readEntry("MinorGridOpacity", m_axis->minorGridOpacity())*100.0) ); m_initializing=true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); this->majorTicksTypeChanged(ui.cbMajorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMajorTicksLineStyle, ui.kcbMajorTicksColor->color()); this->minorTicksTypeChanged(ui.cbMinorTicksType->currentIndex()); GuiTools::updatePenStyles(ui.cbMinorTicksLineStyle, ui.kcbMinorTicksColor->color()); GuiTools::updatePenStyles(ui.cbMajorGridStyle, ui.kcbMajorGridColor->color()); GuiTools::updatePenStyles(ui.cbMinorGridStyle, ui.kcbMinorGridColor->color()); m_initializing=false; } void AxisDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Axis" ); //General group.writeEntry("Orientation", ui.cbOrientation->currentIndex()); if (ui.cbPosition->currentIndex() == 2) { group.writeEntry("Position", (int)Axis::AxisCentered); } else if (ui.cbPosition->currentIndex() == 3) { group.writeEntry("Position", (int)Axis::AxisCustom); } else { if ( ui.cbOrientation->currentIndex() == Axis::AxisHorizontal ) group.writeEntry("Position", ui.cbPosition->currentIndex()); else group.writeEntry("Position", ui.cbPosition->currentIndex()+2); } group.writeEntry("PositionOffset", ui.lePosition->text()); group.writeEntry("Scale", ui.cbScale->currentIndex()); group.writeEntry("Start", ui.leStart->text()); group.writeEntry("End", ui.leEnd->text()); group.writeEntry("ZeroOffset", ui.leZeroOffset->text()); group.writeEntry("ScalingFactor", ui.leScalingFactor->text()); //Title KConfigGroup axisLabelGroup = config.group("AxisLabel"); labelWidget->saveConfig(axisLabelGroup); //Line group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(), Worksheet::Point)); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100); //Major ticks group.writeEntry("MajorTicksDirection", ui.cbMajorTicksDirection->currentIndex()); group.writeEntry("MajorTicksType", ui.cbMajorTicksType->currentIndex()); group.writeEntry("MajorTicksNumber", ui.sbMajorTicksNumber->value()); group.writeEntry("MajorTicksIncrement", ui.leMajorTicksIncrement->text()); group.writeEntry("MajorTicksLineStyle", ui.cbMajorTicksLineStyle->currentIndex()); group.writeEntry("MajorTicksColor", ui.kcbMajorTicksColor->color()); group.writeEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(ui.sbMajorTicksWidth->value(),Worksheet::Point)); group.writeEntry("MajorTicksLength", Worksheet::convertToSceneUnits(ui.sbMajorTicksLength->value(),Worksheet::Point)); group.writeEntry("MajorTicksOpacity", ui.sbMajorTicksOpacity->value()/100); //Minor ticks group.writeEntry("MinorTicksDirection", ui.cbMinorTicksDirection->currentIndex()); group.writeEntry("MinorTicksType", ui.cbMinorTicksType->currentIndex()); group.writeEntry("MinorTicksNumber", ui.sbMinorTicksNumber->value()); group.writeEntry("MinorTicksIncrement", ui.leMinorTicksIncrement->text()); group.writeEntry("MinorTicksLineStyle", ui.cbMinorTicksLineStyle->currentIndex()); group.writeEntry("MinorTicksColor", ui.kcbMinorTicksColor->color()); group.writeEntry("MinorTicksWidth", Worksheet::convertFromSceneUnits(ui.sbMinorTicksWidth->value(),Worksheet::Point)); group.writeEntry("MinorTicksLength", Worksheet::convertFromSceneUnits(ui.sbMinorTicksLength->value(),Worksheet::Point)); group.writeEntry("MinorTicksOpacity", ui.sbMinorTicksOpacity->value()/100); //Extra ticks // TODO // Tick label group.writeEntry("LabelsFormat", ui.cbLabelsFormat->currentIndex()); group.writeEntry("LabelsAutoPrecision", ui.chkLabelsAutoPrecision->isChecked()); group.writeEntry("LabelsPrecision", ui.sbLabelsPrecision->value()); group.writeEntry("LabelsPosition", ui.cbLabelsPosition->currentIndex()); group.writeEntry("LabelsOffset", Worksheet::convertToSceneUnits(ui.sbLabelsOffset->value(), Worksheet::Point)); group.writeEntry("LabelsRotation", ui.sbLabelsRotation->value()); group.writeEntry("LabelsFont", ui.kfrLabelsFont->font()); group.writeEntry("LabelsFontColor", ui.kcbLabelsFontColor->color()); group.writeEntry("LabelsPrefix", ui.leLabelsPrefix->text()); group.writeEntry("LabelsSuffix", ui.leLabelsSuffix->text()); group.writeEntry("LabelsOpacity", ui.sbLabelsOpacity->value()/100); //Grid group.writeEntry("MajorGridStyle", ui.cbMajorGridStyle->currentIndex()); group.writeEntry("MajorGridColor", ui.kcbMajorGridColor->color()); group.writeEntry("MajorGridWidth", Worksheet::convertToSceneUnits(ui.sbMajorGridWidth->value(), Worksheet::Point)); group.writeEntry("MajorGridOpacity", ui.sbMajorGridOpacity->value()/100); group.writeEntry("MinorGridStyle", ui.cbMinorGridStyle->currentIndex()); group.writeEntry("MinorGridColor", ui.kcbMinorGridColor->color()); group.writeEntry("MinorGridWidth", Worksheet::convertToSceneUnits(ui.sbMinorGridWidth->value(), Worksheet::Point)); group.writeEntry("MinorGridOpacity", ui.sbMinorGridOpacity->value()/100); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp index e6f1a5d6f..7f9d64b62 100644 --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp @@ -1,1484 +1,1482 @@ /*************************************************************************** File : CartesianPlotDock.cpp Project : LabPlot Description : widget for cartesian plot properties -------------------------------------------------------------------- Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlotDock.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/core/column/Column.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include -#include - /*! \class CartesianPlotDock \brief Provides a widget for editing the properties of the cartesian plot currently selected in the project explorer. \ingroup kdefrontend */ CartesianPlotDock::CartesianPlotDock(QWidget *parent): QWidget(parent), m_plot(0), labelWidget(0), m_initializing(false) { ui.setupUi(this); //"General"-tab QButtonGroup* rangeButtonsGroup(new QButtonGroup); rangeButtonsGroup->addButton(ui.rbRangeFirst); rangeButtonsGroup->addButton(ui.rbRangeLast); rangeButtonsGroup->addButton(ui.rbRangeFree); //"Range breaks"-tab ui.bAddXBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveXBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbXBreak->addItem("1"); ui.bAddYBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveYBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbYBreak->addItem("1"); //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leBackgroundFileName->setCompleter(completer); //"Title"-tab QHBoxLayout* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget=new LabelWidget(ui.tabTitle); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { QGridLayout* layout = qobject_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Validators ui.leRangeFirst->setValidator( new QIntValidator(ui.leRangeFirst) ); ui.leRangeLast->setValidator( new QIntValidator(ui.leRangeLast) ); ui.leXBreakStart->setValidator( new QDoubleValidator(ui.leXBreakStart) ); ui.leXBreakEnd->setValidator( new QDoubleValidator(ui.leXBreakEnd) ); ui.leYBreakStart->setValidator( new QDoubleValidator(ui.leYBreakStart) ); ui.leYBreakEnd->setValidator( new QDoubleValidator(ui.leYBreakEnd) ); //SIGNAL/SLOT //General connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.sbLeft, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbTop, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbWidth, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbHeight, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.leRangeFirst, SIGNAL(textChanged(QString)), this, SLOT(rangeFirstChanged(QString)) ); connect( ui.leRangeLast, SIGNAL(textChanged(QString)), this, SLOT(rangeLastChanged(QString)) ); connect( rangeButtonsGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(rangeTypeChanged()) ); connect( ui.chkAutoScaleX, SIGNAL(stateChanged(int)), this, SLOT(autoScaleXChanged(int)) ); connect( ui.leXMin, SIGNAL(returnPressed()), this, SLOT(xMinChanged()) ); connect( ui.leXMax, SIGNAL(returnPressed()), this, SLOT(xMaxChanged()) ); connect( ui.cbXScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(xScaleChanged(int)) ); connect( ui.chkAutoScaleY, SIGNAL(stateChanged(int)), this, SLOT(autoScaleYChanged(int)) ); connect( ui.leYMin, SIGNAL(returnPressed()), this, SLOT(yMinChanged()) ); connect( ui.leYMax, SIGNAL(returnPressed()), this, SLOT(yMaxChanged()) ); connect( ui.cbYScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(yScaleChanged(int)) ); //Range breaks connect( ui.chkXBreak, SIGNAL(toggled(bool)), this, SLOT(toggleXBreak(bool)) ); connect( ui.bAddXBreak, SIGNAL(clicked()), this, SLOT(addXBreak()) ); connect( ui.bRemoveXBreak, SIGNAL(clicked()), this, SLOT(removeXBreak()) ); connect( ui.cbXBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentXBreakChanged(int)) ); connect( ui.leXBreakStart, SIGNAL(returnPressed()), this, SLOT(xBreakStartChanged()) ); connect( ui.leXBreakEnd, SIGNAL(returnPressed()), this, SLOT(xBreakEndChanged()) ); connect( ui.sbXBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(xBreakPositionChanged(int)) ); connect( ui.cbXBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(xBreakStyleChanged(int)) ); connect( ui.chkYBreak, SIGNAL(toggled(bool)), this, SLOT(toggleYBreak(bool)) ); connect( ui.bAddYBreak, SIGNAL(clicked()), this, SLOT(addYBreak()) ); connect( ui.bRemoveYBreak, SIGNAL(clicked()), this, SLOT(removeYBreak()) ); connect( ui.cbYBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentYBreakChanged(int)) ); connect( ui.leYBreakStart, SIGNAL(returnPressed()), this, SLOT(yBreakStartChanged()) ); connect( ui.leYBreakEnd, SIGNAL(returnPressed()), this, SLOT(yBreakEndChanged()) ); connect( ui.sbYBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(yBreakPositionChanged(int)) ); connect( ui.cbYBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(yBreakStyleChanged(int)) ); //Background connect( ui.cbBackgroundType, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundTypeChanged(int)) ); connect( ui.cbBackgroundColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundColorStyleChanged(int)) ); connect( ui.cbBackgroundImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundImageStyleChanged(int)) ); connect( ui.cbBackgroundBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundBrushStyleChanged(int)) ); connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile()) ); connect( ui.leBackgroundFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leBackgroundFileName, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameChanged()) ); connect( ui.kcbBackgroundFirstColor, SIGNAL(changed(QColor)), this, SLOT(backgroundFirstColorChanged(QColor)) ); connect( ui.kcbBackgroundSecondColor, SIGNAL(changed(QColor)), this, SLOT(backgroundSecondColorChanged(QColor)) ); connect( ui.sbBackgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(backgroundOpacityChanged(int)) ); //Border connect( ui.cbBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(borderStyleChanged(int)) ); connect( ui.kcbBorderColor, SIGNAL(changed(QColor)), this, SLOT(borderColorChanged(QColor)) ); connect( ui.sbBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(borderWidthChanged(double)) ); connect( ui.sbBorderCornerRadius, SIGNAL(valueChanged(double)), this, SLOT(borderCornerRadiusChanged(double)) ); connect( ui.sbBorderOpacity, SIGNAL(valueChanged(int)), this, SLOT(borderOpacityChanged(int)) ); //Padding connect( ui.sbPaddingHorizontal, SIGNAL(valueChanged(double)), this, SLOT(horizontalPaddingChanged(double)) ); connect( ui.sbPaddingVertical, SIGNAL(valueChanged(double)), this, SLOT(verticalPaddingChanged(double)) ); //theme and template handlers QFrame* frame = new QFrame(this); QHBoxLayout* layout = new QHBoxLayout(frame); m_themeHandler = new ThemeHandler(this); layout->addWidget(m_themeHandler); connect(m_themeHandler, SIGNAL(loadThemeRequested(QString)), this, SLOT(loadTheme(QString))); connect(m_themeHandler, SIGNAL(saveThemeRequested(KConfig&)), this, SLOT(saveTheme(KConfig&))); connect(m_themeHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); //connect(this, SIGNAL(saveThemeEnable(bool)), m_themeHandler, SLOT(saveThemeEnable(bool))); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::CartesianPlot); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); //TODO: activate the tab again once the functionality is implemented ui.tabWidget->removeTab(2); init(); } CartesianPlotDock::~CartesianPlotDock() { } void CartesianPlotDock::init() { this->retranslateUi(); /* //TODO: activate later once range breaking is implemented //create icons for the different styles for scale breaking QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbXBreakStyle->setIconSize( QSize(20,20) ); ui.cbYBreakStyle->setIconSize( QSize(20,20) ); //simple pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.end(); ui.cbXBreakStyle->setItemIcon(0, pm); ui.cbYBreakStyle->setItemIcon(0, pm); //vertical pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(8,14,8,6); pa.drawLine(12,14,12,6); pa.end(); ui.cbXBreakStyle->setItemIcon(1, pm); ui.cbYBreakStyle->setItemIcon(1, pm); //sloped pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(6,14,10,6); pa.drawLine(10,14,14,6); pa.end(); ui.cbXBreakStyle->setItemIcon(2, pm); ui.cbYBreakStyle->setItemIcon(2, pm); */ } void CartesianPlotDock::setPlots(QList list) { m_initializing = true; m_plotList = list; m_plot = list.first(); QList labels; for (auto* plot: list) labels.append(plot->title()); labelWidget->setLabels(labels); //if there is more then one plot in the list, disable the name and comment fields in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_plot->name()); ui.leComment->setText(m_plot->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); } //show the properties of the first plot this->load(); //update active widgets backgroundTypeChanged(ui.cbBackgroundType->currentIndex()); m_themeHandler->setCurrentTheme(m_plot->theme()); //Deactivate the geometry related widgets, if the worksheet layout is active. //Currently, a plot can only be a child of the worksheet itself, so we only need to ask the parent aspect (=worksheet). //TODO redesign this, if the hierarchy will be changend in future (a plot is a child of a new object group/container or so) Worksheet* w = dynamic_cast(m_plot->parentAspect()); if (w) { bool b = (w->layout() == Worksheet::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); connect(w, SIGNAL(layoutChanged(Worksheet::Layout)), this, SLOT(layoutChanged(Worksheet::Layout))); } //SIGNALs/SLOTs connect( m_plot, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(plotDescriptionChanged(const AbstractAspect*)) ); connect( m_plot, SIGNAL(rectChanged(QRectF&)), this, SLOT(plotRectChanged(QRectF&)) ); connect( m_plot, SIGNAL(rangeTypeChanged(CartesianPlot::RangeType)), this, SLOT(plotRangeTypeChanged(CartesianPlot::RangeType)) ); connect( m_plot, SIGNAL(rangeFirstValuesChanged(int)), this, SLOT(plotRangeFirstValuesChanged(int)) ); connect( m_plot, SIGNAL(rangeLastValuesChanged(int)), this, SLOT(plotRangeLastValuesChanged(int)) ); connect( m_plot, SIGNAL(xAutoScaleChanged(bool)), this, SLOT(plotXAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(xMinChanged(float)), this, SLOT(plotXMinChanged(float)) ); connect( m_plot, SIGNAL(xMaxChanged(float)), this, SLOT(plotXMaxChanged(float)) ); connect( m_plot, SIGNAL(xScaleChanged(int)), this, SLOT(plotXScaleChanged(int)) ); connect( m_plot, SIGNAL(yAutoScaleChanged(bool)), this, SLOT(plotYAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(yMinChanged(float)), this, SLOT(plotYMinChanged(float)) ); connect( m_plot, SIGNAL(yMaxChanged(float)), this, SLOT(plotYMaxChanged(float)) ); connect( m_plot, SIGNAL(yScaleChanged(int)), this, SLOT(plotYScaleChanged(int)) ); connect( m_plot, SIGNAL(visibleChanged(bool)), this, SLOT(plotVisibleChanged(bool)) ); //range breaks connect( m_plot, SIGNAL(xRangeBreakingEnabledChanged(bool)), this, SLOT(plotXRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(xRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotXRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); connect( m_plot, SIGNAL(yRangeBreakingEnabledChanged(bool)), this, SLOT(plotYRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(yRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotYRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); // Plot Area connect( m_plot->plotArea(), SIGNAL(backgroundTypeChanged(PlotArea::BackgroundType)), this, SLOT(plotBackgroundTypeChanged(PlotArea::BackgroundType)) ); connect( m_plot->plotArea(), SIGNAL(backgroundColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundBrushStyleChanged(Qt::BrushStyle)), this, SLOT(plotBackgroundBrushStyleChanged(Qt::BrushStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFirstColorChanged(QColor&)), this, SLOT(plotBackgroundFirstColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundSecondColorChanged(QColor&)), this, SLOT(plotBackgroundSecondColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFileNameChanged(QString&)), this, SLOT(plotBackgroundFileNameChanged(QString&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundOpacityChanged(float)), this, SLOT(plotBackgroundOpacityChanged(float)) ); connect( m_plot->plotArea(), SIGNAL(borderPenChanged(QPen&)), this, SLOT(plotBorderPenChanged(QPen&)) ); connect( m_plot->plotArea(), SIGNAL(borderOpacityChanged(float)), this, SLOT(plotBorderOpacityChanged(float)) ); connect( m_plot, SIGNAL(horizontalPaddingChanged(float)), this, SLOT(plotHorizontalPaddingChanged(float)) ); connect( m_plot, SIGNAL(verticalPaddingChanged(float)), this, SLOT(plotVerticalPaddingChanged(float)) ); m_initializing = false; } void CartesianPlotDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } //************************************************************ //**** SLOTs for changes triggered in CartesianPlotDock ****** //************************************************************ void CartesianPlotDock::retranslateUi() { m_initializing = true; //general ui.cbXScaling->addItem( i18n("linear") ); ui.cbXScaling->addItem( i18n("log(x)") ); ui.cbXScaling->addItem( i18n("log2(x)") ); ui.cbXScaling->addItem( i18n("ln(x)") ); ui.cbYScaling->addItem( i18n("linear") ); ui.cbYScaling->addItem( i18n("log(y)") ); ui.cbYScaling->addItem( i18n("log2(y)") ); ui.cbYScaling->addItem( i18n("ln(y)") ); //scale breakings ui.cbXBreakStyle->addItem( i18n("simple") ); ui.cbXBreakStyle->addItem( i18n("vertical") ); ui.cbXBreakStyle->addItem( i18n("sloped") ); ui.cbYBreakStyle->addItem( i18n("simple") ); ui.cbYBreakStyle->addItem( i18n("vertical") ); ui.cbYBreakStyle->addItem( i18n("sloped") ); //plot area ui.cbBackgroundType->addItem(i18n("color")); ui.cbBackgroundType->addItem(i18n("image")); ui.cbBackgroundType->addItem(i18n("pattern")); ui.cbBackgroundColorStyle->addItem(i18n("single color")); ui.cbBackgroundColorStyle->addItem(i18n("horizontal gradient")); ui.cbBackgroundColorStyle->addItem(i18n("vertical gradient")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from top left)")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from bottom left)")); ui.cbBackgroundColorStyle->addItem(i18n("radial gradient")); ui.cbBackgroundImageStyle->addItem(i18n("scaled and cropped")); ui.cbBackgroundImageStyle->addItem(i18n("scaled")); ui.cbBackgroundImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbBackgroundImageStyle->addItem(i18n("centered")); ui.cbBackgroundImageStyle->addItem(i18n("tiled")); ui.cbBackgroundImageStyle->addItem(i18n("center tiled")); GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); m_initializing = false; } // "General"-tab void CartesianPlotDock::nameChanged() { if (m_initializing) return; m_plot->setName(ui.leName->text()); } void CartesianPlotDock::commentChanged() { if (m_initializing) return; m_plot->setComment(ui.leComment->text()); } void CartesianPlotDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setVisible(state); } void CartesianPlotDock::geometryChanged() { if (m_initializing) return; float x = Worksheet::convertToSceneUnits(ui.sbLeft->value(), Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(ui.sbTop->value(), Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(ui.sbWidth->value(), Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(ui.sbHeight->value(), Worksheet::Centimeter); QRectF rect(x,y,w,h); m_plot->setRect(rect); } /*! Called when the layout in the worksheet gets changed. Enables/disables the geometry widgets if the layout was deactivated/activated. Shows the new geometry values of the first plot if the layout was activated. */ void CartesianPlotDock::layoutChanged(Worksheet::Layout layout) { bool b = (layout == Worksheet::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); } void CartesianPlotDock::rangeTypeChanged() { CartesianPlot::RangeType type; if (ui.rbRangeFirst->isChecked()) { ui.leRangeFirst->setEnabled(true); ui.leRangeLast->setEnabled(false); type = CartesianPlot::RangeFirst; } else if (ui.rbRangeLast->isChecked()) { ui.leRangeFirst->setEnabled(false); ui.leRangeLast->setEnabled(true); type = CartesianPlot::RangeLast; } else { ui.leRangeFirst->setEnabled(false); ui.leRangeLast->setEnabled(false); type = CartesianPlot::RangeFree; } if (m_initializing) return; for (auto* plot: m_plotList) plot->setRangeType(type); } void CartesianPlotDock::rangeFirstChanged(const QString& text) { if (m_initializing) return; const int value = text.toInt(); for (auto* plot: m_plotList) plot->setRangeFirstValues(value); } void CartesianPlotDock::rangeLastChanged(const QString & text) { if (m_initializing) return; const int value = text.toInt(); for (auto* plot: m_plotList) plot->setRangeLastValues(value); } void CartesianPlotDock::autoScaleXChanged(int state) { bool checked = (state==Qt::Checked); ui.leXMin->setEnabled(!checked); ui.leXMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot: m_plotList) plot->setAutoScaleX(checked); } void CartesianPlotDock::xMinChanged() { if (m_initializing) return; float value = ui.leXMin->text().toDouble(); for (auto* plot: m_plotList) plot->setXMin(value); } void CartesianPlotDock::xMaxChanged() { if (m_initializing) return; float value = ui.leXMax->text().toDouble(); for (auto* plot: m_plotList) plot->setXMax(value); } /*! called on scale changes (linear, log) for the x-axis */ void CartesianPlotDock::xScaleChanged(int scale) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setXScale((CartesianPlot::Scale) scale); } void CartesianPlotDock::autoScaleYChanged(int state) { bool checked = (state==Qt::Checked); ui.leYMin->setEnabled(!checked); ui.leYMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot: m_plotList) plot->setAutoScaleY(checked); } void CartesianPlotDock::yMinChanged() { if (m_initializing) return; float value = ui.leYMin->text().toDouble(); for (auto* plot: m_plotList) plot->setYMin(value); } void CartesianPlotDock::yMaxChanged() { if (m_initializing) return; float value = ui.leYMax->text().toDouble(); for (auto* plot: m_plotList) plot->setYMax(value); } /*! called on scale changes (linear, log) for the y-axis */ void CartesianPlotDock::yScaleChanged(int index) { if (m_initializing) return; CartesianPlot::Scale scale = (CartesianPlot::Scale)index; for (auto* plot: m_plotList) plot->setYScale(scale); } // "Range Breaks"-tab // x-range breaks void CartesianPlotDock::toggleXBreak(bool b) { ui.frameXBreakEdit->setEnabled(b); ui.leXBreakStart->setEnabled(b); ui.leXBreakEnd->setEnabled(b); ui.sbXBreakPosition->setEnabled(b); ui.cbXBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot: m_plotList) plot->setXRangeBreakingEnabled(b); } void CartesianPlotDock::addXBreak() { ui.bRemoveXBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list<setXRangeBreaks(breaks); ui.cbXBreak->addItem(QString::number(ui.cbXBreak->count()+1)); ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); } void CartesianPlotDock::removeXBreak() { ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); ui.cbXBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); if (index < ui.cbXBreak->count()-1) ui.cbXBreak->setCurrentIndex(index); else ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); ui.bRemoveXBreak->setVisible(ui.cbXBreak->count()!=1); } void CartesianPlotDock::currentXBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->xRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leXBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leXBreakEnd->setText(str); ui.sbXBreakPosition->setValue(rangeBreak.position*100); ui.cbXBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::xBreakStartChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].start = ui.leXBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakEndChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].end = ui.leXBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreakStyle style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } // y-range breaks void CartesianPlotDock::toggleYBreak(bool b) { ui.frameYBreakEdit->setEnabled(b); ui.leYBreakStart->setEnabled(b); ui.leYBreakEnd->setEnabled(b); ui.sbYBreakPosition->setEnabled(b); ui.cbYBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot: m_plotList) plot->setYRangeBreakingEnabled(b); } void CartesianPlotDock::addYBreak() { ui.bRemoveYBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list << b; breaks.lastChanged = breaks.list.size() - 1; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->addItem(QString::number(ui.cbYBreak->count()+1)); ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); } void CartesianPlotDock::removeYBreak() { ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); if (index < ui.cbYBreak->count()-1) ui.cbYBreak->setCurrentIndex(index); else ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); ui.bRemoveYBreak->setVisible(ui.cbYBreak->count() != 1); } void CartesianPlotDock::currentYBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->yRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leYBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leYBreakEnd->setText(str); ui.sbYBreakPosition->setValue(rangeBreak.position*100); ui.cbYBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::yBreakStartChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].start = ui.leYBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakEndChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].end = ui.leYBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreakStyle style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } // "Plot area"-tab void CartesianPlotDock::backgroundTypeChanged(int index) { PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundType(type); } void CartesianPlotDock::backgroundColorStyleChanged(int index) { PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); } if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundColorStyle(style); } void CartesianPlotDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundImageStyle(style); } void CartesianPlotDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundBrushStyle(style); } void CartesianPlotDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFirstColor(c); } void CartesianPlotDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void CartesianPlotDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "CartesianPlotDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const auto& format: QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); formats.isEmpty() ? formats+=f : formats+=' '+f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFileName(path); } void CartesianPlotDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFileName(fileName); } void CartesianPlotDock::backgroundOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundOpacity(opacity); } // "Border"-tab void CartesianPlotDock::borderStyleChanged(int index) { if (m_initializing) return; Qt::PenStyle penStyle = Qt::PenStyle(index); QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setStyle(penStyle); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setColor(color); plot->plotArea()->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void CartesianPlotDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderCornerRadiusChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBorderCornerRadius(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotDock::borderOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot: m_plotList) plot->plotArea()->setBorderOpacity(opacity); } void CartesianPlotDock::horizontalPaddingChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setHorizontalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotDock::verticalPaddingChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setVerticalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } //************************************************************* //****** SLOTs for changes triggered in CartesianPlot ********* //************************************************************* //general void CartesianPlotDock::plotDescriptionChanged(const AbstractAspect* aspect) { if (m_plot != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void CartesianPlotDock::plotRectChanged(QRectF& rect) { m_initializing = true; ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(rect.x(), Worksheet::Centimeter)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(rect.y(), Worksheet::Centimeter)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(rect.width(), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(rect.height(), Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotRangeTypeChanged(CartesianPlot::RangeType type) { m_initializing = true; switch (type) { case CartesianPlot::RangeFree: ui.rbRangeFree->setChecked(true); break; case CartesianPlot::RangeFirst: ui.rbRangeFirst->setChecked(true); break; case CartesianPlot::RangeLast: ui.rbRangeLast->setChecked(true); break; } m_initializing = false; } void CartesianPlotDock::plotRangeFirstValuesChanged(int value) { m_initializing = true; ui.leRangeFirst->setText(QString::number(value)); m_initializing = false; } void CartesianPlotDock::plotRangeLastValuesChanged(int value) { m_initializing = true; ui.leRangeLast->setText(QString::number(value)); m_initializing = false; } void CartesianPlotDock::plotXAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleX->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotXMinChanged(float value) { m_initializing = true; ui.leXMin->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotXMaxChanged(float value) { m_initializing = true; ui.leXMax->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotXScaleChanged(int scale) { m_initializing = true; ui.cbXScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotYAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleY->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotYMinChanged(float value) { m_initializing = true; ui.leYMin->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotYMaxChanged(float value) { m_initializing = true; ui.leYMax->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotYScaleChanged(int scale) { m_initializing = true; ui.cbYScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotVisibleChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //range breaks void CartesianPlotDock::plotXRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkXBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotXRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } void CartesianPlotDock::plotYRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkYBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotYRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } //background void CartesianPlotDock::plotBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void CartesianPlotDock::plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundFileNameChanged(QString& filename) { m_initializing = true; ui.leBackgroundFileName->setText(filename); m_initializing = false; } void CartesianPlotDock::plotBackgroundOpacityChanged(float opacity) { m_initializing = true; ui.sbBackgroundOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void CartesianPlotDock::plotBorderPenChanged(QPen& pen) { m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void CartesianPlotDock::plotBorderCornerRadiusChanged(float value) { m_initializing = true; ui.sbBorderCornerRadius->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotBorderOpacityChanged(float value) { m_initializing = true; float v = (float)value*100.; ui.sbBorderOpacity->setValue(v); m_initializing = false; } void CartesianPlotDock::plotHorizontalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotVerticalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void CartesianPlotDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_plotList.size(); if (size > 1) m_plot->beginMacro(i18n("%1 cartesian plots: template \"%2\" loaded", size, name)); else m_plot->beginMacro(i18n("%1: template \"%2\" loaded", m_plot->name(), name)); this->loadConfig(config); m_plot->endMacro(); } void CartesianPlotDock::load() { //General-tab ui.chkVisible->setChecked(m_plot->isVisible()); ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().x(), Worksheet::Centimeter)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().y(), Worksheet::Centimeter)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().width(), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().height(), Worksheet::Centimeter)); switch (m_plot->rangeType()) { case CartesianPlot::RangeFree: ui.rbRangeFree->setChecked(true); break; case CartesianPlot::RangeFirst: ui.rbRangeFirst->setChecked(true); break; case CartesianPlot::RangeLast: ui.rbRangeLast->setChecked(true); break; } rangeTypeChanged(); ui.leRangeFirst->setText( QString::number(m_plot->rangeFirstValues()) ); ui.leRangeLast->setText( QString::number(m_plot->rangeLastValues()) ); ui.chkAutoScaleX->setChecked(m_plot->autoScaleX()); ui.leXMin->setText( QString::number(m_plot->xMin()) ); ui.leXMax->setText( QString::number(m_plot->xMax()) ); ui.cbXScaling->setCurrentIndex( (int) m_plot->xScale() ); ui.chkAutoScaleY->setChecked(m_plot->autoScaleY()); ui.leYMin->setText( QString::number(m_plot->yMin()) ); ui.leYMax->setText( QString::number(m_plot->yMax()) ); ui.cbYScaling->setCurrentIndex( (int)m_plot->yScale() ); //Title labelWidget->load(); //x-range breaks, show the first break ui.chkXBreak->setChecked(m_plot->xRangeBreakingEnabled()); this->toggleXBreak(m_plot->xRangeBreakingEnabled()); ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); ui.cbXBreak->clear(); if (!m_plot->xRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->xRangeBreaks().list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); } else ui.cbXBreak->addItem("1"); ui.cbXBreak->setCurrentIndex(0); //y-range breaks, show the first break ui.chkYBreak->setChecked(m_plot->yRangeBreakingEnabled()); this->toggleYBreak(m_plot->yRangeBreakingEnabled()); ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); ui.cbYBreak->clear(); if (!m_plot->yRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->yRangeBreaks().list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); } else ui.cbYBreak->addItem("1"); ui.cbYBreak->setCurrentIndex(0); //"Plot Area"-tab //Background ui.cbBackgroundType->setCurrentIndex( (int)m_plot->plotArea()->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_plot->plotArea()->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_plot->plotArea()->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_plot->plotArea()->backgroundSecondColor() ); ui.sbBackgroundOpacity->setValue( round(m_plot->plotArea()->backgroundOpacity()*100.0) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_plot->plotArea()->backgroundFileName().isEmpty() && !QFile::exists(m_plot->plotArea()->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); //Padding ui.sbPaddingHorizontal->setValue( Worksheet::convertFromSceneUnits(m_plot->horizontalPadding(), Worksheet::Centimeter) ); ui.sbPaddingVertical->setValue( Worksheet::convertFromSceneUnits(m_plot->verticalPadding(), Worksheet::Centimeter) ); //Border ui.kcbBorderColor->setColor( m_plot->plotArea()->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_plot->plotArea()->borderPen().style() ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderPen().widthF(), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderCornerRadius(), Worksheet::Centimeter) ); ui.sbBorderOpacity->setValue( round(m_plot->plotArea()->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); } void CartesianPlotDock::loadConfig(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in CartesianPlotDock::setPlots(). //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->loadConfig(plotTitleGroup); //Scale breakings //TODO //Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_plot->plotArea()->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_plot->plotArea()->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_plot->plotArea()->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_plot->plotArea()->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_plot->plotArea()->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_plot->plotArea()->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_plot->plotArea()->backgroundSecondColor()) ); ui.sbBackgroundOpacity->setValue( round(group.readEntry("BackgroundOpacity", m_plot->plotArea()->backgroundOpacity())*100.0) ); ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(group.readEntry("HorizontalPadding", m_plot->horizontalPadding()), Worksheet::Centimeter)); ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(group.readEntry("VerticalPadding", m_plot->verticalPadding()), Worksheet::Centimeter)); //Border-tab ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_plot->plotArea()->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int) m_plot->plotArea()->borderPen().style()) ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_plot->plotArea()->borderPen().widthF()), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderCornerRadius", m_plot->plotArea()->borderCornerRadius()), Worksheet::Centimeter) ); ui.sbBorderOpacity->setValue( group.readEntry("BorderOpacity", m_plot->plotArea()->borderOpacity())*100 ); m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void CartesianPlotDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->saveConfig(plotTitleGroup); //Scale breakings //TODO //Background group.writeEntry("BackgroundType", ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); group.writeEntry("HorizontalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingHorizontal->value(), Worksheet::Centimeter)); group.writeEntry("VerticalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingVertical->value(), Worksheet::Centimeter)); //Border group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); group.writeEntry("BorderCornerRadius", Worksheet::convertToSceneUnits(ui.sbBorderCornerRadius->value(), Worksheet::Centimeter)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); config.sync(); } void CartesianPlotDock::loadTheme(const QString& theme) { for (auto* plot: m_plotList) plot->setTheme(theme); } void CartesianPlotDock::saveTheme(KConfig& config) const { if(!m_plotList.isEmpty()) m_plotList.at(0)->saveTheme(config); } diff --git a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp index 36817b896..8c0966ce4 100644 --- a/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotLegendDock.cpp @@ -1,1053 +1,1051 @@ /*************************************************************************** File : CartesianPlotLegendDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2013-2016 by Alexander Semke (alexander.semke@web.de) Description : widget for cartesian plot legend properties ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlotLegendDock.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/Worksheet.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include -#include - /*! \class CartesianPlotLegendDock \brief Provides a widget for editing the properties of the cartesian plot legend currently selected in the project explorer. \ingroup kdefrontend */ CartesianPlotLegendDock::CartesianPlotLegendDock(QWidget* parent) : QWidget(parent), m_legend(nullptr), labelWidget(nullptr), m_initializing(false) { ui.setupUi(this); //"Title"-tab QHBoxLayout* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget = new LabelWidget(ui.tabTitle); labelWidget->setNoGeometryMode(true); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //"Background"-tab ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leBackgroundFileName->setCompleter(completer); //adjust layouts in the tabs for (int i=0; icount(); ++i) { QGridLayout* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SIGNAL/SLOT //General connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.kfrLabelFont, SIGNAL(fontSelected(QFont)), this, SLOT(labelFontChanged(QFont)) ); connect( ui.kcbLabelColor, SIGNAL(changed(QColor)), this, SLOT(labelColorChanged(QColor)) ); connect( ui.cbOrder, SIGNAL(currentIndexChanged(int)), this, SLOT(labelOrderChanged(int)) ); connect( ui.sbLineSymbolWidth, SIGNAL(valueChanged(double)), this, SLOT(lineSymbolWidthChanged(double)) ); connect( ui.cbPositionX, SIGNAL(currentIndexChanged(int)), this, SLOT(positionXChanged(int)) ); connect( ui.cbPositionY, SIGNAL(currentIndexChanged(int)), this, SLOT(positionYChanged(int)) ); connect( ui.sbPositionX, SIGNAL(valueChanged(double)), this, SLOT(customPositionXChanged(double)) ); connect( ui.sbPositionY, SIGNAL(valueChanged(double)), this, SLOT(customPositionYChanged(double)) ); //Background connect( ui.cbBackgroundType, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundTypeChanged(int)) ); connect( ui.cbBackgroundColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundColorStyleChanged(int)) ); connect( ui.cbBackgroundImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundImageStyleChanged(int)) ); connect( ui.cbBackgroundBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundBrushStyleChanged(int)) ); connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile()) ); connect( ui.leBackgroundFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leBackgroundFileName, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameChanged()) ); connect( ui.kcbBackgroundFirstColor, SIGNAL(changed(QColor)), this, SLOT(backgroundFirstColorChanged(QColor)) ); connect( ui.kcbBackgroundSecondColor, SIGNAL(changed(QColor)), this, SLOT(backgroundSecondColorChanged(QColor)) ); connect( ui.sbBackgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(backgroundOpacityChanged(int)) ); //Border connect( ui.cbBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(borderStyleChanged(int)) ); connect( ui.kcbBorderColor, SIGNAL(changed(QColor)), this, SLOT(borderColorChanged(QColor)) ); connect( ui.sbBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(borderWidthChanged(double)) ); connect( ui.sbBorderCornerRadius, SIGNAL(valueChanged(double)), this, SLOT(borderCornerRadiusChanged(double)) ); connect( ui.sbBorderOpacity, SIGNAL(valueChanged(int)), this, SLOT(borderOpacityChanged(int)) ); //Layout connect( ui.sbLayoutTopMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutTopMarginChanged(double)) ); connect( ui.sbLayoutBottomMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutBottomMarginChanged(double)) ); connect( ui.sbLayoutLeftMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutLeftMarginChanged(double)) ); connect( ui.sbLayoutRightMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutRightMarginChanged(double)) ); connect( ui.sbLayoutHorizontalSpacing, SIGNAL(valueChanged(double)), this, SLOT(layoutHorizontalSpacingChanged(double)) ); connect( ui.sbLayoutVerticalSpacing, SIGNAL(valueChanged(double)), this, SLOT(layoutVerticalSpacingChanged(double)) ); connect( ui.sbLayoutColumnCount, SIGNAL(valueChanged(int)), this, SLOT(layoutColumnCountChanged(int)) ); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::CartesianPlotLegend); ui.verticalLayout->addWidget(templateHandler); templateHandler->show(); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); init(); } void CartesianPlotLegendDock::init() { this->retranslateUi(); } void CartesianPlotLegendDock::setLegends(QList list) { m_initializing = true; m_legendList = list; m_legend=list.first(); //if there is more then one legend in the list, disable the tab "general" if (list.size()==1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_legend->name()); ui.leComment->setText(m_legend->comment()); }else{ ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); } //show the properties of the first curve this->load(); //on the very first start the column count shown in UI is 1. //if the this count for m_legend is also 1 then the slot layoutColumnCountChanged is not called //and we need to disable the "order" widgets here. ui.lOrder->setVisible(m_legend->layoutColumnCount()!=1); ui.cbOrder->setVisible(m_legend->layoutColumnCount()!=1); //legend title QList labels; foreach(CartesianPlotLegend* legend, list) labels.append(legend->title()); labelWidget->setLabels(labels); //update active widgets backgroundTypeChanged(ui.cbBackgroundType->currentIndex()); //SIGNALs/SLOTs //General connect( m_legend, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(legendDescriptionChanged(const AbstractAspect*)) ); connect( m_legend, SIGNAL(labelFontChanged(QFont&)), this, SLOT(legendLabelFontChanged(QFont&)) ); connect( m_legend, SIGNAL(labelColorChanged(QColor&)), this, SLOT(legendLabelColorChanged(QColor&)) ); connect( m_legend, SIGNAL(labelColumnMajorChanged(bool)), this, SLOT(legendLabelOrderChanged(bool)) ); connect( m_legend, SIGNAL(positionChanged(CartesianPlotLegend::PositionWrapper)), this, SLOT(legendPositionChanged(CartesianPlotLegend::PositionWrapper)) ); connect( m_legend, SIGNAL(lineSymbolWidthChanged(float)), this, SLOT(legendLineSymbolWidthChanged(float)) ); connect(m_legend, SIGNAL(visibilityChanged(bool)), this, SLOT(legendVisibilityChanged(bool))); //background connect( m_legend, SIGNAL(backgroundTypeChanged(PlotArea::BackgroundType)), this, SLOT(legendBackgroundTypeChanged(PlotArea::BackgroundType)) ); connect( m_legend, SIGNAL(backgroundColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(legendBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_legend, SIGNAL(backgroundImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(legendBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_legend, SIGNAL(backgroundBrushStyleChanged(Qt::BrushStyle)), this, SLOT(legendBackgroundBrushStyleChanged(Qt::BrushStyle)) ); connect( m_legend, SIGNAL(backgroundFirstColorChanged(QColor&)), this, SLOT(legendBackgroundFirstColorChanged(QColor&)) ); connect( m_legend, SIGNAL(backgroundSecondColorChanged(QColor&)), this, SLOT(legendBackgroundSecondColorChanged(QColor&)) ); connect( m_legend, SIGNAL(backgroundFileNameChanged(QString&)), this, SLOT(legendBackgroundFileNameChanged(QString&)) ); connect( m_legend, SIGNAL(backgroundOpacityChanged(float)), this, SLOT(legendBackgroundOpacityChanged(float)) ); connect( m_legend, SIGNAL(borderPenChanged(QPen&)), this, SLOT(legendBorderPenChanged(QPen&)) ); connect( m_legend, SIGNAL(borderCornerRadiusChanged(float)), this, SLOT(legendBorderCornerRadiusChanged(float)) ); connect( m_legend, SIGNAL(borderOpacityChanged(float)), this, SLOT(legendBorderOpacityChanged(float)) ); //layout connect(m_legend,SIGNAL(layoutTopMarginChanged(float)),this,SLOT(legendLayoutTopMarginChanged(float))); connect(m_legend,SIGNAL(layoutBottomMarginChanged(float)),this,SLOT(legendLayoutBottomMarginChanged(float))); connect(m_legend,SIGNAL(layoutLeftMarginChanged(float)),this,SLOT(legendLayoutLeftMarginChanged(float))); connect(m_legend,SIGNAL(layoutRightMarginChanged(float)),this,SLOT(legendLayoutRightMarginChanged(float))); connect(m_legend,SIGNAL(layoutVerticalSpacingChanged(float)),this,SLOT(legendLayoutVerticalSpacingChanged(float))); connect(m_legend,SIGNAL(layoutHorizontalSpacingChanged(float)),this,SLOT(legendLayoutHorizontalSpacingChanged(float))); connect(m_legend,SIGNAL(layoutColumnCountChanged(int)),this,SLOT(legendLayoutColumnCountChanged(int))); m_initializing = false; } void CartesianPlotLegendDock::activateTitleTab() const{ ui.tabWidget->setCurrentWidget(ui.tabTitle); } //************************************************************ //** SLOTs for changes triggered in CartesianPlotLegendDock ** //************************************************************ void CartesianPlotLegendDock::retranslateUi() { m_initializing = true; ui.cbBackgroundType->addItem(i18n("color")); ui.cbBackgroundType->addItem(i18n("image")); ui.cbBackgroundType->addItem(i18n("pattern")); ui.cbBackgroundColorStyle->addItem(i18n("single color")); ui.cbBackgroundColorStyle->addItem(i18n("horizontal gradient")); ui.cbBackgroundColorStyle->addItem(i18n("vertical gradient")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from top left)")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from bottom left)")); ui.cbBackgroundColorStyle->addItem(i18n("radial gradient")); ui.cbBackgroundImageStyle->addItem(i18n("scaled and cropped")); ui.cbBackgroundImageStyle->addItem(i18n("scaled")); ui.cbBackgroundImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbBackgroundImageStyle->addItem(i18n("centered")); ui.cbBackgroundImageStyle->addItem(i18n("tiled")); ui.cbBackgroundImageStyle->addItem(i18n("center tiled")); ui.cbOrder->addItem(i18n("column major")); ui.cbOrder->addItem(i18n("row major")); ui.cbPositionX->addItem(i18n("left")); ui.cbPositionX->addItem(i18n("center")); ui.cbPositionX->addItem(i18n("right")); ui.cbPositionX->addItem(i18n("custom")); ui.cbPositionY->addItem(i18n("top")); ui.cbPositionY->addItem(i18n("center")); ui.cbPositionY->addItem(i18n("bottom")); ui.cbPositionY->addItem(i18n("custom")); GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); m_initializing = false; } // "General"-tab void CartesianPlotLegendDock::nameChanged() { if (m_initializing) return; m_legend->setName(ui.leName->text()); } void CartesianPlotLegendDock::commentChanged() { if (m_initializing) return; m_legend->setComment(ui.leComment->text()); } void CartesianPlotLegendDock::visibilityChanged(bool state) { if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setVisible(state); } //General void CartesianPlotLegendDock::labelFontChanged(const QFont& font) { if (m_initializing) return; QFont labelsFont = font; labelsFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); foreach(CartesianPlotLegend* legend, m_legendList) legend->setLabelFont(labelsFont); } void CartesianPlotLegendDock::labelColorChanged(const QColor& color) { if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setLabelColor(color); } void CartesianPlotLegendDock::labelOrderChanged(const int index) { if (m_initializing) return; bool columnMajor = (index==0); foreach(CartesianPlotLegend* legend, m_legendList) legend->setLabelColumnMajor(columnMajor); } void CartesianPlotLegendDock::lineSymbolWidthChanged(double value) { if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setLineSymbolWidth(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } /*! called when legend's current horizontal position relative to its parent (left, center, right, custom ) is changed. */ void CartesianPlotLegendDock::positionXChanged(int index) { //Enable/disable the spinbox for the x- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionX->count()-1 ) { ui.sbPositionX->setEnabled(true); }else{ ui.sbPositionX->setEnabled(false); } if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.horizontalPosition = CartesianPlotLegend::HorizontalPosition(index); foreach(CartesianPlotLegend* legend, m_legendList) legend->setPosition(position); } /*! called when legend's current horizontal position relative to its parent (top, center, bottom, custom ) is changed. */ void CartesianPlotLegendDock::positionYChanged(int index) { //Enable/disable the spinbox for the y- oordinates if the "custom position"-item is selected/deselected if (index == ui.cbPositionY->count()-1 ) { ui.sbPositionY->setEnabled(true); }else{ ui.sbPositionY->setEnabled(false); } if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.verticalPosition = CartesianPlotLegend::VerticalPosition(index); foreach(CartesianPlotLegend* legend, m_legendList) legend->setPosition(position); } void CartesianPlotLegendDock::customPositionXChanged(double value) { if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.point.setX(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); foreach(CartesianPlotLegend* legend, m_legendList) legend->setPosition(position); } void CartesianPlotLegendDock::customPositionYChanged(double value) { if (m_initializing) return; CartesianPlotLegend::PositionWrapper position = m_legend->position(); position.point.setY(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); foreach(CartesianPlotLegend* legend, m_legendList) legend->setPosition(position); } // "Background"-tab void CartesianPlotLegendDock::backgroundTypeChanged(int index) { PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); }else{ ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } }else if (type == PlotArea::Image) { ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); }else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setBackgroundType(type); } void CartesianPlotLegendDock::backgroundColorStyleChanged(int index) { PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); }else{ ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); } if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setBackgroundColorStyle(style); } void CartesianPlotLegendDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; foreach(CartesianPlotLegend* legend, m_legendList) legend->setBackgroundImageStyle(style); } void CartesianPlotLegendDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; foreach(CartesianPlotLegend* legend, m_legendList) legend->setBackgroundBrushStyle(style); } void CartesianPlotLegendDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; foreach(CartesianPlotLegend* legend, m_legendList) legend->setBackgroundFirstColor(c); } void CartesianPlotLegendDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setBackgroundSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void CartesianPlotLegendDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "CartesianPlotLegendDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; foreach (const QByteArray& format, QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); formats.isEmpty() ? formats+=f : formats+=' '+f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); foreach (CartesianPlotLegend* legend, m_legendList) legend->setBackgroundFileName(path); } void CartesianPlotLegendDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); foreach (CartesianPlotLegend* legend, m_legendList) legend->setBackgroundFileName(fileName); } void CartesianPlotLegendDock::backgroundOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100.; foreach (CartesianPlotLegend* legend, m_legendList) legend->setBackgroundOpacity(opacity); } // "Border"-tab void CartesianPlotLegendDock::borderStyleChanged(int index) { if (m_initializing) return; Qt::PenStyle penStyle=Qt::PenStyle(index); QPen pen; foreach (CartesianPlotLegend* legend, m_legendList) { pen = legend->borderPen(); pen.setStyle(penStyle); legend->setBorderPen(pen); } } void CartesianPlotLegendDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach (CartesianPlotLegend* legend, m_legendList) { pen = legend->borderPen(); pen.setColor(color); legend->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void CartesianPlotLegendDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach (CartesianPlotLegend* legend, m_legendList) { pen = legend->borderPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); legend->setBorderPen(pen); } } void CartesianPlotLegendDock::borderCornerRadiusChanged(double value) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setBorderCornerRadius(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotLegendDock::borderOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100.; foreach (CartesianPlotLegend* legend, m_legendList) legend->setBorderOpacity(opacity); } //Layout void CartesianPlotLegendDock::layoutTopMarginChanged(double margin) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutTopMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutBottomMarginChanged(double margin) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutBottomMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutLeftMarginChanged(double margin) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutLeftMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutRightMarginChanged(double margin) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutRightMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutHorizontalSpacingChanged(double spacing) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutHorizontalSpacing(Worksheet::convertToSceneUnits(spacing, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutVerticalSpacingChanged(double spacing) { if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutVerticalSpacing(Worksheet::convertToSceneUnits(spacing, Worksheet::Centimeter)); } void CartesianPlotLegendDock::layoutColumnCountChanged(int count) { ui.lOrder->setVisible(count!=1); ui.cbOrder->setVisible(count!=1); if (m_initializing) return; foreach (CartesianPlotLegend* legend, m_legendList) legend->setLayoutColumnCount(count); } //************************************************************* //**** SLOTs for changes triggered in CartesianPlotLegend ***** //************************************************************* //General void CartesianPlotLegendDock::legendDescriptionChanged(const AbstractAspect* aspect) { if (m_legend != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void CartesianPlotLegendDock::legendLabelFontChanged(QFont& font) { m_initializing = true; //we need to set the font size in points for KFontRequester QFont f(font); f.setPointSizeF( Worksheet::convertFromSceneUnits(f.pixelSize(), Worksheet::Point) ); ui.kfrLabelFont->setFont(f); m_initializing = false; } void CartesianPlotLegendDock::legendLabelColorChanged(QColor& color) { m_initializing = true; ui.kcbLabelColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendLabelOrderChanged(bool b) { m_initializing = true; if (b) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major m_initializing = false; } void CartesianPlotLegendDock::legendLineSymbolWidthChanged(float value) { m_initializing = true; ui.sbLineSymbolWidth->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendPositionChanged(const CartesianPlotLegend::PositionWrapper& position) { m_initializing = true; ui.sbPositionX->setValue( Worksheet::convertFromSceneUnits(position.point.x(), Worksheet::Centimeter) ); ui.sbPositionY->setValue( Worksheet::convertFromSceneUnits(position.point.y(), Worksheet::Centimeter) ); ui.cbPositionX->setCurrentIndex( position.horizontalPosition ); ui.cbPositionY->setCurrentIndex( position.verticalPosition ); m_initializing = false; } void CartesianPlotLegendDock::legendVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //Background void CartesianPlotLegendDock::legendBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundFileNameChanged(QString& filename) { m_initializing = true; ui.leBackgroundFileName->setText(filename); m_initializing = false; } void CartesianPlotLegendDock::legendBackgroundOpacityChanged(float opacity) { m_initializing = true; - ui.sbBackgroundOpacity->setValue( round(opacity*100.0) ); + ui.sbBackgroundOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } //Border void CartesianPlotLegendDock::legendBorderPenChanged(QPen& pen) { if (m_initializing) return; m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void CartesianPlotLegendDock::legendBorderCornerRadiusChanged(float value) { m_initializing = true; ui.sbBorderCornerRadius->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendBorderOpacityChanged(float opacity) { m_initializing = true; - ui.sbBorderOpacity->setValue( round(opacity*100.0) ); + ui.sbBorderOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } //Layout void CartesianPlotLegendDock::legendLayoutTopMarginChanged(float value) { m_initializing = true; ui.sbLayoutTopMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutBottomMarginChanged(float value) { m_initializing = true; ui.sbLayoutBottomMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutLeftMarginChanged(float value) { m_initializing = true; ui.sbLayoutLeftMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutRightMarginChanged(float value) { m_initializing = true; ui.sbLayoutRightMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutVerticalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutVerticalSpacing->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutHorizontalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutHorizontalSpacing->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotLegendDock::legendLayoutColumnCountChanged(int value) { m_initializing = true; ui.sbLayoutColumnCount->setValue(value); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void CartesianPlotLegendDock::load() { //General-tab ui.chkVisible->setChecked( m_legend->isVisible() ); //we need to set the font size in points for KFontRequester QFont font = m_legend->labelFont(); - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); // qDebug()<<"font size " << font.pixelSize() << " " << font.pointSizeF(); ui.kfrLabelFont->setFont(font); ui.kcbLabelColor->setColor( m_legend->labelColor() ); bool columnMajor = m_legend->labelColumnMajor(); if (columnMajor) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major ui.sbLineSymbolWidth->setValue( Worksheet::convertFromSceneUnits(m_legend->lineSymbolWidth(), Worksheet::Centimeter) ); //Background-tab ui.cbBackgroundType->setCurrentIndex( (int) m_legend->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_legend->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_legend->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_legend->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_legend->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_legend->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_legend->backgroundSecondColor() ); - ui.sbBackgroundOpacity->setValue( round(m_legend->backgroundOpacity()*100.0) ); + ui.sbBackgroundOpacity->setValue( qRound(m_legend->backgroundOpacity()*100.0) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_legend->backgroundFileName().isEmpty() && !QFile::exists(m_legend->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); //Border ui.kcbBorderColor->setColor( m_legend->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_legend->borderPen().style() ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_legend->borderPen().widthF(), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_legend->borderCornerRadius(), Worksheet::Centimeter) ); - ui.sbBorderOpacity->setValue( round(m_legend->borderOpacity()*100.0) ); + ui.sbBorderOpacity->setValue( qRound(m_legend->borderOpacity()*100.0) ); // Layout ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutTopMargin(), Worksheet::Centimeter) ); ui.sbLayoutBottomMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutBottomMargin(), Worksheet::Centimeter) ); ui.sbLayoutLeftMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutLeftMargin(), Worksheet::Centimeter) ); ui.sbLayoutRightMargin->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutRightMargin(), Worksheet::Centimeter) ); ui.sbLayoutHorizontalSpacing->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutHorizontalSpacing(), Worksheet::Centimeter) ); ui.sbLayoutVerticalSpacing->setValue( Worksheet::convertFromSceneUnits(m_legend->layoutVerticalSpacing(), Worksheet::Centimeter) ); ui.sbLayoutColumnCount->setValue( m_legend->layoutColumnCount() ); m_initializing=true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing=false; } void CartesianPlotLegendDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_legendList.size(); if (size>1) m_legend->beginMacro(i18n("%1 cartesian plot legends: template \"%2\" loaded", size, name)); else m_legend->beginMacro(i18n("%1: template \"%2\" loaded", m_legend->name(), name)); this->loadConfig(config); m_legend->endMacro(); } void CartesianPlotLegendDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "CartesianPlotLegend" ); //General-tab ui.chkVisible->setChecked( group.readEntry("Visible", m_legend->isVisible()) ); //we need to set the font size in points for KFontRequester QFont font = m_legend->labelFont(); - font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); + font.setPointSizeF( qRound(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrLabelFont->setFont( group.readEntry("LabelFont", font) ); ui.kcbLabelColor->setColor( group.readEntry("LabelColor", m_legend->labelColor()) ); bool columnMajor = group.readEntry("LabelColumMajor", m_legend->labelColumnMajor()); if (columnMajor) ui.cbOrder->setCurrentIndex(0); //column major else ui.cbOrder->setCurrentIndex(1); //row major ui.sbLineSymbolWidth->setValue(group.readEntry("LineSymbolWidth", Worksheet::convertFromSceneUnits(m_legend->lineSymbolWidth(), Worksheet::Centimeter)) ); //Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_legend->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_legend->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_legend->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_legend->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_legend->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_legend->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_legend->backgroundSecondColor()) ); - ui.sbBackgroundOpacity->setValue( round(group.readEntry("BackgroundOpacity", m_legend->backgroundOpacity())*100.0) ); + ui.sbBackgroundOpacity->setValue( qRound(group.readEntry("BackgroundOpacity", m_legend->backgroundOpacity())*100.0) ); //Border ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_legend->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int) m_legend->borderPen().style()) ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_legend->borderPen().widthF()), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderCornerRadius", m_legend->borderCornerRadius()), Worksheet::Centimeter) ); - ui.sbBorderOpacity->setValue( round(group.readEntry("BorderOpacity", m_legend->borderOpacity())*100.0) ); + ui.sbBorderOpacity->setValue( qRound(group.readEntry("BorderOpacity", m_legend->borderOpacity())*100.0) ); // Layout ui.sbLayoutTopMargin->setValue(group.readEntry("LayoutTopMargin", Worksheet::convertFromSceneUnits(m_legend->layoutTopMargin(), Worksheet::Centimeter)) ); ui.sbLayoutBottomMargin->setValue(group.readEntry("LayoutBottomMargin", Worksheet::convertFromSceneUnits(m_legend->layoutBottomMargin(), Worksheet::Centimeter)) ); ui.sbLayoutLeftMargin->setValue(group.readEntry("LayoutLeftMargin", Worksheet::convertFromSceneUnits(m_legend->layoutLeftMargin(), Worksheet::Centimeter)) ); ui.sbLayoutRightMargin->setValue(group.readEntry("LayoutRightMargin", Worksheet::convertFromSceneUnits(m_legend->layoutRightMargin(), Worksheet::Centimeter)) ); ui.sbLayoutHorizontalSpacing->setValue(group.readEntry("LayoutHorizontalSpacing", Worksheet::convertFromSceneUnits(m_legend->layoutHorizontalSpacing(), Worksheet::Centimeter)) ); ui.sbLayoutVerticalSpacing->setValue(group.readEntry("LayoutVerticalSpacing", Worksheet::convertFromSceneUnits(m_legend->layoutVerticalSpacing(), Worksheet::Centimeter)) ); ui.sbLayoutColumnCount->setValue(group.readEntry("LayoutColumnCount", m_legend->layoutColumnCount())); //Title group = config.group("PlotLegend"); labelWidget->loadConfig(group); m_initializing=true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing=false; } void CartesianPlotLegendDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "CartesianPlotLegend" ); //General-tab group.writeEntry("Visible", ui.chkVisible->isChecked()); QFont font = m_legend->labelFont(); font.setPointSizeF( Worksheet::convertFromSceneUnits(font.pointSizeF(), Worksheet::Point) ); group.writeEntry("LabelFont", font); group.writeEntry("LabelColor", ui.kcbLabelColor->color()); group.writeEntry("LabelColumMajorOrder", ui.cbOrder->currentIndex()==0);// true for "column major", false for "row major" group.writeEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(ui.sbLineSymbolWidth->value(), Worksheet::Centimeter)); //Background group.writeEntry("BackgroundType", ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); //Border group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); group.writeEntry("BorderCornerRadius", Worksheet::convertToSceneUnits(ui.sbBorderCornerRadius->value(), Worksheet::Centimeter)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); //Layout group.writeEntry("LayoutTopMargin",Worksheet::convertToSceneUnits(ui.sbLayoutTopMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutBottomMargin",Worksheet::convertToSceneUnits(ui.sbLayoutBottomMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutLeftMargin",Worksheet::convertToSceneUnits(ui.sbLayoutLeftMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutRightMargin",Worksheet::convertToSceneUnits(ui.sbLayoutRightMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutVerticalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutVerticalSpacing->value(), Worksheet::Centimeter)); group.writeEntry("LayoutHorizontalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutHorizontalSpacing->value(), Worksheet::Centimeter)); group.writeEntry("LayoutColumnCount", ui.sbLayoutColumnCount->value()); //Title group = config.group("PlotLegend"); labelWidget->saveConfig(group); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/CustomPointDock.cpp b/src/kdefrontend/dockwidgets/CustomPointDock.cpp index 1d7533c04..69262ae2d 100644 --- a/src/kdefrontend/dockwidgets/CustomPointDock.cpp +++ b/src/kdefrontend/dockwidgets/CustomPointDock.cpp @@ -1,505 +1,503 @@ /*************************************************************************** File : CustomPointDock.cpp Project : LabPlot Description : widget for Datapicker-Point properties -------------------------------------------------------------------- Copyright : (C) 2015 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CustomPointDock.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include -#include - CustomPointDock::CustomPointDock(QWidget *parent): QWidget(parent) { ui.setupUi(this); //Validators ui.lePositionX->setValidator( new QDoubleValidator(ui.lePositionX) ); ui.lePositionY->setValidator( new QDoubleValidator(ui.lePositionY) ); //adjust layouts in the tabs for (int i=0; icount(); ++i) { QGridLayout* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SLOTS //General connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.lePositionX, SIGNAL(returnPressed()), this, SLOT(positionXChanged()) ); connect( ui.lePositionY, SIGNAL(returnPressed()), this, SLOT(positionYChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); //Symbols connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolBorderWidthChanged(double)) ); //Template handler TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::CustomPoint); ui.verticalLayout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); init(); } void CustomPointDock::init() { m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); QPainter pa; int iconSize = 20; QPixmap pm(iconSize, iconSize); QPen pen(Qt::SolidPattern, 0); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); for (int i=0; i<18; ++i) { Symbol::Style style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen( pen ); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; } void CustomPointDock::setPoints(QList list) { m_initializing=true; m_pointsList = list; m_point = list.first(); Q_ASSERT(m_point); //if there are more then one point in the list, disable the comment and name widgets in the tab "general" if (list.size()==1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_point->name()); ui.leComment->setText(m_point->comment()); }else{ ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); } //show the properties of the first custom point this->load(); //SIGNALs/SLOTs // general connect(m_point, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(pointDescriptionChanged(const AbstractAspect*))); connect(m_point, SIGNAL(positionChanged(QPointF)), this, SLOT(pointPositionChanged(QPointF))); connect(m_point, SIGNAL(visibleChanged(bool)), this, SLOT(pointVisibilityChanged(bool))); //symbol connect(m_point, SIGNAL(symbolStyleChanged(Symbol::Style)), this, SLOT(pointSymbolStyleChanged(Symbol::Style))); connect(m_point, SIGNAL(symbolSizeChanged(qreal)), this, SLOT(pointSymbolSizeChanged(qreal))); connect(m_point, SIGNAL(symbolRotationAngleChanged(qreal)), this, SLOT(pointSymbolRotationAngleChanged(qreal))); connect(m_point, SIGNAL(symbolOpacityChanged(qreal)), this, SLOT(pointSymbolOpacityChanged(qreal))); connect(m_point, SIGNAL(symbolBrushChanged(QBrush)), this, SLOT(pointSymbolBrushChanged(QBrush))); connect(m_point, SIGNAL(symbolPenChanged(QPen)), this, SLOT(pointSymbolPenChanged(QPen))); } //********************************************************** //**** SLOTs for changes triggered in CustomPointDock ****** //********************************************************** //"General"-tab void CustomPointDock::nameChanged() { if (m_initializing) return; m_point->setName(ui.leName->text()); } void CustomPointDock::commentChanged() { if (m_initializing) return; m_point->setComment(ui.leComment->text()); } void CustomPointDock::positionXChanged() { if (m_initializing) return; QPointF pos = m_point->position(); float x = ui.lePositionX->text().toFloat(); pos.setX(x); foreach(CustomPoint* point, m_pointsList) point->setPosition(pos); } void CustomPointDock::positionYChanged() { if (m_initializing) return; QPointF pos = m_point->position(); float y = ui.lePositionY->text().toFloat(); pos.setY(y); foreach(CustomPoint* point, m_pointsList) point->setPosition(pos); } void CustomPointDock::visibilityChanged(bool state) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: visibility changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) point->setVisible(state); m_point->endMacro(); } void CustomPointDock::symbolStyleChanged(int index) { Symbol::Style style = Symbol::Style(index); //enable/disable the filling options in the GUI depending on the currently selected points. if (style!=Symbol::Line && style!=Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex())==Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex())== Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: style changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) point->setSymbolStyle(style); m_point->endMacro(); } void CustomPointDock::symbolSizeChanged(double value) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: size changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) point->setSymbolSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); m_point->endMacro(); } void CustomPointDock::symbolRotationChanged(int value) { if (m_initializing) return; m_point->beginMacro(i18n("%1 CustomPoints: rotation changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) point->setSymbolRotationAngle(value); m_point->endMacro(); } void CustomPointDock::symbolOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; m_point->beginMacro(i18n("%1 CustomPoints: opacity changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) point->setSymbolOpacity(opacity); m_point->endMacro(); } void CustomPointDock::symbolFillingStyleChanged(int index) { Qt::BrushStyle brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle==Qt::NoBrush)); if (m_initializing) return; QBrush brush; m_point->beginMacro(i18n("%1 CustomPoints: filling style changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) { brush = point->symbolBrush(); brush.setStyle(brushStyle); point->setSymbolBrush(brush); } m_point->endMacro(); } void CustomPointDock::symbolFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; m_point->beginMacro(i18n("%1 CustomPoints: filling color changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) { brush = point->symbolBrush(); brush.setColor(color); point->setSymbolBrush(brush); } m_point->endMacro(); m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void CustomPointDock::symbolBorderStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border style changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) { pen = point->symbolPen(); pen.setStyle(penStyle); point->setSymbolPen(pen); } m_point->endMacro(); } void CustomPointDock::symbolBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border color changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) { pen = point->symbolPen(); pen.setColor(color); point->setSymbolPen(pen); } m_point->endMacro(); m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void CustomPointDock::symbolBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; m_point->beginMacro(i18n("%1 CustomPoints: border width changed", m_pointsList.count())); foreach(CustomPoint* point, m_pointsList) { pen = point->symbolPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); point->setSymbolPen(pen); } m_point->endMacro(); } //********************************************************* //**** SLOTs for changes triggered in CustomPoint ********* //********************************************************* //"General"-tab void CustomPointDock::pointDescriptionChanged(const AbstractAspect* aspect) { if (m_point != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) { ui.leName->setText(aspect->name()); } else if (aspect->comment() != ui.leComment->text()) { ui.leComment->setText(aspect->comment()); } m_initializing = false; } void CustomPointDock::pointPositionChanged(const QPointF& position) { m_initializing = true; ui.lePositionX->setText(QString::number(position.x())); ui.lePositionY->setText(QString::number(position.y())); m_initializing = false; } //"Symbol"-tab void CustomPointDock::pointSymbolStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void CustomPointDock::pointSymbolSizeChanged(qreal size) { m_initializing = true; ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void CustomPointDock::pointSymbolRotationAngleChanged(qreal angle) { m_initializing = true; - ui.sbSymbolRotation->setValue(round(angle)); + ui.sbSymbolRotation->setValue(qRound(angle)); m_initializing = false; } void CustomPointDock::pointSymbolOpacityChanged(qreal opacity) { m_initializing = true; - ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); + ui.sbSymbolOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } void CustomPointDock::pointSymbolBrushChanged(QBrush brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void CustomPointDock::pointSymbolPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); m_initializing = false; } void CustomPointDock::pointVisibilityChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //********************************************************** //******************** SETTINGS **************************** //********************************************************** void CustomPointDock::load() { if (m_point == NULL) return; m_initializing = true; ui.lePositionX->setText(QString::number(m_point->position().x())); ui.lePositionY->setText(QString::number(m_point->position().y())); ui.cbSymbolStyle->setCurrentIndex( (int)m_point->symbolStyle() ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_point->symbolSize(), Worksheet::Point) ); ui.sbSymbolRotation->setValue( m_point->symbolRotationAngle() ); - ui.sbSymbolOpacity->setValue( round(m_point->symbolOpacity()*100.0) ); + ui.sbSymbolOpacity->setValue( qRound(m_point->symbolOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_point->symbolBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_point->symbolBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_point->symbolPen().style() ); ui.kcbSymbolBorderColor->setColor( m_point->symbolPen().color() ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_point->symbolPen().widthF(), Worksheet::Point) ); ui.chkVisible->setChecked( m_point->isVisible() ); m_initializing = false; } void CustomPointDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_pointsList.size(); if (size>1) m_point->beginMacro(i18n("%1 custom points: template \"%2\" loaded", size, name)); else m_point->beginMacro(i18n("%1: template \"%2\" loaded", m_point->name(), name)); this->loadConfig(config); m_point->endMacro(); } void CustomPointDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "CustomPoint" ); ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_point->symbolStyle()) ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_point->symbolSize()), Worksheet::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_point->symbolRotationAngle()) ); - ui.sbSymbolOpacity->setValue( round(group.readEntry("SymbolOpacity", m_point->symbolOpacity())*100.0) ); + ui.sbSymbolOpacity->setValue( qRound(group.readEntry("SymbolOpacity", m_point->symbolOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_point->symbolBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_point->symbolBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_point->symbolPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_point->symbolPen().color()) ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_point->symbolPen().widthF()), Worksheet::Point) ); m_initializing=true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); m_initializing=false; } void CustomPointDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "CustomPoint" ); group.writeEntry("SymbolStyle", ui.cbSymbolStyle->currentText()); group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Point)); group.writeEntry("SymbolRotation", ui.sbSymbolRotation->value()); group.writeEntry("SymbolOpacity", ui.sbSymbolOpacity->value()/100 ); group.writeEntry("SymbolFillingStyle", ui.cbSymbolFillingStyle->currentIndex()); group.writeEntry("SymbolFillingColor", ui.kcbSymbolFillingColor->color()); group.writeEntry("SymbolBorderStyle", ui.cbSymbolBorderStyle->currentIndex()); group.writeEntry("SymbolBorderColor", ui.kcbSymbolBorderColor->color()); group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(),Worksheet::Point)); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/HistogramDock.cpp b/src/kdefrontend/dockwidgets/HistogramDock.cpp index b53474eae..e868ba64a 100644 --- a/src/kdefrontend/dockwidgets/HistogramDock.cpp +++ b/src/kdefrontend/dockwidgets/HistogramDock.cpp @@ -1,1081 +1,1089 @@ /*************************************************************************** File : HistogramDock.cpp Project : LabPlot Description : widget for Histogram properties -------------------------------------------------------------------- Copyright : (C) 2016 Anu Mittal (anu22mittal@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "HistogramDock.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/Worksheet.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include +#include #include #include #include #include /*! \class HistogramDock \brief Provides a widget for editing the properties of the Histograms (2D-curves) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ HistogramDock::HistogramDock(QWidget* parent) : QWidget(parent), cbXColumn(nullptr), m_curve(nullptr), m_aspectTreeModel(nullptr), m_initializing(false) { ui.setupUi(this); //Tab "Values" QGridLayout* gridLayout = qobject_cast(ui.tabValues->layout()); cbValuesColumn = new TreeViewComboBox(ui.tabValues); gridLayout->addWidget(cbValuesColumn, 2, 2, 1, 1); //Tab "Filling" ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFillingFileName->setCompleter(completer); //adjust layouts in the tabs for (int i=0; icount(); ++i){ QGridLayout* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Slots //Values connect( ui.cbValuesType, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesTypeChanged(int)) ); connect( cbValuesColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(valuesColumnChanged(QModelIndex)) ); connect( ui.cbValuesPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesPositionChanged(int)) ); connect( ui.sbValuesDistance, SIGNAL(valueChanged(double)), this, SLOT(valuesDistanceChanged(double)) ); connect( ui.sbValuesRotation, SIGNAL(valueChanged(int)), this, SLOT(valuesRotationChanged(int)) ); connect( ui.sbValuesOpacity, SIGNAL(valueChanged(int)), this, SLOT(valuesOpacityChanged(int)) ); //TODO connect( ui.cbValuesFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesColumnFormatChanged(int)) ); connect( ui.leValuesPrefix, SIGNAL(returnPressed()), this, SLOT(valuesPrefixChanged()) ); connect( ui.leValuesSuffix, SIGNAL(returnPressed()), this, SLOT(valuesSuffixChanged()) ); connect( ui.kfrValuesFont, SIGNAL(fontSelected(QFont)), this, SLOT(valuesFontChanged(QFont)) ); connect( ui.kcbValuesColor, SIGNAL(changed(QColor)), this, SLOT(valuesColorChanged(QColor)) ); //Filling connect( ui.cbFillingPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingPositionChanged(int)) ); connect( ui.cbFillingType, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingTypeChanged(int)) ); connect( ui.cbFillingColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorStyleChanged(int)) ); connect( ui.cbFillingImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingImageStyleChanged(int)) ); connect( ui.cbFillingBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingBrushStyleChanged(int)) ); connect( ui.bFillingOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leFillingFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leFillingFileName, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameChanged()) ); connect( ui.kcbFillingFirstColor, SIGNAL(changed(QColor)), this, SLOT(fillingFirstColorChanged(QColor)) ); connect( ui.kcbFillingSecondColor, SIGNAL(changed(QColor)), this, SLOT(fillingSecondColorChanged(QColor)) ); connect( ui.sbFillingOpacity, SIGNAL(valueChanged(int)), this, SLOT(fillingOpacityChanged(int)) ); //template handler TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::Histogram); ui.verticalLayout->addWidget(templateHandler); templateHandler->show(); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); retranslateUi(); init(); } HistogramDock::~HistogramDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } //TODO: very similiar to ColumnDock void HistogramDock::updateValuesFormatWidgets(const AbstractColumn::ColumnMode columnMode) { ui.cbValuesFormat->clear(); switch (columnMode) { case AbstractColumn::Numeric: ui.cbValuesFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesFormat->addItem(i18n("Automatic (E)"), QVariant('G')); break; case AbstractColumn::Integer: break; case AbstractColumn::Text: ui.cbValuesFormat->addItem(i18n("Text"), QVariant()); break; case AbstractColumn::Month: ui.cbValuesFormat->addItem(i18n("Number without leading zero"), QVariant("M")); ui.cbValuesFormat->addItem(i18n("Number with leading zero"), QVariant("MM")); ui.cbValuesFormat->addItem(i18n("Abbreviated month name"), QVariant("MMM")); ui.cbValuesFormat->addItem(i18n("Full month name"), QVariant("MMMM")); break; case AbstractColumn::Day: ui.cbValuesFormat->addItem(i18n("Number without leading zero"), QVariant("d")); ui.cbValuesFormat->addItem(i18n("Number with leading zero"), QVariant("dd")); ui.cbValuesFormat->addItem(i18n("Abbreviated day name"), QVariant("ddd")); ui.cbValuesFormat->addItem(i18n("Full day name"), QVariant("dddd")); break; case AbstractColumn::DateTime: for (const auto& s: AbstractColumn::dateFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s: AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s1: AbstractColumn::dateFormats()) for (const auto& s2: AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); break; } ui.cbValuesFormat->setCurrentIndex(0); if (columnMode == AbstractColumn::Numeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } else { ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); } if (columnMode == AbstractColumn::Text) { ui.lValuesFormatTop->hide(); ui.lValuesFormat->hide(); ui.cbValuesFormat->hide(); } else { ui.lValuesFormatTop->show(); ui.lValuesFormat->show(); ui.cbValuesFormat->show(); ui.cbValuesFormat->setCurrentIndex(0); } if (columnMode == AbstractColumn::DateTime) { ui.cbValuesFormat->setEditable(true); } else { ui.cbValuesFormat->setEditable(false); } } //TODO: very similiar to ColumnDock void HistogramDock::showValuesColumnFormat(const Column* column){ if (!column){ // no valid column is available // -> hide all the format properties widgets (equivalent to showing the properties of the column mode "Text") this->updateValuesFormatWidgets(AbstractColumn::Text); }else{ AbstractColumn::ColumnMode columnMode = column->columnMode(); //update the format widgets for the new column mode this->updateValuesFormatWidgets(columnMode); //show the actuall formating properties switch(columnMode) { case AbstractColumn::Numeric:{ Double2StringFilter * filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->numericFormat())); ui.sbValuesPrecision->setValue(filter->numDigits()); break; } case AbstractColumn::Text: case AbstractColumn::Integer: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { DateTime2StringFilter * filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->format())); break; } } } } void HistogramDock::setModelIndexFromColumn(TreeViewComboBox* cb, const AbstractColumn* column) { if (column) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else cb->setCurrentModelIndex(QModelIndex()); } void HistogramDock::retranslateUi() { //TODO: // uiGeneralTab.lName->setText(i18n("Name")); // uiGeneralTab.lComment->setText(i18n("Comment")); // uiGeneralTab.chkVisible->setText(i18n("Visible")); // uiGeneralTab.lXColumn->setText(i18n("x-data")); // uiGeneralTab.lYColumn->setText(i18n("y-data")); //TODO updatePenStyles, updateBrushStyles for all comboboxes } // "General"-tab void HistogramDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void HistogramDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void HistogramDock::visibilityChanged(bool state){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setVisible(state); } void HistogramDock::valuesColorChanged(const QColor& color){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setValuesColor(color); } void HistogramDock::init(){ //Values ui.cbValuesType->addItem(i18n("no values")); ui.cbValuesType->addItem("y"); ui.cbValuesType->addItem(i18n("custom column")); ui.cbValuesPosition->addItem(i18n("above")); ui.cbValuesPosition->addItem(i18n("below")); ui.cbValuesPosition->addItem(i18n("left")); ui.cbValuesPosition->addItem(i18n("right")); //Filling ui.cbFillingPosition->clear(); ui.cbFillingPosition->addItem(i18n("none")); ui.cbFillingPosition->addItem(i18n("above")); ui.cbFillingPosition->addItem(i18n("below")); ui.cbFillingPosition->addItem(i18n("zero baseline")); ui.cbFillingPosition->addItem(i18n("left")); ui.cbFillingPosition->addItem(i18n("right")); ui.cbFillingType->clear(); ui.cbFillingType->addItem(i18n("color")); ui.cbFillingType->addItem(i18n("image")); ui.cbFillingType->addItem(i18n("pattern")); ui.cbFillingColorStyle->clear(); ui.cbFillingColorStyle->addItem(i18n("single color")); ui.cbFillingColorStyle->addItem(i18n("horizontal linear gradient")); ui.cbFillingColorStyle->addItem(i18n("vertical linear gradient")); ui.cbFillingColorStyle->addItem(i18n("diagonal linear gradient (start from top left)")); ui.cbFillingColorStyle->addItem(i18n("diagonal linear gradient (start from bottom left)")); ui.cbFillingColorStyle->addItem(i18n("radial gradient")); ui.cbFillingImageStyle->clear(); ui.cbFillingImageStyle->addItem(i18n("scaled and cropped")); ui.cbFillingImageStyle->addItem(i18n("scaled")); ui.cbFillingImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbFillingImageStyle->addItem(i18n("centered")); ui.cbFillingImageStyle->addItem(i18n("tiled")); ui.cbFillingImageStyle->addItem(i18n("center tiled")); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, Qt::SolidPattern); } void HistogramDock::setModel() { QList list; list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<< "Histogram" <<"XYInterpolationCurve"<<"XYFitCurve"<<"XYFourierFilterCurve"; if (cbXColumn) { cbXColumn->setTopLevelClasses(list); } cbValuesColumn->setTopLevelClasses(list); list.clear(); list<<"Column"; m_aspectTreeModel->setSelectableAspects(list); if (cbXColumn) cbXColumn->setModel(m_aspectTreeModel); cbValuesColumn->setModel(m_aspectTreeModel); } void HistogramDock::setCurves(QList list){ m_initializing=true; m_curvesList=list; m_curve=list.first(); Q_ASSERT(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); setModel(); initGeneralTab(); initTabs(); m_initializing=false; } void HistogramDock::initGeneralTab(){ //if there are more then one curve in the list, disable the content in the tab "general" if (m_curvesList.size()==1){ uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.lXColumn->setEnabled(true); cbXColumn->setEnabled(true); this->setModelIndexFromColumn(cbXColumn, m_curve->xColumn()); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); }else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.lXColumn->setEnabled(false); cbXColumn->setEnabled(false); cbXColumn->setCurrentModelIndex(QModelIndex()); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve const Histogram::HistogramData& data = m_curve->histogramData(); uiGeneralTab.cbHistogramType->setCurrentIndex(data.type); uiGeneralTab.cbBins->setCurrentIndex(data.binsOption); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); connect(m_curve, SIGNAL(linePenChanged(QPen)), this, SLOT(curveLinePenChanged(QPen))); connect(m_curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged(bool))); uiGeneralTab.pbRecalculate->setEnabled(m_curve->isSourceDataChangedSinceLastPlot()); //Slots connect(m_curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_curve, SIGNAL(histogramDataChanged(Histogram::HistogramData)), this, SLOT(curveHistogramDataChanged(Histogram::HistogramData))); } //************************************************************* //**** SLOTs for changes triggered in HistogramDock ***** //************************************************************* void HistogramDock::recalculateClicked() { Histogram::HistogramData data; if( data.type != (Histogram::HistogramType)uiGeneralTab.cbHistogramType->currentIndex()) data.type = (Histogram::HistogramType)uiGeneralTab.cbHistogramType->currentIndex(); data.binsOption= (Histogram::BinsOption)uiGeneralTab.cbBins->currentIndex(); data.binValue = uiGeneralTab.sbBins->value(); // m_curve->retransform(); foreach(Histogram* curve, m_curvesList) dynamic_cast(curve)->setHistogramData(data); uiGeneralTab.pbRecalculate->setEnabled(false); } void HistogramDock::enableRecalculate() const { if (m_initializing) return; uiGeneralTab.pbRecalculate->setEnabled(true); } void HistogramDock::curveLinePenChanged(const QPen& pen) { m_initializing = true; uiGeneralTab.kcbLineColor->setColor( pen.color()); m_initializing = false; } //Values-Tab void HistogramDock::curveValuesTypeChanged(Histogram::ValuesType type) { m_initializing = true; ui.cbValuesType->setCurrentIndex((int) type); m_initializing = false; } void HistogramDock::curveValuesColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromColumn(cbValuesColumn, column); m_initializing = false; } void HistogramDock::curveValuesPositionChanged(Histogram::ValuesPosition position) { m_initializing = true; ui.cbValuesPosition->setCurrentIndex((int) position); m_initializing = false; } void HistogramDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Point) ); m_initializing = false; } void HistogramDock::curveValuesRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbValuesRotation->setValue(angle); m_initializing = false; } void HistogramDock::curveValuesOpacityChanged(qreal opacity) { m_initializing = true; ui.sbValuesOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void HistogramDock::curveValuesPrefixChanged(QString prefix) { m_initializing = true; ui.leValuesPrefix->setText(prefix); m_initializing = false; } void HistogramDock::curveValuesSuffixChanged(QString suffix) { m_initializing = true; ui.leValuesSuffix->setText(suffix); m_initializing = false; } void HistogramDock::curveValuesFontChanged(QFont font) { m_initializing = true; font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont(font); m_initializing = false; } void HistogramDock::curveValuesColorChanged(QColor color) { m_initializing = true; ui.kcbValuesColor->setColor(color); m_initializing = false; } /*! called when the type of the values (none, x, y, (x,y) etc.) was changed. */ void HistogramDock::valuesTypeChanged(int index) { Histogram::ValuesType valuesType = Histogram::ValuesType(index); if (valuesType == Histogram::NoValues){ //no values are to paint -> deactivate all the pertinent widgets ui.cbValuesPosition->setEnabled(false); ui.lValuesColumn->hide(); cbValuesColumn->hide(); ui.sbValuesDistance->setEnabled(false); ui.sbValuesRotation->setEnabled(false); ui.sbValuesOpacity->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.sbValuesPrecision->setEnabled(false); ui.leValuesPrefix->setEnabled(false); ui.leValuesSuffix->setEnabled(false); ui.kfrValuesFont->setEnabled(false); ui.kcbValuesColor->setEnabled(false); } else { ui.cbValuesPosition->setEnabled(true); ui.sbValuesDistance->setEnabled(true); ui.sbValuesRotation->setEnabled(true); ui.sbValuesOpacity->setEnabled(true); ui.cbValuesFormat->setEnabled(true); ui.sbValuesPrecision->setEnabled(true); ui.leValuesPrefix->setEnabled(true); ui.leValuesSuffix->setEnabled(true); ui.kfrValuesFont->setEnabled(true); ui.kcbValuesColor->setEnabled(true); const Column* column; if (valuesType == Histogram::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); column= static_cast(cbValuesColumn->currentModelIndex().internalPointer()); } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); column = static_cast(m_curve->xColumn()); } this->showValuesColumnFormat(column); } if (m_initializing) return; for (auto* curve: m_curvesList) curve->setValuesType(valuesType); } /*! called when the custom column for the values was changed. */ void HistogramDock::valuesColumnChanged(const QModelIndex& index){ if (m_initializing) return; Column* column = static_cast(index.internalPointer()); this->showValuesColumnFormat(column); for (auto* curve: m_curvesList) { //TODO save also the format of the currently selected column for the values (precision etc.) curve->setValuesColumn(column); } } void HistogramDock::curveVisibilityChanged(bool on) { m_initializing = true; uiGeneralTab.chkVisible->setChecked(on); m_initializing = false; } void HistogramDock::valuesPositionChanged(int index){ if (m_initializing) return; foreach(Histogram* curve, m_curvesList) curve->setValuesPosition(Histogram::ValuesPosition(index)); } void HistogramDock::valuesDistanceChanged(double value){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void HistogramDock::valuesRotationChanged(int value){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setValuesRotationAngle(value); } void HistogramDock::valuesOpacityChanged(int value){ if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve: m_curvesList) curve->setValuesOpacity(opacity); } void HistogramDock::valuesPrefixChanged(){ if (m_initializing) return; QString prefix = ui.leValuesPrefix->text(); for (auto* curve: m_curvesList) curve->setValuesPrefix(prefix); } void HistogramDock::valuesSuffixChanged(){ if (m_initializing) return; QString suffix = ui.leValuesSuffix->text(); for (auto* curve: m_curvesList) curve->setValuesSuffix(suffix); } void HistogramDock::valuesFontChanged(const QFont& font){ if (m_initializing) return; QFont valuesFont = font; valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); for (auto* curve: m_curvesList) curve->setValuesFont(valuesFont); } //Filling void HistogramDock::curveFillingPositionChanged(Histogram::FillingPosition position) { m_initializing = true; ui.cbFillingPosition->setCurrentIndex((int)position); m_initializing = false; } void HistogramDock::curveFillingTypeChanged(PlotArea::BackgroundType type){ m_initializing = true; ui.cbFillingType->setCurrentIndex(type); m_initializing = false; } void HistogramDock::curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle style){ m_initializing = true; ui.cbFillingColorStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle style){ m_initializing = true; ui.cbFillingImageStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingBrushStyleChanged(Qt::BrushStyle style){ m_initializing = true; ui.cbFillingBrushStyle->setCurrentIndex(style); m_initializing = false; } void HistogramDock::curveFillingFirstColorChanged(QColor& color){ m_initializing = true; ui.kcbFillingFirstColor->setColor(color); m_initializing = false; } void HistogramDock::curveFillingSecondColorChanged(QColor& color){ m_initializing = true; ui.kcbFillingSecondColor->setColor(color); m_initializing = false; } void HistogramDock::curveFillingFileNameChanged(QString& filename){ m_initializing = true; ui.leFillingFileName->setText(filename); m_initializing = false; } void HistogramDock::curveFillingOpacityChanged(float opacity){ m_initializing = true; ui.sbFillingOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void HistogramDock::initTabs() { //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size()==1){ this->setModelIndexFromColumn(cbValuesColumn, m_curve->valuesColumn()); }else { cbValuesColumn->setCurrentModelIndex(QModelIndex()); } //show the properties of the first curve KConfig config("", KConfig::SimpleConfig); loadConfig(config); //Slots //Values-Tab connect(m_curve, SIGNAL(valuesTypeChanged(Histogram::ValuesType)), this, SLOT(curveValuesTypeChanged(Histogram::ValuesType))); connect(m_curve, SIGNAL(valuesColumnChanged(const AbstractColumn*)), this, SLOT(curveValuesColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(valuesPositionChanged(Histogram::ValuesPosition)), this, SLOT(curveValuesPositionChanged(Histogram::ValuesPosition))); connect(m_curve, SIGNAL(valuesDistanceChanged(qreal)), this, SLOT(curveValuesDistanceChanged(qreal))); connect(m_curve, SIGNAL(valuesOpacityChanged(qreal)), this, SLOT(curveValuesOpacityChanged(qreal))); connect(m_curve, SIGNAL(valuesRotationAngleChanged(qreal)), this, SLOT(curveValuesRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(valuesPrefixChanged(QString)), this, SLOT(curveValuesPrefixChanged(QString))); connect(m_curve, SIGNAL(valuesSuffixChanged(QString)), this, SLOT(curveValuesSuffixChanged(QString))); connect(m_curve, SIGNAL(valuesFontChanged(QFont)), this, SLOT(curveValuesFontChanged(QFont))); connect(m_curve, SIGNAL(valuesColorChanged(QColor)), this, SLOT(curveValuesColorChanged(QColor))); //Filling-Tab connect( m_curve, SIGNAL(fillingPositionChanged(Histogram::FillingPosition)), this, SLOT(curveFillingPositionChanged(Histogram::FillingPosition)) ); connect( m_curve, SIGNAL(fillingTypeChanged(PlotArea::BackgroundType)), this, SLOT(curveFillingTypeChanged(PlotArea::BackgroundType)) ); connect( m_curve, SIGNAL(fillingColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_curve, SIGNAL(fillingImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_curve, SIGNAL(fillingBrushStyleChanged(Qt::BrushStyle)), this, SLOT(curveFillingBrushStyleChanged(Qt::BrushStyle)) ); connect( m_curve, SIGNAL(fillingFirstColorChanged(QColor&)), this, SLOT(curveFillingFirstColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingSecondColorChanged(QColor&)), this, SLOT(curveFillingSecondColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingFileNameChanged(QString&)), this, SLOT(curveFillingFileNameChanged(QString&)) ); connect( m_curve, SIGNAL(fillingOpacityChanged(float)), this, SLOT(curveFillingOpacityChanged(float)) ); } //Filling-tab void HistogramDock::fillingPositionChanged(int index){ Histogram::FillingPosition fillingPosition = Histogram::FillingPosition(index); bool b = (fillingPosition != Histogram::NoFilling); ui.cbFillingType->setEnabled(b); ui.cbFillingColorStyle->setEnabled(b); ui.cbFillingBrushStyle->setEnabled(b); ui.cbFillingImageStyle->setEnabled(b); ui.kcbFillingFirstColor->setEnabled(b); ui.kcbFillingSecondColor->setEnabled(b); ui.leFillingFileName->setEnabled(b); ui.bFillingOpen->setEnabled(b); ui.sbFillingOpacity->setEnabled(b); if (m_initializing) return; for (auto* curve: m_curvesList) curve->setFillingPosition(fillingPosition); } void HistogramDock::fillingTypeChanged(int index){ PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color){ ui.lFillingColorStyle->show(); ui.cbFillingColorStyle->show(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbFillingColorStyle->currentIndex(); if (style == PlotArea::SingleColor){ ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); }else{ ui.lFillingFirstColor->setText(i18n("First Color")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); } }else if(type == PlotArea::Image){ ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->show(); ui.cbFillingImageStyle->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->show(); ui.leFillingFileName->show(); ui.bFillingOpen->show(); ui.lFillingFirstColor->hide(); ui.kcbFillingFirstColor->hide(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); }else if(type == PlotArea::Pattern) { ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->show(); ui.cbFillingBrushStyle->show(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } if (m_initializing) return; for (auto* curve: m_curvesList) curve->setFillingType(type); } void HistogramDock::fillingColorStyleChanged(int index){ PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor){ ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First Color")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); } if (m_initializing) return; for (auto* curve: m_curvesList) curve->setFillingColorStyle(style); } void HistogramDock::fillingImageStyleChanged(int index){ if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; for (auto* curve: m_curvesList) curve->setFillingImageStyle(style); } void HistogramDock::fillingBrushStyleChanged(int index){ if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; for (auto* curve: m_curvesList) curve->setFillingBrushStyle(style); } void HistogramDock::fillingFirstColorChanged(const QColor& c){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setFillingFirstColor(c); } void HistogramDock::fillingSecondColorChanged(const QColor& c){ if (m_initializing) return; for (auto* curve: m_curvesList) curve->setFillingSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void HistogramDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "HistogramDock"); QString dir = conf.readEntry("LastImageDir", ""); - QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir); + + QString formats; + foreach(const QByteArray& format, QImageReader::supportedImageFormats()) { + QString f = "*." + QString(format.constData()); + formats.isEmpty() ? formats+=f : formats+=' '+f; + } + + QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir!=dir) conf.writeEntry("LastImageDir", newDir); } ui.leFillingFileName->setText( path ); for (auto* curve: m_curvesList) curve->setFillingFileName(path); } void HistogramDock::fileNameChanged(){ if (m_initializing) return; QString fileName = ui.leFillingFileName->text(); for (auto* curve: m_curvesList) curve->setFillingFileName(fileName); } void HistogramDock::fillingOpacityChanged(int value){ if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve: m_curvesList) curve->setFillingOpacity(opacity); } void HistogramDock::loadConfig(KConfig& config) { KConfigGroup group = config.group(QLatin1String("Histogram")); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in HistogramDock::setCurves(). //Values ui.cbValuesType->setCurrentIndex( group.readEntry("ValuesType", (int) m_curve->valuesType()) ); ui.cbValuesPosition->setCurrentIndex( group.readEntry("ValuesPosition", (int) m_curve->valuesPosition()) ); ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Point) ); ui.sbValuesRotation->setValue( group.readEntry("ValuesRotation", m_curve->valuesRotationAngle()) ); ui.sbValuesOpacity->setValue( round(group.readEntry("ValuesOpacity",m_curve->valuesOpacity())*100.0) ); ui.leValuesPrefix->setText( group.readEntry("ValuesPrefix", m_curve->valuesPrefix()) ); ui.leValuesSuffix->setText( group.readEntry("ValuesSuffix", m_curve->valuesSuffix()) ); QFont valuesFont = m_curve->valuesFont(); valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont( group.readEntry("ValuesFont", valuesFont) ); ui.kcbValuesColor->setColor( group.readEntry("ValuesColor", m_curve->valuesColor()) ); //Filling ui.cbFillingPosition->setCurrentIndex( group.readEntry("FillingPosition", (int) m_curve->fillingPosition()) ); ui.cbFillingType->setCurrentIndex( group.readEntry("FillingType", (int) m_curve->fillingType()) ); ui.cbFillingColorStyle->setCurrentIndex( group.readEntry("FillingColorStyle", (int) m_curve->fillingColorStyle()) ); ui.cbFillingImageStyle->setCurrentIndex( group.readEntry("FillingImageStyle", (int) m_curve->fillingImageStyle()) ); ui.cbFillingBrushStyle->setCurrentIndex( group.readEntry("FillingBrushStyle", (int) m_curve->fillingBrushStyle()) ); ui.leFillingFileName->setText( group.readEntry("FillingFileName", m_curve->fillingFileName()) ); ui.kcbFillingFirstColor->setColor( group.readEntry("FillingFirstColor", m_curve->fillingFirstColor()) ); ui.kcbFillingSecondColor->setColor( group.readEntry("FillingSecondColor", m_curve->fillingSecondColor()) ); ui.sbFillingOpacity->setValue( round(group.readEntry("FillingOpacity", m_curve->fillingOpacity())*100.0) ); } void HistogramDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); // Tab "General" QGridLayout* gridLayout = qobject_cast(generalTab->layout()); cbXColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXColumn, 2, 2, 1, 1); //show the properties of the first curve //bins option uiGeneralTab.cbBins->addItem(i18n("By Number")); uiGeneralTab.cbBins->addItem(i18n("By width")); uiGeneralTab.cbBins->addItem(i18n("Square-root rule")); uiGeneralTab.cbBins->addItem(i18n("Rice rule")); uiGeneralTab.cbBins->addItem(i18n("Sturgis rule")); //types options uiGeneralTab.cbHistogramType->addItem(i18n("Ordinary Histogram")); uiGeneralTab.cbHistogramType->addItem(i18n("Cumulative Histogram")); uiGeneralTab.cbHistogramType->addItem(i18n("AvgShifted Histogram")); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); //General connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( uiGeneralTab.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( cbXColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xColumnChanged(QModelIndex)) ); connect( uiGeneralTab.cbHistogramType, SIGNAL(currentIndexChanged(int)), this, SLOT(histogramTypeChanged(int)) ); connect( uiGeneralTab.cbBins, SIGNAL(currentIndexChanged(int)), this, SLOT(binsOptionChanged(int)) ); connect( uiGeneralTab.sbBins, SIGNAL(valueChanged(int)), this, SLOT(binValueChanged(int)) ); connect( uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked()) ); } void HistogramDock::histogramTypeChanged(int index) { Histogram::HistogramType histogramType = Histogram::HistogramType(index); m_curve->setHistrogramType(histogramType); enableRecalculate(); } void HistogramDock::binValueChanged(int value) { m_curve->setBinValue(value); enableRecalculate(); } void HistogramDock::binsOptionChanged(int index){ Histogram::BinsOption binsOption = Histogram::BinsOption(index); m_curve->setbinsOption(binsOption); enableRecalculate(); } void HistogramDock::lineColorChanged(const QColor& color){ if (m_initializing) return; QPen pen; for (auto* curve: m_curvesList) { pen = curve->linePen(); pen.setColor(color); curve->setLinePen(pen); } } void HistogramDock::xColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(Histogram* curve, m_curvesList) { curve->setXColumn(column); } } void HistogramDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_curvesList.size(); if (size>1) m_curve->beginMacro(i18n("%1 xy-curves: template \"%2\" loaded", size, name)); else m_curve->beginMacro(i18n("%1: template \"%2\" loaded", m_curve->name(), name)); this->loadConfig(config); m_curve->endMacro(); } void HistogramDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Histogram" ); //Values group.writeEntry("ValuesType", ui.cbValuesType->currentIndex()); group.writeEntry("ValuesPosition", ui.cbValuesPosition->currentIndex()); group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(),Worksheet::Point)); group.writeEntry("ValuesRotation", ui.sbValuesRotation->value()); group.writeEntry("ValuesOpacity", ui.sbValuesOpacity->value()/100); group.writeEntry("ValuesPrefix", ui.leValuesPrefix->text()); group.writeEntry("ValuesSuffix", ui.leValuesSuffix->text()); group.writeEntry("ValuesFont", ui.kfrValuesFont->font()); group.writeEntry("ValuesColor", ui.kcbValuesColor->color()); //Filling group.writeEntry("FillingPosition", ui.cbFillingPosition->currentIndex()); group.writeEntry("FillingType", ui.cbFillingType->currentIndex()); group.writeEntry("FillingColorStyle", ui.cbFillingColorStyle->currentIndex()); group.writeEntry("FillingImageStyle", ui.cbFillingImageStyle->currentIndex()); group.writeEntry("FillingBrushStyle", ui.cbFillingBrushStyle->currentIndex()); group.writeEntry("FillingFileName", ui.leFillingFileName->text()); group.writeEntry("FillingFirstColor", ui.kcbFillingFirstColor->color()); group.writeEntry("FillingSecondColor", ui.kcbFillingSecondColor->color()); group.writeEntry("FillingOpacity", ui.sbFillingOpacity->value()/100.0); config.sync(); } //************************************************************* //*********** SLOTs for changes triggered in Histogram ********** //************************************************************* //General-Tab void HistogramDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) { uiGeneralTab.leName->setText(aspect->name()); } else if (aspect->comment() != uiGeneralTab.leComment->text()) { uiGeneralTab.leComment->setText(aspect->comment()); } m_initializing = false; } void HistogramDock::curveHistogramDataChanged(const Histogram::HistogramData& data) { m_initializing = true; uiGeneralTab.cbHistogramType->setCurrentIndex(data.type); uiGeneralTab.cbBins->setCurrentIndex(data.binsOption); uiGeneralTab.sbBins->setValue(data.binValue); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/NoteDock.cpp b/src/kdefrontend/dockwidgets/NoteDock.cpp index 7328daa8b..119a42ca9 100644 --- a/src/kdefrontend/dockwidgets/NoteDock.cpp +++ b/src/kdefrontend/dockwidgets/NoteDock.cpp @@ -1,129 +1,129 @@ /*************************************************************************** File : NotesDock.cpp Project : LabPlot Description : Dock for configuring notes -------------------------------------------------------------------- Copyright : (C) 2016 Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "NoteDock.h" #include "kdefrontend/TemplateHandler.h" #include #include #include NoteDock::NoteDock(QWidget *parent) : QWidget(parent), m_initializing(false), m_notes(0) { ui.setupUi(this); - connect(ui.leName, &QLineEdit::returnPressed, this, [this]() { nameChanged(); }); - connect(ui.leComment, &QLineEdit::returnPressed, this, &NoteDock::commentChanged); - + connect(ui.leName, &QLineEdit::returnPressed, this, [this]() { nameChanged(ui.leName->text()); }); + connect(ui.leComment, &QLineEdit::returnPressed, this, [this]() { commentChanged(ui.leComment->text()); }); + connect(ui.kcbBgColor, &KColorButton::changed, this, &NoteDock::backgroundColorChanged); connect(ui.kcbTextColor, &KColorButton::changed, this, &NoteDock::textColorChanged); connect(ui.kfrTextFont, &KFontRequester::fontSelected, this, &NoteDock::textFontChanged); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::Worksheet); ui.gridLayout->addWidget(templateHandler, 8, 3); templateHandler->show(); connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &NoteDock::loadConfigFromTemplate); connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &NoteDock::saveConfigAsTemplate); } void NoteDock::setNotesList(QList< Note* > list) { m_notesList = list; m_notes = list.first(); m_initializing=true; ui.leName->setText(m_notes->name()); ui.kcbBgColor->setColor(m_notes->backgroundColor()); ui.kcbTextColor->setColor(m_notes->textColor()); ui.kfrTextFont->setFont(m_notes->textFont()); m_initializing=false; } //************************************************************* //********** SLOTs for changes triggered in NoteDock ********** //************************************************************* void NoteDock::nameChanged(const QString& name) { if (m_initializing) return; m_notes->setName(name); } void NoteDock::commentChanged(const QString& name) { if (m_initializing) return; m_notes->setComment(name); } void NoteDock::backgroundColorChanged(const QColor& color) { if (m_initializing) return; foreach(Note* note, m_notesList) note->setBackgroundColor(color); } void NoteDock::textColorChanged(const QColor& color) { if (m_initializing) return; foreach(Note* note, m_notesList) note->setTextColor(color); } void NoteDock::textFontChanged(const QFont& font) { if (m_initializing) return; foreach(Note* note, m_notesList) note->setTextFont(font); } //************************************************************* //************************* Settings ************************** //************************************************************* void NoteDock::loadConfigFromTemplate(KConfig& config) { QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); KConfigGroup group = config.group("Notes"); ui.kcbBgColor->setColor(group.readEntry("BackgroundColor", m_notes->backgroundColor())); ui.kcbTextColor->setColor(group.readEntry("TextColor", m_notes->textColor())); ui.kfrTextFont->setFont(group.readEntry("TextColor", m_notes->textFont())); } void NoteDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group("Notes"); group.writeEntry("BackgroundColor", ui.kcbBgColor->color()); group.writeEntry("TextColor", ui.kcbTextColor->color()); group.writeEntry("TextFont", ui.kfrTextFont->font()); } diff --git a/src/kdefrontend/dockwidgets/WorksheetDock.cpp b/src/kdefrontend/dockwidgets/WorksheetDock.cpp index c3bf18221..a74ebb7e3 100644 --- a/src/kdefrontend/dockwidgets/WorksheetDock.cpp +++ b/src/kdefrontend/dockwidgets/WorksheetDock.cpp @@ -1,957 +1,955 @@ /*************************************************************************** File : WorksheetDock.cpp Project : LabPlot Description : widget for worksheet properties -------------------------------------------------------------------- Copyright : (C) 2010-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "WorksheetDock.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include #include -#include - // a couple of standard sizes in mm, taken from qprinter.cpp const int numOfPaperSizes = 30; const float qt_paperSizes[numOfPaperSizes][2] = { {210, 297}, // A4 {176, 250}, // B5 {215.9f, 279.4f}, // Letter {215.9f, 355.6f}, // Legal {190.5f, 254}, // Executive {841, 1189}, // A0 {594, 841}, // A1 {420, 594}, // A2 {297, 420}, // A3 {148, 210}, // A5 {105, 148}, // A6 {74, 105}, // A7 {52, 74}, // A8 {37, 52}, // A8 {1000, 1414}, // B0 {707, 1000}, // B1 {31, 44}, // B10 {500, 707}, // B2 {353, 500}, // B3 {250, 353}, // B4 {125, 176}, // B6 {88, 125}, // B7 {62, 88}, // B8 {33, 62}, // B9 {163, 229}, // C5E {105, 241}, // US Common {110, 220}, // DLE {210, 330}, // Folio {431.8f, 279.4f}, // Ledger {279.4f, 431.8f} // Tabloid }; /*! \class WorksheetDock \brief Provides a widget for editing the properties of the worksheets currently selected in the project explorer. \ingroup kdefrontend */ WorksheetDock::WorksheetDock(QWidget *parent): QWidget(parent), m_worksheet(0) { ui.setupUi(this); //Background-tab ui.cbBackgroundColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leBackgroundFileName->setCompleter(completer); //adjust layouts in the tabs for (int i=0; icount(); ++i) { QGridLayout* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //SLOTs //General connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.cbSize, SIGNAL(currentIndexChanged(int)), this, SLOT(sizeChanged(int)) ); connect( ui.sbWidth, SIGNAL(valueChanged(double)), this, SLOT(sizeChanged()) ); connect( ui.sbHeight, SIGNAL(valueChanged(double)), this, SLOT(sizeChanged()) ); connect( ui.cbOrientation, SIGNAL(currentIndexChanged(int)), this, SLOT(orientationChanged(int)) ); //Background connect( ui.cbBackgroundType, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundTypeChanged(int)) ); connect( ui.cbBackgroundColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundColorStyleChanged(int)) ); connect( ui.cbBackgroundImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundImageStyleChanged(int)) ); connect( ui.cbBackgroundBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundBrushStyleChanged(int)) ); connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leBackgroundFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leBackgroundFileName, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameChanged()) ); connect( ui.kcbBackgroundFirstColor, SIGNAL(changed(QColor)), this, SLOT(backgroundFirstColorChanged(QColor)) ); connect( ui.kcbBackgroundSecondColor, SIGNAL(changed(QColor)), this, SLOT(backgroundSecondColorChanged(QColor)) ); connect( ui.sbBackgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(backgroundOpacityChanged(int)) ); //Layout connect( ui.chScaleContent, SIGNAL(clicked(bool)), this, SLOT(scaleContentChanged(bool)) ); connect( ui.sbLayoutTopMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutTopMarginChanged(double)) ); connect( ui.sbLayoutBottomMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutBottomMarginChanged(double)) ); connect( ui.sbLayoutLeftMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutLeftMarginChanged(double)) ); connect( ui.sbLayoutRightMargin, SIGNAL(valueChanged(double)), this, SLOT(layoutRightMarginChanged(double)) ); connect( ui.sbLayoutHorizontalSpacing, SIGNAL(valueChanged(double)), this, SLOT(layoutHorizontalSpacingChanged(double)) ); connect( ui.sbLayoutVerticalSpacing, SIGNAL(valueChanged(double)), this, SLOT(layoutVerticalSpacingChanged(double)) ); connect( ui.sbLayoutRowCount, SIGNAL(valueChanged(int)), this, SLOT(layoutRowCountChanged(int)) ); connect( ui.sbLayoutColumnCount, SIGNAL(valueChanged(int)), this, SLOT(layoutColumnCountChanged(int)) ); //theme and template handlers QFrame* frame = new QFrame(this); QHBoxLayout* layout = new QHBoxLayout(frame); m_themeHandler = new ThemeHandler(this); layout->addWidget(m_themeHandler); connect(m_themeHandler, SIGNAL(loadThemeRequested(QString)), this, SLOT(loadTheme(QString))); connect(m_themeHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::Worksheet); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); this->retranslateUi(); } void WorksheetDock::setWorksheets(QList list) { m_initializing = true; m_worksheetList = list; m_worksheet = list.first(); //if there are more then one worksheet in the list, disable the name and comment field in the tab "general" if (list.size()==1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_worksheet->name()); ui.leComment->setText(m_worksheet->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); } //show the properties of the first worksheet this->load(); this->worksheetLayoutChanged(m_worksheet->layout()); m_themeHandler->setCurrentTheme(m_worksheet->theme()); connect(m_worksheet, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(worksheetDescriptionChanged(const AbstractAspect*))); connect(m_worksheet, SIGNAL(pageRectChanged(QRectF)),this, SLOT(worksheetPageRectChanged(QRectF))); connect(m_worksheet,SIGNAL(scaleContentChanged(bool)),this,SLOT(worksheetScaleContentChanged(bool))); connect(m_worksheet,SIGNAL(backgroundTypeChanged(PlotArea::BackgroundType)),this,SLOT(worksheetBackgroundTypeChanged(PlotArea::BackgroundType))); connect(m_worksheet,SIGNAL(backgroundColorStyleChanged(PlotArea::BackgroundColorStyle)),this,SLOT(worksheetBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle))); connect(m_worksheet,SIGNAL(backgroundImageStyleChanged(PlotArea::BackgroundImageStyle)),this,SLOT(worksheetBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle))); connect(m_worksheet,SIGNAL(backgroundBrushStyleChanged(Qt::BrushStyle)),this,SLOT(worksheetBackgroundBrushStyleChanged(Qt::BrushStyle))); connect(m_worksheet,SIGNAL(backgroundFirstColorChanged(QColor)),this,SLOT(worksheetBackgroundFirstColorChanged(QColor))); connect(m_worksheet,SIGNAL(backgroundSecondColorChanged(QColor)),this,SLOT(worksheetBackgroundSecondColorChanged(QColor))); connect(m_worksheet,SIGNAL(backgroundFileNameChanged(QString)),this,SLOT(worksheetBackgroundFileNameChanged(QString))); connect(m_worksheet,SIGNAL(backgroundOpacityChanged(float)),this,SLOT(worksheetBackgroundOpacityChanged(float))); connect(m_worksheet,SIGNAL(layoutChanged(Worksheet::Layout)),this,SLOT(worksheetLayoutChanged(Worksheet::Layout))); connect(m_worksheet,SIGNAL(layoutTopMarginChanged(float)),this,SLOT(worksheetLayoutTopMarginChanged(float))); connect(m_worksheet,SIGNAL(layoutBottomMarginChanged(float)),this,SLOT(worksheetLayoutBottomMarginChanged(float))); connect(m_worksheet,SIGNAL(layoutLeftMarginChanged(float)),this,SLOT(worksheetLayoutLeftMarginChanged(float))); connect(m_worksheet,SIGNAL(layoutRightMarginChanged(float)),this,SLOT(worksheetLayoutRightMarginChanged(float))); connect(m_worksheet,SIGNAL(layoutVerticalSpacingChanged(float)),this,SLOT(worksheetLayoutVerticalSpacingChanged(float))); connect(m_worksheet,SIGNAL(layoutHorizontalSpacingChanged(float)),this,SLOT(worksheetLayoutHorizontalSpacingChanged(float))); connect(m_worksheet,SIGNAL(layoutRowCountChanged(int)),this,SLOT(worksheetLayoutRowCountChanged(int))); connect(m_worksheet,SIGNAL(layoutColumnCountChanged(int)),this,SLOT(worksheetLayoutColumnCountChanged(int))); connect(m_worksheet,SIGNAL(themeChanged(QString)),m_themeHandler,SLOT(setCurrentTheme(QString))); m_initializing = false; } /*! Checks whether the size is one of the QPrinter::PaperSize and updates Size and Orientation checkbox when width/height changes. */ void WorksheetDock::updatePaperSize() { if (m_worksheet->useViewSize()) { ui.cbSize->setCurrentIndex(0); return; } int i=0; //In UI we use cm, so we need to convert to mm first before we check with qt_paperSizes float w=(float)ui.sbWidth->value()*10; float h=(float)ui.sbHeight->value()*10; //check the portrait-orientation first while ( isetCurrentIndex(0); //a QPrinter::PaperSize in portrait-orientation was found } else { //check for the landscape-orientation i=0; while ( isetCurrentIndex(1); //a QPrinter::PaperSize in landscape-orientation was found } //determine the position of the QPrinter::PaperSize in the combobox for (int index = 0; index < numOfPaperSizes+1; ++index) { if (ui.cbSize->itemData(index+2).toInt() == i) { ui.cbSize->setCurrentIndex(index+2); break; } } } //************************************************************* //****** SLOTs for changes triggered in WorksheetDock ********* //************************************************************* void WorksheetDock::retranslateUi() { m_initializing = true; //Geometry ui.cbOrientation->clear(); ui.cbOrientation->addItem(i18n("portrait")); ui.cbOrientation->addItem(i18n("landscape")); ui.cbSize->clear(); ui.cbSize->addItem(i18n("view size")); ui.cbSize->addItem(QString("A0"), QPrinter::A0); ui.cbSize->addItem(QString("A1"), QPrinter::A1); ui.cbSize->addItem(QString("A2"), QPrinter::A2); ui.cbSize->addItem(QString("A3"), QPrinter::A3); ui.cbSize->addItem(QString("A4"), QPrinter::A4); ui.cbSize->addItem(QString("A5"), QPrinter::A5); ui.cbSize->addItem(QString("A6"), QPrinter::A6); ui.cbSize->addItem(QString("A7"), QPrinter::A7); ui.cbSize->addItem(QString("A8"), QPrinter::A8); ui.cbSize->addItem(QString("A9"), QPrinter::A9); ui.cbSize->addItem(QString("B0"), QPrinter::B0); ui.cbSize->addItem(QString("B1"), QPrinter::B1); ui.cbSize->addItem(QString("B2"), QPrinter::B2); ui.cbSize->addItem(QString("B3"), QPrinter::B3); ui.cbSize->addItem(QString("B4"), QPrinter::B4); ui.cbSize->addItem(QString("B5"), QPrinter::B5); ui.cbSize->addItem(QString("B6"), QPrinter::B6); ui.cbSize->addItem(QString("B7"), QPrinter::B7); ui.cbSize->addItem(QString("B8"), QPrinter::B8); ui.cbSize->addItem(QString("B9"), QPrinter::B9); ui.cbSize->addItem(QString("B10"), QPrinter::B10); ui.cbSize->addItem(QString("C5E"), QPrinter::C5E); ui.cbSize->addItem(QString("DLE"), QPrinter::DLE); ui.cbSize->addItem(i18n("Executive"), QPrinter::Executive); ui.cbSize->addItem(i18n("Folio"), QPrinter::Folio); ui.cbSize->addItem(i18n("Ledger"), QPrinter::Ledger); ui.cbSize->addItem(i18n("Legal"), QPrinter::Legal); ui.cbSize->addItem(i18n("Letter"), QPrinter::Letter); ui.cbSize->addItem(i18n("Tabloid"), QPrinter::Tabloid); ui.cbSize->addItem(i18n("US Common #10 Envelope"), QPrinter::Comm10E); ui.cbSize->addItem(i18n("Custom"), QPrinter::Custom); ui.cbSize->insertSeparator(1); //Background ui.cbBackgroundType->clear(); ui.cbBackgroundType->addItem(i18n("color")); ui.cbBackgroundType->addItem(i18n("image")); ui.cbBackgroundType->addItem(i18n("pattern")); ui.cbBackgroundColorStyle->clear(); ui.cbBackgroundColorStyle->addItem(i18n("single color")); ui.cbBackgroundColorStyle->addItem(i18n("horizontal gradient")); ui.cbBackgroundColorStyle->addItem(i18n("vertical gradient")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from top left)")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from bottom left)")); ui.cbBackgroundColorStyle->addItem(i18n("radial gradient")); ui.cbBackgroundImageStyle->clear(); ui.cbBackgroundImageStyle->addItem(i18n("scaled and cropped")); ui.cbBackgroundImageStyle->addItem(i18n("scaled")); ui.cbBackgroundImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbBackgroundImageStyle->addItem(i18n("centered")); ui.cbBackgroundImageStyle->addItem(i18n("tiled")); ui.cbBackgroundImageStyle->addItem(i18n("center tiled")); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); m_initializing = false; } // "General"-tab void WorksheetDock::nameChanged() { if (m_initializing) return; m_worksheet->setName(ui.leName->text()); } void WorksheetDock::commentChanged() { if (m_initializing) return; m_worksheet->setComment(ui.leComment->text()); } void WorksheetDock::scaleContentChanged(bool scaled) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setScaleContent(scaled); } void WorksheetDock::sizeChanged(int i) { int index = ui.cbSize->itemData(i).toInt(); if (index==QPrinter::Custom) { ui.sbWidth->setEnabled(true); ui.sbHeight->setEnabled(true); ui.lOrientation->hide(); ui.cbOrientation->hide(); } else { ui.sbWidth->setEnabled(false); ui.sbHeight->setEnabled(false); if (i==0) { //no orientation available when using the complete view size (first item in the combox is selected) ui.lOrientation->hide(); ui.cbOrientation->hide(); } else { ui.lOrientation->show(); ui.cbOrientation->show(); } } if (m_initializing) return; if (i==0) { //use the complete view size (first item in the combox is selected) foreach(Worksheet* worksheet, m_worksheetList) worksheet->setUseViewSize(true); } else if (index==QPrinter::Custom) { if (m_worksheet->useViewSize()) { foreach(Worksheet* worksheet, m_worksheetList) worksheet->setUseViewSize(false); } } else { //determine the width and the height of the to be used predefined layout float w, h; if (ui.cbOrientation->currentIndex() == 0) { w=qt_paperSizes[index][0]; h=qt_paperSizes[index][1]; } else { w=qt_paperSizes[index][1]; h=qt_paperSizes[index][0]; } m_initializing = true; //w and h from qt_paperSizes above are in mm, in UI we show everything in cm ui.sbWidth->setValue(w/10); ui.sbHeight->setValue(h/10); m_initializing=false; w = Worksheet::convertToSceneUnits(w, Worksheet::Millimeter); h = Worksheet::convertToSceneUnits(h, Worksheet::Millimeter); foreach(Worksheet* worksheet, m_worksheetList) { worksheet->setUseViewSize(false); worksheet->setPageRect(QRect(0,0,w,h)); } } } void WorksheetDock::sizeChanged() { if (m_initializing) return; int w = Worksheet::convertToSceneUnits(ui.sbWidth->value(), Worksheet::Centimeter); int h = Worksheet::convertToSceneUnits(ui.sbHeight->value(), Worksheet::Centimeter); foreach(Worksheet* worksheet, m_worksheetList) worksheet->setPageRect(QRect(0,0,w,h)); } void WorksheetDock::orientationChanged(int index) { Q_UNUSED(index); if (m_initializing) return; this->sizeChanged(ui.cbSize->currentIndex()); } // "Background"-tab void WorksheetDock::backgroundTypeChanged(int index) { PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.leBackgroundFileName->show(); ui.bOpen->show(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.leBackgroundFileName->hide(); ui.bOpen->hide(); } if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundType(type); } void WorksheetDock::backgroundColorStyleChanged(int index) { PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } if (m_initializing) return; int size = m_worksheetList.size(); if (size>1) { m_worksheet->beginMacro(i18n("%1 worksheets: background color style changed", size)); foreach(Worksheet* w, m_worksheetList) w->setBackgroundColorStyle(style); m_worksheet->endMacro(); } else m_worksheet->setBackgroundColorStyle(style); } void WorksheetDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundImageStyle(style); } void WorksheetDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundBrushStyle(style); } void WorksheetDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundFirstColor(c); } void WorksheetDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundSecondColor(c); } void WorksheetDock::backgroundOpacityChanged(int value) { if (m_initializing) return; float opacity = (float)value/100; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundOpacity(opacity); } //"Layout"-tab void WorksheetDock::layoutTopMarginChanged(double margin) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutTopMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void WorksheetDock::layoutBottomMarginChanged(double margin) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutBottomMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void WorksheetDock::layoutLeftMarginChanged(double margin) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutLeftMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void WorksheetDock::layoutRightMarginChanged(double margin) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutRightMargin(Worksheet::convertToSceneUnits(margin, Worksheet::Centimeter)); } void WorksheetDock::layoutHorizontalSpacingChanged(double spacing) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutHorizontalSpacing(Worksheet::convertToSceneUnits(spacing, Worksheet::Centimeter)); } void WorksheetDock::layoutVerticalSpacingChanged(double spacing) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutVerticalSpacing(Worksheet::convertToSceneUnits(spacing, Worksheet::Centimeter)); } void WorksheetDock::layoutRowCountChanged(int count) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutRowCount(count); } void WorksheetDock::layoutColumnCountChanged(int count) { if (m_initializing) return; foreach(Worksheet* worksheet, m_worksheetList) worksheet->setLayoutColumnCount(count); } /*! opens a file dialog and lets the user select the image file. */ void WorksheetDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "WorksheetDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; foreach(const QByteArray& format, QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); formats.isEmpty() ? formats+=f : formats+=' '+f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos!=-1) { QString newDir = path.left(pos); if (newDir!=dir) conf.writeEntry("LastImageDir", newDir); } ui.leBackgroundFileName->setText( path ); foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundFileName(path); } void WorksheetDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); foreach(Worksheet* worksheet, m_worksheetList) worksheet->setBackgroundFileName(fileName); } //************************************************************* //******** SLOTs for changes triggered in Worksheet *********** //************************************************************* void WorksheetDock::worksheetDescriptionChanged(const AbstractAspect* aspect) { if (m_worksheet != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void WorksheetDock::worksheetScaleContentChanged(bool scaled) { m_initializing = true; ui.chScaleContent->setChecked(scaled); m_initializing = false; } void WorksheetDock::worksheetPageRectChanged(const QRectF& rect) { m_initializing = true; ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(rect.width(), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(rect.height(), Worksheet::Centimeter)); updatePaperSize(); m_initializing = false; } void WorksheetDock::worksheetBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void WorksheetDock::worksheetBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void WorksheetDock::worksheetBackgroundFirstColorChanged(const QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void WorksheetDock::worksheetBackgroundSecondColorChanged(const QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void WorksheetDock::worksheetBackgroundFileNameChanged(const QString& name) { m_initializing = true; ui.leBackgroundFileName->setText(name); m_initializing = false; } void WorksheetDock::worksheetBackgroundOpacityChanged(float opacity) { m_initializing = true; - ui.sbBackgroundOpacity->setValue( round(opacity*100.0) ); + ui.sbBackgroundOpacity->setValue( qRound(opacity*100.0) ); m_initializing = false; } void WorksheetDock::worksheetLayoutChanged(Worksheet::Layout layout) { bool b = (layout != Worksheet::NoLayout); ui.sbLayoutTopMargin->setEnabled(b); ui.sbLayoutBottomMargin->setEnabled(b); ui.sbLayoutLeftMargin->setEnabled(b); ui.sbLayoutRightMargin->setEnabled(b); ui.sbLayoutHorizontalSpacing->setEnabled(b); ui.sbLayoutVerticalSpacing->setEnabled(b); ui.sbLayoutRowCount->setEnabled(b); ui.sbLayoutColumnCount->setEnabled(b); if (b) { bool grid = (layout == Worksheet::GridLayout); ui.lGrid->setVisible(grid); ui.lRowCount->setVisible(grid); ui.sbLayoutRowCount->setVisible(grid); ui.lColumnCount->setVisible(grid); ui.sbLayoutColumnCount->setVisible(grid); } else { ui.lGrid->setVisible(true); ui.lRowCount->setVisible(true); ui.sbLayoutRowCount->setVisible(true); ui.lColumnCount->setVisible(true); ui.sbLayoutColumnCount->setVisible(true); } } void WorksheetDock::worksheetLayoutTopMarginChanged(float value) { m_initializing = true; ui.sbLayoutTopMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutBottomMarginChanged(float value) { m_initializing = true; ui.sbLayoutBottomMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutLeftMarginChanged(float value) { m_initializing = true; ui.sbLayoutLeftMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutRightMarginChanged(float value) { m_initializing = true; ui.sbLayoutRightMargin->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutVerticalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutVerticalSpacing->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutHorizontalSpacingChanged(float value) { m_initializing = true; ui.sbLayoutHorizontalSpacing->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void WorksheetDock::worksheetLayoutRowCountChanged(int value) { m_initializing = true; ui.sbLayoutRowCount->setValue(value); m_initializing = false; } void WorksheetDock::worksheetLayoutColumnCountChanged(int value) { m_initializing = true; ui.sbLayoutColumnCount->setValue(value); m_initializing = false; } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void WorksheetDock::load() { // Geometry ui.chScaleContent->setChecked(m_worksheet->scaleContent()); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits( m_worksheet->pageRect().width(), Worksheet::Centimeter) ); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits( m_worksheet->pageRect().height(), Worksheet::Centimeter) ); updatePaperSize(); // Background-tab ui.cbBackgroundType->setCurrentIndex( (int) m_worksheet->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_worksheet->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_worksheet->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_worksheet->backgroundBrushStyle() ); ui.leBackgroundFileName->setText( m_worksheet->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_worksheet->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_worksheet->backgroundSecondColor() ); - ui.sbBackgroundOpacity->setValue( round(m_worksheet->backgroundOpacity()*100) ); + ui.sbBackgroundOpacity->setValue( qRound(m_worksheet->backgroundOpacity()*100) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_worksheet->backgroundFileName().isEmpty() && !QFile::exists(m_worksheet->backgroundFileName())) ui.leBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.leBackgroundFileName->setStyleSheet(""); // Layout ui.sbLayoutTopMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutTopMargin(), Worksheet::Centimeter) ); ui.sbLayoutBottomMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutBottomMargin(), Worksheet::Centimeter) ); ui.sbLayoutLeftMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutLeftMargin(), Worksheet::Centimeter) ); ui.sbLayoutRightMargin->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutRightMargin(), Worksheet::Centimeter) ); ui.sbLayoutHorizontalSpacing->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutHorizontalSpacing(), Worksheet::Centimeter) ); ui.sbLayoutVerticalSpacing->setValue( Worksheet::convertFromSceneUnits(m_worksheet->layoutVerticalSpacing(), Worksheet::Centimeter) ); ui.sbLayoutRowCount->setValue( m_worksheet->layoutRowCount() ); ui.sbLayoutColumnCount->setValue( m_worksheet->layoutColumnCount() ); } void WorksheetDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_worksheetList.size(); if (size>1) m_worksheet->beginMacro(i18n("%1 worksheets: template \"%2\" loaded", size, name)); else m_worksheet->beginMacro(i18n("%1: template \"%2\" loaded", m_worksheet->name(), name)); this->loadConfig(config); m_worksheet->endMacro(); } void WorksheetDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "Worksheet" ); // Geometry ui.chScaleContent->setChecked(group.readEntry("ScaleContent", false)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(group.readEntry("Width", m_worksheet->pageRect().width()), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(group.readEntry("Height", m_worksheet->pageRect().height()), Worksheet::Centimeter)); updatePaperSize(); // Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_worksheet->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_worksheet->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_worksheet->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_worksheet->backgroundBrushStyle()) ); ui.leBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_worksheet->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_worksheet->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_worksheet->backgroundSecondColor()) ); - ui.sbBackgroundOpacity->setValue( round(group.readEntry("BackgroundOpacity", m_worksheet->backgroundOpacity())*100) ); + ui.sbBackgroundOpacity->setValue( qRound(group.readEntry("BackgroundOpacity", m_worksheet->backgroundOpacity())*100) ); // Layout ui.sbLayoutTopMargin->setValue(group.readEntry("LayoutTopMargin", Worksheet::convertFromSceneUnits(m_worksheet->layoutTopMargin(), Worksheet::Centimeter)) ); ui.sbLayoutBottomMargin->setValue(group.readEntry("LayoutBottomMargin", Worksheet::convertFromSceneUnits(m_worksheet->layoutBottomMargin(), Worksheet::Centimeter)) ); ui.sbLayoutLeftMargin->setValue(group.readEntry("LayoutLeftMargin", Worksheet::convertFromSceneUnits(m_worksheet->layoutLeftMargin(), Worksheet::Centimeter)) ); ui.sbLayoutRightMargin->setValue(group.readEntry("LayoutRightMargin", Worksheet::convertFromSceneUnits(m_worksheet->layoutRightMargin(), Worksheet::Centimeter)) ); ui.sbLayoutHorizontalSpacing->setValue(group.readEntry("LayoutHorizontalSpacing", Worksheet::convertFromSceneUnits(m_worksheet->layoutHorizontalSpacing(), Worksheet::Centimeter)) ); ui.sbLayoutVerticalSpacing->setValue(group.readEntry("LayoutVerticalSpacing", Worksheet::convertFromSceneUnits(m_worksheet->layoutVerticalSpacing(), Worksheet::Centimeter)) ); ui.sbLayoutRowCount->setValue(group.readEntry("LayoutRowCount", m_worksheet->layoutRowCount())); ui.sbLayoutColumnCount->setValue(group.readEntry("LayoutColumnCount", m_worksheet->layoutColumnCount())); } void WorksheetDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "Worksheet" ); //General group.writeEntry("ScaleContent",ui.chScaleContent->isChecked()); group.writeEntry("UseViewSize",ui.cbSize->currentIndex()==0); group.writeEntry("Width",Worksheet::convertToSceneUnits(ui.sbWidth->value(), Worksheet::Centimeter)); group.writeEntry("Height",Worksheet::convertToSceneUnits(ui.sbHeight->value(), Worksheet::Centimeter)); //Background group.writeEntry("BackgroundType",ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.leBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); //Layout group.writeEntry("LayoutTopMargin",Worksheet::convertToSceneUnits(ui.sbLayoutTopMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutBottomMargin",Worksheet::convertToSceneUnits(ui.sbLayoutBottomMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutLeftMargin",Worksheet::convertToSceneUnits(ui.sbLayoutLeftMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutRightMargin",Worksheet::convertToSceneUnits(ui.sbLayoutRightMargin->value(), Worksheet::Centimeter)); group.writeEntry("LayoutVerticalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutVerticalSpacing->value(), Worksheet::Centimeter)); group.writeEntry("LayoutHorizontalSpacing",Worksheet::convertToSceneUnits(ui.sbLayoutHorizontalSpacing->value(), Worksheet::Centimeter)); group.writeEntry("LayoutRowCount", ui.sbLayoutRowCount->value()); group.writeEntry("LayoutColumnCount", ui.sbLayoutColumnCount->value()); config.sync(); } void WorksheetDock::loadTheme(const QString& theme) { foreach(Worksheet* worksheet, m_worksheetList) worksheet->setTheme(theme); } diff --git a/src/kdefrontend/dockwidgets/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp index b310b9ce1..84579dc21 100644 --- a/src/kdefrontend/dockwidgets/XYCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCurveDock.cpp @@ -1,2226 +1,2234 @@ /*************************************************************************** File : XYCurveDock.cpp Project : LabPlot Description : widget for XYCurve properties -------------------------------------------------------------------- Copyright : (C) 2010-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2017 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYCurveDock.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include +#include #include #include #include #include /*! \class XYCurveDock \brief Provides a widget for editing the properties of the XYCurves (2D-curves) currently selected in the project explorer. If more than one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYCurveDock::XYCurveDock(QWidget* parent) : QWidget(parent), cbXColumn(nullptr), cbYColumn(nullptr), m_curve(nullptr), m_aspectTreeModel(nullptr) { ui.setupUi(this); //Tab "Values" QGridLayout* gridLayout = qobject_cast(ui.tabValues->layout()); cbValuesColumn = new TreeViewComboBox(ui.tabValues); gridLayout->addWidget(cbValuesColumn, 2, 2, 1, 1); //Tab "Filling" ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFillingFileName->setCompleter(completer); //Tab "Error bars" gridLayout = qobject_cast(ui.tabErrorBars->layout()); cbXErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorPlusColumn, 2, 2, 1, 1); cbXErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorMinusColumn, 3, 2, 1, 1); cbYErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorPlusColumn, 7, 2, 1, 1); cbYErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorMinusColumn, 8, 2, 1, 1); //adjust layouts in the tabs for (int i=0; icount(); ++i) { QGridLayout* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Slots //Lines connect( ui.cbLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(lineTypeChanged(int)) ); connect( ui.sbLineInterpolationPointsCount, SIGNAL(valueChanged(int)), this, SLOT(lineInterpolationPointsCountChanged(int)) ); connect( ui.chkLineSkipGaps, SIGNAL(clicked(bool)), this, SLOT(lineSkipGapsChanged(bool)) ); connect( ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(lineStyleChanged(int)) ); connect( ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(lineWidthChanged(double)) ); connect( ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(lineOpacityChanged(int)) ); connect( ui.cbDropLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineTypeChanged(int)) ); connect( ui.cbDropLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineStyleChanged(int)) ); connect( ui.kcbDropLineColor, SIGNAL(changed(QColor)), this, SLOT(dropLineColorChanged(QColor)) ); connect( ui.sbDropLineWidth, SIGNAL(valueChanged(double)), this, SLOT(dropLineWidthChanged(double)) ); connect( ui.sbDropLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(dropLineOpacityChanged(int)) ); //Symbol connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolsSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolsRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolsOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolsFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolsBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolsBorderWidthChanged(double)) ); //Values connect( ui.cbValuesType, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesTypeChanged(int)) ); connect( cbValuesColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(valuesColumnChanged(QModelIndex)) ); connect( ui.cbValuesPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesPositionChanged(int)) ); connect( ui.sbValuesDistance, SIGNAL(valueChanged(double)), this, SLOT(valuesDistanceChanged(double)) ); connect( ui.sbValuesRotation, SIGNAL(valueChanged(int)), this, SLOT(valuesRotationChanged(int)) ); connect( ui.sbValuesOpacity, SIGNAL(valueChanged(int)), this, SLOT(valuesOpacityChanged(int)) ); //TODO connect( ui.cbValuesFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesColumnFormatChanged(int)) ); connect( ui.leValuesPrefix, SIGNAL(returnPressed()), this, SLOT(valuesPrefixChanged()) ); connect( ui.leValuesSuffix, SIGNAL(returnPressed()), this, SLOT(valuesSuffixChanged()) ); connect( ui.kfrValuesFont, SIGNAL(fontSelected(QFont)), this, SLOT(valuesFontChanged(QFont)) ); connect( ui.kcbValuesColor, SIGNAL(changed(QColor)), this, SLOT(valuesColorChanged(QColor)) ); //Filling connect( ui.cbFillingPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingPositionChanged(int)) ); connect( ui.cbFillingType, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingTypeChanged(int)) ); connect( ui.cbFillingColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorStyleChanged(int)) ); connect( ui.cbFillingImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingImageStyleChanged(int)) ); connect( ui.cbFillingBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingBrushStyleChanged(int)) ); connect(ui.bFillingOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leFillingFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leFillingFileName, SIGNAL(textChanged(const QString&)), this, SLOT(fileNameChanged()) ); connect( ui.kcbFillingFirstColor, SIGNAL(changed(QColor)), this, SLOT(fillingFirstColorChanged(QColor)) ); connect( ui.kcbFillingSecondColor, SIGNAL(changed(QColor)), this, SLOT(fillingSecondColorChanged(QColor)) ); connect( ui.sbFillingOpacity, SIGNAL(valueChanged(int)), this, SLOT(fillingOpacityChanged(int)) ); //Error bars connect( ui.cbXErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(xErrorTypeChanged(int)) ); connect( cbXErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorPlusColumnChanged(QModelIndex)) ); connect( cbXErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbYErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(yErrorTypeChanged(int)) ); connect( cbYErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorPlusColumnChanged(QModelIndex)) ); connect( cbYErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbErrorBarsType, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsTypeChanged(int)) ); connect( ui.sbErrorBarsCapSize, SIGNAL(valueChanged(double)), this, SLOT(errorBarsCapSizeChanged(double)) ); connect( ui.cbErrorBarsStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsStyleChanged(int)) ); connect( ui.kcbErrorBarsColor, SIGNAL(changed(QColor)), this, SLOT(errorBarsColorChanged(QColor)) ); connect( ui.sbErrorBarsWidth, SIGNAL(valueChanged(double)), this, SLOT(errorBarsWidthChanged(double)) ); connect( ui.sbErrorBarsOpacity, SIGNAL(valueChanged(int)), this, SLOT(errorBarsOpacityChanged(int)) ); //template handler TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::XYCurve); ui.verticalLayout->addWidget(templateHandler); templateHandler->show(); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); retranslateUi(); init(); } XYCurveDock::~XYCurveDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void XYCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); // Tab "General" QGridLayout* gridLayout = qobject_cast(generalTab->layout()); cbXColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXColumn, 2, 2, 1, 1); cbYColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYColumn, 3, 2, 1, 1); //General connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( cbXColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xColumnChanged(QModelIndex)) ); connect( cbYColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yColumnChanged(QModelIndex)) ); } void XYCurveDock::init() { m_initializing = true; //Line ui.cbLineType->addItem(i18n("none")); ui.cbLineType->addItem(i18n("line")); ui.cbLineType->addItem(i18n("horiz. start")); ui.cbLineType->addItem(i18n("vert. start")); ui.cbLineType->addItem(i18n("horiz. midpoint")); ui.cbLineType->addItem(i18n("vert. midpoint")); ui.cbLineType->addItem(i18n("2-segments")); ui.cbLineType->addItem(i18n("3-segments")); ui.cbLineType->addItem(i18n("cubic spline (natural)")); ui.cbLineType->addItem(i18n("cubic spline (periodic)")); ui.cbLineType->addItem(i18n("Akima-spline (natural)")); ui.cbLineType->addItem(i18n("Akima-spline (periodic)")); QPainter pa; //TODO size of the icon depending on the actuall height of the combobox? int iconSize = 20; QPixmap pm(iconSize, iconSize); ui.cbLineType->setIconSize(QSize(iconSize, iconSize)); QPen pen(Qt::SolidPattern, 0); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; pen.setColor(color); pa.setPen( pen ); //no line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.end(); ui.cbLineType->setItemIcon(0, pm); //line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(1, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,3); pa.drawLine(17,3,17,17); pa.end(); ui.cbLineType->setItemIcon(2, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,17); pa.drawLine(3,17,17,17); pa.end(); ui.cbLineType->setItemIcon(3, pm); //horizontal midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,3); pa.drawLine(10,3,10,17); pa.drawLine(10,17,17,17); pa.end(); ui.cbLineType->setItemIcon(4, pm); //vertical midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,10); pa.drawLine(3,10,17,10); pa.drawLine(17,10,17,17); pa.end(); ui.cbLineType->setItemIcon(5, pm); //2-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,10); pa.end(); ui.cbLineType->setItemIcon(6, pm); //3-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(7, pm); //natural spline pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.rotate(45); pa.drawArc(2*sqrt(2),-4,17*sqrt(2),20,30*16,120*16); pa.end(); ui.cbLineType->setItemIcon(8, pm); ui.cbLineType->setItemIcon(9, pm); ui.cbLineType->setItemIcon(10, pm); ui.cbLineType->setItemIcon(11, pm); GuiTools::updatePenStyles(ui.cbLineStyle, Qt::black); //Drop lines ui.cbDropLineType->addItem(i18n("no drop lines")); ui.cbDropLineType->addItem(i18n("drop lines, X")); ui.cbDropLineType->addItem(i18n("drop lines, Y")); ui.cbDropLineType->addItem(i18n("drop lines, XY")); ui.cbDropLineType->addItem(i18n("drop lines, X, zero baseline")); ui.cbDropLineType->addItem(i18n("drop lines, X, min baseline")); ui.cbDropLineType->addItem(i18n("drop lines, X, max baseline")); GuiTools::updatePenStyles(ui.cbDropLineStyle, Qt::black); //Symbols GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); ui.cbSymbolStyle->addItem(i18n("none")); for (int i = 1; i < 19; ++i) { //TODO: use enum count Symbol::Style style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; //Values ui.cbValuesType->addItem(i18n("no values")); ui.cbValuesType->addItem("x"); ui.cbValuesType->addItem("y"); ui.cbValuesType->addItem("x, y"); ui.cbValuesType->addItem("(x, y)"); ui.cbValuesType->addItem(i18n("custom column")); ui.cbValuesPosition->addItem(i18n("above")); ui.cbValuesPosition->addItem(i18n("below")); ui.cbValuesPosition->addItem(i18n("left")); ui.cbValuesPosition->addItem(i18n("right")); //Filling ui.cbFillingPosition->clear(); ui.cbFillingPosition->addItem(i18n("none")); ui.cbFillingPosition->addItem(i18n("above")); ui.cbFillingPosition->addItem(i18n("below")); ui.cbFillingPosition->addItem(i18n("zero baseline")); ui.cbFillingPosition->addItem(i18n("left")); ui.cbFillingPosition->addItem(i18n("right")); ui.cbFillingType->clear(); ui.cbFillingType->addItem(i18n("color")); ui.cbFillingType->addItem(i18n("image")); ui.cbFillingType->addItem(i18n("pattern")); ui.cbFillingColorStyle->clear(); ui.cbFillingColorStyle->addItem(i18n("single color")); ui.cbFillingColorStyle->addItem(i18n("horizontal gradient")); ui.cbFillingColorStyle->addItem(i18n("vertical gradient")); ui.cbFillingColorStyle->addItem(i18n("diag. gradient (from top left)")); ui.cbFillingColorStyle->addItem(i18n("diag. gradient (from bottom left)")); ui.cbFillingColorStyle->addItem(i18n("radial gradient")); ui.cbFillingImageStyle->clear(); ui.cbFillingImageStyle->addItem(i18n("scaled and cropped")); ui.cbFillingImageStyle->addItem(i18n("scaled")); ui.cbFillingImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbFillingImageStyle->addItem(i18n("centered")); ui.cbFillingImageStyle->addItem(i18n("tiled")); ui.cbFillingImageStyle->addItem(i18n("center tiled")); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, Qt::SolidPattern); //Error-bars pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10);//vert. line pa.drawLine(10,3,10,17);//hor. line pa.end(); ui.cbErrorBarsType->addItem(i18n("bars")); ui.cbErrorBarsType->setItemIcon(0, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); //vert. line pa.drawLine(10,3,10,17); //hor. line pa.drawLine(7,3,13,3); //upper cap pa.drawLine(7,17,13,17); //bottom cap pa.drawLine(3,7,3,13); //left cap pa.drawLine(17,7,17,13); //right cap pa.end(); ui.cbErrorBarsType->addItem(i18n("bars with ends")); ui.cbErrorBarsType->setItemIcon(1, pm); ui.cbXErrorType->addItem(i18n("no")); ui.cbXErrorType->addItem(i18n("symmetric")); ui.cbXErrorType->addItem(i18n("asymmetric")); ui.cbYErrorType->addItem(i18n("no")); ui.cbYErrorType->addItem(i18n("symmetric")); ui.cbYErrorType->addItem(i18n("asymmetric")); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, Qt::black); } void XYCurveDock::setModel() { QList list; list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; if (cbXColumn) { cbXColumn->setTopLevelClasses(list); cbYColumn->setTopLevelClasses(list); } cbValuesColumn->setTopLevelClasses(list); cbXErrorMinusColumn->setTopLevelClasses(list); cbXErrorPlusColumn->setTopLevelClasses(list); cbYErrorMinusColumn->setTopLevelClasses(list); cbYErrorPlusColumn->setTopLevelClasses(list); list.clear(); list<<"Column"<<"XYCurve"; m_aspectTreeModel->setSelectableAspects(list); if (cbXColumn) { cbXColumn->setModel(m_aspectTreeModel); cbYColumn->setModel(m_aspectTreeModel); } cbValuesColumn->setModel(m_aspectTreeModel); cbXErrorMinusColumn->setModel(m_aspectTreeModel); cbXErrorPlusColumn->setModel(m_aspectTreeModel); cbYErrorMinusColumn->setModel(m_aspectTreeModel); cbYErrorPlusColumn->setModel(m_aspectTreeModel); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYCurveDock::setCurves(QList list) { m_initializing=true; m_curvesList=list; m_curve=list.first(); Q_ASSERT(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); setModel(); initGeneralTab(); initTabs(); m_initializing=false; } void XYCurveDock::initGeneralTab() { //if there are more than one curve in the list, disable the content in the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.lXColumn->setEnabled(true); cbXColumn->setEnabled(true); uiGeneralTab.lYColumn->setEnabled(true); cbYColumn->setEnabled(true); this->setModelIndexFromAspect(cbXColumn, m_curve->xColumn()); this->setModelIndexFromAspect(cbYColumn, m_curve->yColumn()); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.lXColumn->setEnabled(false); cbXColumn->setEnabled(false); uiGeneralTab.lYColumn->setEnabled(false); cbYColumn->setEnabled(false); cbXColumn->setCurrentModelIndex(QModelIndex()); cbYColumn->setCurrentModelIndex(QModelIndex()); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(curveXColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(curveYColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged(bool))); } void XYCurveDock::initTabs() { //if there are more than one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { this->setModelIndexFromAspect(cbValuesColumn, m_curve->valuesColumn()); this->setModelIndexFromAspect(cbXErrorPlusColumn, m_curve->xErrorPlusColumn()); this->setModelIndexFromAspect(cbXErrorMinusColumn, m_curve->xErrorMinusColumn()); this->setModelIndexFromAspect(cbYErrorPlusColumn, m_curve->yErrorPlusColumn()); this->setModelIndexFromAspect(cbYErrorMinusColumn, m_curve->yErrorMinusColumn()); } else { cbValuesColumn->setCurrentModelIndex(QModelIndex()); cbXErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbXErrorMinusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorMinusColumn->setCurrentModelIndex(QModelIndex()); } //show the properties of the first curve KConfig config("", KConfig::SimpleConfig); loadConfig(config); //Slots //Line-Tab connect(m_curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(curveLineTypeChanged(XYCurve::LineType))); connect(m_curve, SIGNAL(lineSkipGapsChanged(bool)), this, SLOT(curveLineSkipGapsChanged(bool))); connect(m_curve, SIGNAL(lineInterpolationPointsCountChanged(int)), this, SLOT(curveLineInterpolationPointsCountChanged(int))); connect(m_curve, SIGNAL(linePenChanged(QPen)), this, SLOT(curveLinePenChanged(QPen))); connect(m_curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(curveLineOpacityChanged(qreal))); connect(m_curve, SIGNAL(dropLineTypeChanged(XYCurve::DropLineType)), this, SLOT(curveDropLineTypeChanged(XYCurve::DropLineType))); connect(m_curve, SIGNAL(dropLinePenChanged(QPen)), this, SLOT(curveDropLinePenChanged(QPen))); connect(m_curve, SIGNAL(dropLineOpacityChanged(qreal)), this, SLOT(curveDropLineOpacityChanged(qreal))); //Symbol-Tab connect(m_curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(curveSymbolsStyleChanged(Symbol::Style))); connect(m_curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(curveSymbolsSizeChanged(qreal))); connect(m_curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(curveSymbolsRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(curveSymbolsOpacityChanged(qreal))); connect(m_curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(curveSymbolsBrushChanged(QBrush))); connect(m_curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(curveSymbolsPenChanged(QPen))); //Values-Tab connect(m_curve, SIGNAL(valuesTypeChanged(XYCurve::ValuesType)), this, SLOT(curveValuesTypeChanged(XYCurve::ValuesType))); connect(m_curve, SIGNAL(valuesColumnChanged(const AbstractColumn*)), this, SLOT(curveValuesColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(valuesPositionChanged(XYCurve::ValuesPosition)), this, SLOT(curveValuesPositionChanged(XYCurve::ValuesPosition))); connect(m_curve, SIGNAL(valuesDistanceChanged(qreal)), this, SLOT(curveValuesDistanceChanged(qreal))); connect(m_curve, SIGNAL(valuesOpacityChanged(qreal)), this, SLOT(curveValuesOpacityChanged(qreal))); connect(m_curve, SIGNAL(valuesRotationAngleChanged(qreal)), this, SLOT(curveValuesRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(valuesPrefixChanged(QString)), this, SLOT(curveValuesPrefixChanged(QString))); connect(m_curve, SIGNAL(valuesSuffixChanged(QString)), this, SLOT(curveValuesSuffixChanged(QString))); connect(m_curve, SIGNAL(valuesFontChanged(QFont)), this, SLOT(curveValuesFontChanged(QFont))); connect(m_curve, SIGNAL(valuesColorChanged(QColor)), this, SLOT(curveValuesColorChanged(QColor))); //Filling-Tab connect( m_curve, SIGNAL(fillingPositionChanged(XYCurve::FillingPosition)), this, SLOT(curveFillingPositionChanged(XYCurve::FillingPosition)) ); connect( m_curve, SIGNAL(fillingTypeChanged(PlotArea::BackgroundType)), this, SLOT(curveFillingTypeChanged(PlotArea::BackgroundType)) ); connect( m_curve, SIGNAL(fillingColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_curve, SIGNAL(fillingImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_curve, SIGNAL(fillingBrushStyleChanged(Qt::BrushStyle)), this, SLOT(curveFillingBrushStyleChanged(Qt::BrushStyle)) ); connect( m_curve, SIGNAL(fillingFirstColorChanged(QColor&)), this, SLOT(curveFillingFirstColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingSecondColorChanged(QColor&)), this, SLOT(curveFillingSecondColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingFileNameChanged(QString&)), this, SLOT(curveFillingFileNameChanged(QString&)) ); connect( m_curve, SIGNAL(fillingOpacityChanged(float)), this, SLOT(curveFillingOpacityChanged(float)) ); //"Error bars"-Tab connect(m_curve, SIGNAL(xErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveXErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(xErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(xErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveYErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(yErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(errorBarsCapSizeChanged(qreal)), this, SLOT(curveErrorBarsCapSizeChanged(qreal))); connect(m_curve, SIGNAL(errorBarsTypeChanged(XYCurve::ErrorBarsType)), this, SLOT(curveErrorBarsTypeChanged(XYCurve::ErrorBarsType))); connect(m_curve, SIGNAL(errorBarsPenChanged(QPen)), this, SLOT(curveErrorBarsPenChanged(QPen))); connect(m_curve, SIGNAL(errorBarsOpacityChanged(qreal)), this, SLOT(curveErrorBarsOpacityChanged(qreal))); } /*! depending on the currently selected values column type (column mode) updates the widgets for the values column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the values column was changed. synchronize this function with ColumnDock::updateFormat. */ void XYCurveDock::updateValuesFormatWidgets(const AbstractColumn::ColumnMode columnMode) { ui.cbValuesFormat->clear(); switch (columnMode) { case AbstractColumn::Numeric: ui.cbValuesFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesFormat->addItem(i18n("Automatic (E)"), QVariant('G')); break; case AbstractColumn::Integer: break; case AbstractColumn::Text: ui.cbValuesFormat->addItem(i18n("Text"), QVariant()); break; case AbstractColumn::Month: ui.cbValuesFormat->addItem(i18n("Number without leading zero"), QVariant("M")); ui.cbValuesFormat->addItem(i18n("Number with leading zero"), QVariant("MM")); ui.cbValuesFormat->addItem(i18n("Abbreviated month name"), QVariant("MMM")); ui.cbValuesFormat->addItem(i18n("Full month name"), QVariant("MMMM")); break; case AbstractColumn::Day: ui.cbValuesFormat->addItem(i18n("Number without leading zero"), QVariant("d")); ui.cbValuesFormat->addItem(i18n("Number with leading zero"), QVariant("dd")); ui.cbValuesFormat->addItem(i18n("Abbreviated day name"), QVariant("ddd")); ui.cbValuesFormat->addItem(i18n("Full day name"), QVariant("dddd")); break; case AbstractColumn::DateTime: { for (const auto& s: AbstractColumn::dateFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s: AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s1: AbstractColumn::dateFormats()) { for (const auto& s2: AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); } break; } } if (columnMode == AbstractColumn::Numeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } else { ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); } if (columnMode == AbstractColumn::Text) { ui.lValuesFormatTop->hide(); ui.lValuesFormat->hide(); ui.cbValuesFormat->hide(); } else { ui.lValuesFormatTop->show(); ui.lValuesFormat->show(); ui.cbValuesFormat->show(); } if (columnMode == AbstractColumn::DateTime) { ui.cbValuesFormat->setCurrentItem("yyyy-MM-dd hh:mm:ss.zzz"); ui.cbValuesFormat->setEditable(true); } else { ui.cbValuesFormat->setCurrentIndex(0); ui.cbValuesFormat->setEditable(false); } } /*! shows the formating properties of the column \c column. Called, when a new column for the values was selected - either by changing the type of the values (none, x, y, etc.) or by selecting a new custom column for the values. */ void XYCurveDock::showValuesColumnFormat(const Column* column) { if (!column) { // no valid column is available // -> hide all the format properties widgets (equivalent to showing the properties of the column mode "Text") this->updateValuesFormatWidgets(AbstractColumn::Text); } else { AbstractColumn::ColumnMode columnMode = column->columnMode(); //update the format widgets for the new column mode this->updateValuesFormatWidgets(columnMode); //show the actuall formating properties switch (columnMode) { case AbstractColumn::Numeric: { Double2StringFilter* filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->numericFormat())); ui.sbValuesPrecision->setValue(filter->numDigits()); break; } case AbstractColumn::Integer: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { DateTime2StringFilter* filter = static_cast(column->outputFilter()); DEBUG(" column values format = " << filter->format().toStdString()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->format())); break; } } } } void XYCurveDock::setModelIndexFromAspect(TreeViewComboBox* cb, const AbstractAspect* aspect) { if (aspect) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(aspect)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in XYCurveDock ******** //************************************************************* void XYCurveDock::retranslateUi() { //TODO: // uiGeneralTab.lName->setText(i18n("Name")); // uiGeneralTab.lComment->setText(i18n("Comment")); // uiGeneralTab.chkVisible->setText(i18n("Visible")); // uiGeneralTab.lXColumn->setText(i18n("x-data")); // uiGeneralTab.lYColumn->setText(i18n("y-data")); //TODO updatePenStyles, updateBrushStyles for all comboboxes } // "General"-tab void XYCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYCurveDock::xColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(XYCurve* curve, m_curvesList) curve->setXColumn(column); } void XYCurveDock::yColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(XYCurve* curve, m_curvesList) curve->setYColumn(column); } void XYCurveDock::visibilityChanged(bool state) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setVisible(state); } // "Line"-tab void XYCurveDock::lineTypeChanged(int index) { XYCurve::LineType lineType = XYCurve::LineType(index); if ( lineType == XYCurve::NoLine) { ui.chkLineSkipGaps->setEnabled(false); ui.cbLineStyle->setEnabled(false); ui.kcbLineColor->setEnabled(false); ui.sbLineWidth->setEnabled(false); ui.sbLineOpacity->setEnabled(false); ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); } else { ui.chkLineSkipGaps->setEnabled(true); ui.cbLineStyle->setEnabled(true); ui.kcbLineColor->setEnabled(true); ui.sbLineWidth->setEnabled(true); ui.sbLineOpacity->setEnabled(true); if (lineType == XYCurve::SplineCubicNatural || lineType == XYCurve::SplineCubicPeriodic || lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) { ui.lLineInterpolationPointsCount->show(); ui.sbLineInterpolationPointsCount->show(); ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } else { ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); ui.lLineSkipGaps->show(); ui.chkLineSkipGaps->show(); } } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setLineType(lineType); } void XYCurveDock::lineSkipGapsChanged(bool skip) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setLineSkipGaps(skip); } void XYCurveDock::lineInterpolationPointsCountChanged(int count) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setLineInterpolationPointsCount(count); } void XYCurveDock::lineStyleChanged(int index) { if (m_initializing) return; Qt::PenStyle penStyle=Qt::PenStyle(index); QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->linePen(); pen.setStyle(penStyle); curve->setLinePen(pen); } } void XYCurveDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->linePen(); pen.setColor(color); curve->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void XYCurveDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->linePen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setLinePen(pen); } } void XYCurveDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setLineOpacity(opacity); } void XYCurveDock::dropLineTypeChanged(int index) { XYCurve::DropLineType dropLineType = XYCurve::DropLineType(index); if ( dropLineType == XYCurve::NoDropLine) { ui.cbDropLineStyle->setEnabled(false); ui.kcbDropLineColor->setEnabled(false); ui.sbDropLineWidth->setEnabled(false); ui.sbDropLineOpacity->setEnabled(false); } else { ui.cbDropLineStyle->setEnabled(true); ui.kcbDropLineColor->setEnabled(true); ui.sbDropLineWidth->setEnabled(true); ui.sbDropLineOpacity->setEnabled(true); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setDropLineType(dropLineType); } void XYCurveDock::dropLineStyleChanged(int index) { if (m_initializing) return; Qt::PenStyle penStyle=Qt::PenStyle(index); QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->dropLinePen(); pen.setStyle(penStyle); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->dropLinePen(); pen.setColor(color); curve->setDropLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbDropLineStyle, color); m_initializing = false; } void XYCurveDock::dropLineWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->dropLinePen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setDropLineOpacity(opacity); } //"Symbol"-tab void XYCurveDock::symbolsStyleChanged(int index) { Symbol::Style style = Symbol::Style(index); if (style == Symbol::NoSymbols) { ui.sbSymbolSize->setEnabled(false); ui.sbSymbolRotation->setEnabled(false); ui.sbSymbolOpacity->setEnabled(false); ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); ui.cbSymbolBorderStyle->setEnabled(false); ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.sbSymbolSize->setEnabled(true); ui.sbSymbolRotation->setEnabled(true); ui.sbSymbolOpacity->setEnabled(true); //enable/disable the symbol filling options in the GUI depending on the currently selected symbol. if (style!=Symbol::Line && style!=Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } ui.cbSymbolBorderStyle->setEnabled(true); bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setSymbolsStyle(style); } void XYCurveDock::symbolsSizeChanged(double value) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void XYCurveDock::symbolsRotationChanged(int value) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setSymbolsRotationAngle(value); } void XYCurveDock::symbolsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setSymbolsOpacity(opacity); } void XYCurveDock::symbolsFillingStyleChanged(int index) { Qt::BrushStyle brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; foreach(XYCurve* curve, m_curvesList) { brush=curve->symbolsBrush(); brush.setStyle(brushStyle); curve->setSymbolsBrush(brush); } } void XYCurveDock::symbolsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; foreach(XYCurve* curve, m_curvesList) { brush=curve->symbolsBrush(); brush.setColor(color); curve->setSymbolsBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void XYCurveDock::symbolsBorderStyleChanged(int index) { Qt::PenStyle penStyle=Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->symbolsPen(); pen.setStyle(penStyle); curve->setSymbolsPen(pen); } } void XYCurveDock::symbolsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->symbolsPen(); pen.setColor(color); curve->setSymbolsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void XYCurveDock::symbolsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->symbolsPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setSymbolsPen(pen); } } //Values-tab /*! called when the type of the values (none, x, y, (x,y) etc.) was changed. */ void XYCurveDock::valuesTypeChanged(int index) { XYCurve::ValuesType valuesType = XYCurve::ValuesType(index); if (valuesType == XYCurve::NoValues) { //no values are to paint -> deactivate all the pertinent widgets ui.cbValuesPosition->setEnabled(false); ui.lValuesColumn->hide(); cbValuesColumn->hide(); ui.sbValuesDistance->setEnabled(false); ui.sbValuesRotation->setEnabled(false); ui.sbValuesOpacity->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.sbValuesPrecision->setEnabled(false); ui.leValuesPrefix->setEnabled(false); ui.leValuesSuffix->setEnabled(false); ui.kfrValuesFont->setEnabled(false); ui.kcbValuesColor->setEnabled(false); } else { ui.cbValuesPosition->setEnabled(true); ui.sbValuesDistance->setEnabled(true); ui.sbValuesRotation->setEnabled(true); ui.sbValuesOpacity->setEnabled(true); ui.cbValuesFormat->setEnabled(true); ui.sbValuesPrecision->setEnabled(true); ui.leValuesPrefix->setEnabled(true); ui.leValuesSuffix->setEnabled(true); ui.kfrValuesFont->setEnabled(true); ui.kcbValuesColor->setEnabled(true); const Column* column; if (valuesType == XYCurve::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); column= static_cast(cbValuesColumn->currentModelIndex().internalPointer()); } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); if (valuesType == XYCurve::ValuesY) column = static_cast(m_curve->yColumn()); else column = static_cast(m_curve->xColumn()); } this->showValuesColumnFormat(column); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setValuesType(valuesType); } /*! called when the custom column for the values was changed. */ void XYCurveDock::valuesColumnChanged(const QModelIndex& index) { if (m_initializing) return; Column* column= static_cast(index.internalPointer()); this->showValuesColumnFormat(column); foreach(XYCurve* curve, m_curvesList) { //TODO save also the format of the currently selected column for the values (precision etc.) curve->setValuesColumn(column); } } void XYCurveDock::valuesPositionChanged(int index) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setValuesPosition(XYCurve::ValuesPosition(index)); } void XYCurveDock::valuesDistanceChanged(double value) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void XYCurveDock::valuesRotationChanged(int value) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setValuesRotationAngle(value); } void XYCurveDock::valuesOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setValuesOpacity(opacity); } void XYCurveDock::valuesPrefixChanged() { if (m_initializing) return; QString prefix = ui.leValuesPrefix->text(); foreach(XYCurve* curve, m_curvesList) curve->setValuesPrefix(prefix); } void XYCurveDock::valuesSuffixChanged() { if (m_initializing) return; QString suffix = ui.leValuesSuffix->text(); foreach(XYCurve* curve, m_curvesList) curve->setValuesSuffix(suffix); } void XYCurveDock::valuesFontChanged(const QFont& font) { if (m_initializing) return; QFont valuesFont = font; valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); foreach(XYCurve* curve, m_curvesList) curve->setValuesFont(valuesFont); } void XYCurveDock::valuesColorChanged(const QColor& color) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setValuesColor(color); } //Filling-tab void XYCurveDock::fillingPositionChanged(int index) { XYCurve::FillingPosition fillingPosition = XYCurve::FillingPosition(index); bool b = (fillingPosition != XYCurve::NoFilling); ui.cbFillingType->setEnabled(b); ui.cbFillingColorStyle->setEnabled(b); ui.cbFillingBrushStyle->setEnabled(b); ui.cbFillingImageStyle->setEnabled(b); ui.kcbFillingFirstColor->setEnabled(b); ui.kcbFillingSecondColor->setEnabled(b); ui.leFillingFileName->setEnabled(b); ui.bFillingOpen->setEnabled(b); ui.sbFillingOpacity->setEnabled(b); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setFillingPosition(fillingPosition); } void XYCurveDock::fillingTypeChanged(int index) { PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lFillingColorStyle->show(); ui.cbFillingColorStyle->show(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbFillingColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->show(); ui.cbFillingImageStyle->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->show(); ui.leFillingFileName->show(); ui.bFillingOpen->show(); ui.lFillingFirstColor->hide(); ui.kcbFillingFirstColor->hide(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->show(); ui.cbFillingBrushStyle->show(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setFillingType(type); } void XYCurveDock::fillingColorStyleChanged(int index) { PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setFillingColorStyle(style); } void XYCurveDock::fillingImageStyleChanged(int index) { if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; foreach(XYCurve* curve, m_curvesList) curve->setFillingImageStyle(style); } void XYCurveDock::fillingBrushStyleChanged(int index) { if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; foreach(XYCurve* curve, m_curvesList) curve->setFillingBrushStyle(style); } void XYCurveDock::fillingFirstColorChanged(const QColor& c) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setFillingFirstColor(c); m_initializing = true; GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, c); m_initializing = false; } void XYCurveDock::fillingSecondColorChanged(const QColor& c) { if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setFillingSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void XYCurveDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "XYCurveDock"); QString dir = conf.readEntry("LastImageDir", ""); - QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir); + + QString formats; + foreach(const QByteArray& format, QImageReader::supportedImageFormats()) { + QString f = "*." + QString(format.constData()); + formats.isEmpty() ? formats+=f : formats+=' '+f; + } + + QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos!=-1) { QString newDir = path.left(pos); if (newDir!=dir) conf.writeEntry("LastImageDir", newDir); } ui.leFillingFileName->setText( path ); foreach(XYCurve* curve, m_curvesList) curve->setFillingFileName(path); } void XYCurveDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leFillingFileName->text(); foreach(XYCurve* curve, m_curvesList) curve->setFillingFileName(fileName); } void XYCurveDock::fillingOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setFillingOpacity(opacity); } //"Error bars"-Tab void XYCurveDock::xErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lXErrorDataPlus->setVisible(false); cbXErrorPlusColumn->setVisible(false); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); ui.lXErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(true); cbXErrorMinusColumn->setVisible(true); ui.lXErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbYErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setXErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::xErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = dynamic_cast(aspect); Q_ASSERT(column); foreach(XYCurve* curve, m_curvesList) curve->setXErrorPlusColumn(column); } void XYCurveDock::xErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = dynamic_cast(aspect); Q_ASSERT(column); foreach(XYCurve* curve, m_curvesList) curve->setXErrorMinusColumn(column); } void XYCurveDock::yErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lYErrorDataPlus->setVisible(false); cbYErrorPlusColumn->setVisible(false); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); ui.lYErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(true); cbYErrorMinusColumn->setVisible(true); ui.lYErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbXErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setYErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::yErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = dynamic_cast(aspect); Q_ASSERT(column); foreach(XYCurve* curve, m_curvesList) curve->setYErrorPlusColumn(column); } void XYCurveDock::yErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = dynamic_cast(aspect); Q_ASSERT(column); foreach(XYCurve* curve, m_curvesList) curve->setYErrorMinusColumn(column); } void XYCurveDock::errorBarsTypeChanged(int index) const { XYCurve::ErrorBarsType type = XYCurve::ErrorBarsType(index); bool b = (type == XYCurve::ErrorBarsWithEnds); ui.lErrorBarsCapSize->setVisible(b); ui.sbErrorBarsCapSize->setVisible(b); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) curve->setErrorBarsType(type); } void XYCurveDock::errorBarsCapSizeChanged(double value) const { if (m_initializing) return; float size = Worksheet::convertToSceneUnits(value, Worksheet::Point); foreach(XYCurve* curve, m_curvesList) curve->setErrorBarsCapSize(size); } void XYCurveDock::errorBarsStyleChanged(int index) const { if (m_initializing) return; Qt::PenStyle penStyle=Qt::PenStyle(index); QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->errorBarsPen(); pen.setStyle(penStyle); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->errorBarsPen(); pen.setColor(color); curve->setErrorBarsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbErrorBarsStyle, color); m_initializing = false; } void XYCurveDock::errorBarsWidthChanged(double value) const { if (m_initializing) return; QPen pen; foreach(XYCurve* curve, m_curvesList) { pen=curve->errorBarsPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsOpacityChanged(int value) const { if (m_initializing) return; qreal opacity = (float)value/100.; foreach(XYCurve* curve, m_curvesList) curve->setErrorBarsOpacity(opacity); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYCurveDock::curveXColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXColumn, column); m_initializing = false; } void XYCurveDock::curveYColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYColumn, column); m_initializing = false; } void XYCurveDock::curveVisibilityChanged(bool on) { m_initializing = true; uiGeneralTab.chkVisible->setChecked(on); m_initializing = false; } //Line-Tab void XYCurveDock::curveLineTypeChanged(XYCurve::LineType type) { m_initializing = true; ui.cbLineType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveLineSkipGapsChanged(bool skip) { m_initializing = true; ui.chkLineSkipGaps->setChecked(skip); m_initializing = false; } void XYCurveDock::curveLineInterpolationPointsCountChanged(int count) { m_initializing = true; ui.sbLineInterpolationPointsCount->setValue(count); m_initializing = false; } void XYCurveDock::curveLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( (int)pen.style()); ui.kcbLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color()); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveDropLineTypeChanged(XYCurve::DropLineType type) { m_initializing = true; ui.cbDropLineType->setCurrentIndex( (int)type ); m_initializing = false; } void XYCurveDock::curveDropLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbDropLineStyle->setCurrentIndex( (int) pen.style()); ui.kcbDropLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, pen.color()); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveDropLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbDropLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //Symbol-Tab void XYCurveDock::curveSymbolsStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void XYCurveDock::curveSymbolsSizeChanged(qreal size) { m_initializing = true; ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveSymbolsRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveSymbolsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveSymbolsBrushChanged(QBrush brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void XYCurveDock::curveSymbolsPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); m_initializing = false; } //Values-Tab void XYCurveDock::curveValuesTypeChanged(XYCurve::ValuesType type) { m_initializing = true; ui.cbValuesType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveValuesColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbValuesColumn, column); m_initializing = false; } void XYCurveDock::curveValuesPositionChanged(XYCurve::ValuesPosition position) { m_initializing = true; ui.cbValuesPosition->setCurrentIndex((int) position); m_initializing = false; } void XYCurveDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveValuesRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbValuesRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveValuesOpacityChanged(qreal opacity) { m_initializing = true; ui.sbValuesOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveValuesPrefixChanged(QString prefix) { m_initializing = true; ui.leValuesPrefix->setText(prefix); m_initializing = false; } void XYCurveDock::curveValuesSuffixChanged(QString suffix) { m_initializing = true; ui.leValuesSuffix->setText(suffix); m_initializing = false; } void XYCurveDock::curveValuesFontChanged(QFont font) { m_initializing = true; font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont(font); m_initializing = false; } void XYCurveDock::curveValuesColorChanged(QColor color) { m_initializing = true; ui.kcbValuesColor->setColor(color); m_initializing = false; } //Filling void XYCurveDock::curveFillingPositionChanged(XYCurve::FillingPosition position) { m_initializing = true; ui.cbFillingPosition->setCurrentIndex((int)position); m_initializing = false; } void XYCurveDock::curveFillingTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbFillingType->setCurrentIndex(type); m_initializing = false; } void XYCurveDock::curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbFillingColorStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbFillingImageStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbFillingBrushStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingFirstColor->setColor(color); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, color); m_initializing = false; } void XYCurveDock::curveFillingSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingSecondColor->setColor(color); m_initializing = false; } void XYCurveDock::curveFillingFileNameChanged(QString& filename) { m_initializing = true; ui.leFillingFileName->setText(filename); m_initializing = false; } void XYCurveDock::curveFillingOpacityChanged(float opacity) { m_initializing = true; ui.sbFillingOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //"Error bars"-Tab void XYCurveDock::curveXErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbXErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveXErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveXErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbYErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveYErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveErrorBarsCapSizeChanged(qreal size) { m_initializing = true; ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsTypeChanged(XYCurve::ErrorBarsType type) { m_initializing = true; ui.cbErrorBarsType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveErrorBarsPenChanged(QPen pen) { m_initializing = true; ui.cbErrorBarsStyle->setCurrentIndex( (int) pen.style()); ui.kcbErrorBarsColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, pen.color()); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbErrorBarsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void XYCurveDock::load() { //General //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( (int) m_curve->lineType() ); ui.chkLineSkipGaps->setChecked( m_curve->lineSkipGaps() ); ui.sbLineInterpolationPointsCount->setValue( m_curve->lineInterpolationPointsCount() ); ui.cbLineStyle->setCurrentIndex( (int) m_curve->linePen().style() ); ui.kcbLineColor->setColor( m_curve->linePen().color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->linePen().widthF(), Worksheet::Point) ); ui.sbLineOpacity->setValue( round(m_curve->lineOpacity()*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( (int) m_curve->dropLineType() ); ui.cbDropLineStyle->setCurrentIndex( (int) m_curve->dropLinePen().style() ); ui.kcbDropLineColor->setColor( m_curve->dropLinePen().color() ); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->dropLinePen().widthF(),Worksheet::Point) ); ui.sbDropLineOpacity->setValue( round(m_curve->dropLineOpacity()*100.0) ); //Symbols //TODO: character ui.cbSymbolStyle->setCurrentIndex( (int)m_curve->symbolsStyle() ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsSize(), Worksheet::Point) ); ui.sbSymbolRotation->setValue( m_curve->symbolsRotationAngle() ); ui.sbSymbolOpacity->setValue( round(m_curve->symbolsOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_curve->symbolsBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_curve->symbolsBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_curve->symbolsPen().style() ); ui.kcbSymbolBorderColor->setColor( m_curve->symbolsPen().color() ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsPen().widthF(), Worksheet::Point) ); //Values ui.cbValuesType->setCurrentIndex( (int) m_curve->valuesType() ); ui.cbValuesPosition->setCurrentIndex( (int) m_curve->valuesPosition() ); ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(m_curve->valuesDistance(), Worksheet::Point) ); ui.sbValuesRotation->setValue( m_curve->valuesRotationAngle() ); ui.sbValuesOpacity->setValue( round(m_curve->valuesOpacity()*100.0) ); ui.leValuesPrefix->setText( m_curve->valuesPrefix() ); ui.leValuesSuffix->setText( m_curve->valuesSuffix() ); QFont valuesFont = m_curve->valuesFont(); valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont(valuesFont); ui.kcbValuesColor->setColor( m_curve->valuesColor() ); //Filling ui.cbFillingPosition->setCurrentIndex( (int) m_curve->fillingPosition() ); ui.cbFillingType->setCurrentIndex( (int)m_curve->fillingType() ); ui.cbFillingColorStyle->setCurrentIndex( (int) m_curve->fillingColorStyle() ); ui.cbFillingImageStyle->setCurrentIndex( (int) m_curve->fillingImageStyle() ); ui.cbFillingBrushStyle->setCurrentIndex( (int) m_curve->fillingBrushStyle() ); ui.leFillingFileName->setText( m_curve->fillingFileName() ); ui.kcbFillingFirstColor->setColor( m_curve->fillingFirstColor() ); ui.kcbFillingSecondColor->setColor( m_curve->fillingSecondColor() ); ui.sbFillingOpacity->setValue( round(m_curve->fillingOpacity()*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( (int) m_curve->xErrorType() ); ui.cbYErrorType->setCurrentIndex( (int) m_curve->yErrorType() ); ui.cbErrorBarsType->setCurrentIndex( (int) m_curve->errorBarsType() ); ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsCapSize(), Worksheet::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( (int) m_curve->errorBarsPen().style() ); ui.kcbErrorBarsColor->setColor( m_curve->errorBarsPen().color() ); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsPen().widthF(),Worksheet::Point) ); ui.sbErrorBarsOpacity->setValue( round(m_curve->errorBarsOpacity()*100.0) ); m_initializing=true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); m_initializing=false; } void XYCurveDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index!=-1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_curvesList.size(); if (size>1) m_curve->beginMacro(i18n("%1 xy-curves: template \"%2\" loaded", size, name)); else m_curve->beginMacro(i18n("%1: template \"%2\" loaded", m_curve->name(), name)); this->loadConfig(config); m_curve->endMacro(); } void XYCurveDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( group.readEntry("LineType", (int) m_curve->lineType()) ); ui.chkLineSkipGaps->setChecked( group.readEntry("LineSkipGaps", m_curve->lineSkipGaps()) ); ui.sbLineInterpolationPointsCount->setValue( group.readEntry("LineInterpolationPointsCount", m_curve->lineInterpolationPointsCount()) ); ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_curve->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_curve->linePen().color()) ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_curve->lineOpacity())*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( group.readEntry("DropLineType", (int) m_curve->dropLineType()) ); ui.cbDropLineStyle->setCurrentIndex( group.readEntry("DropLineStyle", (int) m_curve->dropLinePen().style()) ); ui.kcbDropLineColor->setColor( group.readEntry("DropLineColor", m_curve->dropLinePen().color()) ); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("DropLineWidth", m_curve->dropLinePen().widthF()),Worksheet::Point) ); ui.sbDropLineOpacity->setValue( round(group.readEntry("DropLineOpacity", m_curve->dropLineOpacity())*100.0) ); //Symbols //TODO: character ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_curve->symbolsStyle()) ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_curve->symbolsRotationAngle()) ); ui.sbSymbolOpacity->setValue( round(group.readEntry("SymbolOpacity", m_curve->symbolsOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_curve->symbolsBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_curve->symbolsBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_curve->symbolsPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_curve->symbolsPen().color()) ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Point) ); //Values ui.cbValuesType->setCurrentIndex( group.readEntry("ValuesType", (int) m_curve->valuesType()) ); ui.cbValuesPosition->setCurrentIndex( group.readEntry("ValuesPosition", (int) m_curve->valuesPosition()) ); ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Point) ); ui.sbValuesRotation->setValue( group.readEntry("ValuesRotation", m_curve->valuesRotationAngle()) ); ui.sbValuesOpacity->setValue( round(group.readEntry("ValuesOpacity",m_curve->valuesOpacity())*100.0) ); ui.leValuesPrefix->setText( group.readEntry("ValuesPrefix", m_curve->valuesPrefix()) ); ui.leValuesSuffix->setText( group.readEntry("ValuesSuffix", m_curve->valuesSuffix()) ); QFont valuesFont = m_curve->valuesFont(); valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont( group.readEntry("ValuesFont", valuesFont) ); ui.kcbValuesColor->setColor( group.readEntry("ValuesColor", m_curve->valuesColor()) ); //Filling ui.cbFillingPosition->setCurrentIndex( group.readEntry("FillingPosition", (int) m_curve->fillingPosition()) ); ui.cbFillingType->setCurrentIndex( group.readEntry("FillingType", (int) m_curve->fillingType()) ); ui.cbFillingColorStyle->setCurrentIndex( group.readEntry("FillingColorStyle", (int) m_curve->fillingColorStyle()) ); ui.cbFillingImageStyle->setCurrentIndex( group.readEntry("FillingImageStyle", (int) m_curve->fillingImageStyle()) ); ui.cbFillingBrushStyle->setCurrentIndex( group.readEntry("FillingBrushStyle", (int) m_curve->fillingBrushStyle()) ); ui.leFillingFileName->setText( group.readEntry("FillingFileName", m_curve->fillingFileName()) ); ui.kcbFillingFirstColor->setColor( group.readEntry("FillingFirstColor", m_curve->fillingFirstColor()) ); ui.kcbFillingSecondColor->setColor( group.readEntry("FillingSecondColor", m_curve->fillingSecondColor()) ); ui.sbFillingOpacity->setValue( round(group.readEntry("FillingOpacity", m_curve->fillingOpacity())*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( group.readEntry("XErrorType", (int) m_curve->xErrorType()) ); ui.cbYErrorType->setCurrentIndex( group.readEntry("YErrorType", (int) m_curve->yErrorType()) ); ui.cbErrorBarsType->setCurrentIndex( group.readEntry("ErrorBarsType", (int) m_curve->errorBarsType()) ); ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( group.readEntry("ErrorBarsStyle", (int) m_curve->errorBarsPen().style()) ); ui.kcbErrorBarsColor->setColor( group.readEntry("ErrorBarsColor", m_curve->errorBarsPen().color()) ); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()),Worksheet::Point) ); ui.sbErrorBarsOpacity->setValue( round(group.readEntry("ErrorBarsOpacity", m_curve->errorBarsOpacity())*100.0) ); m_initializing=true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, ui.kcbFillingFirstColor->color()); m_initializing=false; } void XYCurveDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. group.writeEntry("LineType", ui.cbLineType->currentIndex()); group.writeEntry("LineSkipGaps", ui.chkLineSkipGaps->isChecked()); group.writeEntry("LineInterpolationPointsCount", ui.sbLineInterpolationPointsCount->value() ); group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(),Worksheet::Point) ); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100 ); //Drop Line group.writeEntry("DropLineType", ui.cbDropLineType->currentIndex()); group.writeEntry("DropLineStyle", ui.cbDropLineStyle->currentIndex()); group.writeEntry("DropLineColor", ui.kcbDropLineColor->color()); group.writeEntry("DropLineWidth", Worksheet::convertToSceneUnits(ui.sbDropLineWidth->value(),Worksheet::Point) ); group.writeEntry("DropLineOpacity", ui.sbDropLineOpacity->value()/100 ); //Symbol (TODO: character) group.writeEntry("SymbolStyle", ui.cbSymbolStyle->currentText()); group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Point)); group.writeEntry("SymbolRotation", ui.sbSymbolRotation->value()); group.writeEntry("SymbolOpacity", ui.sbSymbolOpacity->value()/100 ); group.writeEntry("SymbolFillingStyle", ui.cbSymbolFillingStyle->currentIndex()); group.writeEntry("SymbolFillingColor", ui.kcbSymbolFillingColor->color()); group.writeEntry("SymbolBorderStyle", ui.cbSymbolBorderStyle->currentIndex()); group.writeEntry("SymbolBorderColor", ui.kcbSymbolBorderColor->color()); group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(),Worksheet::Point)); //Values group.writeEntry("ValuesType", ui.cbValuesType->currentIndex()); group.writeEntry("ValuesPosition", ui.cbValuesPosition->currentIndex()); group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(),Worksheet::Point)); group.writeEntry("ValuesRotation", ui.sbValuesRotation->value()); group.writeEntry("ValuesOpacity", ui.sbValuesOpacity->value()/100); group.writeEntry("ValuesPrefix", ui.leValuesPrefix->text()); group.writeEntry("ValuesSuffix", ui.leValuesSuffix->text()); group.writeEntry("ValuesFont", ui.kfrValuesFont->font()); group.writeEntry("ValuesColor", ui.kcbValuesColor->color()); //Filling group.writeEntry("FillingPosition", ui.cbFillingPosition->currentIndex()); group.writeEntry("FillingType", ui.cbFillingType->currentIndex()); group.writeEntry("FillingColorStyle", ui.cbFillingColorStyle->currentIndex()); group.writeEntry("FillingImageStyle", ui.cbFillingImageStyle->currentIndex()); group.writeEntry("FillingBrushStyle", ui.cbFillingBrushStyle->currentIndex()); group.writeEntry("FillingFileName", ui.leFillingFileName->text()); group.writeEntry("FillingFirstColor", ui.kcbFillingFirstColor->color()); group.writeEntry("FillingSecondColor", ui.kcbFillingSecondColor->color()); group.writeEntry("FillingOpacity", ui.sbFillingOpacity->value()/100.0); //Error bars group.writeEntry("XErrorType", ui.cbXErrorType->currentIndex()); group.writeEntry("YErrorType", ui.cbYErrorType->currentIndex()); group.writeEntry("ErrorBarsType", ui.cbErrorBarsType->currentIndex()); group.writeEntry("ErrorBarsCapSize", Worksheet::convertToSceneUnits(ui.sbErrorBarsCapSize->value(),Worksheet::Point) ); group.writeEntry("ErrorBarsStyle", ui.cbErrorBarsStyle->currentIndex()); group.writeEntry("ErrorBarsColor", ui.kcbErrorBarsColor->color()); group.writeEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(ui.sbErrorBarsWidth->value(),Worksheet::Point) ); group.writeEntry("ErrorBarsOpacity", ui.sbErrorBarsOpacity->value()/100 ); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp b/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp index 4782e03b4..edfc0254c 100644 --- a/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYDataReductionCurveDock.cpp @@ -1,714 +1,712 @@ /*************************************************************************** File : XYDataReductionCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) Description : widget for editing properties of data reduction curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYDataReductionCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #ifndef NDEBUG #include #endif -#include // isnan - /*! \class XYDataReductionCurveDock \brief Provides a widget for editing the properties of the XYDataReductionCurves (2D-curves defined by an data reduction) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYDataReductionCurveDock::XYDataReductionCurveDock(QWidget* parent, QStatusBar* sb) : XYCurveDock(parent), statusBar(sb), cbDataSourceCurve(nullptr), cbXDataColumn(nullptr), cbYDataColumn(nullptr), m_dataReductionCurve(nullptr) { //hide the line connection type ui.cbLineType->setDisabled(true); //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYDataReductionCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = dynamic_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2,2,2,2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 2, 1, 3); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 2, 1, 3); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 2, 1, 3); for (int i=0; i < NSL_GEOM_LINESIM_TYPE_COUNT; ++i) uiGeneralTab.cbType->addItem(i18n(nsl_geom_linesim_type_name[i])); uiGeneralTab.cbType->setItemData(nsl_geom_linesim_type_visvalingam_whyatt, i18n("This method is much slower than any other"), Qt::ToolTipRole); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int)) ); connect( uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged()) ); connect( uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged()) ); connect( uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged()) ); connect( uiGeneralTab.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged()) ); connect( uiGeneralTab.chkAuto, SIGNAL(clicked(bool)), this, SLOT(autoToleranceChanged()) ); connect( uiGeneralTab.sbTolerance, SIGNAL(valueChanged(double)), this, SLOT(toleranceChanged()) ); connect( uiGeneralTab.chkAuto2, SIGNAL(clicked(bool)), this, SLOT(autoTolerance2Changed()) ); connect( uiGeneralTab.sbTolerance2, SIGNAL(valueChanged(double)), this, SLOT(tolerance2Changed()) ); connect( uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked()) ); connect( cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex)) ); connect( cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex)) ); connect( cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex)) ); } void XYDataReductionCurveDock::initGeneralTab() { //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_dataReductionCurve = dynamic_cast(m_curve); Q_ASSERT(m_dataReductionCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_dataReductionCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_dataReductionCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_dataReductionCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_dataReductionCurve->yDataColumn()); uiGeneralTab.cbAutoRange->setChecked(m_dataReductionData.autoRange); uiGeneralTab.sbMin->setValue(m_dataReductionData.xRange.first()); uiGeneralTab.sbMax->setValue(m_dataReductionData.xRange.last()); this->autoRangeChanged(); // update list of selectable types xDataColumnChanged(cbXDataColumn->currentModelIndex()); uiGeneralTab.cbType->setCurrentIndex(m_dataReductionData.type); this->typeChanged(); uiGeneralTab.chkAuto->setChecked(m_dataReductionData.autoTolerance); this->autoToleranceChanged(); uiGeneralTab.sbTolerance->setValue(m_dataReductionData.tolerance); this->toleranceChanged(); uiGeneralTab.chkAuto2->setChecked(m_dataReductionData.autoTolerance2); this->autoTolerance2Changed(); uiGeneralTab.sbTolerance2->setValue(m_dataReductionData.tolerance2); this->tolerance2Changed(); this->showDataReductionResult(); //enable the "recalculate"-button if the source data was changed since the last dataReduction uiGeneralTab.pbRecalculate->setEnabled(m_dataReductionCurve->isSourceDataChangedSinceLastRecalc()); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_dataReductionCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_dataReductionCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_dataReductionCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_dataReductionCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_dataReductionCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_dataReductionCurve, SIGNAL(dataReductionDataChanged(XYDataReductionCurve::DataReductionData)), this, SLOT(curveDataReductionDataChanged(XYDataReductionCurve::DataReductionData))); connect(m_dataReductionCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYDataReductionCurveDock::setModel() { QList list; list<<"Folder"<<"Datapicker"<<"Worksheet"<<"CartesianPlot"<<"XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve: m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYDataReductionCurveDock::setCurves(QList list) { m_initializing=true; m_curvesList=list; m_curve=list.first(); m_dataReductionCurve = dynamic_cast(m_curve); Q_ASSERT(m_dataReductionCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_dataReductionData = m_dataReductionCurve->dataReductionData(); initGeneralTab(); initTabs(); m_initializing=false; //hide the "skip gaps" option after the curves were set ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYDataReductionCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYDataReductionCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYDataReductionCurveDock::dataSourceTypeChanged(int index) { XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYDataReductionCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } // // disable deriv orders and accuracies that need more data points // this->updateSettings(dataSourceCurve->xColumn()); if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYDataReductionCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setXDataColumn(column); //TODO: this->updateSettings(column); ? if (column != 0 && uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } updateTolerance(); updateTolerance2(); } void XYDataReductionCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setYDataColumn(column); updateTolerance(); updateTolerance2(); } void XYDataReductionCurveDock::updateTolerance() { const AbstractColumn* xDataColumn = nullptr; const AbstractColumn* yDataColumn = nullptr; if (m_dataReductionCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { xDataColumn = m_dataReductionCurve->xDataColumn(); yDataColumn = m_dataReductionCurve->yDataColumn(); } else { if (m_dataReductionCurve->dataSourceCurve()) { xDataColumn = m_dataReductionCurve->dataSourceCurve()->xColumn(); yDataColumn = m_dataReductionCurve->dataSourceCurve()->yColumn(); } } if(xDataColumn == nullptr || yDataColumn == nullptr) return; //copy all valid data points for calculating tolerance to temporary vectors QVector xdataVector; QVector ydataVector; const double xmin = m_dataReductionData.xRange.first(); const double xmax = m_dataReductionData.xRange.last(); for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(xDataColumn->valueAt(row)) && !std::isnan(yDataColumn->valueAt(row)) && !xDataColumn->isMasked(row) && !yDataColumn->isMasked(row)) { // only when inside given range if (xDataColumn->valueAt(row) >= xmin && xDataColumn->valueAt(row) <= xmax) { xdataVector.append(xDataColumn->valueAt(row)); ydataVector.append(yDataColumn->valueAt(row)); } } } if(xdataVector.size() > 1) uiGeneralTab.cbType->setEnabled(true); else { uiGeneralTab.cbType->setEnabled(false); return; } #ifndef NDEBUG qDebug()<<"automatic tolerance:"; qDebug()<<"clip_diag_perpoint ="<setDecimals(6); uiGeneralTab.sbTolerance->setMinimum(0); uiGeneralTab.sbTolerance->setSingleStep(0.01); uiGeneralTab.lOption2->hide(); uiGeneralTab.chkAuto2->hide(); uiGeneralTab.sbTolerance2->hide(); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); break; case nsl_geom_linesim_type_douglas_peucker_variant: uiGeneralTab.lOption->setText(i18n("Number of points")); uiGeneralTab.sbTolerance->setDecimals(0); uiGeneralTab.sbTolerance->setMinimum(2); uiGeneralTab.sbTolerance->setSingleStep(1); uiGeneralTab.lOption2->hide(); uiGeneralTab.chkAuto2->hide(); uiGeneralTab.sbTolerance2->hide(); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); break; case nsl_geom_linesim_type_nthpoint: uiGeneralTab.lOption->setText(i18n("Step size")); uiGeneralTab.sbTolerance->setValue(10); uiGeneralTab.sbTolerance->setDecimals(0); uiGeneralTab.sbTolerance->setMinimum(1); uiGeneralTab.sbTolerance->setSingleStep(1); uiGeneralTab.lOption2->hide(); uiGeneralTab.chkAuto2->hide(); uiGeneralTab.sbTolerance2->hide(); break; case nsl_geom_linesim_type_perpdist: // repeat option uiGeneralTab.lOption->setText(i18n("Tolerance (distance)")); uiGeneralTab.sbTolerance->setDecimals(6); uiGeneralTab.sbTolerance->setMinimum(0); uiGeneralTab.sbTolerance->setSingleStep(0.01); uiGeneralTab.sbTolerance2->show(); uiGeneralTab.lOption2->show(); uiGeneralTab.chkAuto2->show(); uiGeneralTab.lOption2->setText(i18n("Repeats")); uiGeneralTab.sbTolerance2->setDecimals(0); uiGeneralTab.sbTolerance2->setMinimum(1); uiGeneralTab.sbTolerance2->setSingleStep(1); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); if (uiGeneralTab.chkAuto2->isChecked()) updateTolerance2(); break; case nsl_geom_linesim_type_visvalingam_whyatt: uiGeneralTab.lOption->setText(i18n("Tolerance (area)")); uiGeneralTab.sbTolerance->setDecimals(6); uiGeneralTab.sbTolerance->setMinimum(0); uiGeneralTab.sbTolerance->setSingleStep(0.01); uiGeneralTab.lOption2->hide(); uiGeneralTab.chkAuto2->hide(); uiGeneralTab.sbTolerance2->hide(); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); break; case nsl_geom_linesim_type_opheim: // min/max tol options uiGeneralTab.lOption->setText(i18n(" Min. Tolerance")); uiGeneralTab.sbTolerance->setDecimals(6); uiGeneralTab.sbTolerance->setMinimum(0); uiGeneralTab.sbTolerance->setSingleStep(0.01); uiGeneralTab.lOption2->setText(i18n("Max. Tolerance")); uiGeneralTab.lOption2->show(); uiGeneralTab.chkAuto2->show(); uiGeneralTab.sbTolerance2->show(); uiGeneralTab.sbTolerance2->setDecimals(6); uiGeneralTab.sbTolerance2->setMinimum(0); uiGeneralTab.sbTolerance2->setSingleStep(0.01); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); if (uiGeneralTab.chkAuto2->isChecked()) updateTolerance2(); break; case nsl_geom_linesim_type_lang: // distance/region uiGeneralTab.lOption->setText(i18n("Tolerance (distance)")); uiGeneralTab.sbTolerance->setDecimals(6); uiGeneralTab.sbTolerance->setMinimum(0); uiGeneralTab.sbTolerance->setSingleStep(0.01); uiGeneralTab.lOption2->setText(i18n("Search region")); uiGeneralTab.lOption2->show(); uiGeneralTab.chkAuto2->show(); uiGeneralTab.sbTolerance2->show(); uiGeneralTab.sbTolerance2->setDecimals(0); uiGeneralTab.sbTolerance2->setMinimum(1); uiGeneralTab.sbTolerance2->setSingleStep(1); if (uiGeneralTab.chkAuto->isChecked()) updateTolerance(); if (uiGeneralTab.chkAuto2->isChecked()) updateTolerance2(); break; } uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDataReductionCurveDock::autoToleranceChanged() { bool autoTolerance = (bool)uiGeneralTab.chkAuto->isChecked(); m_dataReductionData.autoTolerance = autoTolerance; if (autoTolerance) { uiGeneralTab.sbTolerance->setEnabled(false); updateTolerance(); } else uiGeneralTab.sbTolerance->setEnabled(true); } void XYDataReductionCurveDock::toleranceChanged() { m_dataReductionData.tolerance = uiGeneralTab.sbTolerance->value(); uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDataReductionCurveDock::autoTolerance2Changed() { bool autoTolerance2 = (bool)uiGeneralTab.chkAuto2->isChecked(); m_dataReductionData.autoTolerance2 = autoTolerance2; if (autoTolerance2) { uiGeneralTab.sbTolerance2->setEnabled(false); updateTolerance2(); } else uiGeneralTab.sbTolerance2->setEnabled(true); } void XYDataReductionCurveDock::tolerance2Changed() { m_dataReductionData.tolerance2 = uiGeneralTab.sbTolerance2->value(); uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDataReductionCurveDock::recalculateClicked() { //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setMinimum(0); progressBar->setMaximum(100); connect(m_curve, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataReductionData(m_dataReductionData); QApplication::restoreOverrideCursor(); statusBar->removeWidget(progressBar); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Data reduction status: ") + m_dataReductionCurve->dataReductionResult().status); } void XYDataReductionCurveDock::enableRecalculate() const { if (m_initializing) return; //no dataReductioning possible without the x- and y-data bool hasSourceData = false; if (m_dataReductionCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX!=0 && aspectY!=0); } else { hasSourceData = (m_dataReductionCurve->dataSourceCurve() != NULL); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the result and details of the dataReduction */ void XYDataReductionCurveDock::showDataReductionResult() { const XYDataReductionCurve::DataReductionResult& dataReductionResult = m_dataReductionCurve->dataReductionResult(); if (!dataReductionResult.available) { uiGeneralTab.teResult->clear(); return; } QString str = i18n("status:") + ' ' + dataReductionResult.status + "
"; if (!dataReductionResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } if (dataReductionResult.elapsedTime>1000) str += i18n("calculation time: %1 s").arg(QString::number(dataReductionResult.elapsedTime/1000)) + "
"; else str += i18n("calculation time: %1 ms").arg(QString::number(dataReductionResult.elapsedTime)) + "
"; str += "
"; str += i18n("number of points: %1").arg(QString::number(dataReductionResult.npoints)) + "
"; str += i18n("positional squared error: %1").arg(QString::number(dataReductionResult.posError)) + "
"; str += i18n("area error: %1").arg(QString::number(dataReductionResult.areaError)) + "
"; uiGeneralTab.teResult->setText(str); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYDataReductionCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYDataReductionCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYDataReductionCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYDataReductionCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYDataReductionCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYDataReductionCurveDock::curveDataReductionDataChanged(const XYDataReductionCurve::DataReductionData& data) { m_initializing = true; m_dataReductionData = data; //uiGeneralTab.cbType->setCurrentIndex(m_dataReductionData.type); //this->typeChanged(); this->showDataReductionResult(); m_initializing = false; } void XYDataReductionCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp index 079af304a..cb5fc0793 100644 --- a/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYDifferentiationCurveDock.cpp @@ -1,601 +1,600 @@ /*************************************************************************** File : XYDifferentiationCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) Description : widget for editing properties of differentiation curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYDifferentiationCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include extern "C" { #include "backend/nsl/nsl_diff.h" } -#include // isnan /*! \class XYDifferentiationCurveDock \brief Provides a widget for editing the properties of the XYDifferentiationCurves (2D-curves defined by a differentiation) currently selected in the project explorer. If more than one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYDifferentiationCurveDock::XYDifferentiationCurveDock(QWidget* parent) : XYCurveDock(parent), cbDataSourceCurve(nullptr), cbXDataColumn(nullptr), cbYDataColumn(nullptr), m_differentiationCurve(nullptr) { //hide the line connection type ui.cbLineType->setDisabled(true); //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYDifferentiationCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = dynamic_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2,2,2,2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 2, 1, 3); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 2, 1, 3); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 2, 1, 3); for (int i=0; i < NSL_DIFF_DERIV_ORDER_COUNT; ++i) uiGeneralTab.cbDerivOrder->addItem(i18n(nsl_diff_deriv_order_name[i])); uiGeneralTab.pbRecalculate->setIcon( QIcon::fromTheme("run-build") ); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int)) ); connect( uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged()) ); connect( uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged()) ); connect( uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged()) ); connect( uiGeneralTab.cbDerivOrder, SIGNAL(currentIndexChanged(int)), this, SLOT(derivOrderChanged()) ); connect( uiGeneralTab.sbAccOrder, SIGNAL(valueChanged(int)), this, SLOT(accOrderChanged()) ); connect( uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked()) ); connect( cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex)) ); connect( cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex)) ); connect( cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex)) ); } void XYDifferentiationCurveDock::initGeneralTab() { //if there are more than one curve in the list, disable the tab "general" if (m_curvesList.size()==1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); }else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_differentiationCurve = dynamic_cast(m_curve); Q_ASSERT(m_differentiationCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_differentiationCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_differentiationCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_differentiationCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_differentiationCurve->yDataColumn()); uiGeneralTab.cbAutoRange->setChecked(m_differentiationData.autoRange); uiGeneralTab.sbMin->setValue(m_differentiationData.xRange.first()); uiGeneralTab.sbMax->setValue(m_differentiationData.xRange.last()); this->autoRangeChanged(); // update list of selectable types xDataColumnChanged(cbXDataColumn->currentModelIndex()); uiGeneralTab.cbDerivOrder->setCurrentIndex(m_differentiationData.derivOrder); this->derivOrderChanged(); uiGeneralTab.sbAccOrder->setValue(m_differentiationData.accOrder); this->accOrderChanged(); this->showDifferentiationResult(); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_differentiationCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_differentiationCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_differentiationCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_differentiationCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_differentiationCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_differentiationCurve, SIGNAL(differentiationDataChanged(XYDifferentiationCurve::DifferentiationData)), this, SLOT(curveDifferentiationDataChanged(XYDifferentiationCurve::DifferentiationData))); connect(m_differentiationCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYDifferentiationCurveDock::setModel() { QList list; list<<"Folder"<<"Datapicker"<<"Worksheet"<<"CartesianPlot"<<"XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; foreach (XYCurve* curve, m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYDifferentiationCurveDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_differentiationCurve = dynamic_cast(m_curve); Q_ASSERT(m_differentiationCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_differentiationData = m_differentiationCurve->differentiationData(); initGeneralTab(); initTabs(); m_initializing = false; //hide the "skip gaps" option after the curves were set ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYDifferentiationCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYDifferentiationCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYDifferentiationCurveDock::dataSourceTypeChanged(int index) { XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYDifferentiationCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } // disable deriv orders and accuracies that need more data points this->updateSettings(dataSourceCurve->xColumn()); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYDifferentiationCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } // disable deriv orders and accuracies that need more data points this->updateSettings(column); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setXDataColumn(column); } void XYDifferentiationCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setYDataColumn(column); } /*! * disable deriv orders and accuracies that need more data points */ void XYDifferentiationCurveDock::updateSettings(const AbstractColumn* column) { if (!column) return; if (uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } size_t n=0; for (int row=0; row < column->rowCount(); ++row) if (!std::isnan(column->valueAt(row)) && !column->isMasked(row)) n++; const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbDerivOrder->model()); QStandardItem* item = model->item(nsl_diff_deriv_order_first); if (n < 3) item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); else { item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); if (n < 5) uiGeneralTab.sbAccOrder->setMinimum(2); } item = model->item(nsl_diff_deriv_order_second); if (n < 3) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); if (uiGeneralTab.cbDerivOrder->currentIndex() == nsl_diff_deriv_order_second) uiGeneralTab.cbDerivOrder->setCurrentIndex(nsl_diff_deriv_order_first); } else { item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); if (n < 4) uiGeneralTab.sbAccOrder->setMinimum(1); else if (n < 5) uiGeneralTab.sbAccOrder->setMinimum(2); } item = model->item(nsl_diff_deriv_order_third); if (n < 5) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); if (uiGeneralTab.cbDerivOrder->currentIndex() == nsl_diff_deriv_order_third) uiGeneralTab.cbDerivOrder->setCurrentIndex(nsl_diff_deriv_order_first); } else item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); item = model->item(nsl_diff_deriv_order_fourth); if (n < 5) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); if (uiGeneralTab.cbDerivOrder->currentIndex() == nsl_diff_deriv_order_fourth) uiGeneralTab.cbDerivOrder->setCurrentIndex(nsl_diff_deriv_order_first); } else { item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); if (n < 7) uiGeneralTab.sbAccOrder->setMinimum(1); } item = model->item(nsl_diff_deriv_order_fifth); if (n < 7) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); if (uiGeneralTab.cbDerivOrder->currentIndex() == nsl_diff_deriv_order_fifth) uiGeneralTab.cbDerivOrder->setCurrentIndex(nsl_diff_deriv_order_first); } else item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); item = model->item(nsl_diff_deriv_order_sixth); if (n < 7) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); if (uiGeneralTab.cbDerivOrder->currentIndex() == nsl_diff_deriv_order_sixth) uiGeneralTab.cbDerivOrder->setCurrentIndex(nsl_diff_deriv_order_first); } else item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); } void XYDifferentiationCurveDock::autoRangeChanged() { bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); m_differentiationData.autoRange = autoRange; if (autoRange) { uiGeneralTab.lMin->setEnabled(false); uiGeneralTab.sbMin->setEnabled(false); uiGeneralTab.lMax->setEnabled(false); uiGeneralTab.sbMax->setEnabled(false); const AbstractColumn* xDataColumn = 0; if (m_differentiationCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) xDataColumn = m_differentiationCurve->xDataColumn(); else { if (m_differentiationCurve->dataSourceCurve()) xDataColumn = m_differentiationCurve->dataSourceCurve()->xColumn(); } if (xDataColumn) { uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); } } else { uiGeneralTab.lMin->setEnabled(true); uiGeneralTab.sbMin->setEnabled(true); uiGeneralTab.lMax->setEnabled(true); uiGeneralTab.sbMax->setEnabled(true); } } void XYDifferentiationCurveDock::xRangeMinChanged() { double xMin = uiGeneralTab.sbMin->value(); m_differentiationData.xRange.first() = xMin; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDifferentiationCurveDock::xRangeMaxChanged() { double xMax = uiGeneralTab.sbMax->value(); m_differentiationData.xRange.last() = xMax; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDifferentiationCurveDock::derivOrderChanged() { const nsl_diff_deriv_order_type derivOrder = (nsl_diff_deriv_order_type)uiGeneralTab.cbDerivOrder->currentIndex(); m_differentiationData.derivOrder = derivOrder; // update avail. accuracies switch (derivOrder) { case nsl_diff_deriv_order_first: uiGeneralTab.sbAccOrder->setMinimum(2); uiGeneralTab.sbAccOrder->setMaximum(4); uiGeneralTab.sbAccOrder->setSingleStep(2); uiGeneralTab.sbAccOrder->setValue(4); break; case nsl_diff_deriv_order_second: uiGeneralTab.sbAccOrder->setMinimum(1); uiGeneralTab.sbAccOrder->setMaximum(3); uiGeneralTab.sbAccOrder->setSingleStep(1); uiGeneralTab.sbAccOrder->setValue(3); break; case nsl_diff_deriv_order_third: uiGeneralTab.sbAccOrder->setMinimum(2); uiGeneralTab.sbAccOrder->setMaximum(2); break; case nsl_diff_deriv_order_fourth: uiGeneralTab.sbAccOrder->setMinimum(1); uiGeneralTab.sbAccOrder->setMaximum(3); uiGeneralTab.sbAccOrder->setSingleStep(2); uiGeneralTab.sbAccOrder->setValue(3); break; case nsl_diff_deriv_order_fifth: uiGeneralTab.sbAccOrder->setMinimum(2); uiGeneralTab.sbAccOrder->setMaximum(2); break; case nsl_diff_deriv_order_sixth: uiGeneralTab.sbAccOrder->setMinimum(1); uiGeneralTab.sbAccOrder->setMaximum(1); break; } uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDifferentiationCurveDock::accOrderChanged() { int accOrder = (int)uiGeneralTab.sbAccOrder->value(); m_differentiationData.accOrder = accOrder; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYDifferentiationCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); foreach(XYCurve* curve, m_curvesList) if (curve != 0) dynamic_cast(curve)->setDifferentiationData(m_differentiationData); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Differentiation status: ") + m_differentiationCurve->differentiationResult().status); QApplication::restoreOverrideCursor(); } void XYDifferentiationCurveDock::enableRecalculate() const { if (m_initializing) return; //no differentiation possible without the x- and y-data bool hasSourceData = false; if (m_differentiationCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX!=0 && aspectY!=0); } else { hasSourceData = (m_differentiationCurve->dataSourceCurve() != NULL); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the result and details of the differentiation */ void XYDifferentiationCurveDock::showDifferentiationResult() { const XYDifferentiationCurve::DifferentiationResult& differentiationResult = m_differentiationCurve->differentiationResult(); if (!differentiationResult.available) { uiGeneralTab.teResult->clear(); return; } QString str = i18n("status:") + ' ' + differentiationResult.status + "
"; if (!differentiationResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } if (differentiationResult.elapsedTime>1000) str += i18n("calculation time: %1 s").arg(QString::number(differentiationResult.elapsedTime/1000)) + "
"; else str += i18n("calculation time: %1 ms").arg(QString::number(differentiationResult.elapsedTime)) + "
"; str += "

"; uiGeneralTab.teResult->setText(str); //enable the "recalculate"-button if the source data was changed since the last differentiation uiGeneralTab.pbRecalculate->setEnabled(m_differentiationCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*** SLOTs for changes triggered in XYDifferentiationCurve *** //************************************************************* //General-Tab void XYDifferentiationCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYDifferentiationCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYDifferentiationCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYDifferentiationCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYDifferentiationCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYDifferentiationCurveDock::curveDifferentiationDataChanged(const XYDifferentiationCurve::DifferentiationData& data) { m_initializing = true; m_differentiationData = data; uiGeneralTab.cbDerivOrder->setCurrentIndex(m_differentiationData.derivOrder); this->derivOrderChanged(); uiGeneralTab.sbAccOrder->setValue(m_differentiationData.accOrder); this->accOrderChanged(); this->showDifferentiationResult(); m_initializing = false; } void XYDifferentiationCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp index b0ab237c5..31e3c2df5 100644 --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp @@ -1,1146 +1,1140 @@ /*************************************************************************** File : XYFitCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of fit curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYFitCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include "kdefrontend/widgets/FitOptionsWidget.h" #include "kdefrontend/widgets/FitParametersWidget.h" #include #include #include #include #include -#include // DBL_MAX -#include // fabs() - extern "C" { #include "backend/nsl/nsl_sf_stats.h" } /*! \class XYFitCurveDock \brief Provides a widget for editing the properties of the XYFitCurves (2D-curves defined by a fit model) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYFitCurveDock::XYFitCurveDock(QWidget* parent) : XYCurveDock(parent), cbDataSourceCurve(0), cbXDataColumn(0), cbYDataColumn(0), cbXErrorColumn(0), cbYErrorColumn(0), m_fitCurve(0) { //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYFitCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = qobject_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2, 2, 2, 2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 6, 4, 1, 4); cbXDataColumn = new TreeViewComboBox(generalTab); - gridLayout->addWidget(cbXDataColumn, 7, 4, 1, 1); + gridLayout->addWidget(cbXDataColumn, 7, 4, 1, 4); cbXErrorColumn = new TreeViewComboBox(generalTab); - gridLayout->addWidget(cbXErrorColumn, 7, 5, 1, 4); + cbXErrorColumn->setEnabled(false); + gridLayout->addWidget(cbXErrorColumn, 10, 6, 1, 2); cbYDataColumn = new TreeViewComboBox(generalTab); - gridLayout->addWidget(cbYDataColumn, 8, 4, 1, 1); + gridLayout->addWidget(cbYDataColumn, 8, 4, 1, 4); cbYErrorColumn = new TreeViewComboBox(generalTab); - gridLayout->addWidget(cbYErrorColumn, 8, 5, 1, 4); + cbYErrorColumn->setEnabled(false); + gridLayout->addWidget(cbYErrorColumn, 11, 6, 1, 2); - //Weight + //Weights + for(int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) + uiGeneralTab.cbXWeight->addItem(nsl_fit_weight_type_name[i]); for(int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) - uiGeneralTab.cbWeight->addItem(nsl_fit_weight_type_name[i]); - uiGeneralTab.cbWeight->setCurrentIndex(nsl_fit_weight_instrumental); + uiGeneralTab.cbYWeight->addItem(nsl_fit_weight_type_name[i]); + uiGeneralTab.cbXWeight->setCurrentIndex(nsl_fit_weight_no); + uiGeneralTab.cbYWeight->setCurrentIndex(nsl_fit_weight_no); + + // disable nsl_fit_weight_statistical_fit and nsl_fit_weight_relative_fit for xWeight + const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbXWeight->model()); + QStandardItem* item = model->item(nsl_fit_weight_statistical_fit); + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); + item = model->item(nsl_fit_weight_relative_fit); + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); for(int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++) uiGeneralTab.cbCategory->addItem(nsl_fit_model_category_name[i]); //show the fit-model category for the currently selected default (first) fit-model category categoryChanged(uiGeneralTab.cbCategory->currentIndex()); uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); uiGeneralTab.lFuncPic->setAutoFillBackground(true); uiGeneralTab.lFuncPic->setPalette(p); uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme("labplot-format-text-symbol")); uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme("preferences-desktop-font")); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); uiGeneralTab.twLog->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers); // context menus uiGeneralTab.twParameters->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twGoodness->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twLog->setContextMenuPolicy(Qt::CustomContextMenu); connect(uiGeneralTab.twParameters, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(resultParametersContextMenuRequest(const QPoint &)) ); connect(uiGeneralTab.twGoodness, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(resultGoodnessContextMenuRequest(const QPoint &)) ); connect(uiGeneralTab.twLog, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(resultLogContextMenuRequest(const QPoint &)) ); uiGeneralTab.twLog->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->item(0, 1)->setText(QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(1, 1)->setText(i18n("reduced") + " " + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + " (" + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + "/dof)"); uiGeneralTab.twGoodness->item(3, 1)->setText("R" + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(4, 1)->setText("R" + QString::fromUtf8("\u0304") + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(5, 0)->setText(QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ' ' + i18n("test")); uiGeneralTab.twGoodness->item(5, 1)->setText("P > " + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2")); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect(uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged())); connect(uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged())); connect(uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int))); - connect(uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged())); - connect(uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged())); - connect(uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged())); - connect(uiGeneralTab.cbWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(weightChanged(int))); + connect(uiGeneralTab.cbXWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(xWeightChanged(int))); + connect(uiGeneralTab.cbYWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(yWeightChanged(int))); connect(uiGeneralTab.cbCategory, SIGNAL(currentIndexChanged(int)), this, SLOT(categoryChanged(int))); connect(uiGeneralTab.cbModel, SIGNAL(currentIndexChanged(int)), this, SLOT(modelTypeChanged(int))); connect(uiGeneralTab.sbDegree, SIGNAL(valueChanged(int)), this, SLOT(updateModelEquation())); connect(uiGeneralTab.teEquation, SIGNAL(expressionChanged()), this, SLOT(enableRecalculate())); connect(uiGeneralTab.tbConstants, SIGNAL(clicked()), this, SLOT(showConstants())); connect(uiGeneralTab.tbFunctions, SIGNAL(clicked()), this, SLOT(showFunctions())); connect(uiGeneralTab.pbParameters, SIGNAL(clicked()), this, SLOT(showParameters())); connect(uiGeneralTab.pbOptions, SIGNAL(clicked()), this, SLOT(showOptions())); connect(uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked())); connect(cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex))); connect(cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex))); connect(cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex))); connect(cbXErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorColumnChanged(QModelIndex))); connect(cbYErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorColumnChanged(QModelIndex))); } void XYFitCurveDock::initGeneralTab() { //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_fitCurve = dynamic_cast(m_curve); Q_ASSERT(m_fitCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_fitCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_fitCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_fitCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_fitCurve->yDataColumn()); XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, m_fitCurve->xErrorColumn()); XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, m_fitCurve->yErrorColumn()); - uiGeneralTab.cbAutoRange->setChecked(m_fitData.autoRange); - uiGeneralTab.sbMin->setValue(m_fitData.xRange.first()); - uiGeneralTab.sbMax->setValue(m_fitData.xRange.last()); - this->autoRangeChanged(); unsigned int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1); else uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory); m_fitData.modelType = tmpModelType; if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); - uiGeneralTab.cbWeight->setCurrentIndex(m_fitData.weightsType); + uiGeneralTab.cbXWeight->setCurrentIndex(m_fitData.xWeightsType); + uiGeneralTab.cbYWeight->setCurrentIndex(m_fitData.yWeightsType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); updateModelEquation(); this->showFitResult(); uiGeneralTab.chkVisible->setChecked(m_curve->isVisible()); //Slots connect(m_fitCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_fitCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_fitCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_fitCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(xErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(fitDataChanged(XYFitCurve::FitData)), this, SLOT(curveFitDataChanged(XYFitCurve::FitData))); connect(m_fitCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYFitCurveDock::setModel() { QList list; list << "Folder" << "Datapicker" << "Worksheet" << "CartesianPlot" << "XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve: m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list << "Folder" << "Workbook" << "Spreadsheet" << "FileDataSource" << "Column" << "CantorWorksheet" << "Datapicker"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXErrorColumn->setTopLevelClasses(list); cbYErrorColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); cbXErrorColumn->setModel(m_aspectTreeModel); cbYErrorColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYFitCurveDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_fitCurve = dynamic_cast(m_curve); Q_ASSERT(m_fitCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_fitData = m_fitCurve->fitData(); initGeneralTab(); initTabs(); m_initializing = false; } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYFitCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYFitCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYFitCurveDock::dataSourceTypeChanged(int index) { const XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); - cbXErrorColumn->show(); - cbYErrorColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); - cbXErrorColumn->hide(); - cbYErrorColumn->hide(); } if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } - this->updateSettings(dataSourceCurve->xColumn()); - if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } - this->updateSettings(column); - for (auto* curve: m_curvesList) dynamic_cast(curve)->setXDataColumn(column); } -void XYFitCurveDock::updateSettings(const AbstractColumn* column) { - if (!column) - return; - - if (uiGeneralTab.cbAutoRange->isChecked()) { - uiGeneralTab.sbMin->setValue(column->minimum()); - uiGeneralTab.sbMax->setValue(column->maximum()); - } -} - void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setYDataColumn(column); } -void XYFitCurveDock::autoRangeChanged() { - const bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); - m_fitData.autoRange = autoRange; - - if (autoRange) { - uiGeneralTab.sbMin->setEnabled(false); - uiGeneralTab.lXRange2->setEnabled(false); - uiGeneralTab.sbMax->setEnabled(false); - - const AbstractColumn* xDataColumn = 0; - if (m_fitCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) - xDataColumn = m_fitCurve->xDataColumn(); - else { - if (m_fitCurve->dataSourceCurve()) - xDataColumn = m_fitCurve->dataSourceCurve()->xColumn(); - } - - if (xDataColumn) { - uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); - uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); - } - } else { - uiGeneralTab.sbMin->setEnabled(true); - uiGeneralTab.lXRange2->setEnabled(true); - uiGeneralTab.sbMax->setEnabled(true); - } - -} -void XYFitCurveDock::xRangeMinChanged() { - const double xMin = uiGeneralTab.sbMin->value(); - - m_fitData.xRange.first() = xMin; - uiGeneralTab.pbRecalculate->setEnabled(true); -} - -void XYFitCurveDock::xRangeMaxChanged() { - const double xMax = uiGeneralTab.sbMax->value(); - - m_fitData.xRange.last() = xMax; - uiGeneralTab.pbRecalculate->setEnabled(true); -} - void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setXErrorColumn(column); } void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setYErrorColumn(column); +} - //y-error column was selected - in case no weighting is selected yet, automatically select instrumental weighting - if ( uiGeneralTab.cbWeight->currentIndex() == 0 ) - uiGeneralTab.cbWeight->setCurrentIndex((int)nsl_fit_weight_instrumental); +void XYFitCurveDock::xWeightChanged(int index) { + DEBUG("xWeightChanged() weight = " << nsl_fit_weight_type_name[index]); + + m_fitData.xWeightsType = (nsl_fit_weight_type)index; + + // enable/disable weight column + switch ((nsl_fit_weight_type)index) { + case nsl_fit_weight_no: + case nsl_fit_weight_statistical: + case nsl_fit_weight_statistical_fit: + case nsl_fit_weight_relative: + case nsl_fit_weight_relative_fit: + cbXErrorColumn->setEnabled(false); + uiGeneralTab.lXErrorCol->setEnabled(false); + break; + case nsl_fit_weight_instrumental: + case nsl_fit_weight_direct: + case nsl_fit_weight_inverse: + cbXErrorColumn->setEnabled(true); + uiGeneralTab.lXErrorCol->setEnabled(true); + break; + } + enableRecalculate(); } -void XYFitCurveDock::weightChanged(int index) { - DEBUG("weightChanged() weight = " << nsl_fit_weight_type_name[index]); +void XYFitCurveDock::yWeightChanged(int index) { + DEBUG("yWeightChanged() weight = " << nsl_fit_weight_type_name[index]); + + m_fitData.yWeightsType = (nsl_fit_weight_type)index; - m_fitData.weightsType = (nsl_fit_weight_type)index; + // enable/disable weight column + switch ((nsl_fit_weight_type)index) { + case nsl_fit_weight_no: + case nsl_fit_weight_statistical: + case nsl_fit_weight_statistical_fit: + case nsl_fit_weight_relative: + case nsl_fit_weight_relative_fit: + cbYErrorColumn->setEnabled(false); + uiGeneralTab.lYErrorCol->setEnabled(false); + break; + case nsl_fit_weight_instrumental: + case nsl_fit_weight_direct: + case nsl_fit_weight_inverse: + cbYErrorColumn->setEnabled(true); + uiGeneralTab.lYErrorCol->setEnabled(true); + break; + } enableRecalculate(); } /*! * called when the fit model category (basic functions, peak functions etc.) was changed. * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged() * to update the model type dependent widgets in the general-tab. */ void XYFitCurveDock::categoryChanged(int index) { DEBUG("categoryChanged() category = \"" << nsl_fit_model_category_name[index] << "\""); if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1) m_fitData.modelCategory = nsl_fit_model_custom; else m_fitData.modelCategory = (nsl_fit_model_category)index; m_initializing = true; uiGeneralTab.cbModel->clear(); uiGeneralTab.cbModel->show(); uiGeneralTab.lModel->show(); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: for(int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_basic_name[i]); break; case nsl_fit_model_peak: for(int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_peak_name[i]); break; case nsl_fit_model_growth: for(int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_growth_name[i]); break; case nsl_fit_model_distribution: { for(int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_sf_stats_distribution_name[i]); // not-used items are disabled here const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbModel->model()); for(int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) { // unused distributions if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) { QStandardItem* item = model->item(i); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } } break; } case nsl_fit_model_custom: uiGeneralTab.cbModel->addItem(i18n("Custom")); uiGeneralTab.cbModel->hide(); uiGeneralTab.lModel->hide(); } //show the fit-model for the currently selected default (first) fit-model m_fitData.modelType = 0; uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); modelTypeChanged(m_fitData.modelType); m_initializing = false; + enableRecalculate(); } /*! * called when the fit model type (polynomial, power, etc.) was changed. * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap. */ void XYFitCurveDock::modelTypeChanged(int index) { DEBUG("modelTypeChanged() type = " << index << ", initializing = " << m_initializing); // leave if there is no selection if(index == -1) return; unsigned int type = 0; bool custom = false; if (m_fitData.modelCategory == nsl_fit_model_custom) custom = true; else type = (unsigned int)index; m_fitData.modelType = type; uiGeneralTab.teEquation->setReadOnly(!custom); uiGeneralTab.tbFunctions->setVisible(custom); uiGeneralTab.tbConstants->setVisible(custom); // default settings uiGeneralTab.lDegree->setText(i18n("Degree")); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: switch (type) { case nsl_fit_model_polynomial: case nsl_fit_model_fourier: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(10); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_power: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(2); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_exponential: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(10); uiGeneralTab.sbDegree->setValue(1); break; default: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } break; case nsl_fit_model_peak: // all models support multiple peaks uiGeneralTab.lDegree->setText(i18n("Number of peaks")); uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(9); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_growth: case nsl_fit_model_distribution: case nsl_fit_model_custom: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } this->updateModelEquation(); } /*! * Show the preview pixmap of the fit model expression for the current model category and type. * Called when the model type or the degree of the model were changed. */ void XYFitCurveDock::updateModelEquation() { DEBUG("updateModelEquation() category = " << m_fitData.modelCategory << ", type = " << m_fitData.modelType); //this function can also be called when the value for the degree was changed -> update the fit data structure int degree = uiGeneralTab.sbDegree->value(); m_fitData.degree = degree; XYFitCurve::initFitData(m_fitData); // variables/parameter that are known QStringList vars = {"x"}; vars << m_fitData.paramNames; uiGeneralTab.teEquation->setVariables(vars); // set formula picture uiGeneralTab.lEquation->setText(QLatin1String("f(x) =")); QString file; switch (m_fitData.modelCategory) { case nsl_fit_model_basic: { // formula pic depends on degree QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = "4"; if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2) numSuffix = "2"; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix + ".jpg"); break; } case nsl_fit_model_peak: { // formula pic depends on number of peaks QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = "4"; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix + ".jpg"); break; } case nsl_fit_model_growth: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_growth_pic_name[m_fitData.modelType]) + ".jpg"); break; case nsl_fit_model_distribution: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) + ".jpg"); // change label if (m_fitData.modelType == nsl_sf_stats_poisson) uiGeneralTab.lEquation->setText(QLatin1String("f(k)/A =")); else uiGeneralTab.lEquation->setText(QLatin1String("f(x)/A =")); break; case nsl_fit_model_custom: uiGeneralTab.teEquation->show(); uiGeneralTab.teEquation->clear(); uiGeneralTab.teEquation->insertPlainText(m_fitData.model); uiGeneralTab.lFuncPic->hide(); } if (m_fitData.modelCategory != nsl_fit_model_custom) { uiGeneralTab.lFuncPic->setPixmap(file); uiGeneralTab.lFuncPic->show(); uiGeneralTab.teEquation->hide(); } } void XYFitCurveDock::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, SIGNAL(constantSelected(QString)), this, SLOT(insertConstant(QString))); connect(&constants, SIGNAL(constantSelected(QString)), &menu, SLOT(close())); connect(&constants, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos)); } void XYFitCurveDock::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, SIGNAL(functionSelected(QString)), this, SLOT(insertFunction(QString))); connect(&functions, SIGNAL(functionSelected(QString)), &menu, SLOT(close())); connect(&functions, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos)); } void XYFitCurveDock::updateParameterList() { // use current model function m_fitData.model = uiGeneralTab.teEquation->toPlainText(); ExpressionParser* parser = ExpressionParser::getInstance(); QStringList vars; // variables that are known vars << "x"; //TODO: others? m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars); // if number of parameter changed bool moreParameter = false; if (m_fitData.paramNames.size() > m_fitData.paramStartValues.size()) moreParameter = true; if (m_fitData.paramNames.size() != m_fitData.paramStartValues.size()) { m_fitData.paramStartValues.resize(m_fitData.paramNames.size()); m_fitData.paramFixed.resize(m_fitData.paramNames.size()); m_fitData.paramLowerLimits.resize(m_fitData.paramNames.size()); m_fitData.paramUpperLimits.resize(m_fitData.paramNames.size()); } if (moreParameter) { for (int i = m_fitData.paramStartValues.size() - 1; i < m_fitData.paramNames.size(); ++i) { m_fitData.paramStartValues[i] = 1.0; m_fitData.paramFixed[i] = false; - m_fitData.paramLowerLimits[i] = -DBL_MAX; - m_fitData.paramUpperLimits[i] = DBL_MAX; + m_fitData.paramLowerLimits[i] = -std::numeric_limits::max(); + m_fitData.paramUpperLimits[i] = std::numeric_limits::max(); } } parametersChanged(); } void XYFitCurveDock::showParameters() { if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); QMenu menu; FitParametersWidget w(&menu, &m_fitData); connect(&w, SIGNAL(finished()), &menu, SLOT(close())); connect(&w, SIGNAL(parametersChanged()), this, SLOT(parametersChanged())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); menu.setMinimumWidth(w.width()); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbParameters->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.pbParameters->mapToGlobal(pos)); } /*! * called when parameter names and/or start values for the custom model were changed */ void XYFitCurveDock::parametersChanged() { //parameter names were (probably) changed -> set the new names in EquationTextEdit uiGeneralTab.teEquation->setVariables(m_fitData.paramNames); enableRecalculate(); } void XYFitCurveDock::showOptions() { QMenu menu; - FitOptionsWidget w(&menu, &m_fitData); + FitOptionsWidget w(&menu, &m_fitData, m_fitCurve); connect(&w, SIGNAL(finished()), &menu, SLOT(close())); connect(&w, SIGNAL(optionsChanged()), this, SLOT(enableRecalculate())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbParameters->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos)); } void XYFitCurveDock::insertFunction(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str + "(x)"); } void XYFitCurveDock::insertConstant(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str); } void XYFitCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_fitData.degree = uiGeneralTab.sbDegree->value(); if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); for (XYCurve* curve: m_curvesList) dynamic_cast(curve)->setFitData(m_fitData); this->showFitResult(); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Fit status: ") + m_fitCurve->fitResult().status); QApplication::restoreOverrideCursor(); } void XYFitCurveDock::enableRecalculate() const { - if (m_initializing) + if (m_initializing || m_fitCurve == nullptr) return; //no fitting possible without the x- and y-data bool hasSourceData = false; if (m_fitCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX != 0 && aspectY != 0); } else { - hasSourceData = (m_fitCurve->dataSourceCurve() != NULL); + hasSourceData = (m_fitCurve->dataSourceCurve() != nullptr); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } void XYFitCurveDock::resultCopySelection() { QTableWidget* tw = nullptr; int currentTab = uiGeneralTab.twResults->currentIndex(); DEBUG("current tab = " << currentTab); if (currentTab == 0) tw = uiGeneralTab.twParameters; else if (currentTab == 1) tw = uiGeneralTab.twGoodness; else if (currentTab == 2) tw = uiGeneralTab.twLog; else return; QTableWidgetSelectionRange range = tw->selectedRanges().first(); QString str; for (int i = 0; i < range.rowCount(); ++i) { if (i > 0) str += "\n"; for (int j = 0; j < range.columnCount(); ++j) { if (j > 0) str += "\t"; str += tw->item(range.topRow() + i, range.leftColumn() + j)->text(); } } str += "\n"; QApplication::clipboard()->setText(str); DEBUG(QApplication::clipboard()->text().toStdString()); } void XYFitCurveDock::resultCopyAll() { const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); int currentTab = uiGeneralTab.twResults->currentIndex(); QString str; if (currentTab == 0) { str = i18n("Parameters:") + "\n"; const int np = fitResult.paramValues.size(); for (int i = 0; i < np; i++) { if (m_fitData.paramFixed.at(i)) str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + "\n"; else { str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + QString::fromUtf8("\u00b1") + QString::number(fitResult.errorValues.at(i)) - + " (" + QString::number(100.*fitResult.errorValues.at(i)/fabs(fitResult.paramValues.at(i)), 'g', 3) + " %)\n"; + + " (" + QString::number(100.*fitResult.errorValues.at(i)/std::abs(fitResult.paramValues.at(i)), 'g', 3) + " %)\n"; const double margin = fitResult.tdist_marginValues.at(i); - str += " (" + i18n("t statistic:") + ' ' + QString::number(fitResult.tdist_tValues.at(i), 'g', 3) + ", " + QString tdistValueString; + if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) + tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); + else + tdistValueString = QString::fromUtf8("\u221e"); + str += " (" + i18n("t statistic:") + ' ' + tdistValueString + ", " + i18n("p value:") + ' ' + QString::number(fitResult.tdist_pValues.at(i), 'g', 3) + ", " + i18n("conf. interval:") + ' '; - if (fabs(fitResult.tdist_tValues.at(i)) < 1.e6) { + if (std::abs(fitResult.tdist_tValues.at(i)) < 1.e6) { str += QString::number(fitResult.paramValues.at(i) - margin) + " .. " + QString::number(fitResult.paramValues.at(i) + margin) + ")\n"; } else { str += i18n("too small"); } } } } else if (currentTab == 1) { str = i18n("Goodness of fit:") + "\n"; str += i18n("sum of squared residuals") + " (" + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + "): " + QString::number(fitResult.sse) + "\n"; if (fitResult.dof != 0) { str += i18n("reduced") + ' ' + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ": " + QString::number(fitResult.rms) + '\n'; str += i18n("root mean square error") + " (RMSE): " + QString::number(fitResult.rsd) + "\n"; str += i18n("coefficient of determination") + " (R" + QString::fromUtf8("\u00b2") + "): " + QString::number(fitResult.rsquare, 'g', 15) + '\n'; str += i18n("adj. coefficient of determination")+ " (R" + QString::fromUtf8("\u0304") + QString::fromUtf8("\u00b2") + "): " + QString::number(fitResult.rsquareAdj, 'g', 15) + "\n\n"; str += i18n("P > ") + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ": " + QString::number(fitResult.chisq_p, 'g', 3) + '\n'; str += i18n("F statistic") + ": " + QString::number(fitResult.fdist_F, 'g', 3) + '\n'; str += i18n("P > F") + ": " + QString::number(fitResult.fdist_p, 'g', 3) + '\n'; } str += i18n("mean absolute error:") + ' ' + QString::number(fitResult.mae) + '\n'; str += i18n("Akaike information criterion:") + ' ' + QString::number(fitResult.aic) + '\n'; str += i18n("Bayesian information criterion:") + ' ' + QString::number(fitResult.bic) + '\n'; } else if (currentTab == 2) { str = i18n("status:") + ' ' + fitResult.status + '\n'; str += i18n("iterations:") + ' ' + QString::number(fitResult.iterations) + '\n'; str += i18n("tolerance:") + ' ' + QString::number(m_fitData.eps) + '\n'; if (fitResult.elapsedTime > 1000) str += i18n("calculation time: %1 s", fitResult.elapsedTime/1000) + '\n'; else str += i18n("calculation time: %1 ms", fitResult.elapsedTime) + '\n'; str += i18n("degrees of freedom:") + ' ' + QString::number(fitResult.dof) + '\n'; str += i18n("number of parameters:") + ' ' + QString::number(fitResult.paramValues.size()) + '\n'; - str += i18n("X range:") + ' ' + QString::number(uiGeneralTab.sbMin->value()) + " .. " + QString::number(uiGeneralTab.sbMax->value()) + '\n'; + str += i18n("X range:") + ' ' + QString::number(m_fitData.xRange.first()) + " .. " + QString::number(m_fitData.xRange.last()) + '\n'; str += i18n("Iterations:") + '\n'; for (const auto &s: m_fitData.paramNamesUtf8) str += s + '\t'; str += QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s: iterations) if (!s.isEmpty()) str += '\n' + s; } QApplication::clipboard()->setText(str); DEBUG(QApplication::clipboard()->text().toStdString()); } void XYFitCurveDock::resultParametersContextMenuRequest(const QPoint &pos) { QMenu *contextMenu = new QMenu; contextMenu->addAction("Copy selection", this, SLOT(resultCopySelection())); contextMenu->addAction("Copy all", this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twParameters->mapToGlobal(pos)); } void XYFitCurveDock::resultGoodnessContextMenuRequest(const QPoint &pos) { QMenu *contextMenu = new QMenu; contextMenu->addAction("Copy selection", this, SLOT(resultCopySelection())); contextMenu->addAction("Copy all", this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twGoodness->mapToGlobal(pos)); } void XYFitCurveDock::resultLogContextMenuRequest(const QPoint &pos) { QMenu *contextMenu = new QMenu; contextMenu->addAction("Copy selection", this, SLOT(resultCopySelection())); contextMenu->addAction("Copy all", this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twLog->mapToGlobal(pos)); } /*! * show the result and details of fit */ void XYFitCurveDock::showFitResult() { DEBUG("XYFitCurveDock::showFitResult()"); //clear the previous result uiGeneralTab.twParameters->setRowCount(0); for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row) uiGeneralTab.twGoodness->item(row, 2)->setText(""); for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row) uiGeneralTab.twLog->item(row, 1)->setText(""); const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); if (!fitResult.available) { DEBUG("fit result not available"); return; } // General uiGeneralTab.twLog->item(0, 1)->setText(fitResult.status); if (!fitResult.valid) { DEBUG("fit result not valid"); return; } uiGeneralTab.twLog->item(1, 1)->setText(QString::number(fitResult.iterations)); uiGeneralTab.twLog->item(2, 1)->setText(QString::number(m_fitData.eps)); if (fitResult.elapsedTime > 1000) uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime/1000) + " s"); else uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime) + " ms"); uiGeneralTab.twLog->item(4, 1)->setText(QString::number(fitResult.dof)); uiGeneralTab.twLog->item(5, 1)->setText(QString::number(fitResult.paramValues.size())); - uiGeneralTab.twLog->item(6, 1)->setText(QString::number(uiGeneralTab.sbMin->value()) + " .. " + QString::number(uiGeneralTab.sbMax->value()) ); + uiGeneralTab.twLog->item(6, 1)->setText(QString::number(m_fitData.xRange.first()) + " .. " + QString::number(m_fitData.xRange.last()) ); // show all iterations QString str; for (const auto &s: m_fitData.paramNamesUtf8) str += s + '\t'; str += QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s: iterations) if (!s.isEmpty()) str += '\n' + s; uiGeneralTab.twLog->item(7, 1)->setText(str); uiGeneralTab.twLog->resizeRowsToContents(); // Parameters const int np = m_fitData.paramNames.size(); uiGeneralTab.twParameters->setRowCount(np); QStringList headerLabels; headerLabels << i18n("Name") << i18n("Value") << i18n("Error") << i18n("Error, %") << i18n("t statistic") << QLatin1String("P > |t|") << i18n("Conf. Interval"); uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels); for (int i = 0; i < np; i++) { const double paramValue = fitResult.paramValues.at(i); const double errorValue = fitResult.errorValues.at(i); QTableWidgetItem* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i)); uiGeneralTab.twParameters->setItem(i, 0, item); item = new QTableWidgetItem(QString::number(paramValue)); uiGeneralTab.twParameters->setItem(i, 1, item); if (!m_fitData.paramFixed.at(i)) { item = new QTableWidgetItem(QString::number(errorValue, 'g', 6)); uiGeneralTab.twParameters->setItem(i, 2, item); - item = new QTableWidgetItem(QString::number(100.*errorValue/fabs(paramValue), 'g', 3)); + item = new QTableWidgetItem(QString::number(100.*errorValue/std::abs(paramValue), 'g', 3)); uiGeneralTab.twParameters->setItem(i, 3, item); // t values - item = new QTableWidgetItem(QString::number(fitResult.tdist_tValues.at(i), 'g', 3)); + QString tdistValueString; + if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) + tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); + else + tdistValueString = QString::fromUtf8("\u221e"); + item = new QTableWidgetItem(tdistValueString); uiGeneralTab.twParameters->setItem(i, 4, item); // p values const double p = fitResult.tdist_pValues.at(i); item = new QTableWidgetItem(QString::number(p, 'g', 3)); // color p values depending on value if (p > 0.05) item->setTextColor(QApplication::palette().color(QPalette::LinkVisited)); else if (p > 0.01) item->setTextColor(Qt::darkGreen); else if (p > 0.001) item->setTextColor(Qt::darkCyan); else if (p > 0.0001) item->setTextColor(QApplication::palette().color(QPalette::Link)); else item->setTextColor(QApplication::palette().color(QPalette::Highlight)); uiGeneralTab.twParameters->setItem(i, 5, item); // Conf. interval const double margin = fitResult.tdist_marginValues.at(i); if (fitResult.tdist_tValues.at(i) < 1.e6) item = new QTableWidgetItem(QString::number(paramValue - margin) + QLatin1String(" .. ") + QString::number(paramValue + margin)); else item = new QTableWidgetItem(i18n("too small")); uiGeneralTab.twParameters->setItem(i, 6, item); } } // Goodness of fit uiGeneralTab.twGoodness->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); uiGeneralTab.twGoodness->item(0, 2)->setText(QString::number(fitResult.sse)); if (fitResult.dof != 0) { uiGeneralTab.twGoodness->item(1, 2)->setText(QString::number(fitResult.rms)); uiGeneralTab.twGoodness->item(2, 2)->setText(QString::number(fitResult.rsd)); uiGeneralTab.twGoodness->item(3, 2)->setText(QString::number(fitResult.rsquare, 'g', 15)); uiGeneralTab.twGoodness->item(4, 2)->setText(QString::number(fitResult.rsquareAdj, 'g', 15)); // chi^2 and F test p-values uiGeneralTab.twGoodness->item(5, 2)->setText(QString::number(fitResult.chisq_p, 'g', 3)); uiGeneralTab.twGoodness->item(6, 2)->setText(QString::number(fitResult.fdist_F, 'g', 3)); uiGeneralTab.twGoodness->item(7, 2)->setText(QString::number(fitResult.fdist_p, 'g', 3)); uiGeneralTab.twGoodness->item(9, 2)->setText(QString::number(fitResult.aic, 'g', 3)); uiGeneralTab.twGoodness->item(10, 2)->setText(QString::number(fitResult.bic, 'g', 3)); } uiGeneralTab.twGoodness->item(8, 2)->setText(QString::number(fitResult.mae)); //resize the table headers to fit the new content uiGeneralTab.twLog->resizeColumnsToContents(); uiGeneralTab.twParameters->resizeColumnsToContents(); //twGoodness doesn't have any header -> resize sections uiGeneralTab.twGoodness->resizeColumnToContents(0); uiGeneralTab.twGoodness->resizeColumnToContents(1); uiGeneralTab.twGoodness->resizeColumnToContents(2); //enable the "recalculate"-button if the source data was changed since the last fit uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYFitCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) { uiGeneralTab.leName->setText(aspect->name()); } else if (aspect->comment() != uiGeneralTab.leComment->text()) { uiGeneralTab.leComment->setText(aspect->comment()); } m_initializing = false; } void XYFitCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& data) { m_initializing = true; m_fitData = data; if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.teEquation->setPlainText(m_fitData.model); else uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); this->showFitResult(); m_initializing = false; } void XYFitCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.h b/src/kdefrontend/dockwidgets/XYFitCurveDock.h index 76e6ee98e..80c56d1dc 100644 --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.h +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.h @@ -1,114 +1,113 @@ /*************************************************************************** File : XYFitCurveDock.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014 Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of equation curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef XYFITCURVEDOCK_H #define XYFITCURVEDOCK_H #include "kdefrontend/dockwidgets/XYCurveDock.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "ui_xyfitcurvedockgeneraltab.h" class TreeViewComboBox; class XYFitCurveDock: public XYCurveDock { Q_OBJECT public: explicit XYFitCurveDock(QWidget* parent); void setCurves(QList); virtual void setupGeneral(); private: virtual void initGeneralTab(); void showFitResult(); void updateSettings(const AbstractColumn*); Ui::XYFitCurveDockGeneralTab uiGeneralTab; TreeViewComboBox* cbDataSourceCurve; TreeViewComboBox* cbXDataColumn; TreeViewComboBox* cbYDataColumn; TreeViewComboBox* cbXErrorColumn; TreeViewComboBox* cbYErrorColumn; XYFitCurve* m_fitCurve; XYFitCurve::FitData m_fitData; QList parameters; QList parameterValues; protected: virtual void setModel(); private slots: //SLOTs for changes triggered in XYFitCurveDock //general tab void nameChanged(); void commentChanged(); void dataSourceTypeChanged(int); void dataSourceCurveChanged(const QModelIndex&); - void weightChanged(int); + void xWeightChanged(int); + void yWeightChanged(int); void categoryChanged(int); void modelTypeChanged(int); void xDataColumnChanged(const QModelIndex&); void yDataColumnChanged(const QModelIndex&); void xErrorColumnChanged(const QModelIndex&); void yErrorColumnChanged(const QModelIndex&); - void autoRangeChanged(); - void xRangeMinChanged(); - void xRangeMaxChanged(); void showConstants(); void showFunctions(); void updateParameterList(); void showParameters(); void parametersChanged(); void showOptions(); void insertFunction(const QString&) const; void insertConstant(const QString&) const; void recalculateClicked(); void updateModelEquation(); void enableRecalculate() const; void resultParametersContextMenuRequest(const QPoint &); void resultGoodnessContextMenuRequest(const QPoint &); void resultLogContextMenuRequest(const QPoint &); void resultCopySelection(); void resultCopyAll(); //SLOTs for changes triggered in XYCurve //General-Tab void curveDescriptionChanged(const AbstractAspect*); void curveDataSourceTypeChanged(XYCurve::DataSourceType); void curveDataSourceCurveChanged(const XYCurve*); void curveXDataColumnChanged(const AbstractColumn*); void curveYDataColumnChanged(const AbstractColumn*); void curveXErrorColumnChanged(const AbstractColumn*); void curveYErrorColumnChanged(const AbstractColumn*); void curveFitDataChanged(const XYFitCurve::FitData&); void dataChanged(); }; #endif diff --git a/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp b/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp index f272c906f..170e11b25 100644 --- a/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYIntegrationCurveDock.cpp @@ -1,517 +1,516 @@ /*************************************************************************** File : XYIntegrationCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of integration curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYIntegrationCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include extern "C" { #include "backend/nsl/nsl_int.h" } -#include // isnan /*! \class XYIntegrationCurveDock \brief Provides a widget for editing the properties of the XYIntegrationCurves (2D-curves defined by a integration) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYIntegrationCurveDock::XYIntegrationCurveDock(QWidget* parent) : XYCurveDock(parent), cbDataSourceCurve(nullptr), cbXDataColumn(nullptr), cbYDataColumn(nullptr), m_integrationCurve(nullptr) { //hide the line connection type ui.cbLineType->setDisabled(true); //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYIntegrationCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = dynamic_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2,2,2,2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 2, 1, 3); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 2, 1, 3); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 2, 1, 3); for (int i=0; i < NSL_INT_NETHOD_COUNT; i++) uiGeneralTab.cbMethod->addItem(i18n(nsl_int_method_name[i])); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int)) ); connect( uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged()) ); connect( uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged()) ); connect( uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged()) ); connect( uiGeneralTab.cbMethod, SIGNAL(currentIndexChanged(int)), this, SLOT(methodChanged()) ); connect( uiGeneralTab.cbAbsolute, SIGNAL(clicked(bool)), this, SLOT(absoluteChanged()) ); connect( uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked()) ); connect( cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex)) ); connect( cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex)) ); connect( cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex)) ); } void XYIntegrationCurveDock::initGeneralTab() { //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size()==1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); }else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_integrationCurve = dynamic_cast(m_curve); Q_ASSERT(m_integrationCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_integrationCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_integrationCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_integrationCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_integrationCurve->yDataColumn()); uiGeneralTab.cbAutoRange->setChecked(m_integrationData.autoRange); uiGeneralTab.sbMin->setValue(m_integrationData.xRange.first()); uiGeneralTab.sbMax->setValue(m_integrationData.xRange.last()); this->autoRangeChanged(); // update list of selectable types xDataColumnChanged(cbXDataColumn->currentModelIndex()); uiGeneralTab.cbMethod->setCurrentIndex(m_integrationData.method); this->methodChanged(); uiGeneralTab.cbAbsolute->setChecked(m_integrationData.absolute); this->absoluteChanged(); this->showIntegrationResult(); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_integrationCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_integrationCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_integrationCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_integrationCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_integrationCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_integrationCurve, SIGNAL(integrationDataChanged(XYIntegrationCurve::IntegrationData)), this, SLOT(curveIntegrationDataChanged(XYIntegrationCurve::IntegrationData))); connect(m_integrationCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYIntegrationCurveDock::setModel() { QList list; list<<"Folder"<<"Datapicker"<<"Worksheet"<<"CartesianPlot"<<"XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; foreach (XYCurve* curve, m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYIntegrationCurveDock::setCurves(QList list) { m_initializing=true; m_curvesList=list; m_curve=list.first(); m_integrationCurve = dynamic_cast(m_curve); Q_ASSERT(m_integrationCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_integrationData = m_integrationCurve->integrationData(); initGeneralTab(); initTabs(); m_initializing=false; //hide the "skip gaps" option after the curves were set ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYIntegrationCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYIntegrationCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYIntegrationCurveDock::dataSourceTypeChanged(int index) { XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYIntegrationCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } // disable integration orders and accuracies that need more data points this->updateSettings(dataSourceCurve->xColumn()); if (m_initializing) return; foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYIntegrationCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setXDataColumn(column); if (column != 0) { if (uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } // disable integration methods that need more data points this->updateSettings(column); } } /*! * disable deriv orders and accuracies that need more data points */ void XYIntegrationCurveDock::updateSettings(const AbstractColumn* column) { if (!column) return; //TODO // size_t n=0; // for (int row=0; row < column->rowCount(); row++) // if (!std::isnan(column->valueAt(row)) && !column->isMasked(row)) // n++; } void XYIntegrationCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setYDataColumn(column); } void XYIntegrationCurveDock::autoRangeChanged() { bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); m_integrationData.autoRange = autoRange; if (autoRange) { uiGeneralTab.lMin->setEnabled(false); uiGeneralTab.sbMin->setEnabled(false); uiGeneralTab.lMax->setEnabled(false); uiGeneralTab.sbMax->setEnabled(false); const AbstractColumn* xDataColumn = 0; if (m_integrationCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) xDataColumn = m_integrationCurve->xDataColumn(); else { if (m_integrationCurve->dataSourceCurve()) xDataColumn = m_integrationCurve->dataSourceCurve()->xColumn(); } if (xDataColumn) { uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); } } else { uiGeneralTab.lMin->setEnabled(true); uiGeneralTab.sbMin->setEnabled(true); uiGeneralTab.lMax->setEnabled(true); uiGeneralTab.sbMax->setEnabled(true); } } void XYIntegrationCurveDock::xRangeMinChanged() { double xMin = uiGeneralTab.sbMin->value(); m_integrationData.xRange.first() = xMin; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYIntegrationCurveDock::xRangeMaxChanged() { double xMax = uiGeneralTab.sbMax->value(); m_integrationData.xRange.last() = xMax; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYIntegrationCurveDock::methodChanged() { nsl_int_method_type method = (nsl_int_method_type)uiGeneralTab.cbMethod->currentIndex(); m_integrationData.method = method; // update absolute option switch (method) { case nsl_int_method_rectangle: case nsl_int_method_trapezoid: uiGeneralTab.cbAbsolute->setEnabled(true); break; case nsl_int_method_simpson: case nsl_int_method_simpson_3_8: uiGeneralTab.cbAbsolute->setChecked(false); uiGeneralTab.cbAbsolute->setEnabled(false); } uiGeneralTab.pbRecalculate->setEnabled(true); } void XYIntegrationCurveDock::absoluteChanged() { bool absolute = uiGeneralTab.cbAbsolute->isChecked(); m_integrationData.absolute = absolute; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYIntegrationCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); foreach(XYCurve* curve, m_curvesList) dynamic_cast(curve)->setIntegrationData(m_integrationData); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Integration status: ") + m_integrationCurve->integrationResult().status); QApplication::restoreOverrideCursor(); } void XYIntegrationCurveDock::enableRecalculate() const { if (m_initializing) return; //no integration possible without the x- and y-data bool hasSourceData = false; if (m_integrationCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX!=0 && aspectY!=0); } else { hasSourceData = (m_integrationCurve->dataSourceCurve() != NULL); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the result and details of the integration */ void XYIntegrationCurveDock::showIntegrationResult() { const XYIntegrationCurve::IntegrationResult& integrationResult = m_integrationCurve->integrationResult(); if (!integrationResult.available) { uiGeneralTab.teResult->clear(); return; } QString str = i18n("status:") + ' ' + integrationResult.status + "
"; if (!integrationResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } if (integrationResult.elapsedTime>1000) str += i18n("calculation time: %1 s").arg(QString::number(integrationResult.elapsedTime/1000)) + "
"; else str += i18n("calculation time: %1 ms").arg(QString::number(integrationResult.elapsedTime)) + "
"; str += i18n("value: ") + QString::number(integrationResult.value) + "
"; str += "

"; uiGeneralTab.teResult->setText(str); //enable the "recalculate"-button if the source data was changed since the last integration uiGeneralTab.pbRecalculate->setEnabled(m_integrationCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYIntegrationCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) { uiGeneralTab.leName->setText(aspect->name()); } else if (aspect->comment() != uiGeneralTab.leComment->text()) { uiGeneralTab.leComment->setText(aspect->comment()); } m_initializing = false; } void XYIntegrationCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYIntegrationCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYIntegrationCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYIntegrationCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYIntegrationCurveDock::curveIntegrationDataChanged(const XYIntegrationCurve::IntegrationData& data) { m_initializing = true; m_integrationData = data; uiGeneralTab.cbMethod->setCurrentIndex(m_integrationData.method); this->methodChanged(); uiGeneralTab.cbAbsolute->setChecked(m_integrationData.absolute); this->absoluteChanged(); this->showIntegrationResult(); m_initializing = false; } void XYIntegrationCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp index c3f840b52..76a615407 100644 --- a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp @@ -1,617 +1,612 @@ /*************************************************************************** File : XYSmoothCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) Description : widget for editing properties of smooth curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYSmoothCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include -#ifndef NDEBUG -#include -#endif - -#include // isnan /*! \class XYSmoothCurveDock \brief Provides a widget for editing the properties of the XYSmoothCurves (2D-curves defined by an smooth) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYSmoothCurveDock::XYSmoothCurveDock(QWidget* parent) : XYCurveDock(parent), cbDataSourceCurve(nullptr), cbXDataColumn(nullptr), cbYDataColumn(nullptr), m_smoothCurve(nullptr) { //hide the line connection type ui.cbLineType->setDisabled(true); //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYSmoothCurveDock::setupGeneral() { #ifndef NDEBUG qDebug()<<"XYSmoothCurveDock::setupGeneral()"; #endif QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = dynamic_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2,2,2,2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 2, 1, 2); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 2, 1, 2); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 2, 1, 2); for (int i=0; i < NSL_SMOOTH_TYPE_COUNT; i++) uiGeneralTab.cbType->addItem(i18n(nsl_smooth_type_name[i])); for (int i=0; i < NSL_SMOOTH_WEIGHT_TYPE_COUNT; i++) uiGeneralTab.cbWeight->addItem(i18n(nsl_smooth_weight_type_name[i])); for (int i=0; i < NSL_SMOOTH_PAD_MODE_COUNT; i++) uiGeneralTab.cbMode->addItem(i18n(nsl_smooth_pad_mode_name[i])); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect( uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int)) ); connect( uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged()) ); connect( uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged()) ); connect( uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged()) ); connect( uiGeneralTab.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged()) ); connect( uiGeneralTab.sbPoints, SIGNAL(valueChanged(int)), this, SLOT(pointsChanged()) ); connect( uiGeneralTab.cbWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(weightChanged()) ); connect( uiGeneralTab.sbPercentile, SIGNAL(valueChanged(double)), this, SLOT(percentileChanged()) ); connect( uiGeneralTab.sbOrder, SIGNAL(valueChanged(int)), this, SLOT(orderChanged()) ); connect( uiGeneralTab.cbMode, SIGNAL(currentIndexChanged(int)), this, SLOT(modeChanged()) ); connect( uiGeneralTab.sbLeftValue, SIGNAL(valueChanged(double)), this, SLOT(valueChanged()) ); connect( uiGeneralTab.sbRightValue, SIGNAL(valueChanged(double)), this, SLOT(valueChanged()) ); connect( uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked()) ); connect( cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex)) ); connect( cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex)) ); connect( cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex)) ); } void XYSmoothCurveDock::initGeneralTab() { #ifndef NDEBUG qDebug()<<"XYSmoothCurveDock::initGeneralTab()"; #endif //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size()==1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); }else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_smoothCurve = dynamic_cast(m_curve); Q_ASSERT(m_smoothCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_smoothCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_smoothCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_smoothCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_smoothCurve->yDataColumn()); uiGeneralTab.cbAutoRange->setChecked(m_smoothData.autoRange); uiGeneralTab.sbMin->setValue(m_smoothData.xRange.first()); uiGeneralTab.sbMax->setValue(m_smoothData.xRange.last()); this->autoRangeChanged(); // update list of selectable types xDataColumnChanged(cbXDataColumn->currentModelIndex()); uiGeneralTab.cbType->setCurrentIndex(m_smoothData.type); typeChanged(); // needed, when type does not change #ifndef NDEBUG qDebug()<<" curve ="<name(); qDebug()<<" m_smoothData.points ="<setValue(m_smoothData.points); uiGeneralTab.cbWeight->setCurrentIndex(m_smoothData.weight); uiGeneralTab.sbPercentile->setValue(m_smoothData.percentile); uiGeneralTab.sbOrder->setValue(m_smoothData.order); uiGeneralTab.cbMode->setCurrentIndex(m_smoothData.mode); modeChanged(); // needed, when mode does not change uiGeneralTab.sbLeftValue->setValue(m_smoothData.lvalue); uiGeneralTab.sbRightValue->setValue(m_smoothData.rvalue); valueChanged(); this->showSmoothResult(); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_smoothCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_smoothCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_smoothCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_smoothCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_smoothCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_smoothCurve, SIGNAL(smoothDataChanged(XYSmoothCurve::SmoothData)), this, SLOT(curveSmoothDataChanged(XYSmoothCurve::SmoothData))); connect(m_smoothCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYSmoothCurveDock::setModel() { QList list; list<<"Folder"<<"Datapicker"<<"Worksheet"<<"CartesianPlot"<<"XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for(XYCurve* curve: m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list<<"Folder"<<"Workbook"<<"Datapicker"<<"DatapickerCurve"<<"Spreadsheet" <<"FileDataSource"<<"Column"<<"Worksheet"<<"CartesianPlot"<<"XYFitCurve"<<"CantorWorksheet"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYSmoothCurveDock::setCurves(QList list) { #ifndef NDEBUG qDebug()<<"XYSmoothCurveDock::setCurves()"; #endif m_initializing=true; m_curvesList=list; m_curve=list.first(); m_smoothCurve = dynamic_cast(m_curve); Q_ASSERT(m_smoothCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_smoothData = m_smoothCurve->smoothData(); initGeneralTab(); initTabs(); m_initializing=false; //hide the "skip gaps" option after the curves were set ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYSmoothCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYSmoothCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYSmoothCurveDock::dataSourceTypeChanged(int index) { XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; for(XYCurve* curve: m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYSmoothCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } if (m_initializing) return; for(XYCurve* curve: m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYSmoothCurveDock::xDataColumnChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for(XYCurve* curve: m_curvesList) dynamic_cast(curve)->setXDataColumn(column); // disable types that need more data points if (column != 0) { if (uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } unsigned int n=0; for (int row=0; row < column->rowCount(); row++) if (!std::isnan(column->valueAt(row)) && !column->isMasked(row)) n++; // set maximum of sbPoints to number of columns uiGeneralTab.sbPoints->setMaximum(n); } } void XYSmoothCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for(XYCurve* curve: m_curvesList) dynamic_cast(curve)->setYDataColumn(column); } void XYSmoothCurveDock::autoRangeChanged() { bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); m_smoothData.autoRange = autoRange; if (autoRange) { uiGeneralTab.lMin->setEnabled(false); uiGeneralTab.sbMin->setEnabled(false); uiGeneralTab.lMax->setEnabled(false); uiGeneralTab.sbMax->setEnabled(false); const AbstractColumn* xDataColumn = 0; if (m_smoothCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) xDataColumn = m_smoothCurve->xDataColumn(); else { if (m_smoothCurve->dataSourceCurve()) xDataColumn = m_smoothCurve->dataSourceCurve()->xColumn(); } if (xDataColumn) { uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); } } else { uiGeneralTab.lMin->setEnabled(true); uiGeneralTab.sbMin->setEnabled(true); uiGeneralTab.lMax->setEnabled(true); uiGeneralTab.sbMax->setEnabled(true); } } void XYSmoothCurveDock::xRangeMinChanged() { double xMin = uiGeneralTab.sbMin->value(); m_smoothData.xRange.first() = xMin; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::xRangeMaxChanged() { double xMax = uiGeneralTab.sbMax->value(); m_smoothData.xRange.last() = xMax; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::typeChanged() { nsl_smooth_type type = (nsl_smooth_type)uiGeneralTab.cbType->currentIndex(); m_smoothData.type = type; const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbMode->model()); QStandardItem* pad_interp_item = model->item(nsl_smooth_pad_interp); if (type == nsl_smooth_type_moving_average || type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.lWeight->show(); uiGeneralTab.cbWeight->show(); // disable interp pad model for MA and MAL pad_interp_item->setFlags(pad_interp_item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } else { uiGeneralTab.lWeight->hide(); uiGeneralTab.cbWeight->hide(); pad_interp_item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); } if (type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.sbPoints->setSingleStep(1); uiGeneralTab.sbPoints->setMinimum(2); uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } else { uiGeneralTab.sbPoints->setSingleStep(2); uiGeneralTab.sbPoints->setMinimum(3); if (m_smoothData.mode == nsl_smooth_pad_constant) { uiGeneralTab.lRightValue->show(); uiGeneralTab.sbRightValue->show(); } } if (type == nsl_smooth_type_percentile) { uiGeneralTab.lPercentile->show(); uiGeneralTab.sbPercentile->show(); // disable interp pad model for MA and MAL pad_interp_item->setFlags(pad_interp_item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } else { uiGeneralTab.lPercentile->hide(); uiGeneralTab.sbPercentile->hide(); } if (type == nsl_smooth_type_savitzky_golay) { uiGeneralTab.lOrder->show(); uiGeneralTab.sbOrder->show(); } else { uiGeneralTab.lOrder->hide(); uiGeneralTab.sbOrder->hide(); } enableRecalculate(); } void XYSmoothCurveDock::pointsChanged() { m_smoothData.points = uiGeneralTab.sbPoints->value(); // set maximum order uiGeneralTab.sbOrder->setMaximum(m_smoothData.points-1); enableRecalculate(); } void XYSmoothCurveDock::weightChanged() { m_smoothData.weight = (nsl_smooth_weight_type)uiGeneralTab.cbWeight->currentIndex(); enableRecalculate(); } void XYSmoothCurveDock::percentileChanged() { m_smoothData.percentile = uiGeneralTab.sbPercentile->value(); enableRecalculate(); } void XYSmoothCurveDock::orderChanged() { m_smoothData.order = uiGeneralTab.sbOrder->value(); enableRecalculate(); } void XYSmoothCurveDock::modeChanged() { m_smoothData.mode = (nsl_smooth_pad_mode)(uiGeneralTab.cbMode->currentIndex()); if (m_smoothData.mode == nsl_smooth_pad_constant) { uiGeneralTab.lLeftValue->show(); uiGeneralTab.sbLeftValue->show(); if (m_smoothData.type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } else { uiGeneralTab.lRightValue->show(); uiGeneralTab.sbRightValue->show(); } } else { uiGeneralTab.lLeftValue->hide(); uiGeneralTab.sbLeftValue->hide(); uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } enableRecalculate(); } void XYSmoothCurveDock::valueChanged() { m_smoothData.lvalue = uiGeneralTab.sbLeftValue->value(); m_smoothData.rvalue = uiGeneralTab.sbRightValue->value(); enableRecalculate(); } void XYSmoothCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); for(XYCurve* curve: m_curvesList) dynamic_cast(curve)->setSmoothData(m_smoothData); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Smoothing status: ") + m_smoothCurve->smoothResult().status); QApplication::restoreOverrideCursor(); } void XYSmoothCurveDock::enableRecalculate() const { if (m_initializing) return; //no smoothing possible without the x- and y-data bool hasSourceData = false; if (m_smoothCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX!=0 && aspectY!=0); } else { hasSourceData = (m_smoothCurve->dataSourceCurve() != NULL); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the result and details of the smooth */ void XYSmoothCurveDock::showSmoothResult() { const XYSmoothCurve::SmoothResult& smoothResult = m_smoothCurve->smoothResult(); if (!smoothResult.available) { uiGeneralTab.teResult->clear(); return; } //const XYSmoothCurve::SmoothData& smoothData = m_smoothCurve->smoothData(); QString str = i18n("status:") + ' ' + smoothResult.status + "
"; if (!smoothResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } if (smoothResult.elapsedTime>1000) str += i18n("calculation time: %1 s").arg(QString::number(smoothResult.elapsedTime/1000)) + "
"; else str += i18n("calculation time: %1 ms").arg(QString::number(smoothResult.elapsedTime)) + "
"; str += "

"; uiGeneralTab.teResult->setText(str); //enable the "recalculate"-button if the source data was changed since the last smooth uiGeneralTab.pbRecalculate->setEnabled(m_smoothCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYSmoothCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) { uiGeneralTab.leName->setText(aspect->name()); } else if (aspect->comment() != uiGeneralTab.leComment->text()) { uiGeneralTab.leComment->setText(aspect->comment()); } m_initializing = false; } void XYSmoothCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYSmoothCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYSmoothCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYSmoothCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYSmoothCurveDock::curveSmoothDataChanged(const XYSmoothCurve::SmoothData& data) { m_initializing = true; m_smoothData = data; uiGeneralTab.cbType->setCurrentIndex(m_smoothData.type); this->showSmoothResult(); m_initializing = false; } void XYSmoothCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp index f71b1521f..a320b307d 100644 --- a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp @@ -1,356 +1,355 @@ /*************************************************************************** File : FunctionValuesDialog.cpp Project : LabPlot Description : Dialog for generating values from a mathematical function -------------------------------------------------------------------- Copyright : (C) 2014-2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FunctionValuesDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/gsl/ExpressionParser.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include #include #include #include #include #include #include #include /*! \class FunctionValuesDialog \brief Dialog for generating values from a mathematical function. \ingroup kdefrontend */ FunctionValuesDialog::FunctionValuesDialog(Spreadsheet* s, QWidget* parent, Qt::WFlags fl) : QDialog(parent, fl), m_spreadsheet(s) { Q_ASSERT(s); setWindowTitle(i18n("Function values")); ui.setupUi(this); setAttribute(Qt::WA_DeleteOnClose); ui.tbConstants->setIcon( QIcon::fromTheme("labplot-format-text-symbol") ); ui.tbConstants->setIcon( QIcon::fromTheme("format-text-symbol") ); ui.tbFunctions->setIcon( QIcon::fromTheme("preferences-desktop-font") ); ui.teEquation->setMaximumHeight(QLineEdit().sizeHint().height()*2); ui.teEquation->setFocus(); m_topLevelClasses<<"Folder"<<"Workbook"<<"Spreadsheet"<<"FileDataSource"<<"Column"; m_selectableClasses<<"Column"; #if __cplusplus < 201103L m_aspectTreeModel = std::auto_ptr(new AspectTreeModel(m_spreadsheet->project())); #else m_aspectTreeModel = std::unique_ptr(new AspectTreeModel(m_spreadsheet->project())); #endif m_aspectTreeModel->setSelectableAspects(m_selectableClasses); ui.bAddVariable->setIcon(QIcon::fromTheme("list-add")); ui.bAddVariable->setToolTip(i18n("Add new variable")); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - ui.gridLayout->addWidget(btnBox); + ui.verticalLayout->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); - connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &FunctionValuesDialog::close); connect(btnBox, &QDialogButtonBox::accepted, this, &FunctionValuesDialog::accept); connect(btnBox, &QDialogButtonBox::rejected, this, &FunctionValuesDialog::reject); m_okButton->setText(i18n("&Generate")); m_okButton->setToolTip(i18n("Generate function values")); connect( ui.bAddVariable, SIGNAL(pressed()), this, SLOT(addVariable()) ); connect( ui.teEquation, SIGNAL(expressionChanged()), this, SLOT(checkValues()) ); connect( ui.tbConstants, SIGNAL(clicked()), this, SLOT(showConstants()) ); connect( ui.tbFunctions, SIGNAL(clicked()), this, SLOT(showFunctions()) ); connect(m_okButton, &QPushButton::clicked, this, &FunctionValuesDialog::generate); //restore saved settings if available KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); if (conf.exists()) KWindowConfig::restoreWindowSize(windowHandle(), conf); else resize(QSize(300, 0).expandedTo(minimumSize())); } FunctionValuesDialog::~FunctionValuesDialog() { KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void FunctionValuesDialog::setColumns(QVector columns) { m_columns = columns; ui.teEquation->setPlainText(m_columns.first()->formula()); const QStringList& variableNames = m_columns.first()->formulaVariableNames(); if (!variableNames.size()) { //no formular was used for this column -> add the first variable "x" addVariable(); m_variableNames[0]->setText("x"); } else { //formula and variables are available const QStringList& columnPathes = m_columns.first()->formulaVariableColumnPathes(); //add all available variables and select the corresponding columns const QVector columns = m_spreadsheet->project()->children("Column", AbstractAspect::Recursive); for (int i = 0; i < variableNames.size(); ++i) { addVariable(); m_variableNames[i]->setText(variableNames.at(i)); foreach (const AbstractAspect* aspect, columns) { if (aspect->path() == columnPathes.at(i)) { const AbstractColumn* column = dynamic_cast(aspect); if (column) m_variableDataColumns[i]->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else m_variableDataColumns[i]->setCurrentModelIndex(QModelIndex()); break; } } } } } /*! check the user input and enables/disables the Ok-button depending on the correctness of the input */ void FunctionValuesDialog::checkValues() { //check whether the formulr syntax is correct if (!ui.teEquation->isValid()) { m_okButton->setEnabled(false); return; } //check whether for the variables where a name was provided also a column was selected. for (int i = 0; i < m_variableDataColumns.size(); ++i) { if (m_variableNames.at(i)->text().simplified().isEmpty()) continue; TreeViewComboBox* cb = m_variableDataColumns.at(i); AbstractAspect* aspect = static_cast(cb->currentModelIndex().internalPointer()); if (!aspect) { m_okButton->setEnabled(false); return; } } m_okButton->setEnabled(true); } void FunctionValuesDialog::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, SIGNAL(constantSelected(QString)), this, SLOT(insertConstant(QString))); connect(&constants, SIGNAL(constantSelected(QString)), &menu, SLOT(close())); connect(&constants, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+ui.tbConstants->width(),-menu.sizeHint().height()); menu.exec(ui.tbConstants->mapToGlobal(pos)); } void FunctionValuesDialog::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, SIGNAL(functionSelected(QString)), this, SLOT(insertFunction(QString))); connect(&functions, SIGNAL(functionSelected(QString)), &menu, SLOT(close())); connect(&functions, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+ui.tbFunctions->width(),-menu.sizeHint().height()); menu.exec(ui.tbFunctions->mapToGlobal(pos)); } void FunctionValuesDialog::insertFunction(const QString& str) { ui.teEquation->insertPlainText(str + "(x)"); } void FunctionValuesDialog::insertConstant(const QString& str) { ui.teEquation->insertPlainText(str); } void FunctionValuesDialog::addVariable() { QGridLayout* layout = dynamic_cast(ui.frameVariables->layout()); int row = m_variableNames.size(); //text field for the variable name QLineEdit* le = new QLineEdit(); le->setMaximumWidth(30); connect(le, SIGNAL(textChanged(QString)), this, SLOT(variableNameChanged())); layout->addWidget(le, row, 0, 1, 1); m_variableNames<addWidget(l, row, 1, 1, 1); m_variableLabels<setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); connect( cb, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(checkValues()) ); layout->addWidget(cb, row, 2, 1, 1); m_variableDataColumns<setTopLevelClasses(m_topLevelClasses); cb->setModel(m_aspectTreeModel.get()); cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(m_spreadsheet->column(0))); //move the add-button to the next row layout->removeWidget(ui.bAddVariable); layout->addWidget(ui.bAddVariable, row+1,3, 1, 1); //add delete-button for the just added variable if (row != 0) { QToolButton* b = new QToolButton(); b->setIcon(QIcon::fromTheme("list-remove")); b->setToolTip(i18n("Delete variable")); layout->addWidget(b, row, 3, 1, 1); m_variableDeleteButtons<setText(i18n("Variables:")); //TODO: adjust the tab-ordering after new widgets were added } void FunctionValuesDialog::deleteVariable() { QObject* ob = QObject::sender(); int index = m_variableDeleteButtons.indexOf(qobject_cast(ob)) ; delete m_variableNames.takeAt(index+1); delete m_variableLabels.takeAt(index+1); delete m_variableDataColumns.takeAt(index+1); delete m_variableDeleteButtons.takeAt(index); variableNameChanged(); checkValues(); //adjust the layout resize( QSize(width(),0).expandedTo(minimumSize()) ); m_variableNames.size()>1 ? ui.lVariable->setText(i18n("Variables:")) : ui.lVariable->setText(i18n("Variable:")); //TODO: adjust the tab-ordering after some widgets were deleted } void FunctionValuesDialog::variableNameChanged() { QStringList vars; QString text; for (int i = 0; i < m_variableNames.size(); ++i) { QString name = m_variableNames.at(i)->text().simplified(); if (!name.isEmpty()) { vars<name(), m_columns.size())); //determine variable names and the data vectors of the specified columns QStringList variableNames; QStringList columnPathes; QVector*> xVectors; QVector xColumns; int maxRowCount = m_spreadsheet->rowCount(); for (int i=0; itext().simplified(); AbstractAspect* aspect = static_cast(m_variableDataColumns.at(i)->currentModelIndex().internalPointer()); Q_ASSERT(aspect); Column* column = dynamic_cast(aspect); Q_ASSERT(column); columnPathes << column->path(); xColumns << column; xVectors << static_cast* >(column->data()); if (column->rowCount()>maxRowCount) maxRowCount = column->rowCount(); } //resize the spreadsheet if one of the data vectors from other spreadsheet(s) has more elements then the current spreadsheet. if (m_spreadsheet->rowCount()setRowCount(maxRowCount); //create new vector for storing the calculated values //the vectors with the variable data can be smaller then the result vector. So, not all values in the result vector might get initialized. //->"clean" the result vector first QVector new_data(maxRowCount); for (int i=0; itoPlainText(); parser->evaluateCartesian(expression, variableNames, xVectors, &new_data); //set the new values and store the expression, variable names and the used data columns foreach(Column* col, m_columns) { col->setFormula(expression, variableNames, columnPathes); col->replaceValues(0, new_data); } m_spreadsheet->endMacro(); RESET_CURSOR; } diff --git a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp index 8b6dbe5e4..6511724d9 100644 --- a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp @@ -1,725 +1,727 @@ /*************************************************************************** File : RandomValuesDialog.cpp Project : LabPlot Description : Dialog for generating non-uniformly distributed random numbers -------------------------------------------------------------------- Copyright : (C) 2014 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "RandomValuesDialog.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include #include #include #include #include #include #include extern "C" { #include #include "backend/nsl/nsl_sf_stats.h" #include #include } /*! \class RandomValuesDialog \brief Dialog for generating non-uniform random numbers. \ingroup kdefrontend */ RandomValuesDialog::RandomValuesDialog(Spreadsheet* s, QWidget* parent, Qt::WFlags fl) : QDialog(parent, fl), m_spreadsheet(s) { setWindowTitle(i18n("Random values")); QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); QVBoxLayout *layout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - m_okButton = buttonBox->button(QDialogButtonBox::Ok); - m_okButton->setDefault(true); - m_okButton->setToolTip(i18n("Generate random values according to the selected distribution")); - m_okButton->setText(i18n("&Generate")); + m_okButton = buttonBox->button(QDialogButtonBox::Ok); + m_okButton->setDefault(true); + m_okButton->setToolTip(i18n("Generate random values according to the selected distribution")); + m_okButton->setText(i18n("&Generate")); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &RandomValuesDialog::close); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &RandomValuesDialog::reject); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); setAttribute(Qt::WA_DeleteOnClose); for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_RNG_COUNT; i++) - ui.cbDistribution->addItem(i18n(nsl_sf_stats_distribution_name[i]), i); + ui.cbDistribution->addItem(i18n(nsl_sf_stats_distribution_name[i]), i); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); ui.lFuncPic->setAutoFillBackground(true); ui.lFuncPic->setPalette(p); ui.leParameter1->setClearButtonEnabled(true); ui.leParameter2->setClearButtonEnabled(true); ui.leParameter3->setClearButtonEnabled(true); ui.leParameter1->setValidator( new QDoubleValidator(ui.leParameter1) ); ui.leParameter2->setValidator( new QDoubleValidator(ui.leParameter2) ); ui.leParameter3->setValidator( new QDoubleValidator(ui.leParameter3) ); connect(ui.cbDistribution, static_cast(&QComboBox::currentIndexChanged), this, &RandomValuesDialog::distributionChanged); connect(ui.leParameter1, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter2, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter3, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::generate); //restore saved settings if available const KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); if (conf.exists()) { ui.cbDistribution->setCurrentIndex(conf.readEntry("Distribution", 0)); this->distributionChanged(ui.cbDistribution->currentIndex()); //if index=0 no signal is emmited above, call this slot directly here ui.leParameter1->setText(conf.readEntry("Parameter1")); ui.leParameter2->setText(conf.readEntry("Parameter2")); ui.leParameter3->setText(conf.readEntry("Parameter3")); KWindowConfig::restoreWindowSize(windowHandle(), conf); } else { //Gaussian distribution as default this->distributionChanged(0); resize( QSize(400,0).expandedTo(minimumSize()) ); } } RandomValuesDialog::~RandomValuesDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); conf.writeEntry("Distribution", ui.cbDistribution->currentIndex()); conf.writeEntry("Parameter1", ui.leParameter1->text()); conf.writeEntry("Parameter2", ui.leParameter2->text()); conf.writeEntry("Parameter3", ui.leParameter3->text()); KWindowConfig::saveWindowSize(windowHandle(), conf); } void RandomValuesDialog::setColumns(QVector columns) { m_columns = columns; } void RandomValuesDialog::distributionChanged(int index) { nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); // default settings (used by most distributions) ui.lParameter1->show(); ui.leParameter1->show(); ui.lParameter2->show(); ui.leParameter2->show(); ui.lParameter3->hide(); ui.leParameter3->hide(); ui.lFunc->setText("p(x) ="); switch (dist) { case nsl_sf_stats_gaussian: - ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); - ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); - ui.leParameter1->setText("1.0"); - ui.leParameter2->setText("0.0"); + ui.lParameter1->setText(QString::fromUtf8("\u03bc =")); + ui.lParameter2->setText(QString::fromUtf8("\u03c3 =")); + ui.leParameter1->setText("0.0"); + ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gaussian_tail: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(QString::fromUtf8("\u03bc =")); ui.lParameter2->setText(QString::fromUtf8("\u03c3 =")); ui.lParameter3->setText("a ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_exponential: ui.lParameter1->setText(QString::fromUtf8("\u03bb =")); ui.leParameter1->setText("1.0"); ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_laplace: - ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); - ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); - ui.leParameter1->setText("1.0"); - ui.leParameter2->setText("0.0"); + ui.lParameter1->setText(QString::fromUtf8("\u03bc =")); + ui.lParameter2->setText(QString::fromUtf8("\u03c3 =")); + ui.leParameter1->setText("0.0"); + ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_exponential_power: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(QString::fromUtf8("\u03bc =")); ui.lParameter2->setText(QString::fromUtf8("\u03c3 =")); ui.lParameter3->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_cauchy_lorentz: ui.lParameter1->setText(QString::fromUtf8("\u03b3 =")); ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_rayleigh: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_rayleigh_tail: ui.lParameter1->setText(QString::fromUtf8("\u03bc =")); ui.lParameter2->setText(QString::fromUtf8("\u03c3 =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_landau: ui.lParameter1->hide(); ui.leParameter1->hide(); ui.lParameter2->hide(); ui.leParameter2->hide(); break; case nsl_sf_stats_levy_alpha_stable: ui.lParameter1->setText("c ="); ui.lParameter2->setText(QString::fromUtf8("\u03b1 =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_levy_skew_alpha_stable: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(QString::fromUtf8("c =")); ui.lParameter2->setText(QString::fromUtf8("\u03b1 =")); ui.lParameter3->setText(QString::fromUtf8("\u03b2 =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_flat: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gamma: ui.lParameter1->setText(QString::fromUtf8("\u03b8 =")); ui.lParameter2->setText("k ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_weibull: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("k ="); ui.lParameter2->setText(QString::fromUtf8("\u03bb =")); ui.lParameter3->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_beta: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gumbel1: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); ui.lParameter2->setText(QString::fromUtf8("\u03b2 =")); ui.lParameter3->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_gumbel2: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.lParameter3->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_pareto: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_lognormal: ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_chi_squared: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText("n ="); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_fdist: ui.lParameter1->setText(QString::fromUtf8("\u03bd\u2081 =")); ui.lParameter2->setText(QString::fromUtf8("\u03bd\u2082 =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_tdist: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(QString::fromUtf8("\u03bd =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_logistic: ui.lParameter1->setText(QString::fromUtf8("\u03c3 =")); ui.lParameter2->setText(QString::fromUtf8("\u03bc =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_poisson: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText(QString::fromUtf8("\u03bb =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_bernoulli: case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: ui.lParameter2->hide(); ui.leParameter2->hide(); if (dist == nsl_sf_stats_bernoulli) ui.lFunc->setText(""); else ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); ui.leParameter1->setText("0.5"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); - ui.lParameter1->setText("n ="); + ui.lParameter2->setText("n ="); ui.leParameter1->setText("0.5"); ui.leParameter2->setText("100"); break; case nsl_sf_stats_hypergeometric: ui.lParameter3->show(); ui.leParameter3->show(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("n1 ="); ui.lParameter2->setText("n2 ="); ui.lParameter3->setText("t ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("2.0"); ui.leParameter3->setText("3.0"); break; case nsl_sf_stats_maxwell_boltzmann: // additional non-GSL distros case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[dist]) + ".jpg"); ui.lFuncPic->setPixmap(QPixmap(file)); } void RandomValuesDialog::checkValues() { if (ui.leParameter1->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter2->isVisible() && ui.leParameter2->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter3->isVisible() && ui.leParameter3->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } m_okButton->setEnabled(true); return; } void RandomValuesDialog::generate() { Q_ASSERT(m_spreadsheet); //create a generator chosen by the environment variable GSL_RNG_TYPE gsl_rng_env_setup(); const gsl_rng_type* T = gsl_rng_default; gsl_rng* r = gsl_rng_alloc(T); WAIT_CURSOR; foreach (Column* col, m_columns) col->setSuppressDataChangedSignal(true); m_spreadsheet->beginMacro(i18np("%1: fill column with non-uniform random numbers", "%1: fill columns with non-uniform random numbers", m_spreadsheet->name(), m_columns.size())); const int index = ui.cbDistribution->currentIndex(); const nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); + DEBUG("random number distribution: " << nsl_sf_stats_distribution_name[dist]); const int rows = m_spreadsheet->rowCount(); QVector new_data(rows); switch (dist) { case nsl_sf_stats_gaussian: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); + DEBUG(" mu = " << mu << ", sigma = " << sigma); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_gaussian(r, sigma) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_gaussian_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); double a = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_gaussian_tail(r, a, sigma) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_exponential: { double l = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { //GSL uses the inverse for exp. distrib. for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_exponential(r, 1./l) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_laplace: { - double s = ui.leParameter1->text().toDouble(); - double mu = ui.leParameter2->text().toDouble(); + double mu = ui.leParameter1->text().toDouble(); + double s = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_laplace(r, s) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_exponential_power: { double mu = ui.leParameter1->text().toDouble(); double a = ui.leParameter2->text().toDouble(); - double b = ui.leParameter2->text().toDouble(); + double b = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_exppow(r, a, b) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_cauchy_lorentz: { double gamma = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_cauchy(r, gamma) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_rayleigh: { double s = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_rayleigh(r, s); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_rayleigh_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_rayleigh_tail(r, mu, sigma); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_landau: foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_landau(r); col->replaceValues(0, new_data); } break; case nsl_sf_stats_levy_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_levy(r, c, alpha); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_levy_skew_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); double beta = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_levy_skew(r, c, alpha, beta); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_gamma: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_gamma(r, a, b); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_flat: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_flat(r, a, b); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_lognormal: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_lognormal(r, mu, s); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_chi_squared: { double n = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_chisq(r, n); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_fdist: { double nu1 = ui.leParameter1->text().toDouble(); double nu2 = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_fdist(r, nu1, nu2); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_tdist: { double nu = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_tdist(r, nu); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_beta: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_beta(r, a, b); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_logistic: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_logistic(r, s) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_pareto: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_pareto(r, a, b); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_weibull: { double k = ui.leParameter1->text().toDouble(); double l = ui.leParameter2->text().toDouble(); - double mu = ui.leParameter2->text().toDouble(); + double mu = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_weibull(r, l, k) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_gumbel1: { double s = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_gumbel1(r, 1./s, b) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_gumbel2: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_gumbel2(r, a, b) + mu; col->replaceValues(0, new_data); } break; } case nsl_sf_stats_poisson: { double l = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_poisson(r, l); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_bernoulli: { double p = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_bernoulli(r, p); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_binomial(r, p, n); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_negative_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_negative_binomial(r, p, n); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_pascal: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_pascal(r, p, n); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_geometric: { double p = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_geometric(r, p); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_hypergeometric: { double n1 = ui.leParameter1->text().toDouble(); double n2 = ui.leParameter2->text().toDouble(); double t = ui.leParameter3->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_hypergeometric(r, n1, n2, t); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_logarithmic: { double p = ui.leParameter1->text().toDouble(); foreach (Column* col, m_columns) { for (int i = 0; i < rows; ++i) new_data[i] = gsl_ran_logarithmic(r, p); col->replaceValues(0, new_data); } break; } case nsl_sf_stats_maxwell_boltzmann: // additional non-GSL distros case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } foreach (Column* col, m_columns) { col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; gsl_rng_free(r); } diff --git a/src/kdefrontend/ui/dockwidgets/xyfitcurvedockgeneraltab.ui b/src/kdefrontend/ui/dockwidgets/xyfitcurvedockgeneraltab.ui index 56a310bef..fbd7d7f91 100644 --- a/src/kdefrontend/ui/dockwidgets/xyfitcurvedockgeneraltab.ui +++ b/src/kdefrontend/ui/dockwidgets/xyfitcurvedockgeneraltab.ui @@ -1,897 +1,886 @@ XYFitCurveDockGeneralTab 0 0 919 1018 true - - - - - - 6 - - - -999999999.000000000000000 - - - 999999999.000000000000000 - - - - - - - - 0 - 0 - - - - .. - - - - - - - 6 - - - -999999999.000000000000000 - - - 999999999.000000000000000 - - - - - - - - - 1 - - - 50 - - - 1 - - - - - - - x-Data/Error - - - - - - - y-Data/Error - - - - - - - Weight - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 23 - - - - - - - - Comment - - - - - - - - - - f(x) = - - - - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - - - - Degree - - - - - - - - 75 - true - - - - Fit: - - - - - - - Category - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - - - Qt::Horizontal - - - - 41 - 20 - - - - - - - - - + + 75 true - Results: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Data: - - - - Name - - + + - - - - Recalculate + + + + + 0 + 0 + - - + + 0 0 + + + Curve - - - - Qt::Horizontal - - - - 97 - 20 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 10 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 13 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 13 - - - - 0 0 16777215 16777215 QFrame::NoFrame QFrame::Raised 0 0 0 0 16777215 16777215 Functions Constants - - + + + + Qt::Horizontal + + + + + - visible + x-Data + + + + + + + f(x) = + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 23 + - + - - - - - 75 - true - - + + - Data: + y-Data - - + + - Source + x-Error Model - - - - - - - x-Range - - - - - + + - Auto - - - true + visible - - + + Qt::Vertical QSizePolicy::Fixed - 24 + 20 + 13 + + + + + + + + + 75 + true + + + + Fit: + + + + + + + Qt::Horizontal + + + + 97 20 - - - - Specify parameters and their properties + + + + Qt::Horizontal + + + + - Parameters + Source true 0 Parameters true 7 false 75 15 false false Goodness of fit true 11 3 false true 200 50 true false Sum of squared residuals Mean square error Root mean square error RMSE, SD Coefficient of determination Adj. coefficient of determ. F test F P > F Mean absolute error MAE Akaike information criterion AIC Bayesian information criterion BIC Log true 8 2 false true 150 true false false Status Iterations Tolerance Calculation time Degrees of freedom Number of parameter X range Iterations - - + + + + Name + + + + + + + + 75 + true + + + + Results: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + - Qt::Horizontal + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 13 + + + + + + + + Comment + + + + + + + Recalculate + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 24 + 20 + + + + + + + + y-Weight + + + + + + + Specify parameters and their properties + + + Parameters + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Degree Advanced fit options Options + + + + 1 + + + 50 + + + 1 + + + + + + + Category + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + + + Qt::Horizontal + + + + 41 + 20 + + + + + + + + + + + + 75 + true + + + + Errors: + + + + + + + + + + + + + false + + + col = + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + col = + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + KComboBox QComboBox
kcombobox.h
ExpressionTextEdit QTextEdit
kdefrontend/widgets/ExpressionTextEdit.h
diff --git a/src/kdefrontend/ui/fitoptionswidget.ui b/src/kdefrontend/ui/fitoptionswidget.ui index 1db0c7d10..42d858431 100644 --- a/src/kdefrontend/ui/fitoptionswidget.ui +++ b/src/kdefrontend/ui/fitoptionswidget.ui @@ -1,127 +1,159 @@ FitOptionsWidget 0 0 - 244 - 298 + 247 + 364 Qt::Horizontal 129 20 Apply Cancel - - + + + + <html><head/><body><p>This option can be used to turn on and off the usage of given data errors when fitting.</p></body></html> + + + Use given data errors + + + true + + + + + + + + + + + + + Auto x-Range + + + true + + + + + Max. iterations - - + + + + + false Robust fit - - - - - - - <html><head/><body><p>If selected, the resulting fit parameter are set as new start values. </p></body></html> + + + + Tolerance + + + + - Use results as new start values + Evaluated points - + <html><head/><body><p>If selected, the fit function is evaluated for the full range of data points and not only for the given x-range.</p></body></html> Evaluate full range - - - - - - Tolerance + + true - - + + - - + + + + <html><head/><body><p>If selected, the resulting fit parameter are set as new start values. </p></body></html> + - Evaluated Points + Use results as new start values + + + true - - - - <html><head/><body><p>This option can be used to turn on and off the usage of given data errors when fitting.</p></body></html> - + + - Use given data errors + .. diff --git a/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp b/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp index 54da4891e..9c893f115 100644 --- a/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp +++ b/src/kdefrontend/widgets/FITSHeaderEditWidget.cpp @@ -1,629 +1,629 @@ /*************************************************************************** File : FITSHeaderEditWidget.cpp Project : LabPlot Description : Widget for listing/editing FITS header keywords -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FITSHeaderEditWidget.h" #include "ui_fitsheadereditwidget.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" #include "FITSHeaderEditNewKeywordDialog.h" #include "FITSHeaderEditAddUnitDialog.h" #include #include #include #include #include #include #include /*! \class FITSHeaderEditWidget * \brief Widget for listing/editing FITS header keywords * \since 2.4.0 * \ingroup kdefrontend/widgets */ FITSHeaderEditWidget::FITSHeaderEditWidget(QWidget* parent) : QWidget(parent), ui(new Ui::FITSHeaderEditWidget()), m_fitsFilter(new FITSFilter()), m_initializingTable(false) { ui->setupUi(this); initActions(); connectActions(); initContextMenus(); ui->bOpen->setIcon(QIcon::fromTheme("document-open")); ui->bAddKey->setIcon(QIcon::fromTheme("list-add")); ui->bAddKey->setEnabled(false); ui->bAddKey->setToolTip(i18n("Add new keyword")); ui->bRemoveKey->setIcon(QIcon::fromTheme("list-remove")); ui->bRemoveKey->setEnabled(false); ui->bRemoveKey->setToolTip(i18n("Remove selected keyword")); ui->bAddUnit->setIcon(QIcon::fromTheme("document-new")); ui->bAddUnit->setEnabled(false); ui->bAddUnit->setToolTip(i18n("Add unit to keyword")); ui->bClose->setIcon(QIcon::fromTheme("document-close")); ui->bClose->setEnabled(false); ui->bClose->setToolTip(i18n("Close file")); ui->twKeywordsTable->setColumnCount(3); ui->twExtensions->setSelectionMode(QAbstractItemView::SingleSelection); ui->twExtensions->headerItem()->setText(0, i18n("Content")); ui->twKeywordsTable->setHorizontalHeaderItem(0, new QTableWidgetItem(i18n("Key"))); ui->twKeywordsTable->setHorizontalHeaderItem(1, new QTableWidgetItem(i18n("Value"))); ui->twKeywordsTable->setHorizontalHeaderItem(2, new QTableWidgetItem(i18n("Comment"))); ui->twKeywordsTable->setAlternatingRowColors(true); ui->twKeywordsTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); ui->twKeywordsTable->horizontalHeader()->setStretchLastSection(true); ui->twKeywordsTable->installEventFilter(this); ui->twExtensions->installEventFilter(this); setAttribute(Qt::WA_DeleteOnClose); connect(ui->bAddUnit, &QPushButton::clicked, m_action_addmodify_unit, &QAction::triggered); connect(ui->bClose, &QPushButton::clicked, this, &FITSHeaderEditWidget::closeFile); connect(ui->bOpen, &QPushButton::clicked, this, &FITSHeaderEditWidget::openFile); connect(ui->bAddKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::addKeyword); connect(ui->bRemoveKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::removeKeyword); connect(ui->twKeywordsTable, &QTableWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonAddUnit); connect(ui->twKeywordsTable, &QTableWidget::itemChanged, this, &FITSHeaderEditWidget::updateKeyword); connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::fillTableSlot); connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonCloseFile); } /*! * \brief Destructor */ FITSHeaderEditWidget::~FITSHeaderEditWidget() { delete m_fitsFilter; } /*! * \brief Fills the keywords tablewidget. * If the selected extension was not yet selected before, then the keywords are read from the file * and then the table is filled, otherwise the table is filled using the already existing keywords. */ void FITSHeaderEditWidget::fillTable() { m_initializingTable = true; if (!m_extensionDatas.contains(m_seletedExtension)) { m_extensionDatas[m_seletedExtension].keywords = m_fitsFilter->chduKeywords(m_seletedExtension); m_extensionDatas[m_seletedExtension].updates.updatedKeywords.reserve(m_extensionDatas[m_seletedExtension].keywords.size()); m_extensionDatas[m_seletedExtension].updates.updatedKeywords.resize(m_extensionDatas[m_seletedExtension].keywords.size()); m_fitsFilter->parseHeader(m_seletedExtension, ui->twKeywordsTable); } else { QList keywords = m_extensionDatas[m_seletedExtension].keywords; for (int i = 0; i < m_extensionDatas[m_seletedExtension].updates.updatedKeywords.size(); ++i) { FITSFilter::Keyword keyword = m_extensionDatas[m_seletedExtension].updates.updatedKeywords.at(i); if (!keyword.key.isEmpty()) keywords.operator [](i).key = keyword.key; if (!keyword.value.isEmpty()) keywords.operator [](i).value = keyword.value; if (!keyword.comment.isEmpty()) keywords.operator [](i).comment = keyword.comment; } for (const FITSFilter::Keyword& key : m_extensionDatas[m_seletedExtension].updates.newKeywords) keywords.append(key); m_fitsFilter->parseHeader(QString(), ui->twKeywordsTable, false, keywords); } m_initializingTable = false; } /*! * \brief Fills the tablewidget with the keywords of extension \a item * \param item the extension selected * \param col the column of the selected item */ void FITSHeaderEditWidget::fillTableSlot(QTreeWidgetItem *item, int col) { WAIT_CURSOR; const QString& itemText = item->text(col); QString selectedExtension; int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(QLatin1String("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != 0) { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(0) + '[' + item->text(col) + ']'; } } else if (extType == 1) { if (item->parent() != 0) { if (item->parent()->parent() != 0) { bool ok; int hduNum = itemText.right(1).toInt(&ok); selectedExtension = item->parent()->parent()->text(0) + '[' + QString::number(hduNum-1) + ']'; } } } else { if (item->parent()->parent() != 0) selectedExtension = item->parent()->parent()->text(col); } if (!selectedExtension.isEmpty()) { if (!(m_seletedExtension == selectedExtension)) { m_seletedExtension = selectedExtension; fillTable(); } } RESET_CURSOR; } /*! * \brief Shows a dialog for opening a FITS file * If the returned file name is not empty (so a FITS file was selected) and it's not opened yet * then the file is parsed, so the treeview for the extensions is built and the table is filled. */ void FITSHeaderEditWidget::openFile() { KConfigGroup conf(KSharedConfig::openConfig(), "FITSHeaderEditWidget"); QString dir = conf.readEntry("LastDir", ""); QString fileName = QFileDialog::getOpenFileName(this, i18n("Open FITS file"), dir, - i18n("FITS files (*.fits)")); + i18n("FITS files (*.fits *.fit *.fts)")); if (fileName.isEmpty()) return; int pos = fileName.lastIndexOf(QDir::separator()); if (pos!=-1) { QString newDir = fileName.left(pos); if (newDir!=dir) conf.writeEntry("LastDir", newDir); } WAIT_CURSOR; QTreeWidgetItem* root = ui->twExtensions->invisibleRootItem(); const int childCount = root->childCount(); bool opened = false; for (int i = 0; i < childCount; ++i) { if(root->child(i)->text(0) == fileName) { opened = true; break; } } if (!opened) { for (QTreeWidgetItem* item : ui->twExtensions->selectedItems()) item->setSelected(false); m_fitsFilter->parseExtensions(fileName, ui->twExtensions); ui->twExtensions->resizeColumnToContents(0); if (ui->twExtensions->selectedItems().size() > 0) fillTableSlot(ui->twExtensions->selectedItems().at(0), 0); ui->bAddKey->setEnabled(true); ui->bRemoveKey->setEnabled(true); ui->bAddUnit->setEnabled(true); ui->bClose->setEnabled(false); } else { KMessageBox::information(this, i18n("Cannot open file, file already opened."), i18n("File already opened")); } enableButtonAddUnit(); RESET_CURSOR; } /*! * \brief Triggered when clicking the Save button * Saves the modifications (new keywords, new keyword units, keyword modifications, * deleted keywords, deleted extensions) to the FITS files. * \return \c true if there was something saved, otherwise false */ bool FITSHeaderEditWidget::save() { bool saved = false; for (const QString& fileName : m_extensionDatas.keys()) { if (m_extensionDatas[fileName].updates.newKeywords.size() > 0) { m_fitsFilter->addNewKeyword(fileName,m_extensionDatas[fileName].updates.newKeywords); if (!saved) saved = true; } if (m_extensionDatas[fileName].updates.removedKeywords.size() > 0) { m_fitsFilter->deleteKeyword(fileName, m_extensionDatas[fileName].updates.removedKeywords); if (!saved) saved = true; } if (!saved) { for (const FITSFilter::Keyword& key : m_extensionDatas[fileName].updates.updatedKeywords) { if (!key.isEmpty()) { saved = true; break; } } } m_fitsFilter->updateKeywords(fileName, m_extensionDatas[fileName].keywords, m_extensionDatas[fileName].updates.updatedKeywords); m_fitsFilter->addKeywordUnit(fileName, m_extensionDatas[fileName].keywords); m_fitsFilter->addKeywordUnit(fileName, m_extensionDatas[fileName].updates.newKeywords); } if (m_removedExtensions.size() > 0) { m_fitsFilter->removeExtensions(m_removedExtensions); if (!saved) saved = true; } if (saved) { //to reset the window title emit changed(false); } return saved; } /*! * \brief Initializes the context menu's actions. */ void FITSHeaderEditWidget::initActions() { m_action_add_keyword = new QAction(QIcon::fromTheme("list-add"), i18n("Add new keyword"), this); m_action_remove_keyword = new QAction(QIcon::fromTheme("list-remove"), i18n("Remove keyword"), this); m_action_remove_extension = new QAction(i18n("Delete"), this); m_action_addmodify_unit = new QAction(i18n("Add unit"), this); } /*! * \brief Connects signals of the actions to the appropriate slots. */ void FITSHeaderEditWidget::connectActions() { connect(m_action_add_keyword, SIGNAL(triggered()), this, SLOT(addKeyword())); connect(m_action_remove_keyword, SIGNAL(triggered()), this, SLOT(removeKeyword())); connect(m_action_remove_extension, SIGNAL(triggered()), this, SLOT(removeExtension())); connect(m_action_addmodify_unit, SIGNAL(triggered()), this, SLOT(addModifyKeywordUnit())); } /*! * \brief Initializes the context menus. */ void FITSHeaderEditWidget::initContextMenus() { m_KeywordActionsMenu = new QMenu(this); m_KeywordActionsMenu->addAction(m_action_add_keyword); m_KeywordActionsMenu->addAction(m_action_remove_keyword); m_KeywordActionsMenu->addSeparator(); m_KeywordActionsMenu->addAction(m_action_addmodify_unit); m_ExtensionActionsMenu = new QMenu(this); m_ExtensionActionsMenu->addAction(m_action_remove_extension); } /*! * \brief Shows a FITSHeaderEditNewKeywordDialog and decides whether the new keyword provided in the dialog * can be added to the new keywords or not. Updates the tablewidget if it's needed. */ void FITSHeaderEditWidget::addKeyword() { FITSHeaderEditNewKeywordDialog* newKeywordDialog = new FITSHeaderEditNewKeywordDialog; m_initializingTable = true; if (newKeywordDialog->exec() == QDialog::Accepted) { FITSFilter::Keyword newKeyWord = newKeywordDialog->newKeyword(); QList currentKeywords = m_extensionDatas[m_seletedExtension].keywords; for(const FITSFilter::Keyword& keyword : currentKeywords) { if (keyword.operator==(newKeyWord)) { KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot add keyword")); return; } } for(const FITSFilter::Keyword& keyword : m_extensionDatas[m_seletedExtension].updates.newKeywords) { if (keyword.operator==(newKeyWord)) { KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot add keyword")); return; } } for(const QString& keyword : mandatoryKeywords()) { if (!keyword.compare(newKeyWord.key)) { KMessageBox::information(this, i18n("Cannot add mandatory keyword, they are already present"), i18n("Cannot add keyword")); return; } } /* - Column related keyword (TFIELDS, TTYPEn,TFORMn, etc.) in an image - SIMPLE, EXTEND, or BLOCKED keyword in any extension - BSCALE, BZERO, BUNIT, BLANK, DATAMAX, DATAMIN keywords in a table - Keyword name contains illegal character */ m_extensionDatas[m_seletedExtension].updates.newKeywords.append(newKeyWord); const int lastRow = ui->twKeywordsTable->rowCount(); ui->twKeywordsTable->setRowCount(lastRow + 1); QTableWidgetItem* newKeyWordItem = new QTableWidgetItem(newKeyWord.key); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 0, newKeyWordItem); newKeyWordItem = new QTableWidgetItem(newKeyWord.value); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 1, newKeyWordItem); newKeyWordItem = new QTableWidgetItem(newKeyWord.comment); newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui->twKeywordsTable->setItem(lastRow, 2, newKeyWordItem); emit changed(true); } m_initializingTable = false; delete newKeywordDialog; } /*! * \brief Shows a messagebox whether we want to remove the keyword or not. * Mandatory keywords cannot be deleted. */ void FITSHeaderEditWidget::removeKeyword() { const int row = ui->twKeywordsTable->currentRow(); if (row == -1) return; QString key = ui->twKeywordsTable->item(row, 0)->text(); const int rc = KMessageBox::questionYesNo(this, i18n("Are you sure you want to delete the keyword '%1'?").arg(key), i18n("Confirm deletion")); if (rc == KMessageBox::Yes) { bool remove = true; for(const QString& k : mandatoryKeywords()) { if (!k.compare(key)) { remove = false; break; } } if (remove) { FITSFilter::Keyword toRemove = FITSFilter::Keyword(key, ui->twKeywordsTable->item(row, 1)->text(), ui->twKeywordsTable->item(row, 2)->text()); ui->twKeywordsTable->removeRow(row); m_extensionDatas[m_seletedExtension].keywords.removeAt(row); m_extensionDatas[m_seletedExtension].updates.removedKeywords.append(toRemove); emit changed(true); } else KMessageBox::information(this, i18n("Cannot remove mandatory keyword."), i18n("Removing keyword")); } enableButtonAddUnit(); } /*! * \brief Trigggered when an item was updated by the user in the tablewidget * \param item the item which was updated */ void FITSHeaderEditWidget::updateKeyword(QTableWidgetItem *item) { if (!m_initializingTable) { const int row = item->row(); int idx; bool fromNewKeyword = false; if (row > m_extensionDatas[m_seletedExtension].keywords.size()-1) { idx = row - m_extensionDatas[m_seletedExtension].keywords.size(); fromNewKeyword = true; } else idx = row; if (item->column() == 0) { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).key = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.keyUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).key = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.keyUpdated = true; } } else if (item->column() == 1) { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).value = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.valueUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).value = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.valueUpdated = true; } } else { if (!fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.updatedKeywords.operator [](idx).comment = item->text(); m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.commentUpdated = true; } else { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).comment = item->text(); m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.commentUpdated = true; } } emit changed(true); } } /*! * \brief Shows a FITSHeaderEditAddUnitDialog on the selected keyword (provides the keyword's unit to the * dialog if it had one) and if the dialog was accepted then the new keyword unit is set and the tablewidget * is updated (filled with the modifications). */ void FITSHeaderEditWidget::addModifyKeywordUnit() { FITSHeaderEditAddUnitDialog* addUnitDialog; const int selectedRow = ui->twKeywordsTable->currentRow(); int idx; bool fromNewKeyword = false; if (selectedRow > m_extensionDatas[m_seletedExtension].keywords.size()-1) { idx = selectedRow - m_extensionDatas[m_seletedExtension].keywords.size(); fromNewKeyword = true; } else idx = selectedRow; QString unit; if (fromNewKeyword) { if (!m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty()) unit = m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit; } else { if (!m_extensionDatas[m_seletedExtension].keywords.at(idx).unit.isEmpty()) unit = m_extensionDatas[m_seletedExtension].keywords.at(idx).unit; } addUnitDialog = new FITSHeaderEditAddUnitDialog(unit); if (addUnitDialog->exec() == QDialog::Accepted) { if (fromNewKeyword) { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).unit = addUnitDialog->unit(); if (!m_extensionDatas[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty()) { m_extensionDatas[m_seletedExtension].updates.newKeywords.operator [](idx).updates.unitUpdated = true;; } } else { m_extensionDatas[m_seletedExtension].keywords.operator [](idx).unit = addUnitDialog->unit(); if (!m_extensionDatas[m_seletedExtension].keywords.at(idx).unit.isEmpty()) m_extensionDatas[m_seletedExtension].keywords.operator [](idx).updates.unitUpdated = true; } emit changed(true); fillTable(); } delete addUnitDialog; } /*! * \brief Removes the selected extension from the extensions treeview * If the last extension is removed from the tree, then the extension and the file will be removed too. */ void FITSHeaderEditWidget::removeExtension() { QTreeWidgetItem* current = ui->twExtensions->currentItem(); QTreeWidgetItem* newCurrent = ui->twExtensions->itemBelow(current); if (current->parent()) { if (current->parent()->childCount() < 2) delete current->parent(); else delete current; } const QStringList keys = m_extensionDatas.keys(); const int selectedidx = keys.indexOf(m_seletedExtension); if (selectedidx > 0) { const QString& ext = m_seletedExtension; m_extensionDatas.remove(ext); m_removedExtensions.append(ext); m_seletedExtension = keys.at(selectedidx-1); fillTable(); } ui->twExtensions->setCurrentItem(newCurrent); emit changed(true); } /*! * \brief Returns a list of mandatory keywords according to the currently selected extension. * If the currently selected extension is an image then it returns the mandatory keywords of an image, * otherwise the mandatory keywords of a table * \return a list of mandatory keywords */ QList FITSHeaderEditWidget::mandatoryKeywords() const { QList mandatoryKeywords; const QTreeWidgetItem* currentItem = ui->twExtensions->currentItem(); if (currentItem->parent()->text(0).compare(QLatin1String("Images"))) mandatoryKeywords = FITSFilter::mandatoryImageExtensionKeywords(); else mandatoryKeywords = FITSFilter::mandatoryTableExtensionKeywords(); return mandatoryKeywords; } /*! * \brief Manipulates the contextmenu event of the widget * \param watched the object on which the event occurred * \param event the event watched * \return */ bool FITSHeaderEditWidget::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *cm_event = static_cast(event); const QPoint& global_pos = cm_event->globalPos(); if (watched == ui->twKeywordsTable) { if (ui->twExtensions->selectedItems().size() != 0) m_KeywordActionsMenu->exec(global_pos); } else if (watched == ui->twExtensions) { if (ui->twExtensions->selectedItems().size() != 0) { QTreeWidgetItem* current = ui->twExtensions->currentItem(); int col = ui->twExtensions->currentColumn(); if (current->parent()) { if ((current->text(col) != QLatin1String("Images")) && (current->text(col) != QLatin1String("Tables"))) m_ExtensionActionsMenu->exec(global_pos); } } } else return QWidget::eventFilter(watched, event); return true; } else return QWidget::eventFilter(watched, event); } void FITSHeaderEditWidget::closeFile() { if (ui->twExtensions->currentItem()) { QTreeWidgetItem* current = ui->twExtensions->currentItem(); int idxOfCurrentAsTopLevel = -1; for (int i = 0; i < ui->twExtensions->topLevelItemCount(); ++i) { if (current == ui->twExtensions->topLevelItem(i)) { idxOfCurrentAsTopLevel = i; break; } } QTreeWidgetItem* newCurrent = (QTreeWidgetItem*)0; if (idxOfCurrentAsTopLevel == 0) { if (ui->twExtensions->topLevelItemCount() == 1) { //last file closed, deactivate action buttons, clear keywords table ui->twKeywordsTable->setRowCount(0); ui->bClose->setEnabled(false); ui->bAddUnit->setEnabled(false); ui->bAddKey->setEnabled(false); ui->bRemoveKey->setEnabled(false); } else newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel + 1); } else newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel - 1); if (newCurrent) { m_seletedExtension = newCurrent->text(0); fillTable(); } for(const QString& key : m_extensionDatas.keys()) { if (key.startsWith(current->text(0))) m_extensionDatas.remove(key); } delete current; enableButtonAddUnit(); emit changed(true); } } void FITSHeaderEditWidget::enableButtonAddUnit() { if (ui->twKeywordsTable->currentItem() != nullptr) ui->bAddUnit->setEnabled(true); else ui->bAddUnit->setEnabled(false); } void FITSHeaderEditWidget::enableButtonCloseFile(QTreeWidgetItem* item,int col) { Q_UNUSED(col) ui->bClose->setEnabled(item->parent() ? false : true); } diff --git a/src/kdefrontend/widgets/FitOptionsWidget.cpp b/src/kdefrontend/widgets/FitOptionsWidget.cpp index 6afc9bfbb..df55c2e90 100644 --- a/src/kdefrontend/widgets/FitOptionsWidget.cpp +++ b/src/kdefrontend/widgets/FitOptionsWidget.cpp @@ -1,86 +1,138 @@ /*************************************************************************** File : FitOptionsWidget.cc Project : LabPlot Description : widget for editing advanced fit options -------------------------------------------------------------------- Copyright : (C) 2014 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FitOptionsWidget.h" /*! \class FitOptionsWidget \brief Widget for editing advanced fit options. \ingroup kdefrontend */ -FitOptionsWidget::FitOptionsWidget(QWidget *parent, XYFitCurve::FitData* fitData): QWidget(parent), m_fitData(fitData), m_changed(false) { +FitOptionsWidget::FitOptionsWidget(QWidget *parent, XYFitCurve::FitData* fitData, XYFitCurve* fitCurve): + QWidget(parent), m_fitData(fitData), m_fitCurve(fitCurve), m_changed(false) { ui.setupUi(this); ui.pbApply->setIcon(QIcon::fromTheme("dialog-ok-apply")); ui.pbCancel->setIcon(QIcon::fromTheme("dialog-cancel")); //TODO: show "robust" option when robust fitting is possible // ui.cbRobust->addItem(i18n("on")); // ui.cbRobust->addItem(i18n("off")); ui.lRobust->setVisible(false); ui.cbRobust->setVisible(false); ui.leEps->setValidator( new QDoubleValidator(ui.leEps) ); ui.leMaxIterations->setValidator( new QIntValidator(ui.leMaxIterations) ); ui.leEvaluatedPoints->setValidator( new QIntValidator(ui.leEvaluatedPoints) ); ui.leEps->setText(QString::number(m_fitData->eps)); ui.leMaxIterations->setText(QString::number(m_fitData->maxIterations)); ui.leEvaluatedPoints->setText(QString::number(m_fitData->evaluatedPoints)); + ui.cbAutoRange->setChecked(m_fitData->autoRange); + ui.sbMin->setValue(m_fitData->xRange.first()); + ui.sbMax->setValue(m_fitData->xRange.last()); + this->autoRangeChanged(); + ui.cbEvaluateFullRange->setChecked(m_fitData->evaluateFullRange); ui.cbUseDataErrors->setChecked(m_fitData->useDataErrors); ui.cbUseResults->setChecked(m_fitData->useResults); //SLOTS connect(ui.leEps, &QLineEdit::textChanged, this, &FitOptionsWidget::changed) ; connect(ui.leMaxIterations, &QLineEdit::textChanged, this, &FitOptionsWidget::changed); connect(ui.leEvaluatedPoints, &QLineEdit::textChanged, this, &FitOptionsWidget::changed) ; connect(ui.cbEvaluateFullRange, &QCheckBox::clicked, this, &FitOptionsWidget::changed) ; connect(ui.cbUseDataErrors, &QCheckBox::clicked, this, &FitOptionsWidget::changed) ; connect(ui.cbUseResults, &QCheckBox::clicked, this, &FitOptionsWidget::changed) ; connect(ui.pbApply, &QPushButton::clicked, this, &FitOptionsWidget::applyClicked); connect(ui.pbCancel, &QPushButton::clicked, this, &FitOptionsWidget::finished); + connect(ui.cbAutoRange, &QCheckBox::clicked, this, &FitOptionsWidget::autoRangeChanged); + connect(ui.sbMin, static_cast(&QDoubleSpinBox::valueChanged), this, &FitOptionsWidget::xRangeMinChanged); + connect(ui.sbMax, static_cast(&QDoubleSpinBox::valueChanged), this, &FitOptionsWidget::xRangeMaxChanged); +} + +void FitOptionsWidget::autoRangeChanged() { + const bool autoRange = ui.cbAutoRange->isChecked(); + m_fitData->autoRange = autoRange; + + if (autoRange) { + ui.sbMin->setEnabled(false); + ui.lXRange->setEnabled(false); + ui.sbMax->setEnabled(false); + + const AbstractColumn* xDataColumn = 0; + if (m_fitCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) + xDataColumn = m_fitCurve->xDataColumn(); + else { + if (m_fitCurve->dataSourceCurve()) + xDataColumn = m_fitCurve->dataSourceCurve()->xColumn(); + } + + if (xDataColumn) { + ui.sbMin->setValue(xDataColumn->minimum()); + ui.sbMax->setValue(xDataColumn->maximum()); + } + } else { + ui.sbMin->setEnabled(true); + ui.lXRange->setEnabled(true); + ui.sbMax->setEnabled(true); + } + } +void FitOptionsWidget::xRangeMinChanged() { + const double xMin = ui.sbMin->value(); + + m_fitData->xRange.first() = xMin; + changed(); +} + +void FitOptionsWidget::xRangeMaxChanged() { + const double xMax = ui.sbMax->value(); + + m_fitData->xRange.last() = xMax; + changed(); +} + void FitOptionsWidget::applyClicked() { m_fitData->maxIterations = ui.leMaxIterations->text().toFloat(); m_fitData->eps = ui.leEps->text().toFloat(); m_fitData->evaluatedPoints = ui.leEvaluatedPoints->text().toInt(); m_fitData->evaluateFullRange = ui.cbEvaluateFullRange->isChecked(); m_fitData->useDataErrors = ui.cbUseDataErrors->isChecked(); m_fitData->useResults = ui.cbUseResults->isChecked(); if (m_changed) emit optionsChanged(); emit finished(); } void FitOptionsWidget::changed() { m_changed = true; } diff --git a/src/kdefrontend/widgets/FitOptionsWidget.h b/src/kdefrontend/widgets/FitOptionsWidget.h index de5330ddc..38ff35ca0 100644 --- a/src/kdefrontend/widgets/FitOptionsWidget.h +++ b/src/kdefrontend/widgets/FitOptionsWidget.h @@ -1,54 +1,59 @@ /*************************************************************************** File : FitOptionsWidget.h Project : LabPlot Description : widget for editing advanced fit parameters -------------------------------------------------------------------- Copyright : (C) 2014 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2017 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef FITOPTIONSWIDGET_H #define FITOPTIONSWIDGET_H #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "ui_fitoptionswidget.h" class FitOptionsWidget: public QWidget { Q_OBJECT public: - explicit FitOptionsWidget(QWidget*, XYFitCurve::FitData*); + explicit FitOptionsWidget(QWidget*, XYFitCurve::FitData*, XYFitCurve*); private: Ui::FitOptionsWidget ui; XYFitCurve::FitData* m_fitData; + XYFitCurve* m_fitCurve; bool m_changed; signals: void finished(); void optionsChanged(); private slots: + void autoRangeChanged(); + void xRangeMinChanged(); + void xRangeMaxChanged(); void applyClicked(); void changed(); }; #endif //FITOPTIONSWIDGET_H diff --git a/src/kdefrontend/widgets/FitParametersWidget.cpp b/src/kdefrontend/widgets/FitParametersWidget.cpp index 7ddd99c63..48ea2bf95 100644 --- a/src/kdefrontend/widgets/FitParametersWidget.cpp +++ b/src/kdefrontend/widgets/FitParametersWidget.cpp @@ -1,473 +1,471 @@ /*************************************************************************** File : FitParametersWidget.cc Project : LabPlot Description : widget for editing fit parameters -------------------------------------------------------------------- Copyright : (C) 2014-2016 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FitParametersWidget.h" #include #include #include #include -#include - /*! \class FitParametersWidget \brief Widget for editing fit parameters. For predefined models the number of parameters, their names and default values are given - the user can change the start values. For custom models the user has to define here the parameter names and their start values. \ingroup kdefrontend */ FitParametersWidget::FitParametersWidget(QWidget* parent, XYFitCurve::FitData* data) : QWidget(parent), m_fitData(data), m_changed(false), m_rehighlighting(false) { ui.setupUi(this); ui.pbApply->setIcon(QIcon::fromTheme("dialog-ok-apply")); ui.pbCancel->setIcon(QIcon::fromTheme("dialog-cancel")); ui.tableWidget->setColumnCount(5); QTableWidgetItem* headerItem = new QTableWidgetItem(); headerItem->setText(i18n("Name")); ui.tableWidget->setHorizontalHeaderItem(0, headerItem); headerItem = new QTableWidgetItem(); headerItem->setText(i18n("Start value")); ui.tableWidget->setHorizontalHeaderItem(1, headerItem); headerItem = new QTableWidgetItem(); headerItem->setText(i18n("Fixed")); ui.tableWidget->setHorizontalHeaderItem(2, headerItem); headerItem = new QTableWidgetItem(); headerItem->setText(i18n("Lower limit")); ui.tableWidget->setHorizontalHeaderItem(3, headerItem); headerItem = new QTableWidgetItem(); headerItem->setText(i18n("Upper limit")); ui.tableWidget->setHorizontalHeaderItem(4, headerItem); ui.tableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); ui.tableWidget->horizontalHeader()->setStretchLastSection(true); if (m_fitData->modelCategory != nsl_fit_model_custom) { // pre-defined model ui.tableWidget->setRowCount(m_fitData->paramNames.size()); for (int i=0; i < m_fitData->paramNames.size(); ++i){ // name QTableWidgetItem* item = new QTableWidgetItem(m_fitData->paramNamesUtf8.at(i)); item->setFlags(item->flags() ^ Qt::ItemIsEditable); item->setBackground(QBrush(Qt::lightGray)); ui.tableWidget->setItem(i, 0, item); // start value QLineEdit *le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); le->insert(QString::number(m_fitData->paramStartValues.at(i), 'g')); ui.tableWidget->setCellWidget(i, 1, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); // fixed QWidget *widget = new QWidget(); QCheckBox *cb = new QCheckBox(); cb->setChecked(m_fitData->paramFixed.at(i)); QHBoxLayout *cbl = new QHBoxLayout(widget); cbl->addWidget(cb); cbl->setAlignment(Qt::AlignCenter); cbl->setContentsMargins(0, 0, 0, 0); widget->setLayout(cbl); ui.tableWidget->setCellWidget(i, 2, widget); connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); // limits le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); - if (m_fitData->paramLowerLimits.at(i) > -DBL_MAX) + if (m_fitData->paramLowerLimits.at(i) > -std::numeric_limits::max()) le->insert(QString::number(m_fitData->paramLowerLimits.at(i), 'g')); ui.tableWidget->setCellWidget(i, 3, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); - if (m_fitData->paramUpperLimits.at(i) < DBL_MAX) + if (m_fitData->paramUpperLimits.at(i) < std::numeric_limits::max()) le->insert(QString::number(m_fitData->paramUpperLimits.at(i), 'g')); ui.tableWidget->setCellWidget(i, 4, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); } ui.tableWidget->setCurrentCell(0, 1); ui.pbAdd->setVisible(false); ui.pbRemove->setVisible(false); } else { // custom model if (!m_fitData->paramNames.isEmpty()) { // parameters for the custom model are already available -> show them ui.tableWidget->setRowCount(m_fitData->paramNames.size()); for (int i = 0; i < m_fitData->paramNames.size(); ++i){ // name QTableWidgetItem* item = new QTableWidgetItem(m_fitData->paramNames.at(i)); item->setBackground(QBrush(Qt::lightGray)); ui.tableWidget->setItem(i, 0, item); // start value QLineEdit *le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); le->insert(QString::number(m_fitData->paramStartValues.at(i), 'g')); ui.tableWidget->setCellWidget(i, 1, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); // fixed QWidget *widget = new QWidget(); QCheckBox *cb = new QCheckBox(); cb->setChecked(m_fitData->paramFixed.at(i)); QHBoxLayout *cbl = new QHBoxLayout(widget); cbl->addWidget(cb); cbl->setAlignment(Qt::AlignCenter); cbl->setContentsMargins(0, 0, 0, 0); widget->setLayout(cbl); ui.tableWidget->setCellWidget(i, 2, widget); connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); // limits le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); - if (m_fitData->paramLowerLimits.at(i) > -DBL_MAX) + if (m_fitData->paramLowerLimits.at(i) > -std::numeric_limits::max()) le->insert(QString::number(m_fitData->paramLowerLimits.at(i), 'g')); ui.tableWidget->setCellWidget(i, 3, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); - if (m_fitData->paramUpperLimits.at(i) < DBL_MAX) + if (m_fitData->paramUpperLimits.at(i) < std::numeric_limits::max()) le->insert(QString::number(m_fitData->paramUpperLimits.at(i), 'g')); ui.tableWidget->setCellWidget(i, 4, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); } } else { // no parameters available yet -> create the first row in the table for the first parameter ui.tableWidget->setRowCount(1); // name QTableWidgetItem* item = new QTableWidgetItem(); item->setBackground(QBrush(Qt::lightGray)); ui.tableWidget->setItem(0, 0, item); // start value QLineEdit *le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); ui.tableWidget->setCellWidget(0, 1, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); // fixed QWidget *widget = new QWidget(); QCheckBox *cb = new QCheckBox(); QHBoxLayout *cbl = new QHBoxLayout(widget); cbl->addWidget(cb); cbl->setAlignment(Qt::AlignCenter); cbl->setContentsMargins(0, 0, 0, 0); widget->setLayout(cbl); ui.tableWidget->setCellWidget(0, 2, widget); connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); // limits le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); ui.tableWidget->setCellWidget(0, 3, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); ui.tableWidget->setCellWidget(0, 4, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(upperLimitChanged()) ); } ui.tableWidget->setCurrentCell(0, 0); ui.pbAdd->setIcon(QIcon::fromTheme("list-add")); ui.pbAdd->setVisible(true); ui.pbRemove->setIcon(QIcon::fromTheme("list-remove")); ui.pbRemove->setVisible(true); ui.pbRemove->setEnabled(m_fitData->paramNames.size() > 1); } ui.tableWidget->installEventFilter(this); connect( ui.tableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(changed()) ); connect( ui.pbApply, SIGNAL(clicked()), this, SLOT(applyClicked()) ); connect( ui.pbCancel, SIGNAL(clicked()), this, SIGNAL(finished()) ); connect( ui.pbAdd, SIGNAL(clicked()), this, SLOT(addParameter()) ); connect( ui.pbRemove, SIGNAL(clicked()), this, SLOT(removeParameter()) ); } bool FitParametersWidget::eventFilter(QObject* watched, QEvent* event) { if (watched == ui.tableWidget) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { if (m_fitData->modelCategory != nsl_fit_model_custom) { //on the second column with the values is editable. //navigate to the next cell in the second column, or to the apply-button if (ui.tableWidget->currentRow() == ui.tableWidget->rowCount() - 1) { ui.pbApply->setFocus(); ui.tableWidget->clearSelection(); } else { ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow() + 1, 1); } } else { //both columns (names and start values) are editable if (ui.tableWidget->currentColumn() == 0) { //name was entered, navigate to the value-cell ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), 1); } else { //start value was entered, navigate to the next name-cell or to the apply-button if (ui.tableWidget->currentRow() == ui.tableWidget->rowCount() - 1) { ui.pbApply->setFocus(); ui.tableWidget->clearSelection(); } else { ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow() + 1, 0); } } } return true; } } } return QWidget::eventFilter(watched, event); } void FitParametersWidget::applyClicked() { if (m_fitData->modelCategory != nsl_fit_model_custom) { // pre-defined models for (int i=0; i < ui.tableWidget->rowCount(); ++i) { m_fitData->paramStartValues[i] = ((QLineEdit *)ui.tableWidget->cellWidget(i, 1))->text().toDouble(); QWidget *widget = ui.tableWidget->cellWidget(i, 2)->layout()->itemAt(0)->widget(); m_fitData->paramFixed[i] = (qobject_cast(widget))->isChecked(); if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().isEmpty() ) m_fitData->paramLowerLimits[i] = ((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().toDouble(); else - m_fitData->paramLowerLimits[i] = -DBL_MAX; + m_fitData->paramLowerLimits[i] = -std::numeric_limits::max(); if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().isEmpty() ) m_fitData->paramUpperLimits[i] = ((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().toDouble(); else - m_fitData->paramUpperLimits[i] = DBL_MAX; + m_fitData->paramUpperLimits[i] = std::numeric_limits::max(); } } else { // custom model m_fitData->paramNames.clear(); m_fitData->paramNamesUtf8.clear(); m_fitData->paramStartValues.clear(); m_fitData->paramFixed.clear(); m_fitData->paramLowerLimits.clear(); m_fitData->paramUpperLimits.clear(); for (int i=0; i < ui.tableWidget->rowCount(); ++i) { // skip those rows where either the name or the value is empty if ( !ui.tableWidget->item(i, 0)->text().simplified().isEmpty() && !((QLineEdit *)ui.tableWidget->cellWidget(i, 1))->text().simplified().isEmpty() ) { m_fitData->paramNames.append( ui.tableWidget->item(i, 0)->text() ); m_fitData->paramNamesUtf8.append( ui.tableWidget->item(i, 0)->text() ); m_fitData->paramStartValues.append( ((QLineEdit *)ui.tableWidget->cellWidget(i, 1))->text().toDouble() ); QWidget *widget = ui.tableWidget->cellWidget(i, 2)->layout()->itemAt(0)->widget(); m_fitData->paramFixed.append( (qobject_cast(widget))->isChecked() ); if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().isEmpty() ) m_fitData->paramLowerLimits.append( ((QLineEdit *)ui.tableWidget->cellWidget(i, 3))->text().toDouble() ); else - m_fitData->paramLowerLimits.append(-DBL_MAX); + m_fitData->paramLowerLimits.append(-std::numeric_limits::max()); if ( !((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().isEmpty() ) m_fitData->paramUpperLimits.append( ((QLineEdit *)ui.tableWidget->cellWidget(i, 4))->text().toDouble() ); else - m_fitData->paramUpperLimits.append(DBL_MAX); + m_fitData->paramUpperLimits.append(std::numeric_limits::max()); } } } if (m_changed) emit parametersChanged(); emit finished() ; } // check if start values are inside limits void FitParametersWidget::startValueChanged() { const int row = ui.tableWidget->currentRow(); const double value = ((QLineEdit *)ui.tableWidget->cellWidget(row, 1))->text().toDouble(); double lowerLimit, upperLimit; if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) lowerLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().toDouble(); else - lowerLimit = -DBL_MAX; + lowerLimit = -std::numeric_limits::max(); if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) upperLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().toDouble(); else - upperLimit = DBL_MAX; + upperLimit = std::numeric_limits::max(); const bool invalid = (value < lowerLimit || value > upperLimit); highlightInvalid(row, 1, invalid); if (m_rehighlighting) return; //start value was changed -> check whether the lower and upper limits are valid and highlight them if not m_rehighlighting = true; lowerLimitChanged(); upperLimitChanged(); m_rehighlighting = false; m_changed = true; } // check if lower limit fits to start value and upper limit void FitParametersWidget::lowerLimitChanged() { const int row = ui.tableWidget->currentRow(); const double value = ((QLineEdit *)ui.tableWidget->cellWidget(row, 1))->text().toDouble(); double lowerLimit, upperLimit; if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) lowerLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().toDouble(); else - lowerLimit = -DBL_MAX; + lowerLimit = -std::numeric_limits::max(); if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) upperLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().toDouble(); else - upperLimit = DBL_MAX; + upperLimit = std::numeric_limits::max(); const bool invalid = (lowerLimit > value || lowerLimit > upperLimit); highlightInvalid(row, 3, invalid); if (m_rehighlighting) return; //lower limit was changed -> check whether the start value and the upper limit are valid and highlight them if not m_rehighlighting = true; startValueChanged(); upperLimitChanged(); m_rehighlighting = false; m_changed = true; } // check if upper limit fits to start value and lower limit void FitParametersWidget::upperLimitChanged() { const int row = ui.tableWidget->currentRow(); const double value = ((QLineEdit *)ui.tableWidget->cellWidget(row, 1))->text().toDouble(); double lowerLimit, upperLimit; if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().isEmpty() ) lowerLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 3))->text().toDouble(); else - lowerLimit = -DBL_MAX; + lowerLimit = -std::numeric_limits::max(); if ( !((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().isEmpty() ) upperLimit = ((QLineEdit *)ui.tableWidget->cellWidget(row, 4))->text().toDouble(); else - upperLimit = DBL_MAX; + upperLimit = std::numeric_limits::max(); const bool invalid = (upperLimit < value || upperLimit < lowerLimit); highlightInvalid(row, 4, invalid); if (m_rehighlighting) return; //upper limit was changed -> check whether the start value and the lower limit are valid and highlight them if not m_rehighlighting = true; startValueChanged(); lowerLimitChanged(); m_rehighlighting = false; m_changed = true; } void FitParametersWidget::addParameter() { const int rows = ui.tableWidget->rowCount(); ui.tableWidget->setRowCount(rows+1); // name QTableWidgetItem* item = new QTableWidgetItem(); item->setBackground(QBrush(Qt::lightGray)); ui.tableWidget->setItem(rows, 0, item); // start value QLineEdit *le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); le->insert("1"); ui.tableWidget->setCellWidget(rows, 1, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(startValueChanged()) ); // fixed QWidget *widget = new QWidget(); QCheckBox *cb = new QCheckBox(); QHBoxLayout *cbl = new QHBoxLayout(widget); cbl->addWidget(cb); cbl->setAlignment(Qt::AlignCenter); cbl->setContentsMargins(0, 0, 0, 0); widget->setLayout(cbl); ui.tableWidget->setCellWidget(rows, 2, widget); connect(cb, SIGNAL(stateChanged(int)), this, SLOT(changed()) ); // limits le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); ui.tableWidget->setCellWidget(rows, 3, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); le = new QLineEdit(ui.tableWidget); le->setValidator(new QDoubleValidator(le)); le->setFrame(false); ui.tableWidget->setCellWidget(rows, 4, le); connect(le, SIGNAL(textChanged(QString)), this, SLOT(lowerLimitChanged()) ); ui.tableWidget->setCurrentCell(rows, 0); ui.pbRemove->setEnabled(true); changed(); } void FitParametersWidget::removeParameter() { ui.tableWidget->removeRow(ui.tableWidget->currentRow()); if (ui.tableWidget->rowCount() == 1) ui.pbRemove->setEnabled(false); changed(); } void FitParametersWidget::changed() { m_changed = true; } void FitParametersWidget::highlightInvalid(int row, int col, bool invalid) const { QLineEdit* le = ((QLineEdit*)ui.tableWidget->cellWidget(row, col)); if (invalid) le->setStyleSheet("QLineEdit{background: red;}"); else le->setStyleSheet(""); } diff --git a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp index 78f0f013e..5f10a0ead 100644 --- a/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp +++ b/src/kdefrontend/worksheet/ExportWorksheetDialog.cpp @@ -1,253 +1,270 @@ /*************************************************************************** File : ExportWorksheetDialog.cpp Project : LabPlot Description : export worksheet dialog -------------------------------------------------------------------- Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ExportWorksheetDialog.h" #include "ui_exportworksheetwidget.h" #include #include #include #include +#include #include #include #include #include #include /*! \class ExportWorksheetDialog \brief Dialog for exporting a worksheet to a file. \ingroup kdefrontend */ ExportWorksheetDialog::ExportWorksheetDialog(QWidget* parent) : QDialog(parent), - ui(new Ui::ExportWorksheetWidget()) { + ui(new Ui::ExportWorksheetWidget()), m_showOptions(true) { + ui->setupUi(this); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_showOptionsButton = new QPushButton; connect(btnBox, &QDialogButtonBox::clicked, this, &ExportWorksheetDialog::slotButtonClicked); btnBox->addButton(m_showOptionsButton, QDialogButtonBox::ActionRole); ui->verticalLayout->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); m_cancelButton = btnBox->button(QDialogButtonBox::Cancel); QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui->leFileName->setCompleter(completer); ui->bOpen->setIcon(QIcon::fromTheme("document-open")); - ui->cbFormat->addItem(QIcon::fromTheme("application-pdf"), "Portable data format (PDF)"); + ui->cbFormat->addItem(QIcon::fromTheme("application-pdf"), "Portable Data Format (PDF)"); ui->cbFormat->addItem(QIcon::fromTheme("image-svg+xml"), "Scalable Vector Graphics (SVG)"); ui->cbFormat->insertSeparator(3); ui->cbFormat->addItem(QIcon::fromTheme("image-x-generic"), "Portable Network Graphics (PNG)"); ui->cbExportArea->addItem(i18n("Object's bounding box")); ui->cbExportArea->addItem(i18n("Current selection")); ui->cbExportArea->addItem(i18n("Complete worksheet")); ui->cbResolution->addItem(QString::number(QApplication::desktop()->physicalDpiX()) + " (" + i18n("desktop") + ')'); ui->cbResolution->addItem("100"); ui->cbResolution->addItem("150"); ui->cbResolution->addItem("200"); ui->cbResolution->addItem("300"); ui->cbResolution->addItem("600"); ui->cbResolution->setValidator(new QIntValidator(ui->cbResolution)); connect(ui->cbFormat, static_cast(&KComboBox::currentIndexChanged), this, &ExportWorksheetDialog::formatChanged ); connect(ui->bOpen, &QPushButton::clicked, this, &ExportWorksheetDialog::selectFile); connect(ui->leFileName, &QLineEdit::textChanged, this, &ExportWorksheetDialog::fileNameChanged); connect(m_showOptionsButton, &QPushButton::clicked, this, &ExportWorksheetDialog::toggleOptions); + ui->leFileName->setFocus(); setWindowTitle(i18n("Export worksheet")); setWindowIcon(QIcon::fromTheme("document-export-database")); + QTimer::singleShot(0, this, &ExportWorksheetDialog::loadSettings); +} + +void ExportWorksheetDialog::loadSettings() { //restore saved setting KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); + + KWindowConfig::restoreWindowSize(windowHandle(), conf); + ui->cbFormat->setCurrentIndex(conf.readEntry("Format", 0)); ui->cbExportArea->setCurrentIndex(conf.readEntry("Area", 0)); ui->chkExportBackground->setChecked(conf.readEntry("Background", true)); ui->cbResolution->setCurrentIndex(conf.readEntry("Resolution", 0)); - m_showOptions = conf.readEntry("ShowOptions", false); - ui->gbOptions->setVisible(m_showOptions); + m_showOptions = conf.readEntry("ShowOptions", true); m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : - m_showOptionsButton->setText(i18n("Show Options")); - - KWindowConfig::restoreWindowSize(windowHandle(), conf); + m_showOptionsButton->setText(i18n("Show Options")); + ui->gbOptions->setVisible(m_showOptions); } ExportWorksheetDialog::~ExportWorksheetDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); conf.writeEntry("Format", ui->cbFormat->currentIndex()); conf.writeEntry("Area", ui->cbExportArea->currentIndex()); conf.writeEntry("Background", ui->chkExportBackground->isChecked()); conf.writeEntry("Resolution", ui->cbResolution->currentIndex()); conf.writeEntry("ShowOptions", m_showOptions); KWindowConfig::saveWindowSize(windowHandle(), conf); } void ExportWorksheetDialog::setFileName(const QString& name) { KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); QString dir = conf.readEntry("LastDir", ""); if (dir.isEmpty()) dir = QDir::homePath(); ui->leFileName->setText(dir + QDir::separator() + name); this->formatChanged(ui->cbFormat->currentIndex()); } QString ExportWorksheetDialog::path() const { return ui->leFileName->text(); } WorksheetView::ExportFormat ExportWorksheetDialog::exportFormat() const { int index = ui->cbFormat->currentIndex(); //we have a separator in the format combobox at the 3th position -> skip it if (index > 2) index--; return WorksheetView::ExportFormat(index); } WorksheetView::ExportArea ExportWorksheetDialog::exportArea() const { return WorksheetView::ExportArea(ui->cbExportArea->currentIndex()); } bool ExportWorksheetDialog::exportBackground() const { return ui->chkExportBackground->isChecked(); } int ExportWorksheetDialog::exportResolution() const { if (ui->cbResolution->currentIndex() == 0) return QApplication::desktop()->physicalDpiX(); else return ui->cbResolution->currentText().toInt(); } void ExportWorksheetDialog::slotButtonClicked(QAbstractButton* button) { if (button == m_okButton) okClicked(); else if (button == m_cancelButton) { reject(); } } //SLOTS void ExportWorksheetDialog::okClicked() { if ( QFile::exists(ui->leFileName->text()) ) { int r = KMessageBox::questionYesNo(this, i18n("The file already exists. Do you really want to overwrite it?"), i18n("Export")); if (r == KMessageBox::No) return; } KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); conf.writeEntry("Format", ui->cbFormat->currentIndex()); conf.writeEntry("Area", ui->cbExportArea->currentIndex()); conf.writeEntry("Resolution", ui->cbResolution->currentIndex()); QString path = ui->leFileName->text(); if (!path.isEmpty()) { QString dir = conf.readEntry("LastDir", ""); ui->leFileName->setText(path); int pos = path.lastIndexOf(QDir::separator()); if (pos!=-1) { QString newDir = path.left(pos); if (newDir!=dir) conf.writeEntry("LastDir", newDir); } } accept(); } /*! Shows/hides the GroupBox with export options in this dialog. */ void ExportWorksheetDialog::toggleOptions() { m_showOptions = !m_showOptions; ui->gbOptions->setVisible(m_showOptions); m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options")); //resize the dialog resize(layout()->minimumSize()); layout()->activate(); resize( QSize(this->width(),0).expandedTo(minimumSize()) ); } /*! opens a file dialog and lets the user select the file. */ void ExportWorksheetDialog::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ExportWorksheetDialog"); const QString dir = conf.readEntry("LastDir", ""); - const QString path = QFileDialog::getOpenFileName(this, i18n("Export to file"), dir); + + QString format; + if (ui->cbFormat->currentIndex() == 0) + format = i18n("Portable Data Format (PDF) (*.pdf *.PDF)"); + else if (ui->cbFormat->currentIndex() == 1) + format = i18n("Scalable Vector Graphics (SVG) (*.svg *.SVG)"); + else + format = i18n("Portable Network Graphics (PNG) (*.png *.PNG)"); + + const QString path = QFileDialog::getOpenFileName(this, i18n("Export to file"), dir, format); if (!path.isEmpty()) { ui->leFileName->setText(path); int pos = path.lastIndexOf(QDir::separator()); if (pos!=-1) { const QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } } } /*! called when the output format was changed. Adjusts the extension for the specified file. */ void ExportWorksheetDialog::formatChanged(int index) { - //we have a separator in the format combobox at the 4th posiiton -> skip it - if (index > 3) + //we have a separator in the format combobox at the 3rd posiiton -> skip it + if (index > 2) index --; QStringList extensions; - extensions<<".pdf"<<".eps"<<".svg"<<".png"; + extensions << ".pdf" << ".svg" << ".png"; QString path = ui->leFileName->text(); int i = path.indexOf("."); if (i == -1) path = path + extensions.at(index); else path = path.left(i) + extensions.at(index); ui->leFileName->setText(path); // show resolution option for png format ui->lResolution->setVisible(index == 3); ui->cbResolution->setVisible(index == 3); } void ExportWorksheetDialog::fileNameChanged(const QString& name) { m_okButton->setEnabled(!name.simplified().isEmpty()); } diff --git a/src/kdefrontend/worksheet/ExportWorksheetDialog.h b/src/kdefrontend/worksheet/ExportWorksheetDialog.h index 5c94a2d9c..17bc0cb3c 100644 --- a/src/kdefrontend/worksheet/ExportWorksheetDialog.h +++ b/src/kdefrontend/worksheet/ExportWorksheetDialog.h @@ -1,72 +1,73 @@ /*************************************************************************** File : ExportWorksheetDialog.h Project : LabPlot Description : export worksheet dialog -------------------------------------------------------------------- Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef EXPORTWORKSHEETDIALOG_H #define EXPORTWORKSHEETDIALOG_H #include #include "commonfrontend/worksheet/WorksheetView.h" namespace Ui { class ExportWorksheetWidget; } class QPushButton; class QAbstractButton; class ExportWorksheetDialog : public QDialog { Q_OBJECT public: explicit ExportWorksheetDialog(QWidget*); virtual ~ExportWorksheetDialog(); QString path() const; void setFileName(const QString&); WorksheetView::ExportFormat exportFormat() const; WorksheetView::ExportArea exportArea() const; bool exportBackground() const; int exportResolution() const; private: Ui::ExportWorksheetWidget* ui; bool m_showOptions; QPushButton* m_showOptionsButton; QPushButton* m_okButton; QPushButton* m_cancelButton; private slots: void slotButtonClicked(QAbstractButton *); void okClicked(); void toggleOptions(); void selectFile(); void formatChanged(int); void fileNameChanged(const QString&); + void loadSettings(); }; #endif diff --git a/tests/import_export/ASCII/AsciiFilterTest.cpp b/tests/import_export/ASCII/AsciiFilterTest.cpp index 08e8b33b7..e35e13400 100644 --- a/tests/import_export/ASCII/AsciiFilterTest.cpp +++ b/tests/import_export/ASCII/AsciiFilterTest.cpp @@ -1,198 +1,340 @@ /*************************************************************************** File : AsciiFilterTest.cpp Project : LabPlot Description : Tests for the ascii filter -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "AsciiFilterTest.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/spreadsheet/Spreadsheet.h" void AsciiFilterTest::initTestCase() { const QString currentDir = __FILE__; m_dataDir = currentDir.left(currentDir.lastIndexOf(QDir::separator())) + QDir::separator() + QLatin1String("data") + QDir::separator(); // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp //TODO: redesign/remove this qRegisterMetaType("const AbstractAspect*"); qRegisterMetaType("const AbstractColumn*"); } //############################################################################## -//######################## handling of empty files ############################ +//################# handling of empty and sparse files ######################## //############################################################################## void AsciiFilterTest::testEmptyFileAppend() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const int rowCount = spreadsheet.rowCount(); const int colCount = spreadsheet.columnCount(); const QString fileName = m_dataDir + "empty_file.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Append; filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), rowCount); QCOMPARE(spreadsheet.columnCount(), colCount); } void AsciiFilterTest::testEmptyFilePrepend() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const int rowCount = spreadsheet.rowCount(); const int colCount = spreadsheet.columnCount(); const QString fileName = m_dataDir + "empty_file.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Prepend; filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), rowCount); QCOMPARE(spreadsheet.columnCount(), colCount); } void AsciiFilterTest::testEmptyFileReplace() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const int rowCount = spreadsheet.rowCount(); const int colCount = spreadsheet.columnCount(); const QString fileName = m_dataDir + "empty_file.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), rowCount); QCOMPARE(spreadsheet.columnCount(), colCount); } +void AsciiFilterTest::testEmptyLines01() { + Spreadsheet spreadsheet(0, "test", false); + AsciiFilter filter; + const QString fileName = m_dataDir + "empty_lines_01.txt"; + + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setSeparatingCharacter("auto"); + filter.setHeaderEnabled(true); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.rowCount(), 7); //TODO: there are only 3 lines, we need an option to skip empty lines + QCOMPARE(spreadsheet.columnCount(), 3); + + QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); + QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("y")); + QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("values")); + + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + QCOMPARE(spreadsheet.column(1)->integerAt(0), 2); + QCOMPARE(spreadsheet.column(1)->integerAt(1), 4); + QCOMPARE(spreadsheet.column(1)->integerAt(2), 9); + QCOMPARE(spreadsheet.column(2)->integerAt(0), 10); + QCOMPARE(spreadsheet.column(2)->integerAt(1), 40); + QCOMPARE(spreadsheet.column(2)->integerAt(2), 90); +} + +void AsciiFilterTest::testSparseFile01() { + Spreadsheet spreadsheet(0, "test", false); + AsciiFilter filter; + const QString fileName = m_dataDir + "sparse_file_01.txt"; + + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setSeparatingCharacter(","); + filter.setHeaderEnabled(true); + filter.setSimplifyWhitespacesEnabled(true); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.rowCount(), 3); + QCOMPARE(spreadsheet.columnCount(), 3); + + QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("N")); + QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("Col1")); + QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("Col2")); + + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + //TODO: QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Integer); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Integer); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + + //TODO: +// QCOMPARE(spreadsheet.column(1)->integerAt(0), 1); +// QCOMPARE(spreadsheet.column(1)->integerAt(1), 0); +// QCOMPARE(spreadsheet.column(1)->integerAt(2), 1); + + QCOMPARE(spreadsheet.column(2)->integerAt(0), 2); + QCOMPARE(spreadsheet.column(2)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(2)->integerAt(2), 0); +} + +void AsciiFilterTest::testSparseFile02() { + Spreadsheet spreadsheet(0, "test", false); + AsciiFilter filter; + const QString fileName = m_dataDir + "sparse_file_02.txt"; + + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setSeparatingCharacter(","); + filter.setHeaderEnabled(true); + filter.setNaNValueToZero(false); + filter.setSimplifyWhitespacesEnabled(true); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.rowCount(), 3); + QCOMPARE(spreadsheet.columnCount(), 3); + + QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("N")); + QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("Col1")); + QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("Col2")); + + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + //TODO: QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + + //TODO. +// QCOMPARE(spreadsheet.column(1)->valueAt(0), 1.); +// QCOMPARE(std::isnan(spreadsheet.column(1)->valueAt(2)), true); +// QCOMPARE(spreadsheet.column(1)->valueAt(2), 1.); + + QCOMPARE(spreadsheet.column(2)->valueAt(0), 2.); + QCOMPARE(spreadsheet.column(2)->valueAt(1), 2.); + QCOMPARE((bool)std::isnan(spreadsheet.column(2)->valueAt(2)), true); +} + +void AsciiFilterTest::testSparseFile03() { + Spreadsheet spreadsheet(0, "test", false); + AsciiFilter filter; + const QString fileName = m_dataDir + "sparse_file_02.txt"; + + AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; + filter.setSeparatingCharacter(","); + filter.setHeaderEnabled(true); + filter.setNaNValueToZero(true); + filter.setSimplifyWhitespacesEnabled(true); + filter.readDataFromFile(fileName, &spreadsheet, mode); + + QCOMPARE(spreadsheet.rowCount(), 3); + QCOMPARE(spreadsheet.columnCount(), 3); + + QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("N")); + QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("Col1")); + QCOMPARE(spreadsheet.column(2)->name(), QLatin1String("Col2")); + + QCOMPARE(spreadsheet.column(0)->columnMode(), AbstractColumn::Integer); + //TODO: QCOMPARE(spreadsheet.column(1)->columnMode(), AbstractColumn::Numeric); + QCOMPARE(spreadsheet.column(2)->columnMode(), AbstractColumn::Numeric); + + QCOMPARE(spreadsheet.column(0)->integerAt(0), 1); + QCOMPARE(spreadsheet.column(0)->integerAt(1), 2); + QCOMPARE(spreadsheet.column(0)->integerAt(2), 3); + + //TODO. +// QCOMPARE(spreadsheet.column(1)->valueAt(0), 1.); +// QCOMPARE(spreadsheet.column(1)->valueAt(1), 0.); +// QCOMPARE(spreadsheet.column(1)->valueAt(2), 1.); + + QCOMPARE(spreadsheet.column(2)->valueAt(0), 2.); + QCOMPARE(spreadsheet.column(2)->valueAt(1), 2.); + QCOMPARE(spreadsheet.column(2)->valueAt(2), 0.); +} + //############################################################################## //################################ header handling ############################ //############################################################################## void AsciiFilterTest::testHeader01() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); filter.setVectorNames(""); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); QCOMPARE(spreadsheet.columnCount(), 2); } void AsciiFilterTest::testHeader02() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(true); filter.setVectorNames(""); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 2);//out of 3 rows one row is used for the column names (header) QCOMPARE(spreadsheet.columnCount(), 2); QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("1")); //TODO: we start with the names "1" and "2" in the spreadsheet and try to rename them to "1" and "1" (names coming from the file) //-> the second column with the name "2" will be renamed to "3" because of the current logic in AbstractAspect::uniqueNameFor(). QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("3")); } void AsciiFilterTest::testHeader03() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); filter.setVectorNames("x"); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); QCOMPARE(spreadsheet.columnCount(), 1); //one column name was specified, we import only one column QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); } void AsciiFilterTest::testHeader04() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); filter.setVectorNames("x"); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); QCOMPARE(spreadsheet.columnCount(), 1); //one column name was specified -> we import only one column QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); } void AsciiFilterTest::testHeader05() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); filter.setVectorNames("x y"); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); QCOMPARE(spreadsheet.columnCount(), 2); //two names were specified -> we import two columns QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("y")); } void AsciiFilterTest::testHeader06() { Spreadsheet spreadsheet(0, "test", false); AsciiFilter filter; const QString fileName = m_dataDir + "separator_semicolon.txt"; AbstractFileFilter::ImportMode mode = AbstractFileFilter::Replace; filter.setSeparatingCharacter(";"); filter.setHeaderEnabled(false); filter.setVectorNames("x y z"); filter.readDataFromFile(fileName, &spreadsheet, mode); QCOMPARE(spreadsheet.rowCount(), 3); QCOMPARE(spreadsheet.columnCount(), 2); //thee names were specified, but there're only two columns in the file -> we import only two columns QCOMPARE(spreadsheet.column(0)->name(), QLatin1String("x")); QCOMPARE(spreadsheet.column(1)->name(), QLatin1String("y")); } //############################################################################## //##################### handling of different separators ###################### //############################################################################## QTEST_MAIN(AsciiFilterTest) diff --git a/tests/import_export/ASCII/AsciiFilterTest.h b/tests/import_export/ASCII/AsciiFilterTest.h index 774804285..4b28d701a 100644 --- a/tests/import_export/ASCII/AsciiFilterTest.h +++ b/tests/import_export/ASCII/AsciiFilterTest.h @@ -1,58 +1,64 @@ /*************************************************************************** File : AsciiFilterTest.h Project : LabPlot Description : Tests for the ascii filter -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include class AsciiFilterTest : public QObject { Q_OBJECT private slots: void initTestCase(); - //empty files + //empty and sparse files void testEmptyFileAppend(); void testEmptyFilePrepend(); void testEmptyFileReplace(); + void testEmptyLines01(); + + void testSparseFile01(); + void testSparseFile02(); + void testSparseFile03(); + //header handling void testHeader01(); void testHeader02(); void testHeader03(); void testHeader04(); void testHeader05(); void testHeader06(); //different separators //different locales //handling of NANs //automatically skip comments private: QString m_dataDir; }; diff --git a/tests/import_export/ASCII/data/empty_lines_01.txt b/tests/import_export/ASCII/data/empty_lines_01.txt new file mode 100644 index 000000000..5341c0757 --- /dev/null +++ b/tests/import_export/ASCII/data/empty_lines_01.txt @@ -0,0 +1,8 @@ +x y values +1 2 10 + +2 4 40 + + + +3 9 90 diff --git a/tests/import_export/ASCII/data/sparse_file_01.txt b/tests/import_export/ASCII/data/sparse_file_01.txt new file mode 100644 index 000000000..2fb767ced --- /dev/null +++ b/tests/import_export/ASCII/data/sparse_file_01.txt @@ -0,0 +1,4 @@ +N, Col1, Col2 +1, 1, 2 +2, , 2 +3, 1, diff --git a/tests/import_export/ASCII/data/sparse_file_02.txt b/tests/import_export/ASCII/data/sparse_file_02.txt new file mode 100644 index 000000000..f0099f0b5 --- /dev/null +++ b/tests/import_export/ASCII/data/sparse_file_02.txt @@ -0,0 +1,4 @@ +N, Col1, Col2 +1, 1., 2. +2, , 2. +3, 1.,