/* * Copyright (c) 2010 Mark Liversedge (liversedge@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 "HomeWindow.h" HomeWindow::HomeWindow(MainWindow *mainWindow) : GcWindow(mainWindow), mainWindow(mainWindow), active(false), clicked(NULL) { setInstanceName("Home Window"); setControls(new QStackedWidget(this)); setAcceptDrops(true); QVBoxLayout *layout = new QVBoxLayout(this); QFont bigandbold; bigandbold.setPointSize(bigandbold.pointSize() + 2); bigandbold.setWeight(QFont::Bold); QHBoxLayout *titleBar = new QHBoxLayout; title = new QLabel("Home", this); title->setFont(bigandbold); titleBar->addWidget(title); titleBar->addStretch(); #ifdef Q_OS_MAC static CocoaInitializer cocoaInitializer; // we only need one styleSelector = new QtMacSegmentedButton (3, this); styleSelector->setTitle(0, "Tab"); styleSelector->setTitle(1, "Scroll"); styleSelector->setTitle(2, "Tile"); #else styleSelector = new QComboBox(this); styleSelector->addItem("Tab"); styleSelector->addItem("Scroll"); styleSelector->addItem("Tile"); QFont small; small.setPointSize(8); styleSelector->setFont(small); styleSelector->setFixedHeight(20); #endif titleBar->addWidget(styleSelector); layout->addLayout(titleBar); style = new QStackedWidget(this); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); layout->addWidget(style); // each style has its own container widget tabbed = new QTabWidget(this); tabbed->setContentsMargins(0,0,0,0); tabbed->setTabsClosable(true); style->addWidget(tabbed); // tiled tileWidget = new QWidget(this); tileWidget->setContentsMargins(0,0,0,0); //tileWidget->setMouseTracking(true); //tileWidget->installEventFilter(this); tileGrid = new QGridLayout(tileWidget); tileGrid->setSpacing(0); tileArea = new QScrollArea(this); tileArea->setWidgetResizable(true); tileArea->setWidget(tileWidget); tileArea->setFrameStyle(QFrame::NoFrame); tileArea->setContentsMargins(0,0,0,0); style->addWidget(tileArea); winWidget = new QWidget(this); winWidget->setContentsMargins(0,0,0,0); //tileWidget->setMouseTracking(true); //tileWidget->installEventFilter(this); winFlow = new GcWindowLayout(winWidget); winFlow->setSpacing(0); winFlow->setContentsMargins(0,0,0,0); winArea = new QScrollArea(this); winArea->setWidgetResizable(true); winArea->setWidget(winWidget); winArea->setFrameStyle(QFrame::NoFrame); winArea->setContentsMargins(0,0,0,0); style->addWidget(winArea); currentStyle=2; #ifdef Q_OS_MAC styleSelector->setEnabled (2, true); #else styleSelector->setCurrentIndex(2); #endif style->setCurrentIndex(2); // tile area connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged())); connect(tabbed, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int))); connect(tabbed, SIGNAL(tabCloseRequested(int)), this, SLOT(removeChart(int))); #ifdef Q_OS_MAC connect(styleSelector, SIGNAL(clicked(int,bool)), SLOT(styleChanged(int))); #else connect(styleSelector, SIGNAL(currentIndexChanged(int)), SLOT(styleChanged(int))); #endif restoreState(); } HomeWindow::~HomeWindow() { #if 0 disconnect(this); qDebug()<<"removing from layouts!"; // remove from our layouts -- they'reowned by mainwindow for (int i=0; iindexOf(charts[i]); tabbed->removeTab(tabnum); } break; case 1 : // they are lists in a GridLayout { tileGrid->removeWidget(charts[i]); } break; case 2 : // they are in a FlowLayout { winFlow->removeWidget(charts[i]); } default: break; } } #endif } void HomeWindow::configChanged() { // get config } void HomeWindow::selected() { resizeEvent(NULL); // force a relayout rideSelected(); } void HomeWindow::rideSelected() { if (amVisible()) { foreach(GcWindow *x, charts) { if (currentStyle) x->show(); // keep tabs hidden, show the rest x->setProperty("ride", property("ride")); } // show current tab if (!currentStyle && charts.count()) tabSelected(tabbed->currentIndex()); } } void HomeWindow::tabSelected(int index) { if (active || currentStyle != 0) return; if (index >= 0) { charts[index]->show(); charts[index]->setProperty("ride", property("ride")); dynamic_cast(controls())->setCurrentIndex(index); } } void HomeWindow::styleChanged(int id) { // ignore if out of bounds or we're already using that style if (id > 2 || id < 0 || id == currentStyle) return; active = true; // move the windows from there current // position to there new position for (int i=0; iindexOf(charts[i]); tabbed->removeTab(tabnum); } break; case 1 : // they are lists in a GridLayout { tileGrid->removeWidget(charts[i]); } break; case 2 : // they are in a FlowLayout { winFlow->removeWidget(charts[i]); } default: break; } // now put into new style switch (id) { case 0 : // they are tabs in a TabWidget tabbed->addTab(charts[i], charts[i]->property("title").toString()); charts[i]->setContentsMargins(0,0,0,0); charts[i]->hide(); // we need to show on tab selection! break; case 1 : // they are lists in a GridLayout tileGrid->addWidget(charts[i], i,0); charts[i]->setContentsMargins(0,15,0,0); charts[i]->show(); break; case 2 : // thet are in a FlowLayout winFlow->addWidget(charts[i]); charts[i]->setContentsMargins(0,15,0,0); charts[i]->show(); default: break; } } currentStyle = id; style->setCurrentIndex(currentStyle); active = false; if (currentStyle == 0 && charts.count()) tabSelected(0); resizeEvent(NULL); // XXX watch out in case resize event uses this!! update(); } void HomeWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->formats().contains("application/x-qabstractitemmodeldatalist")) event->accept(); else event->accept(); } void HomeWindow::dropEvent(QDropEvent *event) { QStandardItemModel model; model.dropMimeData(event->mimeData(), Qt::CopyAction, 0,0, QModelIndex()); QString chart = model.data(model.index(0,0), Qt::DisplayRole).toString(); // which one am i? for (int i = 0; GcWindows[i].id != GcWindowTypes::None; i++) { if (GcWindows[i].name == chart) { event->accept(); // GcWindowDialog is delete on close, so no need to delete GcWindowDialog *f = new GcWindowDialog(GcWindows[i].id, mainWindow); GcWindow *newone = f->exec(); // returns null if cancelled or closed if (newone) { addChart(newone); newone->show(); } return; } } // nope not one of ours event->ignore(); return; } void HomeWindow::addChart(GcWindow* newone) { int chartnum = charts.count(); if (newone) { //GcWindow *newone = GcWindowRegistry::newGcWindow(GcWindows[i].id, mainWindow); // add the controls QStackedWidget *m = dynamic_cast(controls()); QWidget *x = dynamic_cast(newone)->controls(); QWidget *c = (x != NULL) ? x : new QWidget(this); m->addWidget(c); // watch for enter events! newone->installEventFilter(this); RideItem *notconst = (RideItem*)mainWindow->currentRideItem(); newone->setProperty("ride", QVariant::fromValue(notconst)); // add to tabs switch (currentStyle) { case 0 : newone->setContentsMargins(0,0,0,0); tabbed->addTab(newone, newone->property("title").toString()); break; case 1 : { // add to tiles // set geometry newone->setFixedWidth((tileArea->width()-50)); newone->setFixedHeight(newone->width() * 0.7); int row = chartnum; // / 2; int column = 0; //chartnum % 2; newone->setContentsMargins(0,15,0,0); tileGrid->addWidget(newone, row, column); } break; case 2 : { int heightFactor = newone->property("heightFactor").toInt(); int widthFactor = newone->property("widthFactor").toInt(); // width of area minus content margins and spacing around each item // divided by the number of items #ifdef Q_OS_MAC // Mac QT fucks about with spacing limits, and doesn't honor // spacing < 6 or thereabouts anyway int newwidth = (winArea->width() - 20 - (2*(winArea->contentsMargins().left()+winArea->contentsMargins().right())) - ((2*widthFactor) * 6) ) / widthFactor; #else int newwidth = (winArea->width() - 20 - (2*(winArea->contentsMargins().left()+winArea->contentsMargins().right())) - ((1+widthFactor) * 5) ) / widthFactor; #endif int newheight = (winArea->height() - (winArea->contentsMargins().left()+winArea->contentsMargins().right()) - ((1+heightFactor) * 5) ) / heightFactor; int minWidth = 10; int minHeight = 10; if (newwidth < minWidth) newwidth = minWidth; newone->setFixedWidth(newwidth); if (newheight < minHeight) newheight = minHeight; else newone->setFixedHeight(newheight); newone->setContentsMargins(0,15,0,0); winFlow->addWidget(newone); } break; } // add to the list charts.append(newone); newone->hide(); } } void HomeWindow::removeChart(int num) { if (num >= charts.count()) return; // out of bounds (!) // better let the user confirm since this // is undoable etc - code swiped from delete // ride in MainWindow, seems to work ok ;) QMessageBox msgBox; msgBox.setText(tr("Are you sure you want to remove the chart?")); QPushButton *deleteButton = msgBox.addButton(tr("Remove"),QMessageBox::YesRole); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); msgBox.setIcon(QMessageBox::Critical); msgBox.exec(); if(msgBox.clickedButton() != deleteButton) return; charts[num]->hide(); // just in case its currently selected if (clicked == charts[num]) clicked=NULL; // remove the controls QWidget *m = dynamic_cast(controls())->widget(num); if (m) dynamic_cast(controls())->removeWidget(m); switch(currentStyle) { case 0 : // delete tab and widget tabbed->removeTab(num); break; case 1 : // scrolled tileGrid->removeWidget(charts[num]); break; case 2 : // tiled winFlow->removeWidget(charts[num]); break; default: break; // never reached } delete (GcWindow*)(charts[num]); charts.removeAt(num); update(); } void HomeWindow::resizeEvent(QResizeEvent *) { foreach (GcWindow *x, charts) { switch (currentStyle) { case 0 : // tabbed x->setMinimumSize(0,0); x->setMaximumSize(9999,9999); break; case 1 : // tiled x->setFixedWidth((tileArea->width()-50)); x->setFixedHeight(x->width() * 0.7); break; case 2 : // flow { int heightFactor = x->property("heightFactor").toInt(); int widthFactor = x->property("widthFactor").toInt(); #ifdef Q_OS_MAC // Mac QT fucks about with spacing limits, and doesn't honor // spacing < 6 or thereabouts anyway int newwidth = (winArea->width() - 20 - (2*(winArea->contentsMargins().left()+winArea->contentsMargins().right())) - ((2*widthFactor) * 6) ) / widthFactor; #else int newwidth = (winArea->width() - 20 - (2*(winArea->contentsMargins().left()+winArea->contentsMargins().right())) - ((1+widthFactor) * 5) ) / widthFactor; #endif int newheight = (winArea->height() - (winArea->contentsMargins().left()+winArea->contentsMargins().right()) - ((1+heightFactor) * 5) ) / heightFactor; int minWidth = 10; int minHeight = 10; if (newwidth < minWidth) newwidth = minWidth; x->setFixedWidth(newwidth); if (newheight < minHeight) newheight = minHeight; else x->setFixedHeight(newheight); } break; default: break; } } } bool HomeWindow::eventFilter(QObject *object, QEvent *e) { // is it a mouse enter event? if (e->type() == QEvent::Enter) { // ok which one? for(int i=0; i(controls())->setCurrentIndex(i); // paint the border! charts[i]->repaint(); return false; } } } else if (e->type() == QEvent::Leave) { // unpaint the border for(int i=0; irepaint(); return false; } } } else if (e->type() == QEvent::MouseButtonPress) { int x = ((QMouseEvent*)(e))->pos().x(); int y = ((QMouseEvent*)(e))->pos().y(); if (y<=15) { // on the title bar! -- which one? for(int i=0; i (charts[i]->width()-15)) { removeChart(i); } else { if (charts[i] != clicked) { // we aren't clicking to toggle // clear the chart that is currently clicked // if at all (ie. null means no clicked chart if (clicked) { clicked->setProperty("active", false); clicked->repaint(); } charts[i]->setProperty("active", true); charts[i]->repaint(); clicked = charts[i]; dynamic_cast(controls())->setCurrentIndex(i); } else { // already clicked so unclick it (toggle) charts[i]->setProperty("active", false); charts[i]->repaint(); clicked = NULL; } } return true; } } } return false; } return false; } GcWindowDialog::GcWindowDialog(GcWinID type, MainWindow *mainWindow) : mainWindow(mainWindow), type(type) { setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); setWindowTitle("Chart Setup"); setFixedHeight(500); setFixedWidth(800); setWindowModality(Qt::ApplicationModal); mainLayout = new QVBoxLayout(this); layout = new QHBoxLayout(); mainLayout->addLayout(layout); chartLayout = new QVBoxLayout; layout->addLayout(chartLayout); title = new QLineEdit(this); chartLayout->addWidget(title); win = GcWindowRegistry::newGcWindow(type, mainWindow); chartLayout->addWidget(win); controlLayout = new QFormLayout; height=new QSpinBox(this); height->setRange(1,10); height->setSingleStep(1); height->setValue(2); width=new QSpinBox(this); width->setRange(1,10); width->setSingleStep(1); width->setValue(2); controlLayout->addRow(new QLabel("Height Factor",this), height); controlLayout->addRow(new QLabel("Width Factor",this), width); if (win->controls()) controlLayout->addRow(win->controls()); layout->addLayout(controlLayout); RideItem *notconst = (RideItem*)mainWindow->currentRideItem(); win->setProperty("ride", QVariant::fromValue(notconst)); layout->setStretch(0, 100); layout->setStretch(1, 50); QHBoxLayout *buttons = new QHBoxLayout; mainLayout->addLayout(buttons); buttons->addStretch(); buttons->addWidget((cancel=new QPushButton("Cancel", this))); buttons->addWidget((ok=new QPushButton("OK", this))); connect(ok, SIGNAL(clicked()), this, SLOT(okClicked())); connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked())); hide(); } void GcWindowDialog::okClicked() { // give back to mainWindow so we can re-use // note that in reject they are not and will // get deleted (this has been verfied with // some debug statements in ~GcWindow). win->setParent(mainWindow); if (win->controls()) win->controls()->setParent(mainWindow); // set its title property and geometry factors win->setProperty("title", title->text()); win->setProperty("widthFactor", width->value()); win->setProperty("heightFactor", height->value()); win->setProperty("GcWinID", type); accept(); } void GcWindowDialog::cancelClicked() { reject(); } GcWindow * GcWindowDialog::exec() { if (QDialog::exec()) { return win; } else return NULL; } // static helper to protect special xml characters // ideally we would use XMLwriter to do this but // the file format is trivial and this implementation // is easier to follow and modify... for now. static QString xmlprotect(QString string) { QTextEdit trademark("™"); // process html encoding of(TM) QString tm = trademark.toPlainText(); QString s = string; s.replace( tm, "™" ); s.replace( "&", "&" ); s.replace( ">", ">" ); s.replace( "<", "<" ); s.replace( "\"", """ ); s.replace( "\'", "'" ); return s; } static QString unprotect(QString buffer) { // get local TM character code QTextEdit trademark("™"); // process html encoding of(TM) QString tm = trademark.toPlainText(); // remove quotes QString s = buffer.trimmed(); // replace html (TM) with local TM character s.replace( "™", tm ); // html special chars are automatically handled // XXX other special characters will not work // cross-platform but will work locally, so not a biggie // i.e. if thedefault charts.xml has a special character // in it it should be added here return s; } void HomeWindow::saveState() { // run through each chart and save its USER properties // we do not save all the other Qt properties since // we're not interested in them // XXX currently we support QString, int, double and bool types - beware custom types!! if (charts.count() == 0) return; // don't save empty, use default instead QString filename = mainWindow->home.absolutePath() + "/" + "home" + "-layout.xml"; QFile file(filename); file.open(QFile::WriteOnly); file.resize(0); QTextStream out(&file); out<<"\n"; // iterate over charts foreach (GcWindow *chart, charts) { GcWinID type = chart->property("type").value(); out<<"\t(type)<<"\" " <<"name=\""<property("instanceName").toString())<<"\" " <<"title=\""<property("title").toString())<<"\" >\n"; // iterate over chart properties const QMetaObject *m = chart->metaObject(); for (int i=0; ipropertyCount(); i++) { QMetaProperty p = m->property(i); if (p.isUser(chart)) { if (QString(p.typeName()) == "LTMSettings") qDebug()<<"not supported yet..."; else { out<<"\t\t\n"; } } } out<<"\t\n"; } out<<"\n"; file.close(); } void HomeWindow::restoreState() { // restore window state QString filename = mainWindow->home.absolutePath() + "/" + "home" + "-layout.xml"; QFile file(filename); // setup the handler QXmlInputSource source(&file); QXmlSimpleReader xmlReader; ViewParser handler(mainWindow); xmlReader.setContentHandler(&handler); xmlReader.setErrorHandler(&handler); // parse and instantiate the charts xmlReader.parse(source); // layout the results foreach(GcWindow *chart, handler.charts) addChart(chart); } // // view layout parser - reads in athletehome/xxx-layout.xml // bool ViewParser::startDocument() { return TRUE; } bool ViewParser::endElement( const QString&, const QString&, const QString &qName ) { if (qName == "chart") { // add to the list charts.append(chart); } return TRUE; } bool ViewParser::startElement( const QString&, const QString&, const QString &name, const QXmlAttributes &attrs ) { if (name == "layout") { // XXX do nothing - need to remember style (tab/scroll/tile) ... } else if (name == "chart") { QString name="", title="", typeStr=""; GcWinID type; // get attributes for(int i=0; i(typeStr.toInt()); chart = GcWindowRegistry::newGcWindow(type, mainWindow); chart->hide(); chart->setProperty("title", QVariant(title)); chart->setProperty("instanceName", QVariant(name)); } else if (name == "property") { QString name, type, value; // get attributes for(int i=0; isetProperty(name.toLatin1(), QVariant(value.toInt())); if (type == "double") chart->setProperty(name.toLatin1(), QVariant(value.toDouble())); if (type == "QString") chart->setProperty(name.toLatin1(), QVariant(QString(value))); if (type == "bool") chart->setProperty(name.toLatin1(), QVariant(value.toInt() ? true : false)); if (type == "LTMSettings") qDebug()<<"not supported yet..."; } return TRUE; } bool ViewParser::characters( const QString&) { return TRUE; } bool ViewParser::endDocument() { return TRUE; }