Simon De Backer 71abaf92cb fix deprecated warning int QWheelEvent::delta() const
Use angleDelta() instead

manhattanLength()
Returns the sum of the absolute values of x() and y(),
traditionally known as the "Manhattan length" of the vector
from the origin to the point.

This class was introduced in Qt 5.5
2020-06-12 19:52:50 +02:00

555 lines
16 KiB
C++

/*
Copyright 2006-2019 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "elementview.h"
#include "qetelementeditor.h"
#include "editorcommands.h"
#include "qetapp.h"
/**
Constructeur
@param scene ElementScene visualisee par cette ElementView
@param parent QWidget parent de cette ElementView
*/
ElementView::ElementView(ElementScene *scene, QWidget *parent) :
QGraphicsView(scene, parent),
m_scene(scene),
offset_paste_count_(0)
{
grabGesture(Qt::PinchGesture);
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setInteractive(true);
setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
zoomReset();
connect(m_scene, SIGNAL(pasteAreaDefined(const QRectF &)), this, SLOT(pasteAreaDefined(const QRectF &)));
connect(m_scene, SIGNAL(needZoomFit()), this, SLOT(zoomFit()));
}
/// Destructeur
ElementView::~ElementView() {
}
/// @return l'ElementScene visualisee par cette ElementView
ElementScene *ElementView::scene() const {
return(m_scene);
}
/**
@return le rectangle de l'element visualise par cet ElementView
*/
QRectF ElementView::viewedSceneRect() const {
// recupere la taille du widget viewport
QSize viewport_size = viewport() -> size();
// recupere la transformation viewport -> scene
QTransform view_to_scene = viewportTransform().inverted();
// mappe le coin superieur gauche et le coin inferieur droit de la viewport sur la scene
QPointF scene_left_top = view_to_scene.map(QPointF(0.0, 0.0));
QPointF scene_right_bottom = view_to_scene.map(QPointF(viewport_size.width(), viewport_size.height()));
// en deduit le rectangle visualise par la scene
return(QRectF(scene_left_top, scene_right_bottom));
}
/**
Definit l'ElementScene visualisee par cette ElementView
@param s l'ElementScene visualisee par cette ElementView
*/
void ElementView::setScene(ElementScene *s) {
QGraphicsView::setScene(s);
m_scene = s;
}
/**
Set the Diagram in visualisation mode
*/
void ElementView::setVisualisationMode() {
setDragMode(ScrollHandDrag);
setInteractive(false);
emit(modeChanged());
}
/**
Set the Diagram in Selection mode
*/
void ElementView::setSelectionMode() {
setDragMode(RubberBandDrag);
setInteractive(true);
emit(modeChanged());
}
/**
Agrandit le schema (+33% = inverse des -25 % de zoomMoins())
*/
void ElementView::zoomIn() {
adjustSceneRect();
scale(4.0/3.0, 4.0/3.0);
}
/**
Retrecit le schema (-25% = inverse des +33 % de zoomPlus())
*/
void ElementView::zoomOut() {
adjustSceneRect();
scale(0.75, 0.75);
}
/**
Agrandit le schema avec le trackpad
*/
void ElementView::zoomInSlowly() {
scale(1.02, 1.02);
}
/**
Retrecit le schema avec le trackpad
*/
void ElementView::zoomOutSlowly() {
scale(0.98, 0.98);
}
/**
Agrandit ou rectrecit le schema de facon a ce que tous les elements du
schema soient visibles a l'ecran. S'il n'y a aucun element sur le schema,
le zoom est reinitialise
*/
void ElementView::zoomFit() {
resetSceneRect();
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
/**
Reinitialise le zoom
*/
void ElementView::zoomReset() {
resetSceneRect();
resetMatrix();
scale(4.0, 4.0);
}
/**
* @brief ElementView::adjustSceneRect
* Adjust the scenRect, so that he include all primitives of element
* plus the viewport of the scene with a margin of 1/3 of herself
*/
void ElementView::adjustSceneRect() {
QRectF esgr = m_scene -> elementSceneGeometricRect();
QRectF vpbr = mapToScene(this -> viewport()->rect()).boundingRect();
QRectF new_scene_rect = vpbr.adjusted(-vpbr.width()/3, -vpbr.height()/3, vpbr.width()/3, vpbr.height()/3);
setSceneRect(new_scene_rect.united(esgr));
}
/**
* @brief ElementView::resetSceneRect
* reset le sceneRect (zone du schéma visualisée par l'ElementView) afin que
* celui-ci inclut uniquement les primitives de l'élément dessiné.
*/
void ElementView::resetSceneRect() {
setSceneRect(m_scene -> elementSceneGeometricRect());
}
/**
Gere le fait de couper la selection = l'exporter en XML dans le
presse-papier puis la supprimer.
*/
void ElementView::cut() {
// delegue cette action a la scene
m_scene -> cut();
offset_paste_count_ = -1;
}
/**
Gere le fait de copier la selection = l'exporter en XML dans le
presse-papier.
*/
void ElementView::copy() {
// delegue cette action a la scene
m_scene -> copy();
offset_paste_count_ = 0;
}
/**
Gere le fait de coller le contenu du presse-papier = l'importer dans
l'element. Cette methode examine le contenu du presse-papier. Si celui-ci
semble avoir ete copie depuis cet element, il est colle a cote de sa zone
d'origine ; s'il est recolle, il sera colle un cran a cote de la zone deja
recollee, etc.
Sinon, cette methode demande a l'utilisateur de definir la zone ou le
collage devra s'effectuer.
@see pasteAreaDefined(const QRectF &)
*/
void ElementView::paste() {
QString clipboard_text = QApplication::clipboard() -> text();
if (clipboard_text.isEmpty()) return;
QDomDocument document_xml;
if (!document_xml.setContent(clipboard_text)) return;
if (m_scene -> wasCopiedFromThisElement(clipboard_text)) {
// copier/coller avec decalage
pasteWithOffset(document_xml);
} else {
// copier/coller par choix de la zone de collage
QRectF pasted_content_bounding_rect = m_scene -> boundingRectFromXml(document_xml);
if (pasted_content_bounding_rect.isEmpty()) return;
to_paste_in_area_ = clipboard_text;
getPasteArea(pasted_content_bounding_rect);
}
}
/**
Colle le contenu du presse-papier en demandant systematiquement a
l'utilisateur de choisir une zone de collage
*/
void ElementView::pasteInArea() {
QString clipboard_text = QApplication::clipboard() -> text();
if (clipboard_text.isEmpty()) return;
QDomDocument document_xml;
if (!document_xml.setContent(clipboard_text)) return;
QRectF pasted_content_bounding_rect = m_scene -> boundingRectFromXml(document_xml);
if (pasted_content_bounding_rect.isEmpty()) return;
// copier/coller par choix de la zone de collage
to_paste_in_area_ = clipboard_text;
getPasteArea(pasted_content_bounding_rect);
}
/**
Gere le fait de coller le contenu du presse-papier = l'importer dans
l'element. Cette methode examine le contenu du presse-papier. Si celui-ci
est exploitable, elle le colle a la position passee en parametre.
@see pasteAreaDefined(const QRectF &)
@param position Point de collage
*/
ElementContent ElementView::paste(const QPointF &position) {
QString clipboard_text = QApplication::clipboard() -> text();
if (clipboard_text.isEmpty()) return(ElementContent());
QDomDocument document_xml;
if (!document_xml.setContent(clipboard_text)) return(ElementContent());
// objet pour recuperer le contenu ajoute au schema par le coller
return(paste(document_xml, position));
}
/**
@param to_paste Rectangle englobant les parties a coller
*/
void ElementView::getPasteArea(const QRectF &to_paste) {
// on copie le rectangle fourni - on s'interesse a ses dimensions, pas a sa position
QRectF used_rect(to_paste);
// on lui attribue pour centre l'origine du repere
if (underMouse()) {
used_rect.moveCenter(mapToScene(mapFromGlobal(QCursor::pos())));
} else {
used_rect.moveCenter(QPointF(0.0, 0.0));
}
m_scene -> getPasteArea(used_rect);
}
/**
Slot appele lorsque la scene annonce avoir defini une zone de collage
@param target_rect Rectangle cible pour le collage
*/
ElementContent ElementView::pasteAreaDefined(const QRectF &target_rect) {
if (to_paste_in_area_.isEmpty()) return(ElementContent());
QDomDocument xml_document;
if (!xml_document.setContent(to_paste_in_area_)) {
to_paste_in_area_.clear();
return(ElementContent());
} else {
return(paste(xml_document, target_rect.topLeft()));
}
}
/**
Colle le document XML xml_document a la position pos
@param xml_document Document XML a coller
@param pos Coin superieur gauche du rectangle cible
*/
ElementContent ElementView::paste(const QDomDocument &xml_document, const QPointF &pos) {
// objet pour recuperer le contenu ajoute au schema par le coller
ElementContent content_pasted;
m_scene -> fromXml(xml_document, pos, false, &content_pasted);
// si quelque chose a effectivement ete ajoute au schema, on cree un objet d'annulation
if (content_pasted.count()) {
m_scene -> clearSelection();
PastePartsCommand *undo_object = new PastePartsCommand(this, content_pasted);
m_scene -> undoStack().push(undo_object);
}
return(content_pasted);
}
/**
Colle le document XML xml_document a la position pos
@param xml_document Document XML a coller
*/
ElementContent ElementView::pasteWithOffset(const QDomDocument &xml_document) {
// objet pour recuperer le contenu ajoute au schema par le coller
ElementContent content_pasted;
// rectangle source
QRectF pasted_content_bounding_rect = m_scene -> boundingRectFromXml(xml_document);
if (pasted_content_bounding_rect.isEmpty()) return(content_pasted);
// copier/coller avec decalage
QRectF final_pasted_content_bounding_rect;
++ offset_paste_count_;
if (!offset_paste_count_) {
// the pasted content was cut
start_top_left_corner_ = pasted_content_bounding_rect.topLeft();
final_pasted_content_bounding_rect = pasted_content_bounding_rect;
}
else {
// the pasted content was copied
if (offset_paste_count_ == 1) {
start_top_left_corner_ = pasted_content_bounding_rect.topLeft();
} else {
pasted_content_bounding_rect.moveTopLeft(start_top_left_corner_);
}
// on applique le decalage qui convient
final_pasted_content_bounding_rect = applyMovement(
pasted_content_bounding_rect,
QETElementEditor::pasteOffset()
);
}
QPointF old_start_top_left_corner = start_top_left_corner_;
start_top_left_corner_ = final_pasted_content_bounding_rect.topLeft();
m_scene -> fromXml(xml_document, start_top_left_corner_, false, &content_pasted);
// si quelque chose a effectivement ete ajoute au schema, on cree un objet d'annulation
if (content_pasted.count()) {
m_scene -> clearSelection();
PastePartsCommand *undo_object = new PastePartsCommand(this, content_pasted);
undo_object -> setOffset(offset_paste_count_ - 1, old_start_top_left_corner, offset_paste_count_, start_top_left_corner_);
m_scene -> undoStack().push(undo_object);
}
return(content_pasted);
}
/**
Gere les clics sur la vue - permet de coller lorsaue l'on enfonce le bouton
du milieu de la souris.
@param e QMouseEvent decrivant l'evenement souris
*/
void ElementView::mousePressEvent(QMouseEvent *e) {
if (e->buttons() == Qt::MidButton)
{
setCursor( (Qt::ClosedHandCursor));
reference_view_ = e->pos();
}
else
QGraphicsView::mousePressEvent(e);
}
/**
* @brief ElementView::mouseMoveEvent
* Manage the event move mouse
*/
void ElementView::mouseMoveEvent(QMouseEvent *e) {
if (e->buttons() == Qt::MidButton)
{
QScrollBar *h = horizontalScrollBar();
QScrollBar *v = verticalScrollBar();
QPointF pos = reference_view_ - e -> pos();
reference_view_ = e -> pos();
h -> setValue(h -> value() + pos.x());
v -> setValue(v -> value() + pos.y());
}
else
QGraphicsView::mouseMoveEvent(e);
}
/**
* @brief ElementView::mouseReleaseEvent
* Manage event release click mouse
*/
void ElementView::mouseReleaseEvent(QMouseEvent *e) {
if (e -> button() == Qt::MidButton) {
setCursor(Qt::ArrowCursor);
adjustSceneRect();
return;
}
QGraphicsView::mouseReleaseEvent(e);
}
/**
* @brief ElementView::gestures
* @return
*/
bool ElementView::gestures() const
{
QSettings settings;
return(settings.value("diagramview/gestures", false).toBool());
}
/**
* @brief ElementView::wheelEvent
* @param e
*/
void ElementView::wheelEvent(QWheelEvent *e) {
//Zoom and scrolling
if ( gestures() ) {
if (e -> modifiers() & Qt::ControlModifier)
e -> angleDelta().manhattanLength() > 0 ? zoomInSlowly() : zoomOutSlowly();
else
QGraphicsView::wheelEvent(e);
} else {
e -> angleDelta().manhattanLength() > 0 ? zoomIn(): zoomOut();
}
}
/**
Gere les evenements de la ElementView
@param e Evenement
*/
bool ElementView::event(QEvent *e) {
// By default touch events are converted to mouse events. So
// after this event we will get a mouse event also but we want
// to handle touch events as gestures only. So we need this safeguard
// to block mouse events that are actually generated from touch.
if (e->type() == QEvent::Gesture)
return gestureEvent(static_cast<QGestureEvent *>(e));
return(QGraphicsView::event(e));
}
/**
* Utilise le pincement du trackpad pour zoomer
* @brief ElementView::gestureEvent
* @param event
* @return
*/
bool ElementView::gestureEvent(QGestureEvent *event){
if (QGesture *gesture = event->gesture(Qt::PinchGesture)) {
QPinchGesture *pinch = static_cast<QPinchGesture *>(gesture);
if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged){
qreal value = gesture->property("scaleFactor").toReal();
if (value > 1){
zoomInSlowly();
}else{
zoomOutSlowly();
}
}
}
return true;
}
/**
Dessine l'arriere-plan de l'editeur, cad la grille.
@param p Le QPainter a utiliser pour dessiner
@param r Le rectangle de la zone a dessiner
*/
void ElementView::drawBackground(QPainter *p, const QRectF &r) {
p -> save();
// desactive tout antialiasing, sauf pour le texte
p -> setRenderHint(QPainter::Antialiasing, false);
p -> setRenderHint(QPainter::TextAntialiasing, true);
p -> setRenderHint(QPainter::SmoothPixmapTransform, false);
// dessine un fond blanc
p -> setPen(Qt::NoPen);
p -> setBrush(Qt::white);
p -> drawRect(r);
// determine le zoom en cours
qreal zoom_factor = matrix().m11();
// choisit la granularite de la grille en fonction du zoom en cours
int drawn_x_grid = 1;//scene_ -> xGrid();
int drawn_y_grid = 1;//scene_ -> yGrid();
bool draw_grid = true;
bool draw_cross = false;
if (zoom_factor < (4.0/3.0)) { //< no grid
draw_grid = false;
} else if (zoom_factor < 4.0) { //< grid 10*10
drawn_x_grid *= 10;
drawn_y_grid *= 10;
}else if (zoom_factor < 8.0) { //< grid 5*5
drawn_x_grid *= 5;
drawn_y_grid *= 5;
draw_cross = true;
} else if (zoom_factor < 10.0) { //< grid 2*2
drawn_x_grid *= 2;
drawn_y_grid *= 2;
draw_cross = true;
} else { //< grid 1*1
draw_cross = true;
}
m_scene->setGrid(drawn_x_grid, drawn_y_grid);
if (draw_grid) {
// draw the dot of the grid
QPen pen(Qt::black);
pen.setCosmetic(true);
p -> setPen(pen);
p -> setBrush(Qt::NoBrush);
qreal limite_x = r.x() + r.width();
qreal limite_y = r.y() + r.height();
int g_x = (int)ceil(r.x());
while (g_x % drawn_x_grid) ++ g_x;
int g_y = (int)ceil(r.y());
while (g_y % drawn_y_grid) ++ g_y;
for (int gx = g_x ; gx < limite_x ; gx += drawn_x_grid) {
for (int gy = g_y ; gy < limite_y ; gy += drawn_y_grid) {
if (draw_cross) {
if (!(gx % 10) && !(gy % 10)) {
p -> drawLine(QLineF(gx - 0.25, gy, gx + 0.25, gy));
p -> drawLine(QLineF(gx, gy - 0.25, gx, gy + 0.25));
} else {
p -> drawPoint(gx, gy);
}
} else {
p -> drawPoint(gx, gy);
}
}
}
}
p -> restore();
}
/**
Applique le decalage offset dans le sens movement au rectangle start
@param start rectangle a decaler
@param movement Orientation du decalage a appliquer
@param offset Decalage a appliquer
*/
QRectF ElementView::applyMovement(const QRectF &start, const QPointF &offset) {
// calcule le decalage a appliquer a partir de l'offset
QPointF final_offset;
final_offset.rx() = start.width() + offset.x();
// applique le decalage ainsi calcule
return(start.translated(final_offset));
}