Perspectives - Part 4a of 4

.. Import and Export perspectives to an '.gchartset' file
   as XML data.

.. Added to MainWindow's View menu and the Manage Perspectives
   dialog.
This commit is contained in:
Mark Liversedge
2021-07-05 11:06:02 +01:00
parent 4e570230a7
commit 68e7fcacb4
9 changed files with 274 additions and 58 deletions

View File

@@ -119,13 +119,14 @@ ChartBar::ChartBar(Context *context) : QWidget(context->mainWindow), context(con
connect(menuMapper, SIGNAL(mapped(int)), this, SLOT(triggerContextMenu(int)));
barMenu = new QMenu("Add");
chartMenu = barMenu->addMenu(tr("New "));
chartMenu = barMenu->addMenu(tr("New Chart"));
barMenu->addAction(tr("Import ..."), context->mainWindow, SLOT(importChart()));
barMenu->addAction(tr("Import Chart ..."), context->mainWindow, SLOT(importChart()));
#ifdef GC_HAS_CLOUD_DB
barMenu->addAction(tr("Download ..."), context->mainWindow, SLOT(addChartFromCloudDB()));
barMenu->addAction(tr("Download Chart..."), context->mainWindow, SLOT(addChartFromCloudDB()));
#endif
// menu
connect(menuButton, SIGNAL(clicked()), this, SLOT(menuPopup()));
connect(chartMenu, SIGNAL(aboutToShow()), this, SLOT(setChartMenu()));

View File

@@ -658,6 +658,9 @@ MainWindow::MainWindow(const QDir &home)
viewMenu->addAction(tr("Trends"), this, SLOT(selectHome()));
viewMenu->addAction(tr("Train"), this, SLOT(selectTrain()));
viewMenu->addSeparator();
viewMenu->addAction(tr("Import Perspective..."), this, SLOT(importPerspective()));
viewMenu->addAction(tr("Export Perspective..."), this, SLOT(exportPerspective()));
viewMenu->addSeparator();
subChartMenu = viewMenu->addMenu(tr("Add Chart"));
viewMenu->addAction(tr("Import Chart..."), this, SLOT(importChart()));
#ifdef GC_HAS_CLOUD_DB
@@ -947,6 +950,64 @@ MainWindow::importChart()
}
}
void
MainWindow::exportPerspective()
{
int view = currentTab->currentView();
TabView *current = NULL;
switch (view) {
case 0: current = currentTab->homeView; break;
case 1: current = currentTab->analysisView; break;
case 2: current = currentTab->diaryView; break;
case 3: current = currentTab->trainView; break;
}
// export the current perspective to a file
QString suffix;
QString fileName = QFileDialog::getSaveFileName(this, tr("Export Persepctive"),
QDir::homePath()+"/"+ current->perspective_->title() + ".gchartset",
("*.gchartset;;"), &suffix, QFileDialog::DontUseNativeDialog); // native dialog hangs when threads in use (!)
if (fileName.isEmpty()) {
QMessageBox::critical(this, tr("Export Perspective"), tr("No perspective file selected!"));
} else {
current->exportPerspective(current->perspective_, fileName);
}
}
void
MainWindow::importPerspective()
{
int view = currentTab->currentView();
TabView *current = NULL;
switch (view) {
case 0: current = currentTab->homeView; break;
case 1: current = currentTab->analysisView; break;
case 2: current = currentTab->diaryView; break;
case 3: current = currentTab->trainView; break;
}
// import a new perspective from a file
QString fileName = QFileDialog::getOpenFileName(this, tr("Select Perspective file to export"), "", tr("GoldenCheetah Perspective Files (*.gchartset)"));
if (fileName.isEmpty()) {
QMessageBox::critical(this, tr("Import Perspective"), tr("No perspective file selected!"));
} else {
// import and select it
pactive = true;
current->importPerspective(fileName);
current->setPerspectives(perspectiveSelector);
// and select remember pactive is true, so we do the heavy lifting here
perspectiveSelector->setCurrentIndex(current->perspectives_.count()-1);
current->perspectiveSelected(perspectiveSelector->currentIndex());
pactive = false;
}
}
#ifdef GC_HAS_CLOUD_DB
void

View File

@@ -145,6 +145,10 @@ class MainWindow : public QMainWindow
void perspectiveSelected(int index);
void perspectivesChanged(); // when the list of perspectives is updated in PerspectivesDialog
// import and export perspectives
void exportPerspective();
void importPerspective();
// chart importing
void importCharts(QStringList);

