[education/labplot] src/frontend: In the context menu in the Project Explorer, allow to plot the data from columns having different parents:
Alexander Semke <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b885657777edbb6fe2f4dd89fdc7539d58a20397 by Alexander Semke.
Committed on 02/08/2026 at 18:52.
Pushed by asemke into branch 'master'.
In the context menu in the Project Explorer, allow to plot the data from columns having different parents:
* work with pointers instead of column names in PlotDataDialog since the columns from different spreadsheets can have the same name
* if columns with different parent spreadsheets are selected in the project explorer, show a dedicated "plot data" sub-menu in the context
menu and show the full menu from Spreadsheet in case it's the same parent (old/current behavior).
M +62 -28 src/frontend/ProjectExplorer.cpp
M +70 -64 src/frontend/spreadsheet/PlotDataDialog.cpp
M +4 -3 src/frontend/spreadsheet/PlotDataDialog.h
https://invent.kde.org/education/labplot/-/commit/b885657777edbb6fe2f4dd89fdc7539d58a20397
diff --git a/src/frontend/ProjectExplorer.cpp b/src/frontend/ProjectExplorer.cpp
index b1b0f51817..b7a718ff8b 100644
--- a/src/frontend/ProjectExplorer.cpp
+++ b/src/frontend/ProjectExplorer.cpp
@@ -20,6 +20,7 @@
#include "backend/worksheet/plots/cartesian/CartesianPlot.h"
#include "backend/worksheet/Worksheet.h"
#include "frontend/core/ContentDockWidget.h"
+#include "frontend/spreadsheet/PlotDataDialog.h"
#include <KConfig>
#include <KConfigGroup>
@@ -35,6 +36,7 @@
#include <QDrag>
#include <QHeaderView>
#include <QLineEdit>
+#include <QActionGroup>
#include <QMenu>
#include <QMimeData>
#include <QPushButton>
@@ -152,10 +154,11 @@ void ProjectExplorer::contextMenuEvent(QContextMenuEvent* event) {
// for the current selection determine the selected aspects
const auto& items = m_treeView->selectionModel()->selectedIndexes();
- const int selectedAspectsCount = items.size() / 4; // 4 columns in the tree view, divide by 4 to get the number of rows/aspects
+ const auto columnCount = m_treeView->model()->columnCount();
+ const int selectedAspectsCount = items.size() / columnCount; // divide by the number of columns in the tree view to get the number of rows/aspects
QVector<AbstractAspect*> selectedAspects;
for (int i = 0; i < selectedAspectsCount; ++i) {
- const auto& item = items.at(i * 4);
+ const auto& item = items.at(i * columnCount);
selectedAspects << static_cast<AbstractAspect*>(item.internalPointer());
}
@@ -175,6 +178,24 @@ void ProjectExplorer::contextMenuEvent(QContextMenuEvent* event) {
} else if (selectedAspectsCount > 1) { // multiple aspects are selected
menu = new QMenu(this);
+ // lambda function to check if all selected aspects are of a specific type
+ auto checkAspectType = [&selectedAspects](AspectType type) -> bool {
+ // for WorksheetElements, check the inheritance and not the exact type
+ if (type == AspectType::WorksheetElement) {
+ for (const auto* aspect : selectedAspects) {
+ if (!aspect->inherits<WorksheetElement>())
+ return false;
+ }
+ return true;
+ }
+
+ for (const auto* aspect : selectedAspects) {
+ if (aspect->type() != type)
+ return false;
+ }
+ return true;
+ };
+
// check if the selected objects have the same parent and
// show parent's context menu for columns to allow plot data, etc.
bool sameParent = true;
@@ -191,26 +212,10 @@ void ProjectExplorer::contextMenuEvent(QContextMenuEvent* event) {
}
}
- if (sameParent && parentAspect) {
- // lambda function to check if all selected aspects are of a specific type
- auto checkAspectType = [&selectedAspects](AspectType type) -> bool {
- // for WorksheetElements, check the inheritance and not the exact type
- if (type == AspectType::WorksheetElement) {
- for (const auto* aspect : selectedAspects) {
- if (!aspect->inherits<WorksheetElement>())
- return false;
- }
- return true;
- }
-
- for (const auto* aspect : selectedAspects) {
- if (aspect->type() != type)
- return false;
- }
- return true;
- };
+ const bool allColumns = checkAspectType(AspectType::Column);
- if (checkAspectType(AspectType::Column)) { // check columns
+ if (sameParent && parentAspect) {
+ if (allColumns) { // check columns
if (parentAspect->type() == AspectType::Spreadsheet) {
auto* spreadsheet = static_cast<Spreadsheet*>(parentAspect);
spreadsheet->fillColumnsContextMenu(menu);
@@ -243,6 +248,33 @@ void ProjectExplorer::contextMenuEvent(QContextMenuEvent* event) {
menu->addSeparator();
}
}
+ } else if (allColumns) {
+ // the selected columns belong to different parents (e.g. columns coming from different spreadsheets) ->
+ // other column-specific actions require all columns to belong to the same spreadsheet/notebook,
+ // so we only offer the "Plot Data" action here.
+ QVector<Column*> columns;
+ for (const auto* aspect : selectedAspects) {
+ const auto* column = static_cast<const Column*>(aspect);
+ if (!column->isPlottable()) {
+ columns.clear();
+ break;
+ }
+ columns << const_cast<Column*>(column);
+ }
+
+ if (!columns.isEmpty()) {
+ auto* plotDataMenu = new QMenu(i18n("Plot Data"), menu);
+ auto* plotDataActionGroup = new QActionGroup(menu);
+ CartesianPlot::fillAddNewPlotMenu(plotDataMenu, plotDataActionGroup);
+ connect(plotDataActionGroup, &QActionGroup::triggered, this, [this, columns](QAction* action) {
+ const auto type = static_cast<Plot::PlotType>(action->data().toInt());
+ auto* dlg = new PlotDataDialog(m_project, type);
+ dlg->setSelectedColumns(columns);
+ dlg->exec();
+ });
+ menu->addMenu(plotDataMenu);
+ menu->addSeparator();
+ }
}
// add "expand/collapse" actions if the selected aspects have children
@@ -899,19 +931,20 @@ void ProjectExplorer::selectionChanged(const QItemSelection& selected, const QIt
QModelIndex index;
AbstractAspect* aspect = nullptr;
- // there are four model indices in each row
- //-> divide by 4 to obtain the number of selected rows (=aspects)
+ // there are as many model indices in each row as there are columns in the tree view
+ //-> divide by the column count to obtain the number of selected rows (=aspects)
+ const auto columnCount = m_treeView->model()->columnCount();
const auto& sitems = selected.indexes();
- for (int i = 0; i < sitems.size() / 4; ++i) {
- index = sitems.at(i * 4);
+ for (int i = 0; i < sitems.size() / columnCount; ++i) {
+ index = sitems.at(i * columnCount);
aspect = static_cast<AbstractAspect*>(index.internalPointer());
QDEBUG("sitems ASPECT =" << aspect)
aspect->setSelected(true);
}
const auto& ditems = deselected.indexes();
- for (int i = 0; i < ditems.size() / 4; ++i) {
- index = ditems.at(i * 4);
+ for (int i = 0; i < ditems.size() / columnCount; ++i) {
+ index = ditems.at(i * columnCount);
aspect = static_cast<AbstractAspect*>(index.internalPointer());
QDEBUG("ditems ASPECT =" << aspect)
aspect->setSelected(false);
@@ -1018,7 +1051,8 @@ void ProjectExplorer::deleteSelected() {
if (status == KMessageBox::SecondaryAction)
return;
- m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size() / 4));
+ m_project->beginMacro(
+ i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size() / columnCount));
// determine aspects to be deleted:
// it's enough to delete parent items in the selection only,
diff --git a/src/frontend/spreadsheet/PlotDataDialog.cpp b/src/frontend/spreadsheet/PlotDataDialog.cpp
index 8995393579..e2cfac5582 100644
--- a/src/frontend/spreadsheet/PlotDataDialog.cpp
+++ b/src/frontend/spreadsheet/PlotDataDialog.cpp
@@ -290,7 +290,7 @@ void PlotDataDialog::setSelectedColumns(QVector<Column*> selectedColumns) {
if (m_basicPlotType && !m_fitDistributionMode)
processColumnsForXYCurve(columnNames, xColumnName);
else
- processColumnsForHistogram(columnNames);
+ processColumnsForHistogram();
// resize the scroll area to show five ComboBoxes at maximum without showing the scroll bars
int size = m_columnComboBoxes.size() >= 5 ? 5 : m_columnComboBoxes.size();
@@ -327,33 +327,33 @@ void PlotDataDialog::processColumnsForXYCurve(const QStringList& columnNames, co
ui->gbPlotPlacement->setTitle(i18n("Add Plot to"));
}
- // show all selected/available column names in the data comboboxes
+ // show all selected/available columns in the data comboboxes.
+ // the columns are attached to the items as data (in addition to the column names) since
+ // column names alone are not guaranteed to be unique, e.g. when plotting columns
+ // coming from different spreadsheets that happen to have columns with identical names.
for (auto* const comboBox : m_columnComboBoxes)
- comboBox->addItems(columnNames);
+ populateColumnComboBox(comboBox);
if (!xColumnName.isEmpty()) {
// show in the X-data combobox the first column having X as the plot designation
- ui->cbXColumn->setCurrentIndex(ui->cbXColumn->findText(xColumnName));
+ const int xColumnIndex = columnNames.indexOf(xColumnName);
+ ui->cbXColumn->setCurrentIndex(xColumnIndex);
- // for the remaining columns, show the names in the comboboxes for the Y-data
+ // for the remaining columns, show them in the comboboxes for the Y-data
// TODO: handle columns with error-designations
int yColumnIndex = 1; // the index of the first Y-data comboBox in m_columnComboBoxes
- for (const QString& name : columnNames) {
- if (name != xColumnName) {
+ for (int i = 0; i < columnNames.size(); ++i) {
+ if (i != xColumnIndex) {
QComboBox* comboBox = m_columnComboBoxes[yColumnIndex];
- comboBox->setCurrentIndex(comboBox->findText(name));
+ comboBox->setCurrentIndex(i);
yColumnIndex++;
}
}
} else {
// no column with "x plot designation" is selected, simply show all columns in the order they were selected.
// first selected column will serve as the x-column.
- int yColumnIndex = 0;
- for (const QString& name : columnNames) {
- QComboBox* comboBox = m_columnComboBoxes[yColumnIndex];
- comboBox->setCurrentIndex(comboBox->findText(name));
- yColumnIndex++;
- }
+ for (int i = 0; i < columnNames.size(); ++i)
+ m_columnComboBoxes[i]->setCurrentIndex(i);
}
}
@@ -361,7 +361,7 @@ void PlotDataDialog::processColumnsForXYCurve(const QStringList& columnNames, co
* processes columns for cases where one single column
* is required per "plot" (histogram, boxplot, etc.)
* */
-void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames) {
+void PlotDataDialog::processColumnsForHistogram() {
ui->line->hide();
ui->spacer->changeSize(0, 0);
ui->chkCreateDataCurve->hide();
@@ -377,7 +377,7 @@ void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames)
// use the already available cbXColumn combo box as the first data column
ui->lXColumn->setText(i18n("Data"));
m_columnComboBoxes << ui->cbXColumn;
- ui->cbXColumn->addItems(columnNames);
+ populateColumnComboBox(ui->cbXColumn);
ui->cbXColumn->setCurrentIndex(0);
}
@@ -394,7 +394,7 @@ void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames)
// one data column + tick labels column: one plot, but show the data combobox
ui->lYColumn->setText(i18n("Data"));
m_columnComboBoxes << ui->cbYColumn;
- ui->cbYColumn->addItems(columnNames);
+ populateColumnComboBox(ui->cbYColumn);
ui->cbYColumn->setCurrentIndex(0);
ui->rbCurvePlacementAllInOnePlotArea->setChecked(true);
ui->gbCurvePlacement->hide();
@@ -408,7 +408,7 @@ void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames)
// use the already available cbYColumn combo box
ui->lYColumn->setText(i18n("Data"));
m_columnComboBoxes << ui->cbYColumn;
- ui->cbYColumn->addItems(columnNames);
+ populateColumnComboBox(ui->cbYColumn);
ui->cbYColumn->setCurrentIndex(firstDataIndex);
// add a ComboBox for every further column to be plotted
@@ -419,7 +419,7 @@ void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames)
auto* comboBox = new QComboBox();
gridLayout->addWidget(label, i + rowOffset, 0, 1, 1);
gridLayout->addWidget(comboBox, i + rowOffset, 2, 1, 1);
- comboBox->addItems(columnNames);
+ populateColumnComboBox(comboBox);
comboBox->setCurrentIndex(i);
m_columnComboBoxes << comboBox;
}
@@ -448,7 +448,7 @@ void PlotDataDialog::plot() {
plot->setType(CartesianPlot::Type::FourAxes);
worksheet->addChild(plot);
if (m_columnComboBoxes.count() == 2)
- setAxesColumnLabels(plot, m_columnComboBoxes.at(1)->currentText());
+ setAxesColumnLabels(plot, columnFromComboBox(m_columnComboBoxes.at(1)));
addCurvesToPlot(plot);
setAxesTitles(plot);
@@ -459,15 +459,7 @@ void PlotDataDialog::plot() {
worksheet->endMacro();
} else if (ui->rbPlotPlacementNewWorksheet->isChecked()) { // add curves to a new plot in a new worksheet
// determine the parent folder first where the worksheet will be added as a child
- auto* parent = m_parentAspect->parentAspect();
- if (parent->type() == AspectType::Spreadsheet || parent->type() == AspectType::Workbook)
- parent = parent->parentAspect();
- else if (parent->type() == AspectType::DatapickerCurve)
- parent = parent->parentAspect()->parentAspect();
-#ifdef HAVE_MQTT
- else if (dynamic_cast<MQTTTopic*>(m_parentAspect))
- parent = m_parentAspect->project();
-#endif
+ auto* parent = determineParentFolder();
parent->beginMacro(i18n("Plot data from %1", m_parentAspect->name()));
auto* worksheet = new Worksheet(i18n("Worksheet - %1", m_parentAspect->name()));
parent->addChild(worksheet);
@@ -478,7 +470,7 @@ void PlotDataDialog::plot() {
plot->setType(CartesianPlot::Type::FourAxes);
worksheet->addChild(plot);
if (m_columnComboBoxes.count() == 2)
- setAxesColumnLabels(plot, m_columnComboBoxes.at(1)->currentText());
+ setAxesColumnLabels(plot, columnFromComboBox(m_columnComboBoxes.at(1)));
addCurvesToPlot(plot);
setAxesTitles(plot);
} else {
@@ -498,15 +490,7 @@ void PlotDataDialog::plot() {
parent->endMacro();
} else if (ui->rbPlotPlacementNewWorksheets->isChecked()) { // add curves to a new plot in a new worksheet for each of them
// determine the parent folder first where the new worksheets will be added as children
- auto* parent = m_parentAspect->parentAspect();
- if (parent->type() == AspectType::Spreadsheet || parent->type() == AspectType::Workbook)
- parent = parent->parentAspect();
- else if (parent->type() == AspectType::DatapickerCurve)
- parent = parent->parentAspect()->parentAspect();
-#ifdef HAVE_MQTT
- else if (dynamic_cast<MQTTTopic*>(m_parentAspect))
- parent = m_parentAspect->project();
-#endif
+ auto* parent = determineParentFolder();
parent->beginMacro(i18n("Plot data from %1", m_parentAspect->name()));
addCurvesToWorksheets(parent);
@@ -531,12 +515,42 @@ void PlotDataDialog::plot() {
}
}
-Column* PlotDataDialog::columnFromName(const QString& name) const {
- for (auto* column : m_columns) {
- if (column->name() == name)
- return column;
+Column* PlotDataDialog::columnFromComboBox(const QComboBox* comboBox) const {
+ return static_cast<Column*>(comboBox->currentData().value<void*>());
+}
+
+/*!
+ * determines the parent folder where new worksheet(s) created by this dialog should be added as children.
+ */
+AbstractAspect* PlotDataDialog::determineParentFolder() const {
+ auto* parent = m_parentAspect->parentAspect();
+ if (!parent) {
+ // m_parentAspect doesn't have a parent, e.g. it's the top level project itself
+ // (used when plotting columns coming from different spreadsheets) -> add the new
+ // worksheet(s) as a child of m_parentAspect directly.
+ return m_parentAspect;
}
- return nullptr;
+
+ if (parent->type() == AspectType::Spreadsheet || parent->type() == AspectType::Workbook)
+ parent = parent->parentAspect();
+ else if (parent->type() == AspectType::DatapickerCurve)
+ parent = parent->parentAspect()->parentAspect();
+#ifdef HAVE_MQTT
+ else if (dynamic_cast<MQTTTopic*>(m_parentAspect))
+ parent = m_parentAspect->project();
+#endif
+ return parent;
+}
+
+/*!
+ * populates \c comboBox with the names of all selected columns in \c m_columns.
+ * the column pointer is attached to every item as data since column names alone
+ * are not guaranteed to be unique, e.g. when plotting columns coming from
+ * different spreadsheets that happen to have columns with identical names.
+ */
+void PlotDataDialog::populateColumnComboBox(QComboBox* comboBox) const {
+ for (auto* column : m_columns)
+ comboBox->addItem(column->name(), QVariant::fromValue(static_cast<void*>(column)));
}
/*!
@@ -556,10 +570,10 @@ void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) {
case Plot::PlotType::LineSymbol2PointSegment:
case Plot::PlotType::LineSymbol3PointSegment:
case Plot::PlotType::Formula: {
- Column* xColumn = columnFromName(ui->cbXColumn->currentText());
+ Column* xColumn = columnFromComboBox(ui->cbXColumn);
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- Column* yColumn = columnFromName(name);
+ Column* yColumn = columnFromComboBox(comboBox);
// if only one column was selected, allow to use this column for x and for y.
// otherwise, don't assign xColumn to y
@@ -582,8 +596,7 @@ void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) {
case Plot::PlotType::RunChart:
case Plot::PlotType::ParetoChart: {
for (auto* comboBox : m_columnComboBoxes) {
- const auto& name = comboBox->currentText();
- const auto* column = columnFromName(name);
+ const auto* column = columnFromComboBox(comboBox);
addSingleSourceColumnPlot(column, plot);
}
break;
@@ -593,7 +606,7 @@ void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) {
case Plot::PlotType::LollipopPlot: {
QVector<const AbstractColumn*> columns;
for (auto* comboBox : m_columnComboBoxes)
- columns << columnFromName(comboBox->currentText());
+ columns << columnFromComboBox(comboBox);
addMultiSourceColumnsPlot(columns, plot);
break;
@@ -623,11 +636,10 @@ void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) {
case Plot::PlotType::LineSymbol2PointSegment:
case Plot::PlotType::LineSymbol3PointSegment:
case Plot::PlotType::Formula: {
- const QString& xColumnName = ui->cbXColumn->currentText();
- auto* xColumn = columnFromName(xColumnName);
+ auto* xColumn = columnFromComboBox(ui->cbXColumn);
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- auto* yColumn = columnFromName(name);
+ auto* yColumn = columnFromComboBox(comboBox);
if (yColumn == xColumn)
continue;
@@ -654,7 +666,7 @@ void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) {
case Plot::PlotType::ParetoChart: {
for (auto* comboBox : m_columnComboBoxes) {
const auto& name = comboBox->currentText();
- const auto* column = columnFromName(name);
+ const auto* column = columnFromComboBox(comboBox);
auto* plot = new CartesianPlot(i18n("Plot Area %1", name));
plot->setType(CartesianPlot::Type::FourAxes);
@@ -675,7 +687,7 @@ void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) {
case Plot::PlotType::LollipopPlot: {
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- auto* column = columnFromName(name);
+ auto* column = columnFromComboBox(comboBox);
auto* plot = new CartesianPlot(i18n("Plot Area %1", name));
plot->setType(CartesianPlot::Type::FourAxes);
@@ -715,11 +727,10 @@ void PlotDataDialog::addCurvesToWorksheets(AbstractAspect* parent) {
case Plot::PlotType::LineSymbol2PointSegment:
case Plot::PlotType::LineSymbol3PointSegment:
case Plot::PlotType::Formula: {
- const QString& xColumnName = ui->cbXColumn->currentText();
- Column* xColumn = columnFromName(xColumnName);
+ Column* xColumn = columnFromComboBox(ui->cbXColumn);
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- Column* yColumn = columnFromName(name);
+ Column* yColumn = columnFromComboBox(comboBox);
if (yColumn == xColumn)
continue;
@@ -745,7 +756,7 @@ void PlotDataDialog::addCurvesToWorksheets(AbstractAspect* parent) {
case Plot::PlotType::ParetoChart: {
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- const auto* column = columnFromName(name);
+ const auto* column = columnFromComboBox(comboBox);
auto* worksheet = new Worksheet(i18n("Worksheet - %1", name));
parent->addChild(worksheet);
@@ -765,7 +776,7 @@ void PlotDataDialog::addCurvesToWorksheets(AbstractAspect* parent) {
case Plot::PlotType::LollipopPlot: {
for (auto* comboBox : m_columnComboBoxes) {
const QString& name = comboBox->currentText();
- Column* column = columnFromName(name);
+ Column* column = columnFromComboBox(comboBox);
auto* worksheet = new Worksheet(i18n("Worksheet - %1", name));
parent->addChild(worksheet);
@@ -1044,11 +1055,6 @@ void PlotDataDialog::setAxesColumnLabels(CartesianPlot* plot, const Column* colu
}
}
-void PlotDataDialog::setAxesColumnLabels(CartesianPlot* plot, const QString& columnName) {
- const auto* column = columnFromName(columnName);
- setAxesColumnLabels(plot, column);
-}
-
/*!
* sets the axes titles of the plot according to the selected columns and the plot type,
* called after new plots were added to the plot area.
diff --git a/src/frontend/spreadsheet/PlotDataDialog.h b/src/frontend/spreadsheet/PlotDataDialog.h
index 22e9a54431..397afd8326 100644
--- a/src/frontend/spreadsheet/PlotDataDialog.h
+++ b/src/frontend/spreadsheet/PlotDataDialog.h
@@ -64,7 +64,7 @@ private:
void processColumnsForXYCurve(const QStringList& columnNames, const QString& xColumnName);
- void processColumnsForHistogram(const QStringList&);
+ void processColumnsForHistogram();
void addCurvesToPlot(CartesianPlot*);
void addCurvesToPlots(Worksheet*);
@@ -74,12 +74,13 @@ private:
void addSingleSourceColumnPlot(const Column* column, CartesianPlot*);
void addMultiSourceColumnsPlot(const QVector<const AbstractColumn*>&, CartesianPlot*);
- Column* columnFromName(const QString&) const;
+ Column* columnFromComboBox(const QComboBox*) const;
+ void populateColumnComboBox(QComboBox*) const;
+ AbstractAspect* determineParentFolder() const;
void adjustWorksheetSize(Worksheet*) const;
void setAxesTitles(CartesianPlot*, const QString& yColumnName = QString()) const;
void adjustPadding(CartesianPlot*, const QString& yColumnName, bool firstColumn = false, bool lastColumn = false) const;
- void setAxesColumnLabels(CartesianPlot*, const QString& columnName);
void setAxesColumnLabels(CartesianPlot*, const Column*);
private Q_SLOTS: