diff --git a/org.kde.labplot2.appdata.xml b/org.kde.labplot2.appdata.xml index 70dc2c18a..e477dac36 100644 --- a/org.kde.labplot2.appdata.xml +++ b/org.kde.labplot2.appdata.xml @@ -1,148 +1,150 @@ org.kde.labplot2.desktop CC0-1.0 GPL-2.0+ LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot LabPlot Labplot LabPlot LabPlot xxLabPlotxx LabPlot LabPlot Interactive graphing and analysis of scientific data Representació interactiva de grafs i anàlisi de dades científiques Grafica interattiva e analisi dei dati scientifici Interactief maken van grafieken en analyseren van wetenschappelijke gegevens + Interaktywne przestawianie graficzne i analizy danych naukowych Gráficos interactivos e análise de dados científicos Interaktiv diagramritning och analys av vetenskaplig data Інтерактивна побудова графіків та аналіз наукових даних xxInteractive graphing and analysis of scientific dataxx

LabPlot is an application for interactive graphing and analysis of scientific data.

El LabPlot és una aplicació per a la representació interactiva de grafs i anàlisi de dades científiques.

LabPlot è un'applicazione per la grafica interattiva e per l'analisi dei dati scientifici.

LabPlot2 is een toepassing voor het interactief maken van grafieken en het analyseren van wetenschappelijke gegevens.

+

LabPlot jest aplikacją do interaktywnego przestawiania graficznego i analizy danych naukowych.

O LabPlot é uma aplicação para gráficos interactivos e para a análise de dados científicos.

Labplot är ett program för interaktiv diagramritning och analys av vetenskaplig data.

LabPlot — програма для інтерактивної побудови графіків та аналізу наукових даних.

xxLabPlot is an application for interactive graphing and analysis of scientific data.xx

LabPlot provides an easy way to create, manage and edit plots. It allows you to produce plots based on data from a spreadsheet or on data imported from external files. Plots can be exported to several pixmap and vector graphic formats.

Grafikov LabPlot pruža jednostavan način za stvaranje, upravljanje i uređivanje crtanja dijagrama. On vam omogućava da proizvede crteže na osnovu podataka iz tabele ili podataka uvezenih iz eksternih fajlova. Crteži se mogu izvoziti u nekoliko pixmap i vektor grafičkih formata.

El LabPlot proporciona una manera fàcil de crear, gestionar i editar grafs. Permet produir grafs a partir de les dades d'un full de càlcul o en les dades importades des de fitxers externs. Els grafs es poden exportar a diversos formats de mapa de píxels i de gràfics vectorials.

El LabPlot proporciona una manera fàcil de crear, gestionar i editar grafs. Permet produir grafs a partir de les dades d'un full de càlcul o en les dades importades des de fitxers externs. Els grafs es poden exportar a diversos formats de mapa de píxels i de gràfics vectorials.

LabPlot erlaubt es auf einfache Weise Grafiken zu erzeugen, zu verwalten und zu bearbeiten.Grafiken können aus externen Daten oder aus einer Tabelle erzeugt werden.Der Export der Grafiken in verschiedene Pixmap- und Vektorformate ist möglich.

Το LabPlot παρέχει έναν εύκολο τρόπο δημιουργίας, διαχείρισης και επεξεργασίας γραφικών παραστάσεων. Σας επιτρέπει να δημιουργείτε γραφικές παρασασεις με βάση δεδομένα από φύλλα εργασίας ή εισηγμένα από εξωτερικά αρχεία. Οι γραφικές παραστάσεις μπορούν να εξαχθούν σε διάφορους τύπους αποθήκευσης χρωματικής περίπλεξης ή διανυσματικών γραφικών.

LabPlot provides an easy way to create, manage and edit plots. It allows you to produce plots based on data from a spreadsheet or on data imported from external files. Plots can be exported to several pixmap and vector graphic formats.

LabPlot proporciona un sencillo modo de crear, gestionar y editar gráficos. Le permite generar gráficos basados en datos obtenidos de una hoja de cálculo o importados de archivos externos. Los gráficos se pueden exportar a diversos formatos de imagen y vectoriales.

LabPlot-ek grafikoak sortu, kudeatu eta editatzeko era erraz bat eskaintzen du. Grafikoak sortzen uzten dizu kalkulu-orrietako datuekin edo kanpoko fitxategietatik inportatutako datuekin. Grafikoak hainbat pixel-mapa eta bektore grafiko formatutara esportatu daitezke.

LabPlot tarjoaa helpon tavan luoda, hallita ja muokata kaavioita. Voit tuottaa kaavioita laskentataulukon tai muiden ulkoisten tiedostojen datasta. Kaavioita voi viedä eri bittikartta- ja vektorigrafiikkamuotoihin.

LabPlot fournit une moyen facile de créer, gérer et éditer des courbes. Il vous permet de produire des courbes basées sur des données issues d'un tableur ou d'un fichier externe. Les courbes peuvent être exportées vers plusieurs format d'image matriciels ou vectoriels.

LabPlot fornece unha forma doada de crear, xestionar e editar gráficos. Permítelle producir gráficos baseados en datos dunha folla de cálculo ou datos importados de ficheiros externos. Pode exportar os gráficos en distintos formatos de imaxe, de mapas de píxeles ou vectoriais.

LabPlot menyediakan cara mudah untuk menciptakan, mengelola, dan mengedit plot. Ini memungkinkanmu menghasilkan plot berdasarkan data dari spreadsheet atau data yang diimpor dari file eksternal. Plot bisa diekspor ke beberapa format grafik pixmap dan vector.

LabPlot fornisce una modalità semplice per creare, gestire e modificare i grafici. Ti permette di generare grafici basati sui dati di un foglio elettronico, oppure importati da file esterni. I grafici possono essere esportati in diverse immagini e in diversi formati grafici vettoriali.

Met LabPlot is het eenvoudig plots te maken, te beheren en te bewerken. U kunt er plots mee maken op basis van gegevens in een werkblad (spreadsheet), of van gegevens die uit externe bestanden zijn geïmporteerd. Plots kunnen worden geëxporteerd in diverse pixmap- of vector-grafische bestanden.

LabPlot zapewnia łatwy sposób do tworzenia, zarządzania i edytowania wykresów. Umożliwia tworzenie wykresów na podstawie danych z arkusza kalkulacyjnego lub danych zaimportowanych z plików zewnętrznych. Wykresy można eksportować do kilku formatów graficznych map pikselowych i wektorowych.

O LabPlot oferece uma forma simples de criar, gerir e editar os gráficos. Permite-lhe produzir gráficos com base nos dados de uma folha de cálculo ou nos dados importados de ficheiros externos. Os gráficos podem ser exportados para diferentes formatos de imagens rasterizados e vectoriais.

O LabPlot oferece uma forma simples de criar, gerenciar e editar gráficos. Permite-lhe produzir gráficos com base nos dados de uma planilha ou nos dados importados de arquivos externos. Os gráficos podem ser exportados para diferentes formatos de imagens rasterizadas e vetoriais.

LabPlot poskytuje jednoduché možnosti na vytváranie, správu a úpravu nákresov. Umožní vám vyrobiť nákresy založené na údajoch z tabuľky alebo z údajov importovaných z externých súborov. Nákresy sa dajú exportovať do niekoľkých rastrových a vektorových grafických formátov.

Labplot tillhandahåller ett enkelt sätt att skapa, hantera och redigera diagram. Det låter dig skapa diagram baserat på data från ett kalkylark eller data importerad från externa filer. Diagram kan exporteras till flera olika punktavbildnings- och vektorgrafik-format.

LabPlot çizimleri oluşturmak, yönetmek ve düzenlemek için kolay bir yol sağlar. Bir tablodaki verilere veya harici dosyalardan alınan verilere dayalı çizimler üretmenizi sağlar. Çizimler çeşitli piksmap ve vektör grafik formatlarına aktarılabilir.

За допомогою LabPlot просто створювати креслення, керувати ними та редагувати креслення. За допомогою програми можна створювати креслення на основі електронної таблиці або даних, імпортованих із зовнішнього файла. Креслення можна експортувати у форматі растрового або векторного зображення.

xxLabPlot provides an easy way to create, manage and edit plots. It allows you to produce plots based on data from a spreadsheet or on data imported from external files. Plots can be exported to several pixmap and vector graphic formats.xx

LabPlot 提供了一种方便地创建、 管理和编辑图表的方式。它允许您基于电子表格的数据或从外部文件导入的数据来绘制图表。图表可以导出到多种格式的位图或矢量图形。

