870920 Menu

JUCE类库附带示例的源码分析·10

 某个具体的节点(插件)的状态同样可以保存和读取,实现这两个功能,需借助XmlElement和MemoryBlock等功能类。以下两个函数,被上个示例中的代码所调用。

// 基于给出的xml创建node(加载功能)
void FilterGraph::createNodeFromXml (const XmlElement& xml)
{
// 临时定义一个插件描述对象
PluginDescription pd;
// 遍历1参xml,如果某个子节点可以被插件描述对象加载数据,则加载并退出循环
forEachXmlChildElement (xml, e)
{
if (pd.loadFromXml (*e))
break;
}
// 临时定义一个错误消息字符串对象
String errorMessage;
// 基于刚刚定义的插件描述对象,创建插件实例
AudioPluginInstance* instance =
formatManager.createPluginInstance (pd, errorMessage);
if (instance == nullptr)
return;
// 添加插件,并返回代表该插件的node
AudioProcessorGraph::Node::Ptr node (graph.addNode (instance,
xml.getIntAttribute (“uid”)));
// 定义一个xml对象,初始化为本函数的参数xml对象的状态子节点
const XmlElement* const state = xml.getChildByName (“STATE”);
// 如果状态xml有效,定义一个内存块对象,保存状态xml的所有文本内容
// 由node对象获取它所代表的AudioProcessor,获取后设置该插件的状态信息,
// 状态信息来自于刚刚定义并获取内容的内存块对象 */
if (state != nullptr)
{
MemoryBlock m;
m.fromBase64Encoding (state->getAllSubText());
node->getProcessor()->setStateInformation (m.getData(),
(int) m.getSize());
}
// 节点设置各类属性
node->properties.set (“x”, xml.getDoubleAttribute (“x”));
node->properties.set (“y”, xml.getDoubleAttribute (“y”));
node->properties.set (“uiLastX”, xml.getIntAttribute (“uiLastX”));
node->properties.set (“uiLastY”, xml.getIntAttribute (“uiLastY”));
}
// 此函数是在cpp文件中声明并定义的静态函数。创建给定节点的xml
// 返回的XML节点对象中存储了参数节点的属性和它所代表的插件的状态信息(保存功能)
static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node)
{
// 基于参数node,创建插件实例对象
AudioPluginInstance* plugin = dynamic_cast <AudioPluginInstance*>
(node->getProcessor());
// 如果创建不成功,断言失败
if (plugin == nullptr)
{
jassertfalse
return nullptr;
}
// 定义一个XmlElement对象,设置属性为node的各个属性
// 注意,此处的toString()函数。将数值转为字符串对象
XmlElement* e = new XmlElement (“FILTER”);
e->setAttribute (“uid”, (int) node->nodeId);
e->setAttribute (“x”, node->properties [“x”].toString());
e->setAttribute (“y”, node->properties [“y”].toString());
e->setAttribute (“uiLastX”, node->properties [“uiLastX”].toString());
e->setAttribute (“uiLastY”, node->properties [“uiLastY”].toString());
// 临时定义一个插件描述对象
PluginDescription pd;
// 插件实例对象将自身信息填充到插件描述对象中
plugin->fillInPluginDescription (pd);
// xml总节点添加子节点,所添加的子节点为插件描述对象所创建的xml节点
e->addChildElement (pd.createXml());
// 又定义一个XmlElement对象
XmlElement* state = new XmlElement (“STATE”);
// 定义一个MemoryBlock内存块对象
MemoryBlock m;
// 参数节点返回它所代表(持有的)AudioProcessor(插件),而后插件获取其状态信息
// 所获取的状态信息保存到刚刚定义的MemoryBlock内存块对象中
node->getProcessor()->getStateInformation (m);
// 刚刚定义的XML节点对象添加文本元素,内容为内存块对象所保存的数据的字符串内容
state->addTextElement (m.toBase64Encoding());
// 本函数内定义的XML总节点添加子节点
e->addChildElement (state);
return e; // 返回XML总节点对象
}
MVC、GUI及其他方面的重要知识点:
 GUI子组件类中经常需要使用其父组件对象,常规思路是子组件类中声明父组件的指针或引用,另一个思路是:子组件类不声明父组件对象,而是在private区中写一个函数,利用Component类的模板函数findParentComponentOfClass<父组件>()返回所需的父组件对象(指针):
ParentComp* getParentComp() const noexcept
{
return findParentComponentOfClass<ParentComp>();
}
 本项目中,FilterComponent组件类代表主界面中所显示的组件视图,该类以内联的方式写在GraphEditorPanel.cpp源文件中。编写插件或其他功能性小模块的视图操作时,可参阅该类。
 父组件内拖拽式移动子组件时,往往并不在子组件类中直接设置移动后自己的位置,而是在mouseDown()中记下移动前的坐标,在mouseDrag()中记下本次拖拽所移动的距离,而后交由父组件来定位子组件。要实现这一点,子组件中需声明一个Point<int>数据成员,记录拖拽前的原点位置时,一个技巧是将该坐标转换为屏幕坐标,而后在mouseDrag()中更新坐标并转换为父组件内的坐标:
Point<int> originalPos; // 子组件类中声明此数据成员
// 子组件类的mouseDown()中首先记录本组件的原点坐标,该坐标转换为屏幕坐标
originalPos = localPointToGlobal (Point<int>());
// mouseDrag()中临时定义一个pos对象,初始化为
// 本组件拖拽之前的原始坐标 + 本次鼠标开始拖拽后所产生的距离,此时位于屏幕坐标系中
Point<int> pos (originalPos + Point<int> (e.getDistanceFromDragStartX(),
e.getDistanceFromDragStartY()));
// pos转换为父组件内的坐标。即:将屏幕坐标系转换为父组件的坐标系
pos = getParentComponent()->getLocalPoint (nullptr, pos);
 判断鼠标非点击本组件,而是在本组件内拖拽了,在mouseUp()中使用如下判断:
// 如果鼠标拖拽了本组件,而非在本组件内点击
if (! e.mouseWasClicked())
// …
 Component类的虚函数hitTest(x, y)的作用是:x, y所代表的坐标点返回true,则视为鼠标在本组件内点击了,返回false则视为鼠标未在本组件内点击。示例:
bool MyComp::hitTest (int x, int y)
{
// 点击位置位于本组件的左上部分时,本组件接收并处理该点击事件
return x < getWidth() / 2 && y < getHeight() / 2;
}
 组件类paint()的流程一般是:先填充背景,而后绘制文字和其它图形元素,最后绘制边框。
 以模块为单位写h头文件和cpp源文件,同属一个功能模块的类写在一个编译单元中,即:一个编译单元由一个h头文件和一个cpp源文件组成,h头文件中是外部可以使用的类的类定义,cpp源文件则是这些类的类实现。如果该模块中的某些类不对外,则干脆将这些类的类定义和类实现以内联的形式写在cpp文件中。这种组织形式可以避免项目中出现太多太杂的文件,减少项目的文件数量。当然,这一点并不绝对。如果某个类很大,很复杂,那么还是以最常规的一个类一头一源的方式组织文件和编译单元。