实体捕捉与选择
实体容器
执行一个绘图特征之后,就会在视图中显示一个特定的特征,例如可以通过两点直线特征来绘制两点直线。这些绘制在视图上的图形实际上在内部会有一个实体与之对应,而存放这些实体的地方即为实体容器。在MVC
设计模式中,通常将数据(也就是实体容器)存放在Model
中,也就是本项目中的文档Document
。
本项目中的文档类RS_Document
继承于容器类RS_EntityContainer
,RS_EntityContainer
类内部有一个QList<RS_Entity *> entities;
成员变量(即为实体容器)。
查看工作窗口类QC_MDIWindow
的构造函数,在构造中创建了文档,所以实体容器是属于文档Document
的组成部分,也表明实体容器本身是存放在工作窗口类QC_MDIWindow
中的。其他的对象可以通过传递实体容器的指针来访问实体容器。
QC_MDIWindow::QC_MDIWindow(RS_Document *doc, QWidget *parent, Qt::WindowFlags wflags)
: QMdiSubWindow(parent, wflags)
, m_owner{doc == nullptr}
{
// ...
if (doc==nullptr) {
document = new RS_Graphic(); // 创建文档document
document->newDoc();
} else {
document = doc;
}
// 创建视图View
graphicView = new QG_GraphicView(this, {}, document);
// ...
}
捕捉类RS_Snapper
特征(Action
)的基类是RS_ActionInterface
,而RS_ActionInterface
的基类(之一)是RS_Snapper
。RS_Snapper
类中有一个重要成员是RS_EntityContainer *container
,该参数表示当前活动文档中的实体容器,并且从RS_Snapper
类构造函数中传入。
RS_Snapper::RS_Snapper(RS_EntityContainer& container, RS_GraphicView& graphicView)
:container(&container) // 传入实体容器的指针
,graphicView(&graphicView)
,pImpData(new ImpData)
,snap_indicator(new Indicator)
{}
具体的特征类,例如两点直线类的构造函数为:
RS_ActionDrawLine(RS_EntityContainer& container, RS_GraphicView& graphicView);
表明创建两点直线特征对象时,就会将实体容器指针和当前视图指针传递进来,然后一步一步传递到RS_Snapper
类的构造函数中。
也就是说,所有的特征类中都可以访问当前文档中的实体容器。
RS_Snapper
类中有几个重要的重载函数RS_Entity* catchEntity(...)
,这一系列函数是为了计算实体容器中是否有实体被选中,具体是通过判断鼠标点击距离和最小判定距离来确定是否有实体被选中。
下面的第一个catchEntity
函数首相是将屏幕点转换成视图中的坐标点。
RS_Entity* RS_Snapper::catchEntity(QMouseEvent* e, // Qt事件,主要是为了获取坐标
RS2::EntityType enType,
RS2::ResolveLevel level) {
return catchEntity({ graphicView->toGraphX(e->x()), // 将Qt窗口坐标转换为视图坐标
graphicView->toGraphY(e->y())},
enType,
level);
}
然后调用第二个catchEntity
函数。
RS_Entity* RS_Snapper::catchEntity(const RS_Vector& pos, RS2::ResolveLevel level)
{
double dist (0.); // 存放最短距离
// 查找容器中距离点pos最近的实体,并返回距离dist
RS_Entity* entity = container->getNearestEntity(pos, &dist, level);
int idx = -1;
if (entity != nullptr && entity->getParent()) {
idx = entity->getParent()->findEntity(entity); // 选中实体在容器中的索引
}
// ...
}
需要注意的是,如果在最小判定距离内没有实体,函数RS_Entity* RS_EntityContainer::getNearestEntity()
则会返回nullptr
。
RS_Entity* RS_EntityContainer::getNearestEntity(const RS_Vector& coord,
double* dist, // *dist = 0.0
RS2::ResolveLevel level) const
{
RS_Entity* e = nullptr;
// ...
double d = getDistanceToPoint(coord, &e, level, solidDist);
// ...
if (dist) { *dist = d; }
return e;
}
而在RS_EntityContainer::getDistanceToPoint()
函数中,则是遍历实体容器,计算每个实体的距离。
double RS_EntityContainer::getDistanceToPoint(const RS_Vector& coord,
RS_Entity** entity,
RS2::ResolveLevel level,
double solidDist) const{
double minDist = RS_MAXDOUBLE; // minimum measured distance
double curDist; // currently measured distance
RS_Entity* closestEntity = nullptr; // closest entity found
RS_Entity* subEntity = nullptr;
for(auto e: entities){ // 遍历所有实体,计算最短距离
if (e->isVisible() && (e->getLayer()==nullptr || !e->getLayer()->isLocked())) {
// 计算具体的实体e到指定点coord的最短距离
curDist = e->getDistanceToPoint(coord, &subEntity, level, solidDist);
if (curDist<=minDist) {
// ...
closestEntity = e; // 更新最短距离的实体
minDist = curDist; // 更新最短距离
}
}
}
if (entity) {
*entity = closestEntity;
}
return minDist;
}
实体选择
在没有选择一个特征时,当前的特征为RS_ActionDefault
,当鼠标点击视图时,会通过事件处理器eventHander
将鼠标弹起时间mouseReleaseEvent
转发给默认特征RS_ActionDefault
。然后RS_ActionDefault::mouseReleaseEvent()
被调用。
在RS_ActionDefault::mouseReleaseEvent()
函数中,首先会根据鼠标点击位置去判断是否选中了一个实体(catchEntity()
函数),如果选中了就会重绘选中的实体。
void RS_ActionDefault::mouseReleaseEvent(QMouseEvent* e) {
if (e->button()==Qt::LeftButton) {
pPoints->v2 = graphicView->toGraph(e->x(), e->y()); // 鼠标点转换为视图坐标点
switch (getStatus()) {
case Dragging: {
RS_Entity* en = catchEntity(e); // 获取鼠标点击选中的实体
if (en != nullptr) { // 鼠标点击选中了实体
deletePreview();
RS_Selection s(*container, graphicView);
s.selectSingle(en); // 重绘选中的实体
// ...
}
}
break;
}
// ...
}
重绘选中实体
重绘选中实体是通过RS_Selection
类的selectSingle()
方法来实现的。重绘过程为:首先从视图中删除被选中的实体,然后切换选中的状态(如果该实体未被选中,将状态设置为选中;如果已经是被选中的状态,则将状态设置为取消选中的状态)。最后单独绘制一下该实体。
void RS_Selection::selectSingle(RS_Entity* e)
{
if (e && (! (e->getLayer() && e->getLayer()->isLocked())))
{
if (graphicView)
graphicView->deleteEntity(e); // 先从视图中删除该实体
e->toggleSelected(); // 切换选中状态,选中->取消选中,没选中->选中
if (graphicView)
{
graphicView->drawEntity(e); // 单独绘制选中实体
// ...
}
}
}
总结
特征类继承于捕捉类,表明在执行任意一个特征时,都可以使用捕捉和选择的功能。而创建特征类时,传入了当前工作文档中的实体容器给特征类对象了,然后进一步传递给捕捉类,所以捕捉类能够利用这些数据和当前鼠标点来判断是否有实体被选中了。