您的位置:首页 > 编程语言 > Qt开发

从零开始做3D地图编辑器(十二)(基于QT与OGRE)

2010-05-20 14:49 411 查看
六、实体管理(QTreeWidget、QFileDialog)

这一节的内容相对来说比较多,但是依然是很简单,只不过因为我们要求的操作比较多,制作起来比较麻烦一点而已。最后成品如下图:



在开始之前先把实体管理的XML表确定下来。暂时定了以下结构:

<GroupList>

<Group name=”Tree”/>

<Group name=”House”/>

</GroupList>

<EntityList>

<Entity name=”Test” filename=”test.mesh” Group=”Tree” />

</EntityList>

用名字字符串还是ID,这是个很纠结的问题,不过只是使用编辑器的话那倒无所谓,编辑器在乎的是其稳定性与实用性,效率只要不是太差,一般都能接受(有些商业引擎编译时间那是相当地长啊)。

根据XML表就可以确定树型结构的层次,这种简单的实体管理把层级设定超过两层,简直就是找抽行为。

完成XML表后,现在来分析更细的需求:
1、工具栏:实体管理还有一些常用操作,我把它们分为添加组,删除组,添加实体,删除实体,清空组,清空所有。
2、编辑修改:组这一层不支持从文件中提取,只能由工具栏添加,所以我们要支持双击修改保存文字,实体则是由文件对话框添加的,以后配合好属性栏,就能很方便的修改,所以不需要双击编辑修改。
3、右键菜单:组这一层对应的操作应该是添加实体,删除组,清空组,而实体这一层则只有删除实体这个选项。
4、图标:工具栏肯定需要图标,组和实体前面也需要一个图标,不然看一堆文字很不舒服。
5、拖放:实体可以拖到另一个组。
需求确定后,首先按照上一节的方法新建一个Dock Widget,命名为m_pEntityDock。并把它的可挂接的位置,设为左,右,下三个区域,默认区域设为左侧。
然后我们添加一个新类,类名叫EntityViewWidget,继承于QWidget,构造函数接受父类指针,并把它赋给Qwidget。
类申明如下:

class EntityViewWidget : public QWidget

{

Q_OBJECT;

public:

explicit EntityViewWidget(QWidget *parent = 0);

virtual ~EntityViewWidget();

public Q_SLOTS:

void                                       SSelectionChanged();

void                                       SRemoveGroup();

void                                       SAddGroup();

void                                       SClearGroup();

void                                       SClearAll();

void                                       SRemoveEntity();

void                                       SAddEntity();

protected:

static const int                                     MAX_GROUP_COUNT = 64;

EntityTreeWidget                       *m_pTreeWidget;

QToolBar                                      *m_pToolBar;

QAction                                                  *m_pAddGroup;

QAction                                                  *m_pRemoveGroup;

QAction                                                  *m_pClearGroup;

QAction                                                  *m_pClearAll;

QAction                                                  *m_pAddEntity;

QAction                                                  *m_pRemoveEntity;

QTreeWidgetItem                               *m_pRootItems[MAX_GROUP_COUNT];

QTreeWidgetItem                               *m_pCurrGroupItem;

QTreeWidgetItem                               *m_pCurrEntityItem;

bool                                                         RemoveGroup(int index);

int                                                            m_nGroupCount;

};


先按照第四节的方法在里面创建一个QToolBar,并添加6个基于图标的QAction,并按照上章QT基础里面介绍设定自定义Slot的方法,分别设定好公有Slot函数(SAddEntity、SRemoveEntity、SAddGroup、SRemoveGroup、SClearGroup、SClearAll),因为QT的函数使用的是小写字母开头的驼峰式,所以我把自定义的SLOT用S前缀命名,以免混淆,而普通函数则是大写字母开头,注:这种命名方式并不一定是最好的命名方式。

Slots中有一个SSelectionChanged(),这是针对QTreeWidget的信号itemSelectionChanged的槽,因为添加删除操作和当前选中的item相关联,在这个Slot里面就添加了选中处理代码。
MAX_GROUP_COUNT指的是允许的最大组数。因为QTreeWidget只是指向指针,所以要申请空间给QTreeWidgetItem,这里使用了预分配机制,配合一个int型的m_nGroupCount来避免遍历的工作,这个具体在后面再说。
留了两个指针分别表示当前选中的组和实体,之所以把它们分开是因为有可能会同时用到两个。
EntityTreeWidget是自定义的QTreeWidget,类申明如下:

class EntityTreeWidget : public QTreeWidget

{

Q_OBJECT;

public:

EntityTreeWidget(QWidget *parent = 0);

virtual ~EntityTreeWidget() {};

public Q_SLOTS:

void                    SItemEditDone();

protected:

QTreeWidgetItem            *m_pCurrentItem;

void                                       mousePressEvent(QMouseEvent *evt);

void                                       dragEnterEvent(QDragEnterEvent *evt);

void                                       dragMoveEvent(QDragMoveEvent *evt);

void                                       dropEvent(QDropEvent *evt);

void                                       contextMenuEvent(QContextMenuEvent *evt);

void                                       keyPressEvent(QKeyEvent *evt);

void                                       mouseDoubleClickEvent( QMouseEvent *evt );

};


把这个类放在EntityViewWidget类的上面,因为需要在这个类中调用一些EntityViewWidget的方法。

