/* * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) * * Copyright (C) 2013 Open Education Foundation * * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour * l'Education Numérique en Afrique (GIP ENA) * * This file is part of OpenBoard. * * OpenBoard 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, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * * OpenBoard 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 OpenBoard. If not, see <http://www.gnu.org/licenses/>. */ #include "UBSelectionFrame.h" #include <QtGui> #include "domain/UBItem.h" #include "domain/UBGraphicsItemZLevelUndoCommand.h" #include "domain/UBGraphicsGroupContainerItem.h" #include "board/UBBoardController.h" #include "core/UBSettings.h" #include "core/UBApplication.h" #include "gui/UBResources.h" #include "gui/UBMainWindow.h" #include "core/UBApplication.h" #include "board/UBBoardView.h" #include "board/UBDrawingController.h" UBSelectionFrame::UBSelectionFrame() : mThickness(UBSettings::settings()->objectFrameWidth) , mAntiscaleRatio(1.0) , mRotationAngle(0) , mDeleteButton(0) , mDuplicateButton(0) , mZOrderUpButton(0) , mZOrderDownButton(0) , mGroupButton(0) , mRotateButton(0) { setLocalBrush(QBrush(UBSettings::paletteColor)); setPen(Qt::NoPen); setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Control)); setData(UBGraphicsItemData::itemLayerType, QVariant(itemLayerType::SelectionFrame)); //Necessary to set if we want z value to be assigned correctly setFlags(QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable | ItemIsMovable); connect(UBApplication::boardController, SIGNAL(zoomChanged(qreal)), this, SLOT(onZoomChanged(qreal))); onZoomChanged(UBApplication::boardController->currentZoom()); } void UBSelectionFrame::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); QPainterPath path; QRectF shRect = option->rect; path.addRoundedRect(shRect, adjThickness() / 2, adjThickness() / 2); if (rect().width() > 1 && rect().height() > 1) { QPainterPath extruded; extruded.addRect(shRect.adjusted(adjThickness(), adjThickness(), (adjThickness() * -1), (adjThickness() * -1))); path = path.subtracted(extruded); } painter->fillPath(path, mLocalBrush); } QRectF UBSelectionFrame::boundingRect() const { return rect().adjusted(-adjThickness(), -adjThickness(), adjThickness(), adjThickness()); } QPainterPath UBSelectionFrame::shape() const { QPainterPath resShape; QRectF ownRect = rect(); QRectF shRect = ownRect.adjusted(-adjThickness(), -adjThickness(), adjThickness(), adjThickness()); resShape.addRoundedRect(shRect, adjThickness() / 2, adjThickness() / 2); if (rect().width() > 1 && rect().height() > 1) { QPainterPath extruded; extruded.addRect(ownRect); resShape = resShape.subtracted(extruded); } return resShape; } void UBSelectionFrame::setEnclosedItems(const QList<QGraphicsItem*> pGraphicsItems) { mButtons.clear(); mButtons.append(mDeleteButton); mRotationAngle = 0; QRegion resultRegion; UBGraphicsFlags resultFlags; mEnclosedtems.clear(); // If at least one of the enclosed items is locked, the entire selection is // considered to be locked. mIsLocked = false; foreach (QGraphicsItem *nextItem, pGraphicsItems) { UBGraphicsItemDelegate *nextDelegate = UBGraphicsItem::Delegate(nextItem); if (nextDelegate) { mIsLocked = (mIsLocked || nextDelegate->isLocked()); mEnclosedtems.append(nextDelegate); resultRegion |= nextItem->boundingRegion(nextItem->sceneTransform()); resultFlags |= nextDelegate->ubflags(); } } QRectF resultRect = resultRegion.boundingRect(); setRect(resultRect); mButtons = buttonsForFlags(resultFlags); placeButtons(); if (resultRect.isEmpty()) { hide(); } if (mIsLocked) { QColor baseColor = UBSettings::paletteColor; baseColor.setAlphaF(baseColor.alphaF() / 3); setLocalBrush(QBrush(baseColor)); } else setLocalBrush(QBrush(UBSettings::paletteColor)); } void UBSelectionFrame::updateRect() { QRegion resultRegion; foreach (UBGraphicsItemDelegate *curDelegateItem, mEnclosedtems) { resultRegion |= curDelegateItem->delegated()->boundingRegion(curDelegateItem->delegated()->sceneTransform()); } QRectF result = resultRegion.boundingRect(); setRect(result); placeButtons(); if (result.isEmpty()) { setVisible(false); } } void UBSelectionFrame::updateScale() { setScale(-UBApplication::boardController->currentZoom()); } void UBSelectionFrame::mousePressEvent(QGraphicsSceneMouseEvent *event) { mPressedPos = mLastMovedPos = event->pos(); mLastTranslateOffset = QPointF(); mRotationAngle = 0; if (scene()->itemAt(event->scenePos(), transform()) == mRotateButton) { mOperationMode = om_rotating; } else { mOperationMode = om_moving; } } void UBSelectionFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (mIsLocked) return; QPointF dp = event->pos() - mPressedPos; QPointF rotCenter = mapToScene(rect().center()); foreach (UBGraphicsItemDelegate *curDelegate, mEnclosedtems) { switch (static_cast<int>(mOperationMode)) { case om_moving : { QGraphicsItem *item = curDelegate->delegated(); QTransform ownTransform = item->transform(); QTransform dTransform( ownTransform.m11() , ownTransform.m12() , ownTransform.m13() , ownTransform.m21() , ownTransform.m22() , ownTransform.m23() , ownTransform.m31() + (dp - mLastTranslateOffset).x() , ownTransform.m32() + (dp - mLastTranslateOffset).y() , ownTransform.m33() ); item->setTransform(dTransform); } break; case om_rotating : { QLineF startLine(sceneBoundingRect().center(), event->lastScenePos()); QLineF currentLine(sceneBoundingRect().center(), event->scenePos()); qreal dAngle = startLine.angleTo(currentLine); QGraphicsItem *item = curDelegate->delegated(); QTransform ownTransform = item->transform(); QPointF nextRotCenter = item->mapFromScene(rotCenter); qreal cntrX = nextRotCenter.x(); qreal cntrY = nextRotCenter.y(); ownTransform.translate(cntrX, cntrY); mRotationAngle -= dAngle; ownTransform.rotate(-dAngle); ownTransform.translate(-cntrX, -cntrY); item->update(); item->setTransform(ownTransform, false); qDebug() << "curAngle" << mRotationAngle; } break; } } updateRect(); mLastMovedPos = event->pos(); mLastTranslateOffset = dp; } void UBSelectionFrame::mouseReleaseEvent(QGraphicsSceneMouseEvent */*event*/) { mPressedPos = mLastMovedPos = mLastTranslateOffset = QPointF(); if (mOperationMode == om_moving || mOperationMode == om_rotating) { UBApplication::undoStack->beginMacro(UBSettings::undoCommandTransactionName); foreach (UBGraphicsItemDelegate *d, mEnclosedtems) { d->commitUndoStep(); } UBApplication::undoStack->endMacro(); } mOperationMode = om_idle; } void UBSelectionFrame::onZoomChanged(qreal pZoom) { mAntiscaleRatio = 1 / (UBApplication::boardController->systemScaleFactor() * pZoom); } void UBSelectionFrame::remove() { UBApplication::undoStack->beginMacro(UBSettings::undoCommandTransactionName); foreach (UBGraphicsItemDelegate *d, mEnclosedtems) { d->remove(true); } UBApplication::undoStack->endMacro(); updateRect(); } static bool sortByZ(UBGraphicsItemDelegate* A, UBGraphicsItemDelegate* B) { return (A->delegated()->data(UBGraphicsItemData::ItemOwnZValue).toReal() < B->delegated()->data(UBGraphicsItemData::ItemOwnZValue).toReal() ); } void UBSelectionFrame::duplicate() { UBApplication::undoStack->beginMacro(UBSettings::undoCommandTransactionName); // The mEnclosedtems list items are in order of selection. To avoid losing their // relative zValues when duplicating, we re-order the list. std::sort(mEnclosedtems.begin(), mEnclosedtems.end(), sortByZ); foreach (UBGraphicsItemDelegate *d, mEnclosedtems) { d->duplicate(); } UBApplication::undoStack->endMacro(); updateRect(); } void UBSelectionFrame::increaseZlevelUp() { QList<QGraphicsItem*> selItems = sortedByZ(scene()->selectedItems()); QList<QGraphicsItem*>::iterator itemIt = selItems.end(); while(itemIt != selItems.begin()){ itemIt--; QGraphicsItem* item = *itemIt; ubscene()->changeZLevelTo(item, UBZLayerController::up); } addSelectionUndo(selItems, UBZLayerController::up); } void UBSelectionFrame::increaseZlevelTop() { QList<QGraphicsItem*> selItems = sortedByZ(scene()->selectedItems()); foreach (QGraphicsItem *item, selItems) { ubscene()->changeZLevelTo(item, UBZLayerController::top); } addSelectionUndo(selItems, UBZLayerController::top); } void UBSelectionFrame::increaseZlevelDown() { QList<QGraphicsItem*> selItems = sortedByZ(scene()->selectedItems()); foreach (QGraphicsItem *item, selItems) { ubscene()->changeZLevelTo(item, UBZLayerController::down); } addSelectionUndo(selItems, UBZLayerController::down); } void UBSelectionFrame::increaseZlevelBottom() { QList<QGraphicsItem*> selItems = sortedByZ(scene()->selectedItems()); QListIterator<QGraphicsItem*> iter(selItems); iter.toBack(); while (iter.hasPrevious()) { ubscene()->changeZLevelTo(iter.previous(), UBZLayerController::bottom); } addSelectionUndo(selItems, UBZLayerController::bottom); } void UBSelectionFrame::groupItems() { UBGraphicsGroupContainerItem *groupItem = ubscene()->createGroup(enclosedGraphicsItems()); groupItem->setSelected(true); UBDrawingController::drawingController()->setStylusTool(UBStylusTool::Selector); qDebug() << "Grouping items"; } void UBSelectionFrame::addSelectionUndo(QList<QGraphicsItem*> items, UBZLayerController::moveDestination dest){ if(!items.empty()){ qreal topItemLevel = items.at(0)->data(UBGraphicsItemData::ItemOwnZValue).toReal(); UBGraphicsItemZLevelUndoCommand* cmd = new UBGraphicsItemZLevelUndoCommand(ubscene(), items, topItemLevel, dest); UBApplication::undoStack->push(cmd); } } void UBSelectionFrame::translateItem(QGraphicsItem */*item*/, const QPointF &/*translatePoint*/) { } void UBSelectionFrame::placeButtons() { if (!mButtons.count()) { return; } QTransform tr; tr.scale(mAntiscaleRatio, mAntiscaleRatio); mDeleteButton->setParentItem(this); mDeleteButton->setTransform(tr); QRectF frRect = boundingRect(); qreal topX = frRect.left() - mDeleteButton->renderer()->viewBox().width() * mAntiscaleRatio / 2; qreal topY = frRect.top() - mDeleteButton->renderer()->viewBox().height() * mAntiscaleRatio / 2; qreal bottomX = topX; qreal bottomY = frRect.bottom() - mDeleteButton->renderer()->viewBox().height() * mAntiscaleRatio / 2; mDeleteButton->setPos(topX, topY); mDeleteButton->show(); int i = 1, j = 0, k = 0; while ((i + j + k) < mButtons.size()) { DelegateButton* button = mButtons[i + j]; if (button->getSection() == Qt::TopLeftSection) { button->setParentItem(this); button->setPos(topX + (i++ * 1.6 * adjThickness()), topY); button->setTransform(tr); } else if (button->getSection() == Qt::BottomLeftSection) { button->setParentItem(this); button->setPos(bottomX + (++j * 1.6 * adjThickness()), bottomY); button->setTransform(tr); } else if (button->getSection() == Qt::NoSection) { button->setParentItem(this); placeExceptionButton(button, tr); k++; } else { ++k; } button->show(); } } void UBSelectionFrame::placeExceptionButton(DelegateButton *pButton, QTransform pTransform) { QRectF frRect = boundingRect(); if (pButton == mRotateButton) { qreal topX = frRect.right() - (mRotateButton->renderer()->viewBox().width()) * mAntiscaleRatio - 5; qreal topY = frRect.top() + 5; mRotateButton->setPos(topX, topY); mRotateButton->setTransform(pTransform); } } void UBSelectionFrame::clearButtons() { foreach (DelegateButton *b, mButtons) { b->setParentItem(0); b->hide(); } mButtons.clear(); } inline UBGraphicsScene *UBSelectionFrame::ubscene() { return qobject_cast<UBGraphicsScene*>(scene()); } void UBSelectionFrame::setCursorFromAngle(QString angle) { QWidget *controlViewport = UBApplication::boardController->controlView()->viewport(); QSize cursorSize(45,30); QImage mask_img(cursorSize, QImage::Format_Mono); mask_img.fill(0xff); QPainter mask_ptr(&mask_img); mask_ptr.setBrush( QBrush( QColor(0, 0, 0) ) ); mask_ptr.drawRoundedRect(0,0, cursorSize.width()-1, cursorSize.height()-1, 6, 6); QBitmap bmpMask = QBitmap::fromImage(mask_img); QPixmap pixCursor(cursorSize); pixCursor.fill(QColor(Qt::white)); QPainter painter(&pixCursor); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setBrush(QBrush(Qt::white)); painter.setPen(QPen(QColor(Qt::black))); painter.drawRoundedRect(1,1,cursorSize.width()-2,cursorSize.height()-2,6,6); painter.setFont(QFont("Arial", 10)); painter.drawText(1,1,cursorSize.width(),cursorSize.height(), Qt::AlignCenter, angle.append(QChar(176))); painter.end(); pixCursor.setMask(bmpMask); controlViewport->setCursor(pixCursor); } QList<QGraphicsItem*> UBSelectionFrame::sortedByZ(const QList<QGraphicsItem *> &pItems) { //select only items wiht the same z-level as item's one and push it to sortedItems QMultiMap // It means: keep only the selected items and remove the selection frame from the list QMultiMap<qreal, QGraphicsItem*> sortedItems; foreach (QGraphicsItem *tmpItem, pItems) { if (tmpItem->type() == Type) { continue; } sortedItems.insert(tmpItem->data(UBGraphicsItemData::ItemOwnZValue).toReal(), tmpItem); } return sortedItems.values(); } QList<DelegateButton*> UBSelectionFrame::buttonsForFlags(UBGraphicsFlags fls) { QList<DelegateButton*> result; if (!mDeleteButton) { mDeleteButton = new DelegateButton(":/images/close.svg", this, 0, Qt::TopLeftSection); connect(mDeleteButton, SIGNAL(clicked()), this, SLOT(remove())); } result << mDeleteButton; if (fls | GF_DUPLICATION_ENABLED) { if (!mDuplicateButton) { mDuplicateButton = new DelegateButton(":/images/duplicate.svg", this, 0, Qt::TopLeftSection); connect(mDuplicateButton, SIGNAL(clicked(bool)), this, SLOT(duplicate())); } result << mDuplicateButton; } if (mEnclosedtems.count() >= 1) { if (!mGroupButton) { mGroupButton = new DelegateButton(":/images/groupItems.svg", this, 0, Qt::TopLeftSection); mGroupButton->setShowProgressIndicator(false); connect(mGroupButton, SIGNAL(clicked()), this, SLOT(groupItems())); } result << mGroupButton; } if (fls | GF_ZORDER_MANIPULATIONS_ALLOWED) { if (!mZOrderUpButton) { mZOrderUpButton = new DelegateButton(":/images/z_layer_up.svg", this, 0, Qt::BottomLeftSection); mZOrderUpButton->setShowProgressIndicator(true); connect(mZOrderUpButton, SIGNAL(clicked()), this, SLOT(increaseZlevelUp())); connect(mZOrderUpButton, SIGNAL(longClicked()), this, SLOT(increaseZlevelTop())); } if (!mZOrderDownButton) { mZOrderDownButton = new DelegateButton(":/images/z_layer_down.svg", this, 0, Qt::BottomLeftSection); mZOrderDownButton->setShowProgressIndicator(true); connect(mZOrderDownButton, SIGNAL(clicked()), this, SLOT(increaseZlevelDown())); connect(mZOrderDownButton, SIGNAL(longClicked()), this, SLOT(increaseZlevelBottom())); } result << mZOrderUpButton; result << mZOrderDownButton; } if (fls | GF_REVOLVABLE) { if (!mRotateButton) { mRotateButton = new DelegateButton(":/images/rotate.svg", this, 0, Qt::NoSection); mRotateButton->setCursor(UBResources::resources()->rotateCursor); mRotateButton->setShowProgressIndicator(false); mRotateButton->setTransparentToMouseEvent(true); } result << mRotateButton; } return result; } QList<QGraphicsItem*> UBSelectionFrame::enclosedGraphicsItems() { QList<QGraphicsItem*> result; foreach (UBGraphicsItemDelegate *d, mEnclosedtems) { result << d->delegated(); } return result; }