Files
qt5-legatus-legacy/Lts/Viewer/SituationMap/MapImpl/pixel_based_textbox_handler_impl.cpp
T
2026-08-13 18:33:10 +02:00

564 lines
18 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Interface-Implementation of LocationDisplay
* $Id$
* $HeadURL$
* $Author$
*/
// Own
#include "pixel_based_textbox_handler_impl.h"
#include "map_impl.h"
#include <Map/situation_map_utils.h>
#include <MapImpl/coordinate_translation.h>
// ESRI
#include <arcgis/arcgisversion.h> // This file is created by RME, not ESRI
#include <arcgis/Map.h>
#include <arcgis/MapGraphicsView.h>
// Qt
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsProxyWidget>
#include <QPushButton>
#include <QDebug>
#include <QTimer>
#include <sstream>
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<QSharedPointer<TB_Data>> 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<TB_Data> 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<TB_Data> 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<TB_Data> 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<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> 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<TB_Data> 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<PBTB_ID> PixelBasedTextBoxHandlerImpl::getAllIdsOfParent(const PBTB_ID &pbtbId) const
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START, pbtbId=" << pbtbId;
const int parentId = pbtbId.m_parentId;
QVector<PBTB_ID> retVec;
retVec.reserve(d->m_tbDataCont.size());
QVectorIterator<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> 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<TB_Data> 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<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> 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<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> 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<TB_Data> PixelBasedTextBoxHandlerImpl::getCreateTbData(const PBTB_ID &pbtbId) const
{
QVectorIterator<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> item = it.next();
if (item->m_pbtbId == pbtbId)
return item;
} // while
// not found?
// insert and return;
QSharedPointer<TB_Data> initialData = QSharedPointer<TB_Data>(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<TB_Data> PixelBasedTextBoxHandlerImpl::getTbDataIfExists(const PBTB_ID &pbtbId) const
{
QVectorIterator<QSharedPointer<TB_Data>> it(d->m_tbDataCont);
while (it.hasNext())
{
QSharedPointer<TB_Data> 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