diff options
Diffstat (limited to 'src/pmchart')
106 files changed, 24758 insertions, 0 deletions
diff --git a/src/pmchart/GNUmakefile b/src/pmchart/GNUmakefile new file mode 100644 index 0000000..e3fdef9 --- /dev/null +++ b/src/pmchart/GNUmakefile @@ -0,0 +1,90 @@ +TOPDIR = ../.. +COMMAND = pmchart +PROJECT = $(COMMAND).pro +include $(TOPDIR)/src/include/builddefs + +WRAPPER = $(COMMAND).sh +QRCFILE = $(COMMAND).qrc +RCFILE = $(COMMAND).rc +ICOFILE = $(COMMAND).ico +ICNFILE = $(COMMAND).icns +XMLFILE = $(COMMAND).info +DESKTOP = $(COMMAND).desktop +UIFILES = $(shell echo *.ui) +HEADERS = aboutdialog.h chartdialog.h exportdialog.h hostdialog.h \ + infodialog.h pmchart.h openviewdialog.h saveviewdialog.h \ + recorddialog.h seealsodialog.h searchdialog.h settingsdialog.h \ + samplesdialog.h tabdialog.h tab.h tabwidget.h \ + chart.h console.h main.h namespace.h \ + colorbutton.h colorscheme.h qcolorpicker.h \ + statusbar.h timeaxis.h timecontrol.h \ + groupcontrol.h gadget.h sampling.h tracing.h +SOURCES = $(HEADERS:.h=.cpp) view.cpp +LSRCFILES = $(QRCFILE) $(RCFILE) $(UIFILES) $(HEADERS) $(SOURCES) \ + $(PROJECT) $(DESKTOP) $(WRAPPER).in $(XMLFILE).in +LDIRT = $(COMMAND) $(ICONLINKS) $(WRAPPER) $(XMLFILE) images + +SUBDIRS = views + +default: build-me + +include $(BUILDRULES) + +ifeq "$(ENABLE_QT)" "true" +build-me:: images wrappers + $(QTMAKE) + $(LNMAKE) + +build-me:: $(SUBDIRS) + $(SUBDIRS_MAKERULE) + +ifeq ($(WINDOW),mac) +PKG_MAC_DIR = /Applications/$(COMMAND).app/Contents +wrappers: $(WRAPPER) $(XMLFILE) +else +wrappers: +endif + +$(WRAPPER): $(WRAPPER).in + $(SED) -e '/\# .*/b' -e 's;PKG_MAC_DIR;$(PKG_MAC_DIR);g' < $< > $@ +$(XMLFILE): $(XMLFILE).in + $(SED) -e 's;PACKAGE_VERSION;$(PACKAGE_VERSION);g' < $< > $@ + +install: default $(SUBDIRS) + $(SUBDIRS_MAKERULE) +ifeq ($(WINDOW),win) + $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND) +endif +ifeq ($(WINDOW),x11) + $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND) + $(INSTALL) -m 755 -d $(PCP_DESKTOP_DIR) + $(INSTALL) -m 644 $(DESKTOP) $(PCP_DESKTOP_DIR)/$(DESKTOP) +endif +ifeq ($(WINDOW),mac) + $(INSTALL) -m 755 $(WRAPPER) $(PCP_BIN_DIR)/$(COMMAND) + $(call INSTALL_DIRECTORY_HIERARCHY,$(PKG_MAC_DIR),/Applications) + $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo + $(INSTALL) -m 644 $(XMLFILE) $(PKG_MAC_DIR)/Info.plist + $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS + $(call INSTALL_QT_FRAMEWORKS,$(BINARY)) + $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND) + rm $(BINARY) + $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources + $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE) + $(call INSTALL_QT_RESOURCES,$(PKG_MAC_DIR)/Resources) +endif + +else +build-me: +install: +endif + +default_pcp: default + +install_pcp: install + +images: $(ICNFILE) + $(LN_S) $(TOPDIR)/images images + +$(ICNFILE): + $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE) diff --git a/src/pmchart/aboutdialog.cpp b/src/pmchart/aboutdialog.cpp new file mode 100644 index 0000000..37de391 --- /dev/null +++ b/src/pmchart/aboutdialog.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2006-2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "aboutdialog.h" +#include <pcp/pmapi.h> + +AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + QRegExp rx("\\b(VERSION)\\b"); + QString version = versionTextLabel->text(); + version.replace(rx, pmGetConfig("PCP_VERSION")); + versionTextLabel->setText(version); +} + +void AboutDialog::aboutOKButton_clicked() +{ + done(0); +} diff --git a/src/pmchart/aboutdialog.h b/src/pmchart/aboutdialog.h new file mode 100644 index 0000000..0f8bd81 --- /dev/null +++ b/src/pmchart/aboutdialog.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2006-2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include "ui_aboutdialog.h" + +class AboutDialog : public QDialog, public Ui::AboutDialog +{ + Q_OBJECT + +public: + AboutDialog(QWidget* parent); + +public slots: + virtual void aboutOKButton_clicked(); +}; + +#endif // ABOUTDIALOG_H diff --git a/src/pmchart/aboutdialog.ui b/src/pmchart/aboutdialog.ui new file mode 100644 index 0000000..04dc413 --- /dev/null +++ b/src/pmchart/aboutdialog.ui @@ -0,0 +1,229 @@ +<ui version="4.0" > + <class>AboutDialog</class> + <widget class="QDialog" name="AboutDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>330</width> + <height>240</height> + </rect> + </property> + <property name="minimumSize" > + <size> + <width>330</width> + <height>240</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>330</width> + <height>260</height> + </size> + </property> + <property name="windowTitle" > + <string>About</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/pmchart.png</iconset> + </property> + <property name="modal" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="aboutPixmapLabel" > + <property name="pixmap" > + <pixmap resource="pmchart.qrc" >:/images/pmchart.png</pixmap> + </property> + <property name="scaledContents" > + <bool>false</bool> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="versionTextLabel" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string><b><i>pmchart</i></b><br> +<b><i>Version VERSION</i></b></string> + </property> + <property name="alignment" > + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="authorsTextLabel" > + <property name="midLineWidth" > + <number>0</number> + </property> + <property name="text" > + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Copyright 2012, Red Hat.<br />Copyright 2012-2014, Nathan Scott.<br />Copyright 2006, Ken McDonell.<br />Copyright 2006-2010, Aconex.<br />All rights reserved.<br /><span style=" font-style:italic;"><br />This program is licensed under the<br />GNU General Public License.<br /></span></p></body></html></string> + </property> + <property name="alignment" > + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="aboutOKButton" > + <property name="focusPolicy" > + <enum>Qt::TabFocus</enum> + </property> + <property name="text" > + <string>OK</string> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>aboutOKButton</sender> + <signal>clicked()</signal> + <receiver>AboutDialog</receiver> + <slot>aboutOKButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/chart.cpp b/src/pmchart/chart.cpp new file mode 100644 index 0000000..106ae48 --- /dev/null +++ b/src/pmchart/chart.cpp @@ -0,0 +1,1058 @@ +/* + * Copyright (c) 2012-2014, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * Copyright (c) 2006-2010, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#include "main.h" +#include "tracing.h" +#include "sampling.h" +#include "saveviewdialog.h" + +#include <QtCore/QPoint> +#include <QtCore/QRegExp> +#include <QtGui/QApplication> +#include <QtGui/QWhatsThis> +#include <QtGui/QCursor> +#include <qwt_plot_curve.h> +#include <qwt_plot_picker.h> +#include <qwt_plot_renderer.h> +#include <qwt_legend_item.h> +#include <qwt_scale_widget.h> + +#define DESPERATE 0 + +Chart::Chart(Tab *chartTab, QWidget *parent) : QwtPlot(parent), Gadget(this) +{ + my.tab = chartTab; + my.title = QString::null; + my.style = NoStyle; + my.scheme = QString::null; + my.sequence = 0; + + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + plotLayout()->setCanvasMargin(0); + plotLayout()->setAlignCanvasToScales(true); + plotLayout()->setFixedAxisOffset(54, QwtPlot::yLeft); + enableAxis(xBottom, false); + + // prepare initial engine dealing with (no) chart type + my.engine = new ChartEngine(this); + + // setup the legend (all charts must have one) + setLegendVisible(true); + QwtPlot::legend()->contentsWidget()->setFont(*globalFont); + connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)), + SLOT(legendChecked(QwtPlotItem *, bool))); + + // setup a picker (all charts must have one) + my.picker = new ChartPicker(canvas()); + connect(my.picker, SIGNAL(activated(bool)), + SLOT(activated(bool))); + connect(my.picker, SIGNAL(selected(const QPolygon &)), + SLOT(selected(const QPolygon &))); + connect(my.picker, SIGNAL(selected(const QPointF &)), + SLOT(selected(const QPointF &))); + connect(my.picker, SIGNAL(moved(const QPointF &)), + SLOT(moved(const QPointF &))); + + // feedback into the group about any selection + connect(this, SIGNAL(timeSelectionActive(Gadget *, int)), + my.tab->group(), SLOT(timeSelectionActive(Gadget *, int))); + connect(this, SIGNAL(timeSelectionReactive(Gadget *, int)), + my.tab->group(), SLOT(timeSelectionReactive(Gadget *, int))); + connect(this, SIGNAL(timeSelectionInactive(Gadget *)), + my.tab->group(), SLOT(timeSelectionInactive(Gadget *))); + + console->post("Chart::ctor complete(%p)", this); +} + +Chart::~Chart() +{ + console->post("Chart::~Chart() for chart %p", this); + + for (int i = 0; i < my.items.size(); i++) + delete my.items[i]; + delete my.engine; + delete my.picker; +} + +ChartEngine::ChartEngine(Chart *chart) +{ + my.rateConvert = true; + my.antiAliasing = true; + chart->setAutoReplot(false); + chart->setCanvasBackground(globalSettings.chartBackground); + chart->setAxisFont(QwtPlot::yLeft, *globalFont); + chart->setAxisAutoScale(QwtPlot::yLeft); +} + +ChartItem::ChartItem(QmcMetric *mp, + pmMetricSpec *msp, pmDesc *dp, const QString &legend) +{ + my.metric = mp; + my.units = dp->units; + my.name = shortHostName().append(':').append(QString(msp->metric)); + if (msp->ninst == 1) { + my.name.append("[").append(msp->inst[0]).append("]"); + my.inst = QString(msp->inst[0]); + } else { + my.inst = QString::null; + } + setLegend(legend); + my.removed = false; + my.hidden = false; +} + +// +// Build the legend label string, even if the chart is declared +// "legend off" so that subsequent Edit->Chart Title and Legend +// changes can turn the legend on and off dynamically. +// +// NB: "legend" is not expanded (wrt %h, %i, etc), "label" is. +// +void +ChartItem::setLegend(const QString &legend) +{ + my.legend = legend; + if (legend != QString::null) + expandLegendLabel(legend); + else + clearLegendLabel(); +} + +void +ChartItem::updateLegend() +{ + // drive any change to label into QwtLegendItem + item()->setTitle(my.label); +} + +void +ChartItem::expandLegendLabel(const QString &legend) +{ + bool expandMetricShort = legend.contains("%m"); + bool expandMetricLong = legend.contains("%M"); + bool expandInstShort = legend.contains("%i"); + bool expandInstLong = legend.contains("%I"); + bool expandHostShort = legend.contains("%h"); + bool expandHostLong = legend.contains("%H"); + + my.label = legend; + if (expandMetricShort) + my.label.replace(QRegExp("%m"), shortMetricName()); + if (expandMetricLong) + my.label.replace(QRegExp("%M"), my.metric->name()); + if (expandInstShort) + my.label.replace(QRegExp("%i"), shortInstName()); + if (expandInstLong) + my.label.replace(QRegExp("%I"), my.inst); + if (expandHostShort) + my.label.replace(QRegExp("%h"), shortHostName()); + if (expandHostLong) + my.label.replace(QRegExp("%H"), hostname()); +} + +QString +ChartItem::hostname(void) const +{ + return my.metric->context()->source().context_hostname(); + +} + +QString +ChartItem::shortHostName(void) const +{ + QString hostName = hostname(); + int index; + + if ((index = hostName.indexOf(QChar('.'))) != -1) { + // no change if it looks even vaguely like an IP address + if (!hostName.contains(QRegExp("^\\d+\\.")) && // IPv4 + !hostName.contains(QChar(':'))) // IPv6 + hostName.truncate(index); + } + return hostName; +} + +QString +ChartItem::shortMetricName(void) const +{ + QString shortName = my.metric->name(); + int count, index; + + // heuristic: use final two PMNS components (e.g. dev.bytes) + // reason: short, often this is enough to differentiate well + while ((count = shortName.count(QChar('.'))) > 1) { + index = shortName.indexOf(QChar('.')); + shortName.remove(0, index + 1); + } + return shortName; +} + +QString +ChartItem::shortInstName(void) const +{ + QString shortName = my.inst; + int index; + + // heuristic: cull from the first space onward + // reason: this is required to be unique from PMDAs + if ((index = shortName.indexOf(QChar(' '))) != -1) { + shortName.truncate(index); + } + return shortName; +} + +void +ChartItem::clearLegendLabel(void) +{ + // + // No legend has been explicitly requested - but something + // must be displayed. Use metric name with optional [inst]. + // If that goes beyond a limit, truncate from the front and + // use ellipses to indicate this has happened. + // + if (my.name.size() > PmChart::maximumLegendLength()) { + int size = PmChart::maximumLegendLength() - 3; + my.label = QString("..."); + my.label.append(my.name.right(size)); + } else { + my.label = my.name; + } +} + + +void +Chart::setCurrent(bool enable) +{ + QwtScaleWidget *sp; + QPalette palette; + QwtText t; + + console->post("Chart::setCurrent(%p) %s", this, enable ? "true" : "false"); + + // (Re)set title and y-axis highlight for new/old current chart. + // For title, have to set both QwtText and QwtTextLabel because of + // the way attributes are cached and restored when printing charts + + t = titleLabel()->text(); + t.setColor(enable ? globalSettings.chartHighlight : "black"); + setTitle(t); + palette = titleLabel()->palette(); + palette.setColor(QPalette::Active, QPalette::Text, + enable ? globalSettings.chartHighlight : QColor("black")); + titleLabel()->setPalette(palette); + + sp = axisWidget(QwtPlot::yLeft); + t = sp->title(); + t.setColor(enable ? globalSettings.chartHighlight : "black"); + sp->setTitle(t); +} + +void +Chart::preserveSample(int index, int oldindex) +{ +#if DESPERATE + console->post("Chart::preserveSample %d <- %d (%d items)", + index, oldindex, my.items.size()); +#endif + for (int i = 0; i < my.items.size(); i++) + my.items[i]->preserveSample(index, oldindex); +} + +void +Chart::punchoutSample(int index) +{ +#if DESPERATE + console->post("Chart::punchoutSample %d (%d items)", + index, my.items.size()); +#endif + for (int i = 0; i < my.items.size(); i++) + my.items[i]->punchoutSample(index); +} + +void +Chart::adjustValues(void) +{ + my.engine->replot(); + replot(); +} + +void +Chart::updateValues(bool forward, bool visible, int size, int points, + double left, double right, double delta) +{ +#if DESPERATE + console->post(PmChart::DebugForce, + "Chart::updateValues(forward=%d,visible=%d) sz=%d pts=%d (%d items)", + forward, visible, size, points, my.items.size()); +#endif + + if (visible) { + double scale = pmchart->timeAxis()->scaleValue(delta, points); + setAxisScale(QwtPlot::xBottom, left, right, scale); + } + + if (my.items.size() > 0) + my.engine->updateValues(forward, size, points, left, right, delta); + + if (visible) { + replot(); // done first so Value Axis range is updated + my.engine->redoScale(); + } +} + +void +Chart::replot() +{ + my.engine->replot(); + QwtPlot::replot(); +} + +void +Chart::legendChecked(QwtPlotItem *item, bool down) +{ +#if DESPERATE + console->post(PmChart::DebugForce, "Chart::legendChecked %s for item %p", + down? "down":"up", item); +#endif + + // find matching item and update hidden status if required + bool changed = false; + for (int i = 0; i < my.items.size(); i++) { + if (my.items[i]->item() != item) + continue; + // if the state is changing, note it and update + if (my.items[i]->hidden() != down) { + my.items[i]->setHidden(down); + changed = true; + } + break; + } + + if (changed) { + item->setVisible(down == false); + replot(); + } +} + +// +// Add a new chart item (metric, usually with a specific instance) +// +int +Chart::addItem(pmMetricSpec *msp, const QString &legend) +{ + console->post("Chart::addItem src=%s", msp->source); + if (msp->ninst == 0) + console->post("addItem metric=%s", msp->metric); + else + console->post("addItem instance %s[%s]", msp->metric, msp->inst[0]); + + QmcMetric *mp = my.tab->group()->addMetric(msp, 0.0, true); + if (mp->status() < 0) + return mp->status(); + + pmDesc desc = mp->desc().desc(); + if (my.items.size() == 0) { + // first plot item, setup a new ChartEngine + ChartEngine *engine = + (desc.type == PM_TYPE_EVENT || + desc.type == PM_TYPE_HIGHRES_EVENT) ? + (ChartEngine *) new TracingEngine(this) : + (ChartEngine *) new SamplingEngine(this, desc); + delete my.engine; + my.engine = engine; + } + else if (!my.engine->isCompatible(desc)) { + // not compatible with existing metrics, fail + return PM_ERR_CONV; + } + + // Finally, request the engine allocate a new chart item, + // set prevailing chart style and default color, show it. + // + ChartItem *item = my.engine->addItem(mp, msp, &desc, legend); + setStroke(item, my.style, nextColor(my.scheme, &my.sequence)); + item->setVisible(true); + replot(); + + my.items.append(item); + console->post("addItem %p nitems=%d", item, my.items.size()); + + changeTitle(title(), true); // regenerate %h and/or %H expansion + return my.items.size() - 1; +} + +bool +Chart::activeMetric(int index) const +{ + return activeItem(index); +} + +bool +Chart::activeItem(int index) const +{ + return (my.items[index]->removed() == false); +} + +void +Chart::removeItem(int index) +{ + my.items[index]->remove(); + changeTitle(title(), true); // regenerate %h and/or %H expansion +} + +void +Chart::reviveItem(int index) +{ + my.items[index]->revive(); + changeTitle(title(), true); // regenerate %h and/or %H expansion +} + +void +Chart::resetValues(int samples, double left, double right) +{ + for (int i = 0; i < my.items.size(); i++) + my.items[i]->resetValues(samples, left, right); + replot(); +} + +int +Chart::metricCount() const +{ + return my.items.size(); +} + +QString +Chart::title() +{ + return my.title; +} + +void +Chart::resetTitleFont(void) +{ + QwtText t = titleLabel()->text(); + t.setFont(*globalFont); + setTitle(t); + // have to set font for both QwtText and QwtTextLabel because of + // the way attributes are cached and restored when printing charts + QFont titleFont = *globalFont; + titleFont.setBold(true); + titleLabel()->setFont(titleFont); +} + +void +Chart::resetFont(void) +{ + QwtPlot::legend()->contentsWidget()->setFont(*globalFont); + setAxisFont(QwtPlot::yLeft, *globalFont); + resetTitleFont(); +} + +QString +Chart::hostNameString(bool shortened) +{ + int dot = -1; + QSet<QString> hostNameSet; + QList<ChartItem*>::Iterator item; + + // iterate across this chart's items, not activeGroup + for (item = my.items.begin(); item != my.items.end(); ++item) { + // NB: ... but we don't get notified of direct calls to + // ChartItem::setRemoved(). + if ((*item)->removed()) + continue; + + // QString host = (*item)->metricContext()->source().host(); + // ... but .host() is a possibly-munged of the pmchart -h STRING + // argument, not the actual host name. So get the data source's + // self-declared host name. This string will not have pmproxy @ + // stuff, or pcp://....&attr=... miscellanea. + QString hostName = (*item)->metricContext()->source().context_hostname(); + + // decide whether or not to truncate this hostname + if (shortened) + dot = hostName.indexOf(QChar('.')); + if (dot != -1) { + // no change if it looks even vaguely like an IP address + if (!hostName.contains(QRegExp("^\\d+\\.")) && // IPv4 + !hostName.contains(QChar(':'))) // IPv6 + hostName.truncate(dot); + } + hostNameSet.insert(hostName); + } + + // extract the duplicate-eliminated host names + QSet<QString>::Iterator qsi; + QString names; + for (qsi = hostNameSet.begin(); qsi != hostNameSet.end(); qsi++) { + if (names != "") + names += ","; + names += (*qsi); + } + return names; +} + +// +// If expand is true then expand %h or %H to host name in rendered title label +// +void +Chart::changeTitle(QString title, bool expand) +{ + bool hadTitle = (my.title != QString::null); + bool expandHostShort = title.contains("%h"); + bool expandHostLong = title.contains("%H"); + + my.title = title; // copy into QString + + if (my.title != QString::null) { + if (!hadTitle) + pmchart->updateHeight(titleLabel()->height()); + resetTitleFont(); + if (expand && (expandHostShort || expandHostLong)) { + if (expandHostShort) + title.replace(QRegExp("%h"), hostNameString(true)); + if (expandHostLong) + title.replace(QRegExp("%H"), hostNameString(false)); + setTitle(title); + // NB: my.title retains the %h and/or %H + } + else + setTitle(my.title); + } + else { + if (hadTitle) + pmchart->updateHeight(-(titleLabel()->height())); + setTitle(NULL); + } +} + +QString +Chart::scheme() const +{ + return my.scheme; +} + +void +Chart::setScheme(QString scheme) +{ + my.sequence = 0; + my.scheme = scheme; +} + +void +Chart::setScheme(QString scheme, int sequence) +{ + my.sequence = sequence; + my.scheme = scheme; +} + +Chart::Style +Chart::style() +{ + return my.style; +} + +void +Chart::setStyle(Style style) +{ + my.style = style; +} + +void +Chart::setStroke(int index, Style style, QColor color) +{ + setStroke(my.items[index], style, color); +} + +void +Chart::setStroke(ChartItem *item, Style style, QColor color) +{ + item->setColor(color); + item->setStroke(style, color, my.engine->antiAliasing()); + + if (style != my.style) { + my.engine->setStyle(my.style); + my.style = style; + adjustValues(); + } +} + +QColor +Chart::color(int index) +{ + if (index >= 0 && index < my.items.size()) + return my.items[index]->color(); + return QColor("white"); +} + +void +Chart::setLabel(int index, const QString &s) +{ + if (index >= 0 && index < my.items.size()) { + ChartItem *item = my.items[index]; + item->setLegend(s); + item->updateLegend(); + } +} + +void +Chart::scale(bool *autoScale, double *yMin, double *yMax) +{ + my.engine->scale(autoScale, yMin, yMax); +} + +bool +Chart::autoScale(void) +{ + return my.engine->autoScale(); +} + +void +Chart::setScale(bool autoScale, double yMin, double yMax) +{ + my.engine->setScale(autoScale, yMin, yMax); + replot(); + my.engine->redoScale(); +} + +bool +Chart::rateConvert() +{ + return my.engine->rateConvert(); +} + +void +Chart::setRateConvert(bool enabled) +{ + my.engine->setRateConvert(enabled); +} + +bool +Chart::antiAliasing() +{ + return my.engine->antiAliasing(); +} + +void +Chart::setAntiAliasing(bool enabled) +{ + my.engine->setAntiAliasing(enabled); +} + +QString +Chart::YAxisTitle(void) const +{ + return axisTitle(QwtPlot::yLeft).text(); +} + +void +Chart::setYAxisTitle(const char *p) +{ + QwtText *t; + bool enable = (my.tab->currentGadget() == this); + + if (!p || *p == '\0') + t = new QwtText(" "); // for y-axis alignment (space is invisible) + else + t = new QwtText(p); + t->setFont(*globalFont); + t->setColor(enable ? globalSettings.chartHighlight : "black"); + setAxisTitle(QwtPlot::yLeft, *t); +} + +void +Chart::activated(bool on) +{ + if (on) + Q_EMIT timeSelectionActive(this, + canvas()->mapFromGlobal(QCursor::pos()).x()); + else + Q_EMIT timeSelectionInactive(this); +} + +void +Chart::selected(const QPolygon &poly) +{ + my.engine->selected(poly); + my.tab->setCurrent(this); +} + +void +Chart::selected(const QPointF &p) +{ + showPoint(p); + my.tab->setCurrent(this); +} + +void +Chart::moved(const QPointF &p) +{ + Q_EMIT timeSelectionReactive(this, + canvas()->mapFromGlobal(QCursor::pos()).x()); + my.engine->moved(p); +} + + +void +Chart::showPoint(const QPointF &p) +{ + ChartItem *selected = NULL; + double dist, distance = 10e10; + int index = -1; + + // pixel point + QPoint pp = my.picker->transform(p); + + console->post("Chart::showPoint p=%.2f,%.2f pixel=%d,%d", + p.x(), p.y(), pp.x(), pp.y()); + + // seek the closest curve to the point selected + for (int i = 0; i < my.items.size(); i++) { + QwtPlotCurve *curve = my.items[i]->curve(); + int point = curve->closestPoint(pp, &dist); + + if (dist < distance) { + index = point; + distance = dist; + selected = my.items[i]; + } + } + + // clear existing selections then show this one + bool update = (index >= 0 && pp.y() >= 0); + for (int i = 0; i < my.items.size(); i++) { + ChartItem *item = my.items[i]; + + item->clearCursor(); + if (update && item == selected) + item->updateCursor(p, index); + } +} + +void +Chart::activateTime(QMouseEvent *event) +{ + bool block = signalsBlocked(); + blockSignals(true); + my.picker->widgetMousePressEvent(event); + blockSignals(block); +} + +void +Chart::reactivateTime(QMouseEvent *event) +{ + bool block = signalsBlocked(); + blockSignals(true); + my.picker->widgetMouseMoveEvent(event); + blockSignals(block); +} + +void +Chart::deactivateTime(QMouseEvent *event) +{ + bool block = signalsBlocked(); + blockSignals(true); + my.picker->widgetMouseReleaseEvent(event); + blockSignals(block); +} + +void +Chart::showPoints(const QPolygon &poly) +{ + Q_ASSERT(poly.size() == 2); + + console->post("Chart::showPoints: %d,%d -> %d,%d", + poly.at(0).x(), poly.at(0).y(), poly.at(1).x(), poly.at(1).y()); + + // Transform selected (pixel) points to our coordinate system + QRectF cp = my.picker->invTransform(poly.boundingRect()); + const QPointF &p = cp.topLeft(); + + // + // If a single-point selected, use showPoint instead + // (this uses proximity checking, not bounding box). + // + if (cp.width() == 0 && cp.height() == 0) { + showPoint(p); + } + else { + // clear existing selections, find and show new ones + for (int i = 0; i < my.items.size(); i++) { + ChartItem *item = my.items[i]; + int itemDataSize = item->curve()->dataSize(); + + item->clearCursor(); + for (int index = 0; index < itemDataSize; index++) + if (item->containsPoint(cp, index)) + item->updateCursor(p, index); + } + } + + showInfo(); +} + +// +// give feedback (popup) about the selection +// +void +Chart::showInfo(void) +{ + QString info = QString::null; + + pmchart->timeout(); // clear status bar + for (int i = 0; i < my.items.size(); i++) { + ChartItem *item = my.items[i]; + if (info != QString::null) + info.append("\n"); + info.append(item->cursorInfo()); + } + + while (!info.isEmpty() && (info.at(info.length()-1) == '\n')) + info.chop(1); + + if (!info.isEmpty()) + QWhatsThis::showText(QCursor::pos(), info, this); + else + QWhatsThis::hideText(); +} + +bool +Chart::legendVisible() +{ + // Legend is on or off for all items, only need to test the first item + if (my.items.size() > 0) + return QwtPlot::legend() != NULL; + return false; +} + +// Use Edit->Chart Title and Legend to enable/disable the legend. +// Clicking on individual legend buttons will hide/show the +// corresponding item. +// +void +Chart::setLegendVisible(bool on) +{ + QwtLegend *legend = QwtPlot::legend(); + + if (on) { + if (legend == NULL) { // currently disabled, enable it + legend = new QwtLegend; + + legend->setItemMode(QwtLegend::CheckableItem); + insertLegend(legend, QwtPlot::BottomLegend); + // force each Legend item to "checked" state matching + // the initial plotting state + for (int i = 0; i < my.items.size(); i++) + my.items[i]->item()->setVisible(my.items[i]->removed()); + replot(); + } + } + else { + if (legend != NULL) { + // currently enabled, disable it + insertLegend(NULL, QwtPlot::BottomLegend); + + // delete legend; + // WISHLIST: this can cause a core dump - needs investigating + // [memleak]. Really, all of the legend code needs reworking. + } + } +} + +void +Chart::save(FILE *f, bool hostDynamic) +{ + SaveViewDialog::saveChart(f, this, hostDynamic); +} + +void +Chart::print(QPainter *qp, QRect &rect, bool transparent) +{ + QwtPlotRenderer renderer; + + if (transparent) + renderer.setDiscardFlag(QwtPlotRenderer::DiscardBackground); + renderer.render(this, qp, rect); +} + +QString Chart::name(int index) const +{ + return my.items[index]->name(); +} + +QString Chart::legend(int index) const +{ + return my.items[index]->legend(); +} + +QmcDesc *Chart::metricDesc(int index) const +{ + return my.items[index]->metricDesc(); +} + +QString Chart::metricName(int index) const +{ + return my.items[index]->metricName(); +} + +QString Chart::metricInstance(int index) const +{ + return my.items[index]->metricInstance(); +} + +QmcContext *Chart::metricContext(int index) const +{ + return my.items[index]->metricContext(); +} + +QmcMetric *Chart::metric(int index) const +{ + return my.items[index]->metric(); +} + +QSize Chart::minimumSizeHint() const +{ + return QSize(10,10); +} + +QSize Chart::sizeHint() const +{ + return QSize(150,100); +} + +void +Chart::setupTree(QTreeWidget *tree) +{ + for (int i = 0; i < my.items.size(); i++) { + ChartItem *item = my.items[i]; + + if (item->removed()) + continue; + addToTree(tree, item->name(), + item->metricContext(), item->metricHasInstances(), + item->color(), item->legend()); + } +} + +void +Chart::addToTree(QTreeWidget *treeview, const QString &metric, + const QmcContext *context, bool isInst, QColor color, + const QString &label) +{ + QRegExp regexInstance("\\[(.*)\\]$"); + QRegExp regexNameNode("\\."); + QString source = context->source().source(); + QString inst, name = metric; + QStringList namelist; + int depth, index; + + console->post("Chart::addToTree src=%s metric=%s, isInst=%d", + (const char *)source.toAscii(), (const char *)metric.toAscii(), + isInst); + + depth = name.indexOf(regexInstance); + if (depth > 0) { + inst = name.mid(depth+1); // after '[' + inst.chop(1); // final ']' + name = name.mid(0, depth); // prior '[' + } + // note: hostname removal must be done *after* instance removal + // and must take into consideration IPv4/IPv6 address types too + index = name.lastIndexOf(QChar(':')); + if (index > 0) + name = name.remove(0, index+1); + + namelist = name.split(regexNameNode); + namelist.prepend(source); // add the host/archive root as well. + if (depth > 0) + namelist.append(inst); + depth = namelist.size(); + + // Walk through each component of this name, creating them in the + // target tree (if not there already), right down to the leaf. + + NameSpace *tree = (NameSpace *)treeview->invisibleRootItem(); + NameSpace *item = NULL; + + for (int b = 0; b < depth; b++) { + QString text = namelist.at(b); + bool foundMatchingName = false; + for (int i = 0; i < tree->childCount(); i++) { + item = (NameSpace *)tree->child(i); + if (text == item->text(0)) { + // No insert at this level necessary, move down a level + tree = item; + foundMatchingName = true; + break; + } + } + + // When no more children and no match so far, we create & insert + if (foundMatchingName == false) { + NameSpace *n; + if (b == 0) { + n = new NameSpace(treeview, context); + n->expand(); + n->setExpanded(true, true); + n->setSelectable(false); + } + else { + bool isLeaf = (b == depth-1); + n = new NameSpace(tree, text, isLeaf && isInst); + if (isLeaf) { + n->setLabel(label); + n->setOriginalColor(color); + n->setCurrentColor(color, NULL); + } + n->expand(); + n->setExpanded(!isLeaf, true); + n->setSelectable(isLeaf); + if (!isLeaf) + n->setType(NameSpace::NonLeafName); + else if (isInst) // Constructor sets Instance type + tree->setType(NameSpace::LeafWithIndom); + else + n->setType(NameSpace::LeafNullIndom); + } + tree = n; + } + } +} + + +// +// Override behaviour from QwtPlotCurve legend rendering +// Gives us fine-grained control over the colour that we +// display in the legend boxes for each ChartItem. +// +void +ChartCurve::drawLegendIdentifier(QPainter *painter, const QRectF &rect) const +{ + if (rect.isEmpty()) + return; + + QRectF r(0, 0, rect.width()-1, rect.height()-1); + r.moveCenter(rect.center()); + + QPen pen(QColor(Qt::black)); + pen.setCapStyle(Qt::FlatCap); + QBrush brush(legendColor, Qt::SolidPattern); + + painter->setPen(pen); + painter->setBrush(brush); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->drawRect(r.x(), r.y(), r.width(), r.height()); +} diff --git a/src/pmchart/chart.h b/src/pmchart/chart.h new file mode 100644 index 0000000..f0ddc8e --- /dev/null +++ b/src/pmchart/chart.h @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2012-2014, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * Copyright (c) 2006-2010, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#ifndef CHART_H +#define CHART_H + +#include <QtCore/QString> +#include <QtCore/QDateTime> +#include <QtGui/QColor> +#include <QtGui/QTreeWidget> +#include <qwt_plot.h> +#include <qwt_plot_curve.h> +#include <qwt_plot_picker.h> +#include <qmc_metric.h> +#include "gadget.h" + +class Tab; +class ChartItem; +class ChartPicker; +class ChartEngine; +class TracingEngine; +class SamplingEngine; + +// +// Centre of the pmchart universe +// +class Chart : public QwtPlot, public Gadget +{ + Q_OBJECT + +public: + Chart(Tab *, QWidget *); + ~Chart(void); + + typedef enum { + NoStyle, + LineStyle, + BarStyle, + StackStyle, + AreaStyle, + UtilisationStyle, + EventStyle + } Style; + + virtual void resetFont(); + virtual void setCurrent(bool); + virtual QString scheme() const; // return chart color scheme + virtual void setScheme(QString); // set the chart color scheme + + int addItem(pmMetricSpec *, const QString &); + bool activeItem(int) const; + void removeItem(int); + void reviveItem(int); + + QString title(void); // return copy of chart title + void changeTitle(QString, bool); // QString::null to clear; expand? + QString hostNameString(bool); // short/long host names as qstring + + Style style(void); // return chart style + void setStyle(Style); // set default chart plot style + + QColor color(int); // return color for ith plot + static QColor schemeColor(QString, int *); + void setStroke(int, Style, QColor); // set chart style and color + void setScheme(QString, int); // set the chart scheme and position + + int sequence() // return chart color scheme position + { return my.sequence; } + void setSequence(int sequence) // set the chart color scheme position + { my.sequence = sequence; } + + QString label(int); // return legend label for ith plot + void setLabel(int, const QString &); // set plot legend label + + bool autoScale(void); + void scale(bool *, double *, double *); + // return autoscale state and fixed scale parameters + void setScale(bool, double, double); + // set autoscale state and fixed scale parameters + QString YAxisTitle() const; + void setYAxisTitle(const char *); + bool legendVisible(); + void setLegendVisible(bool); + bool rateConvert(); + void setRateConvert(bool); + bool antiAliasing(); + void setAntiAliasing(bool); + + virtual void save(FILE *, bool); + virtual void print(QPainter *, QRect &, bool); + + virtual void updateValues(bool, bool, int, int, double, double, double); + virtual void resetValues(int, double, double); + virtual void adjustValues(); + + virtual void preserveSample(int, int); + virtual void punchoutSample(int); + + virtual int metricCount() const; + virtual bool activeMetric(int) const; + virtual QString name(int) const; + virtual QString legend(int) const; + virtual QmcMetric *metric(int) const; + virtual QString metricName(int) const; + virtual QmcDesc *metricDesc(int) const; + virtual QString metricInstance(int) const; + virtual QmcContext *metricContext(int) const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setupTree(QTreeWidget *); + void addToTree(QTreeWidget *, const QString &, const QmcContext *, + bool, QColor, const QString &); + + void activateTime(QMouseEvent *); + void reactivateTime(QMouseEvent *); + void deactivateTime(QMouseEvent *); + +Q_SIGNALS: + void timeSelectionActive(Gadget *, int); + void timeSelectionReactive(Gadget *, int); + void timeSelectionInactive(Gadget *); + +public Q_SLOTS: + void replot(void); + +private Q_SLOTS: + void activated(bool); + void selected(const QPolygon &); + void selected(const QPointF &); + void moved(const QPointF &); + void legendChecked(QwtPlotItem *, bool); + +private: + // changing properties + void setStroke(ChartItem *, Style, QColor); + void resetTitleFont(void); + + // handling selection + void showInfo(void); + void showPoint(const QPointF &); + void showPoints(const QPolygon &); + + struct { + Tab *tab; + QList<ChartItem *> items; + + QString title; + QString scheme; + int sequence; + Style style; + + ChartEngine *engine; + ChartPicker *picker; + } my; + + friend class TracingEngine; + friend class SamplingEngine; +}; + +// +// Wrapper class that simply allows us to call the mouse event handlers +// for the picker class. Helps implement the time axis picker extender. +// +class ChartPicker : public QwtPlotPicker +{ +public: + ChartPicker(QwtPlotCanvas *canvas) : + QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft, + QwtPicker::CrossRubberBand, QwtPicker::AlwaysOff, canvas) { } + + void widgetMousePressEvent(QMouseEvent *event) + { QwtPlotPicker::widgetMousePressEvent(event); } + void widgetMouseReleaseEvent(QMouseEvent *event) + { QwtPlotPicker::widgetMouseReleaseEvent(event); } + void widgetMouseMoveEvent(QMouseEvent *event) + { QwtPlotPicker::widgetMouseMoveEvent(event); } +}; + +// +// Abstraction for differences between event tracing and sampling models +// Note that this base class is used for an initially empty chart +// +class ChartEngine +{ +public: + ChartEngine() { } + ChartEngine(Chart *chart); + virtual ~ChartEngine() {} + + // test whether a new metric (type and units) would be compatible + // with this engine and any metrics already plotted in the chart. + virtual bool isCompatible(pmDesc &) { return true; } + + // insert a new item (plot curve) into a chart + virtual ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &) + { return NULL; } // cannot add to an empty engine + + // indicates movement forward/backward occurred + virtual void updateValues(bool, int, int, double, double, double) { } + + // indicates the Y-axis scale needs updating + virtual bool autoScale(void) { return false; } + virtual void redoScale(void) { } + virtual void setScale(bool, double, double) { } + virtual void scale(bool *autoScale, double *yMin, double *yMax) + { *autoScale = false; *yMin = 0.0; *yMax = 1.0; } + + // get/set other attributes of the chart + virtual bool rateConvert(void) const { return my.rateConvert; } + virtual void setRateConvert(bool enabled) { my.rateConvert = enabled; } + virtual bool antiAliasing(void) const { return my.antiAliasing; } + virtual void setAntiAliasing(bool enabled) { my.antiAliasing = enabled; } + virtual void setStyle(Chart::Style) { } + + // prepare for chart replot() being called + virtual void replot(void) { } + + // a selection has been made/changed, handle it + virtual void selected(const QPolygon &) { } + virtual void moved(const QPointF &) { } + +private: + struct { + bool rateConvert; + bool antiAliasing; + } my; +}; + +// +// Helper dealing with overriding of legend behaviour +// +class ChartCurve : public QwtPlotCurve +{ +public: + ChartCurve(const QString &title) + : QwtPlotCurve(title), legendColor(Qt::white) { } + + virtual void drawLegendIdentifier(QPainter *painter, + const QRectF &rect ) const; + void setLegendColor(QColor color) { legendColor = color; } + QColor legendColor; +}; + +// +// Container for an individual plot item within a chart, +// which is always backed by a single metric. +// +class ChartItem +{ +public: + ChartItem() { } + ChartItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &); + virtual ~ChartItem(void) { } + + virtual QwtPlotItem *item(void) = 0; + virtual QwtPlotCurve *curve(void) = 0; + + virtual void preserveSample(int, int) = 0; + virtual void punchoutSample(int) = 0; + virtual void resetValues(int, double, double) = 0; + + virtual void clearCursor() = 0; + virtual bool containsPoint(const QRectF &, int) = 0; + virtual void updateCursor(const QPointF &, int) = 0; + virtual const QString &cursorInfo() = 0; + + virtual void setVisible(bool on) { item()->setVisible(on); } + virtual void setStroke(Chart::Style, QColor, bool) = 0; + virtual void revive(void) = 0; + virtual void remove(void) = 0; + + QString name(void) const { return my.name; } + QString label(void) const { return my.label; } // as displayed, expanded + QString legend(void) const { return my.legend; } // no %i/%h/.. expansion + QString metricName(void) const { return my.metric->name(); } + QString metricInstance(void) const + { return my.metric->numInst() > 0 ? my.metric->instName(0) : QString::null; } + bool metricHasInstances(void) const { return my.metric->hasInstances(); } + QmcDesc *metricDesc(void) const { return (QmcDesc *)&my.metric->desc(); } + QmcContext *metricContext(void) const { return my.metric->context(); } + QmcMetric *metric(void) const { return my.metric; } + QColor color(void) const { return my.color; } + + void setColor(QColor color) { my.color = color; } + void setLegend(const QString &legend); // may include wildcards + void updateLegend(); + + bool hidden(void) { return my.hidden; } + void setHidden(bool hidden) { my.hidden = hidden; } + + bool removed(void) { return my.removed; } + void setRemoved(bool removed) { my.removed = removed; } + +protected: + struct { + QmcMetric *metric; + pmUnits units; // base units, *not* scaled + + QString name; + QString inst; + QString legend; // may contain wildcards (not expanded) + QString label; // as appears visibly, in plot legend + QColor color; + + bool removed; + bool hidden; // true if hidden through legend push button + } my; + +private: + void expandLegendLabel(const QString &legend); + void clearLegendLabel(void); + + QString hostname(void) const; + QString shortHostName(void) const; + QString shortMetricName(void) const; + QString shortInstName(void) const; +}; + +#endif // CHART_H diff --git a/src/pmchart/chartdialog.cpp b/src/pmchart/chartdialog.cpp new file mode 100644 index 0000000..d42b1d4 --- /dev/null +++ b/src/pmchart/chartdialog.cpp @@ -0,0 +1,916 @@ +/* + * Copyright (c) 2013-2014, Red Hat. + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtGui/QHeaderView> +#include <QtGui/QFileDialog> +#include <QtGui/QMessageBox> +#include "chartdialog.h" +#include "qed_colorpicker.h" +#include "hostdialog.h" +#include "chart.h" +#include "tab.h" +#include "main.h" + +ChartDialog::ChartDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + init(); +} + +void ChartDialog::languageChange() +{ + retranslateUi(this); +} + +void ChartDialog::init() +{ + my.rateConvert = true; + my.chartTreeSelected = false; + my.availableTreeSelected = false; + my.chartTreeSingleSelected = NULL; + my.availableTreeSingleSelected = NULL; + connect(chartMetricsTreeWidget, SIGNAL(itemSelectionChanged()), + this, SLOT(chartMetricsItemSelectionChanged())); + connect(availableMetricsTreeWidget, SIGNAL(itemSelectionChanged()), + this, SLOT(availableMetricsItemSelectionChanged())); + connect(availableMetricsTreeWidget, + SIGNAL(itemExpanded(QTreeWidgetItem *)), this, + SLOT(availableMetricsItemExpanded(QTreeWidgetItem *))); + + my.currentColor = qRgb( -1, -1, -1 ); + hEd->setRange(0, 359); + + connect(hEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(sEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(vEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(rEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(gEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(bEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + + connect(applyColorLabel, + SIGNAL(colorDropped(QRgb)), this, SIGNAL(newCol(QRgb))); + connect(applyColorLabel, + SIGNAL(colorDropped(QRgb)), this, SLOT(setRgb(QRgb))); + connect(colorPicker, + SIGNAL(newCol(int,int)), luminancePicker, SLOT(setCol(int,int))); + connect(luminancePicker, + SIGNAL(newHsv(int,int,int)), this, SLOT(newHsv(int,int,int))); + connect(colorLineEdit, + SIGNAL(newColor(QColor)), this, SLOT(newColor(QColor))); + connect(this, + SIGNAL(newCol(QRgb)), this, SLOT(newColorTypedIn(QRgb))); +} + +void ChartDialog::reset() +{ + my.chart = NULL; + + setWindowTitle(tr("New Chart")); + tabWidget->setCurrentIndex(1); + chartMetricsTreeWidget->clear(); + titleLineEdit->setText(tr("")); + typeComboBox->setCurrentIndex((int)Chart::LineStyle - 1); + legendOn->setChecked(true); + legendOff->setChecked(false); + antiAliasingOn->setChecked(false); + antiAliasingOff->setChecked(false); + antiAliasingAuto->setChecked(true); + rateConvertCheckBox->setChecked(true); + rateConvertCheckBox->setEnabled(true); + setCurrentScheme(QString::null); + my.sequence = 0; + + resetCompletely(); + enableUi(); +} + +void ChartDialog::reset(Chart *cp) +{ + bool yAutoScale; + double yMin, yMax; + + my.chart = cp; + + setWindowTitle(tr("Edit Chart")); + tabWidget->setCurrentIndex(0); + setupChartMetricsTree(); + titleLineEdit->setText(cp->title()); + typeComboBox->setCurrentIndex(cp->style() - 1); + legendOn->setChecked(cp->legendVisible()); + legendOff->setChecked(!cp->legendVisible()); + antiAliasingOn->setChecked(cp->antiAliasing()); + antiAliasingOff->setChecked(!cp->antiAliasing()); + antiAliasingAuto->setChecked(false); + my.rateConvert = cp->rateConvert(); + rateConvertCheckBox->setChecked(my.rateConvert); + rateConvertCheckBox->setEnabled(false); + cp->scale(&yAutoScale, &yMin, &yMax); + setScale(yAutoScale, yMin, yMax); + setScheme(cp->scheme(), cp->sequence()); + setupSchemeComboBox(); + + resetCompletely(); + enableUi(); +} + +void ChartDialog::resetPartially(Chart *cp) +{ + my.chart = cp; + + setWindowTitle(tr("Edit Chart")); + setupChartMetricsTree(); + enableUi(); +} + +// +// Code paths common to both Create/Edit dialog uses +// +void ChartDialog::resetCompletely() +{ + if ((my.archiveSource = pmchart->isArchiveTab()) == true) { + sourceButton->setToolTip(tr("Add archives")); + sourceButton->setIcon(QIcon(":/images/archive.png")); + } + else { + sourceButton->setToolTip(tr("Add a host")); + sourceButton->setIcon(QIcon(":/images/computer.png")); + } + setupAvailableMetricsTree(my.archiveSource == true); + my.yMin = yAxisMinimum->value(); + my.yMax = yAxisMaximum->value(); + my.chartTreeSelected = false; + my.availableTreeSelected = false; + my.chartTreeSingleSelected = NULL; + my.availableTreeSingleSelected = NULL; +} + +void ChartDialog::enableUi() +{ + bool selfScaling = autoScaleOff->isChecked(); + minTextLabel->setEnabled(selfScaling); + maxTextLabel->setEnabled(selfScaling); + yAxisMinimum->setEnabled(selfScaling); + yAxisMaximum->setEnabled(selfScaling); + + chartMetricLineEdit->setText(my.chartTreeSingleSelected ? + ((NameSpace *)my.chartTreeSingleSelected)->metricName() : tr("")); + availableMetricLineEdit->setText(my.availableTreeSingleSelected ? + ((NameSpace *)my.availableTreeSingleSelected)->metricName() : tr("")); + metricInfoButton->setEnabled( // there can be only one source + (my.availableTreeSingleSelected && !my.chartTreeSingleSelected) || + (!my.availableTreeSingleSelected && my.chartTreeSingleSelected)); + metricDeleteButton->setEnabled(my.chartTreeSelected); + metricAddButton->setEnabled(my.availableTreeSelected); + metricSearchButton->setEnabled(true); + + revertColorButton->setEnabled(my.chartTreeSingleSelected != NULL); + applyColorButton->setEnabled(my.chartTreeSingleSelected != NULL); + plotLabelLineEdit->setEnabled(my.chartTreeSingleSelected != NULL); + if (my.chartTreeSingleSelected != NULL) { + NameSpace *n = (NameSpace *)my.chartTreeSingleSelected; + revertColorLabel->setColor(n->originalColor()); + setCurrentColor(n->currentColor().rgb()); + plotLabelLineEdit->setText(n->label()); + } + else { + revertColorLabel->setColor(QColor(0xff, 0xff, 0xff)); + setCurrentColor(QColor(0x00, 0x00, 0x00).rgb()); + plotLabelLineEdit->setText(""); + } +} + +// +// Verify user input and don't dismiss dialog (OK) if problems found. +// Needs to handle many cases: New Chart (!my.chart) and Edit Chart, +// as well as Apply and OK. +// +bool ChartDialog::validate(QString &message, int &index) +{ + bool validInput; + + // Check some plots have been selected. + if (!my.chart && chartMetricsTreeWidget->topLevelItemCount() == 0 && + my.availableTreeSelected == false) { + message = tr("No metrics have been selected for plotting.\n"); + validInput = false; + index = 1; + } + // Validate Values Axis scale range if not auto-scaling + else if (autoScaleOn->isChecked() == false && my.yMin >= my.yMax) { + message = tr("Values Axis scale minimum/maximum range is invalid."); + validInput = false; + index = 0; + } + // Check the archive/live type still matches the current Tab + else if (!my.chart && my.archiveSource && !pmchart->isArchiveTab()) { + message = tr("Cannot add an archive Chart to a live Tab"); + validInput = false; + index = 1; + } + else if (!my.chart && !my.archiveSource && pmchart->isArchiveTab()) { + message = tr("Cannot add a live host Chart to an archive Tab"); + validInput = false; + index = 1; + } + else { + validInput = true; + index = 0; + } + return validInput; +} + +void ChartDialog::buttonOk_clicked() +{ + QString message; + int index; + + if (validate(message, index)) { + if (my.chart) + pmchart->acceptEditChart(); + else + pmchart->acceptNewChart(); + QDialog::accept(); + } else { + tabWidget->setCurrentIndex(index); + QMessageBox::warning(this, pmProgname, message, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } +} + +void ChartDialog::buttonApply_clicked() +{ + QString message; + int index; + + if (validate(message, index)) { + if (my.chart) + pmchart->acceptEditChart(); + else // New Chart to Edit Chart transition: + resetPartially(pmchart->acceptNewChart()); + } + else { + tabWidget->setCurrentIndex(index); + QMessageBox::warning(this, pmProgname, message, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } +} + +Chart *ChartDialog::chart() +{ + return my.chart; +} + +void ChartDialog::chartMetricsItemSelectionChanged() +{ + QTreeWidgetItemIterator iterator(chartMetricsTreeWidget, + QTreeWidgetItemIterator::Selected); + my.chartTreeSingleSelected = *iterator; + if ((my.chartTreeSelected = (my.chartTreeSingleSelected != NULL))) + if (*(++iterator) != NULL) + my.chartTreeSingleSelected = NULL; // multiple selections + enableUi(); +} + +void ChartDialog::availableMetricsItemSelectionChanged() +{ + QTreeWidgetItemIterator iterator(availableMetricsTreeWidget, + QTreeWidgetItemIterator::Selected); + my.availableTreeSingleSelected = *iterator; + if ((my.availableTreeSelected = (my.availableTreeSingleSelected != NULL))) + if (*(++iterator) != NULL) + my.availableTreeSingleSelected = NULL; // multiple selections + enableUi(); +} + +void ChartDialog::availableMetricsItemExpanded(QTreeWidgetItem *item) +{ + console->post(PmChart::DebugUi, + "ChartDialog::availableMetricsItemExpanded %p", item); + NameSpace *metricName = (NameSpace *)item; + metricName->setExpanded(true, true); +} + +void ChartDialog::metricInfoButtonClicked() +{ + NameSpace *name = (NameSpace *)(my.chartTreeSingleSelected ? + my.chartTreeSingleSelected : my.availableTreeSingleSelected); + pmchart->metricInfo(name->sourceName(), name->metricName(), + name->metricInstance(), name->sourceType()); +} + +void ChartDialog::metricDeleteButtonClicked() +{ + QList<QTreeWidgetItem *> items = chartMetricsTreeWidget->selectedItems(); + for (int i = 0; i < items.size(); i++) { + NameSpace *name = (NameSpace *)items.at(i); + name->removeFromTree(chartMetricsTreeWidget); + } +} + +void ChartDialog::metricSearchButtonClicked() +{ + pmchart->metricSearch(availableMetricsTreeWidget); +} + +void ChartDialog::availableMetricsTreeWidget_doubleClicked(QModelIndex) +{ + metricAddButtonClicked(); +} + +void ChartDialog::metricAddButtonClicked() +{ + QList<NameSpace *> list; + QTreeWidgetItemIterator iterator(availableMetricsTreeWidget, + QTreeWidgetItemIterator::Selected); + for (; (*iterator); ++iterator) { + NameSpace *item = (NameSpace *)(*iterator); + + if (QmcMetric::real(item->desc().type) == true) + list.append(item); + else { + QString message = item->metricName(); + message.prepend(tr("Cannot plot metric: ")); + message.append(tr("\nThis metric does not have a numeric type.")); + QMessageBox::warning(this, pmProgname, message, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } + } + + QString scheme = my.chart ? my.chart->scheme() : my.scheme; + int sequence = my.chart ? my.chart->sequence() : my.sequence; + + availableMetricsTreeWidget->clearSelection(); + chartMetricsTreeWidget->clearSelection(); // selection(s) made below + for (int i = 0; i < list.size(); i++) + list.at(i)->addToTree(chartMetricsTreeWidget, scheme, &sequence); + + if (my.chart) + my.chart->setSequence(sequence); + else + my.sequence = sequence; +} + +void ChartDialog::archiveButtonClicked() +{ + QFileDialog *af = new QFileDialog(this); + QStringList al; + int sts; + + af->setFileMode(QFileDialog::ExistingFiles); + af->setAcceptMode(QFileDialog::AcceptOpen); + af->setIconProvider(fileIconProvider); + af->setWindowTitle(tr("Add Archive")); + af->setDirectory(QDir::toNativeSeparators(QDir::homePath())); + + if (af->exec() == QDialog::Accepted) + al = af->selectedFiles(); + for (QStringList::Iterator it = al.begin(); it != al.end(); ++it) { + QString archive = *it; + if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) { + archive.prepend(tr("Cannot open PCP archive: ")); + archive.append(tr("\n")); + archive.append(tr(pmErrStr(sts))); + QMessageBox::warning(this, pmProgname, archive, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else { + setupAvailableMetricsTree(true); + archiveGroup->updateBounds(); + const QmcSource source = archiveGroup->context()->source(); + pmtime->addArchive(source.start(), source.end(), + source.timezone(), source.host(), false); + } + } + delete af; +} + +void ChartDialog::hostButtonClicked() +{ + HostDialog *host = new HostDialog(this); + + if (host->exec() == QDialog::Accepted) { + QString hostspec = host->getHostSpecification(); + int sts, flags = host->getContextFlags(); + + if (hostspec == QString::null || hostspec.length() == 0) { + hostspec.append(tr("Hostname not specified\n")); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) { + hostspec.prepend(tr("Cannot connect to host: ")); + hostspec.append(tr("\n")); + hostspec.append(tr(pmErrStr(sts))); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else { + console->post(PmChart::DebugUi, + "ChartDialog::newHost: %s (flags=0x%x)", + (const char *)hostspec.toAscii(), flags); + setupAvailableMetricsTree(false); + } + } + delete host; +} + +void ChartDialog::sourceButtonClicked() +{ + if (my.archiveSource) + archiveButtonClicked(); + else + hostButtonClicked(); +} + +QString ChartDialog::title(void) +{ + return titleLineEdit->text(); +} + +bool ChartDialog::legend(void) +{ + return legendOn->isChecked(); +} + +void ChartDialog::legendOnClicked() +{ + legendOn->setChecked(true); + legendOff->setChecked(false); +} + +void ChartDialog::legendOffClicked() +{ + legendOn->setChecked(false); + legendOff->setChecked(true); +} + +bool ChartDialog::antiAliasing(void) +{ + if (antiAliasingAuto->isChecked()) { + Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1); + return (style != Chart::LineStyle); + } + return antiAliasingOn->isChecked(); +} + +void ChartDialog::antiAliasingOnClicked() +{ + antiAliasingOn->setChecked(true); + antiAliasingOff->setChecked(false); + antiAliasingAuto->setChecked(false); +} + +void ChartDialog::antiAliasingOffClicked() +{ + antiAliasingOn->setChecked(false); + antiAliasingOff->setChecked(true); + antiAliasingAuto->setChecked(false); +} + +void ChartDialog::antiAliasingAutoClicked() +{ + antiAliasingOn->setChecked(false); + antiAliasingOff->setChecked(false); + antiAliasingAuto->setChecked(true); +} + +void ChartDialog::scheme(QString *scheme, int *sequence) +{ + *scheme = my.scheme; + *sequence = my.sequence; +} + +void ChartDialog::setScheme(QString scheme, int sequence) +{ + my.scheme = scheme; + my.sequence = sequence; +} + +void ChartDialog::scale(bool *autoScale, double *yMin, double *yMax) +{ + *autoScale = autoScaleOn->isChecked(); + *yMin = my.yMin; + *yMax = my.yMax; +} + +void ChartDialog::setScale(bool autoScale, double yMin, double yMax) +{ + autoScaleOn->setChecked(autoScale); + autoScaleOff->setChecked(!autoScale); + yAxisMaximum->setValue(yMax); + yAxisMinimum->setValue(yMin); +} + +void ChartDialog::autoScaleOnClicked() +{ + autoScaleOn->setChecked(true); + autoScaleOff->setChecked(false); + minTextLabel->setEnabled(false); + maxTextLabel->setEnabled(false); + yAxisMinimum->setEnabled(false); + yAxisMaximum->setEnabled(false); +} + +void ChartDialog::autoScaleOffClicked() +{ + autoScaleOn->setChecked(false); + autoScaleOff->setChecked(true); + minTextLabel->setEnabled(true); + maxTextLabel->setEnabled(true); + yAxisMinimum->setEnabled(true); + yAxisMaximum->setEnabled(true); +} + +void ChartDialog::yAxisMinimumValueChanged(double value) +{ + my.yMin = value; +} + +void ChartDialog::yAxisMaximumValueChanged(double value) +{ + my.yMax = value; +} + +void ChartDialog::rateConvertClicked() +{ + my.rateConvert = rateConvertCheckBox->isChecked(); +} + +bool ChartDialog::rateConvert() +{ + return my.rateConvert; +} + +void ChartDialog::setRateConvert(bool rateConvert) +{ + my.rateConvert = rateConvert; +} + +// Sets all widgets to display h,s,v +void ChartDialog::newHsv(int h, int s, int v) +{ + setHsv(h, s, v); + colorPicker->setCol(h, s); + luminancePicker->setCol(h, s, v); + colorLineEdit->setCol(h, s, v); +} + +// Sets all widgets to display rgb +void ChartDialog::setCurrentColor(QRgb rgb) +{ + setRgb(rgb); + newColorTypedIn(rgb); +} + +// Sets all widgets except cle to display color +void ChartDialog::newColor(QColor col) +{ + console->post(PmChart::DebugUi, "ChartDialog::newColor"); + int h, s, v; + col.getHsv(&h, &s, &v); + colorPicker->setCol(h, s); + luminancePicker->setCol(h, s, v); + setRgb(col.rgb()); +} + +// Sets all widgets except cs to display rgb +void ChartDialog::newColorTypedIn(QRgb rgb) +{ + console->post(PmChart::DebugUi, "ChartDialog::newColorTypedIn"); + int h, s, v; + rgb2hsv(rgb, h, s, v); + colorPicker->setCol(h, s); + luminancePicker->setCol(h, s, v); + colorLineEdit->setCol(h, s, v); +} + +void ChartDialog::setRgb(QRgb rgb) +{ + console->post(PmChart::DebugUi, "ChartDialog::setRgb"); + my.currentColor = rgb; + rgb2hsv(my.currentColor, my.hue, my.sat, my.val); + hEd->setValue(my.hue); + sEd->setValue(my.sat); + vEd->setValue(my.val); + rEd->setValue(qRed(my.currentColor)); + gEd->setValue(qGreen(my.currentColor)); + bEd->setValue(qBlue(my.currentColor)); + showCurrentColor(); +} + +void ChartDialog::setHsv(int h, int s, int v) +{ + console->post(PmChart::DebugUi, "ChartDialog::setHsv h=%d s=%d v=%d",h,s,v); + QColor c; + c.setHsv(h, s, v); + my.currentColor = c.rgb(); + my.hue = h; my.sat = s; my.val = v; + hEd->setValue(my.hue); + sEd->setValue(my.sat); + vEd->setValue(my.val); + rEd->setValue(qRed(my.currentColor)); + gEd->setValue(qGreen(my.currentColor)); + bEd->setValue(qBlue(my.currentColor)); + showCurrentColor(); +} + +QRgb ChartDialog::currentColor() +{ + return my.currentColor; +} + +void ChartDialog::rgbEd() +{ + my.currentColor = qRgb(rEd->value(), gEd->value(), bEd->value()); + rgb2hsv(my.currentColor, my.hue, my.sat, my.val); + hEd->setValue(my.hue); + sEd->setValue(my.sat); + vEd->setValue(my.val); + showCurrentColor(); + Q_EMIT newCol(my.currentColor); +} + +void ChartDialog::hsvEd() +{ + my.hue = hEd->value(); + my.sat = sEd->value(); + my.val = vEd->value(); + QColor c; + c.setHsv(my.hue, my.sat, my.val); + my.currentColor = c.rgb(); + rEd->setValue(qRed(my.currentColor)); + gEd->setValue(qGreen(my.currentColor)); + bEd->setValue(qBlue(my.currentColor)); + showCurrentColor(); + Q_EMIT newCol(my.currentColor); +} + +void ChartDialog::showCurrentColor() +{ + console->post(PmChart::DebugUi, "ChartDialog::showCurrentColor"); + applyColorLabel->setColor(my.currentColor); + colorLineEdit->setColor(my.currentColor); +} + +void ChartDialog::applyColorButtonClicked() +{ + NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected; + ns->setCurrentColor(my.currentColor, chartMetricsTreeWidget); +} + +void ChartDialog::revertColorButtonClicked() +{ + NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected; + ns->setCurrentColor(ns->originalColor(), NULL); +} + +void ChartDialog::plotLabelLineEdit_editingFinished() +{ + NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected; + ns->setLabel(plotLabelLineEdit->text().trimmed()); +} + +void ChartDialog::setupChartMetricsTree() +{ + chartMetricsTreeWidget->clear(); + my.chart->setupTree(chartMetricsTreeWidget); +} + +void ChartDialog::setupAvailableMetricsTree(bool arch) +{ + NameSpace *current = NULL; + QList<QTreeWidgetItem*> items; + QmcGroup *group = arch ? archiveGroup : liveGroup; + + availableMetricsTreeWidget->clear(); + for (unsigned int i = 0; i < group->numContexts(); i++) { + QmcContext *cp = group->context(i); + if (cp->status() < 0) + continue; + NameSpace *name = new NameSpace(availableMetricsTreeWidget, cp); + name->setExpanded(true, group->numContexts() == 1); + name->setSelectable(false); + availableMetricsTreeWidget->addTopLevelItem(name); + if (i == group->contextIndex()) + current = name; + items.append(name); + } + if (items.size() > 0) + availableMetricsTreeWidget->insertTopLevelItems(0, items); + if (current) + availableMetricsTreeWidget->setCurrentItem(current); +} + + +void ChartDialog::updateChartPlots(Chart *cp) +{ + deleteChartPlots(cp); + if (setupChartPlotsShortcut(cp) == false) + setupChartPlots(cp); +} + +void ChartDialog::deleteChartPlots(Chart *cp) +{ + int m, nplots = cp->metricCount(); // Copy, as we change it in the loop body + + // Iterate over the current Charts metrics, removing any + // that are no longer in the chartMetricsTreeWidget. + // This is a no-op in the createChart case, of course. + + for (m = 0; m < nplots; m++) { + QTreeWidgetItemIterator iterator1(chartMetricsTreeWidget, + QTreeWidgetItemIterator::Selectable); + for (; (*iterator1); ++iterator1) { + if (matchChartPlot(cp, (NameSpace *)(*iterator1), m)) + break; + } + if ((*iterator1) == NULL) + deleteChartPlot(cp, m); + } +} + +void ChartDialog::setupChartPlots(Chart *cp) +{ + // Second step is to iterate over all the chartMetricsTreeWidget + // entries, and either create new plots or edit existing ones. + + QTreeWidgetItemIterator iterator2(chartMetricsTreeWidget, + QTreeWidgetItemIterator::Selectable); + for (; *iterator2; ++iterator2) { + NameSpace *n = (NameSpace *)(*iterator2); + int m; + + if (existsChartPlot(cp, n, &m)) + changeChartPlot(cp, n, m); + else + createChartPlot(cp, n); + } +} + +bool ChartDialog::setupChartPlotsShortcut(Chart *cp) +{ + // This "shortcut" is used in both the New Chart and Edit Chart + // case. It allows the user to bypass the step of moving plots + // from the Available Metrics list to the Chart Plots list. + // + // Return value indicates whether the Chart change is complete + // at the end (i.e. used the shortcut), or whether we need to + // continue on populating the new chart with Chart Plots. + + if (chartMetricsTreeWidget->topLevelItemCount() > 0) + return false; // go do regular creation paths + + int i, m, seq = 0; + QTreeWidgetItemIterator iterator(availableMetricsTreeWidget, + QTreeWidgetItemIterator::Selected); + for (i = 0; (*iterator); ++iterator, i++) { + NameSpace *n = (NameSpace *)(*iterator); + if (existsChartPlot(cp, n, &m)) { + changeChartPlot(cp, n, m); + } else { + QColor c = nextColor(cp->scheme(), &seq); + n->setCurrentColor(c, NULL); + createChartPlot(cp, n); + my.sequence = seq; + } + } + return true; // either way, we're finished now +} + +bool ChartDialog::matchChartPlot(Chart *cp, NameSpace *name, int m) +{ + if (cp->metricContext(m) != name->metricContext()) + return false; + if (cp->metricName(m) != name->metricName()) + return false; + if (cp->metricInstance(m) != name->metricInstance()) + return false; + return true; +} + +bool ChartDialog::existsChartPlot(Chart *cp, NameSpace *name, int *m) +{ + for (int i = 0; i < cp->metricCount(); i++) { + if (matchChartPlot(cp, name, i)) { + *m = i; + return true; + } + } + *m = -1; + return false; +} + +void ChartDialog::changeChartPlot(Chart *cp, NameSpace *name, int m) +{ + Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1); + cp->setStroke(m, style, name->currentColor()); + cp->setLabel(m, name->label()); + cp->reviveItem(m); +} + +void ChartDialog::createChartPlot(Chart *cp, NameSpace *name) +{ + Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1); + pmMetricSpec pms; + QString label; + + label = name->label().isEmpty() ? QString::null : name->label(); + pms.isarch = (name->sourceType() == PM_CONTEXT_LOCAL) ? 2 : + ((name->sourceType() == PM_CONTEXT_ARCHIVE) ? 1 : 0); + pms.source = strdup((const char *)name->sourceName().toAscii()); + pms.metric = strdup((const char *)name->metricName().toAscii()); + if (!pms.source || !pms.metric) + nomem(); + if (name->isInst()) { + pms.ninst = 1; + pms.inst[0] = strdup((const char *)name->metricInstance().toAscii()); + if (!pms.inst[0]) + nomem(); + } + else { + pms.ninst = 0; + pms.inst[0] = NULL; + } + cp->setStyle(style); + int m = cp->addItem(&pms, label); + if (m < 0) { + QString msg; + if (pms.inst[0] != NULL) + msg.sprintf("Error:\nFailed to plot metric \"%s[%s]\" for\n%s %s:\n", + pms.metric, pms.inst[0], + pms.isarch ? "archive" : "host", + pms.source); + else + msg.sprintf("Error:\nFailed to plot metric \"%s\" for\n%s %s:\n", + pms.metric, pms.isarch ? "archive" : "host", + pms.source); + if (m == PM_ERR_CONV) { + msg.append("Units for this metric are not compatible with other plots in this chart"); + } + else + msg.append(pmErrStr(m)); + QMessageBox::critical(pmchart, pmProgname, msg); + } + else { + cp->setStroke(m, style, name->currentColor()); + cp->setLabel(m, name->label()); + } + + if (pms.ninst == 1) + free(pms.inst[0]); + free(pms.metric); + free(pms.source); +} + +void ChartDialog::deleteChartPlot(Chart *cp, int m) +{ + cp->removeItem(m); +} + +void ChartDialog::setCurrentScheme(QString scheme) +{ + my.scheme = scheme; + setupSchemeComboBox(); +} + +void ChartDialog::setupSchemeComboBox() +{ + int index = 0; + + colorSchemeComboBox->blockSignals(true); + colorSchemeComboBox->clear(); + colorSchemeComboBox->addItem("Default Scheme"); + colorSchemeComboBox->addItem("New Scheme"); + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) { + QString name = globalSettings.colorSchemes[i].name(); + if (name == my.scheme) + index = i + 2; + colorSchemeComboBox->addItem(name); + } + colorSchemeComboBox->setCurrentIndex(index); + colorSchemeComboBox->blockSignals(false); +} + +void ChartDialog::colorSchemeComboBox_currentIndexChanged(int index) +{ + if (index == 0) + my.scheme = QString::null; + else if (index == 1) + pmchart->newScheme(); + else + my.scheme = colorSchemeComboBox->itemText(index); +} diff --git a/src/pmchart/chartdialog.h b/src/pmchart/chartdialog.h new file mode 100644 index 0000000..19f8673 --- /dev/null +++ b/src/pmchart/chartdialog.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2013-2014, Red Hat. + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef CHARTDIALOG_H +#define CHARTDIALOG_H + +#include "ui_chartdialog.h" + +class ChartDialog : public QDialog, public Ui::ChartDialog +{ + Q_OBJECT + +public: + ChartDialog(QWidget* parent); + + virtual void init(); + virtual void reset(); + virtual void reset(Chart *); + virtual void enableUi(); + + virtual Chart *chart(void); + virtual QString title(void); + virtual bool legend(void); + virtual bool antiAliasing(void); + virtual void scale(bool *, double *, double *); + virtual void setScale(bool, double, double); + virtual bool rateConvert(); + virtual void setRateConvert(bool); + virtual void scheme(QString *, int *); + virtual void setScheme(QString, int); + virtual void setHsv(int h, int s, int v); + virtual QRgb currentColor(); + virtual void setCurrentColor(QRgb); + virtual void showCurrentColor(); + virtual void setCurrentScheme(QString); + virtual void setupSchemeComboBox(); + virtual void setupChartMetricsTree(); + virtual void setupAvailableMetricsTree(bool); + + virtual void updateChartPlots(Chart *); + +public slots: + virtual void buttonOk_clicked(); + virtual void buttonApply_clicked(); + virtual void chartMetricsItemSelectionChanged(); + virtual void availableMetricsItemSelectionChanged(); + virtual void availableMetricsItemExpanded(QTreeWidgetItem *); + virtual void availableMetricsTreeWidget_doubleClicked(QModelIndex); + virtual void metricInfoButtonClicked(); + virtual void metricSearchButtonClicked(); + virtual void metricDeleteButtonClicked(); + virtual void metricAddButtonClicked(); + virtual void archiveButtonClicked(); + virtual void hostButtonClicked(); + virtual void sourceButtonClicked(); + virtual void legendOnClicked(); + virtual void legendOffClicked(); + virtual void antiAliasingOnClicked(); + virtual void antiAliasingOffClicked(); + virtual void autoScaleOnClicked(); + virtual void autoScaleOffClicked(); + virtual void rateConvertClicked(); + virtual void antiAliasingAutoClicked(); + virtual void yAxisMinimumValueChanged(double); + virtual void yAxisMaximumValueChanged(double); + virtual void newColor(QColor); + virtual void newColorTypedIn(QRgb); + virtual void applyColorButtonClicked(); + virtual void revertColorButtonClicked(); + virtual void newHsv(int, int, int); + virtual void setRgb(QRgb); + virtual void rgbEd(); + virtual void hsvEd(); + virtual void plotLabelLineEdit_editingFinished(); + virtual void colorSchemeComboBox_currentIndexChanged(int); + +Q_SIGNALS: + void newCol(QRgb); + +protected slots: + virtual void languageChange(); + +private: + struct { + bool archiveSource; + + bool chartTreeSelected; + QTreeWidgetItem *chartTreeSingleSelected; + + bool availableTreeSelected; + QTreeWidgetItem *availableTreeSingleSelected; + + double yMin; + double yMax; + Chart *chart; + bool rateConvert; + int sequence; + QString scheme; + + int hue; + int sat; + int val; + QRgb currentColor; + } my; + + void resetCompletely(); + void resetPartially(Chart *); + bool validate(QString &, int &); + void setupChartPlots(Chart *); + bool setupChartPlotsShortcut(Chart *); + bool matchChartPlot(Chart *, NameSpace *, int); + bool existsChartPlot(Chart *, NameSpace *, int *); + void changeChartPlot(Chart *, NameSpace *, int); + void createChartPlot(Chart *, NameSpace *); + void deleteChartPlot(Chart *, int); + void deleteChartPlots(Chart *); +}; + +#endif // CHARTDIALOG_H diff --git a/src/pmchart/chartdialog.ui b/src/pmchart/chartdialog.ui new file mode 100644 index 0000000..192c451 --- /dev/null +++ b/src/pmchart/chartdialog.ui @@ -0,0 +1,2249 @@ +<ui version="4.0" > + <class>ChartDialog</class> + <widget class="QDialog" name="ChartDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>612</width> + <height>457</height> + </rect> + </property> + <property name="focusPolicy" > + <enum>Qt::WheelFocus</enum> + </property> + <property name="windowTitle" > + <string>New Chart</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/pmchart.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="1" > + <widget class="QTabWidget" name="tabWidget" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="currentIndex" > + <number>0</number> + </property> + <widget class="QWidget" name="Widget2" > + <attribute name="title" > + <string>Chart</string> + </attribute> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="2" column="1" > + <widget class="QComboBox" name="colorSchemeComboBox" > + <property name="whatsThis" > + <string>Drop down llist of available color schemes. The color scheme determines the initial color for each new plot added to a chart.</string> + </property> + <item> + <property name="text" > + <string>Default Scheme</string> + </property> + </item> + <item> + <property name="text" > + <string>New Scheme...</string> + </property> + </item> + </widget> + </item> + <item row="1" column="1" > + <widget class="QComboBox" name="typeComboBox" > + <property name="whatsThis" > + <string>Drop down list of chart styles, specifying how the values in a chart should be displayed</string> + </property> + <item> + <property name="text" > + <string>Line Plot</string> + </property> + </item> + <item> + <property name="text" > + <string>Bar Plot</string> + </property> + </item> + <item> + <property name="text" > + <string>Stacked Bar</string> + </property> + </item> + <item> + <property name="text" > + <string>Area Plot</string> + </property> + </item> + <item> + <property name="text" > + <string>Utilization</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0" > + <widget class="QLabel" name="colorSchemeLabel" > + <property name="whatsThis" > + <string>Drop down llist of available color schemes. The color scheme determines the initial color for each new plot added to a chart.</string> + </property> + <property name="text" > + <string>Colors:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="legendOn" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string> + </property> + <property name="text" > + <string>On</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="legendOff" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string> + </property> + <property name="text" > + <string>Off</string> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="3" column="0" > + <widget class="QLabel" name="legendTextLabel" > + <property name="whatsThis" > + <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string> + </property> + <property name="text" > + <string>Legend:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="4" column="0" > + <widget class="QLabel" name="antiAliasingTextLabel" > + <property name="whatsThis" > + <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string> + </property> + <property name="text" > + <string>Antialiasing:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="4" column="1" > + <layout class="QHBoxLayout" > + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="antiAliasingAuto" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Fixed" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>Enable or disable chart rendering with antialiasing automatically, based on the chart style chosen. Line plots will be drawn anti-aliased as this gives a color exactly matching that requested, and bar plots are drawn anti-aliased so their outlines are correctly rendered.</string> + </property> + <property name="text" > + <string>Auto</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>14</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="antiAliasingOn" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string> + </property> + <property name="text" > + <string>On</string> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="antiAliasingOff" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string> + </property> + <property name="text" > + <string>Off</string> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>5</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="1" > + <widget class="QLineEdit" name="titleLineEdit" > + <property name="whatsThis" > + <string>Add or modifiy the chart title (description)</string> + </property> + </widget> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="titleTextLabel" > + <property name="whatsThis" > + <string>Add or modifiy the chart title (description)</string> + </property> + <property name="text" > + <string>Title:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="styleLabel" > + <property name="whatsThis" > + <string>Drop down list of chart styles, specifying how the values in a chart should be displayed</string> + </property> + <property name="text" > + <string>Style:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="yAxisGroupBox" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" > + <string>When enabled, autoscaling causes the Values axis to be scaled based on the minimum and maximum observed values. When disabled, the minimum and maximum points on the Values Axis specified here are always used (even if the values observed lie outside that range).</string> + </property> + <property name="title" > + <string>Values Axis</string> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="2" column="0" > + <widget class="QLabel" name="maxTextLabel" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="text" > + <string>Maximum:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="1" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QDoubleSpinBox" name="yAxisMinimum" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="maximum" > + <double>1000000000.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="minTextLabel" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="text" > + <string>Minimum:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="1" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="autoScaleOn" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>On</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="autoScaleOff" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>Off</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="2" column="1" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QDoubleSpinBox" name="yAxisMaximum" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="maximum" > + <double>1000000000.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="autoScale" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>Auto-Scale:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QCheckBox" name="rateConvertCheckBox" > + <property name="text" > + <string>Rate convert counter metrics</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="Widget3" > + <attribute name="title" > + <string>Metrics</string> + </attribute> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="1" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QTreeWidget" name="availableMetricsTreeWidget" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor" > + <cursor>0</cursor> + </property> + <property name="mouseTracking" > + <bool>true</bool> + </property> + <property name="whatsThis" > + <string>Complete list of metrics and instances which can be added to a new or current chart</string> + </property> + <property name="frameShape" > + <enum>QFrame::StyledPanel</enum> + </property> + <property name="verticalScrollBarPolicy" > + <enum>Qt::ScrollBarAsNeeded</enum> + </property> + <property name="alternatingRowColors" > + <bool>true</bool> + </property> + <property name="selectionMode" > + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="rootIsDecorated" > + <bool>false</bool> + </property> + <property name="uniformRowHeights" > + <bool>true</bool> + </property> + <property name="columnCount" > + <number>1</number> + </property> + <column> + <property name="text" > + <string>Available Metrics</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QLineEdit" name="availableMetricLineEdit" > + <property name="font" > + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="whatsThis" > + <string>Complete metric name of the currently selected metric in the Available Metrics list</string> + </property> + <property name="text" > + <string/> + </property> + <property name="readOnly" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="metricInfoButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Metric Info</string> + </property> + <property name="whatsThis" > + <string>Display information about a metric selected from either of the Chart Plots or Available Metrics lists. This includes metric descriptor, help text (if available), and value at the current time position.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/help-browser.png</iconset> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="metricSearchButton" > + <property name="enabled" > + <bool>true</bool> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Metric Search</string> + </property> + <property name="whatsThis" > + <string>Search for metrics using a regular expression on metric/instance names.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/system-search.png</iconset> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="metricDeleteButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Remove plot from chart</string> + </property> + <property name="whatsThis" > + <string>Remove the current selected plot(s) from the Chart Plots list. When OK is pressed, these plots will be removed from the current chart.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/process-stop.png</iconset> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="metricAddButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Insert plot into chart</string> + </property> + <property name="whatsThis" > + <string>Add a new plot into the Chart Plots list. When OK is pressed, this plot will be added to the new or current chart.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>41</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="sourceButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Add new host</string> + </property> + <property name="whatsThis" > + <string>Add new metric source (host or archive) from which additional Available Metrics can be found for plotting.</string> + </property> + <property name="text" > + <string/> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>51</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="TabPage" > + <attribute name="title" > + <string>Plots</string> + </attribute> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="revertColorButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Revert to previous color</string> + </property> + <property name="statusTip" > + <string>Change back to the previous plot color, as displayed in the frame below.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/go-jump.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QedColorShowLabel" name="revertColorLabel" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="frameShape" > + <enum>QFrame::Panel</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth" > + <number>2</number> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>8</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QedColorShowLabel" name="applyColorLabel" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="frameShape" > + <enum>QFrame::Panel</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth" > + <number>2</number> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="applyColorButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolTip" > + <string>Apply new plot color</string> + </property> + <property name="whatsThis" > + <string>Change the color of the currently selected plot in the Chart Plots list.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QedColorPicker" name="colorPicker" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>176</width> + <height>176</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>176</width> + <height>176</height> + </size> + </property> + <property name="frameShape" > + <enum>QFrame::Panel</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth" > + <number>2</number> + </property> + </widget> + </item> + <item> + <widget class="QedColorLuminancePicker" native="1" name="luminancePicker" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>24</width> + <height>176</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>24</width> + <height>176</height> + </size> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="2" > + <widget class="QLabel" name="saturationTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Saturation</string> + </property> + <property name="text" > + <string>S:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="redTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Red</string> + </property> + <property name="text" > + <string>R:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="1" > + <widget class="QedColSpinBox" name="rEd" > + <property name="toolTip" > + <string>Red</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="1" column="5" > + <widget class="QedColSpinBox" name="bEd" > + <property name="toolTip" > + <string>Blue</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="hueTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Hue</string> + </property> + <property name="text" > + <string>H:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="3" > + <widget class="QedColSpinBox" name="gEd" > + <property name="toolTip" > + <string>Green</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="1" column="4" > + <widget class="QLabel" name="blueTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Blue</string> + </property> + <property name="text" > + <string>B:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="2" > + <widget class="QLabel" name="greenTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Green</string> + </property> + <property name="text" > + <string>G:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="3" > + <widget class="QedColSpinBox" name="sEd" > + <property name="toolTip" > + <string>Saturation</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QedColSpinBox" name="hEd" > + <property name="toolTip" > + <string>Hue</string> + </property> + <property name="whatsThis" > + <string/> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="0" column="5" > + <widget class="QedColSpinBox" name="vEd" > + <property name="toolTip" > + <string>Value</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item row="0" column="4" > + <widget class="QLabel" name="valueTextLabel" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip" > + <string>Value</string> + </property> + <property name="text" > + <string>V:</string> + </property> + <property name="alignment" > + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="1" column="0" > + <widget class="QLabel" name="plotLabelLabel" > + <property name="toolTip" > + <string>Label to use in chart legend for the currently selected Chart Plot.</string> + </property> + <property name="whatsThis" > + <string>Label to use in chart legend for the currently selected Chart Plot.</string> + </property> + <property name="text" > + <string>Plot Label:</string> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QedColLineEdit" name="colorLineEdit" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip" > + <string>Color specification string (e.g. RGB hex value)</string> + </property> + <property name="whatsThis" > + <string>Directly edit the color specification string (e.g. RGB hex value) using this text box. This color can then be used to set the color of the currently selected plot in the Chart Plots list using the left arrow button above.</string> + </property> + </widget> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="plotColorLabel" > + <property name="toolTip" > + <string>Color specification string (e.g. RGB hex value)</string> + </property> + <property name="whatsThis" > + <string>Directly edit the color specification string (e.g. RGB hex value) using this text box. This color can then be used to set the color of the currently selected plot in the Chart Plots list using the left arrow button above.</string> + </property> + <property name="text" > + <string>Plot Color:</string> + </property> + </widget> + </item> + <item row="0" column="2" > + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1" colspan="2" > + <widget class="QLineEdit" name="plotLabelLineEdit" > + <property name="toolTip" > + <string>Label to use in chart legend for the currently selected Chart Plot.</string> + </property> + <property name="whatsThis" > + <string>Label to use in chart legend for the currently selected Chart Plot.</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + <item row="0" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QTreeWidget" name="chartMetricsTreeWidget" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor" > + <cursor>0</cursor> + </property> + <property name="mouseTracking" > + <bool>true</bool> + </property> + <property name="whatsThis" > + <string>List of plots for either a new chart or the currently selected chart</string> + </property> + <property name="frameShape" > + <enum>QFrame::StyledPanel</enum> + </property> + <property name="verticalScrollBarPolicy" > + <enum>Qt::ScrollBarAsNeeded</enum> + </property> + <property name="alternatingRowColors" > + <bool>true</bool> + </property> + <property name="selectionMode" > + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="rootIsDecorated" > + <bool>false</bool> + </property> + <column> + <property name="text" > + <string>Chart Plots</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QLineEdit" name="chartMetricLineEdit" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font" > + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="whatsThis" > + <string>Displays the full metric name of the currently selected chart plot</string> + </property> + <property name="text" > + <string/> + </property> + <property name="readOnly" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" colspan="2" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonApply" > + <property name="text" > + <string>&Apply</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QedColorShowLabel</class> + <extends>QFrame</extends> + <header>qed_colorpicker.h</header> + </customwidget> + <customwidget> + <class>QedColLineEdit</class> + <extends>QLineEdit</extends> + <header>qed_colorpicker.h</header> + </customwidget> + <customwidget> + <class>QedColSpinBox</class> + <extends>QSpinBox</extends> + <header>qed_colorpicker.h</header> + </customwidget> + <customwidget> + <class>QedColorLuminancePicker</class> + <extends>QWidget</extends> + <header>qed_colorpicker.h</header> + </customwidget> + <customwidget> + <class>QedColorPicker</class> + <extends>QFrame</extends> + <header>qed_colorpicker.h</header> + </customwidget> + </customwidgets> + <includes> + <include location="local" >chart.h</include> + <include location="local" >namespace.h</include> + <include location="local" >qed_colorpicker.h</include> + </includes> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonApply</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>buttonApply_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>buttonOk_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>metricInfoButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>metricInfoButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>metricDeleteButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>metricDeleteButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>metricAddButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>metricAddButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sourceButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>sourceButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>applyColorButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>applyColorButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>revertColorButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>revertColorButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>legendOn</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>legendOnClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>legendOff</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>legendOffClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>autoScaleOn</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>autoScaleOnClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>autoScaleOff</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>autoScaleOffClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>yAxisMinimum</sender> + <signal>valueChanged(double)</signal> + <receiver>ChartDialog</receiver> + <slot>yAxisMinimumValueChanged(double)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>yAxisMaximum</sender> + <signal>valueChanged(double)</signal> + <receiver>ChartDialog</receiver> + <slot>yAxisMaximumValueChanged(double)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>rateConvertCheckBox</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>rateConvertClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>plotLabelLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>ChartDialog</receiver> + <slot>plotLabelLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel" > + <x>471</x> + <y>295</y> + </hint> + <hint type="destinationlabel" > + <x>302</x> + <y>213</y> + </hint> + </hints> + </connection> + <connection> + <sender>metricSearchButton</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>metricSearchButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>303</x> + <y>118</y> + </hint> + <hint type="destinationlabel" > + <x>302</x> + <y>213</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorSchemeComboBox</sender> + <signal>currentIndexChanged(int)</signal> + <receiver>ChartDialog</receiver> + <slot>colorSchemeComboBox_currentIndexChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>464</x> + <y>131</y> + </hint> + <hint type="destinationlabel" > + <x>302</x> + <y>213</y> + </hint> + </hints> + </connection> + <connection> + <sender>availableMetricsTreeWidget</sender> + <signal>doubleClicked(QModelIndex)</signal> + <receiver>ChartDialog</receiver> + <slot>availableMetricsTreeWidget_doubleClicked(QModelIndex)</slot> + <hints> + <hint type="sourcelabel" > + <x>334</x> + <y>48</y> + </hint> + <hint type="destinationlabel" > + <x>305</x> + <y>213</y> + </hint> + </hints> + </connection> + <connection> + <sender>antiAliasingOn</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>antiAliasingOnClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>425</x> + <y>194</y> + </hint> + <hint type="destinationlabel" > + <x>305</x> + <y>228</y> + </hint> + </hints> + </connection> + <connection> + <sender>antiAliasingOff</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>antiAliasingOffClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>492</x> + <y>194</y> + </hint> + <hint type="destinationlabel" > + <x>305</x> + <y>228</y> + </hint> + </hints> + </connection> + <connection> + <sender>antiAliasingAuto</sender> + <signal>clicked()</signal> + <receiver>ChartDialog</receiver> + <slot>antiAliasingAutoClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>560</x> + <y>194</y> + </hint> + <hint type="destinationlabel" > + <x>305</x> + <y>228</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/colorbutton.cpp b/src/pmchart/colorbutton.cpp new file mode 100644 index 0000000..66826cf --- /dev/null +++ b/src/pmchart/colorbutton.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "colorbutton.h" +#include <QtGui/QPainter> +#include <QtGui/QColorDialog> +#include <QtGui/QPaintEvent> + +ColorButton::ColorButton(QWidget* parent) : QToolButton(parent) +{ + my.modified = false; + my.color = Qt::white; +} + +bool ColorButton::isSet() +{ + return my.color != Qt::white; +} + +void ColorButton::paintEvent(QPaintEvent *e) +{ + QToolButton::paintEvent(e); + QPainter p(this); + QRect rect = contentsRect()&e->rect(); + p.fillRect(rect.adjusted(+4,+5,-4,-5), my.color); +} + +void ColorButton::clicked() +{ + QColor color = QColorDialog::getColor(my.color); + if (color.isValid()) { + my.color = color; + my.modified = true; + update(); + } +} diff --git a/src/pmchart/colorbutton.h b/src/pmchart/colorbutton.h new file mode 100644 index 0000000..e992531 --- /dev/null +++ b/src/pmchart/colorbutton.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef COLORBUTTON_H +#define COLORBUTTON_H + +#include <QtGui/QToolButton> + +class ColorButton : public QToolButton +{ + Q_OBJECT + +public: + ColorButton(QWidget* parent); + + bool isSet(); + bool isModified() { return my.modified; } + + QColor color() { return my.color; } + void setColor(QColor color) { my.color = color; update(); } + +public slots: + virtual void clicked(); + virtual void paintEvent(QPaintEvent *); + +private: + struct { + QColor color; + bool modified; + } my; +}; + +#endif // COLORBUTTON_H diff --git a/src/pmchart/colorscheme.cpp b/src/pmchart/colorscheme.cpp new file mode 100644 index 0000000..b16b399 --- /dev/null +++ b/src/pmchart/colorscheme.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * Copyright (c) 2013, Red Hat, Inc. + * + * 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. + */ + +#include "colorscheme.h" +#include "main.h" + +ColorScheme::ColorScheme() +{ + my.isModified = false; + my.name = QString::null; +} + +bool ColorScheme::lookupScheme(QString name) +{ + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) + if (name == globalSettings.colorSchemes[i].name()) + return true; + return false; +} + +ColorScheme *ColorScheme::findScheme(QString name) +{ + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) + if (name == globalSettings.colorSchemes[i].name()) + return &globalSettings.colorSchemes[i]; + return NULL; +} + +bool ColorScheme::removeScheme(QString name) +{ + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) + if (name == globalSettings.colorSchemes[i].name()) { + globalSettings.colorSchemes.removeAt(i); + return true; + } + return false; +} + +static inline int hexval(float f) +{ + return ((int)(0.5 + f*256) < 256 ? (int)(0.5 + f*256) : 256); +} + +QColor ColorScheme::colorSpec(QString name) +{ + QColor color; + QString rgbi = name; + + if (rgbi.left(5) != "rgbi:") + color.setNamedColor(name); + else { + float fr, fg, fb; + if (sscanf((const char *)rgbi.toAscii(), "rgbi:%f/%f/%f", &fr, &fg, &fb) == 3) + color.setRgb(hexval(fr), hexval(fg), hexval(fb)); + // else return color as-is, i.e. invalid. + } + return color; +} + +void ColorScheme::clear() +{ + my.colors.clear(); + my.colorNames.clear(); +} + +void ColorScheme::setColorNames(QStringList colorNames) +{ + my.colorNames = colorNames; + for (int i = 0; i < colorNames.size(); i++) + my.colors << QColor(colorNames.at(i)); +} + +void ColorScheme::addColor(QColor color) +{ + my.colors.append(color); + my.colorNames.append(color.name()); +} + +void ColorScheme::addColor(QString name) +{ + my.colors.append(colorSpec(name)); + my.colorNames.append(name); +} diff --git a/src/pmchart/colorscheme.h b/src/pmchart/colorscheme.h new file mode 100644 index 0000000..972a305 --- /dev/null +++ b/src/pmchart/colorscheme.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef COLORSCHEME_H +#define COLORSCHEME_H + +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtGui/QColor> + +class ColorScheme +{ +public: + ColorScheme(); + + QString name() { return my.name; } + int size() { return my.colors.size(); } + QColor color(int i) { return my.colors.at(i); } + QString colorName(int i) { return my.colorNames.at(i); } + + QList<QColor> colors() { return my.colors; } + QStringList colorNames() { return my.colorNames; } + + void setName(QString name) { my.name = name; } + void setModified(bool modified) { my.isModified = modified; } + + void clear(); + void addColor(QString name); + void addColor(QColor color); + void setColorNames(QStringList); + + static ColorScheme *findScheme(QString); + static bool lookupScheme(QString); // search in global list + static bool removeScheme(QString); // remove from global list + + static QColor colorSpec(QString); // QT color / convert rgbi: + +private: + struct { + QString name; + bool isModified; + QList<QColor> colors; + QStringList colorNames; + } my; +}; + +#endif diff --git a/src/pmchart/exportdialog.cpp b/src/pmchart/exportdialog.cpp new file mode 100644 index 0000000..bc99672 --- /dev/null +++ b/src/pmchart/exportdialog.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * Copyright (c) 2013, Red Hat, Inc. + * + * 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. + */ +#include <QtCore/QDir> +#include <QtGui/QPainter> +#include <QtGui/QMessageBox> +#include <QtGui/QImageWriter> +#include "main.h" +#include "exportdialog.h" + +// ExportFileDialog is the one which is displayed when you click on +// the image file selection push button +ExportFileDialog::ExportFileDialog(QWidget *parent) : QFileDialog(parent) +{ + setAcceptMode(QFileDialog::AcceptSave); + setFileMode(QFileDialog::AnyFile); + setIconProvider(fileIconProvider); + setConfirmOverwrite(true); +} + +ExportDialog::ExportDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + init(); +} + +ExportDialog::~ExportDialog() +{ + free(my.format); +} + +void ExportDialog::languageChange() +{ + retranslateUi(this); +} + +void ExportDialog::init() +{ + QChar sep(__pmPathSeparator()); + QString imgfile = QDir::toNativeSeparators(QDir::homePath()); + + my.quality = 0; + my.format = strdup("png"); + imgfile.append(sep); + imgfile.append("export.png"); + fileLineEdit->setText(imgfile); + + int png = 0; + QStringList formats; + QList<QByteArray> array = QImageWriter::supportedImageFormats(); + for (int i = 0; i < array.size(); i++) { + if (array.at(i) == "png") + png = i; + formats << QString(array.at(i)); + } + formatComboBox->blockSignals(true); + formatComboBox->addItems(formats); + formatComboBox->setCurrentIndex(png); + formatComboBox->blockSignals(false); + + qualitySlider->setValue(100); + qualitySlider->setRange(0, 100); + qualitySpinBox->setValue(100); + qualitySpinBox->setRange(0, 100); +} + +void ExportDialog::reset() +{ + QSize size = imageSize(); + widthSpinBox->setValue(size.width()); + heightSpinBox->setValue(size.height()); +} + +void ExportDialog::selectedRadioButton_clicked() +{ + selectedRadioButton->setChecked(true); + allChartsRadioButton->setChecked(false); + reset(); +} + +void ExportDialog::allChartsRadioButton_clicked() +{ + selectedRadioButton->setChecked(false); + allChartsRadioButton->setChecked(true); + reset(); +} + +void ExportDialog::quality_valueChanged(int value) +{ + if (value != my.quality) { + my.quality = value; + displayQualitySpinBox(); + displayQualitySlider(); + } +} + +void ExportDialog::displayQualitySpinBox() +{ + qualitySpinBox->blockSignals(true); + qualitySpinBox->setValue(my.quality); + qualitySpinBox->blockSignals(false); +} + +void ExportDialog::displayQualitySlider() +{ + qualitySlider->blockSignals(true); + qualitySlider->setValue(my.quality); + qualitySlider->blockSignals(false); +} + +void ExportDialog::filePushButton_clicked() +{ + ExportFileDialog file(this); + + file.setDirectory(QDir::toNativeSeparators(QDir::homePath())); + if (file.exec() == QDialog::Accepted) + fileLineEdit->setText(file.selectedFiles().at(0)); +} + +void ExportDialog::formatComboBox_currentIndexChanged(QString suffix) +{ + char *format = strdup((const char *)suffix.toAscii()); + QString file = fileLineEdit->text().trimmed(); + QString regex = my.format; + + regex.append("$"); + file.replace(QRegExp(regex), suffix); + fileLineEdit->setText(file); + free(my.format); + my.format = format; +} + +QSize ExportDialog::imageSize() +{ + Tab *tab = pmchart->activeTab(); + int height = 0, width = 0; + + for (int i = 0; i < tab->gadgetCount(); i++) { + Gadget *gadget = tab->gadget(i); + if (gadget != tab->currentGadget() && selectedRadioButton->isChecked()) + continue; + width = qMax(width, gadget->width()); + height += gadget->height(); + } + height += pmchart->timeAxis()->height() + pmchart->dateLabel()->height(); + height -= TIMEAXISFUDGE; + + return QSize(width, height); +} + +void ExportDialog::flush() +{ + QString file = fileLineEdit->text().trimmed(); + int width = widthSpinBox->value(); + int height = heightSpinBox->value(); + bool everything = allChartsRadioButton->isChecked(); + bool transparent = transparentCheckBox->isChecked(); + + if (ExportDialog::exportFile(file, my.format, my.quality, width, height, + transparent, everything) == false) { + QString message = tr("Failed to save image file\n"); + message.append(file); + QMessageBox::warning(this, pmProgname, message, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } +} + +bool ExportDialog::exportFile(QString &file, const char *format, int quality, + int width, int height, bool transparent, bool everything) +{ + enum QImage::Format rgbFormat = transparent ? + QImage::Format_ARGB32 : QImage::Format_RGB32; + QImage image(width, height, rgbFormat); + QPainter qp(&image); + + console->post("ExportDialog::exportFile file=%s fmt=%s qual=%d w=%d h=%d trans=%d every=%d\n", + (const char *)file.toAscii(), format, quality, width, height, transparent, everything); + + if (transparent) { + image.fill(qRgba(255, 255, 255, 0)); + } + else { + image.fill(qRgba(255, 255, 255, 255)); + } + + pmchart->painter(&qp, width, height, transparent, everything == false); + QImageWriter writer(file, format); + writer.setQuality(quality); + bool sts = writer.write(image); + if (!sts) + fprintf(stderr, "%s: error writing %s (%s): %s\n", + pmProgname, (const char *) file.toAscii(), format, + (const char *) writer.errorString().toAscii()); + return sts; +} + +int ExportDialog::exportFile(char *outfile, char *geometry, bool transparent) +{ + QRegExp regex; + QString file(outfile), format; + bool noFormat = false; + char suffix[32]; + int i; + + // Ensure the requested image format is supported, else use GIF + regex.setPattern("\\.([a-z]+)$"); + regex.setCaseSensitivity(Qt::CaseInsensitive); + if (regex.indexIn(file) == 0) { + noFormat = true; + } + else { + format = regex.cap(1); + QList<QByteArray> array = QImageWriter::supportedImageFormats(); + for (i = 0; i < array.size(); i++) { + if (strcmp(array.at(i), (const char *)format.toAscii()) == 0) + break; + } + if (i == array.size()) + noFormat = true; + } + if (noFormat) { + file.append(".png"); + format = QString("png"); + } + strncpy(suffix, (const char *)format.toAscii(), sizeof(suffix)); + suffix[sizeof(suffix)-1] = '\0'; + + regex.setPattern("(\\d+)x(\\d+)"); + if (regex.indexIn(QString(geometry)) != -1) { + QSize fixed = QSize(regex.cap(1).toInt(), regex.cap(2).toInt()); + pmchart->setFixedSize(fixed); + } + + return ExportDialog::exportFile(file, suffix, 100, pmchart->width(), + pmchart->exportHeight(), transparent, true) == false; +} diff --git a/src/pmchart/exportdialog.h b/src/pmchart/exportdialog.h new file mode 100644 index 0000000..705455d --- /dev/null +++ b/src/pmchart/exportdialog.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef EXPORTDIALOG_H +#define EXPORTDIALOG_H + +#include "ui_exportdialog.h" +#include <QtGui/QFileDialog> + +class ExportDialog : public QDialog, public Ui::ExportDialog +{ + Q_OBJECT + +public: + ExportDialog(QWidget* parent); + ~ExportDialog(); + + virtual void init(); + virtual void reset(); + virtual void flush(); + virtual void displayQualitySpinBox(); + virtual void displayQualitySlider(); + + static bool exportFile(QString &file, const char *format, + int quality, int width, int height, + bool transparent, bool everything); + static int exportFile(char *outfile, char *geometry, bool transparent); + +public slots: + virtual void selectedRadioButton_clicked(); + virtual void allChartsRadioButton_clicked(); + virtual void quality_valueChanged(int); + virtual void filePushButton_clicked(); + virtual void formatComboBox_currentIndexChanged(QString); + +protected slots: + virtual void languageChange(); + +private: + QSize imageSize(); + + struct { + int quality; + char *format; + } my; +}; + +class ExportFileDialog : public QFileDialog +{ + Q_OBJECT + +public: + ExportFileDialog(QWidget *); +}; + +#endif // EXPORTDIALOG_H diff --git a/src/pmchart/exportdialog.ui b/src/pmchart/exportdialog.ui new file mode 100644 index 0000000..85cbabe --- /dev/null +++ b/src/pmchart/exportdialog.ui @@ -0,0 +1,463 @@ +<ui version="4.0" > + <class>ExportDialog</class> + <widget class="QDialog" name="ExportDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>418</width> + <height>218</height> + </rect> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>418</width> + <height>218</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>418</width> + <height>218</height> + </size> + </property> + <property name="windowTitle" > + <string>Export</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/document-export.png</iconset> + </property> + <property name="toolTip" > + <string/> + </property> + <property name="sizeGripEnabled" > + <bool>false</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>20</number> + </property> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="formatTextLabel" > + <property name="text" > + <string>Image Format</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="formatComboBox" > + <property name="maxCount" > + <number>32</number> + </property> + <property name="duplicatesEnabled" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="transparentCheckBox" > + <property name="text" > + <string>Transparent Background</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QRadioButton" name="selectedRadioButton" > + <property name="text" > + <string>Selected Chart Only</string> + </property> + <property name="checked" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="allChartsRadioButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>All Charts in Tab</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item row="1" column="1" > + <widget class="QSpinBox" name="heightSpinBox" > + <property name="suffix" > + <string> px</string> + </property> + <property name="maximum" > + <number>999999999</number> + </property> + </widget> + </item> + <item row="2" column="0" > + <widget class="QLabel" name="qualityLabel" > + <property name="text" > + <string>Quality</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="1" > + <widget class="QSpinBox" name="qualitySpinBox" > + <property name="suffix" > + <string>%</string> + </property> + <property name="maximum" > + <number>999999999</number> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2" > + <widget class="QSlider" name="qualitySlider" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="widthLabel" > + <property name="text" > + <string>Width</string> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="heightLabel" > + <property name="text" > + <string>Height</string> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QSpinBox" name="widthSpinBox" > + <property name="suffix" > + <string> px</string> + </property> + <property name="maximum" > + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item row="1" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="fileLabel" > + <property name="text" > + <string>File</string> + </property> + <property name="alignment" > + <set>Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="fileLineEdit" > + <property name="text" > + <string/> + </property> + <property name="alignment" > + <set>Qt::AlignLeading</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="filePushButton" > + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/document-export.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>214</width> + <height>27</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>ExportDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>ExportDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>selectedRadioButton</sender> + <signal>clicked()</signal> + <receiver>ExportDialog</receiver> + <slot>selectedRadioButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>allChartsRadioButton</sender> + <signal>clicked()</signal> + <receiver>ExportDialog</receiver> + <slot>allChartsRadioButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>filePushButton</sender> + <signal>clicked()</signal> + <receiver>ExportDialog</receiver> + <slot>filePushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>qualitySlider</sender> + <signal>valueChanged(int)</signal> + <receiver>ExportDialog</receiver> + <slot>quality_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>298</x> + <y>116</y> + </hint> + <hint type="destinationlabel" > + <x>187</x> + <y>109</y> + </hint> + </hints> + </connection> + <connection> + <sender>qualitySpinBox</sender> + <signal>valueChanged(int)</signal> + <receiver>ExportDialog</receiver> + <slot>quality_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>332</x> + <y>91</y> + </hint> + <hint type="destinationlabel" > + <x>187</x> + <y>109</y> + </hint> + </hints> + </connection> + <connection> + <sender>formatComboBox</sender> + <signal>currentIndexChanged(QString)</signal> + <receiver>ExportDialog</receiver> + <slot>formatComboBox_currentIndexChanged(QString)</slot> + <hints> + <hint type="sourcelabel" > + <x>145</x> + <y>25</y> + </hint> + <hint type="destinationlabel" > + <x>187</x> + <y>109</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/gadget.cpp b/src/pmchart/gadget.cpp new file mode 100644 index 0000000..0646ffa --- /dev/null +++ b/src/pmchart/gadget.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014, Red Hat. + * Copyright (c) 2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include "gadget.h" + +Gadget::Gadget(QWidget *widget) +{ + my.widget = widget; +} + +QStringList Gadget::hosts() +{ + QStringList hosts; + + for (int m = 0; m < metricCount(); m++) { + if (activeMetric(m) == false) + continue; + QString host = metricContext(m)->source().host(); + if (!hosts.contains(host)) + hosts.append(host); + } + return hosts; +} + +QString Gadget::pmloggerMetricSyntax(int m) +{ + QmcMetric *mp = metric(m); + QString config = mp->name(); + + if (mp->numInst() == 1) { + config.append(" [ \""); + config.append(metricInstance(m)); + config.append("\" ]"); + } + return config; +} + +QString Gadget::pmloggerSyntax() +{ + QString config; + bool beDiscrete = false; + bool nonDiscrete = false; + + // discover whether we need separate log-once/log-every sections + for (int m = 0; m < metricCount(); m++) { + if (activeMetric(m) == false) + continue; + if (metricDesc(m)->desc().sem == PM_SEM_DISCRETE) + beDiscrete = true; + else + nonDiscrete = true; + } + + if (beDiscrete) { + config.append("log mandatory on once {\n"); + for (int m = 0; m < metricCount(); m++) { + if (activeMetric(m) == false) + continue; + if (metricDesc(m)->desc().sem != PM_SEM_DISCRETE) + continue; + config.append('\t'); + config.append(pmloggerMetricSyntax(m)); + config.append('\n'); + } + config.append("}\n"); + } + if (nonDiscrete) { + config.append("log mandatory on default {\n"); + for (int m = 0; m < metricCount(); m++) { + if (activeMetric(m) == false) + continue; + if (metricDesc(m)->desc().sem == PM_SEM_DISCRETE) + continue; + config.append('\t'); + config.append(pmloggerMetricSyntax(m)); + config.append('\n'); + } + config.append("}\n"); + } + return config; +} diff --git a/src/pmchart/gadget.h b/src/pmchart/gadget.h new file mode 100644 index 0000000..1c62e14 --- /dev/null +++ b/src/pmchart/gadget.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012-2014, Red Hat. + * Copyright (c) 2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef GADGET_H +#define GADGET_H + +#include <QtGui/QColor> +#include <QtGui/QWidget> +#include <QtGui/QPainter> +#include "qmc_metric.h" + +class Gadget +{ +public: + Gadget(QWidget *); + virtual ~Gadget() { } + virtual void resetFont() { } + virtual void setCurrent(bool) { } + virtual void setScheme(QString &s) { my.scheme = s; } + virtual QString scheme() const { return my.scheme; } + + virtual void updateBackground(QColor) { } + virtual void updateValues(bool, bool, int, int, double, double, double) { } + + virtual void resetValues(int, double, double) { } + virtual void adjustValues() { } + virtual void preserveSample(int, int) { } + virtual void punchoutSample(int) { } + + virtual void showWidget() { return my.widget->show(); } + virtual int width() const { return my.widget->width(); } + virtual int height() const { return my.widget->height(); } + virtual QSize size() const { return my.widget->size(); } + + virtual void activateTime(QMouseEvent *) { } + virtual void reactivateTime(QMouseEvent *) { } + virtual void deactivateTime(QMouseEvent *) { } + + virtual void save(FILE *, bool) { } + virtual void print(QPainter *, QRect &, bool) { } + + virtual int metricCount() const { return 0; } + virtual bool activeMetric(int) const { return true; } + virtual QmcMetric *metric(int) const { return NULL; } + virtual QmcDesc *metricDesc(int) const { return NULL; } + virtual QString metricInstance(int) const { return QString::null; } + virtual QmcContext *metricContext(int) const { return NULL; } + + virtual QStringList hosts(); // unique hostnames across all metrics + virtual QString pmloggerSyntax(); // pmlogger config text for all metrics + virtual QString pmloggerMetricSyntax(int); // config text for 1 metric + +private: + struct { + QWidget *widget; + QString scheme; + } my; +}; + +#endif // GADGET_H diff --git a/src/pmchart/groupcontrol.cpp b/src/pmchart/groupcontrol.cpp new file mode 100644 index 0000000..5bac8d3 --- /dev/null +++ b/src/pmchart/groupcontrol.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (c) 2012, Red Hat. + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#include "main.h" +#include "groupcontrol.h" + +#define DESPERATE 0 + +GroupControl::GroupControl() +{ + my.samples = 0; + my.visible = 0; + my.realDelta = 0; + my.realPosition = 0; + my.timeData = NULL; + my.timeState = StartState; + my.buttonState = QedTimeButton::Timeless; + my.pmtimeState = QmcTime::StoppedState; + memset(&my.delta, 0, sizeof(struct timeval)); + memset(&my.position, 0, sizeof(struct timeval)); +} + +void +GroupControl::init(int samples, int visible, + struct timeval *interval, struct timeval *position) +{ + my.samples = samples; + my.visible = visible; + + if (isArchiveSource()) { + my.pmtimeState = QmcTime::StoppedState; + my.buttonState = QedTimeButton::StoppedArchive; + } + else { + my.pmtimeState = QmcTime::ForwardState; + my.buttonState = QedTimeButton::ForwardLive; + } + my.delta = *interval; + my.position = *position; + my.realDelta = tosec(*interval); + my.realPosition = tosec(*position); + + my.timeData = (double *)malloc(samples * sizeof(double)); + for (int i = 0; i < samples; i++) + my.timeData[i] = my.realPosition - (i * my.realDelta); +} + +bool +GroupControl::isArchiveSource(void) +{ + // Note: We purposefully are not using QmcGroup::mode() here, as we + // may not have initialised any contexts yet. In such a case, live + // mode is always returned (default, from the QmcGroup constructor). + + return this == archiveGroup; +} + +bool +GroupControl::isActive(QmcTime::Packet *packet) +{ + return (((activeGroup == archiveGroup) && + (packet->source == QmcTime::ArchiveSource)) || + ((activeGroup == liveGroup) && + (packet->source == QmcTime::HostSource))); +} + +void +GroupControl::addGadget(Gadget *gadget) +{ + my.gadgetsList.append(gadget); +} + +void +GroupControl::deleteGadget(Gadget *gadget) +{ + for (int i = 0; i < gadgetCount(); i++) + if (my.gadgetsList.at(i) == gadget) + my.gadgetsList.removeAt(i); +} + +int +GroupControl::gadgetCount() const +{ + return my.gadgetsList.size(); +} + +void +GroupControl::updateBackground(void) +{ + for (int i = 0; i < gadgetCount(); i++) + my.gadgetsList.at(i)->updateBackground(globalSettings.chartBackground); +} + +void +GroupControl::updateTimeAxis(void) +{ + QString tz, otz, unused; + + if (numContexts() > 0 || isArchiveSource() == false) { + if (numContexts() > 0) + defaultTZ(unused, otz); + else + otz = QmcSource::localHost; + tz = otz; + pmchart->timeAxis()->setAxisScale(QwtPlot::xBottom, + my.timeData[my.visible - 1], my.timeData[0], + pmchart->timeAxis()->scaleValue(my.realDelta, my.visible)); + pmchart->setDateLabel(my.position.tv_sec, tz); + pmchart->timeAxis()->replot(); + } else { + pmchart->timeAxis()->noArchiveSources(); + pmchart->setDateLabel(tr("[No open archives]")); + } + + if (console->logLevel(PmChart::DebugProtocol)) { + int i = my.visible - 1; + console->post(PmChart::DebugProtocol, + "GroupControl::updateTimeAxis: tz=%s; visible points=%d", + (const char *)tz.toAscii(), i); + console->post(PmChart::DebugProtocol, + "GroupControl::updateTimeAxis: first time is %.3f (%s)", + my.timeData[i], timeString(my.timeData[i])); + console->post(PmChart::DebugProtocol, + "GroupControl::updateTimeAxis: final time is %.3f (%s)", + my.timeData[0], timeString(my.timeData[0])); + } +} + +void +GroupControl::updateTimeButton(void) +{ + pmchart->setButtonState(my.buttonState); +} + +QmcTime::State +GroupControl::pmtimeState(void) +{ + return my.pmtimeState; +} + +char * +GroupControl::timeState() +{ + static char buf[16]; + + switch (my.timeState) { + case StartState: strcpy(buf, "Start"); break; + case ForwardState: strcpy(buf, "Forward"); break; + case BackwardState: strcpy(buf, "Backward"); break; + case EndLogState: strcpy(buf, "EndLog"); break; + case StandbyState: strcpy(buf, "Standby"); break; + default: strcpy(buf, "Dodgey"); break; + } + return buf; +} + +void +GroupControl::timeSelectionActive(Gadget *source, int timestamp) +{ + QPoint point(timestamp, -1); + QMouseEvent pressed(QEvent::MouseButtonPress, point, + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + for (int i = 0; i < gadgetCount(); i++) { + Gadget *gadget = my.gadgetsList.at(i); + if (source != gadget) + gadget->activateTime(&pressed); + } +} + +void +GroupControl::timeSelectionReactive(Gadget *source, int timestamp) +{ + QPoint point(timestamp, -1); + QMouseEvent moved(QEvent::MouseMove, point, + Qt::NoButton, Qt::LeftButton, Qt::NoModifier); + for (int i = 0; i < gadgetCount(); i++) { + Gadget *gadget = my.gadgetsList.at(i); + if (source != gadget) + gadget->reactivateTime(&moved); + } +} + +void +GroupControl::timeSelectionInactive(Gadget *source) +{ + QPoint point(-1, -1); + QMouseEvent release(QEvent::MouseButtonRelease, point, + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + for (int i = 0; i < gadgetCount(); i++) { + Gadget *gadget = my.gadgetsList.at(i); + if (source != gadget) + gadget->deactivateTime(&release); + } +} + +// +// Drive all updates into each gadget (refresh the display) +// +void +GroupControl::refreshGadgets(bool active) +{ +#if DESPERATE + for (int s = 0; s < my.samples; s++) + console->post(PmChart::DebugProtocol, + "GroupControl::refreshGadgets: timeData[%2d] is %.2f (%s)", + s, my.timeData[s], timeString(my.timeData[s])); + console->post(PmChart::DebugProtocol, + "GroupControl::refreshGadgets: state=%s", timeState()); +#endif + + for (int i = 0; i < gadgetCount(); i++) { + my.gadgetsList.at(i)->updateValues(my.timeState != BackwardState, + active, my.samples, my.visible, + my.timeData[my.visible - 1], + my.timeData[0], + my.realDelta); + } + if (active) { + updateTimeButton(); + updateTimeAxis(); + } +} + +// +// Setup the initial data needed after opening a view. +// All of the work is in archive mode; in live mode we have +// not yet got any historical data that we can display... +// +void +GroupControl::setupWorldView(void) +{ + if (isArchiveSource() == false) + return; + + QmcTime::Packet packet; + packet.source = QmcTime::ArchiveSource; + packet.state = QmcTime::ForwardState; + packet.mode = QmcTime::NormalMode; + memcpy(&packet.delta, pmtime->archiveInterval(), sizeof(packet.delta)); + memcpy(&packet.position, pmtime->archivePosition(), sizeof(packet.position)); + memcpy(&packet.start, pmtime->archiveStart(), sizeof(packet.start)); + memcpy(&packet.end, pmtime->archiveEnd(), sizeof(packet.end)); + adjustWorldView(&packet, true); +} + +// +// Received a Set or a VCRMode requiring us to adjust our state +// and possibly rethink everything. This can result from a time +// control position change, delta change, direction change, etc. +// +void +GroupControl::adjustWorldView(QmcTime::Packet *packet, bool vcrMode) +{ + my.delta = packet->delta; + my.position = packet->position; + my.realDelta = tosec(packet->delta); + my.realPosition = tosec(packet->position); + + console->post("GroupControl::adjustWorldView: " + "sh=%d vh=%d delta=%.2f position=%.2f (%s) state=%s", + my.samples, my.visible, my.realDelta, my.realPosition, + timeString(my.realPosition), timeState()); + + QmcTime::State state = packet->state; + if (isArchiveSource()) { + if (packet->state == QmcTime::ForwardState) + adjustArchiveWorldViewForward(packet, vcrMode); + else if (packet->state == QmcTime::BackwardState) + adjustArchiveWorldViewBackward(packet, vcrMode); + else + adjustArchiveWorldViewStopped(packet, vcrMode); + } + else if (state != QmcTime::StoppedState) + adjustLiveWorldViewForward(packet); + else + adjustLiveWorldViewStopped(packet); +} + +void +GroupControl::adjustLiveWorldViewStopped(QmcTime::Packet *packet) +{ + if (isActive(packet)) { + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + updateTimeButton(); + } +} + +static bool +fuzzyTimeMatch(double a, double b, double tolerance) +{ + // a matches b if the difference is within 1% of the delta (tolerance) + return (a == b || + (b > a && a + tolerance > b) || + (b < a && a - tolerance < b)); +} + +void +GroupControl::adjustLiveWorldViewForward(QmcTime::Packet *packet) +{ + // + // X-Axis _max_ becomes packet->position. + // Rest of (preceeding) time window filled in using packet->delta. + // In live mode, we can only fetch current data. However, we make + // an effort to keep old data that happens to align with the delta + // time points (or near enough) that we are now interested in. So, + // "oi" is the old index, whereas "i" is the new timeData[] index. + // First we try to find a fuzzy match on current old index, if it + // doesn't exit, we continue moving "oi" until it points at a time + // larger than the one we're after, and then see how that fares on + // the next iteration. + // + int i, oi, last = my.samples - 1; + bool preserve = false; + double tolerance = my.realDelta; + double position = my.realPosition - (my.realDelta * my.samples); + + for (i = oi = last; i >= 0; i--, position += my.realDelta) { + while (i > 0 && my.timeData[oi] < position + my.realDelta && oi > 0) { + if (fuzzyTimeMatch(my.timeData[oi], position, tolerance) == false) { +#if DESPERATE + console->post("NO fuzzyTimeMatch %.3f to %.3f (%s)", + my.timeData[oi], position, timeString(position)); +#endif + if (my.timeData[oi] > position) + break; + oi--; + continue; + } + console->post("Saved live data (oi=%d/i=%d) for %s", oi, i, + timeString(position)); + for (int j = 0; j < gadgetCount(); j++) + my.gadgetsList.at(j)->preserveSample(i, oi); + my.timeData[i] = my.timeData[oi]; + preserve = true; + oi--; + break; + } + + if (i == 0) { // refreshGadgets() finishes up last one + console->post("Fetching data[%d] at %s", i, timeString(position)); + my.timeData[i] = position; + fetch(); + } + else if (preserve == false) { +#if DESPERATE + console->post("No live data for %s", timeString(position)); +#endif + my.timeData[i] = position; + for (int j = 0; j < gadgetCount(); j++) + my.gadgetsList.at(j)->punchoutSample(i); + } + else + preserve = false; + } + // One (per-gadget) recalculation & refresh at the end, after all data moved + for (int j = 0; j < gadgetCount(); j++) + my.gadgetsList.at(j)->adjustValues(); + my.timeState = (packet->state == QmcTime::StoppedState) ? + StandbyState : ForwardState; + + bool active = isActive(packet); + if (active) + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + refreshGadgets(active); +} + +void +GroupControl::adjustArchiveWorldViewForward(QmcTime::Packet *packet, bool setup) +{ + console->post("GroupControl::adjustArchiveWorldViewForward"); + my.timeState = ForwardState; + + int setmode = PM_MODE_INTERP; + int delta = packet->delta.tv_sec; + if (packet->delta.tv_usec == 0) { + setmode |= PM_XTB_SET(PM_TIME_SEC); + } else { + delta = delta * 1000 + packet->delta.tv_usec / 1000; + setmode |= PM_XTB_SET(PM_TIME_MSEC); + } + + // + // X-Axis _max_ becomes packet->position. + // Rest of (preceeding) time window filled in using packet->delta. + // + int last = my.samples - 1; + double tolerance = my.realDelta / 20.0; // 5% of the sample interval + double position = my.realPosition - (my.realDelta * last); + + double left = position; + double right = my.realPosition; + double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible); + + for (int i = last; i >= 0; i--, position += my.realDelta) { + if (setup == false && + fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) { + continue; + } + + my.timeData[i] = position; + + struct timeval timeval; + fromsec(position, &timeval); + setArchiveMode(setmode, &timeval, delta); + console->post("Fetching data[%d] at %s", i, timeString(position)); + fetch(); + if (i == 0) // refreshGadgets() finishes up last one + break; + console->post("GroupControl::adjustArchiveWorldViewForward: " + "setting time position[%d]=%.2f[%s] state=%s count=%d", + i, position, timeString(position), + timeState(), gadgetCount()); + for (int j = 0; j < gadgetCount(); j++) + my.gadgetsList.at(j)->updateValues(true, false, my.samples, my.visible, + left, right, interval); + } + + bool active = isActive(packet); + if (setup) + packet->state = QmcTime::StoppedState; + if (active) + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + pmtime->setArchivePosition(&packet->position); + pmtime->setArchiveInterval(&packet->delta); + refreshGadgets(active); +} + +void +GroupControl::adjustArchiveWorldViewBackward(QmcTime::Packet *packet, bool setup) +{ + console->post("GroupControl::adjustArchiveWorldViewBackward"); + my.timeState = BackwardState; + + int setmode = PM_MODE_INTERP; + int delta = packet->delta.tv_sec; + if (packet->delta.tv_usec == 0) { + setmode |= PM_XTB_SET(PM_TIME_SEC); + } else { + delta = delta * 1000 + packet->delta.tv_usec / 1000; + setmode |= PM_XTB_SET(PM_TIME_MSEC); + } + + // + // X-Axis _min_ becomes packet->position. + // Rest of (following) time window filled in using packet->delta. + // + int last = my.samples - 1; + double tolerance = my.realDelta / 20.0; // 5% of the sample interval + double position = my.realPosition; + + double left = position - (my.realDelta * last); + double right = position; + double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible); + + for (int i = 0; i <= last; i++, position -= my.realDelta) { + if (setup == false && + fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) { + continue; + } + + my.timeData[i] = position; + + struct timeval timeval; + fromsec(position, &timeval); + setArchiveMode(setmode, &timeval, -delta); + console->post("Fetching data[%d] at %s", i, timeString(position)); + fetch(); + if (i == last) // refreshGadgets() finishes up last one + break; + console->post("GroupControl::adjustArchiveWorldViewBackward: " + "setting time position[%d]=%.2f[%s] state=%s count=%d", + i, position, timeString(position), + timeState(), gadgetCount()); + for (int j = 0; j < gadgetCount(); j++) + my.gadgetsList.at(j)->updateValues(false, false, my.samples, my.visible, + left, right, interval); + } + + bool active = isActive(packet); + if (setup) + packet->state = QmcTime::StoppedState; + if (active) + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + pmtime->setArchivePosition(&packet->position); + pmtime->setArchiveInterval(&packet->delta); + refreshGadgets(active); +} + +void +GroupControl::adjustArchiveWorldViewStopped(QmcTime::Packet *packet, bool needFetch) +{ + if (needFetch) { // stopped, but VCR reposition event occurred + adjustArchiveWorldViewForward(packet, needFetch); + } else { + my.timeState = StandbyState; + packet->state = QmcTime::StoppedState; + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + updateTimeButton(); + } +} + +// +// Catch the situation where we get a larger than expected increase +// in position. This happens when we restart after a stop in live +// mode (both with and without a change in the delta). +// +static bool +sideStep(double n, double o, double interval) +{ + // tolerance set to 5% of the sample interval: + return fuzzyTimeMatch(o + interval, n, interval/20.0) == false; +} + +// +// Fetch all metric values across all gadgets, and also update the +// unified time axis. +// +void +GroupControl::step(QmcTime::Packet *packet) +{ + double stepPosition = tosec(packet->position); + + console->post(PmChart::DebugProtocol, + "GroupControl::step: stepping to time %.2f, delta=%.2f, state=%s", + stepPosition, my.realDelta, timeState()); + + if ((packet->source == QmcTime::ArchiveSource && + ((packet->state == QmcTime::ForwardState && + my.timeState != ForwardState) || + (packet->state == QmcTime::BackwardState && + my.timeState != BackwardState))) || + sideStep(stepPosition, my.realPosition, my.realDelta)) + return adjustWorldView(packet, false); + + my.pmtimeState = packet->state; + my.position = packet->position; + my.realPosition = stepPosition; + + int last = my.samples - 1; + if (packet->state == QmcTime::ForwardState) { // left-to-right (all but 1st) + if (my.samples > 1) + memmove(&my.timeData[1], &my.timeData[0], sizeof(double) * last); + my.timeData[0] = my.realPosition; + } + else if (packet->state == QmcTime::BackwardState) { // right-to-left + if (my.samples > 1) + memmove(&my.timeData[0], &my.timeData[1], sizeof(double) * last); + my.timeData[last] = my.realPosition - torange(my.delta, last); + } + + fetch(); + + bool active = isActive(packet); + if (isActive(packet)) + newButtonState(packet->state, packet->mode, pmchart->isTabRecording()); + refreshGadgets(active); +} + +void +GroupControl::VCRMode(QmcTime::Packet *packet, bool dragMode) +{ + if (!dragMode) + adjustWorldView(packet, true); +} + +void +GroupControl::setTimezone(QmcTime::Packet *packet, char *tz) +{ + console->post(PmChart::DebugProtocol, "GroupControl::setTimezone %s", tz); + + useTZ(QString(tz)); + + if (isActive(packet)) + updateTimeAxis(); +} + +void +GroupControl::setSampleHistory(int v) +{ + console->post("GroupControl::setSampleHistory (%d -> %d)", my.samples, v); + if (my.samples != v) { + my.samples = v; + + my.timeData = (double *)malloc(my.samples * sizeof(my.timeData[0])); + if (my.timeData == NULL) + nomem(); + + double right = my.realPosition; + for (v = 0; v < my.samples; v++) + my.timeData[v] = my.realPosition - (v * my.realDelta); + double left = my.timeData[v-1]; + for (v = 0; v < gadgetCount(); v++) + my.gadgetsList.at(v)->resetValues(my.samples, left, right); + } +} + +int +GroupControl::sampleHistory(void) +{ + return my.samples; +} + +void +GroupControl::setVisibleHistory(int v) +{ + console->post("GroupControl::setVisibleHistory (%d -> %d)", my.visible, v); + + if (my.visible != v) + my.visible = v; +} + +int +GroupControl::visibleHistory(void) +{ + return my.visible; +} + +double * +GroupControl::timeAxisData(void) +{ + return my.timeData; +} + +QedTimeButton::State +GroupControl::buttonState(void) +{ + return my.buttonState; +} + +void +GroupControl::newButtonState(QmcTime::State s, QmcTime::Mode m, bool record) +{ + if (isArchiveSource() == false) { + if (s == QmcTime::StoppedState) + my.buttonState = record ? + QedTimeButton::StoppedRecord : QedTimeButton::StoppedLive; + else + my.buttonState = record ? + QedTimeButton::ForwardRecord : QedTimeButton::ForwardLive; + } + else if (m == QmcTime::StepMode) { + if (s == QmcTime::ForwardState) + my.buttonState = QedTimeButton::StepForwardArchive; + else if (s == QmcTime::BackwardState) + my.buttonState = QedTimeButton::StepBackwardArchive; + else + my.buttonState = QedTimeButton::StoppedArchive; + } + else if (m == QmcTime::FastMode) { + if (s == QmcTime::ForwardState) + my.buttonState = QedTimeButton::FastForwardArchive; + else if (s == QmcTime::BackwardState) + my.buttonState = QedTimeButton::FastBackwardArchive; + else + my.buttonState = QedTimeButton::StoppedArchive; + } + else if (s == QmcTime::ForwardState) + my.buttonState = QedTimeButton::ForwardArchive; + else if (s == QmcTime::BackwardState) + my.buttonState = QedTimeButton::BackwardArchive; + else + my.buttonState = QedTimeButton::StoppedArchive; +} diff --git a/src/pmchart/groupcontrol.h b/src/pmchart/groupcontrol.h new file mode 100644 index 0000000..c20f29c --- /dev/null +++ b/src/pmchart/groupcontrol.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2006-2008, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#ifndef GROUPCONTROL_H +#define GROUPCONTROL_H + +#include <QtCore/QList> +#include <QtGui/QLabel> +#include <QtGui/QLayout> +#include <QtGui/QPixmap> +#include <qwt_plot.h> +#include <qwt_scale_draw.h> +#include <qmc_group.h> +#include <qmc_time.h> +#include "gadget.h" +#include "qed_timebutton.h" + +class GroupControl : public QObject, public QmcGroup +{ + Q_OBJECT + +public: + GroupControl(); + void init(int, int, struct timeval *, struct timeval *); + + void addGadget(Gadget *); + void deleteGadget(Gadget *); + int gadgetCount() const; + + bool isArchiveSource(); + void updateBackground(); + + void setVisibleHistory(int); + int visibleHistory(); + void setSampleHistory(int); + int sampleHistory(); + + double *timeAxisData(void); + + void step(QmcTime::Packet *); + void VCRMode(QmcTime::Packet *, bool); + void setTimezone(QmcTime::Packet *, char *); + + void setupWorldView(); + void updateTimeButton(); + void updateTimeAxis(void); + void updateTimeAxis(time_t secs); + + QedTimeButton::State buttonState(); + QmcTime::State pmtimeState(); + void newButtonState(QmcTime::State, QmcTime::Mode, bool); + +public Q_SLOTS: + void timeSelectionActive(Gadget *, int); + void timeSelectionReactive(Gadget *, int); + void timeSelectionInactive(Gadget *); + +private: + typedef enum { + StartState, + ForwardState, + BackwardState, + EndLogState, + StandbyState, + } State; + + char *timeState(); + void refreshGadgets(bool); + bool isActive(QmcTime::Packet *); + void adjustWorldView(QmcTime::Packet *, bool); + void adjustLiveWorldViewForward(QmcTime::Packet *); + void adjustLiveWorldViewStopped(QmcTime::Packet *); + void adjustArchiveWorldViewForward(QmcTime::Packet *, bool); + void adjustArchiveWorldViewStopped(QmcTime::Packet *, bool); + void adjustArchiveWorldViewBackward(QmcTime::Packet *, bool); + + struct { + QList<Gadget*> gadgetsList; // gadgets with metrics in this group + + double realDelta; // current update interval + double realPosition; // current time position + struct timeval delta; + struct timeval position; + + int visible; // -v visible points + int samples; // -s total number of samples + double *timeData; // time array (intervals) + + QedTimeButton::State buttonState; + QmcTime::Source pmtimeSource; // reliable archive/host test + QmcTime::State pmtimeState; + State timeState; + } my; +}; + +#endif // GROUPCONTROL_H diff --git a/src/pmchart/hostdialog.cpp b/src/pmchart/hostdialog.cpp new file mode 100644 index 0000000..e46635e --- /dev/null +++ b/src/pmchart/hostdialog.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2013-2014, Red Hat. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtCore/QDir> +#include <QtGui/QMessageBox> +#include "hostdialog.h" +#include "main.h" + +HostDialog::HostDialog(QWidget *parent) : QDialog(parent) +{ + setupUi(this); + my.nssGuiStarted = false; +} + +void +HostDialog::quit() +{ + if (my.nssGuiStarted) { + my.nssGuiProc->terminate(); + my.nssGuiStarted = false; + } +} + +void +HostDialog::languageChange() +{ + retranslateUi(this); +} + +void +HostDialog::secureCheckBox_toggled(bool enableSecure) +{ + certificatesPushButton->setEnabled(enableSecure); +} + +void +HostDialog::proxyCheckBox_toggled(bool enableProxy) +{ + proxyLineEdit->setEnabled(enableProxy); +} + +void +HostDialog::authenticateCheckBox_toggled(bool enableAuthenticate) +{ + usernameLabel->setEnabled(enableAuthenticate); + usernameLineEdit->setEnabled(enableAuthenticate); + passwordLabel->setEnabled(enableAuthenticate); + passwordLineEdit->setEnabled(enableAuthenticate); + realmLabel->setEnabled(enableAuthenticate); + realmLineEdit->setEnabled(enableAuthenticate); +} + +QString +HostDialog::getHostName(void) const +{ + QString host; + + if (hostLineEdit->isModified()) + host = hostLineEdit->text().trimmed(); + if (host.length() == 0) + return QString::null; + return host; +} + +QString +HostDialog::getHostSpecification(void) const +{ + QString host = getHostName(); + + if (hostLineEdit->isModified()) + host = hostLineEdit->text().trimmed(); + if (host.length() == 0) + return QString::null; + + if (proxyLineEdit->isModified()) { + QString proxy = proxyLineEdit->text().trimmed(); + if (proxy.length() > 0) + host.prepend("@").prepend(proxy); + } + + if (authenticateCheckBox->isChecked()) { + QString username = usernameLineEdit->text().trimmed(); + QString password = passwordLineEdit->text().trimmed(); + QString realm = realmLineEdit->text().trimmed(); + + host.append("?username=").append(username); + host.append("&password=").append(password); + host.append("&realm=").append(realm); + } + + return host; +} + +int +HostDialog::getContextFlags(void) const +{ + int flags = 0; + + if (secureCheckBox->isChecked()) + flags |= PM_CTXFLAG_SECURE; + if (compressCheckBox->isChecked()) + flags |= PM_CTXFLAG_COMPRESS; + if (authenticateCheckBox->isChecked()) + flags |= PM_CTXFLAG_AUTH; + return flags; +} + +void +HostDialog::certificatesPushButton_clicked() +{ + if (!my.nssGuiStarted) { + my.nssGuiStarted = true; + nssGuiStart(); + } +} + +void +HostDialog::nssGuiStart() +{ + QString dbpath = QDir::toNativeSeparators(QDir::homePath()); + int sep = __pmPathSeparator(); + + dbpath.append(sep).append(".pki").append(sep).append("nssdb"); + dbpath.prepend("sql:"); // only use sqlite NSS databases + + QStringList arguments; + arguments << "--dbdir"; + arguments << dbpath; + + my.nssGuiProc = new QProcess(this); + connect(my.nssGuiProc, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(nssGuiFinished(int, QProcess::ExitStatus))); + my.nssGuiProc->start("nss-gui", arguments); +} + +void +HostDialog::nssGuiFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + (void)exitStatus; + + if (exitCode) { + QString message(tr("nss-gui helper process failed\nExit status was:")); + message.append(exitCode).append("\n"); + QMessageBox::warning(this, pmProgname, message, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } + my.nssGuiStarted = false; +} diff --git a/src/pmchart/hostdialog.h b/src/pmchart/hostdialog.h new file mode 100644 index 0000000..8303674 --- /dev/null +++ b/src/pmchart/hostdialog.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013-2014, Red Hat. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef HOSTDIALOG_H +#define HOSTDIALOG_H + +#include "ui_hostdialog.h" +#include <QtCore/QProcess> + +class HostDialog : public QDialog, public Ui::HostDialog +{ + Q_OBJECT + +public: + HostDialog(QWidget* parent); + int getContextFlags() const; + QString getHostName(void) const; + QString getHostSpecification() const; + +protected slots: + virtual void languageChange(); + +private slots: + virtual void quit(); + virtual void proxyCheckBox_toggled(bool); + virtual void secureCheckBox_toggled(bool); + virtual void certificatesPushButton_clicked(); + virtual void authenticateCheckBox_toggled(bool); + virtual void nssGuiFinished(int, QProcess::ExitStatus); + +private: + void nssGuiStart(); + + struct { + bool nssGuiStarted; + QProcess *nssGuiProc; + } my; +}; + +#endif // HOSTDIALOG_H diff --git a/src/pmchart/hostdialog.ui b/src/pmchart/hostdialog.ui new file mode 100644 index 0000000..59b88cd --- /dev/null +++ b/src/pmchart/hostdialog.ui @@ -0,0 +1,320 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>HostDialog</class> + <widget class="QDialog" name="HostDialog"> + <property name="windowModality"> + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>383</width> + <height>302</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>100</height> + </size> + </property> + <property name="windowTitle"> + <string>Add Host</string> + </property> + <property name="windowIcon"> + <iconset resource="pmchart.qrc"> + <normaloff>:/images/computer.png</normaloff>:/images/computer.png</iconset> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="0"> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="hostLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + <property name="textFormat"> + <enum>Qt::AutoText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + <property name="buddy"> + <cstring>hostLineEdit</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="proxyLineEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>27</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="hostLineEdit"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>27</height> + </size> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="proxyCheckBox"> + <property name="text"> + <string>Proxy:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="secureGridLayout"> + <item row="0" column="0"> + <widget class="QCheckBox" name="secureCheckBox"> + <property name="text"> + <string>Secure</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="certificatesPushButton"> + <property name="text"> + <string>Certificates</string> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="compressCheckBox"> + <property name="text"> + <string>Compress</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="authenticateCheckBox"> + <property name="text"> + <string>Authenticate:</string> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="authenticateGridLayout"> + <item row="0" column="1"> + <widget class="QLabel" name="usernameLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Username:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="usernameLineEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="passwordLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Password:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="passwordLineEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="realmLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Realm:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLineEdit" name="realmLineEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item row="7" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>hostLineEdit</tabstop> + <tabstop>proxyCheckBox</tabstop> + <tabstop>proxyLineEdit</tabstop> + <tabstop>secureCheckBox</tabstop> + <tabstop>certificatesPushButton</tabstop> + <tabstop>compressCheckBox</tabstop> + <tabstop>authenticateCheckBox</tabstop> + <tabstop>usernameLineEdit</tabstop> + <tabstop>passwordLineEdit</tabstop> + <tabstop>realmLineEdit</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources> + <include location="pmchart.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>HostDialog</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>HostDialog</receiver> + <slot>reject()</slot> + </connection> + <connection> + <sender>proxyCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>HostDialog</receiver> + <slot>proxyCheckBox_toggled(bool)</slot> + </connection> + <connection> + <sender>certificatesPushButton</sender> + <signal>clicked()</signal> + <receiver>HostDialog</receiver> + <slot>certificatesPushButton_clicked()</slot> + </connection> + <connection> + <sender>secureCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>HostDialog</receiver> + <slot>secureCheckBox_toggled(bool)</slot> + </connection> + <connection> + <sender>authenticateCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>HostDialog</receiver> + <slot>authenticateCheckBox_toggled(bool)</slot> + </connection> + </connections> +</ui> diff --git a/src/pmchart/infodialog.cpp b/src/pmchart/infodialog.cpp new file mode 100644 index 0000000..aaa779a --- /dev/null +++ b/src/pmchart/infodialog.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "infodialog.h" +#include <QMessageBox> +#include "main.h" + +InfoDialog::InfoDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + my.pminfoStarted = false; + my.pmvalStarted = false; +} + +void InfoDialog::languageChange() +{ + retranslateUi(this); +} + +void InfoDialog::reset(QString source, QString metric, QString instance, + int sourceType) +{ + pminfoTextEdit->setText(tr("")); + pmvalTextEdit->setText(tr("")); + my.pminfoStarted = false; + my.pmvalStarted = false; + my.metric = metric; + my.source = source; + my.sourceType = sourceType; + my.instance = instance; + + infoTab->setCurrentWidget(pminfoTab); + infoTabCurrentChanged(0); +} + +void InfoDialog::pminfo(void) +{ + my.pminfoProc = new QProcess(this); + QStringList arguments; + + arguments << "-df"; + switch (my.sourceType) { + case PM_CONTEXT_ARCHIVE: + arguments << "-a"; + arguments << my.source; + // no help text in archive mode + break; + case PM_CONTEXT_LOCAL: + arguments << "-L"; + // no host name in local mode + arguments << "-tT"; + break; + default: + arguments << "-h"; + arguments << my.source; + arguments << "-tT"; + break; + } + arguments << my.metric; + + connect(my.pminfoProc, SIGNAL(readyReadStandardOutput()), + this, SLOT(pminfoStdout())); + connect(my.pminfoProc, SIGNAL(readyReadStandardError()), + this, SLOT(pminfoStderr())); + + my.pminfoProc->start("pminfo", arguments); +} + +void InfoDialog::pminfoStdout() +{ + QString s(my.pminfoProc->readAllStandardOutput()); + pminfoTextEdit->append(s); +} + +void InfoDialog::pminfoStderr() +{ + QString s(my.pminfoProc->readAllStandardError()); + pminfoTextEdit->append(s); +} + +void InfoDialog::pmval(void) +{ + QStringList arguments; + QString port; + port.setNum(pmtime->port()); + + my.pmvalProc = new QProcess(this); + arguments << "-f4" << "-p" << port; + if (my.sourceType == PM_CONTEXT_ARCHIVE) + arguments << "-a" << my.source; + else if (my.sourceType == PM_CONTEXT_LOCAL) + arguments << "-L"; + else + arguments << "-h" << my.source; + arguments << my.metric; + + connect(my.pmvalProc, SIGNAL(readyReadStandardOutput()), + this, SLOT(pmvalStdout())); + connect(my.pmvalProc, SIGNAL(readyReadStandardError()), + this, SLOT(pmvalStderr())); + connect(this, SIGNAL(finished(int)), this, SLOT(quit())); + my.pmvalProc->start("pmval", arguments); +} + +void InfoDialog::quit() +{ + if (my.pmvalStarted) { + my.pmvalProc->terminate(); + my.pmvalStarted = false; + } + if (my.pminfoStarted) { + my.pminfoProc->terminate(); + my.pminfoStarted = false; + } +} + +void InfoDialog::pmvalStdout() +{ + QString s(my.pmvalProc->readAllStandardOutput()); + s.trimmed(); + pmvalTextEdit->append(s); +} + +void InfoDialog::pmvalStderr() +{ + QString s(my.pmvalProc->readAllStandardError()); + s.trimmed(); + s.prepend("<b>"); + s.append("</b>"); + pmvalTextEdit->append(s); +} + +void InfoDialog::infoTabCurrentChanged(int) +{ + if (infoTab->currentWidget() == pminfoTab) { + if (!my.pminfoStarted) { + pminfo(); + my.pminfoStarted = true; + } + } + else if (infoTab->currentWidget() == pmvalTab) { + if (!my.pmvalStarted) { + pmval(); + my.pmvalStarted = true; + } + } +} diff --git a/src/pmchart/infodialog.h b/src/pmchart/infodialog.h new file mode 100644 index 0000000..9acbff6 --- /dev/null +++ b/src/pmchart/infodialog.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef INFODIALOG_H +#define INFODIALOG_H + +#include "ui_infodialog.h" +#include <QtCore/QProcess> + +class InfoDialog : public QDialog, public Ui::InfoDialog +{ + Q_OBJECT + +public: + InfoDialog(QWidget* parent); + void reset(QString, QString, QString, int); + void pminfo(); + void pmval(); + +public slots: + virtual void pminfoStdout(); + virtual void pminfoStderr(); + virtual void pmvalStdout(); + virtual void pmvalStderr(); + virtual void infoTabCurrentChanged(int); + virtual void quit(); + +protected slots: + virtual void languageChange(); + +private: + struct { + bool pminfoStarted; + bool pmvalStarted; + int sourceType; + QString source; + QString metric; + QString instance; + QProcess *pminfoProc; + QProcess *pmvalProc; + } my; +}; + +#endif // INFODIALOG_H diff --git a/src/pmchart/infodialog.ui b/src/pmchart/infodialog.ui new file mode 100644 index 0000000..f466101 --- /dev/null +++ b/src/pmchart/infodialog.ui @@ -0,0 +1,181 @@ +<ui version="4.0" stdsetdef="1" > + <class>InfoDialog</class> + <widget class="QDialog" name="InfoDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>413</width> + <height>330</height> + </rect> + </property> + <property name="windowTitle" > + <string>Metric Information</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/help-browser.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <item row="0" column="0" > + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <item row="1" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer name="Horizontal Spacing2" > + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="sizeType" > + <enum>Expanding</enum> + </property> + <property name="orientation" > + <enum>Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0" > + <widget class="QTabWidget" name="infoTab" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>250</width> + <height>150</height> + </size> + </property> + <widget class="QWidget" name="pminfoTab" > + <attribute name="title" > + <string>pminfo</string> + </attribute> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <item row="0" column="0" > + <widget class="QTextEdit" name="pminfoTextEdit" > + <property name="font" > + <font> + <family>Monospace</family> + <pointsize>9</pointsize> + </font> + </property> + <property name="frameShape" > + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth" > + <number>1</number> + </property> + <property name="text" > + <string/> + </property> + <property name="undoRedoEnabled" > + <bool>false</bool> + </property> + <property name="readOnly" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="pmvalTab" > + <attribute name="title" > + <string>pmval</string> + </attribute> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <item row="0" column="0" > + <widget class="QTextEdit" name="pmvalTextEdit" > + <property name="font" > + <font> + <family>Monospace</family> + <pointsize>9</pointsize> + </font> + </property> + <property name="frameShape" > + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Sunken</enum> + </property> + <property name="lineWidth" > + <number>1</number> + </property> + <property name="undoRedoEnabled" > + <bool>false</bool> + </property> + <property name="readOnly" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>InfoDialog</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>infoTab</sender> + <signal>currentChanged(int)</signal> + <receiver>InfoDialog</receiver> + <slot>infoTabCurrentChanged(int)</slot> + </connection> + </connections> +</ui> diff --git a/src/pmchart/main.cpp b/src/pmchart/main.cpp new file mode 100644 index 0000000..93f2acf --- /dev/null +++ b/src/pmchart/main.cpp @@ -0,0 +1,742 @@ +/* + * Copyright (c) 2014, Red Hat. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * Copyright (c) 2007-2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtCore/QSettings> +#include <QtGui/QStatusBar> +#include <QtGui/QApplication> +#include <QtGui/QDesktopWidget> +#include "main.h" +#include "openviewdialog.h" + +#define DESPERATE 0 + +int Cflag; +int Hflag; +int Lflag; +int Wflag; +char *outfile; +char *outgeometry; +QFont *globalFont; +Settings globalSettings; + +// Globals used to provide single instances of classes used across pmchart +GroupControl *liveGroup; // one metrics class group for all hosts +GroupControl *archiveGroup; // one metrics class group for all archives +GroupControl *activeGroup; // currently active metric fetchgroup +TimeControl *pmtime; // one timecontrol class for pmtime +PmChart *pmchart; + +static pmLongOptions longopts[] = { + PMAPI_OPTIONS_HEADER("General options"), + PMOPT_ALIGN, + PMOPT_ARCHIVE, + PMOPT_DEBUG, + PMOPT_HOST, + PMOPT_HOSTSFILE, + PMOPT_NAMESPACE, + PMOPT_SPECLOCAL, + PMOPT_LOCALPMDA, + PMOPT_ORIGIN, + PMOPT_GUIPORT, + PMOPT_START, + PMOPT_SAMPLES, + PMOPT_FINISH, + PMOPT_INTERVAL, + PMOPT_TIMEZONE, + PMOPT_HOSTZONE, + PMOPT_VERSION, + PMOPT_HELP, + PMAPI_OPTIONS_HEADER("Display options"), + { "view", 1, 'c', "VIEW", "chart view(s) to load on startup" }, + { "check", 0, 'C', 0, "parse views, report any errors and exit" }, + { "font-size", 1, 'F', "SIZE", "use font of given size" }, + { "font-family", 1, 'f', "FONT", "use font family" }, + { "geometry", 1, 'g', "WxH", "image geometry Width x Height" }, + { "output", 1, 'o', "FILE", "export image to FILE (type from suffix)" }, + { "samples", 1, 's', "N", "buffer up N points of sample history" }, + { "visible", 1, 'v', "N", "display N points of visible history" }, + { "white", 0, 'W', 0, "export images using an opaque (white) background" }, + PMAPI_OPTIONS_END +}; + +// a := a + b for struct timevals +void tadd(struct timeval *a, struct timeval *b) +{ + a->tv_usec += b->tv_usec; + if (a->tv_usec > 1000000) { + a->tv_usec -= 1000000; + a->tv_sec++; + } + a->tv_sec += b->tv_sec; +} + +// +// a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b +// +int tcmp(struct timeval *a, struct timeval *b) +{ + int res = (int)(a->tv_sec - b->tv_sec); + if (res == 0) + res = (int)(a->tv_usec - b->tv_usec); + return res; +} + +// convert timeval to seconds +double tosec(struct timeval t) +{ + return t.tv_sec + (t.tv_usec / 1000000.0); +} + +// create a time range in seconds from (delta x points) +double torange(struct timeval t, int points) +{ + return tosec(t) * points; +} + +// conversion from seconds (double precision) to struct timeval +void fromsec(double value, struct timeval *tv) +{ + tv->tv_sec = (time_t)value; + tv->tv_usec = (long)(((value - (double)tv->tv_sec) * 1000000.0)); +} + +// debugging, display seconds-since-epoch in human readable format +char *timeString(double seconds) +{ + static char string[32]; + time_t secs = (time_t)seconds; + char *s; + + s = pmCtime(&secs, string); + s[strlen(s)-1] = '\0'; + return s; +} + +// return a string containing hour and milliseconds +char *timeHiResString(double time) +{ + static char s[16]; + char m[8]; + time_t secs = (time_t)time; + struct tm t; + + sprintf(m, "%.3f", time - floor(time)); + pmLocaltime(&secs, &t); + sprintf(s, "%02d:%02d:%02d.%s", t.tm_hour, t.tm_min, t.tm_sec, m+2); + s[strlen(s)-1] = '\0'; + return s; +} + +void nomem(void) +{ + // no point trying to report anything ... dump core is the best bet + abort(); +} + +void setupEnvironment(void) +{ + char *value; + QString confirm = pmGetConfig("PCP_BIN_DIR"); + confirm.prepend("PCP_XCONFIRM_PROG="); + confirm.append(QChar(__pmPathSeparator())); + confirm.append("pmquery"); + if ((value = strdup((const char *)confirm.toAscii())) != NULL) + putenv(value); + if (getenv("PCP_STDERR") == NULL && // do not overwrite, for QA + ((value = strdup("PCP_STDERR=DISPLAY")) != NULL)) + putenv(value); + + QCoreApplication::setOrganizationName("PCP"); + QCoreApplication::setApplicationName(pmProgname); +} + +void writeSettings(void) +{ + QSettings userSettings; + + userSettings.beginGroup(pmProgname); + if (globalSettings.chartDeltaModified) { + globalSettings.chartDeltaModified = false; + userSettings.setValue("chartDelta", globalSettings.chartDelta); + } + if (globalSettings.loggerDeltaModified) { + globalSettings.loggerDeltaModified = false; + userSettings.setValue("loggerDelta", globalSettings.loggerDelta); + } + if (globalSettings.sampleHistoryModified) { + globalSettings.sampleHistoryModified = false; + userSettings.setValue("sampleHistory", globalSettings.sampleHistory); + } + if (globalSettings.visibleHistoryModified) { + globalSettings.visibleHistoryModified = false; + userSettings.setValue("visibleHistory", globalSettings.visibleHistory); + } + if (globalSettings.defaultSchemeModified) { + globalSettings.defaultSchemeModified = false; + userSettings.setValue("defaultColorScheme", + globalSettings.defaultScheme.colorNames()); + } + if (globalSettings.colorSchemesModified) { + globalSettings.colorSchemesModified = false; + userSettings.beginWriteArray("schemes"); + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) { + userSettings.setArrayIndex(i); + userSettings.setValue("name", + globalSettings.colorSchemes[i].name()); + userSettings.setValue("colors", + globalSettings.colorSchemes[i].colorNames()); + } + userSettings.endArray(); + } + if (globalSettings.chartBackgroundModified) { + globalSettings.chartBackgroundModified = false; + userSettings.setValue("chartBackgroundColor", + globalSettings.chartBackgroundName); + } + if (globalSettings.chartHighlightModified) { + globalSettings.chartHighlightModified = false; + userSettings.setValue("chartHighlightColor", + globalSettings.chartHighlightName); + } + if (globalSettings.initialToolbarModified) { + globalSettings.initialToolbarModified = false; + userSettings.setValue("initialToolbar", globalSettings.initialToolbar); + } + if (globalSettings.nativeToolbarModified) { + globalSettings.nativeToolbarModified = false; + userSettings.setValue("nativeToolbar", globalSettings.nativeToolbar); + } + if (globalSettings.toolbarLocationModified) { + globalSettings.toolbarLocationModified = false; + userSettings.setValue("toolbarLocation", + globalSettings.toolbarLocation); + } + if (globalSettings.toolbarActionsModified) { + globalSettings.toolbarActionsModified = false; + userSettings.setValue("toolbarActions", globalSettings.toolbarActions); + } + if (globalSettings.fontFamilyModified) { + globalSettings.fontFamilyModified = false; + if (globalSettings.fontFamily != QString(PmChart::defaultFontFamily())) + userSettings.setValue("fontFamily", globalSettings.fontFamily); + else + userSettings.remove("fontFamily"); + } + if (globalSettings.fontStyleModified) { + globalSettings.fontStyleModified = false; + if (globalSettings.fontStyle != QString("Normal")) + userSettings.setValue("fontStyle", globalSettings.fontStyle); + else + userSettings.remove("fontStyle"); + } + if (globalSettings.fontSizeModified) { + globalSettings.fontSizeModified = false; + if (globalSettings.fontSize != PmChart::defaultFontSize()) + userSettings.setValue("fontSize", globalSettings.fontSize); + else + userSettings.remove("fontSize"); + } + if (globalSettings.savedHostsModified) { + globalSettings.savedHostsModified = false; + if (globalSettings.savedHosts.isEmpty() == false) + userSettings.setValue("savedHosts", globalSettings.savedHosts); + else + userSettings.remove("savedHosts"); + } + + userSettings.endGroup(); +} + +void checkHistory(int samples, int visible) +{ + // sanity checking on sample sizes + if (samples < PmChart::minimumPoints()) { + globalSettings.sampleHistory = PmChart::minimumPoints(); + globalSettings.sampleHistoryModified = true; + } + if (samples > PmChart::maximumPoints()) { + globalSettings.sampleHistory = PmChart::maximumPoints(); + globalSettings.sampleHistoryModified = true; + } + if (visible < PmChart::minimumPoints()) { + globalSettings.visibleHistory = PmChart::minimumPoints(); + globalSettings.visibleHistoryModified = true; + } + if (visible > PmChart::maximumPoints()) { + globalSettings.visibleHistory = PmChart::maximumPoints(); + globalSettings.visibleHistoryModified = true; + } + if (samples < visible) { + globalSettings.sampleHistory = globalSettings.visibleHistory; + globalSettings.sampleHistoryModified = true; + } +} + +static void readSettings(void) +{ + QSettings userSettings; + userSettings.beginGroup(pmProgname); + + // + // Parameters related to sampling + // + globalSettings.chartDeltaModified = false; + globalSettings.chartDelta = userSettings.value("chartDelta", + PmChart::defaultChartDelta()).toDouble(); + globalSettings.loggerDeltaModified = false; + globalSettings.loggerDelta = userSettings.value("loggerDelta", + PmChart::defaultLoggerDelta()).toDouble(); + globalSettings.sampleHistoryModified = false; + globalSettings.sampleHistory = userSettings.value("sampleHistory", + PmChart::defaultSampleHistory()).toInt(); + globalSettings.visibleHistoryModified = false; + globalSettings.visibleHistory = userSettings.value("visibleHistory", + PmChart::defaultVisibleHistory()).toInt(); + checkHistory(globalSettings.sampleHistory, globalSettings.visibleHistory); + if (globalSettings.sampleHistoryModified) { + userSettings.setValue("samplePoints", globalSettings.sampleHistory); + globalSettings.sampleHistoryModified = false; + } + if (globalSettings.visibleHistoryModified) { + userSettings.setValue("visiblePoints", globalSettings.visibleHistory); + globalSettings.visibleHistoryModified = false; + } + + // + // Everything colour (scheme) related + // + QStringList colorList; + globalSettings.defaultSchemeModified = false; + if (userSettings.contains("defaultColorScheme") == true) + colorList = userSettings.value("defaultColorScheme").toStringList(); + else + colorList + << "#ffff00" << "#0000ff" << "#ff0000" << "#008000" << "#ee82ee" + << "#aa5500" << "#666666" << "#aaff00" << "#aa00ff" << "#aaaa7f"; + globalSettings.defaultScheme.setName("#-cycle"); + globalSettings.defaultScheme.setModified(false); + globalSettings.defaultScheme.setColorNames(colorList); + + int size = userSettings.beginReadArray("schemes"); + for (int i = 0; i < size; i++) { + userSettings.setArrayIndex(i); + ColorScheme scheme; + scheme.setName(userSettings.value("name").toString()); + scheme.setModified(false); + scheme.setColorNames(userSettings.value("colors").toStringList()); + globalSettings.colorSchemes.append(scheme); + } + userSettings.endArray(); + + // + // Everything (else) colour related + // + globalSettings.chartBackgroundModified = false; + globalSettings.chartBackgroundName = userSettings.value( + "chartBackgroundColor", "#6ca2c9").toString(); + globalSettings.chartBackground = QColor(globalSettings.chartBackgroundName); + + globalSettings.chartHighlightModified = false; + globalSettings.chartHighlightName = userSettings.value( + "chartHighlightColor", "blue").toString(); + globalSettings.chartHighlight = QColor(globalSettings.chartHighlightName); + + // + // Toolbar user preferences + // + globalSettings.initialToolbarModified = false; + globalSettings.initialToolbar = userSettings.value( + "initialToolbar", 1).toInt(); + globalSettings.nativeToolbarModified = false; + globalSettings.nativeToolbar = userSettings.value( + "nativeToolbar", 1).toInt(); + globalSettings.toolbarLocationModified = false; + globalSettings.toolbarLocation = userSettings.value( + "toolbarLocation", 0).toInt(); + QStringList actionList; + globalSettings.toolbarActionsModified = false; + if (userSettings.contains("toolbarActions") == true) + globalSettings.toolbarActions = + userSettings.value("toolbarActions").toStringList(); + // else: (defaults come from the pmchart.ui interface specification) + + // + // Font preferences + // + globalSettings.fontFamilyModified = false; + globalSettings.fontFamily = userSettings.value( + "fontFamily", PmChart::defaultFontFamily()).toString(); + globalSettings.fontStyleModified = false; + QString fontStyle; + globalSettings.fontStyle = userSettings.value( + "fontStyle", "Normal").toString(); + globalSettings.fontSizeModified = false; + globalSettings.fontSize = userSettings.value( + "fontSize", PmChart::defaultFontSize()).toInt(); + + // + // Saved Hosts list preferences + // + globalSettings.savedHostsModified = false; + if (userSettings.contains("savedHosts") == true) + globalSettings.savedHosts = + userSettings.value("savedHosts").toStringList(); + + userSettings.endGroup(); +} + +static void readSchemes(void) +{ + QChar sep(__pmPathSeparator()); + QString schemes = pmGetConfig("PCP_VAR_DIR"); + schemes.append(sep).append("config"); + schemes.append(sep).append("pmchart"); + schemes.append(sep).append("Schemes"); + + QFileInfo fi(schemes); + if (fi.exists()) + OpenViewDialog::openView(schemes.toAscii()); +} + +// Get next color from given scheme or from default colors for #-cycle +QColor nextColor(QString scheme, int *sequence) +{ + QList<QColor> colorList; + int seq = (*sequence)++; + + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) { + if (globalSettings.colorSchemes[i].name() == scheme) { + colorList = globalSettings.colorSchemes[i].colors(); + break; + } + } + if (colorList.size() < 2) // common case + colorList = globalSettings.defaultScheme.colors(); + if (colorList.size() < 2) // idiot user!? + colorList << QColor("yellow") << QColor("blue") << QColor("red") + << QColor("green") << QColor("violet"); + seq %= colorList.size(); + return colorList.at(seq); +} + +static void setupViewGlobals() +{ + int w, h, points, x, y; + + OpenViewDialog::globals(&w, &h, &points, &x, &y); + if (w || h) { + QSize size = pmchart->size().expandedTo(QSize(w, h)); + QSize desk = QApplication::desktop()->availableGeometry().size(); + pmchart->resize(size.boundedTo(desk)); + } + if (x || y) { + QPoint pos = pmchart->pos(); + if (x) pos.setX(x); + if (y) pos.setY(y); + pmchart->move(pos); + } + if (points) { + if (activeGroup->sampleHistory() < points) + activeGroup->setSampleHistory(points); + activeGroup->setVisibleHistory(points); + } +} + +static int +override(int opt, pmOptions *opts) +{ + (void)opts; + if (opt == 'g') + return 1; + if (opt == 'H') + Hflag = 1; + if (opt == 'L') + Lflag = 1; + return 0; +} + +int +main(int argc, char ** argv) +{ + int c, sts; + int sh = -1; /* sample history length */ + int vh = -1; /* visible history length */ + char *endnum; + Tab *tab; + struct timeval logStartTime; + struct timeval logEndTime; + QStringList configs; + QString tzLabel; + QString tzString; + pmOptions opts; + + memset(&opts, 0, sizeof(opts)); + __pmtimevalNow(&opts.origin); + __pmSetProgname(argv[0]); + QApplication a(argc, argv); + setupEnvironment(); + readSettings(); + + opts.flags = PM_OPTFLAG_MULTI | PM_OPTFLAG_MIXED; + opts.short_options = "A:a:Cc:D:f:F:g:h:H:Ln:o:O:p:s:S:T:t:Vv:WzZ:?"; + opts.long_options = longopts; + opts.short_usage = "[options] [sources]"; + opts.override = override; + + + while ((c = pmGetOptions(argc, argv, &opts)) != EOF) { + switch (c) { + + case 'C': + Cflag++; + break; + + case 'c': + configs.append(opts.optarg); + break; + + case 'f': + globalSettings.fontFamily = opts.optarg; + break; + + case 'F': + sts = (int)strtol(opts.optarg, &endnum, 10); + if (*endnum != '\0' || c < 0) { + pmprintf("%s: -F requires a numeric argument\n", pmProgname); + opts.errors++; + } else { + globalSettings.fontSize = sts; + } + break; + + case 'g': + outgeometry = opts.optarg; + break; + + case 'o': /* output image file */ + outfile = opts.optarg; + break; + + case 'W': /* white image background */ + Wflag = 1; + break; + + case 'v': /* visible history */ + vh = (int)strtol(opts.optarg, &endnum, 10); + if (*endnum != '\0' || vh < 1) { + pmprintf("%s: -v requires a numeric argument, larger than 1\n", + pmProgname); + opts.errors++; + } + break; + } + } + + /* hosts from a Hosts file are added to the SavedHosts list */ + if (Hflag) { + for (int i = 0; i < opts.nhosts; i++) + globalSettings.savedHosts.append(opts.hosts[i]); + globalSettings.savedHostsModified = true; + } + + if (opts.narchives > 0) { + while (opts.optind < argc) + __pmAddOptArchive(&opts, argv[opts.optind++]); + } else { + if (!Hflag) { + for (c = 0; c < globalSettings.savedHosts.size(); c++) { + const QString &host = globalSettings.savedHosts.at(c); + __pmAddOptHost(&opts, (char *)(const char *)host.toAscii()); + } + } + while (opts.optind < argc) + __pmAddOptHost(&opts, argv[opts.optind++]); + } + + if (opts.optind != argc) + opts.errors++; + if (opts.errors) { + pmUsageMessage(&opts); + exit(1); + } + + /* set initial sampling interval from command line, else global setting */ + if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0) + fromsec(globalSettings.chartDelta, &opts.interval); + + console = new QedConsole(opts.origin); + + // + // Deal with user requested sample/visible points globalSettings. These + // (command line) override the QSettings values, for this instance + // of pmchart. They should not be written though, unless requested + // later via the Settings dialog. + // + sh = opts.samples ? opts.samples : -1; + if (vh != -1 || sh != -1) { + if (sh == -1) + sh = globalSettings.sampleHistory; + if (vh == -1) + vh = globalSettings.visibleHistory; + checkHistory(sh, vh); + if (globalSettings.sampleHistoryModified || + globalSettings.visibleHistoryModified) { + pmprintf("%s: invalid sample/visible history\n", pmProgname); + pmflush(); + exit(1); + } + globalSettings.sampleHistory = sh; + globalSettings.visibleHistory = vh; + } + console->post("Global settings setup complete"); + + // Create all of the sources + liveGroup = new GroupControl(); + archiveGroup = new GroupControl(); + if (Lflag) + liveGroup->use(PM_CONTEXT_LOCAL, QmcSource::localHost); + sts = opts.nhosts + opts.narchives; + for (c = 0; c < opts.nhosts; c++) + if (liveGroup->use(PM_CONTEXT_HOST, opts.hosts[c]) < 0) + sts--; + for (c = 0; c < opts.narchives; c++) + if (archiveGroup->use(PM_CONTEXT_ARCHIVE, opts.archives[c]) < 0) + sts--; + if (Lflag == 0 && sts == 0) + liveGroup->createLocalContext(); + pmflush(); + console->post("Metric group setup complete (%d hosts, %d archives)", + opts.nhosts, opts.narchives); + + if (opts.tzflag) { + if (opts.narchives > 0) + archiveGroup->useTZ(); + if (opts.nhosts > 0) + liveGroup->useTZ(); + } + else if (opts.timezone != NULL) { + if (opts.narchives > 0) + archiveGroup->useTZ(QString(opts.timezone)); + if (opts.nhosts > 0) + liveGroup->useTZ(QString(opts.timezone)); + if ((sts = pmNewZone(opts.timezone)) < 0) { + pmprintf("%s: cannot set timezone to \"%s\": %s\n", + pmProgname, (char *)opts.timezone, pmErrStr(sts)); + pmflush(); + exit(1); + } + } + + // + // Choose which Tab will be displayed initially - archive/live. + // If any archives given on command line, we go Archive mode; + // otherwise Live mode wins. Our initial pmtime connection is + // set in that mode too. Later we'll make a second connection + // in the other mode (and only "on-demand"). + // + if (opts.narchives > 0) { + archiveGroup->defaultTZ(tzLabel, tzString); + archiveGroup->updateBounds(); + logStartTime = archiveGroup->logStart(); + logEndTime = archiveGroup->logEnd(); + if ((sts = pmParseTimeWindow(opts.start_optarg, opts.finish_optarg, + opts.align_optarg, opts.origin_optarg, + &logStartTime, &logEndTime, &opts.start, + &opts.finish, &opts.origin, &endnum)) < 0) { + pmprintf("Cannot parse archive time window\n%s\n", endnum); + pmUsageMessage(&opts); + free(endnum); + exit(1); + } + // move position to account for initial visible points + if (tcmp(&opts.origin, &opts.start) <= 0) + for (c = 0; c < globalSettings.visibleHistory - 2; c++) + tadd(&opts.origin, &opts.interval); + if (tcmp(&opts.origin, &opts.finish) > 0) + opts.origin = opts.finish; + } + else { + liveGroup->defaultTZ(tzLabel, tzString); + __pmtimevalNow(&logStartTime); + logEndTime.tv_sec = logEndTime.tv_usec = INT_MAX; + if ((sts = pmParseTimeWindow(opts.start_optarg, opts.finish_optarg, + opts.align_optarg, opts.origin_optarg, + &logStartTime, &logEndTime, &opts.start, + &opts.finish, &opts.origin, &endnum)) < 0) { + pmprintf("Cannot parse live time window\n%s\n", endnum); + pmUsageMessage(&opts); + free(endnum); + exit(1); + } + } + console->post("Timezones and time window setup complete"); + + globalFont = new QFont(globalSettings.fontFamily, globalSettings.fontSize); + if (globalSettings.fontStyle.contains("Italic")) + globalFont->setItalic(true); + if (globalSettings.fontStyle.contains("Bold")) + globalFont->setBold(true); + + tab = new Tab; + fileIconProvider = new QedFileIconProvider(); + + pmchart = new PmChart; + pmtime = new TimeControl; + + console->post("Phase1 user interface constructors complete"); + + // Start pmtime process for time management + pmtime->init(opts.guiport, opts.narchives == 0, &opts.interval, &opts.origin, + &opts.start, &opts.finish, tzString, tzLabel); + + pmchart->init(); + liveGroup->init(globalSettings.sampleHistory, + globalSettings.visibleHistory, + pmtime->liveInterval(), pmtime->livePosition()); + archiveGroup->init(globalSettings.sampleHistory, + globalSettings.visibleHistory, + pmtime->archiveInterval(), pmtime->archivePosition()); + + // + // We setup the pmchart tab list late, so we don't have to deal + // with pmtime messages reaching the Tabs until we're all setup. + // + if (opts.narchives == 0) + tab->init(pmchart->tabWidget(), liveGroup, "Live"); + else + tab->init(pmchart->tabWidget(), archiveGroup, "Archive"); + pmchart->tabWidget()->insertTab(tab); + pmchart->setActiveTab(0, true); + console->post("Phase2 user interface setup complete"); + + readSchemes(); + for (c = 0; c < configs.size(); c++) + if (!OpenViewDialog::openView((const char *)configs[c].toAscii())) + opts.errors++; + if (opts.errors) + exit(1); + setupViewGlobals(); + pmflush(); + + if (Cflag) // done with -c config, quit + return 0; + + pmchart->enableUi(); + pmchart->show(); + console->post("Top level window shown"); + + a.connect(&a, SIGNAL(lastWindowClosed()), pmchart, SLOT(quit())); + return a.exec(); +} diff --git a/src/pmchart/main.h b/src/pmchart/main.h new file mode 100644 index 0000000..f09c77a --- /dev/null +++ b/src/pmchart/main.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014, Red Hat. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#ifndef MAIN_H +#define MAIN_H + +#include <stdio.h> +#include <pcp/pmapi.h> +#include <pcp/impl.h> + +#include "tab.h" +#include "colorscheme.h" +#include "qed_console.h" +#include "timecontrol.h" +#include "qed_fileiconprovider.h" +#include "pmchart.h" + +typedef struct { + // Sampling + double chartDelta; + bool chartDeltaModified; + double loggerDelta; + bool loggerDeltaModified; + int sampleHistory; + bool sampleHistoryModified; + int visibleHistory; + bool visibleHistoryModified; + + // Default Colors + QColor chartBackground; + QString chartBackgroundName; + bool chartBackgroundModified; + QColor chartHighlight; + QString chartHighlightName; + bool chartHighlightModified; + // Color Schemes + ColorScheme defaultScheme; + bool defaultSchemeModified; + QList<ColorScheme> colorSchemes; + bool colorSchemesModified; + + // Toolbar + int initialToolbar; + bool initialToolbarModified; + int nativeToolbar; + bool nativeToolbarModified; + int toolbarLocation; + int toolbarLocationModified; + QStringList toolbarActions; + bool toolbarActionsModified; + + // Font + QString fontFamily; + bool fontFamilyModified; + QString fontStyle; + bool fontStyleModified; + int fontSize; + bool fontSizeModified; + + // Saved Hosts + QStringList savedHosts; + bool savedHostsModified; +} Settings; + +extern Settings globalSettings; +extern void writeSettings(); +extern QColor nextColor(QString, int *); + +extern int Cflag; +extern int Lflag; +extern int Wflag; +extern char *outfile; +extern char *outgeometry; + +extern QFont *globalFont; + +extern GroupControl *activeGroup; +extern GroupControl *liveGroup; +extern GroupControl *archiveGroup; + +class PmChart; +extern PmChart *pmchart; + +class TimeControl; +extern TimeControl *pmtime; + +extern double tosec(struct timeval); +extern double torange(struct timeval, int); +extern void fromsec(double, struct timeval *); +extern char *timeString(double); +extern char *timeHiResString(double); +extern void nomem(void); + +/* + * number of Y pixels to move the time axis up when exporting to + * an image or printing + */ +#define TIMEAXISFUDGE 0 + +#endif // MAIN_H diff --git a/src/pmchart/namespace.cpp b/src/pmchart/namespace.cpp new file mode 100644 index 0000000..f2f91c9 --- /dev/null +++ b/src/pmchart/namespace.cpp @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ + +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtGui/QApplication> +#include <QtGui/QMessageBox> +#include <QtGui/QListView> +#include "namespace.h" +#include "chart.h" +#include "main.h" + +#define DESPERATE 0 + +NameSpace::NameSpace(NameSpace *parent, const QString &name, bool inst) + : QTreeWidgetItem(parent, QTreeWidgetItem::UserType) +{ + my.expanded = false; + my.back = parent; + my.desc = parent->my.desc; + my.context = parent->my.context; + my.basename = name; + if (name == QString::null) + my.type = ChildMinder; + else if (!inst) + my.type = NoType; + else { + my.type = InstanceName; + QFont font = QTreeWidgetItem::font(0); + font.setItalic(true); + setFont(0, font); + } + setText(0, my.basename); +#if DESPERATE + if (my.type == ChildMinder) { + console->post(PmChart::DebugUi, "Added namespace childminder"); + } + else { + console->post(PmChart::DebugUi, "Added non-root namespace node %s (inst=%d)", + (const char *)my.basename.toAscii(), inst); + } +#endif +} + +NameSpace::NameSpace(QTreeWidget *list, const QmcContext *context) + : QTreeWidgetItem(list, QTreeWidgetItem::UserType) +{ + my.expanded = false; + my.back = this; + memset(&my.desc, 0, sizeof(my.desc)); + my.context = (QmcContext *)context; + switch (my.context->source().type()) { + case PM_CONTEXT_ARCHIVE: + my.basename = context->source().source(); + my.icon = QIcon(":/images/archive.png"); + my.type = ArchiveRoot; + break; + case PM_CONTEXT_LOCAL: + my.basename = QString("Local context"); + my.icon = QIcon(":/images/emblem-system.png"); + my.type = LocalRoot; + break; + default: + my.basename = context->source().source(); + my.icon = QIcon(":/images/computer.png"); + my.type = HostRoot; + break; + } + setToolTip(0, sourceTip()); + setText(0, my.basename); + setIcon(0, my.icon); + + console->post(PmChart::DebugUi, "Added root namespace node %s", + (const char *)my.basename.toAscii()); +} + +QString NameSpace::sourceTip() +{ + QString tooltip; + QmcSource source = my.context->source(); + + tooltip = "Performance metrics from host "; + tooltip.append(source.host()); + + if (my.context->source().type() == PM_CONTEXT_ARCHIVE) { + tooltip.append("\n commencing "); + tooltip.append(source.startTime()); + tooltip.append("\n ending "); + tooltip.append(source.endTime()); + } + tooltip.append("\nTimezone: "); + tooltip.append(source.timezone()); + return tooltip; +} + +int NameSpace::sourceType() +{ + return my.context->source().type(); +} + +QString NameSpace::sourceName() +{ + return my.context->source().source(); +} + +QString NameSpace::metricName() +{ + QString s; + + if (my.back->isRoot()) + s = text(0); + else if (my.type == InstanceName) + s = my.back->metricName(); + else { + s = my.back->metricName(); + s.append("."); + s.append(text(0)); + } + return s; +} + +QString NameSpace::metricInstance() +{ + if (my.type == InstanceName) + return text(0); + return QString::null; +} + +void NameSpace::setExpanded(bool expand, bool show) +{ +#if DESPERATE + console->post(PmChart::DebugUi, "NameSpace::setExpanded " + "on %p %s (type=%d expanded=%s, expand=%s, show=%s)", + this, (const char *)metricName().toAscii(), + my.type, + my.expanded? "y" : "n", expand? "y" : "n", show? "y" : "n"); +#endif + + if (expand && !my.expanded) { + NameSpace *kid = (NameSpace *)child(0); + + if (kid && kid->isChildMinder()) { + takeChild(0); + delete kid; + } + my.expanded = true; + pmUseContext(my.context->handle()); + + if (my.type == LeafWithIndom) + expandInstanceNames(show); + else if (my.type != InstanceName) { + expandMetricNames(isRoot() ? "" : metricName(), show); + } + } + + if (show) { + QTreeWidgetItem::setExpanded(expand); + } + +} + +void NameSpace::setSelectable(bool selectable) +{ + if (selectable) + setFlags(flags() | Qt::ItemIsSelectable); + else + setFlags(flags() & ~Qt::ItemIsSelectable); +} + +void NameSpace::setExpandable(bool expandable) +{ + console->post(PmChart::DebugUi, "NameSpace::setExpandable " + "on %p %s (expanded=%s, expandable=%s)", + this, (const char *)metricName().toAscii(), + my.expanded ? "y" : "n", expandable ? "y" : "n"); + + // NOTE: QT4.3 has setChildIndicatorPolicy for this workaround, but we want + // to work on QT4.2 as well (this is used on Debian 4.0 - i.e. my laptop!). + // This is the ChildMinder workaround - we insert a "dummy" child into items + // that we know have children (since we have no way to explicitly set it and + // we want to delay finding the actual children as late as possible). + // When we later do fill in the real kids, we first delete the ChildMinder. + + if (expandable) + addChild(new NameSpace(this, QString::null, false)); +} + +static char *namedup(const char *name, const char *suffix) +{ + char *n; + + if (strlen(name) > 0) { + n = (char *)malloc(strlen(name) + 1 + strlen(suffix) + 1); + sprintf(n, "%s.%s", name, suffix); + } + else { + n = strdup(suffix); + } + return n; +} + +void NameSpace::setFailed(bool failed) +{ + bool selectable = (failed == false && + (my.type == LeafNullIndom || my.type == InstanceName)); + setSelectable(selectable); + bool expandable = (failed == false && + (my.type != LeafNullIndom && my.type != InstanceName)); + setExpandable(expandable); + + QFont font = QTreeWidgetItem::font(0); + font.setStrikeOut(failed); + setFont(0, font); +} + +void NameSpace::expandMetricNames(QString parent, bool show) +{ + char **offspring = NULL; + int *status = NULL; + pmID *pmidlist = NULL; + int i, nleaf = 0; + int sts, noffspring; + NameSpace *m, **leaflist = NULL; + char *name = strdup(parent.toAscii()); + int sort_done, fail_count = 0; + QString failmsg; + + sts = pmGetChildrenStatus(name, &offspring, &status); + if (sts < 0) { + if (!show) + goto done; + QString msg = QString(); + if (isRoot()) + msg.sprintf("Cannot get metric names from source\n%s: %s.\n\n", + (const char *)my.basename.toAscii(), pmErrStr(sts)); + else + msg.sprintf("Cannot get children of node\n\"%s\".\n%s.\n\n", + name, pmErrStr(sts)); + QMessageBox::warning(NULL, pmProgname, msg, + QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + goto done; + } + else { + noffspring = sts; + } + + // Ugliness ahead. + // The Qt routine sortChildren() does not work as we maintain + // our own pointers into the tree items via my.back ... if + // sortChildren() is used, our expansion picking does not work later + // on. + // Sort the PMNS children lexicographically by name before adding them + // into the QTreeWidget ... only tricky part is that we need to sort + // offspring[] AND status[] + // Bubble Sort is probably OK as the number of descendents in the + // PMNS is never huge. + // + sort_done = 0; + while (!sort_done) { + char *ctmp; + int itmp; + sort_done = 1; + for (i = 0; i < noffspring-1; i++) { + if (strcmp(offspring[i], offspring[i+1]) <= 0) + continue; + // swap + ctmp = offspring[i]; + offspring[i] = offspring[i+1]; + offspring[i+1] = ctmp; + itmp = status[i]; + status[i] = status[i+1]; + status[i+1] = itmp; + sort_done = 0; + } + } + + for (i = 0; i < noffspring; i++) { + m = new NameSpace(this, offspring[i], false); + + if (status[i] == PMNS_NONLEAF_STATUS) { + m->setSelectable(false); + m->setExpandable(true); + m->my.type = NonLeafName; + } + else { + // type still not set, could be LEAF_NULL_INDOM or LEAF_WITH_INDOM + // here we add this NameSpace pointer to the leaf list, and also + // modify the offspring list to only contain names (when done). + leaflist = (NameSpace **)realloc(leaflist, + (nleaf + 1) * sizeof(*leaflist)); + leaflist[nleaf] = m; + offspring[nleaf] = namedup(name, offspring[i]); + nleaf++; + } + } + + if (nleaf == 0) { + my.expanded = true; + goto done; + } + + pmidlist = (pmID *)malloc(nleaf * sizeof(*pmidlist)); + if ((sts = pmLookupName(nleaf, offspring, pmidlist)) < 0) { + if (!show) + goto done; + failmsg.sprintf("Cannot find PMIDs: %s.\n", pmErrStr(sts)); + for (i = 0; i < nleaf; i++) { + leaflist[i]->setFailed(true); + if (pmidlist[i] == PM_ID_NULL) { + if (fail_count == 0) + failmsg.append("Metrics:\n"); + if (fail_count < 5) + failmsg.append(" ").append(offspring[i]).append("\n"); + else if (fail_count == 5) + failmsg.append("... (further metrics omitted).\n"); + fail_count++; + } + } + fail_count = fail_count ? fail_count : 1; + } + else { + for (i = 0; i < nleaf; i++) { + m = leaflist[i]; + sts = pmLookupDesc(pmidlist[i], &m->my.desc); + if (sts < 0) { + if (!show) + goto done; + if (fail_count < 3) { + failmsg.append("Cannot find metric descriptor at \""); + failmsg.append(offspring[i]).append("\":\n "); + failmsg.append(pmErrStr(sts)).append(".\n\n"); + } + else if (fail_count == 3) { + failmsg.append("... (further errors omitted).\n"); + } + m->setFailed(true); + fail_count++; + } + else if (m->my.desc.indom == PM_INDOM_NULL) { + m->my.type = LeafNullIndom; + m->setExpandable(false); + m->setSelectable(true); + } + else { + m->my.type = LeafWithIndom; + m->setExpandable(true); + m->setSelectable(false); + } + } + my.expanded = true; + } + +done: + if (fail_count) + QMessageBox::warning(NULL, pmProgname, failmsg, + QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + if (pmidlist) + free(pmidlist); + if (leaflist) + free(leaflist); + if (offspring) { + for (i = 0; i < nleaf; i++) + free(offspring[i]); + free(offspring); + } + if (status) + free(status); + free(name); +} + +void NameSpace::expandInstanceNames(bool show) +{ + int sts, i; + int ninst = 0; + int *instlist = NULL; + char **namelist = NULL; + bool live = (my.context->source().type() != PM_CONTEXT_ARCHIVE); + + sts = live ? pmGetInDom(my.desc.indom, &instlist, &namelist) : + pmGetInDomArchive(my.desc.indom, &instlist, &namelist); + if (sts < 0) { + if (!show) + goto done; + QString msg = QString(); + msg.sprintf("Error fetching instance domain at node \"%s\".\n%s.\n\n", + (const char *)metricName().toAscii(), pmErrStr(sts)); + QMessageBox::warning(NULL, pmProgname, msg, + QMessageBox::Ok | QMessageBox::Default | + QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } + else { + ninst = sts; + my.expanded = true; + } + + for (i = 0; i < ninst; i++) { + NameSpace *m = new NameSpace(this, namelist[i], true); + + m->setExpandable(false); + m->setSelectable(true); + } + +done: + if (instlist) + free(instlist); + if (namelist) + free(namelist); +} + +QString NameSpace::text(int column) const +{ + if (column > 0) + return QString::null; + return my.basename; +} + +QIcon NameSpace::icon(int) const +{ + return my.icon; +} + +void NameSpace::setIcon(int i, const QIcon &icon) +{ + my.icon = icon; + QTreeWidgetItem::setIcon(i, icon); +} + +NameSpace *NameSpace::dup(QTreeWidget *list) +{ + NameSpace *n; + + n = new NameSpace(list, my.context); + n->expand(); + n->setSelectable(false); + return n; +} + +NameSpace *NameSpace::dup(QTreeWidget *, NameSpace *tree, + QString scheme, int *sequence) +{ + NameSpace *n; + + n = new NameSpace(tree, my.basename, my.type == InstanceName); + n->my.context = my.context; + n->my.desc = my.desc; + n->my.type = my.type; + + if (my.type == NoType || my.type == ChildMinder) { + console->post("NameSpace::dup bad type=%d on %p %s)", + my.type, this, (const char *)metricName().toAscii()); + abort(); + } + else if (!isLeaf()) { + n->expand(); + n->setSelectable(false); + } + else { + n->expand(); + n->setSelectable(true); + + QColor c = nextColor(scheme, sequence); + n->setOriginalColor(c); + n->setCurrentColor(c, NULL); + + // this is a leaf so walk back up to the root, opening each node up + NameSpace *up; + for (up = tree; up->my.back != up; up = up->my.back) { + up->expand(); + up->setExpanded(true, true); + } + up->expand(); + up->setExpanded(true, true); // add the host/archive root as well. + + n->setSelected(true); + } + return n; +} + +bool NameSpace::cmp(NameSpace *item) +{ + if (!item) // empty list + return false; + return (item->text(0) == my.basename); +} + +void NameSpace::addToTree(QTreeWidget *target, QString scheme, int *sequence) +{ + QList<NameSpace *> nodelist; + NameSpace *node; + + // Walk through each component of this name, creating them in the + // target list (if not there already), right down to the leaf. + // We hold everything in a temporary list since we need to add to + // the target in root-to-leaf order but we're currently at a leaf. + + for (node = this; node->my.back != node; node = node->my.back) + nodelist.prepend(node); + nodelist.prepend(node); // add the host/archive root as well. + + NameSpace *tree = (NameSpace *)target->invisibleRootItem(); + NameSpace *item = NULL; + + for (int n = 0; n < nodelist.size(); n++) { + node = nodelist.at(n); + bool foundMatchingName = false; + for (int i = 0; i < tree->childCount(); i++) { + item = (NameSpace *)tree->child(i); + if (node->cmp(item)) { + // no insert at this level necessary, move down a level + if (!node->isLeaf()) { + tree = item; + item = (NameSpace *)item->child(0); + } + // already there, just select the existing name + else { + item->setSelected(true); + } + foundMatchingName = true; + break; + } + } + + // When no more children and no match so far, we dup & insert + if (foundMatchingName == false) { + if (node->isRoot()) { + tree = node->dup(target); + } + else if (tree) { + tree = node->dup(target, tree, scheme, sequence); + } + } + } +} + +void NameSpace::removeFromTree(QTreeWidget *) +{ + NameSpace *self, *tree, *node = this; + + this->setSelected(false); + do { + self = node; + tree = node->my.back; + if (node->childCount() == 0) + delete node; + node = tree; + } while (self != tree); +} + +void NameSpace::setCurrentColor(QColor color, QTreeWidget *treeview) +{ + QPixmap pix(8, 8); + + // Set the current metric color, and (optionally) move on to the next + // leaf node in the view (if given treeview parameter is non-NULL). + // We're taking a punt here that the user will move down through the + // metrics just added, and set their prefered color on each one; so, + // by doing the next selection automatically, we save some clicks. + + my.current = color; + pix.fill(color); + setIcon(0, QIcon(pix)); + + if (treeview) { + QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selectable); + if (*it) { + (*it)->setSelected(true); + this->setSelected(false); + } + } +} diff --git a/src/pmchart/namespace.h b/src/pmchart/namespace.h new file mode 100644 index 0000000..ba73fb2 --- /dev/null +++ b/src/pmchart/namespace.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef NAMESPACE_H +#define NAMESPACE_H + +#include <QtCore/QString> +#include <QtGui/QTreeWidget> +#include <QtGui/QTreeWidgetItem> +#include <pcp/pmapi.h> + +class Chart; +class QmcContext; + +class NameSpace : public QTreeWidgetItem +{ +public: + typedef enum { + NoType, + ChildMinder, + ArchiveRoot, + LocalRoot, + HostRoot, + NonLeafName, + LeafNullIndom, + LeafWithIndom, + InstanceName, + } Type; + + NameSpace(QTreeWidget *, const QmcContext *); // for root nodes only + NameSpace(NameSpace *, const QString &, bool); // for all other nodes + + QString text(int) const; + QIcon icon(int) const; + void setIcon(int, const QIcon &); + void expand() { my.expanded = true; } + void setExpanded(bool expanded, bool show); + + pmDesc desc() const { return my.desc; } + int sourceType(); + QString sourceName(); + QString metricName(); + QString metricInstance(); + QmcContext *metricContext() { return my.context; } + + void setType(Type type) { my.type = type; } + bool isRoot() { return my.type == HostRoot || + my.type == LocalRoot || + my.type == ArchiveRoot; } + bool isLeaf() { return my.type == InstanceName || + my.type == LeafNullIndom; } + bool isMetric() { return my.type == LeafWithIndom || + my.type == LeafNullIndom; } + bool isNonLeaf() { return my.type == HostRoot || + my.type == LocalRoot || + my.type == ArchiveRoot || + my.type == NonLeafName; } + bool isInst() { return my.type == InstanceName; } + bool isChildMinder() { return my.type == ChildMinder; } + + void addToTree(QTreeWidget *, QString, int *); // add leaf node to Selected + void removeFromTree(QTreeWidget *); // remove leaf nodes from Selected set + + QColor currentColor() { return my.current; } + void setCurrentColor(QColor, QTreeWidget *); + QColor originalColor() { return my.original; } + void setOriginalColor(QColor original) { my.original = original; } + + QString label() const { return my.label; } + void setLabel(const QString &label) { my.label = label; } + + void setSelectable(bool selectable); + void setExpandable(bool expandable); + void setFailed(bool failed); + +private: + void expandMetricNames(QString, bool); + void expandInstanceNames(bool); + QString sourceTip(); + + bool cmp(NameSpace *); + NameSpace *dup(QTreeWidget *); // copies the root node in addToTree + NameSpace *dup(QTreeWidget *, NameSpace *, QString, int *); // all other nodes + + struct { + bool expanded; // pmGet{ChildrenStatus,Indom} done + pmDesc desc; // metric descriptor for metric leaves + QmcContext *context; // metrics class metric context + QIcon icon; + QColor current; // color we'll use if OK'd + QColor original; // color we started with + QString basename; + QString label; // proxy (live), host (archive), legend label + NameSpace *back; + NameSpace::Type type; + } my; +}; + +#endif // NAMESPACE_H diff --git a/src/pmchart/openviewdialog.cpp b/src/pmchart/openviewdialog.cpp new file mode 100644 index 0000000..d2b63e6 --- /dev/null +++ b/src/pmchart/openviewdialog.cpp @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2013, Red Hat. + * Copyright (c) 2007-2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include "openviewdialog.h" +#include <QtGui/QCompleter> +#include <QtGui/QFileDialog> +#include <QtGui/QMessageBox> +#include "main.h" +#include "hostdialog.h" + +OpenViewDialog::OpenViewDialog(QWidget *parent) : QDialog(parent) +{ + setupUi(this); + my.dirModel = new QDirModel; + my.dirModel->setIconProvider(fileIconProvider); + dirListView->setModel(my.dirModel); + + my.completer = new QCompleter; + my.completer->setModel(my.dirModel); + fileNameLineEdit->setCompleter(my.completer); + + connect(dirListView->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(dirListView_selectionChanged())); + + QDir dir; + QChar sep(__pmPathSeparator()); + QString sys = my.systemDir = pmGetConfig("PCP_VAR_DIR"); + my.systemDir.append(sep); + my.systemDir.append("config"); + my.systemDir.append(sep); + my.systemDir.append("kmchart"); + if (dir.exists(my.systemDir)) + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + my.systemDir); + my.systemDir = sys; + my.systemDir.append(sep); + my.systemDir.append("config"); + my.systemDir.append(sep); + my.systemDir.append("pmchart"); + if (dir.exists(my.systemDir)) + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + my.systemDir); + + QString home = my.userDir = QDir::toNativeSeparators(QDir::homePath()); + my.userDir.append(sep); + my.userDir.append(".pcp"); + my.userDir.append(sep); + my.userDir.append("kmchart"); + if (dir.exists(my.userDir)) + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + my.userDir); + my.userDir = home; + my.userDir.append(sep); + my.userDir.append(".pcp"); + my.userDir.append(sep); + my.userDir.append("pmchart"); + if (dir.exists(my.userDir)) + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + my.userDir); + + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + home); +} + +OpenViewDialog::~OpenViewDialog() +{ + delete my.completer; + delete my.dirModel; +} + +void OpenViewDialog::reset() +{ + if ((my.archiveSource = pmchart->isArchiveTab())) { + sourceLabel->setText(tr("Archive:")); + sourcePushButton->setIcon(QIcon(":/images/archive.png")); + } else { + sourceLabel->setText(tr("Host:")); + sourcePushButton->setIcon(QIcon(":/images/computer.png")); + } + setupComboBoxes(my.archiveSource); + setPath(my.systemDir); +} + +void OpenViewDialog::setPathUi(const QString &path) +{ + if (path.isEmpty()) + return; + + int index = pathComboBox->findText(path); + if (index == -1) { + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + path); + index = pathComboBox->count() - 1; + } + pathComboBox->setCurrentIndex(index); + dirListView->selectionModel()->clear(); + + userToolButton->setChecked(path == my.userDir); + systemToolButton->setChecked(path == my.systemDir); + + fileNameLineEdit->setModified(false); + fileNameLineEdit->clear(); +} + +void OpenViewDialog::setPath(const QModelIndex &index) +{ + console->post("OpenViewDialog::setPath QModelIndex path=%s", + (const char *)my.dirModel->filePath(index).toAscii()); + my.dirIndex = index; + my.dirModel->refresh(index); + dirListView->setRootIndex(index); + setPathUi(my.dirModel->filePath(index)); +} + +void OpenViewDialog::setPath(const QString &path) +{ + console->post("OpenViewDialog::setPath QString path=%s", + (const char *)path.toAscii()); + my.dirIndex = my.dirModel->index(path); + my.dirModel->refresh(my.dirIndex); + dirListView->setRootIndex(my.dirIndex); + setPathUi(path); +} + +void OpenViewDialog::pathComboBox_currentIndexChanged(QString path) +{ + console->post("OpenViewDialog::pathComboBox_currentIndexChanged"); + setPath(path); +} + +void OpenViewDialog::parentToolButton_clicked() +{ + console->post("OpenViewDialog::parentToolButton_clicked"); + setPath(my.dirModel->parent(my.dirIndex)); +} + +void OpenViewDialog::userToolButton_clicked(bool enabled) +{ + if (enabled) { + QDir dir; + if (!dir.exists(my.userDir)) + dir.mkpath(my.userDir); + setPath(my.userDir); + } +} + +void OpenViewDialog::systemToolButton_clicked(bool enabled) +{ + if (enabled) + setPath(my.systemDir); +} + +void OpenViewDialog::dirListView_selectionChanged() +{ + QItemSelectionModel *selections = dirListView->selectionModel(); + QModelIndexList selectedIndexes = selections->selectedIndexes(); + + console->post("OpenViewDialog::dirListView_clicked"); + + my.completer->setCompletionPrefix(my.dirModel->filePath(my.dirIndex)); + if (selectedIndexes.count() != 1) + fileNameLineEdit->setText(""); + else + fileNameLineEdit->setText(my.dirModel->fileName(selectedIndexes.at(0))); +} + +void OpenViewDialog::dirListView_activated(const QModelIndex &index) +{ + QFileInfo fi = my.dirModel->fileInfo(index); + + console->post("OpenViewDialog::dirListView_activated"); + + if (fi.isDir()) + setPath(index); + else if (fi.isFile()) { + QStringList files; + files << fi.absoluteFilePath(); + if (openViewFiles(files) == true) + done(0); + } +} + +int OpenViewDialog::setupArchiveComboBoxes() +{ + QIcon archiveIcon = fileIconProvider->icon(QedFileIconProvider::Archive); + QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer); + int index = 0; + + for (unsigned int i = 0; i < archiveGroup->numContexts(); i++) { + QmcSource source = archiveGroup->context(i)->source(); + sourceComboBox->insertItem(i, archiveIcon, source.source()); + if (i == archiveGroup->contextIndex()) + index = i; + } + return index; +} + +int OpenViewDialog::setupLiveComboBoxes() +{ + QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer); + int index = 0; + + for (unsigned int i = 0; i < liveGroup->numContexts(); i++) { + QmcSource source = liveGroup->context(i)->source(); + sourceComboBox->insertItem(i, hostIcon, source.host()); + if (i == liveGroup->contextIndex()) + index = i; + } + return index; +} + +void OpenViewDialog::setupComboBoxes(bool arch) +{ + // We block signals on the target combo boxes so that we don't + // send spurious signals out about their lists being changed. + // If we did that, we would keep changing the current context. + sourceComboBox->blockSignals(true); + sourceComboBox->clear(); + int index = arch ? setupArchiveComboBoxes() : setupLiveComboBoxes(); + sourceComboBox->setCurrentIndex(index); + sourceComboBox->blockSignals(false); +} + +void OpenViewDialog::archiveAdd() +{ + QFileDialog *af = new QFileDialog(this); + QStringList al; + int sts; + + af->setFileMode(QFileDialog::ExistingFiles); + af->setAcceptMode(QFileDialog::AcceptOpen); + af->setWindowTitle(tr("Add Archive")); + af->setIconProvider(fileIconProvider); + af->setDirectory(QDir::toNativeSeparators(QDir::homePath())); + + if (af->exec() == QDialog::Accepted) + al = af->selectedFiles(); + for (QStringList::Iterator it = al.begin(); it != al.end(); ++it) { + QString archive = *it; + if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) { + archive.prepend(tr("Cannot open PCP archive: ")); + archive.append(tr("\n")); + archive.append(tr(pmErrStr(sts))); + QMessageBox::warning(this, pmProgname, archive, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } else { + setupComboBoxes(true); + archiveGroup->updateBounds(); + QmcSource source = archiveGroup->context()->source(); + pmtime->addArchive(source.start(), source.end(), + source.timezone(), source.host(), false); + } + } + delete af; +} + +void OpenViewDialog::hostAdd() +{ + HostDialog *host = new HostDialog(this); + + if (host->exec() == QDialog::Accepted) { + QString hostspec = host->getHostSpecification(); + int sts, flags = host->getContextFlags(); + + if (hostspec == QString::null || hostspec.length() == 0) { + hostspec.append(tr("Hostname not specified\n")); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) { + hostspec.prepend(tr("Cannot connect to host: ")); + hostspec.append(tr("\n")); + hostspec.append(tr(pmErrStr(sts))); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else { + console->post(PmChart::DebugUi, + "OpenViewDialog::newHost: %s (flags=0x%x)", + (const char *)hostspec.toAscii(), flags); + setupComboBoxes(false); + } + } + delete host; +} + +void OpenViewDialog::sourcePushButton_clicked() +{ + if (my.archiveSource) + archiveAdd(); + else + hostAdd(); +} + +void OpenViewDialog::sourceComboBox_currentIndexChanged(int index) +{ + console->post("OpenViewDialog::sourceComboBox_currentIndexChanged %d", index); + if (my.archiveSource == false) + liveGroup->use((unsigned int)index); + else + archiveGroup->use((unsigned int)index); +} + +bool OpenViewDialog::useLiveContext(QString source) +{ + int sts; + + if ((sts = liveGroup->use(PM_CONTEXT_HOST, source)) < 0) { + QString msg; + msg.sprintf("Failed to connect to pmcd on \"%s\".\n%s.\n\n", + (const char *)source.toAscii(), pmErrStr(sts)); + QMessageBox::warning(NULL, pmProgname, msg, + QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + return false; + } + return true; +} + +bool OpenViewDialog::useArchiveContext(QString source) +{ + int sts; + + if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, source)) < 0) { + QString msg; + msg.sprintf("Failed to open archive \"%s\".\n%s.\n\n", + (const char *)source.toAscii(), pmErrStr(sts)); + QMessageBox::warning(NULL, pmProgname, msg, + QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + return false; + } + return true; +} + +bool OpenViewDialog::useComboBoxContext(bool arch) +{ + if (arch == false) + return useLiveContext(sourceComboBox->currentText()); + else + return useArchiveContext(sourceComboBox->currentText()); +} + +bool OpenViewDialog::openViewFiles(const QStringList &fl) +{ + QString msg; + bool result = true; + + if (pmchart->isArchiveTab() != my.archiveSource) { + if (pmchart->isArchiveTab()) + msg = tr("Cannot open Host View(s) in an Archive Tab\n"); + else + msg = tr("Cannot open Archive View(s) in a Host Tab\n"); + QMessageBox::warning(this, pmProgname, msg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + return false; + } + if (useComboBoxContext(my.archiveSource) == false) + return false; + QStringList files = fl; + for (QStringList::Iterator it = files.begin(); it != files.end(); ++it) + if (openView((const char *)(*it).toAscii()) == false) + result = false; + pmchart->enableUi(); + return result; +} + +void OpenViewDialog::openPushButton_clicked() +{ + QStringList files; + QString msg; + + if (fileNameLineEdit->isModified()) { + QString filename = fileNameLineEdit->text().trimmed(); + if (filename.isEmpty()) + msg = tr("No View file(s) specified"); + else { + QFileInfo f(filename); + if (f.isDir()) { + setPath(filename); + } + else if (f.exists()) { + files << filename; + if (openViewFiles(files) == true) + done(0); + } + else { + msg = tr("No such View file exists:\n"); + msg.append(filename); + } + } + } + else { + QItemSelectionModel *selections = dirListView->selectionModel(); + QModelIndexList selectedIndexes = selections->selectedIndexes(); + + if (selectedIndexes.count() == 0) + msg = tr("No View file(s) selected"); + else { + for (int i = 0; i < selectedIndexes.count(); i++) { + QString filename = my.dirModel->filePath(selectedIndexes.at(i)); + QFileInfo f(filename); + if (f.isDir()) + continue; + files << filename; + } + if (files.size() > 0) + if (openViewFiles(files) == true) + done(0); + } + } + + if (msg.isEmpty() == false) { + QMessageBox::warning(this, pmProgname, msg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } +} diff --git a/src/pmchart/openviewdialog.h b/src/pmchart/openviewdialog.h new file mode 100644 index 0000000..861fd34 --- /dev/null +++ b/src/pmchart/openviewdialog.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef OPENVIEWDIALOG_H +#define OPENVIEWDIALOG_H + +#include "ui_openviewdialog.h" +#include <QtGui/QDirModel> + +class OpenViewDialog : public QDialog, public Ui::OpenViewDialog +{ + Q_OBJECT + +public: + OpenViewDialog(QWidget* parent); + ~OpenViewDialog(); + + void reset(); + void sourceAdd(); + + static bool openView(const char *); + static void globals(int *w, int *h, int *pts, int *x, int *y); + +public slots: + virtual void parentToolButton_clicked(); + virtual void userToolButton_clicked(bool); + virtual void systemToolButton_clicked(bool); + virtual void pathComboBox_currentIndexChanged(QString); + virtual void dirListView_selectionChanged(); + virtual void dirListView_activated(const QModelIndex &); + + virtual void sourceComboBox_currentIndexChanged(int); + virtual void sourcePushButton_clicked(); + virtual void openPushButton_clicked(); + +private: + struct { + QString userDir; + QString systemDir; + bool archiveSource; + QDirModel *dirModel; + QModelIndex dirIndex; + QCompleter *completer; + } my; + + void setPath(const QString &); + void setPath(const QModelIndex &); + void setPathUi(const QString &); + + void setupComboBoxes(bool); + int setupLiveComboBoxes(); + int setupArchiveComboBoxes(); + + void hostAdd(); + void archiveAdd(); + + bool useLiveContext(QString); + bool useArchiveContext(QString); + bool useComboBoxContext(bool); + bool openViewFiles(const QStringList &); +}; + +#endif // OPENVIEWDIALOG_H diff --git a/src/pmchart/openviewdialog.ui b/src/pmchart/openviewdialog.ui new file mode 100644 index 0000000..19c82d6 --- /dev/null +++ b/src/pmchart/openviewdialog.ui @@ -0,0 +1,553 @@ +<ui version="4.0" > + <class>OpenViewDialog</class> + <widget class="QDialog" name="OpenViewDialog" > + <property name="windowModality" > + <enum>Qt::NonModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>469</width> + <height>283</height> + </rect> + </property> + <property name="windowTitle" > + <string>Open View</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/view.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="2" column="0" > + <layout class="QGridLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item row="1" column="1" > + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="4" > + <widget class="QPushButton" name="openPushButton" > + <property name="text" > + <string>Open</string> + </property> + <property name="autoDefault" > + <bool>false</bool> + </property> + <property name="default" > + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="2" > + <widget class="QComboBox" name="sourceComboBox" /> + </item> + <item row="2" column="4" > + <widget class="QPushButton" name="cancelPushButton" > + <property name="text" > + <string>Cancel</string> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="3" > + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>16</width> + <height>29</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="fileNameLabel" > + <property name="text" > + <string>File:</string> + </property> + <property name="alignment" > + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="sourceLabel" > + <property name="text" > + <string>Source:</string> + </property> + </widget> + </item> + <item row="1" column="4" > + <widget class="QPushButton" name="sourcePushButton" > + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/computer.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1" > + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2" > + <widget class="QLineEdit" name="fileNameLineEdit" /> + </item> + <item row="1" column="3" > + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>16</width> + <height>32</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="pathLabel" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>60</width> + <height>20</height> + </size> + </property> + <property name="text" > + <string>Path:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="pathComboBox" /> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>8</width> + <height>26</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="parentToolButton" > + <property name="minimumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="toolTip" > + <string>Parent</string> + </property> + <property name="statusTip" > + <string/> + </property> + <property name="whatsThis" > + <string>Open the parent directory</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="checkable" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>8</width> + <height>26</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="userToolButton" > + <property name="minimumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="toolTip" > + <string>User Views</string> + </property> + <property name="whatsThis" > + <string>Display your personal saved Views. A view is one or more charts.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/toolusers.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="checkable" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="systemToolButton" > + <property name="minimumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="toolTip" > + <string>System Views</string> + </property> + <property name="whatsThis" > + <string>Display preconfigured system Views. A view is one or more charts.</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/view.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="checkable" > + <bool>true</bool> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QListView" name="dirListView" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="selectionMode" > + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="iconSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="flow" > + <enum>QListView::TopToBottom</enum> + </property> + <property name="isWrapping" stdset="0" > + <bool>true</bool> + </property> + <property name="resizeMode" > + <enum>QListView::Adjust</enum> + </property> + <property name="gridSize" > + <size> + <width>150</width> + <height>20</height> + </size> + </property> + <property name="uniformItemSizes" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>fileNameLineEdit</sender> + <signal>returnPressed()</signal> + <receiver>openPushButton</receiver> + <slot>click()</slot> + <hints> + <hint type="sourcelabel" > + <x>255</x> + <y>194</y> + </hint> + <hint type="destinationlabel" > + <x>398</x> + <y>196</y> + </hint> + </hints> + </connection> + <connection> + <sender>dirListView</sender> + <signal>activated(QModelIndex)</signal> + <receiver>OpenViewDialog</receiver> + <slot>dirListView_activated(QModelIndex)</slot> + <hints> + <hint type="sourcelabel" > + <x>243</x> + <y>109</y> + </hint> + <hint type="destinationlabel" > + <x>409</x> + <y>196</y> + </hint> + </hints> + </connection> + <connection> + <sender>userToolButton</sender> + <signal>clicked(bool)</signal> + <receiver>OpenViewDialog</receiver> + <slot>userToolButton_clicked(bool)</slot> + <hints> + <hint type="sourcelabel" > + <x>392</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>systemToolButton</sender> + <signal>clicked(bool)</signal> + <receiver>OpenViewDialog</receiver> + <slot>systemToolButton_clicked(bool)</slot> + <hints> + <hint type="sourcelabel" > + <x>418</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>openPushButton</sender> + <signal>clicked()</signal> + <receiver>OpenViewDialog</receiver> + <slot>openPushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>388</x> + <y>197</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>pathComboBox</sender> + <signal>currentIndexChanged(QString)</signal> + <receiver>OpenViewDialog</receiver> + <slot>pathComboBox_currentIndexChanged(QString)</slot> + <hints> + <hint type="sourcelabel" > + <x>213</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>sourcePushButton</sender> + <signal>clicked()</signal> + <receiver>OpenViewDialog</receiver> + <slot>sourcePushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>388</x> + <y>229</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>cancelPushButton</sender> + <signal>clicked()</signal> + <receiver>OpenViewDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>388</x> + <y>295</y> + </hint> + <hint type="destinationlabel" > + <x>219</x> + <y>159</y> + </hint> + </hints> + </connection> + <connection> + <sender>parentToolButton</sender> + <signal>clicked()</signal> + <receiver>OpenViewDialog</receiver> + <slot>parentToolButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>307</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>211</x> + <y>145</y> + </hint> + </hints> + </connection> + <connection> + <sender>sourceComboBox</sender> + <signal>currentIndexChanged(int)</signal> + <receiver>OpenViewDialog</receiver> + <slot>sourceComboBox_currentIndexChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>185</x> + <y>206</y> + </hint> + <hint type="destinationlabel" > + <x>211</x> + <y>145</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/pmchart.cpp b/src/pmchart/pmchart.cpp new file mode 100644 index 0000000..c80c6f2 --- /dev/null +++ b/src/pmchart/pmchart.cpp @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2012-2014, Red Hat. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * Copyright (c) 2006-2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtCore/QUrl> +#include <QtCore/QTimer> +#include <QtCore/QLibraryInfo> +#include <QtGui/QDesktopServices> +#include <QtGui/QDesktopWidget> +#include <QtGui/QApplication> +#include <QtGui/QPrintDialog> +#include <QtGui/QMessageBox> +#include <QtGui/QWhatsThis> +#include <QtGui/QPainter> + +#include "main.h" +#include "pmchart.h" +#include "aboutdialog.h" +#include "chartdialog.h" +#include "exportdialog.h" +#include "infodialog.h" +#include "openviewdialog.h" +#include "recorddialog.h" +#include "searchdialog.h" +#include "samplesdialog.h" +#include "saveviewdialog.h" +#include "seealsodialog.h" +#include "settingsdialog.h" +#include "tabdialog.h" +#include "statusbar.h" + +PmChart::PmChart() : QMainWindow(NULL) +{ + my.dialogsSetup = false; + setIconSize(QSize(22, 22)); + + setupUi(this); + + my.statusBar = new StatusBar; + setStatusBar(my.statusBar); + connect(my.statusBar->timeFrame(), SIGNAL(clicked()), + this, SLOT(editSamples())); + connect(my.statusBar->timeButton(), SIGNAL(clicked()), + this, SLOT(optionsShowTimeControl())); + + my.timeAxisRightAlign = toolBar->height(); + toolBar->setAllowedAreas(Qt::RightToolBarArea | Qt::TopToolBarArea); + connect(toolBar, SIGNAL(orientationChanged(Qt::Orientation)), + this, SLOT(updateToolbarOrientation(Qt::Orientation))); + updateToolbarLocation(); + setupEnabledActionsList(); + if (!globalSettings.initialToolbar && !outfile) + toolBar->hide(); + + toolbarAction->setChecked(true); + my.toolbarHidden = !globalSettings.initialToolbar; + my.consoleHidden = true; + if (!pmDebug) + consoleAction->setVisible(false); + consoleAction->setChecked(false); + + if (outfile) + QTimer::singleShot(0, this, SLOT(exportFile())); + else + QTimer::singleShot(PmChart::defaultTimeout(), this, SLOT(timeout())); +} + +void PmChart::languageChange() +{ + retranslateUi(this); +} + +void PmChart::init(void) +{ + my.statusBar->init(); +} + +void PmChart::setupDialogs(void) +{ + // In order to speed startup times, we delay creation of these + // global dialogs until after the main window is displayed. We + // do NOT delay until the very last minute, otherwise we start + // to hit the same problem with the dialogs (if we create them + // on-demand there's a noticable delay after action selected). + + if (my.dialogsSetup) + return; + + my.info = new InfoDialog(this); + my.search = new SearchDialog(this); + my.newtab = new TabDialog(this); + my.edittab = new TabDialog(this); + my.samples = new SamplesDialog(this); + my.exporter = new ExportDialog(this); + my.newchart = new ChartDialog(this); + my.openview = new OpenViewDialog(this); + my.saveview = new SaveViewDialog(this); + my.settings = new SettingsDialog(this); + + connect(my.newtab->buttonOk, SIGNAL(clicked()), + this, SLOT(acceptNewTab())); + connect(my.edittab->buttonOk, SIGNAL(clicked()), + this, SLOT(acceptEditTab())); + connect(my.samples->buttonOk, SIGNAL(clicked()), + this, SLOT(acceptEditSamples())); + connect(my.exporter->buttonOk, SIGNAL(clicked()), + this, SLOT(acceptExport())); + my.dialogsSetup = true; +} + +void PmChart::quit() +{ + // End any processes we may have started and close any open dialogs + if (my.dialogsSetup) { + my.info->reject(); + } + if (pmtime) + pmtime->quit(); +#ifdef HAVE_UNSETENV + unsetenv("PCP_STDERR"); +#else + putenv("PCP_STDERR="); +#endif + pmflush(); +} + +void PmChart::setValueText(QString &string) +{ + my.statusBar->setValueText(string); + QTimer::singleShot(PmChart::defaultTimeout(), this, SLOT(timeout())); +} + +void PmChart::timeout() +{ + my.statusBar->clearValueText(); +} + +void PmChart::closeEvent(QCloseEvent *) +{ + quit(); +} + +void PmChart::enableUi(void) +{ + bool haveTabs = (chartTabWidget->size() > 1); + bool haveCharts = (activeTab()->gadgetCount() > 0); + bool haveLoggers = (activeTab()->isRecording()); + bool haveLiveHosts = (!activeTab()->isArchiveSource() && !Lflag); + + closeTabAction->setEnabled(haveTabs); + fileSaveViewAction->setEnabled(haveCharts); + fileExportAction->setEnabled(haveCharts); + filePrintAction->setEnabled(haveCharts); + editChartAction->setEnabled(haveCharts); + closeChartAction->setEnabled(haveCharts); + recordStartAction->setEnabled(haveCharts && haveLiveHosts && !haveLoggers); + recordQueryAction->setEnabled(haveLoggers); + recordStopAction->setEnabled(haveLoggers); + recordDetachAction->setEnabled(haveLoggers); + + zoomInAction->setEnabled(activeGroup->visibleHistory() > minimumPoints()); + zoomOutAction->setEnabled(activeGroup->visibleHistory() < maximumPoints()); +} + +void PmChart::updateBackground(void) +{ + liveGroup->updateBackground(); + archiveGroup->updateBackground(); +} + +int PmChart::defaultFontSize() +{ +#if defined(IS_DARWIN) + return 9; +#elif defined(IS_MINGW) + return 8; +#else + return 7; +#endif +} + +void PmChart::updateHeight(int adjustment) +{ + QSize newSize = size(); + int ideal = newSize.height() + adjustment; // may be negative + int sized = QApplication::desktop()->availableGeometry().height(); + +#ifdef DESPERATE + console->post(PmChart::DebugUi, + "updateHeight() oldsize h=%d,w=%d maximum=%d proposed=%d", + newSize.height(), newSize.width(), sized, ideal); +#endif + + if (ideal > sized) + ideal = sized; + newSize.setHeight(ideal); + resize(newSize); +} + +void PmChart::updateToolbarLocation() +{ + if (globalSettings.toolbarLocation) + addToolBar(Qt::RightToolBarArea, toolBar); + else + addToolBar(Qt::TopToolBarArea, toolBar); + setUnifiedTitleAndToolBarOnMac(globalSettings.nativeToolbar); +} + +void PmChart::updateToolbarOrientation(Qt::Orientation orientation) +{ + my.statusBar->setTimeAxisRightAlignment( + orientation == Qt::Vertical ? my.timeAxisRightAlign : 0); +} + +void PmChart::setButtonState(QedTimeButton::State state) +{ + my.statusBar->timeButton()->setButtonState(state); +} + +void PmChart::step(bool live, QmcTime::Packet *packet) +{ + if (live) + liveGroup->step(packet); + else + archiveGroup->step(packet); +} + +void PmChart::VCRMode(bool live, QmcTime::Packet *packet, bool drag) +{ + if (live) + liveGroup->VCRMode(packet, drag); + else + archiveGroup->VCRMode(packet, drag); +} + +void PmChart::timeZone(bool live, QmcTime::Packet *packet, char *tzdata) +{ + if (live) + liveGroup->setTimezone(packet, tzdata); + else + archiveGroup->setTimezone(packet, tzdata); +} + +void PmChart::setStyle(char *newlook) +{ + QApplication::setStyle(newlook); +} + +void PmChart::fileOpenView() +{ + setupDialogs(); + my.openview->reset(); + my.openview->show(); +} + +void PmChart::fileSaveView() +{ + // If we have one host only, we default to "host dynamic" views. + // Otherwise (multiple hosts), default to explicit host names. + int i, ngadgets = activeTab()->gadgetCount(); + bool hostDynamic = true; + for (i = 0; i < ngadgets; i++) { + Gadget *gadget = activeTab()->gadget(i); + if (gadget->hosts().size() > 1) + hostDynamic = false; + } + + setupDialogs(); + my.saveview->reset(hostDynamic); + my.saveview->show(); +} + +void PmChart::fileExport() +{ + setupDialogs(); + my.exporter->reset(); + my.exporter->show(); +} + +void PmChart::acceptExport() +{ + my.exporter->flush(); +} + +void PmChart::filePrint() +{ + QPrinter printer; + QString creator = QString("pmchart Version "); + + creator.append(pmGetConfig("PCP_VERSION")); + printer.setCreator(creator); + printer.setOrientation(QPrinter::Portrait); + printer.setDocName("pmchart.pdf"); + + QPrintDialog print(&printer, (QWidget *)this); + if (print.exec()) { + QPainter qp(&printer); + painter(&qp, printer.width(), printer.height(), false, false); + } +} + +void PmChart::updateFont(const QString &family, const QString &style, int size) +{ + int i, ngadgets = activeTab()->gadgetCount(); + + globalFont->setFamily(family); + globalFont->setPointSize(size); + globalFont->setStyle(QFont::StyleNormal); + if (style.contains("Italic")) + globalFont->setItalic(true); + if (globalSettings.fontStyle.contains("Bold")) + globalFont->setBold(true); + + for (i = 0; i < ngadgets; i++) + activeTab()->gadget(i)->resetFont(); + my.statusBar->resetFont(); +} + +void PmChart::painter(QPainter *qp, int pw, int ph, bool transparent, bool currentOnly) +{ + double scale_h = 0; + double scale_w = 0; + int i, ngadgets = activeTab()->gadgetCount(); + QSize size; + QRect rect; // used for print layout calculations + + qp->setFont(*globalFont); + + console->post("painter() pw=%d ph=%d ngadgets=%d", pw, ph, ngadgets); + for (i = 0; i < ngadgets; i++) { + Gadget *gadget = activeTab()->gadget(i); + if (currentOnly && gadget != activeTab()->currentGadget()) + continue; + size = gadget->size(); + console->post(" [%d] w=%d h=%d", i, size.width(), size.height()); + if (size.width() > scale_w) scale_w = size.width(); + scale_h += size.height(); + } + console->post(" final_w=%d final_h=%d", (int)scale_w, (int)scale_h); + // timeaxis is _always_ less narrow than the plot(s) + // datelabel is _never_ wider than the timeaxis + // so width calculation is done, just need to consider the heights + size = my.statusBar->timeAxis()->size(); + console->post(" timeaxis w=%d h=%d", size.width(), size.height()); + scale_h += size.height() - TIMEAXISFUDGE; + size = my.statusBar->dateLabel()->size(); + console->post(" datelabel w=%d h=%d", size.width(), size.height()); + scale_h += size.height(); + console->post(" final_w=%d final_h=%d", (int)scale_w, (int)scale_h); + if (scale_w/pw > scale_h/ph) { + // window width drives scaling + scale_w = pw / scale_w; + scale_h = scale_w; + } + else { + // window height drives scaling + scale_h = ph / scale_h; + scale_w = scale_h; + } + console->post(" final chart scale_w=%.2f scale_h=%.2f", scale_w, scale_h); + rect.setX(0); + rect.setY(0); + for (i = 0; i < ngadgets; i++) { + Gadget *gadget = activeTab()->gadget(i); + if (currentOnly && gadget != activeTab()->currentGadget()) + continue; + size = gadget->size(); + rect.setWidth((int)(size.width()*scale_w+0.5)); + rect.setHeight((int)(size.height()*scale_h+0.5)); + console->post(" [%d] @ (%d,%d) w=%d h=%d", i, rect.x(), rect.y(), rect.width(), rect.height()); + gadget->print(qp, rect, transparent); + rect.setY(rect.y()+rect.height()); + } + + // time axis + rect.setY(rect.y() - TIMEAXISFUDGE); + size = my.statusBar->timeAxis()->size(); + rect.setX(pw-(int)(size.width()*scale_w+0.5)); + rect.setWidth((int)(size.width()*scale_w+0.5)); + rect.setHeight((int)(size.height()*scale_h+0.5)); + console->post(" timeaxis @ (%d,%d) w=%d h=%d", rect.x(), rect.y(), rect.width(), rect.height()); + my.statusBar->timeAxis()->print(qp, rect, transparent); + rect.setY(rect.y()+rect.height()); + + // date label below time axis + size = my.statusBar->dateLabel()->size(); + rect.setX(pw-(int)(size.width()*scale_w+0.5)); + rect.setWidth((int)(size.width()*scale_w+0.5)); + rect.setHeight((int)(size.height()*scale_h+0.5)); + console->post(" datelabel @ (%d,%d) w=%d h=%d", rect.x(), rect.y(), rect.width(), rect.height()); + qp->drawText(rect, Qt::AlignRight, my.statusBar->dateText()); +} + +void PmChart::fileQuit() +{ + QApplication::exit(0); +} + +void PmChart::helpManual() +{ + bool ok; + QString documents("file://"); + QString separator = QString(__pmPathSeparator()); + documents.append(pmGetConfig("PCP_HTML_DIR")); + documents.append(separator).append("index.html"); + ok = QDesktopServices::openUrl(QUrl(documents, QUrl::TolerantMode)); + if (!ok) { + documents.prepend("Failed to open:\n"); + QMessageBox::warning(this, pmProgname, documents); + } +} + +void PmChart::helpAbout() +{ + AboutDialog about(this); + about.exec(); +} + +void PmChart::helpAboutQt() +{ + QApplication::aboutQt(); +} + +void PmChart::helpSeeAlso() +{ + SeeAlsoDialog seealso(this); + seealso.exec(); +} + +void PmChart::whatsThis() +{ + QWhatsThis::enterWhatsThisMode(); +} + +void PmChart::optionsShowTimeControl() +{ + if (activeTab()->isArchiveSource()) + pmtime->showArchiveTimeControl(); + else + pmtime->showLiveTimeControl(); +} + +void PmChart::optionsHideTimeControl() +{ + if (activeTab()->isArchiveSource()) + pmtime->hideArchiveTimeControl(); + else + pmtime->hideLiveTimeControl(); +} + +void PmChart::optionsToolbar() +{ + if (my.toolbarHidden) + toolBar->show(); + else + toolBar->hide(); + my.toolbarHidden = !my.toolbarHidden; +} + +void PmChart::optionsConsole() +{ + if (pmDebug) { + if (my.consoleHidden) + console->show(); + else + console->hide(); + my.consoleHidden = !my.consoleHidden; + } +} + +void PmChart::optionsNewPmchart() +{ + QProcess *buddy = new QProcess(this); + QStringList arguments; + QString port; + + port.setNum(pmtime->port()); + arguments << "-p" << port; + for (unsigned int i = 0; i < archiveGroup->numContexts(); i++) { + QmcSource source = archiveGroup->context(i)->source(); + arguments << "-a" << source.source(); + } + for (unsigned int i = 0; i < liveGroup->numContexts(); i++) { + QmcSource source = liveGroup->context(i)->source(); + arguments << "-h" << source.source(); + } + if (Lflag) + arguments << "-L"; + buddy->start("pmchart", arguments); +} + +// +// Note: Called from both Apply and OK on New Chart dialog +// Must only called when the changes are definately going to +// be applied, so all input validation must be done by now. +// +Chart *PmChart::acceptNewChart() +{ + bool yAutoScale; + double yMin, yMax; + QString scheme; + int sequence; + + Chart *cp = new Chart(activeTab(), activeTab()->splitter()); + activeGroup->addGadget(cp); + activeTab()->addGadget(cp); + + QString newTitle = my.newchart->title().trimmed(); + if (newTitle.isEmpty() == false) + cp->changeTitle(newTitle, true); + cp->setLegendVisible(my.newchart->legend()); + cp->setAntiAliasing(my.newchart->antiAliasing()); + cp->setRateConvert(my.newchart->rateConvert()); + my.newchart->updateChartPlots(cp); + my.newchart->scale(&yAutoScale, &yMin, &yMax); + cp->setScale(yAutoScale, yMin, yMax); + my.newchart->scheme(&scheme, &sequence); + cp->setScheme(scheme, sequence); + + activeGroup->setupWorldView(); + activeTab()->showGadgets(); + + enableUi(); + return cp; +} + +void PmChart::fileNewChart() +{ + setupDialogs(); + my.newchart->reset(); + my.newchart->show(); +} + +void PmChart::editChart() +{ + Chart *cp = (Chart *)activeTab()->currentGadget(); + + setupDialogs(); + my.newchart->reset(cp); + my.newchart->show(); +} + +void PmChart::acceptEditChart() +{ + bool yAutoScale; + double yMin, yMax; + QString scheme; + int sequence; + + Chart *cp = my.newchart->chart(); + QString editTitle = my.newchart->title().trimmed(); + if (editTitle.isEmpty() == false && editTitle != cp->title()) + cp->changeTitle(editTitle, true); + cp->setLegendVisible(my.newchart->legend()); + cp->setAntiAliasing(my.newchart->antiAliasing()); + cp->setRateConvert(my.newchart->rateConvert()); + my.newchart->scale(&yAutoScale, &yMin, &yMax); + cp->setScale(yAutoScale, yMin, yMax); + my.newchart->updateChartPlots(cp); + my.newchart->scheme(&scheme, &sequence); + cp->setScheme(scheme, sequence); + cp->replot(); + cp->show(); + + enableUi(); +} + +void PmChart::closeChart() +{ + activeTab()->deleteCurrent(); + enableUi(); +} + +void PmChart::metricInfo(QString src, QString m, QString inst, int srcType) +{ + setupDialogs(); + my.info->reset(src, m, inst, srcType); + my.info->show(); +} + +void PmChart::metricSearch(QTreeWidget *pmns) +{ + setupDialogs(); + my.search->reset(pmns); + my.search->show(); +} + +void PmChart::editSamples() +{ + int samples = activeGroup->sampleHistory(); + int visible = activeGroup->visibleHistory(); + + setupDialogs(); + my.samples->reset(samples, visible); + my.samples->show(); +} + +void PmChart::acceptEditSamples() +{ + activeGroup->setSampleHistory(my.samples->samples()); + activeGroup->setVisibleHistory(my.samples->visible()); + activeGroup->setupWorldView(); + activeTab()->showGadgets(); +} + +void PmChart::editTab() +{ + setupDialogs(); + my.edittab->reset(chartTabWidget->tabText(chartTabWidget->currentIndex()), + activeGroup->isArchiveSource() == false); + my.edittab->show(); +} + +void PmChart::acceptEditTab() +{ + QString label = my.edittab->label(); + chartTabWidget->setTabText(chartTabWidget->currentIndex(), label); +} + +void PmChart::createNewTab(bool live) +{ + setupDialogs(); + my.newtab->reset(QString::null, live); + my.newtab->show(); +} + +void PmChart::addTab() +{ + createNewTab(isArchiveTab() == false); +} + +void PmChart::acceptNewTab() +{ + Tab *tab = new Tab; + QString label = my.newtab->labelLineEdit->text().trimmed(); + + if (my.newtab->isArchiveSource()) + tab->init(chartTabWidget, archiveGroup, label); + else + tab->init(chartTabWidget, liveGroup, label); + chartTabWidget->insertTab(tab); + enableUi(); +} + +void PmChart::addActiveTab(Tab *tab) +{ + chartTabWidget->insertTab(tab); + setActiveTab(chartTabWidget->size() - 1, true); + enableUi(); +} + +void PmChart::zoomIn() +{ + int visible = activeGroup->visibleHistory(); + int samples = activeGroup->sampleHistory(); + int decrease = qMax(qMin((int)((double)samples / 10), visible/2), 1); + + console->post("zoomIn: vis=%d s=%d dec=%d\n", visible, samples, decrease); + + visible = qMax(visible - decrease, minimumPoints()); + activeGroup->setVisibleHistory(visible); + activeGroup->setupWorldView(); + activeTab()->showGadgets(); + + zoomInAction->setEnabled(visible > minimumPoints()); + zoomOutAction->setEnabled(visible < samples); +} + +void PmChart::zoomOut() +{ + int visible = activeGroup->visibleHistory(); + int samples = activeGroup->sampleHistory(); + int increase = qMax(qMin((int)((double)samples / 10), visible/2), 1); + + console->post("zoomOut: vis=%d s=%d dec=%d\n", visible, samples, increase); + + visible = qMin(visible + increase, samples); + activeGroup->setVisibleHistory(visible); + activeGroup->setupWorldView(); + activeTab()->showGadgets(); + + zoomInAction->setEnabled(visible > minimumPoints()); + zoomOutAction->setEnabled(visible < samples); +} + +bool PmChart::isTabRecording() +{ + return activeTab()->isRecording(); +} + +bool PmChart::isArchiveTab() +{ + return activeTab()->isArchiveSource(); +} + +void PmChart::closeTab() +{ + int index = chartTabWidget->currentIndex(); + + chartTabWidget->removeTab(index); + if (index > 0) + index--; + setActiveTab(index, false); + enableUi(); +} + +void PmChart::setActiveTab(int index, bool redisplay) +{ + console->post("PmChart::setActiveTab index=%d r=%d", index, redisplay); + + if (chartTabWidget->setActiveTab(index) == true) + activeGroup = archiveGroup; + else + activeGroup = liveGroup; + activeGroup->updateTimeButton(); + activeGroup->updateTimeAxis(); + + if (redisplay) + chartTabWidget->setCurrentIndex(index); +} + +void PmChart::activeTabChanged(int index) +{ + if (index < chartTabWidget->size()) + setActiveTab(index, false); + enableUi(); +} + +void PmChart::editSettings() +{ + setupDialogs(); + my.settings->reset(); + my.settings->show(); +} + +void PmChart::setDateLabel(time_t seconds, QString tz) +{ + char datestring[32]; + QString label; + + if (seconds) { + pmCtime(&seconds, datestring); + label = tr(datestring); + label.remove(10, 9); + label.replace(15, 1, " "); + label.append(tz); + } + else { + label = tr(""); + } + my.statusBar->setDateText(label); +} + +void PmChart::setDateLabel(QString label) +{ + my.statusBar->setDateText(label); +} + +void PmChart::setRecordState(bool record) +{ + liveGroup->newButtonState(liveGroup->pmtimeState(), + QmcTime::NormalMode, record); + setButtonState(liveGroup->buttonState()); + enableUi(); +} + +void PmChart::recordStart() +{ + if (activeTab()->startRecording()) + setRecordState(true); +} + +void PmChart::recordStop() +{ + activeTab()->stopRecording(); +} + +void PmChart::recordQuery() +{ + activeTab()->queryRecording(); +} + +void PmChart::recordDetach() +{ + activeTab()->detachLoggers(); +} + +QList<QAction*> PmChart::toolbarActionsList() +{ + return my.toolbarActionsList; +} + +QList<QAction*> PmChart::enabledActionsList() +{ + return my.enabledActionsList; +} + +void PmChart::setupEnabledActionsList() +{ + // ToolbarActionsList is a list of all Actions available. + // The SeparatorsList contains Actions that are group "breaks", and + // which must be followed by a separator (if they are not the final + // action in the toolbar, of course). + // Finally the enabledActionsList lists the default enabled Actions. + + my.toolbarActionsList << fileNewChartAction + << editChartAction << closeChartAction; + my.toolbarActionsList << fileOpenViewAction << fileSaveViewAction; + addSeparatorAction(); // end of chart/view group + my.toolbarActionsList << fileExportAction << filePrintAction; + addSeparatorAction(); // end exported formats + my.toolbarActionsList << addTabAction << editTabAction << closeTabAction; + my.toolbarActionsList << zoomInAction << zoomOutAction; + addSeparatorAction(); // end tab group + my.toolbarActionsList << recordStartAction << recordStopAction; + addSeparatorAction(); // end recording group + my.toolbarActionsList << editSettingsAction; + addSeparatorAction(); // end settings group + my.toolbarActionsList << showTimeControlAction << hideTimeControlAction + << newPmchartAction; + addSeparatorAction(); // end other processes + my.toolbarActionsList << helpManualAction << helpWhatsThisAction; + + // needs to match pmchart.ui + my.enabledActionsList << fileNewChartAction << fileOpenViewAction + // separator + << zoomInAction << zoomOutAction + // separator + << fileExportAction + // separator + << showTimeControlAction << newPmchartAction; + + if (globalSettings.toolbarActions.size() > 0) { + setEnabledActionsList(globalSettings.toolbarActions, false); + updateToolbarContents(); + } +} + +void PmChart::addSeparatorAction() +{ + int index = my.toolbarActionsList.size() - 1; + my.separatorsList << my.toolbarActionsList.at(index); +} + +void PmChart::updateToolbarContents() +{ + bool needSeparator = false; + + toolBar->clear(); + for (int i = 0; i < my.toolbarActionsList.size(); i++) { + QAction *action = my.toolbarActionsList.at(i); + if (my.enabledActionsList.contains(action)) { + toolBar->addAction(action); + if (needSeparator) { + toolBar->insertSeparator(action); + needSeparator = false; + } + } + if (my.separatorsList.contains(action)) + needSeparator = true; + } +} + +void PmChart::setEnabledActionsList(QStringList tools, bool redisplay) +{ + my.enabledActionsList.clear(); + for (int i = 0; i < my.toolbarActionsList.size(); i++) { + QAction *action = my.toolbarActionsList.at(i); + if (tools.contains(action->iconText())) + my.enabledActionsList.append(action); + } + + if (redisplay) { + my.toolbarHidden = (my.enabledActionsList.size() == 0); + toolbarAction->setChecked(my.toolbarHidden); + if (my.toolbarHidden) + toolBar->hide(); + else + toolBar->show(); + } +} + +void PmChart::newScheme() +{ + my.settings->newScheme(); +} + +void PmChart::newScheme(QString cs) +{ + my.newchart->setCurrentScheme(cs); + my.newchart->setupSchemeComboBox(); +} + +void PmChart::exportFile() +{ + int sts = ExportDialog::exportFile(outfile, outgeometry, Wflag == 0); + QApplication::exit(sts); +} diff --git a/src/pmchart/pmchart.desktop b/src/pmchart/pmchart.desktop new file mode 100644 index 0000000..813a72e --- /dev/null +++ b/src/pmchart/pmchart.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=PCP Charts +Comment=Strip Chart tool for plotting Performance Co-Pilot metrics +Exec=pmchart +Icon=pmchart +Terminal=false +Type=Application +Categories=System;Utility;Network;Qt; diff --git a/src/pmchart/pmchart.h b/src/pmchart/pmchart.h new file mode 100644 index 0000000..381ccdc --- /dev/null +++ b/src/pmchart/pmchart.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2012-2014, Red Hat. + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef PMCHART_H +#define PMCHART_H + +#include "ui_pmchart.h" +#include "statusbar.h" +#include <qmc_time.h> + +class TimeAxis; +class NameSpace; +class TabDialog; +class InfoDialog; +class ChartDialog; +class ExportDialog; +class SearchDialog; +class SamplesDialog; +class OpenViewDialog; +class SaveViewDialog; +class SettingsDialog; + +class PmChart : public QMainWindow, public Ui::PmChart +{ + Q_OBJECT + +public: + PmChart(); + + typedef enum { + DebugApp = 0x1, + DebugUi = 0x1, + DebugProtocol = 0x2, + DebugView = 0x4, + DebugTimeless = 0x8, + DebugForce = 0x10, + } DebugOptions; + + static int defaultFontSize(); + static const char *defaultFontFamily() { return "Sans Serif"; } + static double defaultChartDelta() { return 1.0; } // seconds + static double defaultLoggerDelta() { return 1.0; } + static int defaultVisibleHistory() { return 60; } // points + static int defaultSampleHistory() { return 180; } + static int defaultTimeout() { return 8000; } // milliseconds + static int minimumPoints() { return 2; } + static int maximumPoints() { return 720; } + static int maximumLegendLength() { return 120; } // chars + static int minimumChartHeight() { return 80; } // pixels + + Tab *activeTab() { return chartTabWidget->activeTab(); } + void setActiveTab(int index, bool redisplay); + void addActiveTab(Tab *tab); + bool isArchiveTab(); + bool isTabRecording(); + TabWidget *tabWidget() { return chartTabWidget; } + TimeAxis *timeAxis() { return my.statusBar->timeAxis(); } + QLabel *dateLabel() { return my.statusBar->dateLabel(); } + Chart *acceptNewChart(); + + virtual void step(bool livemode, QmcTime::Packet *pmtime); + virtual void VCRMode(bool livemode, QmcTime::Packet *pmtime, bool drag); + virtual void timeZone(bool livemode, QmcTime::Packet *pmtime, char *tzdata); + virtual void setStyle(char *style); + virtual void updateHeight(int); + virtual void metricInfo(QString src, QString m, QString inst, int srcType); + virtual void metricSearch(QTreeWidget *pmns); + virtual void createNewTab(bool liveMode); + virtual void setValueText(QString &text); + virtual void setDateLabel(QString label); + virtual void setDateLabel(time_t seconds, QString tz); + virtual void setButtonState(QedTimeButton::State state); + virtual void setRecordState(bool recording); + + virtual void updateToolbarContents(); + virtual void updateToolbarLocation(); + virtual QList<QAction*> toolbarActionsList(); + virtual QList<QAction*> enabledActionsList(); + virtual void setupEnabledActionsList(); + virtual void addSeparatorAction(); + virtual void setEnabledActionsList(QStringList tools, bool redisplay); + + virtual void newScheme(); // request new scheme of settings dialog + virtual void newScheme(QString); // reply back to requesting dialog(s) + virtual void updateBackground(); + virtual void updateFont(const QString &family, const QString &style, int size); + + void painter(QPainter *qp, int pw, int ph, bool transparent, bool currentOnly); + + // Adjusted height for exporting images (without UI elements) + int exportHeight() + { return height() - menuBar()->height() - toolBar->height(); } + +public slots: + virtual void init(); + virtual void quit(); + virtual void enableUi(); + virtual void exportFile(); + virtual void setupDialogs(); + virtual void fileOpenView(); + virtual void fileSaveView(); + virtual void fileExport(); + virtual void acceptExport(); + virtual void filePrint(); + virtual void fileQuit(); + virtual void helpManual(); + virtual void helpAbout(); + virtual void helpAboutQt(); + virtual void helpSeeAlso(); + virtual void whatsThis(); + virtual void optionsShowTimeControl(); + virtual void optionsHideTimeControl(); + virtual void optionsToolbar(); + virtual void optionsConsole(); + virtual void optionsNewPmchart(); + virtual void fileNewChart(); + virtual void editChart(); + virtual void acceptEditChart(); + virtual void closeChart(); + virtual void editTab(); + virtual void acceptEditTab(); + virtual void editSamples(); + virtual void acceptEditSamples(); + virtual void addTab(); + virtual void acceptNewTab(); + virtual void closeTab(); + virtual void activeTabChanged(int); + virtual void editSettings(); + virtual void recordStart(); + virtual void recordQuery(); + virtual void recordStop(); + virtual void recordDetach(); + virtual void timeout(); + virtual void zoomIn(); + virtual void zoomOut(); + virtual void updateToolbarOrientation(Qt::Orientation); + +protected slots: + virtual void languageChange(); + virtual void closeEvent(QCloseEvent *); + +private: + struct { + bool dialogsSetup; + bool toolbarHidden; + bool consoleHidden; + + TabDialog *newtab; + TabDialog *edittab; + InfoDialog *info; + ChartDialog *newchart; // shared by New and Edit Chart actions + ExportDialog *exporter; + SearchDialog *search; + SamplesDialog *samples; + OpenViewDialog *openview; + SaveViewDialog *saveview; + SettingsDialog *settings; + + QList<QAction*> separatorsList; // separator follow these + QList<QAction*> toolbarActionsList; // all toolbar actions + QList<QAction*> enabledActionsList; // currently visible actions + + int timeAxisRightAlign; + StatusBar *statusBar; + } my; + + void editTab(int index); +}; + +#endif // PMCHART_H diff --git a/src/pmchart/pmchart.info.in b/src/pmchart/pmchart.info.in new file mode 100644 index 0000000..c9334c6 --- /dev/null +++ b/src/pmchart/pmchart.info.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.9"> +<dict> + <key>CFBundleIconFile</key> + <string>pmchart.icns</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleGetInfoString</key> + <string>PACKAGE_VERSION</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleExecutable</key> + <string>pmchart</string> + <key>CFBundleIdentifier</key> + <string>com.aconex.pmchart</string> +</dict> +</plist> diff --git a/src/pmchart/pmchart.pro b/src/pmchart/pmchart.pro new file mode 100644 index 0000000..0068be9 --- /dev/null +++ b/src/pmchart/pmchart.pro @@ -0,0 +1,45 @@ +TEMPLATE = app +LANGUAGE = C++ +HEADERS = pmchart.h main.h \ + aboutdialog.h chartdialog.h exportdialog.h \ + hostdialog.h infodialog.h openviewdialog.h \ + recorddialog.h samplesdialog.h saveviewdialog.h \ + searchdialog.h seealsodialog.h settingsdialog.h \ + tab.h tabdialog.h \ + chart.h colorbutton.h colorscheme.h statusbar.h \ + namespace.h \ + tabwidget.h timeaxis.h timecontrol.h \ + groupcontrol.h gadget.h sampling.h tracing.h +SOURCES = pmchart.cpp main.cpp \ + aboutdialog.cpp chartdialog.cpp exportdialog.cpp \ + hostdialog.cpp infodialog.cpp openviewdialog.cpp \ + recorddialog.cpp samplesdialog.cpp saveviewdialog.cpp \ + searchdialog.cpp seealsodialog.cpp settingsdialog.cpp \ + tab.cpp tabdialog.cpp \ + chart.cpp colorbutton.cpp colorscheme.cpp statusbar.cpp \ + namespace.cpp \ + tabwidget.cpp timeaxis.cpp timecontrol.cpp \ + groupcontrol.cpp gadget.cpp sampling.cpp tracing.cpp \ + view.cpp +FORMS = aboutdialog.ui chartdialog.ui exportdialog.ui \ + hostdialog.ui infodialog.ui pmchart.ui openviewdialog.ui \ + recorddialog.ui samplesdialog.ui saveviewdialog.ui \ + searchdialog.ui seealsodialog.ui settingsdialog.ui \ + tabdialog.ui +ICON = pmchart.icns +RC_FILE = pmchart.rc +RESOURCES = pmchart.qrc +INCLUDEPATH += ../include +INCLUDEPATH += ../libpcp_qed/src ../libpcp_qmc/src ../libpcp_qwt/src +CONFIG += qt warn_on +release:DESTDIR = build/debug +debug:DESTDIR = build/release +LIBS += -L../libpcp/src +LIBS += -L../libpcp_qed/src -L../libpcp_qed/src/$$DESTDIR +LIBS += -L../libpcp_qmc/src -L../libpcp_qmc/src/$$DESTDIR +LIBS += -L../libpcp_qwt/src -L../libpcp_qwt/src/$$DESTDIR +LIBS += -lpcp_qed -lpcp_qmc -lpcp_qwt -lpcp +win32:LIBS += -lwsock32 +QT += network svg +QMAKE_INFO_PLIST = pmchart.info +QMAKE_CXXFLAGS += $$(PCP_CFLAGS) diff --git a/src/pmchart/pmchart.qrc b/src/pmchart/pmchart.qrc new file mode 100644 index 0000000..0d057f5 --- /dev/null +++ b/src/pmchart/pmchart.qrc @@ -0,0 +1,60 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>images/aboutpcp.png</file> + <file>images/aboutpmchart.png</file> + <file>images/camera-video.png</file> + <file>images/camera-video-close.png</file> + <file>images/document-new.png</file> + <file>images/document-open.png</file> + <file>images/document-close.png</file> + <file>images/document-print.png</file> + <file>images/document-export.png</file> + <file>images/document-properties.png</file> + <file>images/document-save.png</file> + <file>images/pmtime.png</file> + <file>images/pmtime-close.png</file> + <file>images/edit-clear.png</file> + <file>images/emblem-system.png</file> + <file>images/whatsthis.png</file> + <file>images/computer.png</file> + <file>images/process-stop.png</file> + <file>images/go-previous.png</file> + <file>images/system-search.png</file> + <file>images/help-browser.png</file> + <file>images/help-contents.png</file> + <file>images/go-jump.png</file> + <file>images/pmchart.png</file> + <file>images/archive.png</file> + <file>images/filearchive.png</file> + <file>images/filegeneric.png</file> + <file>images/filehtml.png</file> + <file>images/fileimage.png</file> + <file>images/filefolder.png</file> + <file>images/filepackage.png</file> + <file>images/filespreadsheet.png</file> + <file>images/fileview.png</file> + <file>images/filewordprocessor.png</file> + <file>images/settings.png</file> + <file>images/folio.png</file> + <file>images/filefolio.png</file> + <file>images/view.png</file> + <file>images/logfile.png</file> + <file>images/tab-new.png</file> + <file>images/tab-edit.png</file> + <file>images/tab-close.png</file> + <file>images/toolusers.png</file> + <file>images/zoom-in.png</file> + <file>images/zoom-out.png</file> + <file>images/play_live.png</file> + <file>images/stop_live.png</file> + <file>images/play_record.png</file> + <file>images/stop_record.png</file> + <file>images/play_archive.png</file> + <file>images/stop_archive.png</file> + <file>images/back_archive.png</file> + <file>images/stepfwd_archive.png</file> + <file>images/stepback_archive.png</file> + <file>images/fastfwd_archive.png</file> + <file>images/fastback_archive.png</file> +</qresource> +</RCC> diff --git a/src/pmchart/pmchart.rc b/src/pmchart/pmchart.rc new file mode 100644 index 0000000..438b694 --- /dev/null +++ b/src/pmchart/pmchart.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "images/pmchart.ico" diff --git a/src/pmchart/pmchart.sh.in b/src/pmchart/pmchart.sh.in new file mode 100755 index 0000000..9c9054d --- /dev/null +++ b/src/pmchart/pmchart.sh.in @@ -0,0 +1,2 @@ +#!/bin/sh +exec PKG_MAC_DIR/MacOS/pmchart "$@" diff --git a/src/pmchart/pmchart.ui b/src/pmchart/pmchart.ui new file mode 100644 index 0000000..094be26 --- /dev/null +++ b/src/pmchart/pmchart.ui @@ -0,0 +1,989 @@ +<ui version="4.0" > + <class>PmChart</class> + <widget class="QMainWindow" name="PmChart" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>370</width> + <height>270</height> + </rect> + </property> + <property name="minimumSize" > + <size> + <width>182</width> + <height>196</height> + </size> + </property> + <property name="windowTitle" > + <string>PCP Charts</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>22</width> + <height>22</height> + </size> + </property> + <widget class="QWidget" name="widget" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="TabWidget" name="chartTabWidget" > + <property name="focusPolicy" > + <enum>Qt::NoFocus</enum> + </property> + <property name="whatsThis" > + <string>The main chart canvas, displaying all charts in the current Tab.</string> + </property> + <property name="tabShape" > + <enum>QTabWidget::Rounded</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>2</number> + </property> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="MenuBar" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>370</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="Help" > + <property name="title" > + <string>&Help</string> + </property> + <addaction name="helpManualAction" /> + <addaction name="separator" /> + <addaction name="helpAboutAction" /> + <addaction name="helpAboutQtAction" /> + <addaction name="helpSeeAlsoAction" /> + <addaction name="separator" /> + <addaction name="helpWhatsThisAction" /> + </widget> + <widget class="QMenu" name="Time" > + <property name="title" > + <string>&Options</string> + </property> + <addaction name="showTimeControlAction" /> + <addaction name="hideTimeControlAction" /> + <addaction name="toolbarAction" /> + <addaction name="consoleAction" /> + <addaction name="separator" /> + <addaction name="newPmchartAction" /> + </widget> + <widget class="QMenu" name="Record" > + <property name="title" > + <string>&Record</string> + </property> + <addaction name="recordStartAction" /> + <addaction name="recordQueryAction" /> + <addaction name="recordStopAction" /> + <addaction name="separator" /> + <addaction name="recordDetachAction" /> + </widget> + <widget class="QMenu" name="File" > + <property name="title" > + <string>&File</string> + </property> + <addaction name="fileNewChartAction" /> + <addaction name="closeChartAction" /> + <addaction name="fileOpenViewAction" /> + <addaction name="addTabAction" /> + <addaction name="closeTabAction" /> + <addaction name="separator" /> + <addaction name="fileSaveViewAction" /> + <addaction name="separator" /> + <addaction name="fileExportAction" /> + <addaction name="filePrintAction" /> + <addaction name="separator" /> + <addaction name="fileQuitAction" /> + </widget> + <widget class="QMenu" name="Edit" > + <property name="title" > + <string>&Edit</string> + </property> + <addaction name="editChartAction" /> + <addaction name="editTabAction" /> + <addaction name="separator" /> + <addaction name="editSamplesAction" /> + <addaction name="zoomInAction" /> + <addaction name="zoomOutAction" /> + <addaction name="separator" /> + <addaction name="editSettingsAction" /> + </widget> + <addaction name="File" /> + <addaction name="Edit" /> + <addaction name="Record" /> + <addaction name="Time" /> + <addaction name="separator" /> + <addaction name="Help" /> + </widget> + <widget class="QToolBar" name="toolBar" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>48</width> + <height>36</height> + </size> + </property> + <property name="whatsThis" > + <string>Configurable toolbar, use the Preferences dialog to change its contents</string> + </property> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <attribute name="toolBarArea" > + <number>4</number> + </attribute> + <addaction name="fileNewChartAction" /> + <addaction name="fileOpenViewAction" /> + <addaction name="separator" /> + <addaction name="zoomInAction" /> + <addaction name="zoomOutAction" /> + <addaction name="separator" /> + <addaction name="fileExportAction" /> + <addaction name="separator" /> + <addaction name="showTimeControlAction" /> + <addaction name="newPmchartAction" /> + </widget> + <action name="fileOpenViewAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/view.png</normaloff>:/images/view.png</iconset> + </property> + <property name="text" > + <string>&Open View...</string> + </property> + <property name="iconText" > + <string>Open View</string> + </property> + <property name="shortcut" > + <string>Ctrl+O</string> + </property> + </action> + <action name="fileSaveViewAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-save.png</normaloff>:/images/document-save.png</iconset> + </property> + <property name="text" > + <string>&Save View ...</string> + </property> + <property name="iconText" > + <string>Save View</string> + </property> + <property name="shortcut" > + <string>Ctrl+S</string> + </property> + </action> + <action name="fileExportAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-export.png</normaloff>:/images/document-export.png</iconset> + </property> + <property name="text" > + <string>&Export...</string> + </property> + <property name="iconText" > + <string>Export</string> + </property> + <property name="shortcut" > + <string>Ctrl+E</string> + </property> + </action> + <action name="filePrintAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-print.png</normaloff>:/images/document-print.png</iconset> + </property> + <property name="text" > + <string>&Print...</string> + </property> + <property name="iconText" > + <string>Print</string> + </property> + <property name="shortcut" > + <string>Ctrl+P</string> + </property> + </action> + <action name="fileQuitAction" > + <property name="text" > + <string>&Quit</string> + </property> + <property name="iconText" > + <string>Quit</string> + </property> + <property name="shortcut" > + <string>Ctrl+Q</string> + </property> + </action> + <action name="newPmchartAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset> + </property> + <property name="text" > + <string>New &pmchart</string> + </property> + <property name="iconText" > + <string>New pmchart</string> + </property> + </action> + <action name="showTimeControlAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/pmtime.png</normaloff>:/images/pmtime.png</iconset> + </property> + <property name="text" > + <string>Show Time</string> + </property> + </action> + <action name="hideTimeControlAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/pmtime-close.png</normaloff>:/images/pmtime-close.png</iconset> + </property> + <property name="text" > + <string>Hide Time</string> + </property> + </action> + <action name="toolbarAction" > + <property name="checkable" > + <bool>true</bool> + </property> + <property name="text" > + <string>Toolbar</string> + </property> + </action> + <action name="consoleAction" > + <property name="checkable" > + <bool>true</bool> + </property> + <property name="text" > + <string>Console</string> + </property> + </action> + <action name="editChartAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-properties.png</normaloff>:/images/document-properties.png</iconset> + </property> + <property name="text" > + <string>&Chart...</string> + </property> + <property name="iconText" > + <string>Edit Chart</string> + </property> + </action> + <action name="editTabAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/tab-edit.png</normaloff>:/images/tab-edit.png</iconset> + </property> + <property name="text" > + <string>Tab...</string> + </property> + <property name="iconText" > + <string>Edit Tab</string> + </property> + </action> + <action name="editSamplesAction" > + <property name="text" > + <string>Samples...</string> + </property> + <property name="iconText" > + <string>Edit Samples</string> + </property> + </action> + <action name="editSettingsAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset> + </property> + <property name="text" > + <string>Preferences...</string> + </property> + <property name="iconText" > + <string>Preferences</string> + </property> + </action> + <action name="closeTabAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/tab-close.png</normaloff>:/images/tab-close.png</iconset> + </property> + <property name="text" > + <string>Close Tab</string> + </property> + <property name="iconText" > + <string>Close Tab</string> + </property> + <property name="shortcut" > + <string>Ctrl+W</string> + </property> + </action> + <action name="fileNewChartAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-new.png</normaloff>:/images/document-new.png</iconset> + </property> + <property name="text" > + <string>&New Chart...</string> + </property> + <property name="iconText" > + <string>New Chart</string> + </property> + </action> + <action name="addTabAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/tab-new.png</normaloff>:/images/tab-new.png</iconset> + </property> + <property name="text" > + <string>Add Tab...</string> + </property> + <property name="iconText" > + <string>Add Tab</string> + </property> + <property name="shortcut" > + <string>Ctrl+T</string> + </property> + </action> + <action name="closeChartAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/document-close.png</normaloff>:/images/document-close.png</iconset> + </property> + <property name="text" > + <string>Close Chart</string> + </property> + <property name="iconText" > + <string>Close Chart</string> + </property> + <property name="shortcut" > + <string>Ctrl+D</string> + </property> + </action> + <action name="zoomInAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/zoom-in.png</normaloff>:/images/zoom-in.png</iconset> + </property> + <property name="text" > + <string>Zoom &In</string> + </property> + <property name="iconText" > + <string>Zoom In</string> + </property> + </action> + <action name="zoomOutAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/zoom-out.png</normaloff>:/images/zoom-out.png</iconset> + </property> + <property name="text" > + <string>&Zoom Out</string> + </property> + <property name="iconText" > + <string>Zoom Out</string> + </property> + </action> + <action name="helpManualAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/help-contents.png</normaloff>:/images/help-contents.png</iconset> + </property> + <property name="text" > + <string>&Manual</string> + </property> + <property name="iconText" > + <string>Manual</string> + </property> + <property name="shortcut" > + <string>F1</string> + </property> + </action> + <action name="helpAboutAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset> + </property> + <property name="text" > + <string>About</string> + </property> + <property name="iconText" > + <string>About</string> + </property> + <property name="shortcut" > + <string/> + </property> + </action> + <action name="helpAboutQtAction" > + <property name="text" > + <string>About Qt</string> + </property> + <property name="iconText" > + <string>About Qt</string> + </property> + </action> + <action name="helpSeeAlsoAction" > + <property name="text" > + <string>See Also</string> + </property> + <property name="iconText" > + <string>See Also</string> + </property> + </action> + <action name="helpWhatsThisAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/whatsthis.png</normaloff>:/images/whatsthis.png</iconset> + </property> + <property name="text" > + <string>What's This</string> + </property> + <property name="iconText" > + <string>What's This</string> + </property> + <property name="shortcut" > + <string>Shift+F1</string> + </property> + </action> + <action name="recordStartAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/camera-video.png</normaloff>:/images/camera-video.png</iconset> + </property> + <property name="text" > + <string>Start...</string> + </property> + </action> + <action name="recordQueryAction" > + <property name="text" > + <string>Query</string> + </property> + </action> + <action name="recordStopAction" > + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/camera-video-close.png</normaloff>:/images/camera-video-close.png</iconset> + </property> + <property name="text" > + <string>Stop</string> + </property> + </action> + <action name="recordDetachAction" > + <property name="text" > + <string>Detach</string> + </property> + </action> + </widget> + <includes> + <include location="local" >qmc_time.h</include> + <include location="local" >tabdialog.h</include> + <include location="local" >infodialog.h</include> + <include location="local" >chartdialog.h</include> + <include location="local" >exportdialog.h</include> + <include location="local" >recorddialog.h</include> + <include location="local" >settingsdialog.h</include> + <include location="local" >qprinter.h</include> + <include location="local" >qfiledialog.h</include> + <include location="local" >timeaxis.h</include> + <include location="local" >qed_timebutton.h</include> + <include location="local" >timeaxis.h</include> + </includes> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>fileNewChartAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>fileNewChart()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>fileOpenViewAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>fileOpenView()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>fileSaveViewAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>fileSaveView()</slot> + </connection> + <connection> + <sender>fileExportAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>fileExport()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>filePrintAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>filePrint()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>fileQuitAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>fileQuit()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>helpAboutAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>helpAbout()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>helpAboutQtAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>helpAboutQt()</slot> + </connection> + <connection> + <sender>helpSeeAlsoAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>helpSeeAlso()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>helpWhatsThisAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>whatsThis()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>showTimeControlAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>optionsShowTimeControl()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>hideTimeControlAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>optionsHideTimeControl()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>toolbarAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>optionsToolbar()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>consoleAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>optionsConsole()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>newPmchartAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>optionsNewPmchart()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>editSettingsAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>editSettings()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>chartTabWidget</sender> + <signal>currentChanged(int)</signal> + <receiver>PmChart</receiver> + <slot>activeTabChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>editChartAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>editChart()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>closeChartAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>closeChart()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>editTabAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>editTab()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>editSamplesAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>editSamples()</slot> + </connection> + <connection> + <sender>closeTabAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>closeTab()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>addTabAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>addTab()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>helpManualAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>helpManual()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>recordStartAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>recordStart()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + <connection> + <sender>recordQueryAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>recordQuery()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + <connection> + <sender>recordStopAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>recordStop()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + <connection> + <sender>recordDetachAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>recordDetach()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + <connection> + <sender>zoomInAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>zoomIn()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + <connection> + <sender>zoomOutAction</sender> + <signal>triggered()</signal> + <receiver>PmChart</receiver> + <slot>zoomOut()</slot> + <hints> + <hint type="sourcelabel" > + <x>-1</x> + <y>-1</y> + </hint> + <hint type="destinationlabel" > + <x>171</x> + <y>133</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/recorddialog.cpp b/src/pmchart/recorddialog.cpp new file mode 100644 index 0000000..08415e3 --- /dev/null +++ b/src/pmchart/recorddialog.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2007-2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include "recorddialog.h" +#include <QtCore/QTextStream> +#include <QtGui/QMessageBox> +#include <QtGui/QDoubleValidator> +#include "main.h" +#include "tab.h" +#include "saveviewdialog.h" + +RecordDialog::RecordDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + deltaLineEdit->setValidator( + new QDoubleValidator(0.001, INT_MAX, 3, deltaLineEdit)); +} + +void RecordDialog::languageChange() +{ + retranslateUi(this); +} + +void RecordDialog::init(Tab *tab) +{ + QChar sep(__pmPathSeparator()); + QString pmlogger = QDir::toNativeSeparators(QDir::homePath()); + QString view, folio, archive; + + pmlogger.append(sep); + pmlogger.append(".pcp"); + pmlogger.append(sep); + pmlogger.append("pmlogger"); + pmlogger.append(sep); + view = folio = archive = pmlogger; + + view.append("[date].view"); + viewLineEdit->setText(view); + folio.append("[date].folio"); + folioLineEdit->setText(folio); + archive.append("[host]"); + archive.append(sep); + archive.append("[date]"); + archiveLineEdit->setText(archive); + + my.tab = tab; + my.units = QmcTime::Seconds; + deltaLineEdit->setText( + QmcTime::deltaString(globalSettings.loggerDelta, my.units)); + + selectedRadioButton->setChecked(false); + allGadgetsRadioButton->setChecked(true); +} + +void RecordDialog::selectedRadioButton_clicked() +{ + selectedRadioButton->setChecked(true); + allGadgetsRadioButton->setChecked(false); +} + +void RecordDialog::allGadgetsRadioButton_clicked() +{ + selectedRadioButton->setChecked(false); + allGadgetsRadioButton->setChecked(true); +} + +void RecordDialog::deltaUnitsComboBox_activated(int value) +{ + double delta = tosec(*(pmtime->liveInterval())); + my.units = (QmcTime::DeltaUnits)value; + deltaLineEdit->setText(QmcTime::deltaString(delta, my.units)); +} + +void RecordDialog::viewPushButton_clicked() +{ + RecordFileDialog view(this); + + QChar sep(__pmPathSeparator()); + QString pmlogger = QDir::toNativeSeparators(QDir::homePath()); + pmlogger.append(sep); + pmlogger.append(".pcp"); + pmlogger.append(sep); + pmlogger.append("pmlogger"); + pmlogger.append(sep); + + view.setDirectory(pmlogger); + if (view.exec() == QDialog::Accepted) + viewLineEdit->setText(view.selectedFiles().at(0)); +} + +void RecordDialog::folioPushButton_clicked() +{ + RecordFileDialog folio(this); + + QChar sep(__pmPathSeparator()); + QString pmlogger = QDir::toNativeSeparators(QDir::homePath()); + pmlogger.append(sep); + pmlogger.append(".pcp"); + pmlogger.append(sep); + pmlogger.append("pmlogger"); + pmlogger.append(sep); + + folio.setDirectory(pmlogger); + if (folio.exec() == QDialog::Accepted) + folioLineEdit->setText(folio.selectedFiles().at(0)); +} + +void RecordDialog::archivePushButton_clicked() +{ + RecordFileDialog archive(this); + + QChar sep(__pmPathSeparator()); + QString pmlogger = QDir::toNativeSeparators(QDir::homePath()); + pmlogger.append(sep); + pmlogger.append(".pcp"); + pmlogger.append(sep); + pmlogger.append("pmlogger"); + pmlogger.append(sep); + + archive.setDirectory(pmlogger); + if (archive.exec() == QDialog::Accepted) + archiveLineEdit->setText(archive.selectedFiles().at(0)); +} + +bool RecordDialog::saveFolio(QString folioname, QString viewname) +{ + QFile folio(folioname); + + if (!folio.open(QIODevice::WriteOnly)) { + QString msg = tr("Cannot open file: "); + msg.append(folioname); + msg.append("\n"); + msg.append(folio.errorString()); + QMessageBox::warning(this, pmProgname, msg); + return false; + } + + QTextStream stream(&folio); + QString datetime; + + datetime = QDateTime::currentDateTime().toString("ddd MMM d hh:mm:ss yyyy"); + stream << "PCPFolio\n"; + stream << "Version: 1\n"; + stream << "# use pmafm(1) to process this PCP archive folio\n" << "#\n"; + stream << "Created: on " << QmcSource::localHost; + stream << " at " << datetime << "\n"; + stream << "Creator: pmchart " << viewname << "\n"; + stream << "#\t\tHost\t\tBasename\n"; + + for (int i = 0; i < my.hosts.size(); i++) { + QString host = my.hosts.at(i); + QString archive = my.archives.at(i); + QFileInfo logFile(archive); + QDir logDir = logFile.dir(); + logDir.mkpath(logDir.absolutePath()); + stream << "Archive:\t" << my.hosts.at(i) << "\t\t" << archive << "\n"; + } + return true; +} + +bool RecordDialog::saveConfig(QString configfile, QString configdata) +{ + QFile config(configfile); + + if (!config.open(QIODevice::WriteOnly)) { + QString msg = tr("Cannot open file: "); + msg.append(configfile); + msg.append("\n"); + msg.append(config.errorString()); + QMessageBox::warning(this, pmProgname, msg); + return false; + } + + QTextStream stream(&config); + stream << configdata; + return true; +} + +PmLogger::PmLogger(QObject *parent) : QProcess(parent) +{ + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(finished(int, QProcess::ExitStatus))); +} + +void PmLogger::init(Tab *tab, QString host, QString logfile) +{ + my.tab = tab; + my.host = host; + my.logfile = logfile; + my.terminating = false; +} + +void PmLogger::terminate() +{ + my.terminating = true; +} + +void PmLogger::finished(int, QProcess::ExitStatus) +{ + if (my.terminating == false) { + my.terminating = true; + my.tab->stopRecording(); + + QString msg = "Recording process (pmlogger) exited unexpectedly\n"; + msg.append("for host "); + msg.append(my.host); + msg.append(".\n\n"); + msg.append("Additional diagnostics may be available in the log:\n"); + msg.append(my.logfile); + QMessageBox::warning(pmchart, pmProgname, msg); + } +} + +void RecordDialog::buttonOk_clicked() +{ + if (deltaLineEdit->isModified()) { + // convert to seconds, make sure its still in range 0.001-INT_MAX + double input = QmcTime::deltaValue(deltaLineEdit->text(), my.units); + if (input < 0.001 || input > INT_MAX) { + QString msg = tr("Record Sampling Interval is invalid.\n"); + msg.append(deltaLineEdit->text()); + msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n"); + QMessageBox::warning(this, pmProgname, msg); + return; + } + } + + QString today = QDateTime::currentDateTime().toString("yyyyMMdd.hh.mm.ss"); + + QString view = viewLineEdit->text().trimmed(); + view.replace(QRegExp("^~"), QDir::toNativeSeparators(QDir::homePath())); + view.replace(QRegExp("\\[date\\]"), today); + view.replace(QRegExp("\\[host\\]"), QmcSource::localHost); + QFileInfo viewFile(view); + QDir viewDir = viewFile.dir(); + if (viewDir.mkpath(viewDir.absolutePath()) == false) { + QString msg = tr("Failed to create path for view:\n"); + msg.append(view); + msg.append("\n"); + QMessageBox::warning(this, pmProgname, msg); + return; + } + + QString folio = folioLineEdit->text().trimmed(); + folio.replace(QRegExp("^~"), QDir::toNativeSeparators(QDir::homePath())); + folio.replace(QRegExp("\\[date\\]"), today); + folio.replace(QRegExp("\\[host\\]"), QmcSource::localHost); + QFileInfo folioFile(folio); + QDir folioDir = folioFile.dir(); + if (folioDir.mkpath(folioDir.absolutePath()) == false) { + QString msg = tr("Failed to create path for folio:\n"); + msg.append(folio); + msg.append("\n"); + QMessageBox::warning(this, pmProgname, msg); + return; + } + + console->post("RecordDialog verifying paths view=%s folio=%s", + (const char *)folio.toAscii(), (const char *)view.toAscii()); + + my.view = view; + my.folio = folio; + my.delta.setNum(QmcTime::deltaValue(deltaLineEdit->text(), my.units), 'f'); + + Tab *tab = pmchart->activeTab(); + for (int c = 0; c < tab->gadgetCount(); c++) { + Gadget *gadget = tab->gadget(c); + if (selectedRadioButton->isChecked() && gadget != tab->currentGadget()) + continue; + QStringList ghosts = gadget->hosts(); + for (int i = 0; i < ghosts.count(); i++) { + if (!my.hosts.contains(ghosts.at(i))) + my.hosts.append(ghosts.at(i)); + } + } + + for (int h = 0; h < my.hosts.count(); h++) { + QString archive = archiveLineEdit->text().trimmed(); + QString rehomer = QDir::toNativeSeparators(QDir::homePath()); + archive.replace(QRegExp("^~"), rehomer); + archive.replace(QRegExp("\\[host\\]"), my.hosts.at(h)); + archive.replace(QRegExp("\\[date\\]"), today); + my.archives.append(archive); + } + + if (SaveViewDialog::saveView(view, false, false, false, true) == false) + return; + if (saveFolio(folio, view) == false) + return; + QDialog::accept(); +} + +// +// write pmlogger, pmchart and pmafm configs, then start pmloggers. +// +void RecordDialog::startLoggers() +{ + QString pmlogger = pmGetConfig("PCP_BINADM_DIR"); + QChar sep(__pmPathSeparator()); + pmlogger.append(sep); + pmlogger.append("pmlogger"); + + QString regex = "^"; + regex.append(QDir::toNativeSeparators(QDir::homePath())); + my.folio.replace(QRegExp(regex), "~"); + + Tab *tab = pmchart->activeTab(); + tab->addFolio(my.folio, my.view); + + for (int i = 0; i < my.hosts.size(); i++) { + PmLogger *process = new PmLogger(pmchart); + QString archive = my.archives.at(i); + QString host = my.hosts.at(i); + QString logfile, configfile; + + configfile = archive; + configfile.append(".config"); + logfile = archive; + logfile.append(".log"); + + process->init(my.tab, host, logfile); + + QStringList arguments; + arguments << "-r" << "-c" << configfile << "-h" << host << "-x0"; + arguments << "-l" << logfile << "-t" << my.delta << archive; + + QString configdata("#pmlogger Version 1\n\n"); // header for file(1) + if (selectedRadioButton->isChecked()) + configdata.append(tab->currentGadget()->pmloggerSyntax()); + else + for (int c = 0; c < tab->gadgetCount(); c++) + configdata.append(tab->gadget(c)->pmloggerSyntax()); + saveConfig(configfile, configdata); + + process->start(pmlogger, arguments); + tab->addLogger(process, archive); + + // Send initial control messages to pmlogger + QStringList control; + control << "V0\n"; + control << "F" << my.folio << "\n"; + control << "Ppmchart\n" << "R\n"; + for (int i = 0; i < control.size(); i++) + process->write(control.at(i).toAscii()); + } +} + +// RecordFileDialog is the one which is displayed when you click +// on one of the file selection push buttons (view/logfile/folio). + +RecordFileDialog::RecordFileDialog(QWidget *parent) : QFileDialog(parent) +{ + setAcceptMode(QFileDialog::AcceptSave); + setFileMode(QFileDialog::AnyFile); + setIconProvider(fileIconProvider); + setConfirmOverwrite(true); +} + +void RecordFileDialog::setFileName(QString path) +{ + selectFile(path); +} diff --git a/src/pmchart/recorddialog.h b/src/pmchart/recorddialog.h new file mode 100644 index 0000000..41c9ca9 --- /dev/null +++ b/src/pmchart/recorddialog.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef RECORDDIALOG_H +#define RECORDDIALOG_H + +#include "ui_recorddialog.h" +#include <QtCore/QProcess> +#include <QtGui/QFileDialog> +#include <qmc_time.h> + +class Tab; + +class RecordDialog : public QDialog, public Ui::RecordDialog +{ + Q_OBJECT + +public: + RecordDialog(QWidget* parent); + + virtual void init(Tab *tab); + virtual bool saveFolio(QString, QString); + virtual bool saveConfig(QString, QString); + virtual void startLoggers(); + +public slots: + virtual void deltaUnitsComboBox_activated(int); + virtual void selectedRadioButton_clicked(); + virtual void allGadgetsRadioButton_clicked(); + virtual void viewPushButton_clicked(); + virtual void folioPushButton_clicked(); + virtual void archivePushButton_clicked(); + virtual void buttonOk_clicked(); + +protected slots: + virtual void languageChange(); + +private: + struct { + Tab *tab; + QString delta; + QmcTime::DeltaUnits units; + + QString view; + QString folio; + QStringList hosts; + QStringList archives; + } my; +}; + +class PmLogger : public QProcess +{ + Q_OBJECT + +public: + PmLogger(QObject *parent); + void init(Tab *tab, QString host, QString log); + QString host() { return my.host; } + +public slots: + void terminate(); + void finished(int, QProcess::ExitStatus); + +private: + struct { + Tab *tab; + QString host; + QString logfile; + bool terminating; + } my; +}; + +class RecordFileDialog : public QFileDialog, public Ui::RecordDialog +{ + Q_OBJECT + +public: + RecordFileDialog(QWidget* parent); + void setFileName(QString); +}; + +#endif // RECORDDIALOG_H diff --git a/src/pmchart/recorddialog.ui b/src/pmchart/recorddialog.ui new file mode 100644 index 0000000..0f8744d --- /dev/null +++ b/src/pmchart/recorddialog.ui @@ -0,0 +1,540 @@ +<ui version="4.0" > + <class>RecordDialog</class> + <widget class="QDialog" name="RecordDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>410</width> + <height>240</height> + </rect> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>410</width> + <height>240</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>16777215</width> + <height>240</height> + </size> + </property> + <property name="windowTitle" > + <string>Record</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/archive.png</iconset> + </property> + <property name="toolTip" > + <string/> + </property> + <property name="sizeGripEnabled" > + <bool>false</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QRadioButton" name="allGadgetsRadioButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>All Charts in Tab</string> + </property> + <property name="checked" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="selectedRadioButton" > + <property name="text" > + <string>Selected Chart Only</string> + </property> + <property name="checked" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="deltaLabel" > + <property name="text" > + <string>Logging Interval:</string> + </property> + <property name="alignment" > + <set>Qt::AlignVCenter</set> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLineEdit" name="deltaLineEdit" > + <property name="text" > + <string/> + </property> + <property name="alignment" > + <set>Qt::AlignRight</set> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="deltaUnitsComboBox" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="currentIndex" > + <number>1</number> + </property> + <property name="maxCount" > + <number>6</number> + </property> + <property name="insertPolicy" > + <enum>QComboBox::NoInsert</enum> + </property> + <property name="duplicatesEnabled" > + <bool>false</bool> + </property> + <item> + <property name="text" > + <string>Milliseconds</string> + </property> + </item> + <item> + <property name="text" > + <string>Seconds</string> + </property> + </item> + <item> + <property name="text" > + <string>Minutes</string> + </property> + </item> + <item> + <property name="text" > + <string>Hours</string> + </property> + </item> + <item> + <property name="text" > + <string>Days</string> + </property> + </item> + <item> + <property name="text" > + <string>Weeks</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="folioTextLabel" > + <property name="text" > + <string>Folio</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="folioLineEdit" /> + </item> + <item> + <widget class="QPushButton" name="folioPushButton" > + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/folio.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="viewTextLabel" > + <property name="text" > + <string>View</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="viewLineEdit" /> + </item> + <item> + <widget class="QPushButton" name="viewPushButton" > + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/view.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="archiveTextLabel" > + <property name="text" > + <string>Archive</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="archiveLineEdit" /> + </item> + <item> + <widget class="QPushButton" name="archivePushButton" > + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/archive.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>buttonOk_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>selectedRadioButton</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>selectedRadioButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>allGadgetsRadioButton</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>allGadgetsRadioButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>deltaUnitsComboBox</sender> + <signal>activated(int)</signal> + <receiver>RecordDialog</receiver> + <slot>deltaUnitsComboBox_activated(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>viewPushButton</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>viewPushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>folioPushButton</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>folioPushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>archivePushButton</sender> + <signal>clicked()</signal> + <receiver>RecordDialog</receiver> + <slot>archivePushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/samplesdialog.cpp b/src/pmchart/samplesdialog.cpp new file mode 100644 index 0000000..9244dc8 --- /dev/null +++ b/src/pmchart/samplesdialog.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include "samplesdialog.h" +#include "main.h" + +SamplesDialog::SamplesDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); +} + +void SamplesDialog::languageChange() +{ + retranslateUi(this); +} + +void SamplesDialog::reset(int samples, int visible) +{ + my.samples = my.visible = 0; + + visibleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + visibleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + sampleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + sampleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + visibleCounter->setValue(visible); + visibleSlider->setValue(visible); + sampleCounter->setValue(samples); + sampleSlider->setValue(samples); + + console->post(PmChart::DebugUi, "SamplesDialog::reset tot=%d/%d vis=%d/%d", + samples, my.samples, visible, my.visible); +} + +int SamplesDialog::samples() +{ + return sampleCounter->value(); +} + +int SamplesDialog::visible() +{ + return visibleCounter->value(); +} + +void SamplesDialog::sampleValueChanged(int value) +{ + if (my.samples != value) { + my.samples = value; + displaySampleCounter(); + displaySampleSlider(); + if (my.visible > my.samples) + visibleSlider->setValue(value); + } +} + +void SamplesDialog::visibleValueChanged(int value) +{ + if (my.visible != value) { + my.visible = value; + displayVisibleCounter(); + displayVisibleSlider(); + if (my.visible > my.samples) + sampleSlider->setValue(value); + } +} + +void SamplesDialog::displaySampleSlider() +{ + sampleSlider->blockSignals(true); + sampleSlider->setValue(my.samples); + sampleSlider->blockSignals(false); +} + +void SamplesDialog::displayVisibleSlider() +{ + visibleSlider->blockSignals(true); + visibleSlider->setValue(my.visible); + visibleSlider->blockSignals(false); +} + +void SamplesDialog::displaySampleCounter() +{ + sampleCounter->blockSignals(true); + sampleCounter->setValue(my.samples); + sampleCounter->blockSignals(false); +} + +void SamplesDialog::displayVisibleCounter() +{ + visibleCounter->blockSignals(true); + visibleCounter->setValue(my.visible); + visibleCounter->blockSignals(false); +} diff --git a/src/pmchart/samplesdialog.h b/src/pmchart/samplesdialog.h new file mode 100644 index 0000000..f2c0b1f --- /dev/null +++ b/src/pmchart/samplesdialog.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SAMPLESDIALOG_H +#define SAMPLESDIALOG_H + +#include "ui_samplesdialog.h" + +class SamplesDialog : public QDialog, public Ui::SamplesDialog +{ + Q_OBJECT + +public: + SamplesDialog(QWidget* parent); + void reset(int, int); + int samples(); + int visible(); + +public slots: + void sampleValueChanged(int); + void visibleValueChanged(int); + +protected slots: + void languageChange(); + +private: + void displaySampleSlider(); + void displayVisibleSlider(); + void displaySampleCounter(); + void displayVisibleCounter(); + + struct { + int samples; + int visible; + } my; +}; + +#endif // SAMPLESDIALOG_H diff --git a/src/pmchart/samplesdialog.ui b/src/pmchart/samplesdialog.ui new file mode 100644 index 0000000..06fc356 --- /dev/null +++ b/src/pmchart/samplesdialog.ui @@ -0,0 +1,353 @@ +<ui version="4.0" > + <class>SamplesDialog</class> + <widget class="QDialog" name="SamplesDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>330</width> + <height>220</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>330</width> + <height>220</height> + </size> + </property> + <property name="focusPolicy" > + <enum>Qt::WheelFocus</enum> + </property> + <property name="windowTitle" > + <string>Add Samples</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/zoom-out.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout" > + <item row="0" column="0" > + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="2" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="sampleTextLabel" > + <property name="text" > + <string>Total Samples:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" > + <size> + <width>26</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSpinBox" name="sampleCounter" > + <property name="maximum" > + <number>999999999</number> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QSlider" name="visibleSlider" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="3" column="0" > + <widget class="QSlider" name="sampleSlider" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="visibleTextLabel" > + <property name="text" > + <string>Visible Points:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" > + <size> + <width>26</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSpinBox" name="visibleCounter" > + <property name="maximum" > + <number>999999999</number> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item row="1" column="0" > + <spacer name="verticalSpacer" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>SamplesDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>SamplesDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sampleCounter</sender> + <signal>valueChanged(int)</signal> + <receiver>SamplesDialog</receiver> + <slot>sampleValueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sampleSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>SamplesDialog</receiver> + <slot>sampleValueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>visibleCounter</sender> + <signal>valueChanged(int)</signal> + <receiver>SamplesDialog</receiver> + <slot>visibleValueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>visibleSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>SamplesDialog</receiver> + <slot>visibleValueChanged(int)</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/sampling.cpp b/src/pmchart/sampling.cpp new file mode 100644 index 0000000..f3e1507 --- /dev/null +++ b/src/pmchart/sampling.cpp @@ -0,0 +1,880 @@ +/* + * Copyright (c) 2012, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include <limits> +#include "sampling.h" +#include "main.h" +#include <qnumeric.h> +#include <qwt_picker_machine.h> + +SamplingItem::SamplingItem(Chart *parent, + QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp, + const QString &legend, Chart::Style style, int samples, int index) + : ChartItem(mp, msp, dp, legend) +{ + pmDesc desc = mp->desc().desc(); + + my.chart = parent; + my.info = QString::null; + + // initialize the pcp data and item data arrays + my.dataCount = 0; + my.data = NULL; + my.itemData = NULL; + resetValues(samples, 0.0, 0.0); + + // set base scale, then tweak if value to plot is time / time + my.scale = 1; + if (style != Chart::UtilisationStyle && + desc.sem == PM_SEM_COUNTER && desc.units.dimTime == 0) { + if (desc.units.scaleTime == PM_TIME_USEC) + my.scale = 0.000001; + else if (desc.units.scaleTime == PM_TIME_MSEC) + my.scale = 0.001; + } + + // create and attach the plot right here + my.curve = new SamplingCurve(label()); + my.curve->attach(parent); + + // the 1000 is arbitrary ... just want numbers to be monotonic + // decreasing as plots are added + my.curve->setZ(1000 - index); +} + +SamplingItem::~SamplingItem(void) +{ + if (my.data != NULL) + free(my.data); + if (my.itemData != NULL) + free(my.itemData); +} + +QwtPlotItem * +SamplingItem::item(void) +{ + return my.curve; +} + +QwtPlotCurve * +SamplingItem::curve(void) +{ + return my.curve; +} + +void +SamplingItem::resetValues(int values, double, double) +{ + size_t size; + + // Reset sizes of pcp data array and the plot data array + size = values * sizeof(my.data[0]); + if ((my.data = (double *)realloc(my.data, size)) == NULL) + nomem(); + size = values * sizeof(my.itemData[0]); + if ((my.itemData = (double *)realloc(my.itemData, size)) == NULL) + nomem(); + if (my.dataCount > values) + my.dataCount = values; +} + +void +SamplingItem::preserveSample(int index, int oldindex) +{ + if (my.dataCount > oldindex) + my.itemData[index] = my.data[index] = my.data[oldindex]; + else + my.itemData[index] = my.data[index] = qQNaN(); +} + +void +SamplingItem::punchoutSample(int index) +{ + my.data[index] = my.itemData[index] = qQNaN(); +} + +void +SamplingItem::updateValues(bool forward, + bool rateConvert, pmUnits *units, int sampleHistory, int, + double, double, double) +{ + pmAtomValue scaled, raw; + QmcMetric *metric = ChartItem::my.metric; + double value; + int sz; + + if (metric->numValues() < 1 || metric->error(0)) { + value = qQNaN(); + } else { + // convert raw value to current chart scale + raw.d = rateConvert ? metric->value(0) : metric->currentValue(0); + pmConvScale(PM_TYPE_DOUBLE, &raw, &ChartItem::my.units, &scaled, units); + value = scaled.d * my.scale; + } + + if (my.dataCount < sampleHistory) + sz = qMax(0, (int)(my.dataCount * sizeof(double))); + else + sz = qMax(0, (int)((my.dataCount - 1) * sizeof(double))); + + if (forward) { + memmove(&my.data[1], &my.data[0], sz); + memmove(&my.itemData[1], &my.itemData[0], sz); + my.data[0] = value; + } else { + memmove(&my.data[0], &my.data[1], sz); + memmove(&my.itemData[0], &my.itemData[1], sz); + my.data[my.dataCount - 1] = value; + } + + if (my.dataCount < sampleHistory) + my.dataCount++; +} + +void +SamplingItem::rescaleValues(pmUnits *new_units) +{ + pmUnits *old_units = &ChartItem::my.units; + pmAtomValue old_av, new_av; + + console->post("Chart::update change units from %s to %s", + pmUnitsStr(old_units), pmUnitsStr(new_units)); + + for (int i = my.dataCount - 1; i >= 0; i--) { + if (my.data[i] != qQNaN()) { + old_av.d = my.data[i]; + pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units); + my.data[i] = new_av.d; + } + if (my.itemData[i] != qQNaN()) { + old_av.d = my.itemData[i]; + pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units); + my.itemData[i] = new_av.d; + } + } +} + +void +SamplingItem::replot(int history, double *timeData) +{ + int count = qMin(history, my.dataCount); + my.curve->setRawSamples(timeData, my.itemData, count); +} + +void +SamplingItem::revive(void) +{ + if (removed()) { + setRemoved(false); + my.curve->attach(my.chart); + } +} + +void +SamplingItem::remove(void) +{ + setRemoved(true); + my.curve->detach(); + + // We can't really do this properly (free memory, etc) - working around + // metrics class limit (its using an ordinal index for metrics, remove any + // and we'll get problems. Which means the plots array must also remain + // unchanged, as we drive things via the metriclist at times. D'oh. + // This blows - it means we have to continue to fetch metrics for those + // metrics that have been removed from the chart, which may be remote + // hosts, hosts which are down (introducing retry issues...). Bother. + + //delete my.curve; + //free(my.legend); +} + +void +SamplingItem::setStroke(Chart::Style style, QColor color, bool antiAlias) +{ + int sem = metric()->desc().desc().sem; + bool step = (sem == PM_SEM_INSTANT || sem == PM_SEM_DISCRETE); + + my.curve->setLegendColor(color); + my.curve->setRenderHint(QwtPlotItem::RenderAntialiased, antiAlias); + + switch (style) { + case Chart::BarStyle: + my.curve->setPen(color); + my.curve->setBrush(QBrush(color, Qt::SolidPattern)); + my.curve->setStyle(QwtPlotCurve::Sticks); + break; + + case Chart::AreaStyle: + my.curve->setPen(color); + my.curve->setBrush(QBrush(color, Qt::SolidPattern)); + my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines); + break; + + case Chart::UtilisationStyle: + my.curve->setPen(QColor(Qt::black)); + my.curve->setStyle(QwtPlotCurve::Steps); + my.curve->setBrush(QBrush(color, Qt::SolidPattern)); + break; + + case Chart::LineStyle: + my.curve->setPen(color); + my.curve->setBrush(QBrush(Qt::NoBrush)); + my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines); + break; + + case Chart::StackStyle: + my.curve->setPen(QColor(Qt::black)); + my.curve->setBrush(QBrush(color, Qt::SolidPattern)); + my.curve->setStyle(QwtPlotCurve::Steps); + break; + + default: + break; + } +} + +void +SamplingItem::clearCursor() +{ + // nothing to do here. +} + +bool +SamplingItem::containsPoint(const QRectF &, int) +{ + return false; +} + +void +SamplingItem::updateCursor(const QPointF &p, int) +{ + QString title = my.chart->YAxisTitle(); + + my.info.sprintf("[%.2f", (float)p.y()); + if (title != QString::null) { + my.info.append(" "); + my.info.append(title); + } + my.info.append(" at "); + my.info.append(timeHiResString(p.x())); + my.info.append("]"); + + pmchart->setValueText(my.info); +} + +const QString & +SamplingItem::cursorInfo() +{ + return my.info; +} + +void +SamplingItem::copyRawDataPoint(int index) +{ + if (index < 0) + index = my.dataCount - 1; + my.itemData[index] = my.data[index]; +} + +int +SamplingItem::maximumDataCount(int maximum) +{ + return qMax(maximum, my.dataCount); +} + +void +SamplingItem::truncateData(int offset) +{ + for (int index = my.dataCount + 1; index < offset; index++) { + my.data[index] = 0; + // don't re-set dataCount ... so we don't plot these values, + // we just want them to count 0 towards any Stack aggregation + } +} + +double +SamplingItem::sumData(int index, double sum) +{ + if (index < 0) + index = my.dataCount - 1; + if (index < my.dataCount && !qIsNaN(my.data[index])) + sum += my.data[index]; + return sum; +} + +void +SamplingItem::copyRawDataArray(void) +{ + for (int index = 0; index < my.dataCount; index++) + my.itemData[index] = my.data[index]; +} + +void +SamplingItem::copyDataPoint(int index) +{ + if (hidden() || index >= my.dataCount) + my.itemData[index] = qQNaN(); + else + my.itemData[index] = my.data[index]; +} + +void +SamplingItem::setPlotUtil(int index, double sum) +{ + if (index < 0) + index = my.dataCount - 1; + if (hidden() || sum == 0.0 || + index >= my.dataCount || qIsNaN(my.data[index])) + my.itemData[index] = qQNaN(); + else + my.itemData[index] = 100.0 * my.data[index] / sum; +} + +double +SamplingItem::setPlotStack(int index, double sum) +{ + if (index < 0) + index = my.dataCount - 1; + if (!hidden() && !qIsNaN(my.itemData[index])) { + sum += my.itemData[index]; + my.itemData[index] = sum; + } + return sum; +} + +double +SamplingItem::setDataStack(int index, double sum) +{ + if (index < 0) + index = my.dataCount - 1; + if (hidden() || qIsNaN(my.data[index])) { + my.itemData[index] = qQNaN(); + } else { + sum += my.data[index]; + my.itemData[index] = sum; + } + return sum; +} + + +// +// SamplingCurve deals with overriding some QwtPlotCurve defaults; +// particularly around dealing with empty sections of chart (NaN), +// and the way the legend is rendered. +// + +void +SamplingCurve::drawSeries(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to) const +{ + int okFrom, okTo = from; + int size = (to > 0) ? to : dataSize(); + + while (okTo < size) { + okFrom = okTo; + while (qIsNaN(sample(okFrom).y()) && okFrom < size) + ++okFrom; + okTo = okFrom; + while (!qIsNaN(sample(okTo).y()) && okTo < size) + ++okTo; + if (okFrom < size) + QwtPlotCurve::drawSeries(p, xMap, yMap, canvasRect, okFrom, okTo-1); + } +} + + +// +// SamplingScaleEngine deals with rendering the vertical Y-Axis +// + +SamplingScaleEngine::SamplingScaleEngine() : QwtLinearScaleEngine() +{ + my.autoScale = true; + my.minimum = 0.0; + my.maximum = 1.0; +} + +void +SamplingScaleEngine::setScale(bool autoScale, + double minValue, double maxValue) +{ + my.autoScale = autoScale; + my.minimum = minValue; + my.maximum = maxValue; +} + +void +SamplingScaleEngine::autoScale(int maxSteps, double &minValue, + double &maxValue, double &stepSize) const +{ + if (my.autoScale) { + if (minValue > 0) + minValue = 0.0; + } else { + minValue = my.minimum; + maxValue = my.maximum; + } + QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize); +} + + +// +// The SamplingEngine implements all sampling-specific Chart behaviour +// +SamplingEngine::SamplingEngine(Chart *chart, pmDesc &desc) +{ + QwtPlotPicker *picker = chart->my.picker; + ChartEngine *engine = chart->my.engine; + + my.chart = chart; + my.rateConvert = engine->rateConvert(); + my.antiAliasing = engine->antiAliasing(); + + normaliseUnits(desc); + my.units = desc.units; + + my.scaleEngine = new SamplingScaleEngine(); + chart->setAxisScaleEngine(QwtPlot::yLeft, my.scaleEngine); + chart->setAxisScaleDraw(QwtPlot::yLeft, new QwtScaleDraw()); + + // use an individual point picker for sampled data + picker->setStateMachine(new QwtPickerDragPointMachine()); + picker->setRubberBand(QwtPicker::CrossRubberBand); + picker->setRubberBandPen(QColor(Qt::green)); +} + +SamplingItem * +SamplingEngine::samplingItem(int index) +{ + return (SamplingItem *)my.chart->my.items[index]; +} + +ChartItem * +SamplingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend) +{ + int sampleHistory = my.chart->my.tab->group()->sampleHistory(); + int existingItemCount = my.chart->metricCount(); + SamplingItem *item = new SamplingItem(my.chart, mp, msp, desc, legend, + my.chart->my.style, + sampleHistory, existingItemCount); + + // Find current max count for all plot items + int i, size = 0; + for (i = 0; i < existingItemCount; i++) + size = samplingItem(i)->maximumDataCount(size); + // Zero any plot from there to end, so Stack<->Line transitions work + for (i = 0; i < existingItemCount; i++) + samplingItem(i)->truncateData(size); + + return item; +} + +void +SamplingEngine::normaliseUnits(pmDesc &desc) +{ + if (my.rateConvert && desc.sem == PM_SEM_COUNTER) { + if (desc.units.dimTime == 0) { + desc.units.dimTime = -1; + desc.units.scaleTime = PM_TIME_SEC; + } + else if (desc.units.dimTime == 1) { + desc.units.dimTime = 0; + // don't play with scaleTime, need native per item scaleTime + // so we can apply correct scaling via item->scale, e.g. in + // the msec -> msec/sec after rate conversion ... see the + // calculation for item->scale below + } + } +} + +bool +SamplingEngine::isCompatible(pmDesc &desc) +{ + console->post("SamplingEngine::isCompatible" + " type=%d, units=%s", desc.type, pmUnitsStr(&desc.units)); + + if (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT) + return false; + normaliseUnits(desc); + if (my.units.dimSpace != desc.units.dimSpace || + my.units.dimTime != desc.units.dimTime || + my.units.dimCount != desc.units.dimCount) + return false; + return true; +} + +void +SamplingEngine::updateValues(bool forward, + int size, int points, double left, double right, double delta) +{ + int i, index = forward ? 0 : -1; /* first or last data point */ + int itemCount = my.chart->metricCount(); + Chart::Style style = my.chart->my.style; + + // Drive new values into each chart item + for (int i = 0; i < itemCount; i++) { + samplingItem(i)->updateValues(forward, my.rateConvert, &my.units, + size, points, left, right, delta); + } + + if (style == Chart::BarStyle || style == Chart::AreaStyle || style == Chart::LineStyle) { + for (i = 0; i < itemCount; i++) + samplingItem(i)->copyRawDataPoint(index); + } + // Utilisation: like Stack, but normalize value to a percentage (0,100) + else if (style == Chart::UtilisationStyle) { + double sum = 0.0; + // compute sum + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->sumData(index, sum); + // scale all components + for (i = 0; i < itemCount; i++) + samplingItem(i)->setPlotUtil(index, sum); + // stack components + sum = 0.0; + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->setPlotStack(index, sum); + } + else if (style == Chart::StackStyle) { + double sum = 0.0; + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->setDataStack(index, sum); + } + +#if DESPERATE + for (i = 0; i < my.chart->metricCount(); i++) { + console->post(PmChart::DebugForce, "metric[%d] value %f", i, + samplingItem(i)->metric()->currentValue(0)); + } +#endif +} + +void +SamplingEngine::redoScale(void) +{ + bool rescale = false; + + // The 1,000 and 0.1 thresholds are just a heuristic guess. + // + // We're assuming lBound() plays no part in this, which is OK as + // the upper bound of the y-axis range (hBound()) drives the choice + // of appropriate units scaling. + // + if (my.scaleEngine->autoScale() && + my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound() > 1000) { + double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound(); + if (my.units.dimSpace == 1) { + switch (my.units.scaleSpace) { + case PM_SPACE_BYTE: + my.units.scaleSpace = PM_SPACE_KBYTE; + rescale = true; + break; + case PM_SPACE_KBYTE: + my.units.scaleSpace = PM_SPACE_MBYTE; + rescale = true; + break; + case PM_SPACE_MBYTE: + my.units.scaleSpace = PM_SPACE_GBYTE; + rescale = true; + break; + case PM_SPACE_GBYTE: + my.units.scaleSpace = PM_SPACE_TBYTE; + rescale = true; + break; + case PM_SPACE_TBYTE: + my.units.scaleSpace = PM_SPACE_PBYTE; + rescale = true; + break; + case PM_SPACE_PBYTE: + my.units.scaleSpace = PM_SPACE_EBYTE; + rescale = true; + break; + } + if (rescale) { + // logic here depends on PM_SPACE_* values being consecutive + // integer values as the scale increases + scaled_max /= 1024; + while (scaled_max > 1000) { + my.units.scaleSpace++; + scaled_max /= 1024; + if (my.units.scaleSpace == PM_SPACE_EBYTE) break; + } + } + } + else if (my.units.dimTime == 1) { + switch (my.units.scaleTime) { + case PM_TIME_NSEC: + my.units.scaleTime = PM_TIME_USEC; + rescale = true; + scaled_max /= 1000; + break; + case PM_TIME_USEC: + my.units.scaleTime = PM_TIME_MSEC; + rescale = true; + scaled_max /= 1000; + break; + case PM_TIME_MSEC: + my.units.scaleTime = PM_TIME_SEC; + rescale = true; + scaled_max /= 1000; + break; + case PM_TIME_SEC: + my.units.scaleTime = PM_TIME_MIN; + rescale = true; + scaled_max /= 60; + break; + case PM_TIME_MIN: + my.units.scaleTime = PM_TIME_HOUR; + rescale = true; + scaled_max /= 60; + break; + } + if (rescale) { + // logic here depends on PM_TIME* values being consecutive + // integer values as the scale increases + while (scaled_max > 1000) { + my.units.scaleTime++; + if (my.units.scaleTime <= PM_TIME_SEC) + scaled_max /= 1000; + else + scaled_max /= 60; + if (my.units.scaleTime == PM_TIME_HOUR) break; + } + } + } + } + + if (rescale == false && + my.scaleEngine->autoScale() && + my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound() < 0.1) { + double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound(); + if (my.units.dimSpace == 1) { + switch (my.units.scaleSpace) { + case PM_SPACE_KBYTE: + my.units.scaleSpace = PM_SPACE_BYTE; + rescale = true; + break; + case PM_SPACE_MBYTE: + my.units.scaleSpace = PM_SPACE_KBYTE; + rescale = true; + break; + case PM_SPACE_GBYTE: + my.units.scaleSpace = PM_SPACE_MBYTE; + rescale = true; + break; + case PM_SPACE_TBYTE: + my.units.scaleSpace = PM_SPACE_GBYTE; + rescale = true; + break; + case PM_SPACE_PBYTE: + my.units.scaleSpace = PM_SPACE_TBYTE; + rescale = true; + break; + case PM_SPACE_EBYTE: + my.units.scaleSpace = PM_SPACE_PBYTE; + rescale = true; + break; + } + if (rescale) { + // logic here depends on PM_SPACE_* values being consecutive + // integer values (in reverse) as the scale decreases + scaled_max *= 1024; + while (scaled_max < 0.1) { + my.units.scaleSpace--; + scaled_max *= 1024; + if (my.units.scaleSpace == PM_SPACE_BYTE) break; + } + } + } + else if (my.units.dimTime == 1) { + switch (my.units.scaleTime) { + case PM_TIME_USEC: + my.units.scaleTime = PM_TIME_NSEC; + rescale = true; + scaled_max *= 1000; + break; + case PM_TIME_MSEC: + my.units.scaleTime = PM_TIME_USEC; + rescale = true; + scaled_max *= 1000; + break; + case PM_TIME_SEC: + my.units.scaleTime = PM_TIME_MSEC; + rescale = true; + scaled_max *= 1000; + break; + case PM_TIME_MIN: + my.units.scaleTime = PM_TIME_SEC; + rescale = true; + scaled_max *= 60; + break; + case PM_TIME_HOUR: + my.units.scaleTime = PM_TIME_MIN; + rescale = true; + scaled_max *= 60; + break; + } + if (rescale) { + // logic here depends on PM_TIME* values being consecutive + // integer values (in reverse) as the scale decreases + while (scaled_max < 0.1) { + my.units.scaleTime--; + if (my.units.scaleTime < PM_TIME_SEC) + scaled_max *= 1000; + else + scaled_max *= 60; + if (my.units.scaleTime == PM_TIME_NSEC) break; + } + } + } + } + + if (rescale) { + // + // need to rescale ... we transform all of the historical (raw) + // data, new data will be taken care of by changing my.units. + // + for (int i = 0; i < my.chart->metricCount(); i++) + samplingItem(i)->rescaleValues(&my.units); + + if (my.chart->my.style == Chart::UtilisationStyle) + my.chart->setYAxisTitle("% utilization"); + else + my.chart->setYAxisTitle(pmUnitsStr(&my.units)); + my.chart->replot(); + } +} + +void +SamplingEngine::replot(void) +{ + GroupControl *group = my.chart->my.tab->group(); + int vh = group->visibleHistory(); + double *vp = group->timeAxisData(); + int itemCount = my.chart->metricCount(); + int maxCount = 0; + int i, m; + double sum; + +#if DESPERATE + console->post(PmChart::DebugForce, "SamplingEngine::replot %d items)", itemCount); +#endif + + for (i = 0; i < itemCount; i++) + samplingItem(i)->replot(vh, vp); + + switch (my.chart->style()) { + case Chart::BarStyle: + case Chart::AreaStyle: + case Chart::LineStyle: + for (i = 0; i < itemCount; i++) + samplingItem(i)->copyRawDataArray(); + break; + + case Chart::UtilisationStyle: + for (i = 0; i < itemCount; i++) + maxCount = samplingItem(i)->maximumDataCount(maxCount); + for (m = 0; m < maxCount; m++) { + sum = 0.0; + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->sumData(m, sum); + for (i = 0; i < itemCount; i++) + samplingItem(i)->setPlotUtil(m, sum); + sum = 0.0; + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->setPlotStack(m, sum); + } + break; + + case Chart::StackStyle: + for (i = 0; i < itemCount; i++) + maxCount = samplingItem(i)->maximumDataCount(maxCount); + for (m = 0; m < maxCount; m++) { + for (i = 0; i < itemCount; i++) + samplingItem(i)->copyDataPoint(m); + sum = 0.0; + for (i = 0; i < itemCount; i++) + sum = samplingItem(i)->setPlotStack(m, sum); + } + break; + + default: + break; + } +} + +void +SamplingEngine::scale(bool *autoScale, double *yMin, double *yMax) +{ + *autoScale = my.scaleEngine->autoScale(); + *yMin = my.scaleEngine->minimum(); + *yMax = my.scaleEngine->maximum(); +} + +void +SamplingEngine::setScale(bool autoScale, double yMin, double yMax) +{ + my.scaleEngine->setScale(autoScale, yMin, yMax); + + if (autoScale) + my.chart->setAxisAutoScale(QwtPlot::yLeft); + else + my.chart->setAxisScale(QwtPlot::yLeft, yMin, yMax); +} + +void +SamplingEngine::selected(const QPolygon &) +{ + // Nothing to do here. +} + +void +SamplingEngine::moved(const QPointF &p) +{ + my.chart->showPoint(p); +} + +void +SamplingEngine::setStyle(Chart::Style style) +{ + // Y-Axis title choice is difficult. A Utilisation plot by definition + // is dimensionless and scaled to a percentage, so a label of just + // "% utilization" makes sense ... there has been some argument in + // support of "% time utilization" as a special case when the metrics + // involve some aspect of time, but the base metrics in the common case + // are counters in units of time (e.g. the CPU view), which after rate + // conversion is indistinguishable from instantaneous or discrete + // metrics of dimension time^0 which are units compatible ... so we're + // opting for the simplest possible interpretation of utilization or + // everything else. + // + switch (style) { + case Chart::BarStyle: + case Chart::AreaStyle: + case Chart::LineStyle: + case Chart::StackStyle: + if (my.chart->style() == Chart::UtilisationStyle) + my.scaleEngine->setAutoScale(true); + my.chart->setYAxisTitle(pmUnitsStr(&my.units)); + break; + case Chart::UtilisationStyle: + my.scaleEngine->setScale(false, 0.0, 100.0); + my.chart->setYAxisTitle("% utilization"); + break; + default: + break; + } +} diff --git a/src/pmchart/sampling.h b/src/pmchart/sampling.h new file mode 100644 index 0000000..fa8c552 --- /dev/null +++ b/src/pmchart/sampling.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2012, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SAMPLING_H +#define SAMPLING_H + +#include <QtCore/QVariant> +#include <qwt_plot.h> +#include <qwt_plot_curve.h> +#include <qwt_scale_engine.h> +#include "chart.h" + +class SamplingCurve : public ChartCurve +{ +public: + SamplingCurve(const QString &title) : ChartCurve(title) { } + + virtual void drawSeries(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to) const; +}; + +class SamplingItem : public ChartItem +{ +public: + SamplingItem(Chart *, + QmcMetric *, pmMetricSpec *, pmDesc *, + const QString &, Chart::Style, int, int); + ~SamplingItem(void); + + QwtPlotItem *item(); + QwtPlotCurve *curve(); + + void preserveSample(int, int); + void punchoutSample(int); + void updateValues(bool, bool, pmUnits *, int, int, double, double, double); + void rescaleValues(pmUnits *); + void resetValues(int, double, double); + void revive(); + void remove(); + void setStroke(Chart::Style, QColor, bool); + + void clearCursor(); + bool containsPoint(const QRectF &, int); + void updateCursor(const QPointF &, int); + const QString &cursorInfo(); + + void replot(int, double *); + void copyRawDataArray(void); + void copyRawDataPoint(int index); + void copyDataPoint(int index); + int maximumDataCount(int maximum); + void truncateData(int offset); + double sumData(int index, double sum); + void setPlotUtil(int index, double sum); + double setPlotStack(int index, double sum); + double setDataStack(int index, double sum); + +private: + struct { + Chart *chart; + SamplingCurve *curve; + QString info; + double scale; + double *data; + double *itemData; + int dataCount; + } my; +}; + +// +// *Always* clamp minimum metric value at zero when positive - +// preventing confusion when values silently change up towards +// the maximum over time (for pmchart, our users are expecting +// a constant zero baseline at all times, or so we're told). +// +class SamplingScaleEngine : public QwtLinearScaleEngine +{ + friend class Chart; + +public: + SamplingScaleEngine(); + + double minimum() const { return my.minimum; } + double maximum() const { return my.maximum; } + bool autoScale() const { return my.autoScale; } + void setAutoScale(bool autoScale) { my.autoScale = autoScale; } + void setScale(bool autoScale, double minValue, double maxValue); + virtual void autoScale(int maxSteps, double &minValue, + double &maxValue, double &stepSize) const; + +private: + struct { + bool autoScale; + double minimum; + double maximum; + } my; +}; +// +// +// Implement sampling-specific behaviour within a Chart +// +class SamplingEngine : public ChartEngine +{ +public: + SamplingEngine(Chart *chart, pmDesc &); + + bool isCompatible(pmDesc &); + ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &); + + void updateValues(bool, int, int, double, double, double); + void replot(void); + + bool autoScale() { return my.scaleEngine->autoScale(); } + void redoScale(void); + void setScale(bool, double, double); + void scale(bool *, double *, double *); + void setStyle(Chart::Style); + + bool rateConvert() const { return my.rateConvert; } + void setRateConvert(bool enabled) { my.rateConvert = enabled; } + bool antiAliasing() const { return my.antiAliasing; } + void setAntiAliasing(bool enabled) { my.antiAliasing = enabled; } + + void selected(const QPolygon &); + void moved(const QPointF &); + +private: + SamplingItem *samplingItem(int index); + void normaliseUnits(pmDesc &desc); + + struct { + pmUnits units; + bool rateConvert; + bool antiAliasing; + SamplingScaleEngine *scaleEngine; + Chart *chart; + } my; +}; + +#endif // SAMPLING_H diff --git a/src/pmchart/saveviewdialog.cpp b/src/pmchart/saveviewdialog.cpp new file mode 100644 index 0000000..0a03d27 --- /dev/null +++ b/src/pmchart/saveviewdialog.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2007-2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include "saveviewdialog.h" +#include <QtCore/QDir> +#include <QtGui/QCompleter> +#include <QtGui/QMessageBox> +#include "main.h" + +SaveViewDialog::SaveViewDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + my.dirModel = new QDirModel; + my.dirModel->setIconProvider(fileIconProvider); + dirListView->setModel(my.dirModel); + + my.completer = new QCompleter; + my.completer->setModel(my.dirModel); + fileNameLineEdit->setCompleter(my.completer); + + connect(dirListView->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(dirListView_selectionChanged())); + + QDir dir; + QChar sep(__pmPathSeparator()); + QString home = my.userDir = QDir::toNativeSeparators(QDir::homePath()); + my.userDir.append(sep); + my.userDir.append(".pcp"); + my.userDir.append(sep); + my.userDir.append("kmchart"); + if (!dir.exists(my.userDir)) { + my.userDir = home; + my.userDir.append(sep); + my.userDir.append(".pcp"); + my.userDir.append(sep); + my.userDir.append("pmchart"); + } + my.hostDynamic = true; + my.sizeDynamic = true; + + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + my.userDir); + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + home); +} + +SaveViewDialog::~SaveViewDialog() +{ + delete my.completer; + delete my.dirModel; +} + +void SaveViewDialog::reset(bool hostDynamic) +{ + QDir d; + if (!d.exists(my.userDir)) + d.mkpath(my.userDir); + setPath(my.userDir); + preserveHostCheckBox->setChecked(hostDynamic == false); +} + +void SaveViewDialog::setPathUi(const QString &path) +{ + if (path.isEmpty()) + return; + + int index = pathComboBox->findText(path); + if (index == -1) { + pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder), + path); + index = pathComboBox->count() - 1; + } + pathComboBox->setCurrentIndex(index); + dirListView->selectionModel()->clear(); + + userToolButton->setChecked(path == my.userDir); + + fileNameLineEdit->setModified(false); + fileNameLineEdit->clear(); +} + +void SaveViewDialog::setPath(const QModelIndex &index) +{ + console->post("SaveViewDialog::setPath QModelIndex path=%s", + (const char *)my.dirModel->filePath(index).toAscii()); + my.dirIndex = index; + my.dirModel->refresh(index); + dirListView->setRootIndex(index); + setPathUi(my.dirModel->filePath(index)); +} + +void SaveViewDialog::setPath(const QString &path) +{ + console->post("SaveViewDialog::setPath QString path=%s", + (const char *)path.toAscii()); + my.dirIndex = my.dirModel->index(path); + my.dirModel->refresh(my.dirIndex); + dirListView->setRootIndex(my.dirIndex); + setPathUi(path); +} + +void SaveViewDialog::pathComboBox_currentIndexChanged(QString path) +{ + console->post("SaveViewDialog::pathComboBox_currentIndexChanged"); + setPath(path); +} + +void SaveViewDialog::parentToolButton_clicked() +{ + console->post("SaveViewDialog::parentToolButton_clicked"); + setPath(my.dirModel->parent(my.dirIndex)); +} + +void SaveViewDialog::userToolButton_clicked(bool enabled) +{ + if (enabled) { + QDir dir; + if (!dir.exists(my.userDir)) + dir.mkpath(my.userDir); + setPath(my.userDir); + } +} + +void SaveViewDialog::dirListView_selectionChanged() +{ + QItemSelectionModel *selections = dirListView->selectionModel(); + QModelIndexList selectedIndexes = selections->selectedIndexes(); + + console->post("SaveViewDialog::dirListView_clicked"); + + my.completer->setCompletionPrefix(my.dirModel->filePath(my.dirIndex)); + if (selectedIndexes.count() != 1) + fileNameLineEdit->setText(""); + else + fileNameLineEdit->setText(my.dirModel->fileName(selectedIndexes.at(0))); +} + +void SaveViewDialog::dirListView_activated(const QModelIndex &index) +{ + QFileInfo fi = my.dirModel->fileInfo(index); + + console->post("SaveViewDialog::dirListView_activated"); + + if (fi.isDir()) { + setPath(index); + } + else { + QString msg = fi.filePath(); + msg.prepend(tr("View file ")); + msg.append(tr(" exists. Overwrite?\n")); + if (QMessageBox::question(this, pmProgname, msg, + QMessageBox::Cancel|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::Ok, QMessageBox::NoButton) == QMessageBox::Ok) + if (saveViewFile(fi.absoluteFilePath()) == true) + done(0); + } +} + +void SaveViewDialog::preserveHostCheckBox_toggled(bool hostPreserve) +{ + my.hostDynamic = (hostPreserve == false); +} + +void SaveViewDialog::preserveSizeCheckBox_toggled(bool sizePreserve) +{ + my.sizeDynamic = (sizePreserve == false); +} + +bool SaveViewDialog::saveViewFile(const QString &filename) +{ + if (my.sizeDynamic == false) + setGlobals(pmchart->size().width(), pmchart->size().height(), + activeGroup->visibleHistory(), + pmchart->pos().x(), pmchart->pos().y()); + return saveView(filename, my.hostDynamic, my.sizeDynamic, false, true); +} + +void SaveViewDialog::savePushButton_clicked() +{ + QString msg, filename; + QChar sep(__pmPathSeparator()); + + if (fileNameLineEdit->isModified()) { + filename = fileNameLineEdit->text().trimmed(); + filename.prepend(my.dirModel->filePath(my.dirIndex).append(sep)); + } else { + QItemSelectionModel *selections = dirListView->selectionModel(); + QModelIndexList selectedIndexes = selections->selectedIndexes(); + + if (selectedIndexes.count() == 1) + filename = my.dirModel->filePath(selectedIndexes.at(0)); + } + + if (filename.isEmpty()) + msg = tr("No View file specified"); + else { + QFileInfo fi(filename); + if (fi.isDir()) + setPath(filename); + else if (fi.exists()) { + msg = filename; + msg.prepend(tr("View file ")); + msg.append(tr(" exists. Overwrite?\n")); + if (QMessageBox::question(this, pmProgname, msg, + QMessageBox::Cancel|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::Ok, QMessageBox::NoButton) == QMessageBox::Ok) + if (saveViewFile(fi.absoluteFilePath()) == true) + done(0); + msg = ""; + } + else if (saveViewFile(fi.absoluteFilePath()) == true) + done(0); + } + + if (msg.isEmpty() == false) { + QMessageBox::warning(this, pmProgname, msg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } +} diff --git a/src/pmchart/saveviewdialog.h b/src/pmchart/saveviewdialog.h new file mode 100644 index 0000000..8ad81a2 --- /dev/null +++ b/src/pmchart/saveviewdialog.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SAVEVIEWDIALOG_H +#define SAVEVIEWDIALOG_H + +#include "ui_saveviewdialog.h" +#include <QtGui/QDirModel> + +class Chart; + +class SaveViewDialog : public QDialog, public Ui::SaveViewDialog +{ + Q_OBJECT + +public: + SaveViewDialog(QWidget* parent); + ~SaveViewDialog(); + void reset(bool); + + static void saveChart(FILE *, Chart *, bool); + static bool saveView(QString, bool, bool, bool, bool); + static void setGlobals(int w, int h, int pts, int x, int y); + +public slots: + virtual void parentToolButton_clicked(); + virtual void userToolButton_clicked(bool); + virtual void pathComboBox_currentIndexChanged(QString); + + virtual void dirListView_selectionChanged(); + virtual void dirListView_activated(const QModelIndex &); + + virtual void preserveHostCheckBox_toggled(bool); + virtual void preserveSizeCheckBox_toggled(bool); + virtual void savePushButton_clicked(); + +private: + struct { + QString userDir; + bool hostDynamic; // on-the-fly or explicit-hostnames-in-view + bool sizeDynamic; // on-the-fly or explicit-geometry-in-view + QDirModel *dirModel; + QModelIndex dirIndex; + QCompleter *completer; + } my; + + void setPath(const QString &); + void setPath(const QModelIndex &); + void setPathUi(const QString &); + + bool saveViewFile(const QString &); +}; + +#endif // SAVEVIEWDIALOG_H diff --git a/src/pmchart/saveviewdialog.ui b/src/pmchart/saveviewdialog.ui new file mode 100644 index 0000000..877567e --- /dev/null +++ b/src/pmchart/saveviewdialog.ui @@ -0,0 +1,453 @@ +<ui version="4.0" > + <class>SaveViewDialog</class> + <widget class="QDialog" name="SaveViewDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>414</width> + <height>260</height> + </rect> + </property> + <property name="windowTitle" > + <string>Save View</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/view.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="2" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLabel" name="fileNameLabel" > + <property name="minimumSize" > + <size> + <width>60</width> + <height>20</height> + </size> + </property> + <property name="text" > + <string>Filename:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="fileNameLineEdit" /> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QListView" name="dirListView" > + <property name="font" > + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="selectionMode" > + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="iconSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="flow" > + <enum>QListView::TopToBottom</enum> + </property> + <property name="isWrapping" stdset="0" > + <bool>true</bool> + </property> + <property name="resizeMode" > + <enum>QListView::Adjust</enum> + </property> + <property name="gridSize" > + <size> + <width>150</width> + <height>20</height> + </size> + </property> + <property name="uniformItemSizes" > + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="pathLabel" > + <property name="minimumSize" > + <size> + <width>60</width> + <height>20</height> + </size> + </property> + <property name="text" > + <string>Path:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="pathComboBox" /> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>8</width> + <height>26</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="parentToolButton" > + <property name="minimumSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="toolTip" > + <string>Parent</string> + </property> + <property name="statusTip" > + <string/> + </property> + <property name="whatsThis" > + <string>Open the parent directory</string> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="checkable" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>8</width> + <height>26</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="userToolButton" > + <property name="minimumSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="text" > + <string/> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/toolusers.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="checkable" > + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="preserveHostCheckBox" > + <property name="layoutDirection" > + <enum>Qt::LeftToRight</enum> + </property> + <property name="text" > + <string>Preserve hostnames in View</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="preserveSizeCheckBox" > + <property name="text" > + <string>Preserve window geometry in View</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>101</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>2</number> + </property> + <item> + <widget class="QPushButton" name="savePushButton" > + <property name="text" > + <string>Save</string> + </property> + <property name="autoDefault" > + <bool>false</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelButton" > + <property name="text" > + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>savePushButton</sender> + <signal>clicked()</signal> + <receiver>SaveViewDialog</receiver> + <slot>savePushButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>fileNameLineEdit</sender> + <signal>returnPressed()</signal> + <receiver>savePushButton</receiver> + <slot>click()</slot> + <hints> + <hint type="sourcelabel" > + <x>181</x> + <y>182</y> + </hint> + <hint type="destinationlabel" > + <x>355</x> + <y>183</y> + </hint> + </hints> + </connection> + <connection> + <sender>cancelButton</sender> + <signal>clicked()</signal> + <receiver>SaveViewDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>355</x> + <y>217</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>preserveHostCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>SaveViewDialog</receiver> + <slot>preserveHostCheckBox_toggled(bool)</slot> + <hints> + <hint type="sourcelabel" > + <x>159</x> + <y>217</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>parentToolButton</sender> + <signal>clicked()</signal> + <receiver>SaveViewDialog</receiver> + <slot>parentToolButton_clicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>309</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>userToolButton</sender> + <signal>clicked(bool)</signal> + <receiver>SaveViewDialog</receiver> + <slot>userToolButton_clicked(bool)</slot> + <hints> + <hint type="sourcelabel" > + <x>385</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>pathComboBox</sender> + <signal>currentIndexChanged(QString)</signal> + <receiver>SaveViewDialog</receiver> + <slot>pathComboBox_currentIndexChanged(QString)</slot> + <hints> + <hint type="sourcelabel" > + <x>180</x> + <y>22</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>dirListView</sender> + <signal>activated(QModelIndex)</signal> + <receiver>SaveViewDialog</receiver> + <slot>dirListView_activated(QModelIndex)</slot> + <hints> + <hint type="sourcelabel" > + <x>202</x> + <y>102</y> + </hint> + <hint type="destinationlabel" > + <x>202</x> + <y>120</y> + </hint> + </hints> + </connection> + <connection> + <sender>preserveSizeCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>SaveViewDialog</receiver> + <slot>preserveSizeCheckBox_toggled(bool)</slot> + <hints> + <hint type="sourcelabel" > + <x>110</x> + <y>233</y> + </hint> + <hint type="destinationlabel" > + <x>206</x> + <y>129</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/searchdialog.cpp b/src/pmchart/searchdialog.cpp new file mode 100644 index 0000000..668d61a --- /dev/null +++ b/src/pmchart/searchdialog.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "searchdialog.h" +#include <QMessageBox> +#include "main.h" + +SearchDialog::SearchDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); + my.count = 0; +} + +void SearchDialog::languageChange() +{ + retranslateUi(this); +} + +void SearchDialog::reset(QTreeWidget *pmns) +{ + my.pmns = pmns; + buttonOk->setEnabled(false); + if (matchList->count() > 0) + buttonAll->setEnabled(true); + else + buttonAll->setEnabled(false); + changed(); + listchanged(); +} + +void SearchDialog::clear() +{ + this->hostPattern->clear(); + this->metricPattern->clear(); + this->instancePattern->clear(); + this->resultStatus->setText(""); + matchList->clear(); + my.pmnsList.clear(); + buttonSearch->setEnabled(false); + buttonOk->setEnabled(false); + buttonAll->setEnabled(false); +} + +void SearchDialog::changed() +{ + bool hostEnabled = (hostPattern->text() != QString::null); + bool metricEnabled = (metricPattern->text() != QString::null); + bool instanceEnabled = (instancePattern->text() != QString::null); + buttonSearch->setEnabled(hostEnabled || metricEnabled || instanceEnabled); +} + +void SearchDialog::selectall() +{ + matchList->selectAll(); +} + +void SearchDialog::listchanged() +{ + QList<QListWidgetItem *> check = matchList->selectedItems(); + if (check.size() == 0) { + buttonOk->setEnabled(false); + } + else { + buttonOk->setEnabled(true); + } +} + +void SearchDialog::search() +{ + QString res; + QTreeWidgetItemIterator iterator(my.pmns, QTreeWidgetItemIterator::All); + int h_match = 0; + int m_match = 0; + int i_match; + int count; + QRegExp h_rx; + QRegExp m_rx; + QRegExp i_rx; + + console->post(PmChart::DebugUi, + "SearchDialog::search host=\"%s\" metric=\"%s\" instance=\"%s\"", + (const char *)hostPattern->text().toAscii(), + (const char *)metricPattern->text().toAscii(), + (const char *)instancePattern->text().toAscii()); + + if (hostPattern->text() == QString::null && + metricPattern->text() == QString::null && + instancePattern->text() == QString::null) { + // got here via pressing Enter from one of the pattern input fields, + // and all the fields are empty ... do nothing + return; + } + + if (hostPattern->text() != QString::null) + h_rx.setPattern(hostPattern->text()); + + if (metricPattern->text() != QString::null) + m_rx.setPattern(metricPattern->text()); + + if (instancePattern->text() != QString::null) + i_rx.setPattern(instancePattern->text()); + + matchList->clear(); + my.pmnsList.clear(); + count = 0; + for (; (*iterator); ++iterator) { + NameSpace *item = (NameSpace *)(*iterator); + if (item->isRoot()) { + // host name + if (hostPattern->text() != QString::null) + h_match = h_rx.indexIn(item->sourceName()); + else + h_match = 0; + if (h_match >= 0) { + console->post(PmChart::DebugUi, "SearchDialog::search " + "host=\"%s\" h_match=%d", + (const char *)item->sourceName().toAscii(), h_match); + } + item->setExpanded(true, false); + m_match = -2; + } + else if (h_match >= 0 && item->isMetric()) { + // metric name + count++; + if (metricPattern->text() != QString::null) + m_match = m_rx.indexIn(item->metricName()); + else + m_match = 0; + if (m_match >= 0) { + if (item->isLeaf() && + instancePattern->text() == QString::null) { + QString fqn = item->sourceName().append(":"); + fqn.append(item->metricName()); + matchList->addItem(fqn); + my.pmnsList.append(item); + m_match = -2; + + console->post(PmChart::DebugUi, "SearchDialog::search " + "host=%s h_match=%d metric=%s m_match=%d", + (const char *)item->sourceName().toAscii(), h_match, + (const char *)item->metricName().toAscii(), m_match); + } + if (item->isLeaf() == false) { + // has instance domain + item->setExpanded(true, false); + count--; + } + } + } + else if (h_match >= 0 && m_match >= 0 && item->isInst()) { + // matched last metric, now related instance name ... + count++; + if (instancePattern->text() != QString::null) + i_match = i_rx.indexIn(item->metricInstance()); + else + i_match = 0; + if (i_match >= 0) { + QString fqn = item->sourceName().append(":"); + fqn.append(item->metricName()); + fqn.append("[").append(item->metricInstance()).append("]"); + matchList->addItem(fqn); + my.pmnsList.append(item); + + console->post(PmChart::DebugUi, "SearchDialog::search " + "host=%s h_match=%d metric=%s m_match=%d inst=%s i_match=%d", + (const char *)item->sourceName().toAscii(), h_match, + (const char *)item->metricName().toAscii(), m_match, + (const char *)item->metricInstance().toAscii(), i_match); + } + } + else if (item->isNonLeaf()) { + item->setExpanded(true, false); + m_match = -2; + } + else + m_match = -2; + } + + if (matchList->count() > 0) { + buttonAll->setEnabled(true); + } + + QTextStream(&res) << "Matched " << matchList->count() << " of " << count << " possibilities"; + this->resultStatus->setText(res); +} + +void SearchDialog::ok() +{ + QList<QListWidgetItem *> selected = matchList->selectedItems(); + int i; + int row; + NameSpace *parent; + + for (i = 0; i < selected.size(); i++) { + row = matchList->row(selected[i]); +#if DESPERATE + fprintf(stderr, "[%d] %s:%s[%s]\n", + row, (const char *)my.pmnsList[row]->sourceName().toAscii(), + (const char *)my.pmnsList[row]->metricName().toAscii(), + (const char *)my.pmnsList[row]->metricInstance().toAscii()); +#endif + my.pmnsList[row]->setSelected(true); + parent = (NameSpace *)my.pmnsList[row]->parent(); + while (parent->isRoot() == false) { +#if DESPERATE + fprintf(stderr, "SearchDialog::ok expand: %s\n", + (const char *)parent->metricName().toAscii()); +#endif + parent->QTreeWidgetItem::setExpanded(true); + parent = (NameSpace *)parent->parent(); + } + } + + accept(); +} diff --git a/src/pmchart/searchdialog.h b/src/pmchart/searchdialog.h new file mode 100644 index 0000000..f156e9a --- /dev/null +++ b/src/pmchart/searchdialog.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SEARCHDIALOG_H +#define SEARCHDIALOG_H + +#include "ui_searchdialog.h" +#include <QtCore/QProcess> + +class NameSpace; +class QTreeWidget; + +class SearchDialog : public QDialog, public Ui::SearchDialog +{ + Q_OBJECT + +public: + SearchDialog(QWidget* parent); + void reset(QTreeWidget *pmns); + +public slots: + virtual void clear(); + virtual void search(); + virtual void ok(); + virtual void changed(); + virtual void selectall(); + virtual void listchanged(); + +protected slots: + virtual void languageChange(); + +private: + struct { + QTreeWidget *pmns; + bool isArchive; + QString source; + QString metric; + int count; + QList<NameSpace *> pmnsList; + } my; +}; + +#endif // SEARCHDIALOG_H diff --git a/src/pmchart/searchdialog.ui b/src/pmchart/searchdialog.ui new file mode 100644 index 0000000..c2cd03c --- /dev/null +++ b/src/pmchart/searchdialog.ui @@ -0,0 +1,353 @@ +<ui version="4.0" > + <class>SearchDialog</class> + <widget class="QDialog" name="SearchDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>452</width> + <height>345</height> + </rect> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle" > + <string>Metric Search</string> + </property> + <layout class="QGridLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" > + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item row="0" column="0" > + <widget class="QLabel" name="hostLabel" > + <property name="text" > + <string>Host Name Pattern:</string> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="metricLabel" > + <property name="text" > + <string>Metric Name Pattern:</string> + </property> + </widget> + </item> + <item row="2" column="0" > + <widget class="QLabel" name="instanceLabel" > + <property name="text" > + <string>Instance Name Pattern:</string> + </property> + </widget> + </item> + <item row="0" column="1" > + <widget class="QLineEdit" name="hostPattern" /> + </item> + <item row="1" column="1" > + <widget class="QLineEdit" name="metricPattern" /> + </item> + <item row="2" column="1" > + <widget class="QLineEdit" name="instancePattern" /> + </item> + </layout> + </item> + <item row="2" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QPushButton" name="buttonAll" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>Select &All</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="resultStatus" > + <property name="text" > + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>271</width> + <height>27</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonSearch" > + <property name="text" > + <string>&Search</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonClear" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>C&lear</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0" > + <widget class="QListWidget" name="matchList" > + <property name="selectionMode" > + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + </widget> + </item> + <item row="4" column="0" > + <layout class="QHBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>71</width> + <height>27</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>hostPattern</tabstop> + <tabstop>metricPattern</tabstop> + <tabstop>instancePattern</tabstop> + <tabstop>buttonSearch</tabstop> + <tabstop>matchList</tabstop> + <tabstop>buttonOk</tabstop> + <tabstop>buttonCancel</tabstop> + <tabstop>buttonClear</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonSearch</sender> + <signal>clicked()</signal> + <receiver>SearchDialog</receiver> + <slot>search()</slot> + <hints> + <hint type="sourcelabel" > + <x>280</x> + <y>250</y> + </hint> + <hint type="destinationlabel" > + <x>225</x> + <y>83</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonClear</sender> + <signal>clicked()</signal> + <receiver>SearchDialog</receiver> + <slot>clear()</slot> + <hints> + <hint type="sourcelabel" > + <x>450</x> + <y>96</y> + </hint> + <hint type="destinationlabel" > + <x>225</x> + <y>83</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>SearchDialog</receiver> + <slot>ok()</slot> + <hints> + <hint type="sourcelabel" > + <x>365</x> + <y>250</y> + </hint> + <hint type="destinationlabel" > + <x>225</x> + <y>83</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>SearchDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>450</x> + <y>250</y> + </hint> + <hint type="destinationlabel" > + <x>225</x> + <y>83</y> + </hint> + </hints> + </connection> + <connection> + <sender>hostPattern</sender> + <signal>textEdited(QString)</signal> + <receiver>SearchDialog</receiver> + <slot>changed()</slot> + <hints> + <hint type="sourcelabel" > + <x>303</x> + <y>21</y> + </hint> + <hint type="destinationlabel" > + <x>229</x> + <y>130</y> + </hint> + </hints> + </connection> + <connection> + <sender>metricPattern</sender> + <signal>textEdited(QString)</signal> + <receiver>SearchDialog</receiver> + <slot>changed()</slot> + <hints> + <hint type="sourcelabel" > + <x>303</x> + <y>21</y> + </hint> + <hint type="destinationlabel" > + <x>229</x> + <y>130</y> + </hint> + </hints> + </connection> + <connection> + <sender>instancePattern</sender> + <signal>textEdited(QString)</signal> + <receiver>SearchDialog</receiver> + <slot>changed()</slot> + <hints> + <hint type="sourcelabel" > + <x>303</x> + <y>79</y> + </hint> + <hint type="destinationlabel" > + <x>229</x> + <y>130</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonAll</sender> + <signal>clicked()</signal> + <receiver>SearchDialog</receiver> + <slot>selectall()</slot> + <hints> + <hint type="sourcelabel" > + <x>47</x> + <y>118</y> + </hint> + <hint type="destinationlabel" > + <x>230</x> + <y>195</y> + </hint> + </hints> + </connection> + <connection> + <sender>matchList</sender> + <signal>itemSelectionChanged()</signal> + <receiver>SearchDialog</receiver> + <slot>listchanged()</slot> + <hints> + <hint type="sourcelabel" > + <x>230</x> + <y>242</y> + </hint> + <hint type="destinationlabel" > + <x>230</x> + <y>195</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/seealsodialog.cpp b/src/pmchart/seealsodialog.cpp new file mode 100644 index 0000000..ae663e8 --- /dev/null +++ b/src/pmchart/seealsodialog.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "seealsodialog.h" + +SeeAlsoDialog::SeeAlsoDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); +} + +void SeeAlsoDialog::seeAlsoOKButton_clicked() +{ + done(0); +} diff --git a/src/pmchart/seealsodialog.h b/src/pmchart/seealsodialog.h new file mode 100644 index 0000000..b5465b6 --- /dev/null +++ b/src/pmchart/seealsodialog.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SEEALSODIALOG_H +#define SEEALSODIALOG_H + +#include "ui_seealsodialog.h" + +class SeeAlsoDialog : public QDialog, public Ui::SeeAlsoDialog +{ + Q_OBJECT + +public: + SeeAlsoDialog(QWidget* parent); + +public slots: + virtual void seeAlsoOKButton_clicked(); +}; + +#endif // SEEALSODIALOG_H diff --git a/src/pmchart/seealsodialog.ui b/src/pmchart/seealsodialog.ui new file mode 100644 index 0000000..ed89b55 --- /dev/null +++ b/src/pmchart/seealsodialog.ui @@ -0,0 +1,235 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SeeAlsoDialog</class> + <widget class="QDialog" name="SeeAlsoDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>410</width> + <height>250</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>410</width> + <height>250</height> + </size> + </property> + <property name="windowTitle"> + <string>Acknowledgements</string> + </property> + <property name="windowIcon"> + <iconset resource="pmchart.qrc"> + <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="0"> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="seeAlsoPCPPixmapLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>76</width> + <height>82</height> + </size> + </property> + <property name="pixmap"> + <pixmap resource="pmchart.qrc">:/images/aboutpcp.png</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>51</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="versionPCPTextLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><b><i>Performance Co-Pilot (PCP)</i></b><br> +Copyright (c) 2010-2014 Red Hat<br> +Copyright (c) 2006-2012 Aconex<br> +Copyright (c) 1995-2007 SGI<br> +... and many, many others!<br> +<i>http://oss.sgi.com/projects/pcp</i></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="versionQwtTextLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><b><i>Qt Widgets for Technical Apps (Qwt)</i></b><br> +Copyright (c) 1997 Josef Wilgen<br> +Copyright (c) 2002 Uwe Rathmann<br> +<i>http://qwt.sourceforge.net/</i></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="seeAlsoOKButton"> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + <property name="text"> + <string>OK</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc"/> + </resources> + <connections> + <connection> + <sender>seeAlsoOKButton</sender> + <signal>clicked()</signal> + <receiver>SeeAlsoDialog</receiver> + <slot>seeAlsoOKButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/settingsdialog.cpp b/src/pmchart/settingsdialog.cpp new file mode 100644 index 0000000..2daa8b8 --- /dev/null +++ b/src/pmchart/settingsdialog.cpp @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2007, 2009, Aconex. All Rights Reserved. + * + * 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. + */ +#include "settingsdialog.h" +#include <QtGui/QCompleter> +#include <QtGui/QMessageBox> +#include <QtGui/QFontDatabase> +#include <QtGui/QListWidgetItem> +#include "main.h" +#include "hostdialog.h" + +SettingsDialog::SettingsDialog(QWidget* parent) + : QDialog(parent), disabled(Qt::Dense4Pattern) +{ + setupUi(this); + +#ifndef IS_DARWIN // only relevent as an option on Mac OS X + nativeToolbarCheckBox->setEnabled(false); +#endif + + setupActionsList(); + enabled = actionListWidget->item(0)->background(); + setupHostComboBox(activeGroup->context()->source().host()); + + chartDeltaLineEdit->setValidator( + new QDoubleValidator(0.001, INT_MAX, 3, chartDeltaLineEdit)); + loggerDeltaLineEdit->setValidator( + new QDoubleValidator(0.001, INT_MAX, 3, loggerDeltaLineEdit)); +} + +void SettingsDialog::languageChange() +{ + retranslateUi(this); +} + +int SettingsDialog::colorArray(ColorButton ***array) +{ + static ColorButton *buttons[] = { + colorButton1, colorButton2, colorButton3, colorButton4, + colorButton5, colorButton6, colorButton7, colorButton8, + colorButton9, colorButton10, colorButton11, colorButton12, + colorButton13, colorButton14, colorButton15, colorButton16, + colorButton17, colorButton18, colorButton19, colorButton20, + colorButton21, colorButton22, + }; + *array = &buttons[0]; + return sizeof(buttons) / sizeof(buttons[0]); +} + +void SettingsDialog::enableUi() +{ + bool colors = (settingsTab->currentIndex() == 1); + bool userScheme = (schemeComboBox->currentIndex() > 1); + + if (colors) { + removeSchemeButton->show(); + updateSchemeButton->show(); + } + else { + removeSchemeButton->hide(); + updateSchemeButton->hide(); + } + removeSchemeButton->setEnabled(userScheme); +} + +void SettingsDialog::reset() +{ + my.chartUnits = QmcTime::Seconds; + chartDeltaLineEdit->setText( + QmcTime::deltaString(globalSettings.chartDelta, my.chartUnits)); + my.loggerUnits = QmcTime::Seconds; + loggerDeltaLineEdit->setText( + QmcTime::deltaString(globalSettings.loggerDelta, my.loggerUnits)); + + my.visibleHistory = my.sampleHistory = 0; + visibleCounter->setValue(globalSettings.visibleHistory); + visibleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + visibleSlider->setValue(globalSettings.visibleHistory); + visibleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + sampleCounter->setValue(globalSettings.sampleHistory); + sampleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + sampleSlider->setValue(globalSettings.sampleHistory); + sampleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints()); + + defaultBackgroundButton->setColor(QColor(globalSettings.chartBackground)); + selectedHighlightButton->setColor(QColor(globalSettings.chartHighlight)); + + setupSchemeComboBox(); + setupSchemePalette(); + setupActionsList(); + setupFontLists(); + + setupSavedHostsList(); + removeHostButton->setEnabled(globalSettings.savedHosts.size() > 0); + + startupToolbarCheckBox->setCheckState( + globalSettings.initialToolbar ? Qt::Checked : Qt::Unchecked); + nativeToolbarCheckBox->setCheckState( + globalSettings.nativeToolbar ? Qt::Checked : Qt::Unchecked); + toolbarAreasComboBox->setCurrentIndex( + globalSettings.toolbarLocation ? 1: 0); + + enableUi(); +} + +void SettingsDialog::settingsTab_currentChanged(int) +{ + enableUi(); +} + +// +// Sampling preferences +// + +void SettingsDialog::chartDeltaLineEdit_editingFinished() +{ + double input = globalSettings.chartDelta; + + // convert to seconds, make sure its still in range 0.001-INT_MAX + if (chartDeltaLineEdit->isModified()) + input = QmcTime::deltaValue(chartDeltaLineEdit->text(), my.chartUnits); + if (input < 0.001 || input > INT_MAX) { + QString msg = tr("Default Chart Sampling Interval is invalid.\n"); + msg.append(chartDeltaLineEdit->text()); + msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n"); + QMessageBox::warning(this, pmProgname, msg); + } + else if (input != globalSettings.chartDelta) { + globalSettings.chartDeltaModified = true; + globalSettings.chartDelta = input; + writeSettings(); + } +} + +void SettingsDialog::loggerDeltaLineEdit_editingFinished() +{ + double input = globalSettings.loggerDelta; + + // convert to seconds, make sure its still in range 0.001-INT_MAX + if (loggerDeltaLineEdit->isModified()) + input = QmcTime::deltaValue(loggerDeltaLineEdit->text(), my.loggerUnits); + if (input < 0.001 || input > INT_MAX) { + QString msg = tr("Default Record Sampling Interval is invalid.\n"); + msg.append(loggerDeltaLineEdit->text()); + msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n"); + QMessageBox::warning(this, pmProgname, msg); + } + else if (input != globalSettings.loggerDelta) { + globalSettings.loggerDeltaModified = true; + globalSettings.loggerDelta = input; + writeSettings(); + } +} + +void SettingsDialog::chartDeltaUnitsComboBox_activated(int value) +{ + double v = QmcTime::deltaValue(chartDeltaLineEdit->text(), my.chartUnits); + my.chartUnits = (QmcTime::DeltaUnits)value; + chartDeltaLineEdit->setText(QmcTime::deltaString(v, my.chartUnits)); +} + +void SettingsDialog::loggerDeltaUnitsComboBox_activated(int value) +{ + double v = QmcTime::deltaValue(loggerDeltaLineEdit->text(), my.loggerUnits); + my.loggerUnits = (QmcTime::DeltaUnits)value; + loggerDeltaLineEdit->setText(QmcTime::deltaString(v, my.loggerUnits)); +} + +void SettingsDialog::visible_valueChanged(int value) +{ + if (value != my.visibleHistory) { + my.visibleHistory = value; + displayVisibleCounter(); + displayVisibleSlider(); + if (my.visibleHistory > my.sampleHistory) + sampleSlider->setValue(value); + globalSettings.visibleHistoryModified = true; + globalSettings.visibleHistory = my.visibleHistory; + writeSettings(); + } +} + +void SettingsDialog::sample_valueChanged(int value) +{ + if (value != my.sampleHistory) { + my.sampleHistory = value; + displayTotalCounter(); + displayTotalSlider(); + if (my.visibleHistory > my.sampleHistory) + visibleSlider->setValue(value); + globalSettings.sampleHistoryModified = true; + globalSettings.sampleHistory = my.sampleHistory; + writeSettings(); + } +} + +void SettingsDialog::displayTotalSlider() +{ + sampleSlider->blockSignals(true); + sampleSlider->setValue(my.sampleHistory); + sampleSlider->blockSignals(false); +} + +void SettingsDialog::displayVisibleSlider() +{ + visibleSlider->blockSignals(true); + visibleSlider->setValue(my.visibleHistory); + visibleSlider->blockSignals(false); +} + +void SettingsDialog::displayTotalCounter() +{ + sampleCounter->blockSignals(true); + sampleCounter->setValue(my.sampleHistory); + sampleCounter->blockSignals(false); +} + +void SettingsDialog::displayVisibleCounter() +{ + visibleCounter->blockSignals(true); + visibleCounter->setValue(my.visibleHistory); + visibleCounter->blockSignals(false); +} + +// +// Font preferences +// + +void SettingsDialog::setupFontLists() +{ + QFontDatabase database; + const QStringList families = database.families(); + + console->post(PmChart::DebugUi, + "SettingsDialog::setupFontLists: default %s [%d]", + PmChart::defaultFontFamily(), + PmChart::defaultFontSize()); + + QCompleter *completeFamily = new QCompleter(families, familyLineEdit); + familyLineEdit->setCompleter(completeFamily); + + familyListWidget->insertItems(0, families); + QString family = globalSettings.fontFamily; + familyLineEdit->setText(family); + updateFontList(familyListWidget, family); + + styleListWidget->insertItems(0, database.styles(family)); + QString style = globalSettings.fontStyle; + styleLineEdit->setText(style); + updateFontList(styleListWidget, style); + + QStringList sizes; + foreach (int points, database.smoothSizes(family, style)) + sizes << QString::number(points); + sizeListWidget->insertItems(0, sizes); + QString size = QString::number(globalSettings.fontSize); + sizeLineEdit->setText(size); + updateFontList(sizeListWidget, size); +} + +void SettingsDialog::updateFontList(QListWidget *list, const QString &text) +{ + QList<QListWidgetItem *>items; + + items = list->findItems(text, Qt::MatchExactly); + if (items.size() > 0) + list->setCurrentItem(items.at(0)); +} + +void SettingsDialog::familyLineEdit_editingFinished() +{ + updateFontList(familyListWidget, familyLineEdit->text()); +} + +void SettingsDialog::familyListWidget_itemClicked(QListWidgetItem *item) +{ + familyLineEdit->setText(item->text()); +} + +void SettingsDialog::styleLineEdit_editingFinished() +{ + updateFontList(styleListWidget, styleLineEdit->text()); +} + +void SettingsDialog::styleListWidget_itemClicked(QListWidgetItem *item) +{ + styleLineEdit->setText(item->text()); +} + +void SettingsDialog::sizeLineEdit_editingFinished() +{ + updateFontList(sizeListWidget, sizeLineEdit->text()); +} + +void SettingsDialog::sizeListWidget_itemClicked(QListWidgetItem *item) +{ + sizeLineEdit->setText(item->text()); +} + +void SettingsDialog::resetFontButton_clicked() +{ + QString family = PmChart::defaultFontFamily(); + familyLineEdit->setText(family); + updateFontList(familyListWidget, family); + globalSettings.fontFamilyModified = true; + globalSettings.fontFamily = family; + + QString style = QString("Normal"); + styleLineEdit->setText(style); + updateFontList(styleListWidget, style); + globalSettings.fontStyleModified = true; + globalSettings.fontStyle = style; + + QString size = QString::number(PmChart::defaultFontSize()); + int fontSize = size.toInt(); + sizeLineEdit->setText(size); + updateFontList(sizeListWidget, size); + globalSettings.fontSizeModified = true; + globalSettings.fontSize = fontSize; + + writeSettings(); + pmchart->updateFont(family, style, fontSize); +} + +void SettingsDialog::applyFontButton_clicked() +{ + QString family = familyLineEdit->text(); + globalSettings.fontFamilyModified = true; + globalSettings.fontFamily = family; + + QString style = styleLineEdit->text(); + globalSettings.fontStyleModified = true; + globalSettings.fontStyle = style; + + int size = sizeLineEdit->text().toInt(); + globalSettings.fontSizeModified = true; + globalSettings.fontSize = size; + + writeSettings(); + pmchart->updateFont(family, style, size); +} + +// +// Toolbar preferences +// + +void SettingsDialog::setupActionsList() +{ + QList<QAction*> actionsList = pmchart->toolbarActionsList(); + QList<QAction*> enabledList = pmchart->enabledActionsList(); + + actionListWidget->blockSignals(true); + actionListWidget->clear(); + for (int i = 0; i < actionsList.size(); i++) { + QAction *action = actionsList.at(i); + QListWidgetItem *item = new QListWidgetItem(action->icon(), action->iconText()); + actionListWidget->insertItem(i, item); + if (enabledList.contains(action) == false) + item->setBackground(disabled); + } + actionListWidget->blockSignals(false); +} + +void SettingsDialog::startupToolbarCheckBox_clicked() +{ + globalSettings.initialToolbar = + startupToolbarCheckBox->checkState() == Qt::Checked; + globalSettings.initialToolbarModified = true; + writeSettings(); +} + +void SettingsDialog::nativeToolbarCheckBox_clicked() +{ + globalSettings.nativeToolbar = + nativeToolbarCheckBox->checkState() == Qt::Checked; + globalSettings.nativeToolbarModified = true; + writeSettings(); + pmchart->updateToolbarLocation(); +} + +void SettingsDialog::toolbarAreasComboBox_currentIndexChanged(int) +{ + globalSettings.toolbarLocation = toolbarAreasComboBox->currentIndex(); + globalSettings.toolbarLocationModified = true; + writeSettings(); + pmchart->updateToolbarLocation(); +} + +void SettingsDialog::actionListWidget_itemClicked(QListWidgetItem *item) +{ + QStringList actions; + + item->setBackground(item->background() == disabled ? enabled : disabled); + for (int i = 0; i < actionListWidget->count(); i++) { + QListWidgetItem *listitem = actionListWidget->item(i); + if (listitem->background() != disabled) + actions.append(listitem->text()); + } + globalSettings.toolbarActions = actions; + globalSettings.toolbarActionsModified = true; + writeSettings(); + pmchart->setEnabledActionsList(actions, true); + pmchart->updateToolbarContents(); +} + +// +// Colors preferences +// + +void SettingsDialog::selectedHighlightButton_clicked() +{ + selectedHighlightButton->clicked(); + if (selectedHighlightButton->isSet()) { + globalSettings.chartHighlightModified = true; + globalSettings.chartHighlight = selectedHighlightButton->color(); + globalSettings.chartHighlightName = + globalSettings.chartHighlight.name(); + writeSettings(); + } +} + +void SettingsDialog::defaultBackgroundButton_clicked() +{ + defaultBackgroundButton->clicked(); + if (defaultBackgroundButton->isSet()) { + globalSettings.chartBackgroundModified = true; + globalSettings.chartBackground = defaultBackgroundButton->color(); + globalSettings.chartBackgroundName = + globalSettings.chartBackground.name(); + pmchart->updateBackground(); + writeSettings(); + } +} + +void SettingsDialog::colorButtonClicked(int n) +{ + ColorButton **buttons; + + colorArray(&buttons); + buttons[n-1]->clicked(); + if (buttons[n-1]->isSet()) { + ColorButton **buttons; + + int colorCount = colorArray(&buttons); + for (int i = 0; i < colorCount; i++) { + QColor c = buttons[i]->color(); + if (c == Qt::white) + continue; + globalSettings.defaultScheme.addColor(c); + } + + globalSettings.defaultSchemeModified = true; + writeSettings(); + } +} + +void SettingsDialog::newScheme() +{ + reset(); + my.newScheme = QString::null; + settingsTab->setCurrentIndex(1); // Colors Tab + + // Disable signals here and explicitly call the index changed + // routine so that we don't race with setFocus and selectAll. + schemeComboBox->blockSignals(true); + schemeComboBox->setCurrentIndex(1); // New Scheme + schemeComboBox->blockSignals(false); + schemeComboBox_currentIndexChanged(1); + schemeLineEdit->selectAll(); + schemeLineEdit->setFocus(); + show(); +} + +void SettingsDialog::removeSchemeButton_clicked() +{ + ColorScheme::removeScheme(schemeComboBox->currentText()); + setupSchemeComboBox(); + schemeLineEdit->clear(); + globalSettings.colorSchemesModified = true; + writeSettings(); +} + +void SettingsDialog::updateSchemeColors(ColorScheme *scheme) +{ + ColorButton **buttons; + int colorCount = colorArray(&buttons); + + scheme->clear(); + for (int i = 0; i < colorCount; i++) { + QColor c = buttons[i]->color(); + if (c == Qt::white) + continue; + scheme->addColor(c); + } + scheme->setModified(true); +} + +void SettingsDialog::updateSchemeButton_clicked() +{ + int index; + QString oldName = schemeComboBox->currentText(); + QString newName = schemeLineEdit->text(); + + if (schemeComboBox->currentIndex() > 1) { // Edit scheme + if (newName != oldName) { + if (ColorScheme::lookupScheme(newName) == true) + goto conflict; + index = schemeComboBox->currentIndex(); + schemeComboBox->setItemText(index, newName); + } + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) { + if (oldName == globalSettings.colorSchemes[i].name()) { + globalSettings.colorSchemes[i].setName(newName); + updateSchemeColors(&globalSettings.colorSchemes[i]); + break; + } + } + } + else if (schemeComboBox->currentIndex() == 1) { // New Scheme + if (ColorScheme::lookupScheme(newName) == true) + goto conflict; + ColorScheme scheme; + my.newScheme = newName; + scheme.setName(newName); + updateSchemeColors(&scheme); + index = globalSettings.colorSchemes.size(); + globalSettings.colorSchemes.append(scheme); + pmchart->newScheme(my.newScheme); + schemeComboBox->blockSignals(true); + schemeComboBox->addItem(newName); + schemeComboBox->setCurrentIndex(index + 2); + schemeComboBox->blockSignals(false); + } + else if (schemeComboBox->currentIndex() == 0) { // Default + updateSchemeColors(&globalSettings.defaultScheme); + } + globalSettings.colorSchemesModified = true; + writeSettings(); + return; + +conflict: + QString msg = newName; + msg.prepend("New scheme name \""); + msg.append("\" conflicts with an existing name"); + QMessageBox::warning(this, pmProgname, msg); +} + +void SettingsDialog::setupSchemePalette() +{ + ColorButton **buttons; + int colorCount = colorArray(&buttons); + int i = 0, index = schemeComboBox->currentIndex(); + + if (index == 1) // keep whatever is there as the starting point + i = colorCount; + else if (index == 0) { + for (i = 0; i < globalSettings.defaultScheme.size() && i < colorCount; i++) + buttons[i]->setColor(globalSettings.defaultScheme.color(i)); + } + else if (index > 1) { + int j = index - 2; + for (i = 0; i < globalSettings.colorSchemes[j].size() && i < colorCount; i++) + buttons[i]->setColor(globalSettings.colorSchemes[j].color(i)); + } + + while (i < colorCount) + buttons[i++]->setColor(QColor(Qt::white)); +} + +void SettingsDialog::setupSchemeComboBox() +{ + schemeComboBox->blockSignals(true); + schemeComboBox->clear(); + schemeComboBox->addItem(tr("Default Scheme")); + schemeComboBox->addItem(tr("New Scheme")); + for (int i = 0; i < globalSettings.colorSchemes.size(); i++) + schemeComboBox->addItem(globalSettings.colorSchemes[i].name()); + schemeComboBox->setCurrentIndex(0); + schemeComboBox->blockSignals(false); +} + +void SettingsDialog::schemeComboBox_currentIndexChanged(int index) +{ + if (index == 0) { // Default Scheme + schemeLineEdit->setEnabled(false); + schemeLineEdit->setText("#-cycle"); + removeSchemeButton->setEnabled(false); + setupSchemePalette(); + } + else { + schemeLineEdit->setText(schemeComboBox->currentText()); + schemeLineEdit->setEnabled(true); + if (index == 1) + removeSchemeButton->setEnabled(false); + else { + removeSchemeButton->setEnabled(true); + setupSchemePalette(); + } + } +} + +// +// Host Preferences +// + +void SettingsDialog::setupSavedHostsList() +{ + QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer); + QStringList savedHostsList = globalSettings.savedHosts; + const QString hostcombo = hostComboBox->currentText(); + + savedHostsListWidget->blockSignals(true); + savedHostsListWidget->clear(); + for (int i = 0; i < savedHostsList.size(); i++) { + const QString &hostname = savedHostsList.at(i); + QListWidgetItem *item = new QListWidgetItem(hostIcon, hostname); + savedHostsListWidget->insertItem(i, item); + if (hostname == hostcombo) + savedHostsListWidget->setCurrentItem(item); + } + savedHostsListWidget->blockSignals(false); +} + +void SettingsDialog::savedHostsListWidget_itemSelectionChanged() +{ + QList<QListWidgetItem *>selections = savedHostsListWidget->selectedItems(); + removeHostButton->setEnabled(selections.size() > 0); +} + +void SettingsDialog::removeHostButton_clicked() +{ + QList<QListWidgetItem *>selections = savedHostsListWidget->selectedItems(); + + for (int i = 0; i < selections.size(); i++) { + QListWidgetItem *item = selections.at(i); + savedHostsListWidget->removeItemWidget(item); + delete item; + } + globalSettings.savedHosts.clear(); + for (int i = 0; i < savedHostsListWidget->count(); i++) + globalSettings.savedHosts << savedHostsListWidget->item(i)->text(); + globalSettings.savedHostsModified = true; + removeHostButton->setEnabled(false); + writeSettings(); +} + +void SettingsDialog::addHostButton_clicked() +{ + QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer); + const QString hostname = hostComboBox->currentText(); + bool found = false; + + for (int i = 0; i < savedHostsListWidget->count(); i++) { + QListWidgetItem *item = savedHostsListWidget->item(i); + if (item->text() != hostname) { + item->setSelected(false); + } else { + savedHostsListWidget->setCurrentItem(item); + found = true; + } + } + if (!found) { + QListWidgetItem *item = new QListWidgetItem(hostIcon, hostname); + savedHostsListWidget->addItem(item); + savedHostsListWidget->setCurrentItem(item); + globalSettings.savedHostsModified = true; + globalSettings.savedHosts << hostname; + } + removeHostButton->setEnabled(true); + writeSettings(); +} + +void SettingsDialog::setupHostComboBox(const QString &hostname) +{ + QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer); + int index = 0; + + hostComboBox->blockSignals(true); + hostComboBox->clear(); + for (unsigned int i = 0; i < liveGroup->numContexts(); i++) { + QmcSource source = liveGroup->context(i)->source(); + const QString &srchost = source.host(); + + if (hostname == srchost) + index = i; + hostComboBox->insertItem(i, hostIcon, source.host()); + } + hostComboBox->setCurrentIndex(index); + hostComboBox->blockSignals(false); +} + +void SettingsDialog::hostButton_clicked() +{ + HostDialog *host = new HostDialog(this); + + if (host->exec() == QDialog::Accepted) { + QString hostname = host->getHostName(); + QString hostspec = host->getHostSpecification(); + int sts, flags = host->getContextFlags(); + + if (hostspec == QString::null || hostspec.length() == 0) { + hostspec.append(tr("Hostname not specified\n")); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) { + hostspec.prepend(tr("Cannot connect to host: ")); + hostspec.append(tr("\n")); + hostspec.append(tr(pmErrStr(sts))); + QMessageBox::warning(this, pmProgname, hostspec, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + Qt::NoButton, Qt::NoButton); + } else { + console->post(PmChart::DebugUi, + "OpenViewDialog::newHost: %s (flags=0x%x)", + (const char *)hostspec.toAscii(), flags); + setupHostComboBox(hostname); + if (globalSettings.savedHosts.contains(hostname) == false) { + globalSettings.savedHostsModified = true; + globalSettings.savedHosts << hostname; + setupSavedHostsList(); + writeSettings(); + } + } + } + delete host; +} diff --git a/src/pmchart/settingsdialog.h b/src/pmchart/settingsdialog.h new file mode 100644 index 0000000..91a6df8 --- /dev/null +++ b/src/pmchart/settingsdialog.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2007, 2009, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include "ui_settingsdialog.h" +#include "colorbutton.h" +#include "colorscheme.h" +#include <qmc_time.h> + +class SettingsDialog : public QDialog, public Ui::SettingsDialog +{ + Q_OBJECT + +public: + SettingsDialog(QWidget* parent); + void enableUi(); + void reset(); + + void newScheme(); + int colorArray(ColorButton *** array); + +public slots: + virtual void settingsTab_currentChanged(int index); + + virtual void chartDeltaLineEdit_editingFinished(); + virtual void loggerDeltaLineEdit_editingFinished(); + virtual void chartDeltaUnitsComboBox_activated(int value); + virtual void loggerDeltaUnitsComboBox_activated(int value); + virtual void visible_valueChanged(int value); + virtual void sample_valueChanged(int value); + + virtual void selectedHighlightButton_clicked(); + virtual void defaultBackgroundButton_clicked(); + virtual void colorButtonClicked(int); + virtual void removeSchemeButton_clicked(); + virtual void updateSchemeButton_clicked(); + virtual void schemeComboBox_currentIndexChanged(int); + + virtual void familyLineEdit_editingFinished(); + virtual void familyListWidget_itemClicked(QListWidgetItem *); + virtual void styleLineEdit_editingFinished(); + virtual void styleListWidget_itemClicked(QListWidgetItem *); + virtual void sizeLineEdit_editingFinished(); + virtual void sizeListWidget_itemClicked(QListWidgetItem *); + virtual void resetFontButton_clicked(); + virtual void applyFontButton_clicked(); + + virtual void hostButton_clicked(); + virtual void savedHostsListWidget_itemSelectionChanged(); + virtual void removeHostButton_clicked(); + virtual void addHostButton_clicked(); + + virtual void startupToolbarCheckBox_clicked(); + virtual void nativeToolbarCheckBox_clicked(); + virtual void toolbarAreasComboBox_currentIndexChanged(int); + virtual void actionListWidget_itemClicked(QListWidgetItem *); + +protected slots: + virtual void languageChange(); + + virtual void colorButton1_clicked() { colorButtonClicked(1); } + virtual void colorButton2_clicked() { colorButtonClicked(2); } + virtual void colorButton3_clicked() { colorButtonClicked(3); } + virtual void colorButton4_clicked() { colorButtonClicked(4); } + virtual void colorButton5_clicked() { colorButtonClicked(5); } + virtual void colorButton6_clicked() { colorButtonClicked(6); } + virtual void colorButton7_clicked() { colorButtonClicked(7); } + virtual void colorButton8_clicked() { colorButtonClicked(8); } + virtual void colorButton9_clicked() { colorButtonClicked(9); } + virtual void colorButton10_clicked() { colorButtonClicked(10); } + virtual void colorButton11_clicked() { colorButtonClicked(11); } + virtual void colorButton12_clicked() { colorButtonClicked(12); } + virtual void colorButton13_clicked() { colorButtonClicked(13); } + virtual void colorButton14_clicked() { colorButtonClicked(14); } + virtual void colorButton15_clicked() { colorButtonClicked(15); } + virtual void colorButton16_clicked() { colorButtonClicked(16); } + virtual void colorButton17_clicked() { colorButtonClicked(17); } + virtual void colorButton18_clicked() { colorButtonClicked(18); } + virtual void colorButton19_clicked() { colorButtonClicked(19); } + virtual void colorButton20_clicked() { colorButtonClicked(20); } + virtual void colorButton21_clicked() { colorButtonClicked(21); } + virtual void colorButton22_clicked() { colorButtonClicked(22); } + +private: + // font preferences + void setupFontLists(); + void updateFontList(QListWidget *, const QString &); + + // hosts preferences + void setupSavedHostsList(); + void setupHostComboBox(const QString &); + + // toolbar preferences + void setupActionsList(); + + // colors preferences + void setupSchemePalette(); + void setupSchemeComboBox(); + ColorScheme *lookupScheme(QString); + void updateSchemeColors(ColorScheme *); + + // sampling preferences + void displayTotalSlider(); + void displayVisibleSlider(); + void displayTotalCounter(); + void displayVisibleCounter(); + + struct { + QmcTime::DeltaUnits chartUnits; + QmcTime::DeltaUnits loggerUnits; + int visibleHistory; + int sampleHistory; + QString newScheme; + } my; + + QBrush enabled, disabled; // brushes for painting action list backgrounds +}; + +#endif // SETTINGSDIALOG_H diff --git a/src/pmchart/settingsdialog.ui b/src/pmchart/settingsdialog.ui new file mode 100644 index 0000000..8f49b91 --- /dev/null +++ b/src/pmchart/settingsdialog.ui @@ -0,0 +1,2330 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SettingsDialog</class> + <widget class="QDialog" name="SettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>390</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>390</width> + <height>300</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>390</width> + <height>300</height> + </size> + </property> + <property name="windowTitle"> + <string>Settings</string> + </property> + <property name="windowIcon"> + <iconset resource="pmchart.qrc"> + <normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <widget class="QTabWidget" name="settingsTab"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>371</width> + <height>241</height> + </rect> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="samplesTab"> + <attribute name="title"> + <string>Sampling</string> + </attribute> + <widget class="QWidget" name="samplesLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>5</y> + <width>351</width> + <height>171</height> + </rect> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="loggerDeltaLabel"> + <property name="text"> + <string>Default Record Interval:</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="chartDeltaLabel"> + <property name="text"> + <string>Default Chart Interval:</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="3"> + <widget class="QSlider" name="visibleSlider"> + <property name="maximum"> + <number>999999999</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="visibleTextLabel"> + <property name="text"> + <string>Default Visible Points:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="sampleTextLabel"> + <property name="text"> + <string>Default Sample Count:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="4"> + <widget class="QSpinBox" name="sampleCounter"> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="chartDeltaLineEdit"> + <property name="maxLength"> + <number>6</number> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QSpinBox" name="visibleCounter"> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + <item row="3" column="1" colspan="3"> + <widget class="QSlider" name="sampleSlider"> + <property name="maximum"> + <number>999999999</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="3" colspan="2"> + <widget class="QComboBox" name="chartDeltaUnitsComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <property name="maxCount"> + <number>6</number> + </property> + <property name="insertPolicy"> + <enum>QComboBox::NoInsert</enum> + </property> + <property name="duplicatesEnabled"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>Milliseconds</string> + </property> + </item> + <item> + <property name="text"> + <string>Seconds</string> + </property> + </item> + <item> + <property name="text"> + <string>Minutes</string> + </property> + </item> + <item> + <property name="text"> + <string>Hours</string> + </property> + </item> + <item> + <property name="text"> + <string>Days</string> + </property> + </item> + <item> + <property name="text"> + <string>Weeks</string> + </property> + </item> + </widget> + </item> + <item row="1" column="3" colspan="2"> + <widget class="QComboBox" name="loggerDeltaUnitsComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <property name="maxCount"> + <number>6</number> + </property> + <property name="insertPolicy"> + <enum>QComboBox::NoInsert</enum> + </property> + <property name="duplicatesEnabled"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>Milliseconds</string> + </property> + </item> + <item> + <property name="text"> + <string>Seconds</string> + </property> + </item> + <item> + <property name="text"> + <string>Minutes</string> + </property> + </item> + <item> + <property name="text"> + <string>Hours</string> + </property> + </item> + <item> + <property name="text"> + <string>Days</string> + </property> + </item> + <item> + <property name="text"> + <string>Weeks</string> + </property> + </item> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="loggerDeltaLineEdit"> + <property name="maxLength"> + <number>6</number> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="colorTab"> + <attribute name="title"> + <string>Colors</string> + </attribute> + <widget class="QGroupBox" name="colorSchemesGroupBox"> + <property name="geometry"> + <rect> + <x>10</x> + <y>80</y> + <width>351</width> + <height>121</height> + </rect> + </property> + <property name="title"> + <string>Color Schemes</string> + </property> + <widget class="ColorButton" name="colorButton1"> + <property name="geometry"> + <rect> + <x>10</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton2"> + <property name="geometry"> + <rect> + <x>40</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton3"> + <property name="geometry"> + <rect> + <x>70</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton4"> + <property name="geometry"> + <rect> + <x>100</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton5"> + <property name="geometry"> + <rect> + <x>130</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton6"> + <property name="geometry"> + <rect> + <x>160</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton7"> + <property name="geometry"> + <rect> + <x>190</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton8"> + <property name="geometry"> + <rect> + <x>220</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton9"> + <property name="geometry"> + <rect> + <x>250</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton10"> + <property name="geometry"> + <rect> + <x>280</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton11"> + <property name="geometry"> + <rect> + <x>310</x> + <y>50</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton12"> + <property name="geometry"> + <rect> + <x>10</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton13"> + <property name="geometry"> + <rect> + <x>40</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton14"> + <property name="geometry"> + <rect> + <x>70</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton15"> + <property name="geometry"> + <rect> + <x>100</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton16"> + <property name="geometry"> + <rect> + <x>130</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton17"> + <property name="geometry"> + <rect> + <x>160</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton18"> + <property name="geometry"> + <rect> + <x>190</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton19"> + <property name="geometry"> + <rect> + <x>220</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton20"> + <property name="geometry"> + <rect> + <x>250</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton21"> + <property name="geometry"> + <rect> + <x>280</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="colorButton22"> + <property name="geometry"> + <rect> + <x>310</x> + <y>70</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="QComboBox" name="schemeComboBox"> + <property name="geometry"> + <rect> + <x>11</x> + <y>22</y> + <width>171</width> + <height>26</height> + </rect> + </property> + </widget> + <widget class="QLineEdit" name="schemeLineEdit"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="geometry"> + <rect> + <x>190</x> + <y>20</y> + <width>151</width> + <height>29</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>#-cycle</string> + </property> + </widget> + <widget class="QPushButton" name="updateSchemeButton"> + <property name="geometry"> + <rect> + <x>200</x> + <y>100</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Update</string> + </property> + </widget> + <widget class="QPushButton" name="removeSchemeButton"> + <property name="geometry"> + <rect> + <x>70</x> + <y>100</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </widget> + <widget class="ColorButton" name="selectedHighlightButton"> + <property name="geometry"> + <rect> + <x>200</x> + <y>40</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="ColorButton" name="defaultBackgroundButton"> + <property name="geometry"> + <rect> + <x>200</x> + <y>10</y> + <width>30</width> + <height>20</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + <widget class="QLabel" name="selectedHighlightLabel"> + <property name="geometry"> + <rect> + <x>10</x> + <y>30</y> + <width>171</width> + <height>41</height> + </rect> + </property> + <property name="text"> + <string>Selected Chart Highlight:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + <widget class="QLabel" name="defaultBackgroundLabel"> + <property name="geometry"> + <rect> + <x>10</x> + <y>0</y> + <width>171</width> + <height>41</height> + </rect> + </property> + <property name="text"> + <string>Default Chart Background:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </widget> + <widget class="QWidget" name="fontTab"> + <attribute name="title"> + <string>Font</string> + </attribute> + <widget class="QWidget" name="sizeLayoutWidget"> + <property name="geometry"> + <rect> + <x>310</x> + <y>10</y> + <width>48</width> + <height>161</height> + </rect> + </property> + <layout class="QVBoxLayout" name="sizeVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="sizeLabel"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="sizeLineEdit"/> + </item> + <item> + <widget class="QListWidget" name="sizeListWidget"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="styleLayoutWidget"> + <property name="geometry"> + <rect> + <x>220</x> + <y>10</y> + <width>82</width> + <height>161</height> + </rect> + </property> + <layout class="QVBoxLayout" name="styleVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="styleLabel"> + <property name="text"> + <string>Style</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="styleLineEdit"/> + </item> + <item> + <widget class="QListWidget" name="styleListWidget"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="familyLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>205</width> + <height>161</height> + </rect> + </property> + <layout class="QVBoxLayout" name="familyVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="familyLabel"> + <property name="text"> + <string>Family</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="familyLineEdit"/> + </item> + <item> + <widget class="QListWidget" name="familyListWidget"/> + </item> + </layout> + </widget> + <widget class="QPushButton" name="resetFontButton"> + <property name="geometry"> + <rect> + <x>80</x> + <y>180</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Reset</string> + </property> + </widget> + <widget class="QPushButton" name="applyFontButton"> + <property name="geometry"> + <rect> + <x>210</x> + <y>180</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Hosts</string> + </attribute> + <widget class="QPushButton" name="addHostButton"> + <property name="geometry"> + <rect> + <x>210</x> + <y>180</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Add</string> + </property> + </widget> + <widget class="QPushButton" name="removeHostButton"> + <property name="geometry"> + <rect> + <x>80</x> + <y>180</y> + <width>75</width> + <height>21</height> + </rect> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + <widget class="QPushButton" name="hostButton"> + <property name="text" > + <string>...</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" >:/images/computer.png</iconset> + </property> + <property name="iconSize" > + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="geometry"> + <rect> + <x>260</x> + <y>10</y> + <width>92</width> + <height>27</height> + </rect> + </property> + </widget> + <widget class="QComboBox" name="hostComboBox"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>241</width> + <height>27</height> + </rect> + </property> + </widget> + <widget class="QGroupBox" name="savedHostsGroupBox"> + <property name="geometry"> + <rect> + <x>10</x> + <y>40</y> + <width>341</width> + <height>131</height> + </rect> + </property> + <property name="title"> + <string>Saved Hosts</string> + </property> + <widget class="QListWidget" name="savedHostsListWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>20</y> + <width>321</width> + <height>111</height> + </rect> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="viewMode"> + <enum>QListView::ListMode</enum> + </property> + <property name="selectionRectVisible"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </widget> + </widget> + <widget class="QWidget" name="toolbarTab"> + <attribute name="title"> + <string>Toolbar</string> + </attribute> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="0"> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QCheckBox" name="startupToolbarCheckBox"> + <property name="text"> + <string>Show Toolbar on Startup</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="nativeToolbarCheckBox"> + <property name="text"> + <string>Native Toolbar Style</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="toolbarLocationLabel"> + <property name="text"> + <string>Location and Orientation:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="toolbarAreasComboBox"> + <item> + <property name="text"> + <string>Top and Horizonal</string> + </property> + </item> + <item> + <property name="text"> + <string>Right and Vertical</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="toolbarActionsLabel"> + <property name="text"> + <string>Actions:</string> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="actionListWidget"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DragDrop</enum> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + <property name="textElideMode"> + <enum>Qt::ElideNone</enum> + </property> + <property name="horizontalScrollMode"> + <enum>QAbstractItemView::ScrollPerPixel</enum> + </property> + <property name="flow"> + <enum>QListView::LeftToRight</enum> + </property> + <property name="isWrapping" stdset="0"> + <bool>false</bool> + </property> + <property name="spacing"> + <number>2</number> + </property> + <property name="viewMode"> + <enum>QListView::IconMode</enum> + </property> + <property name="uniformItemSizes"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <widget class="QPushButton" name="closeSettingsButton"> + <property name="geometry"> + <rect> + <x>290</x> + <y>260</y> + <width>92</width> + <height>31</height> + </rect> + </property> + <property name="text"> + <string>&Close</string> + </property> + <property name="shortcut"> + <string/> + </property> + <property name="autoDefault"> + <bool>true</bool> + </property> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>ColorButton</class> + <extends>QToolButton</extends> + <header>colorbutton.h</header> + </customwidget> + </customwidgets> + <resources> + <include location="pmchart.qrc"/> + </resources> + <connections> + <connection> + <sender>closeSettingsButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>defaultBackgroundButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>defaultBackgroundButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton1</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton1_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton2</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton2_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton3</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton3_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton4</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton4_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton5</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton5_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton6</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton6_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton7</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton7_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton8</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton8_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton9</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton9_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton10</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton10_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton11</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton11_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>345</x> + <y>163</y> + </hint> + <hint type="destinationlabel"> + <x>193</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton12</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton12_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>45</x> + <y>183</y> + </hint> + <hint type="destinationlabel"> + <x>193</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton12</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton12_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton13</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton13_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton14</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton14_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton15</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton15_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton16</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton16_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton17</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton17_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton18</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton18_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton19</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton19_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton20</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton20_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton21</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton21_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>colorButton22</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>colorButton22_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>345</x> + <y>183</y> + </hint> + <hint type="destinationlabel"> + <x>193</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>chartDeltaLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>SettingsDialog</receiver> + <slot>chartDeltaLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>loggerDeltaLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>SettingsDialog</receiver> + <slot>loggerDeltaLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>chartDeltaUnitsComboBox</sender> + <signal>activated(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>chartDeltaUnitsComboBox_activated(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>loggerDeltaUnitsComboBox</sender> + <signal>activated(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>loggerDeltaUnitsComboBox_activated(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sampleCounter</sender> + <signal>valueChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>sample_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sampleSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>sample_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>visibleCounter</sender> + <signal>valueChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>visible_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>visibleSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>visible_valueChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>startupToolbarCheckBox</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>startupToolbarCheckBox_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>111</x> + <y>51</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>nativeToolbarCheckBox</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>nativeToolbarCheckBox_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>toolbarAreasComboBox</sender> + <signal>currentIndexChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>toolbarAreasComboBox_currentIndexChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>276</x> + <y>86</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>actionListWidget</sender> + <signal>itemClicked(QListWidgetItem*)</signal> + <receiver>SettingsDialog</receiver> + <slot>actionListWidget_itemClicked(QListWidgetItem*)</slot> + <hints> + <hint type="sourcelabel"> + <x>201</x> + <y>163</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>schemeComboBox</sender> + <signal>currentIndexChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>schemeComboBox_currentIndexChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>110</x> + <y>138</y> + </hint> + <hint type="destinationlabel"> + <x>193</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>selectedHighlightButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>selectedHighlightButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>346</x> + <y>94</y> + </hint> + <hint type="destinationlabel"> + <x>194</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>removeSchemeButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>removeSchemeButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>129</x> + <y>244</y> + </hint> + <hint type="destinationlabel"> + <x>194</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>updateSchemeButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>updateSchemeButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>43</x> + <y>244</y> + </hint> + <hint type="destinationlabel"> + <x>194</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>settingsTab</sender> + <signal>currentChanged(int)</signal> + <receiver>SettingsDialog</receiver> + <slot>settingsTab_currentChanged(int)</slot> + <hints> + <hint type="sourcelabel"> + <x>195</x> + <y>114</y> + </hint> + <hint type="destinationlabel"> + <x>194</x> + <y>129</y> + </hint> + </hints> + </connection> + <connection> + <sender>familyLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>SettingsDialog</receiver> + <slot>familyLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>styleLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>SettingsDialog</receiver> + <slot>styleLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sizeLineEdit</sender> + <signal>editingFinished()</signal> + <receiver>SettingsDialog</receiver> + <slot>sizeLineEdit_editingFinished()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>familyListWidget</sender> + <signal>itemClicked(QListWidgetItem*)</signal> + <receiver>SettingsDialog</receiver> + <slot>familyListWidget_itemClicked(QListWidgetItem*)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>styleListWidget</sender> + <signal>itemClicked(QListWidgetItem*)</signal> + <receiver>SettingsDialog</receiver> + <slot>styleListWidget_itemClicked(QListWidgetItem*)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>sizeListWidget</sender> + <signal>itemClicked(QListWidgetItem*)</signal> + <receiver>SettingsDialog</receiver> + <slot>sizeListWidget_itemClicked(QListWidgetItem*)</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>resetFontButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>resetFontButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>applyFontButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>applyFontButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>hostButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>hostButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>savedHostsListWidget</sender> + <signal>itemSelectionChanged()</signal> + <receiver>SettingsDialog</receiver> + <slot>savedHostsListWidget_itemSelectionChanged()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>removeHostButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>removeHostButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>addHostButton</sender> + <signal>clicked()</signal> + <receiver>SettingsDialog</receiver> + <slot>addHostButton_clicked()</slot> + <hints> + <hint type="sourcelabel"> + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel"> + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/statusbar.cpp b/src/pmchart/statusbar.cpp new file mode 100644 index 0000000..2947a87 --- /dev/null +++ b/src/pmchart/statusbar.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtGui> +#include <QHBoxLayout> +#include "statusbar.h" +#include "main.h" + +StatusBar::StatusBar() +{ + setFixedHeight(buttonSize()); + setSizeGripEnabled(false); + + my.timeButton = new QedTimeButton(this); + my.timeButton->setFixedSize(QSize(buttonSize(), buttonSize())); + my.timeButton->setWhatsThis(QApplication::translate("PmChart", + "VCR state button, also used to display the time control window.", + 0, QApplication::UnicodeUTF8)); + my.timeFrame = new QToolButton(this); + my.timeFrame->setMinimumSize(QSize(buttonSize(), buttonSize())); + my.timeFrame->setSizePolicy( + QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + my.timeFrame->setWhatsThis(QApplication::translate("PmChart", + "Unified time axis, displaying the current time position at the " + "rightmost point, and either status information or the timeframe " + "covering all Visible Points to the left", + 0, QApplication::UnicodeUTF8)); + + delete layout(); + QHBoxLayout *box = new QHBoxLayout; + box->setMargin(0); + box->setSpacing(1); + box->addWidget(my.timeButton); + box->addWidget(my.timeFrame); + setLayout(box); + + my.timeAxis = new TimeAxis(my.timeFrame); + my.timeAxis->setFixedHeight(timeAxisHeight()); + my.gadgetLabel = new QLabel(my.timeFrame); + my.gadgetLabel->hide(); // shown with gadget Tabs + + my.dateLabel = new QLabel(my.timeFrame); + my.dateLabel->setIndent(8); + my.dateLabel->setAlignment(Qt::AlignRight | Qt::AlignBottom); + + my.labelSpacer = new QSpacerItem(10, 0, + QSizePolicy::Fixed, QSizePolicy::Minimum); + my.rightSpacer = new QSpacerItem(0, 0, + QSizePolicy::Fixed, QSizePolicy::Minimum); + + my.valueLabel = new QLabel(my.timeFrame); + my.valueLabel->setIndent(8); + my.valueLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); + + my.grid = new QGridLayout; // Grid of [5 x 3] cells + my.grid->setMargin(0); + my.grid->setSpacing(0); + my.grid->addWidget(my.gadgetLabel, 0, 0, 1, 3); + my.grid->addWidget(my.timeAxis, 0, 0, 1, 3); // top two rows, all columns + my.grid->addWidget(my.dateLabel, 2, 2, 1, 1); // bottom row, last two cols + my.grid->addItem(my.labelSpacer, 2, 1, 1, 1); // bottom row, second column + my.grid->addWidget(my.valueLabel, 2, 0, 1, 1); // bottom row, first column. + my.grid->addItem(my.rightSpacer, 0, 4, 2, 1); // all rows, in final column + my.timeFrame->setLayout(my.grid); + + resetFont(); +} + +void StatusBar::resetFont() +{ + setFont(*globalFont); + my.dateLabel->setFont(*globalFont); + my.valueLabel->setFont(*globalFont); + my.gadgetLabel->setFont(*globalFont); + my.timeAxis->resetFont(); +} + +void StatusBar::setTimeAxisRightAlignment(int width) +{ + my.rightSpacer->changeSize(width, buttonSize(), + QSizePolicy::Fixed, QSizePolicy::Fixed); + my.grid->invalidate(); +} + +void StatusBar::init() +{ + my.timeAxis->init(); +} + +bool StatusBar::event(QEvent *e) +{ + if (e->type() == QEvent::Show) + my.grid->update(); + return QStatusBar::event(e); +} + +void StatusBar::resizeEvent(QResizeEvent *e) +{ + my.timeFrame->resize(e->size().width()-1 - buttonSize(), buttonSize()); +} + +void StatusBar::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QStyleOption opt(0); + + opt.rect.setRect(buttonSize()+2, 0, width()-buttonSize()-2, buttonSize()); + opt.palette = palette(); + opt.state = QStyle::State_None; + style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, my.timeFrame); +} diff --git a/src/pmchart/statusbar.h b/src/pmchart/statusbar.h new file mode 100644 index 0000000..85f3b3d --- /dev/null +++ b/src/pmchart/statusbar.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef STATUSBAR_H +#define STATUSBAR_H + +#include <QtGui/QLabel> +#include <QtGui/QStatusBar> +#include <QtGui/QGridLayout> +#include "qed_timebutton.h" +#include "timeaxis.h" + +class StatusBar : public QStatusBar +{ + Q_OBJECT + +public: + StatusBar(); + void init(); + void resetFont(); + + static int buttonSize() { return 56; } // pixels + static int timeAxisHeight() { return 30; } // pixels + + QLabel *dateLabel() { return my.dateLabel; } + TimeAxis *timeAxis() { return my.timeAxis; } + QToolButton *timeFrame() { return my.timeFrame; } + QedTimeButton *timeButton() { return my.timeButton; } + + QString dateText() { return my.dateLabel->text(); } + void setDateText(QString &s) { my.dateLabel->setText(s); } + void setValueText(QString &s) { my.valueLabel->setText(s); } + void clearValueText() { my.valueLabel->clear(); } + + void setTimeAxisRightAlignment(int w); + +protected: + bool event(QEvent *); + void paintEvent(QPaintEvent *); + void resizeEvent(QResizeEvent *); + +private: + struct { + QGridLayout *grid; + QSpacerItem *labelSpacer; // spacer between date/value labels + QSpacerItem *rightSpacer; // spacer at right edge for toolbar + QToolButton *timeFrame; + QedTimeButton *timeButton; + TimeAxis *timeAxis; + QLabel *gadgetLabel; + QLabel *valueLabel; + QLabel *dateLabel; + } my; +}; + +#endif diff --git a/src/pmchart/tab.cpp b/src/pmchart/tab.cpp new file mode 100644 index 0000000..3d5ef2c --- /dev/null +++ b/src/pmchart/tab.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#include "main.h" +#include <QtGui/QMessageBox> +#include "openviewdialog.h" +#include "recorddialog.h" + +Tab::Tab(): QWidget(NULL) +{ + my.currentGadget = -1; + my.recording = false; + my.splitter = NULL; + my.group = NULL; +} + +void Tab::init(QTabWidget *tab, GroupControl *group, QString label) +{ + my.splitter = new QSplitter(tab); + my.splitter->setOrientation(Qt::Vertical); + my.splitter->setSizePolicy(QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding); + tab->blockSignals(true); + tab->addTab(my.splitter, label); + tab->blockSignals(false); + my.group = group; +} + +void Tab::addGadget(Gadget *gadget) +{ + if (gadgetCount()) + pmchart->updateHeight(PmChart::minimumChartHeight()); + my.gadgetsList.append(gadget); + if (my.currentGadget == -1) + setCurrentGadget(gadgetCount() - 1); + gadget->showWidget(); + console->post("Tab::addChart: [%d]->Chart %p", my.currentGadget, gadget); +} + +int Tab::deleteCurrent(void) +{ + return deleteGadget(my.currentGadget); +} + +int Tab::deleteGadget(Gadget *gadget) +{ + for (int i = 0; i < gadgetCount(); i++) + if (my.gadgetsList.at(i) == gadget) + return deleteGadget(i); + return 0; +} + +int Tab::deleteGadget(int index) +{ + Gadget *gadget = my.gadgetsList.at(index); + int newCurrent = my.currentGadget; + int oldCurrent = my.currentGadget; + int oldCount = gadgetCount(); + int height = gadget->height(); + + if (index < oldCurrent || index == oldCount - 1) + newCurrent--; + if (newCurrent < 0) + my.currentGadget = -1; + else + setCurrent(my.gadgetsList.at(newCurrent)); + + my.group->deleteGadget(gadget); + my.gadgetsList.removeAt(index); + delete gadget; + + if (gadgetCount() > 0) + pmchart->updateHeight(-height); + + return my.currentGadget; +} + +int Tab::gadgetCount(void) +{ + return my.gadgetsList.size(); +} + +int Tab::currentGadgetIndex(void) +{ + return my.currentGadget; +} + +Gadget *Tab::gadget(int index) +{ + if (index >= gadgetCount()) + return NULL; + return my.gadgetsList.at(index); +} + +Gadget *Tab::currentGadget(void) +{ + if (my.currentGadget == -1) + return NULL; + return my.gadgetsList.at(my.currentGadget); +} + +void Tab::setCurrent(Gadget *gadget) +{ + int index; + + for (index = 0; index < gadgetCount(); index++) + if (my.gadgetsList.at(index) == gadget) + break; + if (index == gadgetCount()) + abort(); // bad, bad, bad - attempted setCurrent on invalid gadget + setCurrentGadget(index); +} + +void Tab::setCurrentGadget(int index) +{ + if (index >= gadgetCount() || index == my.currentGadget) + return; + if (my.currentGadget >= 0) + my.gadgetsList.at(my.currentGadget)->setCurrent(false); + my.currentGadget = index; + if (my.currentGadget >= 0) + my.gadgetsList.at(my.currentGadget)->setCurrent(true); +} + +void Tab::showGadgets() +{ + for (int index = 0; index < gadgetCount() - 1; index++) + my.gadgetsList.at(index)->showWidget(); +} + +bool Tab::isArchiveSource(void) +{ + return my.group->isArchiveSource(); +} + +bool Tab::isRecording(void) +{ + return my.recording; +} + +void Tab::addFolio(QString folio, QString view) +{ + my.view = view; + my.folio = folio; +} + +void Tab::addLogger(PmLogger *pmlogger, QString archive) +{ + my.loggerList.append(pmlogger); + my.archiveList.append(archive); +} + +bool Tab::startRecording(void) +{ + RecordDialog record(this); + + console->post("Tab::startRecording"); + record.init(this); + if (record.exec() != QDialog::Accepted) + my.recording = false; + else { // write pmlogger/pmchart/pmafm configs and start up loggers. + console->post("Tab::startRecording starting loggers"); + record.startLoggers(); + my.recording = true; + } + return my.recording; +} + +void Tab::stopRecording(void) +{ + QString msg = "Q\n", errmsg; + int count = my.loggerList.size(); + int i, sts, error = 0; + + console->post("Tab::stopRecording stopping %d logger(s)", count); + for (int i = 0; i < count; i++) { + if (my.loggerList.at(i)->state() == QProcess::NotRunning) { + errmsg.append(tr("Record process (pmlogger) failed for host: ")); + errmsg.append(my.loggerList.at(i)->host()); + errmsg.append("\n"); + error++; + } + else { + my.loggerList.at(i)->write(msg.toAscii()); + my.loggerList.at(i)->terminate(); + } + } + + for (i = 0; i < my.archiveList.size(); i++) { + QString archive = my.archiveList.at(i); + + console->post("Tab::stopRecording opening archive %s", + (const char *)archive.toAscii()); + if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) { + errmsg.append(tr("Cannot open PCP archive: ")); + errmsg.append(archive); + errmsg.append("\n"); + errmsg.append(tr(pmErrStr(sts))); + errmsg.append("\n"); + error++; + } + else { + archiveGroup->updateBounds(); + QmcSource source = archiveGroup->context()->source(); + pmtime->addArchive(source.start(), source.end(), + source.timezone(), source.host(), true); + } + } + + // If all is well, we can now create the new "Record" Tab. + // Order of cleanup and changing Record mode state is different + // in the error case to non-error case, this is important for + // getting the window state correct (i.e. pmchart->enableUi()). + + if (error) { + cleanupRecording(); + pmchart->setRecordState(false); + QMessageBox::warning(this, pmProgname, errmsg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } + else { + // Make the current Tab stop recording before changing Tabs + pmchart->setRecordState(false); + + Tab *tab = new Tab; + console->post("Tab::stopRecording creating tab: delta=%.2f pos=%.2f", + tosec(*pmtime->archiveInterval()), + tosec(*pmtime->archivePosition())); + // TODO: may need to update archive samples/visible? + tab->init(pmchart->tabWidget(), archiveGroup, "Record"); + pmchart->addActiveTab(tab); + OpenViewDialog::openView((const char *)my.view.toAscii()); + cleanupRecording(); + } +} + +void Tab::cleanupRecording(void) +{ + my.recording = false; + my.loggerList.clear(); + my.archiveList.clear(); + my.view = QString::null; + my.folio = QString::null; +} + +void Tab::queryRecording(void) +{ + QString msg = "?\n", errmsg; + int i, error = 0, count = my.loggerList.size(); + + console->post("Tab::stopRecording querying %d logger(s)", count); + for (i = 0; i < count; i++) { + if (my.loggerList.at(i)->state() == QProcess::NotRunning) { + errmsg.append(tr("Record process (pmlogger) failed for host: ")); + errmsg.append(my.loggerList.at(i)->host()); + errmsg.append("\n"); + error++; + } + else { + my.loggerList.at(i)->write(msg.toAscii()); + } + } + + if (error) { + msg = "Q\n"; // if one fails, we shut down all loggers + for (i = 0; i < count; i++) + my.loggerList.at(i)->write(msg.toAscii()); + cleanupRecording(); + pmchart->setRecordState(false); + QMessageBox::warning(this, pmProgname, errmsg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } +} + +void Tab::detachLoggers(void) +{ + QString msg = "D\n", errmsg; + int error = 0, count = my.loggerList.size(); + + console->post("Tab::detachLoggers detaching %d logger(s)", count); + for (int i = 0; i < count; i++) { + if (my.loggerList.at(i)->state() == QProcess::NotRunning) { + errmsg.append(tr("Record process (pmlogger) failed for host: ")); + errmsg.append(my.loggerList.at(i)->host()); + errmsg.append("\n"); + error++; + } + else { + my.loggerList.at(i)->write(msg.toAscii()); + } + } + + if (error) { + cleanupRecording(); + pmchart->setRecordState(false); + QMessageBox::warning(this, pmProgname, errmsg, + QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape, + QMessageBox::NoButton, QMessageBox::NoButton); + } + else { + pmchart->setRecordState(false); + cleanupRecording(); + } +} diff --git a/src/pmchart/tab.h b/src/pmchart/tab.h new file mode 100644 index 0000000..a9bc0f5 --- /dev/null +++ b/src/pmchart/tab.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006-2008, Aconex. All Rights Reserved. + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * + * 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. + */ +#ifndef TAB_H +#define TAB_H + +#include <QtCore/QList> +#include <QtGui/QSplitter> +#include <QtGui/QTabWidget> +#include "groupcontrol.h" +#include "gadget.h" + +class PmLogger; + +class Tab : public QWidget +{ + Q_OBJECT + +public: + Tab(); + void init(QTabWidget *, GroupControl *, QString); + QWidget *splitter() { return my.splitter; } + GroupControl *group() { return my.group; } + bool isArchiveSource(); // query if tab is for archives + + void addGadget(Gadget *); // append gadget to the Tab, make it current + int deleteCurrent(); // remove current gadget, return new current + int deleteGadget(Gadget *); // remove given gadget, return current index + int deleteGadget(int); // remove 'N'th gadget, return current index + + int gadgetCount(); // count of entries in the list of gadgets + Gadget *gadget(int); // gadget at specified list position + Gadget *currentGadget(); // current gadget (can be NULL) + void setCurrent(Gadget *); + int currentGadgetIndex(); // current gadget index (can be -1) + void setCurrentGadget(int); + + void showGadgets(); + + void addFolio(QString, QString); + void addLogger(PmLogger *, QString); + + bool isRecording(); + bool startRecording(); + void queryRecording(); + void stopRecording(); + void detachLoggers(); + +private: + void cleanupRecording(); + + struct { + QSplitter *splitter; // dynamically divides charts + + GroupControl *group; + QList<Gadget*> gadgetsList; + int currentGadget; + + bool recording; // running any pmlogger's? + QString view; + QString folio; + QList<QString> archiveList; // list of archive names + QList<PmLogger*> loggerList; // list of pmloggers for our Tab + } my; +}; + +#endif // TAB_H diff --git a/src/pmchart/tabdialog.cpp b/src/pmchart/tabdialog.cpp new file mode 100644 index 0000000..9da0c0d --- /dev/null +++ b/src/pmchart/tabdialog.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include "tabdialog.h" +#include "main.h" + +TabDialog::TabDialog(QWidget* parent) : QDialog(parent) +{ + setupUi(this); +} + +void TabDialog::languageChange() +{ + retranslateUi(this); +} + +void TabDialog::reset(QString label, bool live) +{ + if (label == QString::null) { + setWindowTitle(tr("Add Tab")); + labelLineEdit->setText(live ? tr("Live") : tr("Archive")); + } + else { + setWindowTitle(tr("Edit Tab")); + liveHostRadioButton->setEnabled(false); + archivesRadioButton->setEnabled(false); + labelLineEdit->setText(label); + } + + liveHostRadioButton->setChecked(live); + archivesRadioButton->setChecked(!live); + + my.archiveSource = !live; + + console->post(PmChart::DebugUi, "TabDialog::reset arch=%s", + my.archiveSource ? "true" : "false"); +} + +bool TabDialog::isArchiveSource() +{ + return my.archiveSource; +} + +QString TabDialog::label() const +{ + return labelLineEdit->text(); +} + +void TabDialog::liveHostRadioButtonClicked() +{ + if (labelLineEdit->text() == tr("Archive")) + labelLineEdit->setText(tr("Live")); + liveHostRadioButton->setChecked(true); + archivesRadioButton->setChecked(false); + my.archiveSource = false; + console->post(PmChart::DebugUi, + "TabDialog::liveHostRadioButtonClicked archive=%s", + my.archiveSource ? "true" : "false"); +} + +void TabDialog::archivesRadioButtonClicked() +{ + if (labelLineEdit->text() == tr("Live")) + labelLineEdit->setText(tr("Archive")); + liveHostRadioButton->setChecked(false); + archivesRadioButton->setChecked(true); + my.archiveSource = true; + console->post(PmChart::DebugUi, + "TabDialog::archivesRadioButtonClicked archive=%s", + my.archiveSource ? "true" : "false"); +} diff --git a/src/pmchart/tabdialog.h b/src/pmchart/tabdialog.h new file mode 100644 index 0000000..7dfc6b3 --- /dev/null +++ b/src/pmchart/tabdialog.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef TABDIALOG_H +#define TABDIALOG_H + +#include "ui_tabdialog.h" + +class TabDialog : public QDialog, public Ui::TabDialog +{ + Q_OBJECT + +public: + TabDialog(QWidget* parent); + + void reset(QString, bool); + bool isArchiveSource(); + QString label() const; + +public slots: + void liveHostRadioButtonClicked(); + void archivesRadioButtonClicked(); + +protected slots: + void languageChange(); + +private: + struct { + bool archiveSource; + } my; +}; + +#endif // TABDIALOG_H diff --git a/src/pmchart/tabdialog.ui b/src/pmchart/tabdialog.ui new file mode 100644 index 0000000..f1c64b9 --- /dev/null +++ b/src/pmchart/tabdialog.ui @@ -0,0 +1,321 @@ +<ui version="4.0" > + <class>TabDialog</class> + <widget class="QDialog" name="TabDialog" > + <property name="windowModality" > + <enum>Qt::WindowModal</enum> + </property> + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>340</width> + <height>150</height> + </rect> + </property> + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>340</width> + <height>150</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>350</width> + <height>250</height> + </size> + </property> + <property name="focusPolicy" > + <enum>Qt::WheelFocus</enum> + </property> + <property name="windowTitle" > + <string>Add Tab</string> + </property> + <property name="windowIcon" > + <iconset resource="pmchart.qrc" >:/images/tab-edit.png</iconset> + </property> + <property name="sizeGripEnabled" > + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout" > + <item row="0" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="tabSourceLabel" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="text" > + <string>Source:</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QRadioButton" name="liveHostRadioButton" > + <property name="sizePolicy" > + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="text" > + <string>Live</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/computer.png</normaloff>:/images/computer.png</iconset> + </property> + <property name="checked" > + <bool>true</bool> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="archivesRadioButton" > + <property name="text" > + <string>Archives</string> + </property> + <property name="icon" > + <iconset resource="pmchart.qrc" > + <normaloff>:/images/archive.png</normaloff>:/images/archive.png</iconset> + </property> + <property name="autoExclusive" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint"> + <size> + <width>34</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QLabel" name="tabTextLabel" > + <property name="text" > + <string>Label:</string> + </property> + <property name="wordWrap" > + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="labelLineEdit" /> + </item> + </layout> + </item> + <item row="2" column="0" > + <spacer name="verticalSpacer" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" > + <size> + <width>5</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0" > + <layout class="QHBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType" > + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonOk" > + <property name="text" > + <string>&OK</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonCancel" > + <property name="text" > + <string>&Cancel</string> + </property> + <property name="shortcut" > + <string/> + </property> + <property name="autoDefault" > + <bool>true</bool> + </property> + <property name="default" > + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="pmchart.qrc" /> + </resources> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>clicked()</signal> + <receiver>TabDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonCancel</sender> + <signal>clicked()</signal> + <receiver>TabDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>liveHostRadioButton</sender> + <signal>clicked()</signal> + <receiver>TabDialog</receiver> + <slot>liveHostRadioButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>archivesRadioButton</sender> + <signal>clicked()</signal> + <receiver>TabDialog</receiver> + <slot>archivesRadioButtonClicked()</slot> + <hints> + <hint type="sourcelabel" > + <x>20</x> + <y>20</y> + </hint> + <hint type="destinationlabel" > + <x>20</x> + <y>20</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/pmchart/tabwidget.cpp b/src/pmchart/tabwidget.cpp new file mode 100644 index 0000000..bc1ede6 --- /dev/null +++ b/src/pmchart/tabwidget.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "tabwidget.h" +#include "tab.h" +#include "main.h" + +TabWidget::TabWidget(QWidget *parent) : QTabWidget(parent) +{ + setFont(*globalFont); + my.activeTab = NULL; +} + +bool TabWidget::setActiveTab(Tab *tab) +{ + my.activeTab = tab; + return tab->isArchiveSource(); +} + +bool TabWidget::setActiveTab(int index) +{ + my.activeTab = my.tabs.at(index); + return my.activeTab->isArchiveSource(); +} + +void TabWidget::insertTab(Tab *tab) +{ + my.tabs.append(tab); + tabBar()->setVisible(my.tabs.size() > 1); +} + +void TabWidget::removeTab(int index) +{ + my.tabs.removeAt(index); + tabBar()->setVisible(my.tabs.size() > 1); + QTabWidget::removeTab(index); +} diff --git a/src/pmchart/tabwidget.h b/src/pmchart/tabwidget.h new file mode 100644 index 0000000..42aa6c3 --- /dev/null +++ b/src/pmchart/tabwidget.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef TABWIDGET_H +#define TABWIDGET_H + +#include <QtCore/QList> +#include <QtGui/QTabBar> +#include <QtGui/QTabWidget> + +class Tab; + +class TabWidget : public QTabWidget +{ + Q_OBJECT + +public: + TabWidget(QWidget *parent); + + int size() { return my.tabs.size(); } + Tab *at(int i) { return my.tabs.at(i); } + Tab *activeTab() { return my.activeTab; } + + bool setActiveTab(Tab *); + bool setActiveTab(int); + void insertTab(Tab *); + void removeTab(int); + +private: + struct { + Tab *activeTab; + QList<Tab*> tabs; + } my; +}; + +#endif // TABWIDGET_H diff --git a/src/pmchart/timeaxis.cpp b/src/pmchart/timeaxis.cpp new file mode 100644 index 0000000..d0b8a89 --- /dev/null +++ b/src/pmchart/timeaxis.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2007-2008, Aconex. All Rights Reserved. + * + * 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. + */ +#include <QtCore/QEvent> +#include <QtCore/QDateTime> +#include <QtGui/QResizeEvent> +#include <qwt_plot_renderer.h> +#include <qwt_scale_draw.h> +#include <qwt_scale_widget.h> +#include <qwt_text.h> +#include <qwt_text_label.h> +#include "timeaxis.h" +#include "main.h" + +#define DESPERATE 0 + +class TimeScaleDraw: public QwtScaleDraw +{ +public: + TimeScaleDraw(void) :QwtScaleDraw() { } + virtual QwtText label(double v) const + { + struct tm tm; + QString string; + time_t seconds = (time_t)v; + + pmLocaltime(&seconds, &tm); + string.sprintf("%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); + return string; + } + virtual void getBorderDistHint(const QFont &f, int &start, int &end) const + { + if (orientation() == Qt::Horizontal) { + start = 0; + end = 0; + } + else { + QwtScaleDraw::getBorderDistHint(f, start, end); + } + } +}; + +TimeAxis::TimeAxis(QWidget *parent) : QwtPlot(parent) +{ + clearScaleCache(); + setFocusPolicy(Qt::NoFocus); +} + +void TimeAxis::init() +{ + enableAxis(xTop, false); + enableAxis(yLeft, false); + enableAxis(yRight, false); + enableAxis(xBottom, true); + + setAutoReplot(false); + setAxisFont(QwtPlot::xBottom, *globalFont); + setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw()); + setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignHCenter | Qt::AlignBottom); +} + +void TimeAxis::resetFont() +{ + setAxisFont(QwtPlot::xBottom, *globalFont); +} + +void TimeAxis::print(QPainter *qp, QRect &rect, bool transparent) +{ + QwtPlotRenderer renderer; + + if (transparent) + renderer.setDiscardFlag(QwtPlotRenderer::DiscardBackground); + renderer.render(this, qp, rect); +} + +void TimeAxis::noArchiveSources() +{ + setAxisScale(QwtPlot::xBottom, 0, 1, 0); + replot(); +} + +void TimeAxis::clearScaleCache() +{ + my.points = 0; + my.delta = my.scale = 0.0; +} + +double TimeAxis::scaleValue(double delta, int points) +{ + if (my.delta == delta && my.points == points) + return my.scale; + + // divisor is the amount of space (pixels) set aside for one major label. + int maxMajor = qMax(1, width() / 54); + int maxMinor; + + my.scale = (1.0 / ((double)width() / (points * 8.0))) * 8.0; // 8.0 is magic + my.scale *= delta; + + // This is a sliding scale which converts arbitrary steps into more + // human-digestable increments - seconds, ten seconds, minutes, etc. + if (my.scale <= 10.0) { + maxMinor = 10; + } else if (my.scale <= 20.0) { // two-seconds up to 20 seconds + my.scale = floor((my.scale + 1) / 2) * 2.0; + maxMinor = 10; + } else if (my.scale <= 60.0) { // ten-secondly up to a minute + my.scale = floor((my.scale + 5) / 10) * 10.0; + maxMinor = 10; + } else if (my.scale <= 600.0) { // minutely up to ten minutes + my.scale = floor((my.scale + 30) / 60) * 60.0; + maxMinor = 10; + } else if (my.scale < 3600.0) { // 10 minutely up to an hour + my.scale = floor((my.scale + 300) / 600) * 600.0; + maxMinor = 6; + } else if (my.scale < 86400.0) { // hourly up to a day + my.scale = floor((my.scale + 1800) / 3600) * 3600.0; + maxMinor = 24; + } else { // daily then on (60 * 60 * 24) + my.scale = 86400.0; + maxMinor = 10; + } + +#if DESPERATE + console->post(PmChart::DebugForce, "TimeAxis::scaleValue" + " width=%d points=%d scale=%.2f delta=%.2f maj=%d min=%d\n", + width(), points, my.scale, delta, maxMajor, maxMinor); +#endif + + my.delta = delta; + my.points = points; + setAxisMaxMajor(xBottom, maxMajor); + setAxisMaxMinor(xBottom, maxMinor); + return my.scale; +} + +// +// Update the time axis if width changes, idea is to display increased +// precision as more screen real estate becomes available. scaleValue +// is the critical piece of code in implementing it, as it uses width(). +// +void TimeAxis::resizeEvent(QResizeEvent *e) +{ + QwtPlot::resizeEvent(e); + if (e->size().width() != e->oldSize().width()) { + clearScaleCache(); + activeGroup->updateTimeAxis(); + } +} diff --git a/src/pmchart/timeaxis.h b/src/pmchart/timeaxis.h new file mode 100644 index 0000000..323d626 --- /dev/null +++ b/src/pmchart/timeaxis.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef TIMEAXIS_H +#define TIMEAXIS_H + +#include <qwt_plot_canvas.h> +#include <qwt_plot_layout.h> +#include <qwt_plot.h> +#include <QResizeEvent> + +class Tab; + +class TimeAxis : public QwtPlot +{ + Q_OBJECT +public: + TimeAxis(QWidget *); + + void init(); + void resetFont(); + void clearScaleCache(); + double scaleValue(double delta, int count); + void noArchiveSources(); + void print(QPainter *, QRect &, bool); + +protected: + void resizeEvent(QResizeEvent *); + +private: + struct { + int points; + double delta; + double scale; + } my; +}; + +#endif /* TIMEAXIS_H */ diff --git a/src/pmchart/timecontrol.cpp b/src/pmchart/timecontrol.cpp new file mode 100644 index 0000000..e72bcd0 --- /dev/null +++ b/src/pmchart/timecontrol.cpp @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2014, Red Hat. + * Copyright (c) 2006-2007, Aconex. All Rights Reserved. + * + * 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. + */ +#include "main.h" +#include <qmc_time.h> +#include <QtGui/QMessageBox> +#include <QtGui/QApplication> +#include <QtNetwork/QHostAddress> + +TimeControl::TimeControl() : QProcess(NULL) +{ + my.tcpPort = -1; + my.tzLength = 0; + my.tzData = NULL; + my.bufferLength = sizeof(QmcTime::Packet); + + my.buffer = (char *)malloc(my.bufferLength); + my.livePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet)); + my.archivePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet)); + if (!my.buffer || !my.livePacket || !my.archivePacket) + nomem(); + my.livePacket->magic = QmcTime::Magic; + my.livePacket->source = QmcTime::HostSource; + my.liveState = TimeControl::Disconnected; + my.liveSocket = new QTcpSocket(this); + connect(my.liveSocket, SIGNAL(connected()), + SLOT(liveSocketConnected())); + connect(my.liveSocket, SIGNAL(disconnected()), + SLOT(liveCloseConnection())); + connect(my.liveSocket, SIGNAL(readyRead()), + SLOT(liveProtocolMessage())); + + my.archivePacket->magic = QmcTime::Magic; + my.archivePacket->source = QmcTime::ArchiveSource; + my.archiveState = TimeControl::Disconnected; + my.archiveSocket = new QTcpSocket(this); + connect(my.archiveSocket, SIGNAL(connected()), + SLOT(archiveSocketConnected())); + connect(my.archiveSocket, SIGNAL(disconnected()), + SLOT(archiveCloseConnection())); + connect(my.archiveSocket, SIGNAL(readyRead()), + SLOT(archiveProtocolMessage())); +} + +void TimeControl::quit() +{ + disconnect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(endTimeControl())); + if (my.liveSocket) + disconnect(my.liveSocket, SIGNAL(disconnected()), this, + SLOT(liveCloseConnection())); + if (my.archiveSocket) + disconnect(my.archiveSocket, SIGNAL(disconnected()), this, + SLOT(archiveCloseConnection())); + if (my.liveSocket) { + my.liveSocket->close(); + my.liveSocket = NULL; + } + if (my.archiveSocket) { + my.archiveSocket->close(); + my.archiveSocket = NULL; + } + terminate(); +} + +void TimeControl::init(int port, bool live, + struct timeval *interval, struct timeval *position, + struct timeval *starttime, struct timeval *endtime, + QString tzstring, QString tzlabel) +{ + struct timeval now; + int tzlen = tzstring.length(), lablen = tzlabel.length(); + + my.tzLength = tzlen+1 + lablen+1; + my.tzData = (char *)realloc(my.tzData, my.tzLength); + if (!my.tzData) + nomem(); + + my.livePacket->length = my.archivePacket->length = + sizeof(QmcTime::Packet) + my.tzLength; + my.livePacket->command = my.archivePacket->command = QmcTime::Set; + my.livePacket->delta = my.archivePacket->delta = *interval; + if (live) { + my.livePacket->position = *position; + my.livePacket->start = *starttime; + my.livePacket->end = *endtime; + memset(&my.archivePacket->position, 0, sizeof(struct timeval)); + memset(&my.archivePacket->start, 0, sizeof(struct timeval)); + memset(&my.archivePacket->end, 0, sizeof(struct timeval)); + } else { + __pmtimevalNow(&now); + my.archivePacket->position = *position; + my.archivePacket->start = *starttime; + my.archivePacket->end = *endtime; + my.livePacket->position = now; + my.livePacket->start = now; + my.livePacket->end = now; + } + strncpy(my.tzData, (const char *)tzstring.toAscii(), tzlen+1); + strncpy(my.tzData + tzlen+1, (const char *)tzlabel.toAscii(), lablen+1); + + if (port <= 0) { + startTimeServer(); + } else { + my.tcpPort = port; + liveConnect(); + archiveConnect(); + } +} + +void TimeControl::addArchive( + struct timeval starttime, struct timeval endtime, + QString tzstring, QString tzlabel, bool atEnd) +{ + QmcTime::Packet *message; + int tzlen = tzstring.length(), lablen = tzlabel.length(); + int sz = sizeof(QmcTime::Packet) + tzlen + 1 + lablen + 1; + + if (my.archivePacket->position.tv_sec == 0) { // first archive + my.archivePacket->position = atEnd ? endtime : starttime; + my.archivePacket->start = starttime; + my.archivePacket->end = endtime; + } + + if ((message = (QmcTime::Packet *)malloc(sz)) == NULL) + nomem(); + *message = *my.archivePacket; + message->command = QmcTime::Bounds; + message->length = sz; + message->start = starttime; + message->end = endtime; + strncpy((char *)message->data, (const char *)tzstring.toAscii(), tzlen+1); + strncpy((char *)message->data + tzlen+1, + (const char *)tzlabel.toAscii(), lablen+1); + if (my.archiveSocket->write((const char *)message, sz) < 0) + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Cannot update pmtime boundaries."), + QApplication::tr("Quit") ); + free(message); +} + +void TimeControl::liveConnect() +{ + console->post("Connecting to pmtime, live source"); + my.liveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort); +} + +void TimeControl::archiveConnect() +{ + console->post("Connecting to pmtime, archive source"); + my.archiveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort); +} + +void TimeControl::showLiveTimeControl(void) +{ + my.livePacket->command = QmcTime::GUIShow; + my.livePacket->length = sizeof(QmcTime::Packet); + if (my.liveSocket->write((const char *)my.livePacket, + sizeof(QmcTime::Packet)) < 0) + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Cannot get pmtime to show itself."), + QApplication::tr("Quit") ); +} + +void TimeControl::showArchiveTimeControl(void) +{ + my.archivePacket->command = QmcTime::GUIShow; + my.archivePacket->length = sizeof(QmcTime::Packet); + if (my.archiveSocket->write((const char *)my.archivePacket, + sizeof(QmcTime::Packet)) < 0) + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Cannot get pmtime to show itself."), + QApplication::tr("Quit") ); +} + +void TimeControl::hideLiveTimeControl() +{ + my.livePacket->command = QmcTime::GUIHide; + my.livePacket->length = sizeof(QmcTime::Packet); + if (my.liveSocket->write((const char *)my.livePacket, + sizeof(QmcTime::Packet)) < 0) + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Cannot get pmtime to hide itself."), + QApplication::tr("Quit") ); +} + +void TimeControl::hideArchiveTimeControl() +{ + my.archivePacket->command = QmcTime::GUIHide; + my.archivePacket->length = sizeof(QmcTime::Packet); + if (my.archiveSocket->write((const char *)my.archivePacket, + sizeof(QmcTime::Packet)) < 0) + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Cannot get pmtime to hide itself."), + QApplication::tr("Quit") ); +} + +void TimeControl::endTimeControl(void) +{ + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Time Control process pmtime has exited."), + QApplication::tr("Quit") ); + exit(-1); +} + +void TimeControl::liveCloseConnection() +{ + console->post("Disconnected from pmtime, live source"); + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Connection to pmtime lost (live mode)."), + QApplication::tr("Quit") ); + quit(); +} + +void TimeControl::archiveCloseConnection() +{ + console->post("Disconnected from pmtime, archive source"); + QMessageBox::warning(0, + QApplication::tr("Error"), + QApplication::tr("Connection to pmtime lost (archive mode)."), + QApplication::tr("Quit") ); + quit(); +} + +void TimeControl::liveSocketConnected() +{ + if (my.liveSocket->write((const char *)my.livePacket, + sizeof(QmcTime::Packet)) < 0) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr( + "Failed socket write in live pmtime negotiation."), + QApplication::tr("Quit") ); + exit(1); + } + if (my.liveSocket->write((const char *)my.tzData, my.tzLength) < 0) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr( + "Failed to send timezone in live pmtime negotiation."), + QApplication::tr("Quit")); + exit(1); + } + my.liveState = TimeControl::AwaitingACK; +} + +void TimeControl::archiveSocketConnected() +{ + if (my.archiveSocket->write((const char *)my.archivePacket, + sizeof(QmcTime::Packet)) < 0) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr( + "Failed socket write in archive pmtime negotiation."), + QApplication::tr("Quit") ); + exit(1); + } + if (my.archiveSocket->write((const char *)my.tzData, my.tzLength) < 0) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr( + "Failed timezone send in archive pmtime negotiation."), + QApplication::tr("Quit") ); + exit(1); + } + my.archiveState = TimeControl::AwaitingACK; +} + +// +// Start a shiny new pmtime process. +// The one process serves time for all (live and archive) tabs. +// We do have to specify which form will be used first, however. +// +void TimeControl::startTimeServer() +{ + QStringList arguments; + + if (pmDebug & DBG_TRACE_TIMECONTROL) + arguments << "-D" << "all"; + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(endTimeControl())); + connect(this, SIGNAL(readyReadStandardOutput()), this, + SLOT(readPortFromStdout())); + start("pmtime", arguments); +} + +// +// When pmtime starts in "port probe" mode, port# is written to +// stdout. We can only complete negotiation once we have that... +// +void TimeControl::readPortFromStdout(void) +{ + bool ok; + QString data = readAllStandardOutput(); + + my.tcpPort = data.remove("port=").toInt(&ok, 10); + if (!ok) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Bad port number from pmtime program."), + QApplication::tr("Quit") ); + exit(1); + } + + liveConnect(); + archiveConnect(); +} + +void TimeControl::protocolMessage(bool live, + QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state) +{ + int sts, need = sizeof(QmcTime::Packet), offset = 0; + QmcTime::Packet *msg; + + // Read one pmtime packet, handling both small reads and large packets + for (;;) { + sts = socket->read(my.buffer + offset, need); + if (sts < 0) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Failed socket read in pmtime transfer."), + QApplication::tr("Quit") ); + exit(1); + } + else if (sts != need) { + need -= sts; + offset += sts; + continue; + } + + msg = (QmcTime::Packet *)my.buffer; + if (msg->magic != QmcTime::Magic) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Bad client message magic number."), + QApplication::tr("Quit") ); + exit(1); + } + if (msg->length > my.bufferLength) { + my.bufferLength = msg->length; + my.buffer = (char *)realloc(my.buffer, my.bufferLength); + if (!my.buffer) + nomem(); + msg = (QmcTime::Packet *)my.buffer; + } + if (msg->length > (uint)offset + sts) { + offset += sts; + need = msg->length - offset; + continue; + } + break; + } + +#if DESPERATE + console->post(PmChart::DebugProtocol, + "TimeControl::protocolMessage: recv pos=%s state=%d", + timeString(tosec(packet->position)), *state); +#endif + + switch (*state) { + case TimeControl::AwaitingACK: + if (msg->command != QmcTime::ACK) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Initial ACK not received from pmtime."), + QApplication::tr("Quit") ); + exit(1); + } + if (msg->source != packet->source) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("pmtime not serving same metric source."), + QApplication::tr("Quit") ); + exit(1); + } + *state = TimeControl::ClientReady; + if (msg->length > packet->length) { + packet = (QmcTime::Packet *)realloc(packet, msg->length); + if (!packet) + nomem(); + } + // + // Note: we drive the local state from the time control values, + // and _not_ from the values that we initially sent to it. + // + memcpy(packet, msg, msg->length); + pmchart->VCRMode(live, msg, true); + break; + + case TimeControl::ClientReady: + if (msg->command == QmcTime::Step) { + pmchart->step(live, msg); + msg->command = QmcTime::ACK; + msg->length = sizeof(QmcTime::Packet); + sts = socket->write((const char *)msg, msg->length); + if (sts < 0 || sts != (int)msg->length) { + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Failed pmtime write for STEP ACK."), + QApplication::tr("Quit") ); + exit(1); + } + } else if (msg->command == QmcTime::VCRMode || + msg->command == QmcTime::VCRModeDrag || + msg->command == QmcTime::Bounds) { + pmchart->VCRMode(live, msg, msg->command == QmcTime::VCRModeDrag); + } else if (msg->command == QmcTime::TZ) { + pmchart->timeZone(live, msg, (char *)msg->data); + } + break; + + default: + QMessageBox::critical(0, + QApplication::tr("Fatal error"), + QApplication::tr("Protocol error with pmtime."), + QApplication::tr("Quit") ); + // fall through + case TimeControl::Disconnected: + exit(1); + } +} + +void TimeControl::protocolMessageLoop(bool live, + QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state) +{ + do { + protocolMessage(live, packet, socket, state); + } while (socket->bytesAvailable() >= (int)sizeof(QmcTime::Packet)); +} diff --git a/src/pmchart/timecontrol.h b/src/pmchart/timecontrol.h new file mode 100644 index 0000000..2884bc7 --- /dev/null +++ b/src/pmchart/timecontrol.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2006-2007, Aconex. All Rights Reserved. + * + * 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. + */ +#ifndef TIMECONTROL_H +#define TIMECONTROL_H + +#include <QtCore/QString> +#include <QtCore/QProcess> +#include <QtNetwork/QTcpSocket> +#include <qmc_time.h> + +class TimeControl : public QProcess +{ + Q_OBJECT + +public: + TimeControl(); + + void init(int port, bool livemode, + struct timeval *interval, struct timeval *position, + struct timeval *starttime, struct timeval *endtime, + QString tzstring, QString tzlabel); + void quit(); + + void addArchive(struct timeval starttime, struct timeval endtime, + QString tzstring, QString tzlabel, bool atEnd); + + void liveConnect(); + void archiveConnect(); + + int port() { return my.tcpPort; } + struct timeval *liveInterval() { return &my.livePacket->delta; } + struct timeval *livePosition() { return &my.livePacket->position; } + struct timeval *archiveInterval() { return &my.archivePacket->delta; } + struct timeval *archivePosition() { return &my.archivePacket->position; } + void setArchivePosition(struct timeval *pos) { my.archivePacket->position = *pos; } + void setArchiveInterval(struct timeval *delta) { my.archivePacket->delta = *delta; } + struct timeval *archiveStart() { return &my.archivePacket->start; } + struct timeval *archiveEnd() { return &my.archivePacket->end; } + +public slots: + void showLiveTimeControl(); + void hideLiveTimeControl(); + void showArchiveTimeControl(); + void hideArchiveTimeControl(); + void endTimeControl(); + + void readPortFromStdout(); + + void liveCloseConnection(); + void liveSocketConnected(); + void liveProtocolMessage() + { + protocolMessageLoop(true, my.livePacket, my.liveSocket, &my.liveState); + } + + void archiveCloseConnection(); + void archiveSocketConnected(); + void archiveProtocolMessage() + { + protocolMessageLoop(false, my.archivePacket, my.archiveSocket, + &my.archiveState); + } + +private: + typedef enum { + Disconnected = 1, + AwaitingACK = 2, + ClientReady = 3, + } ProtocolState; + + void startTimeServer(); + void protocolMessage(bool live, QmcTime::Packet *pmtime, + QTcpSocket *socket, ProtocolState *state); + void protocolMessageLoop(bool live, QmcTime::Packet *pmtime, + QTcpSocket *socket, ProtocolState *state); + + struct { + int tcpPort; + int tzLength; + char *tzData; + + unsigned int bufferLength; + char *buffer; + + QTcpSocket *liveSocket; + QmcTime::Packet *livePacket; + ProtocolState liveState; + + QTcpSocket *archiveSocket; + QmcTime::Packet *archivePacket; + ProtocolState archiveState; + } my; +}; + +#endif // TIMECONTROL_H diff --git a/src/pmchart/tracing.cpp b/src/pmchart/tracing.cpp new file mode 100644 index 0000000..feb0dac --- /dev/null +++ b/src/pmchart/tracing.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2012, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * + * 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. + */ +#include <qwt_plot_directpainter.h> +#include <qwt_picker_machine.h> +#include <qwt_plot_marker.h> +#include <qwt_symbol.h> +#include "tracing.h" +#include "main.h" + +#define DESPERATE 0 + +TracingItem::TracingItem(Chart *chart, + QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp, const QString &legend) + : ChartItem(mp, msp, dp, legend) +{ + my.chart = chart; + my.minSpanID = 0; + my.maxSpanID = 1; + my.minSpanTime = tosec(mp->context()->source().start()); + if (mp->context()->source().isArchive()) + my.maxSpanTime = tosec(mp->context()->source().end()); + else + my.maxSpanTime = my.minSpanTime * 1.1; + + my.spanSymbol = new QwtIntervalSymbol(QwtIntervalSymbol::Box); + my.spanCurve = new QwtPlotIntervalCurve(label()); + my.spanCurve->setItemAttribute(QwtPlotItem::Legend, false); + my.spanCurve->setStyle(QwtPlotIntervalCurve::NoCurve); + my.spanCurve->setOrientation(Qt::Horizontal); + my.spanCurve->setSymbol(my.spanSymbol); + my.spanCurve->setZ(1); // lowest/furthest + + my.dropSymbol = new QwtIntervalSymbol(QwtIntervalSymbol::Box); + my.dropCurve = new QwtPlotIntervalCurve(label()); + my.dropCurve->setItemAttribute(QwtPlotItem::Legend, false); + my.dropCurve->setStyle(QwtPlotIntervalCurve::NoCurve); + my.dropCurve->setOrientation(Qt::Vertical); + my.dropCurve->setSymbol(my.dropSymbol); + my.dropCurve->setZ(2); // middle/central + + my.pointSymbol = new QwtSymbol(QwtSymbol::Ellipse); + my.pointCurve = new ChartCurve(label()); + my.pointCurve->setStyle(QwtPlotCurve::NoCurve); + my.pointCurve->setSymbol(my.pointSymbol); + my.pointCurve->setZ(3); // higher/closer + + my.selectionSymbol = new QwtSymbol(QwtSymbol::Ellipse); + my.selectionCurve = new QwtPlotCurve(label()); + my.selectionCurve->setItemAttribute(QwtPlotItem::Legend, false); + my.selectionCurve->setStyle(QwtPlotCurve::NoCurve); + my.selectionCurve->setSymbol(my.selectionSymbol); + my.selectionCurve->setZ(4); // highest/closest + + my.spanCurve->attach(chart); + my.dropCurve->attach(chart); + my.pointCurve->attach(chart); + my.selectionCurve->attach(chart); +} + +TracingItem::~TracingItem(void) +{ + delete my.spanCurve; + delete my.spanSymbol; + delete my.dropCurve; + delete my.dropSymbol; + delete my.pointCurve; + delete my.pointSymbol; + delete my.selectionCurve; + delete my.selectionSymbol; +} + +QwtPlotItem * +TracingItem::item(void) +{ + return my.pointCurve; +} + +QwtPlotCurve * +TracingItem::curve(void) +{ + return my.pointCurve; +} + +TracingEvent::TracingEvent(QmcEventRecord const &record, pmID pmid, int inst) +{ + my.timestamp = tosec(*record.timestamp()); + my.missed = record.missed(); + my.flags = record.flags(); + my.pmid = pmid; + my.inst = inst; + my.spanID = record.identifier(); + my.rootID = record.parent(); + + // details displayed about this record (on selection) + my.description.append(timeHiResString(my.timestamp)); + my.description.append(": flags="); + my.description.append(pmEventFlagsStr(record.flags())); + if (record.missed()> 0) { + my.description.append(" ("); + my.description.append(QString::number(record.missed())); + my.description.append(" missed)"); + } + my.description.append("\n"); + record.parameterSummary(my.description, inst); +} + +TracingEvent::~TracingEvent() +{ + my.timestamp = 0.0; +} + +void +TracingItem::rescaleValues(double *minValue, double *maxValue) +{ + if (my.minSpanID < *minValue) + *minValue = my.minSpanID; + if (my.maxSpanID > *maxValue) + *maxValue = my.maxSpanID; +} + +// +// Walk the vectors/lists and drop no-longer-needed events. +// For points/events/drops (single time value): +// Events arrive time-ordered, so we can short-circuit these +// walks once we are within the time window. Two phases - +// walk once from the left, then a subsequent walk from the +// right (note: done *after* we modify the original vector) +// For horizonal spans (i.e. two time values): +// This is still true - events arrive in order - but we have +// to walk the entire list as these ranges can overlap. But +// thats OK - we expect far fewer spans than total events. +// + +void +TracingItem::cullOutlyingSpans(double left, double right) +{ + // Start from the end so that we can remove as we go + // without interfering with the index we are using. + for (int i = my.spans.size() - 1; i >= 0; i--) { + const QwtIntervalSample &span = my.spans.at(i); + if (span.interval.maxValue() >= left || + span.interval.minValue() <= right) + continue; + my.spans.remove(i); + } +} + +void +TracingItem::cullOutlyingDrops(double left, double right) +{ + int i, cull; + + for (i = cull = 0; i < my.drops.size(); i++, cull++) + if (my.drops.at(i).value >= left) + break; + if (cull) + my.drops.remove(0, cull); // cull from the start (0-index) + for (i = my.drops.size() - 1, cull = 0; i >= 0; i--, cull++) + if (my.drops.at(i).value <= right) + break; + if (cull) + my.drops.remove(my.drops.size() - cull, cull); // cull from end +} + +void +TracingItem::cullOutlyingPoints(double left, double right) +{ + int i, cull; + + for (i = cull = 0; i < my.points.size(); i++, cull++) + if (my.points.at(i).x() >= left) + break; + if (cull) + my.points.remove(0, cull); // cull from the start (0-index) + for (i = my.points.size() - 1, cull = 0; i >= 0; i--, cull++) + if (my.points.at(i).x() <= right) + break; + if (cull) + my.points.remove(my.points.size() - cull, cull); // cull from end +} + +void +TracingItem::cullOutlyingEvents(double left, double right) +{ + int i, cull; + + for (i = cull = 0; i < my.events.size(); i++, cull++) + if (my.events.at(i).timestamp() >= left) + break; + if (cull) + my.events.remove(0, cull); // cull from the start (0-index) + for (i = my.events.size() - 1, cull = 0; i >= 0; i--, cull++) + if (my.events.at(i).timestamp() <= right) + break; + if (cull) + my.events.remove(my.events.size() - cull, cull); // cull from end +} + +void +TracingItem::resetValues(int, double left, double right) +{ + cullOutlyingSpans(left, right); + cullOutlyingDrops(left, right); + cullOutlyingPoints(left, right); + cullOutlyingEvents(left, right); + + // update the display + my.dropCurve->setSamples(my.drops); + my.spanCurve->setSamples(my.spans); + my.pointCurve->setSamples(my.points); + my.selectionCurve->setSamples(my.selections); +} + +// +// Requirement here is to merge in any event records that have +// just arrived in the last timestep (from my.metric) with the +// set already being displayed. +// Additionally, we need to cull those that are no longer within +// the time window of interest. +// +void +TracingItem::updateValues(TracingEngine *engine, double left, double right) +{ + QmcMetric *metric = ChartItem::my.metric; + +#if DESPERATE + console->post(PmChart::DebugForce, "TracingItem::updateValues: " + "%d total events, left=%.2f right=%.2f\n" + "Metadata counts: drops=%d spans=%d points=%d " + "Metric values: %d count\n", + my.events.size(), left, right, + my.drops.size(), my.spans.size(), my.points.size(), metric->numValues()); +#endif + + cullOutlyingSpans(left, right); + cullOutlyingDrops(left, right); + cullOutlyingPoints(left, right); + cullOutlyingEvents(left, right); + + // crack open newly arrived event records + if (metric->numValues() > 0) + updateEvents(engine, metric); + + // update the display + my.dropCurve->setSamples(my.drops); + my.spanCurve->setSamples(my.spans); + my.pointCurve->setSamples(my.points); + my.selectionCurve->setSamples(my.selections); +} + +void +TracingItem::updateEvents(TracingEngine *engine, QmcMetric *metric) +{ + if (metric->hasIndom() && !metric->explicitInsts()) { + for (int i = 0; i < metric->numInst(); i++) + updateEventRecords(engine, metric, i); + } else { + updateEventRecords(engine, metric, 0); + } +} + +// +// Fetch the new set of events, merging them into the existing sets +// - "Points" curve has an entry for *every* event to be displayed. +// - "Span" curve has an entry for all *begin/end* flagged events. +// These are initially unpaired, unless PMDA is playing games, and +// so usually min/max is used as a placeholder until corresponding +// begin/end event arrives. +// - "Drop" curve has an entry to match up events with the parents. +// The parent is the root "span" (terminology on loan from Dapper) +// +void +TracingItem::updateEventRecords(TracingEngine *engine, QmcMetric *metric, int index) +{ + if (metric->error(index) == 0) { + QVector<QmcEventRecord> const &records = metric->eventRecords(index); + int slot = 0, parentSlot = -1; + QString name; + + // First lookup the event "slot" (aka "span" / y-axis-entry) + // Strategy is to use the ID from the event (if one exists), + // which must be mapped to a y-axis integer-based index. If + // no ID, we fall back to metric instance ID, else just zero. + // + if (metric->hasInstances()) { + if (metric->explicitInsts()) + index = 0; + slot = metric->instIndex(index); + name = metric->instName(index); + } else { + name = metric->name(); + } + addTraceSpan(engine, name, slot); + + for (int i = 0; i < records.size(); i++) { + QmcEventRecord const &record = records.at(i); + + my.events.append(TracingEvent(record, metric->metricID(), index)); + TracingEvent &event = my.events.last(); + + if (event.hasIdentifier() && name == QString::null) { + addTraceSpan(engine, event.spanID(), slot); + } + + // this adds the basic point (ellipse), all events get one + my.points.append(QPointF(event.timestamp(), slot)); + + parentSlot = -1; + if (event.hasParent()) { // lookup parent in yMap + parentSlot = engine->getTraceSpan(event.rootID(), parentSlot); + if (parentSlot == -1) + addTraceSpan(engine, event.rootID(), parentSlot); + // do this on start/end only? (or if first?) + my.drops.append(QwtIntervalSample(event.timestamp(), + QwtInterval(slot, parentSlot))); + } + +#if DESPERATE + QString timestamp = QmcSource::timeStringBrief(record.timestamp()); + console->post(PmChart::DebugForce, "TracingItem::updateEventRecords: " + "[%s] span: %s (slot=%d) id=%s, root: %s (slot=%d,id=%s), start=%s end=%s", + (const char *)timestamp.toAscii(), + (const char *)event.spanID().toAscii(), slot, + event.hasIdentifier() ? "y" : "n", + (const char *)event.rootID().toAscii(), parentSlot, + event.hasParent() ? "y" : "n", + event.hasStartFlag() ? "y" : "n", event.hasEndFlag() ? "y" : "n"); +#endif + + if (event.hasStartFlag()) { + if (!my.spans.isEmpty()) { + QwtIntervalSample &active = my.spans.last(); + // did we get a start, then another start? + // (if so, just end the previous span now) + if (active.interval.maxValue() == my.maxSpanTime) + active.interval.setMaxValue(event.timestamp()); + } + // no matter what, we'll start a new span here + my.spans.append(QwtIntervalSample(slot, + QwtInterval(event.timestamp(), my.maxSpanTime))); + } + if (event.hasEndFlag()) { + if (!my.spans.isEmpty()) { + QwtIntervalSample &active = my.spans.last(); + // did we get an end, then another end? + // (if so, move previous span end to now) + if (active.interval.maxValue() == my.maxSpanTime) + active.interval.setMaxValue(event.timestamp()); + } else { + // got an end, but we haven't seen a start + my.spans.append(QwtIntervalSample(index, + QwtInterval(my.minSpanTime, event.timestamp()))); + } + } + // Have not yet handled missed events (i.e. event.missed()) + // Could have a separate list of events? (render differently?) + } + } else { +#if DESPERATE + // + // TODO: need to track this failure point, and ensure that the + // begin/end spans do not cross this boundary. + // + console->post(PmChart::DebugForce, + "TracingItem::updateEventRecords: NYI error path: %d (%s)", + metric->error(index), pmErrStr(metric->error(index))); +#endif + } +} + +void +TracingItem::addTraceSpan(TracingEngine *engine, const QString &span, int slot) +{ + double spanID = (double)slot; + + my.minSpanID = qMin(my.minSpanID, spanID); + my.maxSpanID = qMax(my.maxSpanID, spanID); + engine->addTraceSpan(span, slot); +} + +void +TracingItem::setStroke(Chart::Style, QColor color, bool) +{ + QPen outline(QColor(Qt::black)); + QColor darkColor(color); + QColor alphaColor(color); + + darkColor.dark(180); + alphaColor.setAlpha(196); + QBrush alphaBrush(alphaColor); + + my.pointCurve->setLegendColor(color); + + my.spanSymbol->setWidth(6); + my.spanSymbol->setBrush(alphaBrush); + my.spanSymbol->setPen(outline); + + my.dropSymbol->setWidth(1); + my.dropSymbol->setBrush(Qt::NoBrush); + my.dropSymbol->setPen(outline); + + my.pointSymbol->setSize(8); + my.pointSymbol->setColor(color); + my.pointSymbol->setPen(outline); + + my.selectionSymbol->setSize(8); + my.selectionSymbol->setColor(color.dark(180)); + my.selectionSymbol->setPen(outline); +} + +bool +TracingItem::containsPoint(const QRectF &rect, int index) +{ + if (my.points.isEmpty()) + return false; + return rect.contains(my.points.at(index)); +} + +void +TracingItem::updateCursor(const QPointF &, int index) +{ + Q_ASSERT(index <= my.points.size()); + Q_ASSERT(index <= (int)my.pointCurve->dataSize()); + + my.selections.append(my.points.at(index)); + my.selectionInfo.append(my.events.at(index).description()); + + // required for immediate chart update after selection + QBrush pointBrush = my.pointSymbol->brush(); + my.pointSymbol->setBrush(my.selectionSymbol->brush()); + QwtPlotDirectPainter directPainter; + directPainter.drawSeries(my.pointCurve, index, index); + my.pointSymbol->setBrush(pointBrush); +} + +void +TracingItem::clearCursor(void) +{ + // immediately clear any current visible selections + for (int index = 0; index < my.selections.size(); index++) { + QwtPlotDirectPainter directPainter; + directPainter.drawSeries(my.pointCurve, index, index); + } + my.selections.clear(); + my.selectionInfo.clear(); +} + +// +// Display information text associated with selected events +// +const QString & +TracingItem::cursorInfo(void) +{ + if (my.selections.size() > 0) { + QString preamble = metricName(); + if (metricHasInstances()) + preamble.append("[").append(metricInstance()).append("]"); + preamble.append("\n"); + my.selectionInfo.prepend(preamble); + } + return my.selectionInfo; +} + +void +TracingItem::revive(void) +{ + if (removed()) { + setRemoved(false); + my.dropCurve->attach(my.chart); + my.spanCurve->attach(my.chart); + my.pointCurve->attach(my.chart); + my.selectionCurve->attach(my.chart); + } +} + +void +TracingItem::remove(void) +{ + setRemoved(true); + my.dropCurve->detach(); + my.spanCurve->detach(); + my.pointCurve->detach(); + my.selectionCurve->detach(); +} + +void +TracingItem::redraw(void) +{ + if (removed() == false) { + // point curve update by legend check, but not the rest: + my.dropCurve->setVisible(hidden() == false); + my.spanCurve->setVisible(hidden() == false); + my.selectionCurve->setVisible(hidden() == false); + } +} + + +TracingScaleEngine::TracingScaleEngine(TracingEngine *engine) : QwtLinearScaleEngine() +{ + my.engine = engine; + my.minSpanID = 0.0; + my.maxSpanID = 1.0; + setMargins(0.5, 0.5); +} + +void +TracingScaleEngine::getScale(double *minValue, double *maxValue) +{ + *minValue = my.minSpanID; + *maxValue = my.maxSpanID; +} + +void +TracingScaleEngine::setScale(double minValue, double maxValue) +{ + my.minSpanID = minValue; + my.maxSpanID = maxValue; +} + +bool +TracingScaleEngine::updateScale(double minValue, double maxValue) +{ + bool changed = false; + + if (minValue < my.minSpanID) { + my.minSpanID = minValue; + changed = true; + } + if (maxValue > my.maxSpanID) { + my.maxSpanID = maxValue; + changed = true; + } + return changed; +} + +void +TracingScaleEngine::autoScale(int maxSteps, double &minValue, + double &maxValue, double &stepSize) const +{ + minValue = my.minSpanID; + maxValue = my.maxSpanID; + stepSize = 1.0; + QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize); +} + +QwtScaleDiv +TracingScaleEngine::divideScale(double x1, double x2, int numMajorSteps, + int /*numMinorSteps*/, double /*stepSize*/) const +{ + // discard minor steps - y-axis is displaying trace identifiers; + // sub-divisions of an identifier makes no sense + return QwtLinearScaleEngine::divideScale(x1, x2, numMajorSteps, 0, 1.0); +} + + +// +// Use the hash map to provide event identifiers that map to given numeric IDs +// These values were mapped into the hash when we decoded the event records. +// +QwtText +TracingScaleDraw::label(double value) const +{ + int slot = (int)value; + const int LABEL_CUTOFF = 8; // maximum width for label (units: characters) + QString label = my.engine->getSpanLabel(slot); + +#if DESPERATE + console->post(PmChart::DebugForce, + "TracingScaleDraw::label: lookup ID %d (=>\"%s\")", + slot, (const char *)label.toAscii()); +#endif + + // ensure label is not too long to fit + label.truncate(LABEL_CUTOFF); + // and only use up to the first space + if ((slot = label.indexOf(' ')) >= 0) + label.truncate(slot); + return label; +} + +void +TracingScaleDraw::getBorderDistHint(const QFont &f, int &start, int &end) const +{ + if (orientation() == Qt::Vertical) + start = end = 0; + else + QwtScaleDraw::getBorderDistHint(f, start, end); +} + + +// +// The (chart-level) implementation of tracing charts +// +TracingEngine::TracingEngine(Chart *chart) +{ + QwtPlotPicker *picker = chart->my.picker; + + my.chart = chart; + my.chart->my.style = Chart::EventStyle; + + my.scaleDraw = new TracingScaleDraw(this); + chart->setAxisScaleDraw(QwtPlot::yLeft, my.scaleDraw); + + my.scaleEngine = new TracingScaleEngine(this); + chart->setAxisScaleEngine(QwtPlot::yLeft, my.scaleEngine); + + // use a rectangular point picker for event tracing + picker->setStateMachine(new QwtPickerDragRectMachine()); + picker->setRubberBand(QwtPicker::RectRubberBand); + picker->setRubberBandPen(QColor(Qt::green)); +} + +ChartItem * +TracingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend) +{ + return new TracingItem(my.chart, mp, msp, desc, legend); +} + +TracingItem * +TracingEngine::tracingItem(int index) +{ + return (TracingItem *)my.chart->my.items[index]; +} + +void +TracingEngine::selected(const QPolygon &poly) +{ + my.chart->showPoints(poly); +} + +void +TracingEngine::replot(void) +{ + for (int i = 0; i < my.chart->metricCount(); i++) + tracingItem(i)->redraw(); +} + +void +TracingEngine::updateValues(bool, int, int, double left, double right, double) +{ + // Drive new values into each chart item + for (int i = 0; i < my.chart->metricCount(); i++) + tracingItem(i)->updateValues(this, left, right); +} + +int +TracingEngine::getTraceSpan(const QString &spanID, int slot) const +{ + return my.traceSpanMapping.value(spanID, slot); +} + +void +TracingEngine::addTraceSpan(const QString &spanID, int slot) +{ + Q_ASSERT(spanID != QString::null && spanID != ""); + console->post("TracingEngine::addTraceSpan: \"%s\" <=> slot %d (%d/%d span/label)", + (const char *)spanID.toAscii(), slot, + my.traceSpanMapping.size(), my.labelSpanMapping.size()); + my.traceSpanMapping.insert(spanID, slot); + my.labelSpanMapping.insert(slot, spanID); +} + +QString +TracingEngine::getSpanLabel(int slot) const +{ + return my.labelSpanMapping.value(slot); +} + +void +TracingEngine::redoScale(void) +{ + double minValue, maxValue; + + my.scaleEngine->getScale(&minValue, &maxValue); + + for (int i = 0; i < my.chart->metricCount(); i++) + tracingItem(i)->rescaleValues(&minValue, &maxValue); + + if (my.scaleEngine->updateScale(minValue, maxValue)) { + my.scaleDraw->invalidate(); + replot(); + } +} + +bool +TracingEngine::isCompatible(pmDesc &desc) +{ + return (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT); +} + +void +TracingEngine::scale(bool *autoScale, double *yMin, double *yMax) +{ + *autoScale = true; + my.scaleEngine->getScale(yMin, yMax); +} + +void +TracingEngine::setScale(bool, double, double) +{ + my.chart->setAxisAutoScale(QwtPlot::yLeft); +} + +void +TracingEngine::setStyle(Chart::Style) +{ + my.chart->setYAxisTitle(""); +} diff --git a/src/pmchart/tracing.h b/src/pmchart/tracing.h new file mode 100644 index 0000000..3072808 --- /dev/null +++ b/src/pmchart/tracing.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2012, Red Hat. + * Copyright (c) 2012, Nathan Scott. All Rights Reserved. + * + * 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. + */ +#ifndef TRACING_H +#define TRACING_H + +#include "chart.h" +#include <qvector.h> +#include <qwt_plot.h> +#include <qwt_scale_draw.h> +#include <qwt_scale_engine.h> +#include <qwt_interval_symbol.h> +#include <qwt_plot_intervalcurve.h> + +class TracingEvent +{ +public: + TracingEvent() { } + TracingEvent(QmcEventRecord const &, pmID, int); + bool operator<(TracingEvent const& rhs) // for sorting + { return my.timestamp < rhs.timestamp(); } + virtual ~TracingEvent(); + + double timestamp(void) const { return my.timestamp; } + int missed(void) const { return my.missed; } + bool hasIdentifier(void) const { return my.flags & PM_EVENT_FLAG_ID; } + bool hasParent(void) const { return my.flags & PM_EVENT_FLAG_PARENT; } + bool hasStartFlag(void) const { return my.flags & PM_EVENT_FLAG_START; } + bool hasEndFlag(void) const { return my.flags & PM_EVENT_FLAG_END; } + + const QString &spanID(void) const { return my.spanID; } + const QString &rootID(void) const { return my.rootID; } + const QString &description(void) const { return my.description; } + +private: + struct { + double timestamp; // from PMDA + int missed; // from PMDA + int flags; // from PMDA + pmID pmid; // metric + int inst; // instance + QString spanID; // identifier + QString rootID; // parent ID + QString description; // parameters, etc + } my; +}; + +class TracingItem : public ChartItem +{ +public: + TracingItem() : ChartItem() { } + TracingItem(Chart *, QmcMetric *, pmMetricSpec *, pmDesc *, const QString &); + virtual ~TracingItem(); + + QwtPlotItem *item(); + QwtPlotCurve *curve(); + + void preserveSample(int, int) { } + void punchoutSample(int) { } + void resetValues(int, double, double); + void updateValues(TracingEngine *, double, double); + void rescaleValues(double *, double *); + + void clearCursor(void); + bool containsPoint(const QRectF &, int); + void updateCursor(const QPointF &, int); + const QString &cursorInfo(); + + void setStroke(Chart::Style, QColor, bool); + void revive(void); + void remove(void); + void redraw(void); + +private: + void cullOutlyingDrops(double, double); + void cullOutlyingSpans(double, double); + void cullOutlyingPoints(double, double); + void cullOutlyingEvents(double, double); + + void updateEvents(TracingEngine *, QmcMetric *); + void updateEventRecords(TracingEngine *, QmcMetric *, int); + void addTraceSpan(TracingEngine *, const QString &, int); + void showEventInfo(bool, int); + + struct { + QVector<TracingEvent> events; // all events, raw data + QVector<QPointF> selections; // time series of selected points + QString selectionInfo; + + QVector<QPointF> points; // displayed trace data (point form) + ChartCurve *pointCurve; + QwtSymbol *pointSymbol; + + QVector<QPointF> selectionPoints; // displayed user-selected trace points + QwtPlotCurve *selectionCurve; + QwtSymbol *selectionSymbol; + + QVector<QwtIntervalSample> spans; // displayed trace data (horizontal span) + QwtPlotIntervalCurve *spanCurve; + QwtIntervalSymbol *spanSymbol; + + QVector<QwtIntervalSample> drops; // displayed trace data (vertical drop) + QwtPlotIntervalCurve *dropCurve; + QwtIntervalSymbol *dropSymbol; + + double minSpanID; + double maxSpanID; + double minSpanTime; + double maxSpanTime; + + Chart *chart; + } my; +}; + +// +// Handles updates to the Y-Axis +// +class TracingScaleEngine : public QwtLinearScaleEngine +{ +public: + TracingScaleEngine(TracingEngine *engine); + + virtual void autoScale(int maxSteps, double &minValue, + double &maxValue, double &stepSize) const; + virtual QwtScaleDiv divideScale(double x1, double x2, + int numMajorSteps, int numMinorSteps, double stepSize = 0.0) const; + + void getScale(double *minSpanID, double *maxSpanID); + void setScale(double minSpanID, double maxSpanID); + bool updateScale(double minSpanID, double maxSpanID); + +private: + struct { + double minSpanID; + double maxSpanID; + TracingEngine *engine; + } my; +}; + +// +// Implements a mapping from span identifier to span name +// for updating the Y-Axis display. +// +class TracingScaleDraw : public QwtScaleDraw +{ +public: + TracingScaleDraw(TracingEngine *engine) : QwtScaleDraw() { my.engine = engine; } + virtual QwtText label(double v) const; + virtual void getBorderDistHint(const QFont &f, int &start, int &end) const; + void invalidate() { invalidateCache(); } + +private: + struct { + TracingEngine *engine; + } my; +}; + +// +// Implement tracing-specific behaviour within a Chart +// +class TracingEngine : public ChartEngine +{ + friend class Chart; + +public: + TracingEngine(Chart *); + + bool isCompatible(pmDesc &); + ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &); + + void updateValues(bool, int, int, double, double, double); + + void redoScale(void); + void setScale(bool, double, double); + void scale(bool *, double *, double *); + void setStyle(Chart::Style); + + void addTraceSpan(const QString &, int); + int getTraceSpan(const QString &, int) const; + QString getSpanLabel(int) const; + + void selected(const QPolygon &); + void replot(void); + +private: + TracingItem *tracingItem(int index); + + struct { + QHash<QString, int> traceSpanMapping; // map, event ID to y-axis point + QHash<int, QString> labelSpanMapping; // reverse -> y-axis point to ID + TracingScaleEngine *scaleEngine; + TracingScaleDraw *scaleDraw; // convert ints to spanID + Chart *chart; + } my; +}; + +#endif // TRACING_H diff --git a/src/pmchart/view.cpp b/src/pmchart/view.cpp new file mode 100644 index 0000000..fcb6cf0 --- /dev/null +++ b/src/pmchart/view.cpp @@ -0,0 +1,1313 @@ +/* + * Copyright (c) 2006, Ken McDonell. All Rights Reserved. + * Copyright (c) 2013, Red Hat Inc. + * + * 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. + */ +#include <QtCore/QString> +#include <QtGui/QMessageBox> +#include <QtGui/QColor> + +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include "main.h" +#include "openviewdialog.h" +#include "saveviewdialog.h" + +/* + * View file parsing routines and global variables follow. These are + * currently not part of any class, and may need to be reworked a bit + * to use more portable (Qt) file IO interfaces (for a Windows port). + */ +static char _fname[MAXPATHLEN]; +static uint _line; +static uint _errors; +static uint _width; +static uint _height; +static uint _points; +static uint _xpos; +static uint _ypos; + +#define MAXWDSZ 256 + +// parser states +#define S_BEGIN 0 +#define S_VERSION 1 +#define S_TOP 2 +#define S_CHART 3 +#define S_PLOT 4 + +// config file styles (mode) +#define M_UNKNOWN 0 +#define M_PMCHART 1 +#define M_KMCHART 2 + +// global attributes +#define G_WIDTH 1 +#define G_HEIGHT 2 +#define G_POINTS 3 +#define G_XPOS 4 +#define G_YPOS 5 + +// host mode +#define H_DYNAMIC 1 +#define H_LITERAL 2 + +// error severity +#define E_INFO 0 +#define E_CRIT 1 +#define E_WARN 2 + +// version numbers we're willing and able to support +#define P1_1 101 +#define P1_2 102 +#define P2_0 200 +#define P2_1 201 +#define K1 1 + +// instance / matching / not-matching +#define IM_NONE 0 +#define IM_INST 1 +#define IM_MATCH 2 +#define IM_NOT_MATCH 3 + +const char *_style[] = { "None", "Line", "Bar", "Stack", "Area", "Util" }; +#define stylestr(x) _style[(int)x] + +static void err(int severity, int do_where, QString msg) +{ + if (do_where) { + QString where = QString(); + where.sprintf("%s[%d] ", _fname, _line); + msg.prepend(where); + } + if (Cflag) { + if (severity == E_CRIT) + msg.prepend("Error: "); + else if (severity == E_WARN) + msg.prepend("Warning: "); + // else do nothing for E_INFO + msg.append("\n"); + fflush(stderr); + pmprintf("%s", (const char *)msg.toAscii()); + pmflush(); + } + else { + if (severity == E_CRIT) + QMessageBox::critical(pmchart, pmProgname, msg); + else if (severity == E_WARN) + QMessageBox::warning(pmchart, pmProgname, msg); + else + QMessageBox::information(pmchart, pmProgname, msg); + } + _errors++; +} + +static char *getwd(FILE *f) +{ + static char buf[MAXWDSZ]; + static int lastc = 0; + char *p; + int c; + int quote = 0; + + if ((char)lastc == '\n') { +eol: + buf[0] = '\n'; + buf[1] = '\0'; + _line++; + lastc = 0; + goto done; + } + + // skip to first non-white space + p = buf; + while ((c = fgetc(f)) != EOF) { + if ((char)c == '\n') + goto eol; + if (!isspace((char)c)) { + // got one + if ((char)c == '"') { + quote = 1; + } + else { + *p++ = c; + } + break; + } + } + if (feof(f)) return NULL; + + for ( ; p < &buf[MAXWDSZ]; ) { + if ((c = fgetc(f)) == EOF) break; + if ((char)c == '\n') { + lastc = c; + break; + } + if (quote == 0 && isspace((char)c)) break; + if (quote && (char)c == '"') break; + *p++ = c; + } + + if (p == &buf[MAXWDSZ]) { + QString msg = QString(); + p[-1] = '\0'; + msg.sprintf("Word truncated after %d characters!\n\"%20.20s ... %20.20s\"", (int)sizeof(buf)-1, buf, &p[-21]); + err(E_CRIT, true, msg); + } + else + *p = '\0'; + +done: + if ((pmDebug & DBG_TRACE_APPL0) && (pmDebug & DBG_TRACE_APPL2)) { + if (buf[0] == '\n') + fprintf(stderr, "openView getwd=EOL\n"); + else + fprintf(stderr, "openView getwd=\"%s\"\n", buf); + } + + return buf; +} + +static void eol(FILE *f) +{ + char *w; + + while ((w = getwd(f)) != NULL && w[0] != '\n') { + QString msg = QString("Syntax error: unexpected word \""); + msg.append(w); + msg.append("\""); + err(E_CRIT, true, msg); + } +} + +static void skip2eol(FILE *f) +{ + char *w; + + while ((w = getwd(f)) != NULL && w[0] != '\n') { + ; + } +} + +static void xpect(const char *want, const char *got) +{ + QString msg = QString("Syntax error: expecting \""); + msg.append(want); + msg.append("\", found "); + if (got == NULL) + msg.append("End-of-File"); + else if (got[0] == '\n') + msg.append("End-of-Line"); + else { + msg.append("\""); + msg.append(got); + msg.append("\""); + } + err(E_CRIT, true, msg); +} + +static QColor colorSpec(QString colorName, int *sequence) +{ + if (colorName == "#-cycle") + return nextColor("#-cycle", sequence); + if (ColorScheme::lookupScheme(colorName) == true) + return nextColor(colorName, sequence); + QColor color = ColorScheme::colorSpec(colorName); + if (!color.isValid()) { + QString errmsg; + errmsg.append("Invalid color name: "); + errmsg.append(colorName); + err(E_CRIT, true, errmsg); + color = Qt::white; + } + return color; +} + +void OpenViewDialog::globals(int *w, int *h, int *pts, int *x, int *y) +{ + // Note: we use global variables here so that all views specified + // on the command line get input into these values (the maximum + // observed value is always used), without clobbering each other. + // + *w = _width; + *h = _height; + *pts = _points; + *x = _xpos; + *y = _ypos; +} + +bool OpenViewDialog::openView(const char *path) +{ + Tab *tab; + Chart *cp = NULL; + int ct = pmchart->tabWidget()->currentIndex(); + int m; + ColorScheme scheme; + FILE *f; + int is_popen = 0; + char *w; + int state = S_BEGIN; + int mode = M_UNKNOWN; + int h_mode; + int version; + QString errmsg; + QRegExp regex; + int sep = __pmPathSeparator(); + int sts = 0; + + if (strcmp(path, "-") == 0) { + f = stdin; + strcpy(_fname, "stdin"); + } + else if (path[0] == '/') { + strcpy(_fname, path); + if ((f = fopen(_fname, "r")) == NULL) + goto noview; + } + else { + QString homepath = QDir::toNativeSeparators(QDir::homePath()); + + strcpy(_fname, path); + if ((f = fopen(_fname, "r")) == NULL) { + // not found, start the great hunt + // try user's pmchart dir ... + snprintf(_fname, sizeof(_fname), + "%s%c" ".pcp%c" "pmchart%c" "%s", + (const char *)homepath.toAscii(), sep, sep, sep, path); + if ((f = fopen(_fname, "r")) == NULL) { + // try system pmchart dir + snprintf(_fname, sizeof(_fname), + "%s%c" "config%c" "pmchart%c" "%s", + pmGetConfig("PCP_VAR_DIR"), sep, sep, sep, path); + if ((f = fopen(_fname, "r")) == NULL) { + // try user's kmchart dir + snprintf(_fname, sizeof(_fname), + "%s%c" ".pcp%c" "kmchart%c" "%s", + (const char *)homepath.toAscii(), + sep, sep, sep, path); + if ((f = fopen(_fname, "r")) == NULL) { + // try system kmchart dir + snprintf(_fname, sizeof(_fname), + "%s%c" "config%c" "kmchart%c" "%s", + pmGetConfig("PCP_VAR_DIR"), + sep, sep, sep, path); + if ((f = fopen(_fname, "r")) == NULL) { + snprintf(_fname, sizeof(_fname), + "%s%c" "config%c" "pmchart%c" "%s", + pmGetConfig("PCP_VAR_DIR"), + sep, sep, sep, path); + goto noview; + } + } + } + } + } + // check for executable and popen() as needed + // + if (fgetc(f) == '#' && fgetc(f) == '!') { + char cmd[MAXPATHLEN]; + sprintf(cmd, "%s", _fname); + fclose(f); + if ((f = popen(cmd, "r")) == NULL) + goto nopipe; + is_popen = 1; + } + else { + rewind(f); + } + } + + _line = 1; + _errors = 0; + console->post(PmChart::DebugView, "Load View: %s", _fname); + + while ((w = getwd(f)) != NULL) { + if (state == S_BEGIN) { + // expect #pmchart + if (strcasecmp(w, "#pmchart") == 0) + mode = M_PMCHART; + else if (strcasecmp(w, "#kmchart") == 0) + mode = M_KMCHART; + else { + xpect("#pmchart\" or \"#kmchart", w); + goto abandon; + } + eol(f); + state = S_VERSION; + continue; + } + + if (w[0] == '\n') + // skip empty lines and comments + continue; + + if (w[0] == '#') { + // and comments + skip2eol(f); + continue; + } + + if (state == S_VERSION) { + // expect version X.X host [dynamic|static] + if (strcasecmp(w, "version") != 0) { + xpect("version", w); + goto abandon; + } + w = getwd(f); + if (w == NULL || w[0] == '\n') { + xpect("<version number>", w); + goto abandon; + } + version = 0; + if (mode == M_PMCHART) { + if (strcmp(w, "2.1") == 0) + version = P2_1; + else if (strcmp(w, "2.0") == 0) + version = P2_0; + else if (strcmp(w, "1.1") == 0) + version = P1_1; + else if (strcmp(w, "1.2") == 0) + version = P1_2; + } + else if (mode == M_KMCHART) { + if (strcmp(w, "1") == 0) + version = K1; + } + if (version == 0) { + xpect("<version number>", w); + goto abandon; + } + w = getwd(f); + if (w == NULL || w[0] == '\n') { + if (mode == M_KMCHART) { + // host [literal|dynamic] is optional for new pmchart + h_mode = H_DYNAMIC; + state = S_TOP; + continue; + } + else { + xpect("host", w); + goto abandon; + } + } + if (strcasecmp(w, "host") != 0) { + xpect("host", w); + goto abandon; + } + w = getwd(f); + if (w != NULL && strcasecmp(w, "literal") == 0) { + h_mode = H_LITERAL; + } + else if (w != NULL && strcasecmp(w, "dynamic") == 0) { + h_mode = H_DYNAMIC; + } + else { + xpect("literal\" or \"dynamic", w); + goto abandon; + } + eol(f); + state = S_TOP; + } + + else if (state == S_TOP) { + if (strcasecmp(w, "chart") == 0) { +new_chart: + char *title = NULL; + Chart::Style style = Chart::NoStyle; + int autoscale = 1; + char *endnum; + double ymin = 0; + double ymax = 0; + int legend = 1; + int antialias = 1; + + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("title\" or \"style", w); + goto abort_chart; + } + if (strcasecmp(w, "title") == 0) { + // optional title "<title>" + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<title>", w); + goto abort_chart; + } + if ((title = strdup(w)) == NULL) nomem(); + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("style", w); + goto abort_chart; + } + } + if (strcasecmp(w, "style") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<chart style>", w); + goto abort_chart; + } + if (strcasecmp(w, "plot") == 0) + style = Chart::LineStyle; + else if (strcasecmp(w, "line") == 0) + style = Chart::LineStyle; + else if (strcasecmp(w, "bar") == 0) + style = Chart::BarStyle; + else if (strcasecmp(w, "stacking") == 0) + style = Chart::StackStyle; + else if (strcasecmp(w, "area") == 0) + style = Chart::AreaStyle; + else if (strcasecmp(w, "utilization") == 0) + style = Chart::UtilisationStyle; + else if (strcasecmp(w, "event") == 0) + style = Chart::EventStyle; + else { + xpect("<chart style>", w); + goto abort_chart; + } + } + + // down to the optional bits + // - scale + // - legend + if ((w = getwd(f)) == NULL || w[0] == '\n') + goto done_chart; + if (strcasecmp(w, "scale") == 0) { + // scale [from] ymin [to] ymax + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("from or <ymin>", w); + goto abort_chart; + } + if (strcasecmp(w, "from") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<ymin>", w); + goto abort_chart; + } + } + ymin = strtod(w, &endnum); + if (*endnum != '\0') { + xpect("<ymin>", w); + goto abort_chart; + } + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("to or <ymax>", w); + goto abort_chart; + } + if (strcasecmp(w, "to") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<ymax>", w); + goto abort_chart; + } + } + ymax = strtod(w, &endnum); + if (*endnum != '\0') { + xpect("<ymax>", w); + goto abort_chart; + } + autoscale = 0; + if ((w = getwd(f)) == NULL || w[0] == '\n') + goto done_chart; + } + if (strcasecmp(w, "legend") == 0) { + // optional legend on|off + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("on\" or \"off", w); + goto abort_chart; + } + if (strcasecmp(w, "on") == 0) + legend = 1; + else if (strcasecmp(w, "off") == 0) + legend = 0; + else { + xpect("on\" or \"off", w); + goto abort_chart; + } + if ((w = getwd(f)) == NULL || w[0] == '\n') + goto done_chart; + } + if (strcasecmp(w, "antialiasing") == 0) { + // optional antialiasing on|off + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("on\" or \"off", w); + goto abort_chart; + } + if (strcasecmp(w, "on") == 0) + antialias = 1; + else if (strcasecmp(w, "off") == 0) + antialias = 0; + else { + xpect("on\" or \"off", w); + goto abort_chart; + } + if ((w = getwd(f)) == NULL || w[0] == '\n') + goto done_chart; + } +done_chart: + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "openView: new chart: style=%s", + stylestr(style)); + if (title != NULL) + fprintf(stderr, " title=\"%s\"", title); + if (autoscale) + fprintf(stderr, " autoscale=yes"); + else + fprintf(stderr, " ymin=%.1f ymax=%.1f", ymin, ymax); + if (legend) + fprintf(stderr, " legend=yes"); + if (!antialias) + fprintf(stderr, " antialias=no"); + fputc('\n', stderr); + } + if (Cflag == 0 || Cflag == 2) { + tab = pmchart->activeTab(); + cp = new Chart(tab, tab->splitter()); + cp->setStyle(style); + cp->setScheme(scheme.name()); + if (title != NULL) + cp->changeTitle(title, mode == M_KMCHART); + if (legend == 0) + cp->setLegendVisible(false); + if (antialias == 0) + cp->setAntiAliasing(false); + cp->setScale(autoscale, ymin, ymax); + activeGroup->addGadget(cp); + tab->addGadget(cp); + } + state = S_CHART; + if (title != NULL) free(title); + continue; + +abort_chart: + // unrecoverable error in the chart clause of the view + // specification, abandon this one + if (title != NULL) free(title); + goto abandon; + } + else if (strcasecmp(w, "global") == 0) { + char *endnum; + uint value, attr; + + // Global window attributes (geometry and visible points) + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w); + goto abandon; + } + else if (strcasecmp(w, "width") == 0) + attr = G_WIDTH; + else if (strcasecmp(w, "height") == 0) + attr = G_HEIGHT; + else if (strcasecmp(w, "points") == 0) + attr = G_POINTS; + else if (strcasecmp(w, "xpos") == 0) + attr = G_XPOS; + else if (strcasecmp(w, "ypos") == 0) + attr = G_YPOS; + else { + xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w); + goto abandon; + } + w = getwd(f); + if (w == NULL || w[0] == '\n') { + xpect("<global attribute value>", w); + goto abandon; + } + value = (uint)strtoul(w, &endnum, 0); + if (*endnum != '\0') { + xpect("<global attribute value>", w); + goto abandon; + } + switch (attr) { + case G_WIDTH: + _width = qMax(_width, value); + break; + case G_HEIGHT: + _height = qMax(_height, value); + break; + case G_POINTS: + _points = qMax(_points, value); + break; + case G_XPOS: + _xpos = qMax(_xpos, value); + break; + case G_YPOS: + _ypos = qMax(_ypos, value); + break; + } + eol(f); + } + else if (strcasecmp(w, "scheme") == 0) { + // + // scheme <name> <color> <color>... + // provides finer-grained control over the color selections + // for an individual chart. The default color scheme is + // named #-cycle. A scheme can be used in place of a direct + // color name specification, and the color for a plot is + // then defined as the next unused color from that scheme. + // + w = getwd(f); + if (w == NULL || w[0] == '\n') { + xpect("<color scheme value>", w); + goto abandon; + } + else if (strcmp(w, "#-cycle") == 0) { + xpect("<non-default color scheme name>", w); + goto abandon; + } + if (ColorScheme::lookupScheme(w) == true) { + // duplicate - ignore (probably using a seen view again) + skip2eol(f); + continue; + } + scheme.setName(QString(w)); + scheme.clear(); + w = getwd(f); + while (w && w[0] != '\n') { + scheme.addColor(QString(w)); + w = getwd(f); + } + if (scheme.size() < 2) { + xpect("<list of color names>", w); + goto abandon; + } + globalSettings.colorSchemes.append(scheme); + } + else if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) { +new_tab: + QString label, host; + int samples = globalSettings.sampleHistory; + int points = globalSettings.visibleHistory; + char *endnum; + + w = getwd(f); + if (w == NULL || w[0] == '\n') { + xpect("<tab label>", w); + goto abandon; + } + label = w; + + w = getwd(f); + if (w == NULL || w[0] == '\n') + goto done_tab; + + // default "host" specification for the tab is optional + if (strcasecmp(w, "host") == 0) { + w = getwd(f); + if (w == NULL || w[0] == '\n') { + xpect("<host>", w); + goto abandon; + } + host = w; + w = getwd(f); + if (w == NULL || w[0] == '\n') + goto done_tab; + } + if (strcasecmp(w, "points") != 0) { + xpect("<tab points>", w); + goto abandon; + } + w = getwd(f); + if (w) + points = (uint)strtoul(w, &endnum, 0); + if (w == NULL || w[0] == '\n' || *endnum != '\0') { + xpect("<tab points value>", w); + goto abandon; + } + + w = getwd(f); + if (w == NULL || w[0] == '\n') + goto done_tab; + if (strcasecmp(w, "samples") != 0) { + xpect("<tab samples>", w); + goto abandon; + } + w = getwd(f); + if (w) + samples = (uint)strtoul(w, &endnum, 0); + if (w == NULL || w[0] == '\n' || *endnum != '\0') { + xpect("<tab samples value>", w); + goto abandon; + } + +done_tab: + tab = pmchart->activeTab(); + bool isArchive = tab->isArchiveSource(); + + if (host != QString::null) { + if (isArchive) + archiveGroup->use(PM_CONTEXT_ARCHIVE, host); + else + liveGroup->use(PM_CONTEXT_HOST, host); + } + + if (tab->gadgetCount() == 0) { // edit the initial tab + TabWidget *tabWidget = pmchart->tabWidget(); + tabWidget->setTabText(tabWidget->currentIndex(), label); + } + else { // create a completely new tab from scratch + tab = new Tab; + if (isArchive) + tab->init(pmchart->tabWidget(), archiveGroup, label); + else + tab->init(pmchart->tabWidget(), liveGroup, label); + pmchart->addActiveTab(tab); + } + activeGroup->setSampleHistory(samples); + activeGroup->setVisibleHistory(points); + } + else { + xpect("chart\", \"global\", \"scheme\" or \"tab", w); + goto abandon; + } + } + + else if (state == S_CHART) { + int optional; + char *legend = NULL; + char *color = NULL; + char *host = NULL; + int inst_match_type = IM_NONE; + int numinst = -1; + int nextinst = -1; + int *instlist = NULL; + char **namelist = NULL; + pmMetricSpec pms; + int abort = 1; // default @ skip + + memset(&pms, 0, sizeof(pms)); + if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) { + // new tab + state = S_TOP; + goto new_tab; + } + if (strcasecmp(w, "chart") == 0) { + // new chart + state = S_TOP; + goto new_chart; + } + if (strcasecmp(w, "plot") == 0) { + optional = 0; + } + else if (strcasecmp(w, "optional-plot") == 0) { + optional = 1; + } + else { + xpect("plot\" or \"optional-plot", w); + goto skip; + } + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("title\" or \"color", w); + goto skip; + } + if (strcasecmp(w, "title") == 0 || + (mode == M_KMCHART && strcasecmp(w, "legend") == 0)) { + // optional title "<title>" or + // (for new pmchart) legend "<title>" + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<legend title>", w); + goto skip; + } + if ((legend = strdup(w)) == NULL) nomem(); + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("color", w); + goto skip; + } + } + // color <color> is mandatory for pmchart, optional for + // new pmchart (where the default is color #-cycle) + if (strcasecmp(w, "color") == 0 || strcasecmp(w, "colour") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<color>", w); + goto skip; + } + if ((color = strdup(w)) == NULL) nomem(); + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("host", w); + goto skip; + } + } + else if (mode == M_PMCHART) { + xpect("color", w); + goto skip; + } + // host <host> is mandatory for pmchart, optional for + // new pmchart (where the default is host *) + if (strcasecmp(w, "host") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<host>", w); + goto skip; + } + if (strcmp(w, "*") == 0) + host = NULL; // just like the new pmchart default + else { + if ((host = strdup(w)) == NULL) nomem(); + } + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("metric", w); + goto skip; + } + } + else if (mode == M_PMCHART) { + xpect("host", w); + goto skip; + } + // metric is mandatory + if (strcasecmp(w, "metric") == 0) { + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<metric>", w); + goto skip; + } + if ((pms.metric = strdup(w)) == NULL) nomem(); + } + else { + xpect("metric", w); + goto skip; + } + pms.ninst = 0; + pms.inst[0] = NULL; + if ((w = getwd(f)) != NULL && w[0] != '\n') { + // optional parts + // instance ... + // matching ... + // not-matching ... + if (strcasecmp(w, "instance") == 0) { + inst_match_type = IM_INST; + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<instance>", w); + goto skip; + } + pms.ninst = 1; + if ((pms.inst[0] = strdup(w)) == NULL) nomem(); + } + else if (strcasecmp(w, "matching") == 0) { + inst_match_type = IM_MATCH; + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<pattern>", w); + goto skip; + } + pms.ninst = 1; + pms.inst[0] = strdup(w); + } + else if (strcasecmp(w, "not-matching") == 0) { + inst_match_type = IM_NOT_MATCH; + if ((w = getwd(f)) == NULL || w[0] == '\n') { + xpect("<pattern>", w); + goto skip; + } + pms.ninst = 1; + pms.inst[0] = strdup(w); + } + else { + xpect("instance\" or \"matching\" or \"not-matching", w); + goto skip; + } + if (mode == M_PMCHART) { + // pmchart has this lame "instance extends to end + // of line" syntax ... sigh + while ((w = getwd(f)) != NULL && w[0] != '\n') { + pms.inst[0] = (char *)realloc(pms.inst[0], strlen(pms.inst[0]) + strlen(w) + 2); + if (pms.inst[0] == NULL) nomem(); + // if more than one space in the input, touch luck! + strcat(pms.inst[0], " "); + strcat(pms.inst[0], w); + } + if (pms.inst[0] != NULL) { + pms.ninst = 1; + } + } + else { + // expect end of line after instance/pattern + // (pmchart uses quotes to make instance a single + // lexical element in the line) + eol(f); + } + } + + abort = 0; + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "openView: new %s", optional ? "optional-plot" : "plot"); + if (legend != NULL) fprintf(stderr, " legend=\"%s\"", legend); + if (color != NULL) fprintf(stderr, " color=%s", color); + if (host != NULL) fprintf(stderr, " host=%s", host); + fprintf(stderr, " metric=%s", pms.metric); + if (pms.ninst == 1) { + fprintf(stderr, " inst=%s", pms.inst[0]); + if (inst_match_type == IM_NONE) + fprintf(stderr, " match=none (botch?)"); + else if (inst_match_type == IM_INST) + fprintf(stderr, " match=instance"); + else if (inst_match_type == IM_MATCH) + fprintf(stderr, " match=matching"); + else if (inst_match_type == IM_NOT_MATCH) + fprintf(stderr, " match=not-matching"); + } + fputc('\n', stderr); + } + if (Cflag == 0 || Cflag == 2) { + pms.isarch = (activeGroup == archiveGroup); + if (host != NULL) { + // host literal, add to the list of sources + if (activeGroup == archiveGroup) { + QString hostname = host; + if (archiveGroup->use(PM_CONTEXT_HOST, hostname) < 0) { + QString msg; + msg.sprintf("\nHost \"%s\" cannot be matched to an open archive for metric %s", + host, pms.metric); + errmsg.append(msg); + goto skip; + } + QmcSource source = archiveGroup->context()->source(); + pms.source = strdup((const char *) + source.source().toAscii()); + } + else { + pms.source = strdup(host); + } + } + else { + // no explicit host, use current default source + QmcSource source = activeGroup->context()->source(); + pms.source = strdup((const char *) + source.source().toAscii()); + } + // expand instances when not specified for metrics + // with instance domains and all instances required, + // or matching or not-matching instances required + // + if (inst_match_type != IM_INST) { + pmID pmid; + pmDesc desc; + + // if pmLookupName() or pmLookupDesc() fail, we'll + // notice in addPlot() and report the error below, + // so no need to do anything special here + // + if (pmLookupName(1, &pms.metric, &pmid) < 0) + goto try_plot; + if (pmLookupDesc(pmid, &desc) < 0) + goto try_plot; + if (desc.indom == PM_INDOM_NULL) { + if (inst_match_type == IM_MATCH || + inst_match_type == IM_NOT_MATCH) { + // a bit embarrassing + QString msg = QString(); + msg.sprintf("\nMetric \"%s\" for\n%s %s: no instance domain, cannot handle matching specification", + pms.metric, pms.isarch ? "archive" : "host", + pms.source); + errmsg.append(msg); + goto skip; + } + goto try_plot; + } + + if (pms.isarch) + numinst = pmGetInDomArchive(desc.indom, &instlist, &namelist); + else + numinst = pmGetInDom(desc.indom, &instlist, &namelist); + if (numinst < 0) { + QString msg = QString(); + msg.sprintf("\nMetric \"%s\" for\n%s %s: empty instance domain", + pms.metric, pms.isarch ? "archive" : "host", + pms.source); + errmsg.append(msg); + goto skip; + } + if (inst_match_type != IM_NONE) { + regex.setPattern(pms.inst[0]); + if (!regex.isValid()) { + errmsg = "Invalid regular expression:\n "; + errmsg.append(pms.inst[0]); + errmsg.append("\n\n"); + errmsg.append(regex.errorString()); + goto skip; + } + } + pms.ninst = numinst ? 1 : 0; + if (pms.inst[0] != NULL) { + free(pms.inst[0]); + pms.inst[0] = NULL; + } + } + +try_plot: + if (numinst > 0) { + pms.inst[0] = NULL; + for (nextinst++ ; nextinst < numinst; nextinst++) { + sts = 0; + if (inst_match_type == IM_MATCH || + inst_match_type == IM_NOT_MATCH) { + sts = regex.indexIn(QString(namelist[nextinst])); + } + switch (inst_match_type) { + case IM_MATCH: + if (sts != -1) + pms.inst[0] = namelist[nextinst]; + break; + case IM_NOT_MATCH: + if (sts == -1) + pms.inst[0] = namelist[nextinst]; + break; + case IM_NONE: + pms.inst[0] = namelist[nextinst]; + break; + } + if (pms.inst[0] != NULL) + break; + } + if (nextinst == numinst) + goto skip; + } + m = cp->addItem(&pms, QString(legend)); + if (m < 0) { + if (!optional) { + QString msg; + if (pms.inst[0] != NULL) + msg.sprintf("\nFailed to plot metric \"%s[%s]\" for\n%s %s:\n", + pms.metric, pms.inst[0], + pms.isarch ? "archive" : "host", + pms.source); + else + msg.sprintf("\nFailed to plot metric \"%s\" for\n%s %s:\n", + pms.metric, pms.isarch ? "archive" : "host", + pms.source); + if (m == PM_ERR_CONV) { + msg.append("Units for this metric are not compatible with other plots in this chart"); + } + else + msg.append(pmErrStr(m)); + errmsg.append(msg); + } + } + else if (color != NULL && strcmp(color, "#-cycle") != 0) { + int seq = cp->sequence(); + cp->setStroke(m, cp->style(), colorSpec(color, &seq)); + cp->setSequence(seq); + } + if (numinst > 0) + // more instances still to be processed for this metric + goto try_plot; + + } + +skip: + if (legend != NULL) free(legend); + if (color != NULL) free(color); + if (host != NULL) free(host); + if (instlist != NULL) free(instlist); + if (namelist != NULL) free(namelist); + if (pms.source != NULL) free(pms.source); + if (pms.metric != NULL) free(pms.metric); + if (pms.inst[0] != NULL) free(pms.inst[0]); + + if (abort) + goto abandon; + + continue; + } + + else { + QString msg = QString(); + msg.sprintf("Botch, state=%d", state); + err(E_CRIT, true, msg); + goto abandon; + } + + continue; + +abandon: + // giving up on the whole view specification + break; + + } + + if (!errmsg.isEmpty()) { + err(E_CRIT, true, errmsg); + } + + if (f != stdin) { + if (is_popen) + pclose(f); + else + fclose(f); + } + + if (_errors) + return false; + + if (ct != pmchart->tabWidget()->currentIndex()) // new Tabs added + pmchart->setActiveTab(ct, true); + + if ((Cflag == 0 || Cflag == 2) && cp != NULL) { + activeGroup->setupWorldView(); + pmchart->activeTab()->showGadgets(); + } + return true; + +noview: + errmsg = QString("Cannot open view file \""); + errmsg.append(_fname); + errmsg.append("\"\n"); + errmsg.append(strerror(errno)); + err(E_CRIT, false, errmsg); + return false; + +nopipe: + errmsg.sprintf("Cannot execute \"%s\"\n%s", _fname, strerror(errno)); + err(E_CRIT, false, errmsg); + return false; +} + +void SaveViewDialog::setGlobals(int width, int height, int points, int x, int y) +{ + _width = width; + _height = height; + _points = points; + _xpos = x; + _ypos = y; +} + +static void saveScheme(FILE *f, QString scheme) +{ + ColorScheme *cs = ColorScheme::findScheme(scheme); + int m; + + if (cs) { + fprintf(f, "scheme %s", (const char *)cs->name().toAscii()); + for (m = 0; m < cs->size(); m++) + fprintf(f, " %s", (const char *)cs->colorName(m).toAscii()); + fprintf(f, "\n\n"); + } +} + +void SaveViewDialog::saveChart(FILE *f, Chart *cp, bool hostDynamic) +{ + const char *s; + double ymin, ymax; + bool autoscale; + + fprintf(f, "chart"); + if (cp->title() != QString::null) + fprintf(f, " title \"%s\"", (const char*)cp->title().toAscii()); + switch (cp->style()) { + case Chart::LineStyle: + s = "plot"; + break; + case Chart::BarStyle: + s = "bar"; + break; + case Chart::StackStyle: + s ="stacking"; + break; + case Chart::AreaStyle: + s = "area"; + break; + case Chart::UtilisationStyle: + s = "utilization"; + break; + case Chart::EventStyle: + s = "event"; + break; + case Chart::NoStyle: + default: + s = "none"; + break; + } + fprintf(f, " style %s", s); + if (cp->style() != Chart::UtilisationStyle) { + cp->scale(&autoscale, &ymin, &ymax); + if (!autoscale) + fprintf(f, " scale %f %f", ymin, ymax); + } + if (!cp->legendVisible()) + fprintf(f, " legend off"); + if (!cp->antiAliasing()) + fprintf(f, " antialiasing off"); + fputc('\n', f); + for (int m = 0; m < cp->metricCount(); m++) { + QString legend; + if (cp->activeItem(m) == false) + continue; + fprintf(f, "\tplot"); + legend = cp->legend(m); + if (legend != QString::null) + fprintf(f, " legend \"%s\"", (const char *)legend.toAscii()); + fprintf(f, " color %s", (const char *)cp->color(m).name().toAscii()); + if (hostDynamic == false) + fprintf(f, " host %s", (const char *) + cp->metricContext(m)->source().host().toAscii()); + fprintf(f, " metric %s", (const char *) + cp->metricName(m).toAscii()); + if (cp->metric(m)->explicitInsts()) + fprintf(f, " instance \"%s\"", (const char*)cp->metricInstance(m).toAscii()); + fputc('\n', f); + } +} + +bool SaveViewDialog::saveView(QString file, bool hostDynamic, + bool sizeDynamic, bool allTabs, bool allCharts) +{ + FILE *f; + int c, t; + Tab *tab; + Gadget *gadget; + char *path = strdup((const char *)file.toAscii()); + QStringList schemes; + + if ((f = fopen(path, "w")) == NULL) + goto noview; + + fprintf(f, "#kmchart\nversion %d\n\n", K1); + if (sizeDynamic == false) { + fprintf(f, "global width %u\n", _width); + fprintf(f, "global height %u\n", _height); + fprintf(f, "global points %u\n", _points); + fprintf(f, "global xpos %u\n", _xpos); + fprintf(f, "global ypos %u\n", _ypos); + fprintf(f, "\n"); + } + + for (c = 0; c < pmchart->activeTab()->gadgetCount(); c++) { + gadget = pmchart->activeTab()->gadget(c); + if (gadget->scheme() == QString::null || + schemes.contains(gadget->scheme()) == true) + continue; + schemes.append(gadget->scheme()); + } + for (c = 0; c < schemes.size(); c++) + saveScheme(f, schemes.at(c)); + + if (allTabs) { + TabWidget *tabWidget = pmchart->tabWidget(); + for (t = 0; t < tabWidget->size(); t++) { + tab = tabWidget->at(t); + fprintf(f, "\ntab \"%s\"\n\n", + (const char *) tabWidget->tabText(t).toAscii()); + for (c = 0; c < tab->gadgetCount(); c++) + tab->gadget(c)->save(f, hostDynamic); + } + } + else { + tab = pmchart->activeTab(); + if (!allCharts) + tab->currentGadget()->save(f, hostDynamic); + else + for (c = 0; c < tab->gadgetCount(); c++) + tab->gadget(c)->save(f, hostDynamic); + } + + fflush(f); + fclose(f); + free (path); + return true; + +noview: + QString errmsg; + errmsg.sprintf("Cannot open \"%s\" for writing\n%s", path, strerror(errno)); + err(E_CRIT, false, errmsg); + free (path); + return false; +} diff --git a/src/pmchart/views/Apache b/src/pmchart/views/Apache new file mode 100644 index 0000000..5df9062 --- /dev/null +++ b/src/pmchart/views/Apache @@ -0,0 +1,8 @@ +#kmchart +version 1 + +chart title "Apache Hit Rate [%h]" style plot legend off + plot metric apache.total_accesses + +chart title "Apache Data Rate [%h]" style plot legend off + plot metric apache.total_kbytes diff --git a/src/pmchart/views/ApacheServer b/src/pmchart/views/ApacheServer new file mode 100644 index 0000000..10b01b9 --- /dev/null +++ b/src/pmchart/views/ApacheServer @@ -0,0 +1,38 @@ +#kmchart +version 1 + +# CPU view +chart title "CPU Utilization [%h]" style utilization + plot legend "User" color #2d2de2 metric kernel.all.cpu.user + plot legend "Sys" color #e71717 metric kernel.all.cpu.sys + optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice + optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr + optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total + optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal + plot legend "Idle" color #16d816 metric kernel.all.cpu.idle + +chart title "Average Load [%h]" style plot antialiasing off + plot legend "1 min" metric kernel.all.load instance "1 minute" + plot legend "# cpus" metric hinv.ncpu + +# Netbytes view +chart title "Network Interface Bytes [%h]" style stacking + plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface" + plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface" + +chart title "Busy/Idle servers" style plot antialiasing off + plot legend "Busy" metric apache.busy_servers + plot legend "Idle" metric apache.idle_servers + +chart style stacking + plot legend "Closing" color #ffff00 metric apache.sb_closing + plot legend "DNS" color #0000ff metric apache.sb_dns_lookup + plot legend "Finishing" color #ff0000 metric apache.sb_finishing + plot legend "Cleanup" color #008000 metric apache.sb_idle_cleanup + plot legend "Keepalive" color #ee82ee metric apache.sb_keepalive + plot legend "Logging" color #aa5500 metric apache.sb_logging + plot legend "Open" color #666666 metric apache.sb_open_slot + plot legend "Reading" color #aaff00 metric apache.sb_reading + plot legend "Starting" color #aa00ff metric apache.sb_starting + plot legend "Waiting" color #aaaa7f metric apache.sb_waiting + plot legend "Writing" color #ffff00 metric apache.sb_writing_reply diff --git a/src/pmchart/views/BusyCPU b/src/pmchart/views/BusyCPU new file mode 100755 index 0000000..90903da --- /dev/null +++ b/src/pmchart/views/BusyCPU @@ -0,0 +1,164 @@ +#!/bin/sh +# +# Dynamic kmchart view for the most busy current processes ... note +# this is a snapshot at the time the view is instantiated and does +# not track the busiest processes over time +# +# Busy here means CPU consumption +# + +# a token attempt to make this general +. /etc/pcp.env + +tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1 +trap "rm -rf $tmp; exit" 0 1 2 3 15 + +# bad stuff ... +# +_err() +{ + echo "Failed to fetch user mode CPU cycles metrics" >$tmp/msg + echo >>$tmp/msg + for f in $tmp/err.* + do + cat $f >>$tmp/msg + done + echo >>$tmp/msg + echo "Sorry, there is nothing to display." >>$tmp/msg + pmconfirm >/dev/null \ + -header "pmchart view construction failure" \ + -file $tmp/msg \ + -icon error \ + -B "OK" + exit +} + +# find top 10 consumers of user mode CPU cycles and generate a plot for each +# +# top(1) is ill-suited for use in a script, so we have to emulate this +# using PCP tools as follows: +# 1. get cummulative user mode CPU cycles for all processes +# 2. sleep 5 seconds +# 3. get cummulative user mode CPU cycles for all processes +# 4. compute delta user mode CPU cycles from 1. and 2. +# 5. sort and select top 10 processes +# + +# use $tmp/sed to remove this shell and its children from +# the list of busy processses +# +echo "/\[0*$$ /d" >$tmp/sed + +# progress notifier while we do our job +# +pmquery >/dev/null 2>&1 \ + -timeout 5 \ + -header "kmchart view construction" \ + -t "Finding top CPU burners, please wait 5 seconds ..." & +query=$! +echo "/\[0*$query /d" >>$tmp/sed + +# deal with alternative metric names ... +# +i=0 +fail=true +for metric in proc.psinfo.utime proc.psusage.utime +do + nval=`pmprobe -if $* $metric 2>&1 \ + | tee $tmp/err.$i \ + | $PCP_AWK_PROG '{print $2}'` + if [ "$nval" -gt 0 ] + then + fail=false + break + fi + i=`expr $i + 1` +done +if $fail +then + _err + #NOTREACHED +fi + +# note arguments from pmchart are nothing or -h host or -a archive +# +if pminfo -F $* $metric >$tmp/1 2>$tmp/err.a +then + sleep 5 + if pminfo -F $* $metric >$tmp/2 2>$tmp/err.b + then + : + else + _err + #NOTREACHED + fi +else + _err + #NOTREACHED +fi + +# get pid and user mode CPU cycles usage from lines like +# inst [8072 or "08072 rlogin gonzo.melbourne "] value 28 +# and turn them into this +# $tmp/1.list +# 8072 28 +# $tmp/2.list +# 8072 28 08072 rlogin gonzo.melbourne +# + +sed -f $tmp/sed $tmp/1 \ +| sed -n -e '/inst \[/{ +s/.*inst \[// +s/ or .* \([0-9][0-9]*\)$/ \1/p +}' \ +| sort >$tmp/1.list + +sed -n -e '/inst \[/{ +s/.*inst \[// +s/or "\(.*\)".* \([0-9][0-9]*\)$/\2 \1/p +}' $tmp/2 \ +| sort >$tmp/2.list + +#DEBUG# echo "First list ..." >/tmp/busy.debug +#DEBUG# cat $tmp/1.list >>/tmp/busy.debug +#DEBUG# echo "Second list ..." >>/tmp/busy.debug +#DEBUG# cat $tmp/2.list >>/tmp/busy.debug + +join $tmp/1.list $tmp/2.list \ +| $PCP_AWK_PROG ' +$3 > $2 { # this process has consumed some user mode CPU cycles + printf "%d",$3-$2 + for (i = 4; i <= NF; i++) printf " %s",$i + print"" + }' >$tmp/found + +if [ ! -s $tmp/found ] +then + # no processes qualify + # + pmconfirm >/dev/null \ + -header "pmchart view construction failure" \ + -t "No qualifying processes were found!" \ + -icon warning \ + -B "OK" + exit +fi + +# chart preamble +# +cat <<End-of-File +#pmchart +Version 2.0 host dynamic + +Chart Title "Top user mode CPU burners at `date +'%a %b %e %R'`" Style stacking +End-of-File + +# plot specifications, one per process +# + +sort -nr +0 -1 $tmp/found \ +| sed 10q \ +| while read cpuburn inst +do + echo " Plot Color #-cycle Host * Metric $metric Instance $inst" +done diff --git a/src/pmchart/views/CPU b/src/pmchart/views/CPU new file mode 100644 index 0000000..8724505 --- /dev/null +++ b/src/pmchart/views/CPU @@ -0,0 +1,11 @@ +#kmchart +version 1 + +chart title "CPU Utilization [%h]" style utilization + plot legend "User" color #2d2de2 metric kernel.all.cpu.user + plot legend "Kernel" color #e71717 metric kernel.all.cpu.sys + optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice + optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr + optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total + optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal + plot legend "Idle" color #16d816 metric kernel.all.cpu.idle diff --git a/src/pmchart/views/Cisco b/src/pmchart/views/Cisco new file mode 100644 index 0000000..fae3416 --- /dev/null +++ b/src/pmchart/views/Cisco @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Cisco Data Rate [%h]" style plot + plot legend "In %i" metric cisco.rate_in + plot legend "Out %i" metric cisco.rate_out diff --git a/src/pmchart/views/Disk b/src/pmchart/views/Disk new file mode 100644 index 0000000..a6da4d1 --- /dev/null +++ b/src/pmchart/views/Disk @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "IOPS over all Disks [%h]" style stacking + plot legend "Reads" color yellow metric disk.all.read + plot legend "Writes" color violet metric disk.all.write diff --git a/src/pmchart/views/Diskbytes b/src/pmchart/views/Diskbytes new file mode 100644 index 0000000..4dcadbe --- /dev/null +++ b/src/pmchart/views/Diskbytes @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Disk Throughput [%h]" style stacking + plot legend "Read rate" color yellow metric disk.all.read_bytes + plot legend "Write rate" color violet metric disk.all.write_bytes diff --git a/src/pmchart/views/ElasticsearchServer b/src/pmchart/views/ElasticsearchServer new file mode 100644 index 0000000..4372c8a --- /dev/null +++ b/src/pmchart/views/ElasticsearchServer @@ -0,0 +1,29 @@ +#kmchart +version 1 + +# CPU view +chart title "CPU Utilization [%h]" style utilization + plot legend "User" color #2d2de2 metric kernel.all.cpu.user + plot legend "Sys" color #e71717 metric kernel.all.cpu.sys + optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice + optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr + optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total + optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal + plot legend "Idle" color #16d816 metric kernel.all.cpu.idle + +chart title "Average Load [%h]" style plot antialiasing off + plot legend "1 min" metric kernel.all.load instance "1 minute" + plot legend "# cpus" metric hinv.ncpu + +# Query/Fetch per second +chart title "Searches" style plot antialiasing off + plot legend "Query" metric elasticsearch.search.all.total.search.query_total + plot legend "Fetch" metric elasticsearch.search.all.total.search.fetch_total + +# CPU usage per node +chart title "CPU %" style plot antialiasing off + plot legend "cpu %i" metric elasticsearch.nodes.process.cpu.percent + +# Doc count per node +chart title "Document" style plot antialiasing off + plot legend "docs %i" metric elasticsearch.nodes.indices.docs.count
\ No newline at end of file diff --git a/src/pmchart/views/Filesystem b/src/pmchart/views/Filesystem new file mode 100644 index 0000000..34c587c --- /dev/null +++ b/src/pmchart/views/Filesystem @@ -0,0 +1,5 @@ +#kmchart +version 1 + +chart title "File System Fullness % [%h]" style plot + plot legend "%i" metric filesys.full not-matching "/dev/shm|/dev$" diff --git a/src/pmchart/views/GNUmakefile b/src/pmchart/views/GNUmakefile new file mode 100644 index 0000000..1d6f3f3 --- /dev/null +++ b/src/pmchart/views/GNUmakefile @@ -0,0 +1,21 @@ +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs + +VIEWDIR = $(PCP_VAR_DIR)/config/pmchart +VIEWS = CPU Disk Diskbytes Loadavg NFS2 NFS3 Filesystem Memory Netbytes \ + Netpackets PMCD Syscalls Paging Overview Schemes Sockets Swap \ + ApacheServer ElasticsearchServer vCPU MemAvailable + +LSRCFILES = $(VIEWS) + +default build-me: + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 -d $(VIEWDIR) + $(INSTALL) -m 0444 $(VIEWS) $(VIEWDIR) + +default_pcp: default + +install_pcp: install diff --git a/src/pmchart/views/Loadavg b/src/pmchart/views/Loadavg new file mode 100644 index 0000000..8655205 --- /dev/null +++ b/src/pmchart/views/Loadavg @@ -0,0 +1,7 @@ +#kmchart +version 1 + +chart title "Load Average [%h]" style plot antialiasing off + plot title "1 min" metric kernel.all.load instance "1 minute" + plot title "5 min" metric kernel.all.load instance "5 minute" + plot title "15 min"metric kernel.all.load instance "15 minute" diff --git a/src/pmchart/views/MemAvailable b/src/pmchart/views/MemAvailable new file mode 100644 index 0000000..718a95b --- /dev/null +++ b/src/pmchart/views/MemAvailable @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Memory Available [%h]" style area + optional-plot color #00ff00 metric mem.util.available + plot color #3549ff metric mem.physmem diff --git a/src/pmchart/views/Memory b/src/pmchart/views/Memory new file mode 100644 index 0000000..3c54ad6 --- /dev/null +++ b/src/pmchart/views/Memory @@ -0,0 +1,10 @@ +#kmchart +version 1 + +chart title "Real Memory Usage [%h]" style stacking + # Linux + optional-plot color #9cffab metric mem.util.cached + optional-plot color #fe68ad metric mem.util.bufmem + optional-plot color #ffae2c metric mem.util.other + # all + plot color #00ff00 metric mem.util.free diff --git a/src/pmchart/views/NFS2 b/src/pmchart/views/NFS2 new file mode 100644 index 0000000..1d442a7 --- /dev/null +++ b/src/pmchart/views/NFS2 @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "NFS2 Calls [%h]" style plot + plot legend "client" metric nfs.client.calls + plot legend "server" metric nfs.server.calls diff --git a/src/pmchart/views/NFS3 b/src/pmchart/views/NFS3 new file mode 100644 index 0000000..807935e --- /dev/null +++ b/src/pmchart/views/NFS3 @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "NFS3 Calls [%h]" style plot + plot legend "client" metric nfs3.client.calls + plot legend "server" metric nfs3.server.calls diff --git a/src/pmchart/views/Netbytes b/src/pmchart/views/Netbytes new file mode 100644 index 0000000..451faea --- /dev/null +++ b/src/pmchart/views/Netbytes @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Network Interface Bytes [%h]" style stacking + plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface" + plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface" diff --git a/src/pmchart/views/Netpackets b/src/pmchart/views/Netpackets new file mode 100644 index 0000000..a492b55 --- /dev/null +++ b/src/pmchart/views/Netpackets @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Network Interface Packets [%h]" style stacking + plot legend "in %i" metric network.interface.in.packets not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface" + plot legend "out %i" metric network.interface.out.packets not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface" diff --git a/src/pmchart/views/Overview b/src/pmchart/views/Overview new file mode 100644 index 0000000..a70ed40 --- /dev/null +++ b/src/pmchart/views/Overview @@ -0,0 +1,32 @@ +#kmchart +version 1 + +# CPU view +chart title "CPU Utilization [%h]" style utilization + plot legend "User" color #2d2de2 metric kernel.all.cpu.user + plot legend "Sys" color #e71717 metric kernel.all.cpu.sys + optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice + optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr + optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total + optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal + plot legend "Idle" color #16d816 metric kernel.all.cpu.idle + +chart title "Average Load [%h]" style plot antialiasing off + plot legend "1 min" metric kernel.all.load instance "1 minute" + plot legend "# cpus" metric hinv.ncpu + +# Disk view +chart title "IOPS over all Disks [%h]" style stacking + plot legend "Reads" color yellow metric disk.all.read + plot legend "Writes" color violet metric disk.all.write + +# Netbytes view +chart title "Network Interface Bytes [%h]" style stacking + plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface" + plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface" + +chart title "Real Memory Usage [%h]" style stacking + optional-plot color #9cffab metric mem.util.cached + optional-plot color #fe68ad metric mem.util.bufmem + optional-plot color #ffae2c metric mem.util.other + plot color #00ff00 metric mem.util.free diff --git a/src/pmchart/views/PMCD b/src/pmchart/views/PMCD new file mode 100644 index 0000000..bd82c56 --- /dev/null +++ b/src/pmchart/views/PMCD @@ -0,0 +1,14 @@ +#kmchart +version 1 + +chart title "Packets for PMCD [%h]" style stacking + plot legend "In" metric pmcd.pdu_in.total + plot legend "Out" metric pmcd.pdu_out.total + +chart title "CPU Time for PMCD and DSO PMDAs [%h]" style stacking + optional-plot legend "User" color #2d2de2 metric proc.psinfo.utime matching "[\\/](pmcd |pmcd$)" + optional-plot legend "Sys" color #e71717 metric proc.psinfo.stime matching "[\\/](pmcd |pmcd$)" + +chart title "CPU Time for Other PMDAs [%h]" style stacking legend off + optional-plot color #2d2de2 metric proc.psinfo.utime matching "[\\/]pmda[a-z]" + optional-plot color #e71717 metric proc.psinfo.stime matching "[\\/]pmda[a-z]" diff --git a/src/pmchart/views/Paging b/src/pmchart/views/Paging new file mode 100644 index 0000000..39a397c --- /dev/null +++ b/src/pmchart/views/Paging @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "VM Activity - Page Migrations [%h]" style plot + plot legend "Out" metric swap.pagesout + plot legend "In" metric swap.pagesin diff --git a/src/pmchart/views/Schemes b/src/pmchart/views/Schemes new file mode 100644 index 0000000..b4c98d3 --- /dev/null +++ b/src/pmchart/views/Schemes @@ -0,0 +1,6 @@ +#kmchart +version 1 +scheme Fire #f2b600 #ede200 #d21005 #e4d904 #f35241 +scheme Nature #b4ff3a #d2954e #069806 #8b5f07 #08ed00 +scheme Neon #ff34f9 #fffb00 #1df0ff #12ff01 #ffc800 +scheme Ocean #f5dd85 #aaf5cb #4790f5 #bdcfcc #12cfa0 diff --git a/src/pmchart/views/Sendmail b/src/pmchart/views/Sendmail new file mode 100644 index 0000000..faf1f59 --- /dev/null +++ b/src/pmchart/views/Sendmail @@ -0,0 +1,10 @@ +#kmchart +version 1 + +chart title "Sendmail Bytes [%h]" style plot + plot legend "Recv" color #137bfe metric sendmail.total.bytes_from + plot legend "Sent" color #fefa1a metric sendmail.total.bytes_to + +chart title "Sendmail Mail Items [%h]" style plot + plot legend "Recv" color #1e1cfe metric sendmail.total.msgs_from + plot legend "Sent" color #fe9913 metric sendmail.total.msgs_to diff --git a/src/pmchart/views/ShpingCPU b/src/pmchart/views/ShpingCPU new file mode 100644 index 0000000..a39e808 --- /dev/null +++ b/src/pmchart/views/ShpingCPU @@ -0,0 +1,8 @@ +#kmchart +version 1 + +chart title "User CPU Time for shping Commands [%h]" style plot + plot legend "%i" metric shping.time.cpu_usr + +chart title "System CPU Time for shping Commands [%h]" style plot + plot legend "%i" metric shping.time.cpu_sys diff --git a/src/pmchart/views/ShpingElapsed b/src/pmchart/views/ShpingElapsed new file mode 100644 index 0000000..b83d3c0 --- /dev/null +++ b/src/pmchart/views/ShpingElapsed @@ -0,0 +1,5 @@ +#kmchart +version 1 + +chart title "shping Response Time [%h]" style plot + plot legend "%i" metric shping.time.real diff --git a/src/pmchart/views/Sockets b/src/pmchart/views/Sockets new file mode 100644 index 0000000..177b3aa --- /dev/null +++ b/src/pmchart/views/Sockets @@ -0,0 +1,7 @@ +#kmchart +version 1 + +chart title "Sockets in Use [%h]" style plot + optional-plot legend "TCP" metric network.sockstat.tcp.inuse + optional-plot legend "UDP" metric network.sockstat.udp.inuse + optional-plot legend "Raw" metric network.sockstat.raw.inuse diff --git a/src/pmchart/views/Swap b/src/pmchart/views/Swap new file mode 100644 index 0000000..542636c --- /dev/null +++ b/src/pmchart/views/Swap @@ -0,0 +1,6 @@ +#kmchart +version 1 + +chart title "Logical Swap Allocation [%h]" style stacking + optional-plot legend "Free" color #16e116 metric swap.free + optional-plot legend "Used" color #e71717 metric swap.used diff --git a/src/pmchart/views/Syscalls b/src/pmchart/views/Syscalls new file mode 100644 index 0000000..bd260b3 --- /dev/null +++ b/src/pmchart/views/Syscalls @@ -0,0 +1,9 @@ +#kmchart +version 1 + +chart title "System Calls [%h]" style plot + optional-plot legend "All" metric kernel.all.syscall + optional-plot legend "exec" metric kernel.all.sysexec + optional-plot legend "fork" metric kernel.all.sysfork + optional-plot legend "read" metric kernel.all.sysread + optional-plot legend "write" metric kernel.all.syswrite diff --git a/src/pmchart/views/vCPU b/src/pmchart/views/vCPU new file mode 100644 index 0000000..a45680f --- /dev/null +++ b/src/pmchart/views/vCPU @@ -0,0 +1,12 @@ +#kmchart +version 1 + +chart title "CPU and Guest CPU Utilization [%h]" style utilization + plot legend "User" color #2d2de2 metric kernel.all.cpu.vuser + plot legend "Kernel" color #e71717 metric kernel.all.cpu.sys + optional-plot legend "Guest" color #666666 metric kernel.all.cpu.guest + optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice + optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr + optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total + optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal + plot legend "Idle" color #16d816 metric kernel.all.cpu.idle |