因为需求,所以重载了一些QTreeWidget的事件,这也是使用自定义TreeWidget的主要原因,它们用来处理按键、拖动、上下文菜单,另外还自定义了一个Slot,用来解决编辑后的反馈。

再来看EntityTreeWidget的实现,代码如下:
EntityTreeWidget::EntityTreeWidget(QWidget *parent) :

QTreeWidget(parent),m_pCurrentItem(0)

{

setColumnCount(1); //设置栏目数

setHeaderHidden(true); //隐藏栏目名

setSelectionMode(QAbstractItemView:: SingleSelection);//我们只允许选中单行

setSelectionBehavior(QAbstractItemView::SelectItems);//设定几种蓝框选中方式

setDragDropOverwriteMode(false);//关闭拖动放下覆盖指定项模式

setDragDropMode(QAbstractItemView::DragDrop);//设定拖放方式,我们这里支持拖和放

connect(itemDelegate(),SIGNAL(closeEditor(QWidget*, QAbstractItemDelegate::EndEditHint)), this, SLOT(SItemEditDone()));//在编辑完成时去触发指定槽

}

//-------------------------编辑完成时的槽---------------------------------------------------------------

//-------用来修改item的文字,以及更改属性栏值

void EntityTreeWidget::SItemEditDone()

{

if(m_pCurrentItem)

{

int index = indexOfTopLevelItem(m_pCurrentItem);//取得当前的ID

m_pCurrentItem->text(0).toStdString();//修改名字

m_pCurrentItem = 0;

}

}

//-------------------刚开始拖放---------------------------------------------------------------------

void EntityTreeWidget::dragEnterEvent(QDragEnterEvent *evt)

{

if(evt->source() == this )

{

evt->acceptProposedAction();//开启拖放行为,你如果不接受的话,

//dragMoveEvent和dropEvent就不起作用

}

}

//--------------拖动移动要告诉用户是否可以被放下--------------------

void EntityTreeWidget::dragMoveEvent(QDragMoveEvent *evt)

{

bool allow = false;

QTreeWidgetItem *item = itemAt(evt->pos());//取得当时所在的Item

if(item)              //如果这时鼠标位置有item

{

if(item->parent() == 0)     //判断目标位置是否为组,实体不允许放在实体下面

{

QList<QTreeWidgetItem*> list = selectedItems(); //取得当前选中列表

if (list.count() == 1)  //因为只支持单个选中

{

if(list[0]->parent() != 0)//组也不能放在组下面

{

allow = true;

}

}

allow = true;

}

}

if(allow)

evt->accept();           //接受放置

else

evt->ignore();            //不接受放置

}

//------------------------放下处理----------------------------------------------------------------

void EntityTreeWidget::dropEvent(QDropEvent *evt)

{

bool allow = false;

QTreeWidgetItem *item = itemAt(evt->pos());

if(!item || !(item->parent() == 0) || !(evt->source() == this))//如果为空,或者是实体,

//或者是从别的地方拖过来的,就忽略

{

evt->ignore();

return;

}

int index = indexOfTopLevelItem(item);

QList<QTreeWidgetItem*> list = selectedItems();

if (list.count() == 1)

{

if(list[0]->parent() != 0)

{

list[0]->parent() -> removeChild(list[0]); //从原组中移除

item->addChild(list[0]);                      //添加进新组

}

}

}

//-------------------------处理上下文菜单---------------------------------------------------------

void EntityTreeWidget::contextMenuEvent(QContextMenuEvent *evt)

{

QTreeWidgetItem *item = itemAt(evt->x(), evt->y());//取得item和,evt->pos()一样效果

if(item)

{

if (item->parent() == 0)

{

QMenu* contextMenu = new QMenu(this);            //创建上下文菜单

int index = indexOfTopLevelItem(item);

contextMenu->addAction(tr("Add Entity"), parent(), SLOT(SAddEntity()));

contextMenu->addSeparator();

contextMenu->addAction(tr("Remove Group"), parent(), SLOT(SRemoveGroup()));                       contextMenu->addSeparator();

contextMenu->addAction(tr("Clear Group"), parent(), SLOT(SClearGroup()));

contextMenu->addSeparator();

contextMenu->exec(QCursor::pos());     //执行上下文菜单

delete contextMenu;                          //一定要记得Delete,不然会内存泄漏

return;

}

else

{

QMenu* contextMenu = new QMenu(this);

int index = indexOfTopLevelItem(item);

contextMenu->addAction(tr("Remove"), parent(), SLOT(SRemoveEntity()));

contextMenu->addSeparator();

contextMenu->exec(QCursor::pos());

delete contextMenu;

return;

}

}

evt->accept();

}

//------------------------------------按键处理-----------------------------------------

void EntityTreeWidget::keyPressEvent(QKeyEvent *evt)

{

QTreeWidget::keyPressEvent(evt);

}

//-------------------鼠标按下处理,修改属性栏值(现在无)-----------------------

void EntityTreeWidget::mousePressEvent(QMouseEvent *evt)

{

QTreeWidget::mousePressEvent(evt);

}

//---------------------------------双击事件-------------------------------------------------------

void EntityTreeWidget::mouseDoubleClickEvent(QMouseEvent *evt)

{

m_pCurrentItem = 0;

QTreeWidgetItem *item = itemAt(evt->pos());

if(item && item->parent() == 0)

{

m_pCurrentItem = item;

editItem(item, 0);//打开编辑功能

return;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: