/** * @RDE_HEADER@ * Interface-Implementation of LocationDisplay * $Id$ * $HeadURL$ * $Author$ */ // Own #include "pixel_based_textbox_handler_impl.h" #include "map_impl.h" #include #include // ESRI #include // This file is created by RME, not ESRI #include #include // Qt #include #include #include #include #include #include #include #include namespace SituationMap { //----------------------------------------------------------------------------- // PBTB_ID output operator: //----------------------------------------------------------------------------- std::ostream &operator<< (std::ostream &stream, const PBTB_ID &value) { stream << value.toString(); return stream; } //----------------------------------------------------------------------------- // class //----------------------------------------------------------------------------- class PixelBasedTextBoxHandlerImpl::Private { public: Private() = default; ~Private() = default; // ESRI data: // Own data: SituationMap::MapImpl *m_mapImpl{nullptr}; QVector> m_tbDataCont; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // class PixelBasedTextBoxHandlerImpl //----------------------------------------------------------------------------- PixelBasedTextBoxHandlerImpl::PixelBasedTextBoxHandlerImpl(SituationMap::MapImpl *mapImpl) : m_logger(getLogger("PixelBasedTextBoxHandlerImpl")) , d(new Private) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; d->m_mapImpl = mapImpl; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } PixelBasedTextBoxHandlerImpl::~PixelBasedTextBoxHandlerImpl() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; delete d; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } QTextEdit *PixelBasedTextBoxHandlerImpl::createTextBox(PBTB_ID &pbtbIdRef, const PixelTextBoxData &ptbd, bool enableGeometryModifications) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, (((pbtbId=" << pbtbIdRef.toString() << "))) , ptbd=" << ptbd.toDebugString() << ", enableGeometryModifications=" << enableGeometryModifications; if (!d->m_mapImpl) return nullptr; QSharedPointer tbData = getCreateTbData(pbtbIdRef); // pbtbId may have changed, write back to caller: pbtbIdRef = tbData->m_pbtbId; // backgroundColor is special (not with QTextEdit), so update tbData here: if (ptbd.m_backgroundColor.isValid()) tbData->m_ptbd.m_backgroundColor = ptbd.m_backgroundColor; else tbData->m_ptbd.m_backgroundColor = Qt::white; TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; if (!guiDataRef.m_textEdit) { QTextEditWithDoubleClick *myTextEdit = new QTextEditWithDoubleClick(); guiDataRef.m_textEdit = myTextEdit; guiDataRef.m_textEdit->setObjectName(QStringLiteral("WidgetOverlay_QTextEdit")); guiDataRef.m_textEdit->setReadOnly(true); guiDataRef.m_textEdit->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); if (ptbd.m_size.isValid()) guiDataRef.m_textEdit->setGeometry(0, 0, ptbd.m_size.width(), ptbd.m_size.height()); if (enableGeometryModifications) { connect(myTextEdit, &QTextEditWithDoubleClick::doubleClickEvent, this, [this] (const PBTB_ID &pbtbId) { emit pixelBasedTextBoxDoubleClickEvent(pbtbId); }); } // It looks like: // GraphicsItem <-> ProxyWidget <-> Widget/TextEdit // (QGraphicsItem is a baseclass of both GraphicsItem as well as ProxyWidget) // GraphicsItem becomes parent of ProxyWidget // Then TextEdit is added via setWidget to ProxyWidget // Now both are connected, TextEdit in front of GraphicsItem which ist little bit bigger // GraphicsItem is movable, GI an TE are connected to each other... // 10.05.2023, Norbert Thoden // (((Idea: Maybe ProxyWidget should be parent of GraphicsItem // to put GraphicsItem on top of TextEdit???))) QGraphicsRectItemWithEvents *grItem = new QGraphicsRectItemWithEvents(0, 0, 0, 0); // x,y == rel position, size will be set later grItem->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); grItem->setCursor(Qt::SizeAllCursor); if (enableGeometryModifications) { grItem->activatePositionChangedEvents(); connect(grItem, &QGraphicsRectItemWithEvents::positionChangedFinishedEvent, this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); } guiDataRef.m_handle_or_proxyControl = grItem; guiDataRef.m_proxy = new QGraphicsProxyWidget(guiDataRef.m_handle_or_proxyControl); // parent is important for cleanup! guiDataRef.m_proxy->setObjectName(QStringLiteral("ProxyWidgetOverlay_QTextEdit")); guiDataRef.m_proxy->setProperty(PBTB_ID_PROPERTY_NAME, tbData->m_pbtbId.toString()); // maybe helpful in future guiDataRef.m_proxy->setWidget(guiDataRef.m_textEdit); guiDataRef.m_proxy->setPos(QPoint(0, 0)); // relative position to GraphicsItem !? guiDataRef.m_proxy->setOpacity(0.99); // avoid white on white guiDataRef.m_handle_or_proxyControl->setPen(QPen(Qt::gray)); guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(Qt::gray)); if (enableGeometryModifications) guiDataRef.m_handle_or_proxyControl->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); d->m_mapImpl->getQGraphicsView()->scene()->addItem(guiDataRef.m_handle_or_proxyControl); if (enableGeometryModifications) { // SizeGrip, removeBtn : QSizeGripWithEvent *sizeGrip = new QSizeGripWithEvent(tbData->m_pbtbId, guiDataRef.m_textEdit); QHBoxLayout *layout = new QHBoxLayout(guiDataRef.m_textEdit); layout->setContentsMargins(0, 0, 0, 0); QPushButton *btn = new QPushButton(guiDataRef.m_textEdit); btn->setIcon(QIcon(QStringLiteral("icon:bgv_view_close.svg"))); connect(btn, &QPushButton::clicked, this, [this, pbtbIdRef](bool) { emit pixelBasedTextBoxRemoveRequestedEvent(pbtbIdRef); }); layout->addWidget(btn, 1, Qt::AlignRight | Qt::AlignBottom); layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom); // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen // pass geometryChanged via signal-signal-connection: connect(sizeGrip, &QSizeGripWithEvent::geometryChangedFinishedEvent, this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); // : SizeGrip, removeBtn } // Geometry : tbData->onGeometryChanged(); if (!tbData->connectGeometryChanges()) ERROR_LOG(m_logger) << LOGFUNCTION << " connectGeometryChanges failed!"; // // [GUEZE-2947] Undo,Redo fuer PixelBasedTextBoxen // // pass geometryChanged via signal-signal-connection: // connect(tbData.data(), &TB_Data::geometryChanged, // this, &PixelBasedTextBoxHandler::pixelBasedTextBoxGeometryChanged, Qt::UniqueConnection); // : Geometry } // // update GUI: // // update text guiDataRef.m_textEdit->setText(ptbd.m_htmlText); if (guiDataRef.m_textEdit->toPlainText().isEmpty()) { const QString sansSerif = QLatin1String("Sans Serif"); QFont textFont = guiDataRef.m_textEdit->font(); textFont.setFamily(sansSerif); textFont.setFamilies({sansSerif}); guiDataRef.m_textEdit->setFont(textFont); } // update backgroundColor const QColor &bgCol = ptbd.m_backgroundColor; if (bgCol.isValid()) { QString styleSheet = QString(QStringLiteral("QTextEdit { background-color: rgb(%1, %2, %3) }")).arg(bgCol.red()).arg(bgCol.green()).arg(bgCol.blue()); guiDataRef.m_textEdit->setStyleSheet(styleSheet); } // update geometry and size: guiDataRef.m_handle_or_proxyControl->setPos(ptbd.m_position); if (ptbd.m_size.isValid()) guiDataRef.m_textEdit->setGeometry(0, 0, ptbd.m_size.width(), ptbd.m_size.height()); TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbIdRef; return guiDataRef.m_textEdit.data(); } QTextEdit *PixelBasedTextBoxHandlerImpl::updateTextBox(const PBTB_ID &pbtbId, const PixelTextBoxData &ptbd) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, (((pbtbId=" << pbtbId.toString() << "))) , ptbd=" << ptbd.toDebugString(); QSharedPointer tbDataToCheckIfItExists = getTbDataIfExists(pbtbId); if(!tbDataToCheckIfItExists) { ERROR_LOG(m_logger) << LOGFUNCTION << " TextBox does not exists, pbtbId=" << pbtbId.toString(); return nullptr; } PBTB_ID pbtbIdCopy = pbtbId; QTextEdit *te = this->createTextBox(pbtbIdCopy, ptbd, false); // enableGeometryModifications==false - that part is already done TRACE_LOG(m_logger) << LOGFUNCTION << " END, (((pbtbId=" << pbtbId.toString() << "))) , ptbd=" << ptbd.toDebugString(); return te; }; QTextEdit *PixelBasedTextBoxHandlerImpl::getTextBox(const PBTB_ID &pbtbId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId.toString(); if (!d->m_mapImpl) return nullptr; QSharedPointer tbData = getTbDataIfExists(pbtbId); QTextEdit *te = nullptr; if (tbData && tbData->hasValidId()) { TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; te = guiDataRef.m_textEdit; } TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId << ", te = " << te; return te; } void PixelBasedTextBoxHandlerImpl::removeSelection() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; QMutableVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer tbData = it.next(); if (tbData) { TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(Qt::gray)); } } // while TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } bool PixelBasedTextBoxHandlerImpl::setSelected(const PBTB_ID &pbtbId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; // part 1 removeSelection(); // part 2 QSharedPointer tbData = getCreateTbData(pbtbId); if (tbData->m_pbtbId != pbtbId) return false; QColor col = Qt::magenta; col.setAlpha(128); TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; guiDataRef.m_handle_or_proxyControl->setBrush(QBrush(col)); TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId; return true; } QVector PixelBasedTextBoxHandlerImpl::getAllIdsOfParent(const PBTB_ID &pbtbId) const { TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; const int parentId = pbtbId.m_parentId; QVector retVec; retVec.reserve(d->m_tbDataCont.size()); QVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer item = it.next(); if (item->m_pbtbId.m_parentId == parentId) retVec.append(item->m_pbtbId); } // while TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId << ", retVec.size()" << retVec.size(); return retVec; } PixelTextBoxData PixelBasedTextBoxHandlerImpl::getPixelTextBoxData(const PBTB_ID &pbtbId) const { TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; QSharedPointer tbData = getCreateTbData(pbtbId); if (tbData->m_pbtbId != pbtbId) return {}; PixelTextBoxData &ptbdRef = tbData->m_ptbd; TB_Data::TB_GUI_DATA &guiDataRef = tbData->m_guiData; // // current guiData to ptbd: // // text ptbdRef.m_htmlText = guiDataRef.m_textEdit->document()->toHtml(); // ptbdRef.m_backgroundColor; // already updated, see createTextBox() ptbdRef.m_position = guiDataRef.m_handle_or_proxyControl->pos().toPoint(); ptbdRef.m_size = guiDataRef.m_textEdit->geometry().size(); TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId << ", ptbdRef = " << ptbdRef; return ptbdRef; } bool PixelBasedTextBoxHandlerImpl::removeTextBox(const PBTB_ID &pbtbId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId; if (!d->m_mapImpl) return false; if (d->m_tbDataCont.isEmpty()) return true; QMutableVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer item = it.next(); if (item->m_pbtbId == pbtbId) { it.remove(); // dtor will destroy guiElements TRACE_LOG(m_logger) << LOGFUNCTION << " END, removed pbtbId=" << pbtbId; return true; } } // while TRACE_LOG(m_logger) << LOGFUNCTION << " END, pbtbId=" << pbtbId; return false; } bool PixelBasedTextBoxHandlerImpl::removeAllTextBoxesOfParent(int parentId) { TRACE_LOG(m_logger) << LOGFUNCTION << " START, parentId=" << parentId; if (!d->m_mapImpl) return false; int cnt = 0; QMutableVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer item = it.next(); if (item->m_pbtbId.m_parentId == parentId) { it.remove(); // dtor will destroy guiElements ++cnt; } } // while TRACE_LOG(m_logger) << LOGFUNCTION << " END, parentId=" << parentId << ", removed #items = " << cnt; return true; } QSharedPointer PixelBasedTextBoxHandlerImpl::getCreateTbData(const PBTB_ID &pbtbId) const { QVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer item = it.next(); if (item->m_pbtbId == pbtbId) return item; } // while // not found? // insert and return; QSharedPointer initialData = QSharedPointer(new TB_Data()); initialData->m_pbtbId.m_parentId = pbtbId.m_parentId; // !!! keep m_parentId !!! initialData->m_pbtbId.m_uid = PBTB_ID::getNextId(); d->m_tbDataCont.append(initialData); return d->m_tbDataCont.last(); } QSharedPointer PixelBasedTextBoxHandlerImpl::getTbDataIfExists(const PBTB_ID &pbtbId) const { QVectorIterator> it(d->m_tbDataCont); while (it.hasNext()) { QSharedPointer item = it.next(); if (item->m_pbtbId == pbtbId) return item; } // while return {}; } TB_Data::TB_Data() { } TB_Data::~TB_Data() { m_guiData.resetGui(); } bool TB_Data::hasValidId() const { return m_pbtbId.isValid(); } bool TB_Data::connectGeometryChanges() { if (!m_guiData.m_proxy) return false; // QTextEdit has no geometryChanged or similar, but ProxyWidget has: connect(m_guiData.m_proxy, &QGraphicsProxyWidget::geometryChanged, this, &TB_Data::onGeometryChanged); return true; } void TB_Data::onGeometryChanged() { // enableGeometryModifications could be 'read' by m_sizeGrip which is only created if modifications are enabled: const bool enableGeometryModifications = m_guiData.m_handle_or_proxyControl->flags().testFlag(QGraphicsItem::ItemIsMovable); if (enableGeometryModifications) m_guiData.m_handle_or_proxyControl->setRect(m_guiData.m_textEdit->geometry().adjusted(-10, -10, 10, 10)); // bigger than textEdit else m_guiData.m_handle_or_proxyControl->setRect(m_guiData.m_textEdit->geometry().adjusted(1, 1, -1, -1)); // smaller than textEdit (invisible) emit geometryChanged(m_pbtbId); } //----------------------------------------------------------------------------- // class QTextEditWithDoubleClick //----------------------------------------------------------------------------- void QTextEditWithDoubleClick::mouseDoubleClickEvent(QMouseEvent */*event*/) { const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); emit doubleClickEvent(pbtbId); // do NOT forward to baseClass. So we avoid the text being displayed xor/inverted // BaseClass::mouseDoubleClickEvent(event); // Maybe the selection coud be 'removed' in that way: // QTextCursor tc = textCursor(); // tc.clearSelection(); // setTextCursor(tc); } //----------------------------------------------------------------------------- // class QGraphicsRectItemWithMoveEvent //----------------------------------------------------------------------------- void QGraphicsRectItemWithEvents::activatePositionChangedEvents() { m_qtimer = new QTimer(this); m_qtimer->setInterval(100); m_qtimer->setSingleShot(true); connect(m_qtimer, &QTimer::timeout, this, &QGraphicsRectItemWithEvents::onTimeout); } void QGraphicsRectItemWithEvents::onTimeout() { const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); emit positionChangedEvent(pbtbId); } void QGraphicsRectItemWithEvents::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { BaseClass::mouseMoveEvent(event); m_qtimer->start(); m_isMoving = true; } void QGraphicsRectItemWithEvents::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { BaseClass::mouseReleaseEvent(event); if (m_isMoving) { const QString &pbtbProp = this->property(PBTB_ID_PROPERTY_NAME).toString(); const PBTB_ID &pbtbId = SituationMap::PBTB_ID::fromString(pbtbProp); emit positionChangedFinishedEvent(pbtbId); m_isMoving = false; } } //----------------------------------------------------------------------------- // class QSizeGripWithEvent //----------------------------------------------------------------------------- void QSizeGripWithEvent::mouseMoveEvent(QMouseEvent *event) { BaseClass::mouseMoveEvent(event); m_isGeometryChanging = true; } void QSizeGripWithEvent::mouseReleaseEvent(QMouseEvent *mouseEvent) { BaseClass::mouseReleaseEvent(mouseEvent); if (m_isGeometryChanging) { m_isGeometryChanging = false; emit geometryChangedFinishedEvent(m_pbtbId); } } } // namespace SituationMap