View File

@@ -1410,6 +1410,112 @@ Perspective::presetSelected(int n)
}
}
/*--------------------------------------------------------------------------------
* Import and Export the Perspective to xml
* -----------------------------------------------------------------------------*/
Perspective *Perspective::fromFile(Context *context, QString filename, int type)
{
Perspective *returning = NULL;
QFileInfo finfo(filename);
QString content = "";
// no such file
if (!finfo.exists()) return returning;
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
content = file.readAll();
file.close();
} else return returning;
// parse the content
QXmlInputSource source;
source.setData(content);
QXmlSimpleReader xmlReader;
ViewParser handler(context, type, false);
xmlReader.setContentHandler(&handler);
xmlReader.setErrorHandler(&handler);
// parse and instantiate the charts
xmlReader.parse(source);
// none loaded ?
if (handler.perspectives.count() == 0) return returning;
// return the first one (if there are multiple)
returning = handler.perspectives[0];
// delete any further perspectives
for(int i=1; i<handler.perspectives.count(); i++) delete (handler.perspectives[i]);
// return it, but bear in mind it hasn't been initialised (current ride, date range etc)
return returning;
}
bool
Perspective::toFile(QString filename)
{
QFile file(filename);
if (!file.open(QFile::WriteOnly)) return false;
// truncate and use 8bit encoding
file.resize(0);
QTextStream out(&file);
out.setCodec("UTF-8");
// write to output stream
toXml(out);
// all done
file.close();
return true;
}
void
Perspective::toXml(QTextStream &out)
{
out<<"<layout name=\""<< title_ <<"\" style=\"" << currentStyle <<"\">\n";
// iterate over charts
foreach (GcChartWindow *chart, charts) {
GcWinID type = chart->property("type").value<GcWinID>();
out<<"\t<chart id=\""<<static_cast<int>(type)<<"\" "
<<"name=\""<<Utils::xmlprotect(chart->property("instanceName").toString())<<"\" "
<<"title=\""<<Utils::xmlprotect(chart->property("title").toString())<<"\" >\n";
// iterate over chart properties
const QMetaObject *m = chart->metaObject();
for (int i=0; i<m->propertyCount(); i++) {
QMetaProperty p = m->property(i);
if (p.isUser(chart)) {
out<<"\t\t<property name=\""<<Utils::xmlprotect(p.name())<<"\" "
<<"type=\""<<p.typeName()<<"\" "
<<"value=\"";
if (QString(p.typeName()) == "int") out<<p.read(chart).toInt();
if (QString(p.typeName()) == "double") out<<p.read(chart).toDouble();
if (QString(p.typeName()) == "QDate") out<<p.read(chart).toDate().toString();
if (QString(p.typeName()) == "QString") out<<Utils::xmlprotect(p.read(chart).toString());
if (QString(p.typeName()) == "bool") out<<p.read(chart).toBool();
if (QString(p.typeName()) == "LTMSettings") {
QByteArray marshall;
QDataStream s(&marshall, QIODevice::WriteOnly);
LTMSettings x = p.read(chart).value<LTMSettings>();
s << x;
out<<marshall.toBase64();
}
out<<"\" />\n";
}
}
out<<"\t</chart>\n";
}
out<<"</layout>\n";
}
/*--------------------------------------------------------------------------------
* Import Chart Dialog - select/deselect charts before importing them
* -----------------------------------------------------------------------------*/

View File