https://labplot.kde.org/ https://bugs.kde.org/enter_bug.cgi?product=LabPlot2&format=guided Fit example Exemple d'ajust Exemple d'ajust Fit example Ejemplo de ajuste Egokitzeko adibidea Exemplo de axuste. Contoh pas Adatta esempio Voorbeeld aanpassing Przykład dopasowania Exemplo de ajuste Exemplo de ajuste Anpassningsexempel Приклад апроксимації xxFit examplexx https://cdn.kde.org/screenshots/labplot2/labplot2_appdata_01.png Mathematical function Funció matemàtica Funció matemàtica Matematická funkce Mathematische Funktion Mathematical function Función matemática Funtzio matematikoa Función matemática. Fungsi matematika Funzioni matematica Wiskundige functie Funkcja matematyczna Função matemática Função matemática Matematisk funktion Математична функція xxMathematical functionxx https://cdn.kde.org/screenshots/labplot2/labplot2_appdata_02.png CAS worksheet Full de treball CAS Full de treball CAS CAS-Arbeitsblatt CAS worksheet Hoja de trabajo CAS CAS lan-orria Folla de traballo de CAS. Lembar kerja CAS Foglio di lavoro CAS CAS werkblad Arkusz roboczy CAS Folha de cálculo CAS Folha de trabalho CAS CAS-arbetsblad Робочий аркуш СКА xxCAS worksheetxx https://cdn.kde.org/screenshots/labplot2/labplot2_appdata_03.png KDE labplot2
diff --git a/src/backend/core/datatypes/Double2StringFilter.cpp b/src/backend/core/datatypes/Double2StringFilter.cpp index bf2a102f7..692973bcf 100644 --- a/src/backend/core/datatypes/Double2StringFilter.cpp +++ b/src/backend/core/datatypes/Double2StringFilter.cpp @@ -1,134 +1,133 @@ /*************************************************************************** File : Double2StringFilter.cpp Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Locale-aware conversion filter double -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 "Double2StringFilter.h" #include "backend/lib/XmlStreamReader.h" #include #include #include class Double2StringFilterSetFormatCmd : public QUndoCommand { public: Double2StringFilterSetFormatCmd(Double2StringFilter* target, char new_format); void redo() override; void undo() override; private: Double2StringFilter* m_target; char m_other_format; }; class Double2StringFilterSetDigitsCmd : public QUndoCommand { public: Double2StringFilterSetDigitsCmd(Double2StringFilter* target, int new_digits); void redo() override; void undo() override; private: Double2StringFilter* m_target; int m_other_digits; }; void Double2StringFilter::writeExtraAttributes(QXmlStreamWriter * writer) const { writer->writeAttribute("format", QString(QChar(numericFormat()))); writer->writeAttribute("digits", QString::number(numDigits())); } bool Double2StringFilter::load(XmlStreamReader* reader, bool preview) { if (preview) return true; QXmlStreamAttributes attribs = reader->attributes(); QString format_str = attribs.value(reader->namespaceUri().toString(), "format").toString(); QString digits_str = attribs.value(reader->namespaceUri().toString(), "digits").toString(); if (AbstractSimpleFilter::load(reader, preview)) { bool ok; int digits = digits_str.toInt(&ok); - if ( (format_str.size() != 1) || !ok ) { - reader->raiseError(i18n("missing or invalid format attribute")); - } else { - setNumericFormat( format_str.at(0).toLatin1() ); - setNumDigits( digits ); - } + if (ok) + setNumDigits(digits); + + if (format_str.size() != 1) + setNumericFormat(format_str.at(0).toLatin1()); } else return false; return !reader->hasError(); } void Double2StringFilter::setNumericFormat(char format) { exec(new Double2StringFilterSetFormatCmd(this, format)); } void Double2StringFilter::setNumDigits(int digits) { exec(new Double2StringFilterSetDigitsCmd(this, digits)); } Double2StringFilterSetFormatCmd::Double2StringFilterSetFormatCmd(Double2StringFilter* target, char new_format) : m_target(target), m_other_format(new_format) { if (m_target->parentAspect()) setText(i18n("%1: set numeric format to '%2'", m_target->parentAspect()->name(), new_format)); else setText(i18n("set numeric format to '%1'", new_format)); } void Double2StringFilterSetFormatCmd::redo() { char tmp = m_target->m_format; m_target->m_format = m_other_format; m_other_format = tmp; emit m_target->formatChanged(); } void Double2StringFilterSetFormatCmd::undo() { redo(); } Double2StringFilterSetDigitsCmd::Double2StringFilterSetDigitsCmd(Double2StringFilter* target, int new_digits) : m_target(target), m_other_digits(new_digits) { if (m_target->parentAspect()) setText(i18n("%1: set decimal digits to %2", m_target->parentAspect()->name(), new_digits)); else setText(i18n("set decimal digits to %1", new_digits)); } void Double2StringFilterSetDigitsCmd::redo() { int tmp = m_target->m_digits; m_target->m_digits = m_other_digits; m_other_digits = tmp; emit m_target->digitsChanged(); } void Double2StringFilterSetDigitsCmd::undo() { redo(); } diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp index e90085241..ec1025078 100644 --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -1,1030 +1,1030 @@ /*************************************************************************** File : Worksheet.cpp Project : LabPlot Description : Worksheet -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-2019 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 "Worksheet.h" #include "WorksheetPrivate.h" #include "WorksheetElement.h" #include "commonfrontend/worksheet/WorksheetView.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include #include /** * \class Worksheet * \brief Top-level container for worksheet elements like plot, labels, etc. * * The worksheet is, besides the data containers \c Spreadsheet and \c Matrix, another central part of the application * and provides an area for showing and grouping together different kinds of worksheet objects - plots, labels &etc; * * * \ingroup worksheet */ Worksheet::Worksheet(const QString& name, bool loading) : AbstractPart(name, AspectType::Worksheet), d(new WorksheetPrivate(this)) { connect(this, &Worksheet::aspectAdded, this, &Worksheet::handleAspectAdded); connect(this, &Worksheet::aspectAboutToBeRemoved, this, &Worksheet::handleAspectAboutToBeRemoved); connect(this, &Worksheet::aspectRemoved, this, &Worksheet::handleAspectRemoved); if (!loading) init(); } Worksheet::~Worksheet() { delete d; } void Worksheet::init() { KConfig config; KConfigGroup group = config.group("Worksheet"); //size d->scaleContent = group.readEntry("ScaleContent", false); d->useViewSize = group.readEntry("UseViewSize", false); d->pageRect.setX(0); d->pageRect.setY(0); d->pageRect.setWidth(group.readEntry("Width", 1500)); d->pageRect.setHeight(group.readEntry("Height", 1500)); d->m_scene->setSceneRect(d->pageRect); //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); //layout d->layout = (Worksheet::Layout) group.readEntry("Layout", (int) Worksheet::VerticalLayout); d->layoutTopMargin = group.readEntry("LayoutTopMargin", convertToSceneUnits(1, Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", convertToSceneUnits(1, Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", convertToSceneUnits(1, Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", convertToSceneUnits(1, Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutRowCount = group.readEntry("LayoutRowCount", 2); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 2); //default theme KConfigGroup settings = KSharedConfig::openConfig()->group(QLatin1String("Settings_Worksheet")); d->theme = settings.readEntry(QStringLiteral("Theme"), QString()); if (!d->theme.isEmpty()) loadTheme(d->theme); } /*! converts from \c unit to the scene units. At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertToSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value*10.0; case Worksheet::Centimeter: return value*100.0; case Worksheet::Inch: return value*25.4*10.; case Worksheet::Point: return value*25.4/72.*10.; } return 0; } /*! converts from the scene units to \c unit . At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertFromSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value/10.0; case Worksheet::Centimeter: return value/100.0; case Worksheet::Inch: return value/25.4/10.; case Worksheet::Point: return value/25.4/10.*72.; } return 0; } QIcon Worksheet::icon() const { return QIcon::fromTheme("labplot-worksheet"); } /** * Return a new context menu. The caller takes ownership of the menu. */ QMenu* Worksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* Worksheet::view() const { if (!m_partView) { m_view = new WorksheetView(const_cast(this)); m_partView = m_view; connect(m_view, &WorksheetView::statusInfo, this, &Worksheet::statusInfo); } return m_partView; } /*! * returns the list of all parent aspects (folders and sub-folders) * together with all the data containers required to plot the data in the worksheet */ QVector Worksheet::dependsOn() const { //add all parent aspects (folders and sub-folders) QVector aspects = AbstractAspect::dependsOn(); //traverse all plots and add all data containers they depend on for (const auto* plot : children()) aspects << plot->dependsOn(); return aspects; } bool Worksheet::exportView() const { auto* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const WorksheetView::ExportArea area = dlg->exportArea(); const bool background = dlg->exportBackground(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, area, background, resolution); RESET_CURSOR; } delete dlg; return ret; } bool Worksheet::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18nc("@title:window", "Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Worksheet::printPreview() const { auto* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &WorksheetView::print); return dlg->exec(); } void Worksheet::handleAspectAdded(const AbstractAspect* aspect) { const auto* addedElement = qobject_cast(aspect); if (!addedElement) return; if (aspect->parentAspect() != this) return; //add the GraphicsItem of the added child to the scene QGraphicsItem* item = addedElement->graphicsItem(); d->m_scene->addItem(item); qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->graphicsItem()->setZValue(zVal++); //if a theme was selected in the worksheet, apply this theme for newly added children if (!d->theme.isEmpty() && !isLoading()) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(addedElement)->loadThemeConfig(config); } const CartesianPlot* plot = dynamic_cast(aspect); if (plot) { auto* p = const_cast(plot); p->setLocked(d->plotsLocked); } //recalculated the layout if (!isLoading()) { if (d->layout != Worksheet::NoLayout) d->updateLayout(false); } } void Worksheet::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const auto* removedElement = qobject_cast(aspect); if (removedElement) { QGraphicsItem* item = removedElement->graphicsItem(); d->m_scene->removeItem(item); } } void Worksheet::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); Q_UNUSED(child); if (d->layout != Worksheet::NoLayout) d->updateLayout(false); } QGraphicsScene* Worksheet::scene() const { return d->m_scene; } QRectF Worksheet::pageRect() const { return d->m_scene->sceneRect(); } /*! this slot is called when a worksheet element is selected in the project explorer. emits \c itemSelected() which forwards this event to the \c WorksheetView in order to select the corresponding \c QGraphicsItem. */ void Worksheet::childSelected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemSelected(element->graphicsItem()); } /*! this slot is called when a worksheet element is deselected in the project explorer. emits \c itemDeselected() which forwards this event to \c WorksheetView in order to deselect the corresponding \c QGraphicsItem. */ void Worksheet::childDeselected(const AbstractAspect* aspect) { auto* element = qobject_cast(const_cast(aspect)); if (element) emit itemDeselected(element->graphicsItem()); } /*! * Emits the signal to select or to deselect the aspect corresponding to \c QGraphicsItem \c item in the project explorer, * if \c selected=true or \c selected=false, respectively. * The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer. * This function is called in \c WorksheetView upon selection changes. */ void Worksheet::setItemSelectedInView(const QGraphicsItem* item, const bool b) { //determine the corresponding aspect const AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; //forward selection/deselection to AbstractTreeModel if (b) emit childAspectSelectedInView(aspect); else emit childAspectDeselectedInView(aspect); } /*! * helper function: checks whether \c aspect or one of its children has the \c GraphicsItem \c item * Returns a pointer to \c WorksheetElement having this item. */ WorksheetElement* Worksheet::aspectFromGraphicsItem(const WorksheetElement* aspect, const QGraphicsItem* item) const { if ( aspect->graphicsItem() == item ) return const_cast(aspect); else { for (const auto* child : aspect->children(AbstractAspect::IncludeHidden) ) { WorksheetElement* a = this->aspectFromGraphicsItem(child, item); if (a) return a; } return nullptr; } } /*! Selects or deselects the worksheet in the project explorer. This function is called in \c WorksheetView. The worksheet gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void Worksheet::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void Worksheet::deleteAspectFromGraphicsItem(const QGraphicsItem* item) { Q_ASSERT(item); //determine the corresponding aspect AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; if (aspect->parentAspect()) aspect->parentAspect()->removeChild(aspect); else this->removeChild(aspect); } void Worksheet::setIsClosing() { if (m_view) m_view->setIsClosing(); } void Worksheet::update() { emit requestUpdate(); } void Worksheet::setSuppressLayoutUpdate(bool value) { d->suppressLayoutUpdate = value; } void Worksheet::updateLayout() { d->updateLayout(); } Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotActionMode() { return d->cartesianPlotActionMode; } bool Worksheet::plotsLocked() { return d->plotsLocked; } void Worksheet::setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode) { if (d->cartesianPlotActionMode == mode) return; d->cartesianPlotActionMode = mode; project()->setChanged(true); } void Worksheet::setPlotsLocked(bool lock) { if (d->plotsLocked == lock) return; d->plotsLocked = lock; for (auto* plot: children()) plot->setLocked(lock); project()->setChanged(true); } void Worksheet::registerShortcuts() { m_view->registerShortcuts(); } void Worksheet::unregisterShortcuts() { m_view->unregisterShortcuts(); } /* =============================== getter methods for general options ==================================== */ BASIC_D_READER_IMPL(Worksheet, bool, scaleContent, scaleContent) BASIC_D_READER_IMPL(Worksheet, bool, useViewSize, useViewSize) /* =============================== getter methods for background options ================================= */ BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_D_READER_IMPL(Worksheet, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_D_READER_IMPL(Worksheet, QString, backgroundFileName, backgroundFileName) BASIC_D_READER_IMPL(Worksheet, float, backgroundOpacity, backgroundOpacity) /* =============================== getter methods for layout options ====================================== */ BASIC_D_READER_IMPL(Worksheet, Worksheet::Layout, layout, layout) BASIC_D_READER_IMPL(Worksheet, float, layoutTopMargin, layoutTopMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutBottomMargin, layoutBottomMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutLeftMargin, layoutLeftMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutRightMargin, layoutRightMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_D_READER_IMPL(Worksheet, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_D_READER_IMPL(Worksheet, int, layoutRowCount, layoutRowCount) BASIC_D_READER_IMPL(Worksheet, int, layoutColumnCount, layoutColumnCount) CLASS_D_READER_IMPL(Worksheet, QString, theme, theme) /* ============================ setter methods and undo commands for general options ===================== */ void Worksheet::setUseViewSize(bool useViewSize) { if (useViewSize != d->useViewSize) { d->useViewSize = useViewSize; emit useViewSizeRequested(); } } STD_SETTER_CMD_IMPL_S(Worksheet, SetScaleContent, bool, scaleContent) void Worksheet::setScaleContent(bool scaleContent) { if (scaleContent != d->scaleContent) exec(new WorksheetSetScaleContentCmd(d, scaleContent, ki18n("%1: change \"rescale the content\" property"))); } /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void Worksheet::setBackgroundType(PlotArea::BackgroundType type) { if (type != d->backgroundType) exec(new WorksheetSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void Worksheet::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { if (style != d->backgroundColorStyle) exec(new WorksheetSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void Worksheet::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { if (style != d->backgroundImageStyle) exec(new WorksheetSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void Worksheet::setBackgroundBrushStyle(Qt::BrushStyle style) { if (style != d->backgroundBrushStyle) exec(new WorksheetSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void Worksheet::setBackgroundFirstColor(const QColor &color) { if (color!= d->backgroundFirstColor) exec(new WorksheetSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void Worksheet::setBackgroundSecondColor(const QColor &color) { if (color!= d->backgroundSecondColor) exec(new WorksheetSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFileName, QString, backgroundFileName, update) void Worksheet::setBackgroundFileName(const QString& fileName) { if (fileName!= d->backgroundFileName) exec(new WorksheetSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundOpacity, float, backgroundOpacity, update) void Worksheet::setBackgroundOpacity(float opacity) { if (opacity != d->backgroundOpacity) exec(new WorksheetSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } /* ============================ setter methods and undo commands for layout options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetLayout, Worksheet::Layout, layout, updateLayout) void Worksheet::setLayout(Worksheet::Layout layout) { if (layout != d->layout) { beginMacro(i18n("%1: set layout", name())); exec(new WorksheetSetLayoutCmd(d, layout, ki18n("%1: set layout"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutTopMargin, float, layoutTopMargin, updateLayout) void Worksheet::setLayoutTopMargin(float margin) { if (margin != d->layoutTopMargin) { beginMacro(i18n("%1: set layout top margin", name())); exec(new WorksheetSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutBottomMargin, float, layoutBottomMargin, updateLayout) void Worksheet::setLayoutBottomMargin(float margin) { if (margin != d->layoutBottomMargin) { beginMacro(i18n("%1: set layout bottom margin", name())); exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutLeftMargin, float, layoutLeftMargin, updateLayout) void Worksheet::setLayoutLeftMargin(float margin) { if (margin != d->layoutLeftMargin) { beginMacro(i18n("%1: set layout left margin", name())); exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRightMargin, float, layoutRightMargin, updateLayout) void Worksheet::setLayoutRightMargin(float margin) { if (margin != d->layoutRightMargin) { beginMacro(i18n("%1: set layout right margin", name())); exec(new WorksheetSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, updateLayout) void Worksheet::setLayoutVerticalSpacing(float spacing) { if (spacing != d->layoutVerticalSpacing) { beginMacro(i18n("%1: set layout vertical spacing", name())); exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, updateLayout) void Worksheet::setLayoutHorizontalSpacing(float spacing) { if (spacing != d->layoutHorizontalSpacing) { beginMacro(i18n("%1: set layout horizontal spacing", name())); exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRowCount, int, layoutRowCount, updateLayout) void Worksheet::setLayoutRowCount(int count) { if (count != d->layoutRowCount) { beginMacro(i18n("%1: set layout row count", name())); exec(new WorksheetSetLayoutRowCountCmd(d, count, ki18n("%1: set layout row count"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutColumnCount, int, layoutColumnCount, updateLayout) void Worksheet::setLayoutColumnCount(int count) { if (count != d->layoutColumnCount) { beginMacro(i18n("%1: set layout column count", name())); exec(new WorksheetSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); endMacro(); } } class WorksheetSetPageRectCmd : public StandardMacroSetterCmd { public: WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const KLocalizedString& description) : StandardMacroSetterCmd(target, &Worksheet::Private::pageRect, newValue, description) {} void finalize() override { m_target->updatePageRect(); emit m_target->q->pageRectChanged(m_target->*m_field); } void finalizeUndo() override { m_target->m_scene->setSceneRect(m_target->*m_field); emit m_target->q->pageRectChanged(m_target->*m_field); } }; void Worksheet::setPageRect(const QRectF& rect) { //don't allow any rectangulars of width/height equal to zero if (qFuzzyCompare(rect.width(), 0.) || qFuzzyCompare(rect.height(), 0.)) { emit pageRectChanged(d->pageRect); return; } if (rect != d->pageRect) { if (!d->useViewSize) { beginMacro(i18n("%1: set page size", name())); exec(new WorksheetSetPageRectCmd(d, rect, ki18n("%1: set page size"))); endMacro(); } else { d->pageRect = rect; d->updatePageRect(); emit pageRectChanged(d->pageRect); } } } void Worksheet::setPrinting(bool on) const { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) child->setPrinting(on); } STD_SETTER_CMD_IMPL_S(Worksheet, SetTheme, QString, theme) void Worksheet::setTheme(const QString& theme) { if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else { exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } } //############################################################################## //###################### Private implementation ############################### //############################################################################## WorksheetPrivate::WorksheetPrivate(Worksheet* owner) : q(owner), m_scene(new QGraphicsScene()) { } QString WorksheetPrivate::name() const { return q->name(); } /*! * called if the worksheet page (the actual size of worksheet's rectangular) was changed. * if a layout is active, it is is updated - this adjusts the sizes of the elements in the layout to the new page size. * if no layout is active and the option "scale content" is active, \c handleResize() is called to adjust zhe properties. */ void WorksheetPrivate::updatePageRect() { if (q->isLoading()) return; QRectF oldRect = m_scene->sceneRect(); m_scene->setSceneRect(pageRect); if (layout != Worksheet::NoLayout) updateLayout(); else { if (scaleContent) { qreal horizontalRatio = pageRect.width() / oldRect.width(); qreal verticalRatio = pageRect.height() / oldRect.height(); QVector childElements = q->children(AbstractAspect::IncludeHidden); if (useViewSize) { //don't make the change of the geometry undoable/redoable if the view size is used. for (auto* elem : childElements) { elem->setUndoAware(false); elem->handleResize(horizontalRatio, verticalRatio, true); elem->setUndoAware(true); } } else { - for (auto* child : childElements) - child->handleResize(horizontalRatio, verticalRatio, true); +// for (auto* child : childElements) +// child->handleResize(horizontalRatio, verticalRatio, true); } } } } void WorksheetPrivate::update() { q->update(); } WorksheetPrivate::~WorksheetPrivate() { delete m_scene; } void WorksheetPrivate::updateLayout(bool undoable) { if (suppressLayoutUpdate) return; QVector list = q->children(); if (layout == Worksheet::NoLayout) { for (auto* elem : list) elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); return; } float x = layoutLeftMargin; float y = layoutTopMargin; float w, h; int count = list.count(); if (layout == Worksheet::VerticalLayout) { w = m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (count-1)*layoutVerticalSpacing)/count; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); y += h + layoutVerticalSpacing; } } else if (layout == Worksheet::HorizontalLayout) { w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (count-1)*layoutHorizontalSpacing)/count; h = m_scene->sceneRect().height() - layoutTopMargin-layoutBottomMargin; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; } } else { //GridLayout //add new rows, if not sufficient if (count > layoutRowCount*layoutColumnCount) { layoutRowCount = floor( (float)count/layoutColumnCount + 0.5); emit q->layoutRowCountChanged(layoutRowCount); } w = (m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (layoutColumnCount-1)*layoutHorizontalSpacing)/layoutColumnCount; h = (m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (layoutRowCount-1)*layoutVerticalSpacing)/layoutRowCount; int columnIndex = 0; //counts the columns in a row for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x += w + layoutHorizontalSpacing; columnIndex++; if (columnIndex == layoutColumnCount) { columnIndex = 0; x = layoutLeftMargin; y += h + layoutVerticalSpacing; } } } } void WorksheetPrivate::setContainerRect(WorksheetElementContainer* elem, float x, float y, float h, float w, bool undoable) { if (useViewSize) { //when using the view size, no need to put rect changes onto the undo-stack elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else { //don't put rect changed onto the undo-stack if undoable-flag is set to true, //e.g. when new child is added or removed (the layout and the childrend rects will be updated anyway) if (!undoable) { elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else elem->setRect(QRectF(x,y,w,h)); } elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Worksheet::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "worksheet" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); QRectF rect = d->m_scene->sceneRect(); writer->writeAttribute( "x", QString::number(rect.x()) ); writer->writeAttribute( "y", QString::number(rect.y()) ); writer->writeAttribute( "width", QString::number(rect.width()) ); writer->writeAttribute( "height", QString::number(rect.height()) ); writer->writeAttribute( "useViewSize", QString::number(d->useViewSize) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "layout", QString::number(d->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->writeAttribute( "rowCount", QString::number(d->layoutRowCount) ); writer->writeEndElement(); //background properties 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(); // cartesian properties writer->writeStartElement( "plotProperties" ); writer->writeAttribute( "plotsLocked", QString::number(d->plotsLocked) ); writer->writeAttribute( "cartesianPlotActionMode", QString::number(d->cartesianPlotActionMode)); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); // close "worksheet" section } //! Load from XML bool Worksheet::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; //clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used d->theme.clear(); KLocalizedString attributeWarning = ki18n("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() == "worksheet") 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.subs("x").toString()); else rect.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else rect.setY(str.toDouble()); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else rect.setWidth(str.toDouble()); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else rect.setHeight(str.toDouble()); READ_INT_VALUE("useViewSize", useViewSize, int); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); READ_INT_VALUE("layout", layout, Worksheet::Layout); READ_DOUBLE_VALUE("topMargin", layoutTopMargin); READ_DOUBLE_VALUE("bottomMargin", layoutBottomMargin); READ_DOUBLE_VALUE("leftMargin", layoutLeftMargin); READ_DOUBLE_VALUE("rightMargin", layoutRightMargin); READ_DOUBLE_VALUE("verticalSpacing", layoutVerticalSpacing); READ_DOUBLE_VALUE("horizontalSpacing", layoutHorizontalSpacing); READ_INT_VALUE("columnCount", layoutColumnCount, int); READ_INT_VALUE("rowCount", layoutRowCount, int); } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); READ_INT_VALUE("type", backgroundType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", backgroundColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", backgroundImageStyle, PlotArea::BackgroundImageStyle); READ_INT_VALUE("brushStyle", backgroundBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; READ_DOUBLE_VALUE("opacity", backgroundOpacity); } else if(!preview && reader->name() == "plotProperties") { attribs = reader->attributes(); READ_INT_VALUE("plotsLocked", plotsLocked, bool); READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode); } else if (reader->name() == "cartesianPlot") { CartesianPlot* plot = new CartesianPlot(QString()); plot->setIsLoading(true); if (!plot->load(reader, preview)) { delete plot; return false; } else addChildFast(plot); } else if (reader->name() == "textLabel") { TextLabel* label = new TextLabel(QString()); if (!label->load(reader, preview)) { delete label; return false; } else addChildFast(label); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) { d->m_scene->setSceneRect(rect); d->updateLayout(); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Worksheet::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); //apply the same background color for Worksheet as for the CartesianPlot 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()))); //load the theme for all the children const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } diff --git a/src/backend/worksheet/WorksheetElementContainer.cpp b/src/backend/worksheet/WorksheetElementContainer.cpp index b6860d8a3..fa4737534 100644 --- a/src/backend/worksheet/WorksheetElementContainer.cpp +++ b/src/backend/worksheet/WorksheetElementContainer.cpp @@ -1,286 +1,286 @@ /*************************************************************************** File : WorksheetElementContainer.cpp Project : LabPlot Description : Worksheet element container - parent of multiple elements -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-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 "backend/worksheet/WorksheetElementContainer.h" #include "backend/worksheet/WorksheetElementContainerPrivate.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include #include #include #include #include /** * \class WorksheetElementContainer * \brief Worksheet element container - parent of multiple elements * \ingroup worksheet * This class provides the functionality for a containers of multiple * worksheet elements. Such a container can be a plot or group of elements. */ WorksheetElementContainer::WorksheetElementContainer(const QString& name, AspectType type) : WorksheetElement(name, type), d_ptr(new WorksheetElementContainerPrivate(this)) { connect(this, &WorksheetElementContainer::aspectAdded, this, &WorksheetElementContainer::handleAspectAdded); } WorksheetElementContainer::WorksheetElementContainer(const QString& name, WorksheetElementContainerPrivate* dd, AspectType type) : WorksheetElement(name, type), d_ptr(dd) { connect(this, &WorksheetElementContainer::aspectAdded, this, &WorksheetElementContainer::handleAspectAdded); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene WorksheetElementContainer::~WorksheetElementContainer() = default; QGraphicsItem* WorksheetElementContainer::graphicsItem() const { return const_cast(static_cast(d_ptr)); } QRectF WorksheetElementContainer::rect() const { Q_D(const WorksheetElementContainer); return d->rect; } STD_SWAP_METHOD_SETTER_CMD_IMPL(WorksheetElementContainer, SetVisible, bool, swapVisible) void WorksheetElementContainer::setVisible(bool on) { Q_D(WorksheetElementContainer); //take care of proper ordering on the undo-stack, //when making the container and all its children visible/invisible. //if visible is set true, change the visibility of the container first if (on) { beginMacro( i18n("%1: set visible", name()) ); exec( new WorksheetElementContainerSetVisibleCmd(d, on, ki18n("%1: set visible")) ); } else { beginMacro( i18n("%1: set invisible", name()) ); } //change the visibility of all children QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (auto* elem : childList) elem->setVisible(on); //if visible is set false, change the visibility of the container last if (!on) exec(new WorksheetElementContainerSetVisibleCmd(d, false, ki18n("%1: set invisible"))); endMacro(); } bool WorksheetElementContainer::isVisible() const { Q_D(const WorksheetElementContainer); return d->isVisible(); } bool WorksheetElementContainer::isFullyVisible() const { QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (const auto* elem : childList) { if (!elem->isVisible()) return false; } return true; } void WorksheetElementContainer::setPrinting(bool on) { Q_D(WorksheetElementContainer); d->m_printing = on; } void WorksheetElementContainer::retransform() { // if (isLoading()) // return; PERFTRACE("WorksheetElementContainer::retransform()"); Q_D(WorksheetElementContainer); QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (auto* child : childList) child->retransform(); d->recalcShapeAndBoundingRect(); } /*! * called if the size of the worksheet page was changed and the content has to be adjusted/resized (\c pageResize = true) * or if a new rectangular for the element container was set (\c pageResize = false). * In the second case, \c WorksheetElement::handleResize() is called for every worksheet child to adjuste the content to the new size. * In the first case, a new rectangular for the container is calculated and set first, which on the other hand, triggers the content adjustments * in the container children. */ void WorksheetElementContainer::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("WorksheetElementContainer::handleResize()"); Q_D(const WorksheetElementContainer); if (pageResize) { QRectF rect(d->rect); rect.setWidth(d->rect.width()*horizontalRatio); rect.setHeight(d->rect.height()*verticalRatio); setRect(rect); } else { - for (auto* elem : children(IncludeHidden)) - elem->handleResize(horizontalRatio, verticalRatio); +// for (auto* elem : children(IncludeHidden)) +// elem->handleResize(horizontalRatio, verticalRatio); } } void WorksheetElementContainer::handleAspectAdded(const AbstractAspect* aspect) { Q_D(WorksheetElementContainer); const auto* element = qobject_cast(aspect); if (element && (aspect->parentAspect() == this)) { connect(element, &WorksheetElement::hovered, this, &WorksheetElementContainer::childHovered); connect(element, &WorksheetElement::unhovered, this, &WorksheetElementContainer::childUnhovered); element->graphicsItem()->setParentItem(d); qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->setZValue(zVal++); } if (!isLoading()) d->recalcShapeAndBoundingRect(); } void WorksheetElementContainer::childHovered() { Q_D(WorksheetElementContainer); if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } } void WorksheetElementContainer::childUnhovered() { Q_D(WorksheetElementContainer); if (!d->isSelected()) { d->m_hovered = true; d->update(); } } void WorksheetElementContainer::prepareGeometryChange() { Q_D(WorksheetElementContainer); d->prepareGeometryChangeRequested(); } //################################################################ //################### Private implementation ########################## //################################################################ WorksheetElementContainerPrivate::WorksheetElementContainerPrivate(WorksheetElementContainer *owner) : q(owner) { setAcceptHoverEvents(true); } QString WorksheetElementContainerPrivate::name() const { return q->name(); } void WorksheetElementContainerPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { scene()->clearSelection(); setSelected(true); QMenu* menu = q->createContextMenu(); menu->exec(event->screenPos()); } void WorksheetElementContainerPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; update(); } } void WorksheetElementContainerPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; update(); } } bool WorksheetElementContainerPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibleChanged(on); return oldValue; } void WorksheetElementContainerPrivate::prepareGeometryChangeRequested() { prepareGeometryChange(); recalcShapeAndBoundingRect(); } void WorksheetElementContainerPrivate::recalcShapeAndBoundingRect() { // if (q->isLoading()) // return; //old logic calculating the bounding box as as the box covering all children. //we might need this logic later once we implement something like selection of multiple plots, etc. // boundingRectangle = QRectF(); // QVector childList = q->children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); // foreach (const WorksheetElement* elem, childList) // boundingRectangle |= elem->graphicsItem()->mapRectToParent(elem->graphicsItem()->boundingRect()); // float penWidth = 2.; boundingRectangle = q->rect(); boundingRectangle = QRectF(-boundingRectangle.width()/2 - penWidth / 2, -boundingRectangle.height()/2 - penWidth / 2, boundingRectangle.width() + penWidth, boundingRectangle.height() + penWidth); QPainterPath path; path.addRect(boundingRectangle); //make the shape somewhat thicker then the hoveredPen to make the selection/hovering box more visible containerShape = QPainterPath(); containerShape.addPath(WorksheetElement::shapeFromPath(path, QPen(QBrush(), penWidth))); } // Inherited from QGraphicsItem QRectF WorksheetElementContainerPrivate::boundingRect() const { return boundingRectangle; } // Inherited from QGraphicsItem void WorksheetElementContainerPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(containerShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(containerShape); } } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index b7859f733..b1453ea42 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,3303 +1,3303 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2018 by 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 "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 "XYConvolutionCurve.h" #include "XYCorrelationCurve.h" #include "backend/core/Project.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/spreadsheet/Spreadsheet.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 /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name) : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd) : AbstractPlot(name, dd, AspectType::CartesianPlot) { 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->xRangeFormat = CartesianPlot::Numeric; d->yRangeFormat = CartesianPlot::Numeric; d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->rangeFirstValues = 1000; d->rangeLastValues = 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 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. //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 d->autoScaleOffsetFactor = 0.0f; m_plotArea = new PlotArea(name() + " plot area"); addChildFast(m_plotArea); //Plot title m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //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, &AbstractAspect::aspectAdded, this, &CartesianPlot::childAdded); connect(this, &AbstractAspect::aspectRemoved, this, &CartesianPlot::childRemoved); 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.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; //Axes Axis* axis = new Axis("x axis 1", 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", 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); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", 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", 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); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; Axis* axis = new Axis("x axis 1", 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", 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", 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", 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", 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", 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; //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); d->retransform(); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), 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); addConvolutionCurveAction = new QAction(i18n("xy-curve from a (De-)Convolution"), this); addCorrelationCurveAction = new QAction(i18n("xy-curve from a Auto-/Cross-Correlation"), 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); // addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-convolution-curve"), i18n("xy-curve from a (de-)convolution"), this); // addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-correlation-curve"), i18n("xy-curve from a auto-/cross-correlation"), 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); addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this); connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve); connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram); connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve); connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); connect(addLegendAction, &QAction::triggered, this, static_cast(&CartesianPlot::addLegend)); connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis); connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis); connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel); connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::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); addConvolutionAction = new QAction(i18n("Convolute/Deconvolute"), this); addCorrelationAction = new QAction(i18n("Auto-/Cross-Correlation"), 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, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::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, &QAction::triggered, this, &CartesianPlot::scaleAuto); connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoX); connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoY); connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn); connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut); connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX); connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX); connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY); connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY); connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX); connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX); connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY); connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY); //visibility action visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add New")); addNewMenu->setIcon(QIcon::fromTheme("list-add")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramAction); 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->addAction(addConvolutionCurveAction); addNewMenu->addAction(addCorrelationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addTextLabelAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); zoomMenu = new QMenu(i18n("Zoom")); zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); 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(nullptr, dataManipulationMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addAction(addConvolutionAction); dataAnalysisMenu->addAction(addCorrelationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addMenu(dataFitMenu); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); themeMenu->setIcon(QIcon::fromTheme("color-management")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme); connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close); auto* 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"); } QVector CartesianPlot::dependsOn() const { //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) QVector aspects; for (const auto* curve : children()) { if (curve->xColumn() && dynamic_cast(curve->xColumn()->parentAspect()) ) aspects << curve->xColumn()->parentAspect(); if (curve->yColumn() && dynamic_cast(curve->yColumn()->parentAspect()) ) aspects << curve->yColumn()->parentAspect(); } return aspects; } 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 a : vec) { auto* aspect = (AbstractAspect*)a; auto* 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(); } bool CartesianPlot::isPanningActive() const { Q_D(const CartesianPlot); return d->panningStarted; } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormat) 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, double, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, 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, double, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, 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) /*! returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) in plot's coordinates */ QRectF CartesianPlot::dataRect() const { Q_D(const CartesianPlot); return d->dataRect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } const QString& CartesianPlot::xRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->xRangeDateTimeFormat; } const QString& CartesianPlot::yRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->yRangeDateTimeFormat; } //############################################################################## //###################### 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())); }; void redo() override { - const double horizontalRatio = m_rect.width() / m_private->rect.width(); - const double verticalRatio = m_rect.height() / m_private->rect.height(); +// const double horizontalRatio = m_rect.width() / m_private->rect.width(); +// const double verticalRatio = m_rect.height() / m_private->rect.height(); qSwap(m_private->rect, m_rect); - m_private->q->handleResize(horizontalRatio, verticalRatio, false); +// m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; void undo() override { 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, ki18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged); void CartesianPlot::setXRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->xRangeFormat) exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged); void CartesianPlot::setYRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->yRangeFormat) exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format"))); } 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, ki18n("%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, ki18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; void redo() override { 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); }; void undo() override { 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; double m_minOld; double 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, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%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, ki18n("%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, ki18n("%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, ki18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; void redo() override { 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); }; void undo() override { 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; double m_minOld; double 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, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%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, ki18n("%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, ki18n("%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, ki18n("%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, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", 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", 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 nullptr; } 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(XYAnalysisCurve::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(XYAnalysisCurve::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(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add integration 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(XYAnalysisCurve::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(XYAnalysisCurve::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() { DEBUG("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(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const auto* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); curve->initStartValues(curCurve); //fit with weights for y if the curve has error bars for y if (curCurve->yErrorType() == XYCurve::SymmetricError && curCurve->yErrorPlusColumn()) { XYFitCurve::FitData fitData = curve->fitData(); fitData.yWeightsType = nsl_fit_weight_instrumental; curve->setFitData(fitData); curve->setYErrorColumn(curCurve->yErrorPlusColumn()); } curve->recalculate(); //add the child after the fit was calculated so the dock widgets gets the fit results //and call retransform() after this to calculate and to paint the data points of the fit-curve this->addChild(curve); curve->retransform(); } 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(XYAnalysisCurve::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::addConvolutionCurve() { XYConvolutionCurve* curve = new XYConvolutionCurve("Convolution"); this->addChild(curve); } void CartesianPlot::addCorrelationCurve() { XYCorrelationCurve* curve = new XYCorrelationCurve("Auto-/Cross-Correlation"); this->addChild(curve); } /*! * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. */ void CartesianPlot::addLegend(CartesianPlotLegend* legend) { m_legend = legend; this->addChild(legend); } 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::addTextLabel() { TextLabel* label = new TextLabel("text label"); this->addChild(label); label->setParentGraphicsItem(graphicsItem()); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const auto* curve = qobject_cast(child); if (curve) { connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged); connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged); connect(curve, static_cast(&XYCurve::visibilityChanged), this, &CartesianPlot::curveVisibilityChanged); //update the legend on changes of the name, line and symbol styles connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::lineOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsStyleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsSizeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsRotationAngleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; //in case the first curve is added, check whether we start plotting datetime data if (children().size() == 1) { const auto* col = dynamic_cast(curve->xColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all horizontal axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisHorizontal) { auto* filter = static_cast(col->outputFilter()); d->xRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat); axis->setUndoAware(true); } } } } col = dynamic_cast(curve->yColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all vertical axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisVertical) { auto* filter = static_cast(col->outputFilter()); d->yRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->yRangeDateTimeFormat); axis->setUndoAware(true); } } } } } } else { const auto* hist = qobject_cast(child); if (hist) { connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged); connect(hist, &Histogram::visibilityChanged, this, &CartesianPlot::curveVisibilityChanged); updateLegend(); } // if an element is hovered, the curves which are handled manually in this class // must be unhovered const WorksheetElement* element = static_cast(child); connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered); } if (!isLoading()) { //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty()) { const auto* elem = dynamic_cast(child); if (elem) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(elem)->loadThemeConfig(config); } } else { //no theme is available, apply the default colors for curves only, s.a. XYCurve::loadThemeConfig() const auto* curve = dynamic_cast(child); if (curve) { int index = indexOfChild(curve); QColor themeColor; if (index < m_themeColorPalette.size()) themeColor = m_themeColorPalette.at(index); else { if (m_themeColorPalette.size()) themeColor = m_themeColorPalette.last(); } auto* c = const_cast(curve); //Line QPen p = curve->linePen(); p.setColor(themeColor); c->setLinePen(p); //Drop line p = curve->dropLinePen(); p.setColor(themeColor); c->setDropLinePen(p); //Symbol QBrush brush = c->symbolsBrush(); brush.setColor(themeColor); c->setSymbolsBrush(brush); p = c->symbolsPen(); p.setColor(themeColor); c->setSymbolsPen(p); //Filling c->setFillingFirstColor(themeColor); //Error bars p.setColor(themeColor); c->setErrorBarsPen(p); } } } } 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 auto* curve = qobject_cast(child); if (curve) updateLegend(); } } /*! * \brief CartesianPlot::childHovered * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot), * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent) */ void CartesianPlot::childHovered() { Q_D(CartesianPlot); bool curveSender = dynamic_cast(QObject::sender()) != nullptr; if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } if (!curveSender) { for (auto curve: children()) curve->setHover(false); } } 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; bool updated = false; if (d->autoScaleX && d->autoScaleY) updated = this->scaleAuto(); else if (d->autoScaleX) updated = this->scaleAutoX(); else if (d->autoScaleY) updated = this->scaleAutoY(); if (!updated || !QObject::sender()) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() //via plot->dataChaged() in the file filter (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* c : children()) { c->recalcLogicalPoints(); c->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() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX) updated = this->scaleAutoX(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); const AbstractColumn* col = curve->xColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } /*! 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() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleY) this->scaleAutoY(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); const AbstractColumn* col = curve->yColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } 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 auto* 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::setLocked(bool locked) { Q_D(CartesianPlot); d->locked = locked; } bool 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->dataColumn()) continue; const double min = curve->getXMinimum(); if (d->curvesXMin > min) d->curvesXMin = min; const double max = curve->getXMaximum(); 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 { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); d->retransformScales(); } return update; } bool 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; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } 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 { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); d->retransformScales(); } return update; } bool CartesianPlot::scaleAuto() { DEBUG("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->dataColumn()) continue; const double min = curve->getXMinimum(); if (d->curvesXMin > min) d->curvesXMin = min; const double max = curve->getXMaximum(); 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; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; 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; } DEBUG(" xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax); 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 { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); } 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 { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); } d->retransformScales(); } return (updateX || updateY); } void CartesianPlot::zoomIn() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->xMax - d->xMin); double 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); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->xMax-d->xMin); double 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); setAutoScaleX(false); double oldRange = (d->xMax-d->xMin); double 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); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double oldRange = (d->xMax-d->xMin); double 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); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->yMax-d->yMin); double 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); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double oldRange = (d->yMax-d->yMin); double 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); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); double 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), q(plot) { 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); updateDataRect(); 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() { DEBUG("CartesianPlotPrivate::retransformScales()"); DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); PERFTRACE("CartesianPlotPrivate::retransformScales()"); auto* 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 double deltaXMin = 0; double deltaXMax = 0; double deltaYMin = 0; double 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(); } /* * calculates the rectangular of the are showing the actual data (plot's rect minus padding), * in plot's coordinates. */ void CartesianPlotPrivate::updateDataRect() { dataRect = mapRectFromScene(rect); dataRect.setX(dataRect.x() + horizontalPadding); dataRect.setY(dataRect.y() + verticalPadding); dataRect.setWidth(dataRect.width() - horizontalPadding); dataRect.setHeight(dataRect.height() - verticalPadding); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } void CartesianPlotPrivate::xRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisHorizontal) axis->retransformTickLabelStrings(); } } void CartesianPlotPrivate::yRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisVertical) axis->retransformTickLabelStrings(); } } /*! * 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) { DEBUG("CartesianPlotPrivate::createScale() scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (std::numeric_limits::lowest(), std::numeric_limits::max()); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type); } /*! * 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; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; const qreal w = rect.width(); const qreal 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); } //############################################################################## //################################## Events ################################## //############################################################################## 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(dataRect.height()/2); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(-dataRect.width()/2); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else { if (!locked && dataRect.contains(event->pos()) ){ panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); } QGraphicsItem::mousePressEvent(event); } } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas const int deltaXScene = (m_panningStart.x() - event->pos().x()); const int deltaYScene = (m_panningStart.y() - event->pos().y()); if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5) return; const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); const float deltaX = (logicalStart.x() - logicalEnd.x()); const float deltaY = (logicalStart.y() - logicalEnd.y()); xMax += deltaX; xMin += deltaX; yMax += deltaY; yMin += deltaY; retransformScales(); m_panningStart = event->pos(); } else QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(QString()); return; } QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = event->pos(); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); info += QLatin1String(", "); if (yRangeFormat == CartesianPlot::Numeric) info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionEnd.setX(event->pos().x()); m_selectionEnd.setY(-dataRect.height()/2); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(dataRect.width()/2); m_selectionEnd.setY(event->pos().y()); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); if (yRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } q->info(info); update(); } } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { setCursor(Qt::ArrowCursor); if (mouseMode == CartesianPlot::SelectionMode) { panningStarted = false; //TODO: why do we do this all the time?!?! const QPointF& itemPos = pos();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and set it QRectF newRect; const qreal w = rect.width(); const qreal 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) { if (locked) return; //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 (dataRect.contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); info += ", y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); } 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="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); 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="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); update(); } else if (mouseMode == CartesianPlot::MouseMode::SelectionMode) { // hover the nearest curve to the mousepointer // hovering curves is implemented in the parent, because no ignoreEvent() exists // for it. Checking all curves and hover the first bool curve_hovered = false; QVector curves = q->children(); for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves if(curve_hovered){ // if a curve is already hovered, disable hover for the rest curves[i]->setHover(false); continue; } if(curves[i]->activateCurve(event->pos())){ curves[i]->setHover(true); curve_hovered = true; continue; } curves[i]->setHover(false); } } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(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(); } float penWidth = 6.; QRectF rect = q->m_plotArea->graphicsItem()->boundingRect(); rect = QRectF(-rect.width()/2 - penWidth / 2, -rect.height()/2 - penWidth / 2, rect.width() + penWidth, rect.height() + penWidth); if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth, Qt::SolidLine)); painter->drawRect(rect); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth, Qt::SolidLine)); painter->drawRect(rect); } } //############################################################################## //################## 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, 'g', 16)); writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) ); writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) ); writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "xRangeFormat", QString::number(d->xRangeFormat) ); writer->writeAttribute( "yRangeFormat", QString::number(d->yRangeFormat) ); 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 (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool titleLabelRead = false; 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.subs("x").toString()); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); READ_INT_VALUE("autoScaleX", autoScaleX, bool); READ_INT_VALUE("autoScaleY", autoScaleY, bool); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMin").toString()); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMax").toString()); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMin").toString()); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMax").toString()); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale); READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale); READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat); READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat); READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); READ_DOUBLE_VALUE("verticalPadding", verticalPadding); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); 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(); READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { if (!titleLabelRead) { //the first text label is always the title label m_title->load(reader, preview); titleLabelRead = true; //TODO: the name is read in m_title->load() but we overwrite it here //since the old projects don't have this " - Title" appendix yet that we add in init(). //can be removed in couple of releases m_title->setName(name() + QLatin1String(" - ") + i18n("Title")); } else { TextLabel* label = new TextLabel("text label"); if (label->load(reader, preview)) { addChildFast(label); label->setParentGraphicsItem(graphicsItem()); } else { delete label; return false; } } } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis(QString()); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyConvolutionCurve") { XYConvolutionCurve* curve = new XYConvolutionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyCorrelationCurve") { XYCorrelationCurve* curve = new XYCorrelationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, QString()); 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, QString()); 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 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); } else { //initialize the color palette with default colors this->setColorPalette(KConfig()); } 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(); // theme path is saved with UNIX dir separator str = str.right(str.length() - str.lastIndexOf(QLatin1Char('/')) - 1); DEBUG(" set theme to " << str.toStdString()); 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) { if (config.hasGroup(QLatin1String("Theme"))) { KConfigGroup group = config.group(QLatin1String("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())); } else { //no theme is available, provide 5 "default colors" m_themeColorPalette.clear(); m_themeColorPalette.append(QColor(25, 25, 25)); m_themeColorPalette.append(QColor(0, 0, 127)); m_themeColorPalette.append(QColor(127 ,0, 0)); m_themeColorPalette.append(QColor(0, 127, 0)); m_themeColorPalette.append(QColor(85, 0, 127)); } //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.25f,0.45f,0.65f}; //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/kdefrontend/ThemeHandler.cpp b/src/kdefrontend/ThemeHandler.cpp index 5e2334abf..15fe007ba 100644 --- a/src/kdefrontend/ThemeHandler.cpp +++ b/src/kdefrontend/ThemeHandler.cpp @@ -1,255 +1,254 @@ /*************************************************************************** File : ThemeHandler.cpp Project : LabPlot Description : Widget for handling saving and loading of themes -------------------------------------------------------------------- Copyright : (C) 2016 Prakriti Bhardwaj (p_bhardwaj14@informatik.uni-kl.de) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 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 "ThemeHandler.h" #include "widgets/ThemesWidget.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include #include #include #include #include // #include /*! \class ThemeHandler \brief Provides a widget with buttons for loading of themes. Emits \c loadConfig() signal that have to be connected to the appropriate slots in the backend (plot widgets) \ingroup kdefrontend */ ThemeHandler::ThemeHandler(QWidget* parent) : QWidget(parent) { auto* horizontalLayout = new QHBoxLayout(this); horizontalLayout->setSpacing(0); horizontalLayout->setMargin(0); m_pbLoadTheme = new QPushButton(this); horizontalLayout->addWidget(m_pbLoadTheme); m_pbLoadTheme->setText(i18n("Apply Theme")); // pbSaveTheme = new QPushButton(this); // horizontalLayout->addWidget(pbSaveTheme); // pbSaveTheme->setText(i18n("Save Theme")); /* pbPublishTheme = new QPushButton(this); horizontalLayout->addWidget(pbPublishTheme); pbPublishTheme->setText("Publish Theme"); pbPublishTheme->setEnabled(false); */ connect(m_pbLoadTheme, &QPushButton::clicked, this, &ThemeHandler::showPanel); // connect( pbSaveTheme, SIGNAL(clicked()), this, SLOT(saveMenu())); // connect( pbPublishTheme, SIGNAL(clicked()), this, SLOT(publishThemes())); m_themeList = themeList(); m_pbLoadTheme->setEnabled(!m_themeList.isEmpty()); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /*! * get list of all theme files (full path) */ QStringList ThemeHandler::themeList() { DEBUG("ThemeHandler::themeList()"); // find all available themes files (system wide and user specific local files) QStringList dirs = QStandardPaths::locateAll(QStandardPaths::DataLocation, "themes", QStandardPaths::LocateDirectory); QStringList themes; for (const auto& dir : dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*"), QDir::Files); while (it.hasNext()) themes.append(it.next()); } if (!themes.isEmpty()) DEBUG(" first theme path: " << themes.first().toStdString()); return themes; } /*! * get list of all theme names */ QStringList ThemeHandler::themes() { DEBUG("ThemeHandler::themes()"); QStringList themePaths = themeList(); QStringList themes; for (int i = 0; i < themePaths.size(); ++i) { QFileInfo fileinfo(themePaths.at(i)); themes.append(fileinfo.fileName().split('.').at(0)); } if (!themes.isEmpty()) { DEBUG(" first theme: " << themes.first().toStdString()); QDEBUG(" themes = " << themes); } return themes; } /*! * get path for theme of name 'name' */ const QString ThemeHandler::themeFilePath(const QString& name) { DEBUG("ThemeHandler::themeFilePath() name = " << name.toStdString()); QStringList themePaths = themeList(); for (int i = 0; i < themePaths.size(); ++i) { if (themePaths.at(i).indexOf(name) != -1) { DEBUG(" theme \"" << name.toStdString() << "\" path: " << themePaths.at(i).toStdString()); return themePaths.at(i); } } return QString(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ThemeHandler::setCurrentTheme(const QString& name) { if (!name.isEmpty()) { m_pbLoadTheme->setText(i18n("Apply theme [active '%1']", name)); m_pbLoadTheme->setToolTip(i18n("Theme '%1' is active. Click on the button to change the theme.", name)); } else { m_pbLoadTheme->setText(i18n("Apply Theme")); m_pbLoadTheme->setToolTip(i18n("No theme is active. Click on the button to select a theme.")); } m_currentTheme = name; } void ThemeHandler::loadSelected(const QString& name) { emit loadThemeRequested(name); this->setCurrentTheme(name); if (!name.isEmpty()) emit info( i18n("Theme \"%1\" was loaded.", name) ); else emit info( i18n("Theming deactivated.") ); //in case a local theme file was loaded (we have write access), allow to publish it //TODO: activate this later // if (KStandardDirs::checkAccess(themeFilePath, W_OK)) { // pbPublishTheme->setEnabled(true); // m_currentLocalTheme = themeFilePath.right(themeFilePath.length() - themeFilePath.lastIndexOf(QDir::separator()) - 1); // } else { // pbPublishTheme->setEnabled(false); // m_currentLocalTheme.clear(); // } } void ThemeHandler::showPanel() { QMenu menu; ThemesWidget themeWidget(&menu); connect(&themeWidget, &ThemesWidget::themeSelected, this, &ThemeHandler::loadSelected); connect(&themeWidget, &ThemesWidget::themeSelected, &menu, &QMenu::close); connect(&themeWidget, &ThemesWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&themeWidget); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+m_pbLoadTheme->width(),-menu.sizeHint().height()); - menu.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); menu.exec(m_pbLoadTheme->mapToGlobal(pos)); } // void ThemeHandler::saveMenu() { // QMenu menu; // menu.addSection(i18n("Save As")); // // // add editable action // QWidgetAction* widgetAction = new QWidgetAction(this); // QFrame* frame = new QFrame(this); // QHBoxLayout* layout = new QHBoxLayout(frame); // // QLabel* label = new QLabel(i18n("Enter name:"), frame); // layout->addWidget(label); // // QLineEdit* leFilename = new QLineEdit(QString(), frame); // layout->addWidget(leFilename); // connect(leFilename, SIGNAL(returnPressed(QString)), this, SLOT(saveNewSelected(QString))); // connect(leFilename, SIGNAL(returnPressed(QString)), &menu, SLOT(close())); // // widgetAction->setDefaultWidget(frame); // menu.addAction(widgetAction); // // QPoint pos(-menu.sizeHint().width() + m_pbSaveTheme->width(), -menu.sizeHint().height()); // menu.exec(m_pbSaveTheme->mapToGlobal(pos)); // leFilename->setFocus(); // } // void ThemeHandler::saveNewSelected(const QString& filename) { // KConfig config(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + "themes" + '/' + filename, KConfig::SimpleConfig); // emit saveThemeRequested(config); // emit info( i18n("New theme \"%1\" was saved.", filename) ); // // m_currentLocalTheme = filename; // m_themeList.append(config.name()); // // //enable the publish button so the newly created theme can be published // //TODO: enable this later // // pbPublishTheme->setEnabled(true); // } /*! opens the dialog to upload the currently selected local theme. The publish button is only enabled if a local theme was loaded or one of the themes was modified and saved locally. */ // void ThemeHandler::publishThemes() { // int ret = KMessageBox::questionYesNo(this, // i18n("Do you want to upload your theme %1 to public web server?", m_currentLocalTheme), // i18n("Publish Theme")); // if (ret != KMessageBox::Yes) // return; // // // creating upload dialog // KNS3::UploadDialog dialog("labplot2_themes.knsrc", this); // dialog.setUploadFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + "themes" + '/' + m_currentLocalTheme); // dialog.setUploadName(m_currentLocalTheme); // //dialog.setDescription(); TODO: allow the user to provide a short description for the theme to be uploaded // dialog.exec(); // } diff --git a/src/kdefrontend/dockwidgets/ColumnDock.cpp b/src/kdefrontend/dockwidgets/ColumnDock.cpp index d70eb4bf3..9dc18dcfd 100644 --- a/src/kdefrontend/dockwidgets/ColumnDock.cpp +++ b/src/kdefrontend/dockwidgets/ColumnDock.cpp @@ -1,425 +1,430 @@ /*************************************************************************** File : ColumnDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2011-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for column 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 "ColumnDock.h" #include "backend/core/AbstractFilter.h" #include "backend/core/datatypes/SimpleCopyThroughFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/String2DoubleFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include /*! \class ColumnDock \brief Provides a widget for editing the properties of the spreadsheet columns currently selected in the project explorer. \ingroup kdefrontend */ ColumnDock::ColumnDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; connect(ui.leName, &QLineEdit::textChanged, this, &ColumnDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &ColumnDock::commentChanged); connect(ui.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int))); connect(ui.cbFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(formatChanged(int))); connect(ui.sbPrecision, SIGNAL(valueChanged(int)), this, SLOT(precisionChanged(int)) ); connect(ui.cbPlotDesignation, SIGNAL(currentIndexChanged(int)), this, SLOT(plotDesignationChanged(int))); retranslateUi(); } void ColumnDock::setColumns(QList list) { m_initializing = true; m_columnsList = list; m_column = list.first(); m_aspect = list.first(); //check whether we have non-editable columns (e.g. columns for residuals calculated in XYFitCurve) bool nonEditable = false; for (auto* col : m_columnsList) { auto* s = dynamic_cast(col->parentAspect()); if (s) { if (dynamic_cast(s)) { nonEditable = true; break; } } else { nonEditable = true; break; } } if (list.size() == 1) { //names and comments of non-editable columns in a file data source can be changed. if (!nonEditable && dynamic_cast(m_column->parentAspect()) != nullptr) { ui.leName->setEnabled(false); ui.leComment->setEnabled(false); } else { ui.leName->setEnabled(true); ui.leComment->setEnabled(true); } ui.leName->setText(m_column->name()); ui.leComment->setText(m_column->comment()); } else { ui.leName->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first column AbstractColumn::ColumnMode columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); ui.cbPlotDesignation->setCurrentIndex( int(m_column->plotDesignation()) ); if (!nonEditable) { // slots connect(m_column, &AbstractColumn::aspectDescriptionChanged, this, &ColumnDock::columnDescriptionChanged); connect(m_column, &AbstractColumn::modeChanged, this, &ColumnDock::columnModeChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::formatChanged, this, &ColumnDock::columnFormatChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &ColumnDock::columnPrecisionChanged); connect(m_column, &AbstractColumn::plotDesignationChanged, this, &ColumnDock::columnPlotDesignationChanged); } else { //disable widgets if we have at least one non-editable column ui.cbType->setEnabled(false); ui.lFormat->hide(); ui.cbFormat->hide(); ui.lPrecision->hide(); ui.sbPrecision->hide(); ui.lPlotDesignation->hide(); ui.cbPlotDesignation->hide(); } m_initializing = false; } void ColumnDock::updateTypeWidgets(AbstractColumn::ColumnMode mode) { ui.cbType->setCurrentIndex(ui.cbType->findData((int)mode)); switch (mode) { case AbstractColumn::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); ui.sbPrecision->setValue(filter->numDigits()); break; } case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { auto* filter = static_cast(m_column->outputFilter()); DEBUG(" set column format: " << filter->format().toStdString()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } case AbstractColumn::Integer: // nothing to set case AbstractColumn::Text: break; } } /*! depending on the currently selected column type (column mode) updates the widgets for the column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the type (column mode) is changed. */ void ColumnDock::updateFormatWidgets(AbstractColumn::ColumnMode mode) { ui.cbFormat->clear(); switch (mode) { case AbstractColumn::Numeric: ui.cbFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbFormat->addItem(i18n("Automatic (g)"), QVariant('g')); ui.cbFormat->addItem(i18n("Automatic (G)"), QVariant('G')); break; case AbstractColumn::Month: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("M")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("MM")); ui.cbFormat->addItem(i18n("Abbreviated Month Name"), QVariant("MMM")); ui.cbFormat->addItem(i18n("Full Month Name"), QVariant("MMMM")); break; case AbstractColumn::Day: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("d")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("dd")); ui.cbFormat->addItem(i18n("Abbreviated Day Name"), QVariant("ddd")); ui.cbFormat->addItem(i18n("Full Day Name"), QVariant("dddd")); break; case AbstractColumn::DateTime: for (const auto& s : AbstractColumn::dateTimeFormats()) ui.cbFormat->addItem(s, QVariant(s)); break; case AbstractColumn::Integer: case AbstractColumn::Text: break; } if (mode == AbstractColumn::Numeric) { ui.lPrecision->show(); ui.sbPrecision->show(); } else { ui.lPrecision->hide(); ui.sbPrecision->hide(); } if (mode == AbstractColumn::Text || mode == AbstractColumn::Integer) { ui.lFormat->hide(); ui.cbFormat->hide(); } else { ui.lFormat->show(); ui.cbFormat->show(); } if (mode == AbstractColumn::DateTime) { ui.cbFormat->setEditable(true); ui.cbFormat->setCurrentItem("yyyy-MM-dd hh:mm:ss.zzz"); } else { ui.cbFormat->setEditable(false); ui.cbFormat->setCurrentIndex(0); } } //************************************************************* //******** SLOTs for changes triggered in ColumnDock ********** //************************************************************* void ColumnDock::retranslateUi() { m_initializing = true; ui.cbType->clear(); ui.cbType->addItem(i18n("Numeric"), QVariant(int(AbstractColumn::Numeric))); ui.cbType->addItem(i18n("Integer"), QVariant(int(AbstractColumn::Integer))); ui.cbType->addItem(i18n("Text"), QVariant(int(AbstractColumn::Text))); ui.cbType->addItem(i18n("Month Names"), QVariant(int(AbstractColumn::Month))); ui.cbType->addItem(i18n("Day Names"), QVariant(int(AbstractColumn::Day))); ui.cbType->addItem(i18n("Date and Time"), QVariant(int(AbstractColumn::DateTime))); ui.cbPlotDesignation->clear(); ui.cbPlotDesignation->addItem(i18n("None")); ui.cbPlotDesignation->addItem(i18n("X")); ui.cbPlotDesignation->addItem(i18n("Y")); ui.cbPlotDesignation->addItem(i18n("Z")); ui.cbPlotDesignation->addItem(i18n("X-error")); ui.cbPlotDesignation->addItem(i18n("X-error -")); ui.cbPlotDesignation->addItem(i18n("X-error +")); ui.cbPlotDesignation->addItem(i18n("Y-error")); ui.cbPlotDesignation->addItem(i18n("Y-error -")); ui.cbPlotDesignation->addItem(i18n("Y-error +")); m_initializing = false; } /*! called when the type (column mode - numeric, text etc.) of the column was changed. */ void ColumnDock::typeChanged(int index) { DEBUG("ColumnDock::typeChanged()"); if (m_initializing) return; AbstractColumn::ColumnMode columnMode = (AbstractColumn::ColumnMode)ui.cbType->itemData(index).toInt(); - int format_index = ui.cbFormat->currentIndex(); m_initializing = true; this->updateFormatWidgets(columnMode); m_initializing = false; switch (columnMode) { case AbstractColumn::Numeric: { int digits = ui.sbPrecision->value(); for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); - filter->setNumericFormat(ui.cbFormat->itemData(format_index).toChar().toLatin1()); + + //TODO: using + //char format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1(); + //outside of the for-loop and + //filter->setNumericFormat(format); + //inseide the loop leads to wrong results when converting from integer to numeric -> 'f' is set instead of 'e' + filter->setNumericFormat(ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1()); filter->setNumDigits(digits); col->endMacro(); } break; } case AbstractColumn::Integer: case AbstractColumn::Text: for (auto* col : m_columnsList) col->setColumnMode(columnMode); break; case AbstractColumn::Month: case AbstractColumn::Day: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is saved as item data QString format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toString(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; case AbstractColumn::DateTime: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is the current text QString format = ui.cbFormat->currentText(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; } DEBUG("ColumnDock::typeChanged() DONE"); } /*! called when the format for the current type (column mode) was changed. */ void ColumnDock::formatChanged(int index) { DEBUG("ColumnDock::formatChanged()"); if (m_initializing) return; AbstractColumn::ColumnMode mode = (AbstractColumn::ColumnMode)ui.cbType->itemData(ui.cbType->currentIndex()).toInt(); - int format_index = index; switch (mode) { case AbstractColumn::Numeric: { + char format = ui.cbFormat->itemData(index).toChar().toLatin1(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); - filter->setNumericFormat(ui.cbFormat->itemData(format_index).toChar().toLatin1()); + filter->setNumericFormat(format); } break; } case AbstractColumn::Integer: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { - QString format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toString(); + QString format = ui.cbFormat->itemData(index).toString(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); } break; } } DEBUG("ColumnDock::formatChanged() DONE"); } void ColumnDock::precisionChanged(int digits) { if (m_initializing) return; for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setNumDigits(digits); } } void ColumnDock::plotDesignationChanged(int index) { if (m_initializing) return; auto pd = AbstractColumn::PlotDesignation(index); for (auto* col : m_columnsList) col->setPlotDesignation(pd); } //************************************************************* //********* SLOTs for changes triggered in Column ************* //************************************************************* void ColumnDock::columnDescriptionChanged(const AbstractAspect* aspect) { if (m_column != 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 ColumnDock::columnModeChanged(const AbstractAspect* aspect) { if (m_column != aspect) return; m_initializing = true; AbstractColumn::ColumnMode columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); m_initializing = false; } void ColumnDock::columnFormatChanged() { DEBUG("ColumnDock::columnFormatChanged()"); m_initializing = true; AbstractColumn::ColumnMode columnMode = m_column->columnMode(); switch (columnMode) { case AbstractColumn::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); break; } case AbstractColumn::Integer: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } } m_initializing = false; } void ColumnDock::columnPrecisionChanged() { m_initializing = true; auto* filter = static_cast(m_column->outputFilter()); ui.sbPrecision->setValue(filter->numDigits()); m_initializing = false; } void ColumnDock::columnPlotDesignationChanged(const AbstractColumn* col) { m_initializing = true; ui.cbPlotDesignation->setCurrentIndex( int(col->plotDesignation()) ); m_initializing = false; } diff --git a/src/kdefrontend/widgets/ThemesWidget.cpp b/src/kdefrontend/widgets/ThemesWidget.cpp index 47bf4eb96..f21d166c8 100644 --- a/src/kdefrontend/widgets/ThemesWidget.cpp +++ b/src/kdefrontend/widgets/ThemesWidget.cpp @@ -1,137 +1,144 @@ /*************************************************************************** File : ThemesWidget.cpp Project : LabPlot Description : widget for selecting themes -------------------------------------------------------------------- Copyright : (C) 2016 Prakriti Bhardwaj (p_bhardwaj14@informatik.uni-kl.de) Copyright : (C) 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 * * * ***************************************************************************/ #include "ThemesWidget.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include // #include /*! \class ThemesWidget \brief Widget for showing theme previews and for selecting a theme. \ingroup kdefrontend */ ThemesWidget::ThemesWidget(QWidget* parent) : QListView(parent) { setSelectionMode(QAbstractItemView::SingleSelection); setWordWrap(true); setViewMode(QListWidget::IconMode); - setResizeMode(QListWidget::Adjust); + setResizeMode(QListWidget::Fixed); setDragDropMode(QListView::NoDragDrop); //make the icon 3x3cm big and show two of them in the height int size = 3.0/2.54 * QApplication::desktop()->physicalDpiX(); setIconSize(QSize(size, size)); - setMinimumSize(1.1*size, 2.1*size); //add some offset here to take care of potential scrollbars, etc. (not very precise...) setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //show preview pixmaps auto* mContentItemModel = new QStandardItemModel(this); QStringList themeList = ThemeHandler::themes(); QStringList themeImgPathList = QStandardPaths::locateAll(QStandardPaths::DataLocation, "themes/screenshots/", QStandardPaths::LocateDirectory); if (themeImgPathList.isEmpty()) return; const QString& themeImgPath = themeImgPathList.first(); QString tempPath; for (int i = 0; i < themeList.size(); ++i) { auto* listItem = new QStandardItem(); tempPath = themeImgPath + themeList.at(i) + ".png"; if (!QFile::exists(tempPath)) tempPath = themeImgPath + "Unavailable.png"; listItem->setIcon(QIcon(QPixmap(tempPath))); listItem->setText(themeList.at(i)); mContentItemModel->appendRow(listItem); } //create and add the icon for "None" QPixmap pm(size, size); QPen pen(Qt::SolidPattern, 1); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; pen.setColor(color); QPainter pa; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); - pa.drawRect(5, 5, size-10, size-10); + pa.drawRect(2, 2, size - 4, size - 4); pa.end(); auto* listItem = new QStandardItem(); listItem->setIcon(pm); listItem->setText(i18n("None")); mContentItemModel->appendRow(listItem); //adding download themes option //TODO: activate this later // QStandardItem* listItem = new QStandardItem(); // listItem->setIcon(QIcon::fromTheme("get-hot-new-stuff")); // listItem->setText("Download Themes"); // listItem->setData("file_download_theme", Qt::UserRole); // mContentItemModel->appendRow(listItem); setModel(mContentItemModel); + //resize the widget to show three items + QFont font; + QFontMetrics fm(font); + QSize widgetSize(size + qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent), + 3*(size + fm.height() + spacing()) + fm.height()); + setMinimumSize(widgetSize); + setMaximumSize(widgetSize); + //SLOTS connect(this, &ThemesWidget::clicked, this, &ThemesWidget::applyClicked); } void ThemesWidget::applyClicked(const QModelIndex& index) { const QString& themeName = index.data(Qt::DisplayRole).toString(); //TODO: activate this later // if (themeName == "file_download_theme") // this->downloadThemes(); // else if (index.row() == model()->rowCount()-1) emit themeSelected(QString()); //item with the string "None" was selected -> no theme else emit themeSelected(themeName); } //TODO: activate this later // void ThemesWidget::downloadThemes() { // KNS3::DownloadDialog dialog("labplot2_themes.knsrc", this); // dialog.exec(); // foreach (const KNS3::Entry& e, dialog.changedEntries()) { // kDebug() << "Changed Entry: " << e.name(); // } // }