/*************************************************************************** * * * copyright : (C) 2007 The University of Toronto * * netterfield@astro.utoronto.ca * * * * 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. * * * ***************************************************************************/ #include "view.h" #include "scene.h" #include "viewitem.h" #include "layoutboxitem.h" #include "mainwindow.h" #include "application.h" #include "applicationsettings.h" #include "viewdialog.h" #include "viewgridlayout.h" #include "document.h" #include "plotitemmanager.h" #include "plotitem.h" #include "plotaxis.h" #include "dialogdefaults.h" #include "datacollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Kst { View::View() : QGraphicsView(kstApp->mainWindow()) { init(); } View::View(QWidget* parent) : QGraphicsView(parent) { init(); } void View::init() { _viewMode = Data; _mouseMode = Default; _layoutBoxItem = 0; _gridSpacing = QSizeF(20,20); _showGrid = false; _snapToGridHorizontal = false; _snapToGridVertical = false; _plotBordersDirty = false; _printing = false; _dataMode = false; _fontRescale = 1.0; _childMaximized = false; _undoStack = new QUndoStack(this); _referenceFontSizeToView = true; setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setScene(new Scene(this)); scene()->setSceneRect(rect()); scene()->installEventFilter(this); setInteractive(true); setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); setFrameStyle(QFrame::NoFrame); setContextMenuPolicy(Qt::DefaultContextMenu); setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); setViewport(0); connect(ApplicationSettings::self(), SIGNAL(modified()), this, SLOT(updateSettings())); loadSettings(); _editAction = new QAction(tr("Edit"), this); //_editAction->setShortcut(Qt::Key_E); // registerShortcut(_editAction); connect(_editAction, SIGNAL(triggered()), this, SLOT(edit())); _autoLayoutAction = new QAction(tr("Automatic"), this); //_autoLayoutAction->setShortcut(Qt::Key_U); //registerShortcut(_autoLayoutAction); connect(_autoLayoutAction, SIGNAL(triggered()), this, SLOT(createUnprotectedLayout())); _protectedLayoutAction = new QAction(tr("Protect Layout"), this); connect(_protectedLayoutAction, SIGNAL(triggered()), this, SLOT(createLayout())); _customLayoutAction = new QAction(tr("Columns"), this); connect(_customLayoutAction, SIGNAL(triggered()), this, SLOT(createCustomLayout())); connect(this, SIGNAL(viewModeChanged(View::ViewMode)), PlotItemManager::self(), SLOT(clearFocusedPlots())); applyDialogDefaultsFill(); } View::~View() { // PlotItems are QGraphicsItems and managed by Qt's graphic view delete _undoStack; delete _layoutBoxItem; } QUndoStack *View::undoStack() const { return _undoStack; } ViewItem *View::selectedViewItem() const { QList items = scene()->selectedItems(); if (items.isEmpty()) return 0; //return the first item return dynamic_cast(items.first()); } void View::applyDialogDefaultsFill() { //set the brush QBrush brush; brush = dialogDefaultsBrush(staticDefaultsGroupName()); setBackgroundBrush(brush); } void View::save(QXmlStreamWriter &xml) { QList items = scene()->items(Qt::AscendingOrder); xml.writeAttribute("width", QVariant(sceneRect().width()).toString()); xml.writeAttribute("height", QVariant(sceneRect().height()).toString()); xml.writeAttribute("color", backgroundBrush().color().name()); xml.writeAttribute("style", QVariant((int)backgroundBrush().style()).toString()); if (backgroundBrush().gradient()) { QString stopList; foreach(const QGradientStop &stop, backgroundBrush().gradient()->stops()) { qreal point = (qreal)stop.first; QColor color = (QColor)stop.second; stopList += QString::number(point); stopList += ','; stopList += color.name(); stopList += ','; } xml.writeAttribute("gradient", stopList); } foreach(QGraphicsItem* viewItem, items) { if (!viewItem->parentItem()) { dynamic_cast(viewItem)->save(xml); } } } View::ViewMode View::viewMode() const { return _viewMode; } void View::setViewMode(ViewMode mode) { ViewMode oldMode = _viewMode; _viewMode = mode; emit viewModeChanged(oldMode); } View::MouseMode View::mouseMode() const { return _mouseMode; } void View::setMouseMode(MouseMode mode) { //Clear the creation polygons if we're currently //in Create mouse mode. MouseMode oldMode = _mouseMode; if (oldMode == Create) { _creationPolygonPress.clear(); _creationPolygonRelease.clear(); _creationPolygonMove.clear(); } _mouseMode = mode; if (_mouseMode != Create) { setCursor(Qt::ArrowCursor); setDragMode(QGraphicsView::RubberBandDrag); } else { setDragMode(QGraphicsView::NoDrag); } emit mouseModeChanged(oldMode); } void View::setZoomOnly(ZoomOnlyMode zoomMode) { QList plots = PlotItemManager::plotsForView(this); foreach (PlotItem* plot, plots) { plot->setZoomOnly(zoomMode); // should we overload setZoomOnly? QList renderers = plot->renderItems(); foreach (PlotRenderItem* renderer, renderers) { renderer->setZoomOnly(zoomMode); } } } QPolygonF View::creationPolygon(CreationEvents events) const { if (events == View::MousePress) return _creationPolygonPress; if (events == View::MouseRelease) return _creationPolygonRelease; if (events == View::MouseMove) return _creationPolygonMove; return QPolygonF(); } void View::setShowGrid(bool showGrid) { //Don't repaint unless absolutely necessary if (_showGrid == showGrid) return; _showGrid = showGrid; invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } void View::setGridSpacing(const QSizeF &gridSpacing) { //Don't repaint unless absolutely necessary if (_gridSpacing == gridSpacing) return; _gridSpacing = gridSpacing; invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } QPointF View::snapPoint(const QPointF &point) { qreal x = point.x(); qreal y = point.y(); if (_snapToGridHorizontal && gridSpacing().width() > 0) x -= fmod(point.x(), gridSpacing().width()); if (_snapToGridVertical && gridSpacing().height() > 0) y -= fmod(point.y(), gridSpacing().height()); return QPointF(x, y); } bool View::event(QEvent *event) { if (event->type() == QEvent::Shortcut) { QShortcutEvent *e = static_cast(event); QPointF mousePos = mapToScene(mapFromGlobal(QCursor::pos())); QList list = scene()->items(mousePos); if (list.isEmpty()) { if (e->key() == _editAction->shortcut()) { _editAction->trigger(); } } else { foreach (QGraphicsItem *item, list) { ViewItem *viewItem = dynamic_cast(item); if (!viewItem) continue; // Qt bug: http://bugreports.qt.nokia.com/browse/QTBUG-8188 // also see PlotRenderItem::hoverEnterEvent, there is a workaround. //if (viewItem && viewItem->tryShortcut(e->key())) { if (viewItem && viewItem->hasFocus() && viewItem->tryShortcut(e->key())) { return true; } } } } // } else if (event->type() == QEvent::MouseButtonPress) { // QMouseEvent *e = static_cast(event); // if (e->button() == Qt::RightButton) { // //contextMenuEvent(); // //return true; // } // } return QGraphicsView::event(event); } bool View::eventFilter(QObject *obj, QEvent *event) { if (obj != scene() || _mouseMode != Create) return QGraphicsView::eventFilter(obj, event); switch (event->type()) { case QEvent::GraphicsSceneMousePress: { QGraphicsSceneMouseEvent *e = static_cast(event); if (e->button() == Qt::LeftButton) { _creationPolygonPress << snapPoint(e->buttonDownScenePos(Qt::LeftButton)); emit creationPolygonChanged(MousePress); return true; //filter this otherwise something can grab our mouse... } } case QEvent::GraphicsSceneMouseRelease: { QGraphicsSceneMouseEvent *e = static_cast(event); if (e->button() != Qt::LeftButton) break; _creationPolygonRelease << snapPoint(e->scenePos()); emit creationPolygonChanged(MouseRelease); break; } case QEvent::GraphicsSceneMouseMove: { QGraphicsSceneMouseEvent *e = static_cast(event); _creationPolygonMove << snapPoint(e->scenePos()); emit creationPolygonChanged(MouseMove); break; } case QEvent::KeyPress: { QKeyEvent *e = static_cast(event); if (e->key() == Qt::Key_Escape) { emit creationPolygonChanged(EscapeEvent); } } default: break; } return QGraphicsView::eventFilter(obj, event); } void View::createCustomLayout() { bool ok; int default_cols = qMax(1,int(sqrt((qreal)Data::self()->plotList().count()))); int columns = QInputDialog::getInt(this, tr("Kst: Column Layout"), tr("Layout in columns in order of creation.\nSelect number of columns:"),default_cols, 1, 15, 1, &ok); if (ok) { createLayout(false, columns); } } void View::createLayout(bool preserve, int columns) { PlotItemManager::self()->clearFocusedPlots(); LayoutCommand *layout = new LayoutCommand(new LayoutBoxItem(this)); layout->createLayout(preserve, columns); if (_layoutBoxItem) { _layoutBoxItem->setEnabled(false); delete _layoutBoxItem; _layoutBoxItem = 0; } } void View::appendToLayout(CurvePlacement::Layout layout, ViewItem* item, int columns) { AppendLayoutCommand *appendlayout = new AppendLayoutCommand(new LayoutBoxItem(this)); appendlayout->appendLayout(layout, item, columns); if (_layoutBoxItem) { LayoutBoxItem *layoutBox = _layoutBoxItem; _layoutBoxItem->setEnabled(false); delete layoutBox; } } void View::processResize(QSize size) { setPlotBordersDirty(true); if (size != sceneRect().size()) { QRectF oldSceneRect = sceneRect(); viewport()->resize(size); setSceneRect(QRectF(0.0, 0.0, size.width() - 1.0, size.height() - 1.0)); setCacheMode(QGraphicsView::CacheBackground); foreach (QGraphicsItem *item, items()) { if (item->parentItem()) continue; ViewItem *viewItem = dynamic_cast(item); Q_ASSERT(viewItem); viewItem->updateChildGeometry(oldSceneRect, sceneRect()); } } } void View::resizeEvent(QResizeEvent *event) { if (event) { QGraphicsView::resizeEvent(event); } setPlotBordersDirty(true); if (size() != sceneRect().size()) { QRectF oldSceneRect = sceneRect(); setSceneRect(QRectF(0.0, 0.0, width() - 1.0, height() - 1.0)); setCacheMode(QGraphicsView::CacheBackground); foreach (QGraphicsItem *item, items()) { if (item->parentItem()) continue; ViewItem *viewItem = dynamic_cast(item); Q_ASSERT(viewItem); viewItem->updateChildGeometry(oldSceneRect, sceneRect()); } } } void View::forceChildResize(QRectF oldRect, QRectF newRect) { foreach (QGraphicsItem *item, items()) { if (item->parentItem()) continue; ViewItem *viewItem = dynamic_cast(item); Q_ASSERT(viewItem); viewItem->updateChildGeometry(oldRect, newRect); } } void View::drawBackground(QPainter *painter, const QRectF &rect) { if (isPrinting()) { QBrush currentBrush(backgroundBrush()); setBackgroundBrush(Qt::white); QGraphicsView::drawBackground(painter, rect); setBackgroundBrush(currentBrush); return; } QGraphicsView::drawBackground(painter, rect); if (!showGrid()) return; painter->save(); QColor c = Qt::gray; c.setAlphaF(c.alphaF() * 0.2); painter->setPen(c); // http://techbase.kde.org/Development/Tutorials/Graphics/Performance says // setOpacity turns off hardware acceleration. // they suggest setting alpha in the color instead, which // we have done, above. //painter->setOpacity(0.2); const QRectF r = sceneRect(); qreal spacing = gridSpacing().width(); //FIXME We should probably only draw those lines that intercept rect //vertical lines qreal x = r.left() + spacing; while (x < r.right() && spacing > 0) { QLineF line(QPointF(x, r.top()), QPointF(x, r.bottom())); painter->drawLine(line); x += spacing; } spacing = gridSpacing().height(); //horizontal lines qreal y = r.top() + spacing; while (y < r.bottom() && spacing > 0) { QLineF line(QPointF(r.left(), y), QPointF(r.right(), y)); painter->drawLine(line); y += spacing; } painter->restore(); } void View::updateSettings() { setShowGrid(ApplicationSettings::self()->showGrid()); setSnapToGrid(ApplicationSettings::self()->snapToGrid()); setGridSpacing(QSizeF(ApplicationSettings::self()->gridHorizontalSpacing(), ApplicationSettings::self()->gridVerticalSpacing())); //FIXME: only if reference size changed... forceChildResize(sceneRect(), sceneRect()); //FIXME: should trigger a paint. } void View::loadSettings() { setShowGrid(ApplicationSettings::self()->showGrid()); setSnapToGrid(ApplicationSettings::self()->snapToGrid()); setGridSpacing(QSizeF(ApplicationSettings::self()->gridHorizontalSpacing(), ApplicationSettings::self()->gridVerticalSpacing())); } void View::updateChildGeometry(const QRectF &oldSceneRect) { foreach (QGraphicsItem *item, items()) { if (item->parentItem()) continue; ViewItem *viewItem = dynamic_cast(item); Q_ASSERT(viewItem); viewItem->updateChildGeometry(oldSceneRect, sceneRect()); } } // returns the size of a font, rescaled by the size of the paint device // relative to the reference window size. qreal View::scaledFontSize(qreal pointSize, const QPaintDevice &p) const { qreal fontSize; qreal height_inch = qreal(height())/qreal(p.logicalDpiY()); qreal width_inch = qreal(width())/qreal(p.logicalDpiX()); qreal scale = 2.54*(width_inch + height_inch)/(ApplicationSettings::self()->referenceViewHeightCM() + ApplicationSettings::self()->referenceViewWidthCM()); fontSize = pointSize * scale * _fontRescale; /* if (!_printing) { fontSize = (qreal)(height() + width()) / (ApplicationSettings::self()->referenceViewHeight() + ApplicationSettings::self()->referenceViewWidth()) * pointSize * _fontRescale; } else { fontSize = (qreal)(p.heightMM() + p.widthMM()) * 0.1 / (ApplicationSettings::self()->referenceViewHeightCM() + ApplicationSettings::self()->referenceViewWidthCM()) * pointSize * _fontRescale; } */ if (fontSize < ApplicationSettings::self()->minimumFontSize()) { fontSize = ApplicationSettings::self()->minimumFontSize(); } if (fontSize < 0) return 1; #ifdef Q_OS_WIN // On Windows more and more memory gets allocated when fontsize // is too detailed, somewhere some strange caching happens. const qreal fontPrecision = 4; fontSize = floor(fontSize * fontPrecision + 0.5) / fontPrecision; #endif return fontSize; } // Set the font sizes of all plots in the view to a default size, scaled // by the default global font scale, and the application minimum font scale. double View::resetPlotFontSizes(PlotItem* plot) { QList plots; plots.append(plot); return resetPlotFontSizes(plots); } double View::resetPlotFontSizes(QList new_plots) { QList plots(new_plots); plots.append(PlotItemManager::self()->plotsForView(this)); qreal pointSize = dialogDefaults().value("plot/globalFontScale",16.0).toDouble(); // the 4 in the line below is a magic number that impedes scaling until you // have more than a couple of rows/columns. A 1 would make it scale more // aggressively. The behavior looks pretty good to me with 4. qreal count = qMax(plots.count()-1, 1); //qreal newPointSize = qMax(pointSize/qSqrt(count) , ApplicationSettings::self()->minimumFontSize()); qreal newPointSize = (pointSize - ApplicationSettings::self()->minimumFontSize())/qSqrt(count) + ApplicationSettings::self()->minimumFontSize(); if (newPointSizeminimumFontSize()); foreach(PlotItem* plotItem, plots) { plotItem->setGlobalFontScale(pointSize); plotItem->rightLabelDetails()->setFontScale(pointSize); plotItem->leftLabelDetails()->setFontScale(pointSize); plotItem->bottomLabelDetails()->setFontScale(pointSize); plotItem->topLabelDetails()->setFontScale(pointSize); plotItem->numberLabelDetails()->setFontScale(pointSize); if (plotItem->showLegend()) { plotItem->legend()->setFontScale(legendPointSize); } } return pointSize; } // copy the font settings of the first plotItem in the view into // plot, then break. void View::configurePlotFontDefaults(PlotItem *plot) { if (plot) { foreach(PlotItem* plotItem, PlotItemManager::self()->plotsForView(this)) { if (plot != plotItem) { plot->setGlobalFont(plotItem->globalFont()); plot->setGlobalFontScale(plotItem->globalFontScale()); plot->setGlobalFontColor(plotItem->globalFontColor()); plot->leftLabelDetails()->setFontUseGlobal(plotItem->leftLabelDetails()->fontUseGlobal()); plot->leftLabelDetails()->setFont(plotItem->leftLabelDetails()->font()); plot->leftLabelDetails()->setFontScale(plotItem->leftLabelDetails()->fontScale()); plot->leftLabelDetails()->setFontColor(plotItem->leftLabelDetails()->fontColor()); plot->rightLabelDetails()->setFontUseGlobal(plotItem->rightLabelDetails()->fontUseGlobal()); plot->rightLabelDetails()->setFont(plotItem->rightLabelDetails()->font()); plot->rightLabelDetails()->setFontScale(plotItem->rightLabelDetails()->fontScale()); plot->rightLabelDetails()->setFontColor(plotItem->rightLabelDetails()->fontColor()); plot->topLabelDetails()->setFontUseGlobal(plotItem->topLabelDetails()->fontUseGlobal()); plot->topLabelDetails()->setFont(plotItem->topLabelDetails()->font()); plot->topLabelDetails()->setFontScale(plotItem->topLabelDetails()->fontScale()); plot->topLabelDetails()->setFontColor(plotItem->topLabelDetails()->fontColor()); plot->bottomLabelDetails()->setFontUseGlobal(plotItem->bottomLabelDetails()->fontUseGlobal()); plot->bottomLabelDetails()->setFont(plotItem->bottomLabelDetails()->font()); plot->bottomLabelDetails()->setFontScale(plotItem->bottomLabelDetails()->fontScale()); plot->bottomLabelDetails()->setFontColor(plotItem->bottomLabelDetails()->fontColor()); plot->numberLabelDetails()->setFontUseGlobal(plotItem->numberLabelDetails()->fontUseGlobal()); plot->numberLabelDetails()->setFont(plotItem->numberLabelDetails()->font()); plot->numberLabelDetails()->setFontScale(plotItem->numberLabelDetails()->fontScale()); plot->numberLabelDetails()->setFontColor(plotItem->numberLabelDetails()->fontColor()); plot->setItemPen(plotItem->storedPen()); plot->setItemBrush(plotItem->brush()); plot->xAxis()->copyProperties(plotItem->xAxis()); plot->yAxis()->copyProperties(plotItem->yAxis()); break; } } } } void View::viewContextMenuEvent() { QMenu menu; addTitle(&menu); menu.addAction(_editAction); QMenu layoutMenu; layoutMenu.setTitle(tr("Cleanup Layout")); layoutMenu.addAction(_autoLayoutAction); //layoutMenu.addAction(_protectedLayoutAction); layoutMenu.addAction(_customLayoutAction); menu.addMenu(&layoutMenu); menu.exec(QCursor::pos()); } void View::addTitle(QMenu *menu) const { QWidgetAction *action = new QWidgetAction(menu); action->setEnabled(false); QLabel *label = new QLabel(tr("View Menu"), menu); label->setAlignment(Qt::AlignCenter); label->setStyleSheet("QLabel {" "border-bottom: 2px solid lightGray;" "font: bold large;" "padding: 3px;" "margin: 1px;" "}"); action->setDefaultWidget(label); menu->addAction(action); } void View::edit() { ViewDialog *editDialog = new ViewDialog(this, kstApp->mainWindow()); editDialog->show(); } void View::viewChanged() { //kstApp->mainWindow()->document()->setChanged(true); } QList View::layoutableViewItems() { QList graphics_items = scene()->items(); QList layoutable_view_items; foreach(QGraphicsItem* graphics_item, graphics_items) { ViewItem *item = dynamic_cast(graphics_item); if (item && (!item->hasStaticGeometry()) && item->isVisible() && item->allowsLayout() && ((dynamic_cast(item->parentViewItem())!=0) || (!item->parentViewItem()))) { layoutable_view_items.append(item); } } qSort(layoutable_view_items.begin(), layoutable_view_items.end(), shortNameLessThan); return layoutable_view_items; } } // vim: ts=2 sw=2 et