@@ -43,6 +43,7 @@ class LTMSettings;
class TabView;
class ViewParser;
class PerspectiveDialog;
class QTextStream;
class Perspective : public GcWindow
{
@@ -58,6 +59,11 @@ class Perspective : public GcWindow
Perspective(Context *, QString title, int type);
~Perspective();
// import and export
static Perspective *fromFile(Context *context, QString filename, int type);
bool toFile(QString filename);
void toXml(QTextStream &out);
QString title() const { return title_; }
void resetLayout();

View File

@@ -23,6 +23,7 @@
#include <QFormLayout>
#include <QLabel>
#include <QMessageBox>
#include <QFileDialog>
///
/// PerspectiveDialog
@@ -31,7 +32,7 @@ PerspectiveDialog::PerspectiveDialog(QWidget *parent, TabView *tabView) : QDialo
{
setWindowTitle("Manage Perspectives");
setMinimumWidth(400*dpiXFactor);
setMinimumWidth(450*dpiXFactor);
setMinimumHeight(450*dpiXFactor);
//setAttribute(Qt::WA_DeleteOnClose);
@@ -77,6 +78,9 @@ PerspectiveDialog::PerspectiveDialog(QWidget *parent, TabView *tabView) : QDialo
upPerspective->setFixedSize(20*dpiXFactor,20*dpiYFactor);
downPerspective->setFixedSize(20*dpiXFactor,20*dpiYFactor);
importPerspective = new QPushButton(tr("Import"), this);
exportPerspective = new QPushButton(tr("Export"), this);
addPerspective = new QPushButton("+", this);
removePerspective = new QPushButton("-", this);
@@ -98,6 +102,9 @@ PerspectiveDialog::PerspectiveDialog(QWidget *parent, TabView *tabView) : QDialo
xb->addWidget(upPerspective);
xb->addWidget(downPerspective);
xb->addStretch();
xb->addWidget(importPerspective);
xb->addWidget(exportPerspective);
xb->addStretch();
xb->addWidget(addPerspective);
xb->addWidget(removePerspective);
mainLayout->addLayout(xb);
@@ -112,6 +119,9 @@ PerspectiveDialog::PerspectiveDialog(QWidget *parent, TabView *tabView) : QDialo
connect(perspectiveTable, SIGNAL(chartMoved(GcChartWindow*)), this, SLOT(perspectiveSelected())); // just reset the chart list
connect(perspectiveTable, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(perspectiveNameChanged(QTableWidgetItem*))); // user edit
connect(importPerspective, SIGNAL(clicked(bool)), this, SLOT(importPerspectiveClicked()));
connect(exportPerspective, SIGNAL(clicked(bool)), this, SLOT(exportPerspectiveClicked()));
connect(removePerspective, SIGNAL(clicked(bool)), this, SLOT(removePerspectiveClicked()));
connect(addPerspective, SIGNAL(clicked(bool)), this, SLOT(addPerspectiveClicked()));
connect(upPerspective, SIGNAL(clicked(bool)), this, SLOT(upPerspectiveClicked()));
@@ -227,6 +237,47 @@ PerspectiveDialog::addPerspectiveClicked()
}
}
void
PerspectiveDialog::exportPerspectiveClicked()
{
int index = perspectiveTable->selectedItems()[0]->row();
// wipe it - tabView will worry about bounds and switching if we delete the currently selected one
Perspective *current = tabView->perspectives_[index];
// export the current perspective to a file
QString suffix;
QString fileName = QFileDialog::getSaveFileName(this, tr("Export Persepctive"),
QDir::homePath()+"/"+ current->title() + ".gchartset",
("*.gchartset;;"), &suffix, QFileDialog::DontUseNativeDialog); // native dialog hangs when threads in use (!)
if (fileName.isEmpty()) {
QMessageBox::critical(this, tr("Export Perspective"), tr("No perspective file selected!"));
} else {
tabView->exportPerspective(current, fileName);
}
}
void
PerspectiveDialog::importPerspectiveClicked()
{
// import a new perspective from a file
QString fileName = QFileDialog::getOpenFileName(this, tr("Select Perspective file to export"), "", tr("GoldenCheetah Perspective Files (*.gchartset)"));
if (fileName.isEmpty()) {
QMessageBox::critical(this, tr("Import Perspective"), tr("No perspective file selected!"));
} else {
// import and select it
tabView->importPerspective(fileName);
// update the table
setTables();
// new one added
emit perspectivesChanged();
}
}
void
PerspectiveDialog::upPerspectiveClicked()
{

View File

@@ -80,6 +80,9 @@ class PerspectiveDialog : public QDialog
void upPerspectiveClicked();
void downPerspectiveClicked();
void exportPerspectiveClicked();
void importPerspectiveClicked();
void perspectiveNameChanged(QTableWidgetItem *);
signals:
@@ -91,6 +94,7 @@ class PerspectiveDialog : public QDialog
PerspectiveTableWidget *perspectiveTable;
ChartTableWidget *chartTable;
QPushButton *exportPerspective, *importPerspective;
QPushButton *addPerspective, *removePerspective;
QToolButton *upPerspective, *downPerspective;
QLabel *instructions;

View File

@@ -294,44 +294,8 @@ TabView::saveState()
// so lets run through the perspectives
foreach(Perspective *page, perspectives_) {
out<<"<layout name=\""<< page->title_ <<"\" style=\"" << page->currentStyle <<"\">\n";
// iterate over charts
foreach (GcChartWindow *chart, page->charts) {
GcWinID type = chart->property("type").value<GcWinID>();
out<<"\t<chart id=\""<<static_cast<int>(type)<<"\" "
<<"name=\""<<Utils::xmlprotect(chart->property("instanceName").toString())<<"\" "
<<"title=\""<<Utils::xmlprotect(chart->property("title").toString())<<"\" >\n";
// iterate over chart properties
const QMetaObject *m = chart->metaObject();
for (int i=0; i<m->propertyCount(); i++) {
QMetaProperty p = m->property(i);
if (p.isUser(chart)) {
out<<"\t\t<property name=\""<<Utils::xmlprotect(p.name())<<"\" "
<<"type=\""<<p.typeName()<<"\" "
<<"value=\"";
if (QString(p.typeName()) == "int") out<<p.read(chart).toInt();
if (QString(p.typeName()) == "double") out<<p.read(chart).toDouble();
if (QString(p.typeName()) == "QDate") out<<p.read(chart).toDate().toString();
if (QString(p.typeName()) == "QString") out<<Utils::xmlprotect(p.read(chart).toString());
if (QString(p.typeName()) == "bool") out<<p.read(chart).toBool();
if (QString(p.typeName()) == "LTMSettings") {
QByteArray marshall;
QDataStream s(&marshall, QIODevice::WriteOnly);
LTMSettings x = p.read(chart).value<LTMSettings>();
s << x;
out<<marshall.toBase64();
}
out<<"\" />\n";
}
}
out<<"\t</chart>\n";
}
out<<"</layout>\n";
// write to output stream
page->toXml(out);
}
out<<"</layouts>\n";
file.close();
@@ -419,6 +383,7 @@ TabView::restoreState(bool useDefault)
// if we *still* don't have content then something went
// badly wrong, so only reset if its not blank
QList<Perspective*>restored;
if (content != "") {
// whilst this happens don't show user
@@ -434,22 +399,16 @@ TabView::restoreState(bool useDefault)
// parse and instantiate the charts
xmlReader.parse(source);
perspectives_ = handler.perspectives;
restored = handler.perspectives;
}
if (legacy && perspectives_.count() == 1) perspectives_[0]->title_ = "General";
if (legacy && restored.count() == 1) restored[0]->title_ = "General";
if (perspectives_.count() == 0) {
perspective_ = new Perspective(context, "empty", type);
perspectives_ << perspective_;
}
// MUST have at least one
if (restored.count() == 0) restored << new Perspective(context, "empty", type);
// add to stack
foreach(Perspective *page, perspectives_) {
pstack->addWidget(page);
cstack->addWidget(page->controls());
page->configChanged(0); // set colors correctly- will have missed from startup
}
// initialise them
foreach(Perspective *page, restored) appendPerspective(page);
// default to first one
perspective_ = perspectives_[0];
@@ -471,7 +430,29 @@ TabView::restoreState(bool useDefault)
context->athlete->selectRideFile(context->athlete->rideCache->rides().last()->fileName);
}
}
}
void
TabView::appendPerspective(Perspective *page)
{
perspectives_ << page;
pstack->addWidget(page);
cstack->addWidget(page->controls());
page->configChanged(0); // set colors correctly- will have missed from startup
}
void
TabView::importPerspective(QString filename)
{
Perspective *newone = Perspective::fromFile(context, filename, type);
if (newone) appendPerspective(newone);
}
void
TabView::exportPerspective(Perspective *p, QString filename)
{
// write to file
p->toFile(filename);
}
void
@@ -483,10 +464,8 @@ TabView::addPerspective(QString name)
if (type == VIEW_TRAIN) page->styleChanged(2);
else page->styleChanged(0);
perspectives_ << page;
pstack->addWidget(page);
cstack->addWidget(page->controls());
page->configChanged(0); // set colors correctly- will be empty...
// append
appendPerspective(page);
}
void

View File

@@ -76,6 +76,7 @@ class TabView : public QWidget
// load/save perspectives
void restoreState(bool useDefault = false);
void saveState();
void appendPerspective(Perspective *page);
void setPerspectives(QComboBox *perspectiveSelector); // set the combobox when view selected
void perspectiveSelected(int index); // combobox selections changed because the user selected a perspective
@@ -104,6 +105,9 @@ class TabView : public QWidget
void importChart(QMap<QString,QString>properties, bool select) { perspective_->importChart(properties, select); }
void importPerspective(QString filename);
void exportPerspective(Perspective *, QString filename);
signals:
void sidebarClosed(); // the user dragged the sidebar closed.