Commit c2082c81 authored by Don Gagne's avatar Don Gagne

Merge pull request #1466 from dogmaphobic/connectionWork

Link Management Work
parents 36b702a8 5feeb517
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
// If you need to make an incompatible changes to stored settings, bump this version number // If you need to make an incompatible changes to stored settings, bump this version number
// up by 1. This will caused store settings to be cleared on next boot. // up by 1. This will caused store settings to be cleared on next boot.
#define QGC_SETTINGS_VERSION 5 #define QGC_SETTINGS_VERSION 6
#define QGC_APPLICATION_NAME "QGroundControl" #define QGC_APPLICATION_NAME "QGroundControl"
#define QGC_ORG_NAME "QGroundControl.org" #define QGC_ORG_NAME "QGroundControl.org"
......
...@@ -40,6 +40,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -40,6 +40,7 @@ This file is part of the QGROUNDCONTROL project
LinkConfiguration::LinkConfiguration(const QString& name) LinkConfiguration::LinkConfiguration(const QString& name)
: _preferred(false) : _preferred(false)
, _dynamic(false)
{ {
_link = NULL; _link = NULL;
_name = name; _name = name;
...@@ -51,6 +52,7 @@ LinkConfiguration::LinkConfiguration(LinkConfiguration* copy) ...@@ -51,6 +52,7 @@ LinkConfiguration::LinkConfiguration(LinkConfiguration* copy)
_link = copy->getLink(); _link = copy->getLink();
_name = copy->name(); _name = copy->name();
_preferred = copy->isPreferred(); _preferred = copy->isPreferred();
_dynamic = copy->isDynamic();
Q_ASSERT(!_name.isEmpty()); Q_ASSERT(!_name.isEmpty());
} }
...@@ -60,6 +62,7 @@ void LinkConfiguration::copyFrom(LinkConfiguration* source) ...@@ -60,6 +62,7 @@ void LinkConfiguration::copyFrom(LinkConfiguration* source)
_link = source->getLink(); _link = source->getLink();
_name = source->name(); _name = source->name();
_preferred = source->isPreferred(); _preferred = source->isPreferred();
_dynamic = source->isDynamic();
} }
/*! /*!
......
...@@ -97,6 +97,18 @@ public: ...@@ -97,6 +97,18 @@ public:
*/ */
void setPreferred(bool preferred = true) { _preferred = preferred; } void setPreferred(bool preferred = true) { _preferred = preferred; }
/*!
*
* Is this a dynamic configuration? (non persistent)
* @return True if this is an automatically added configuration.
*/
bool isDynamic() { return _dynamic; }
/*!
* Set if this is this a dynamic configuration. (decided at runtime)
*/
void setDynamic(bool dynamic = true) { _dynamic = dynamic; }
/// Virtual Methods /// Virtual Methods
/*! /*!
...@@ -171,6 +183,7 @@ protected: ...@@ -171,6 +183,7 @@ protected:
private: private:
QString _name; QString _name;
bool _preferred; ///< Determined internally if this is a preferred connection. It comes up first in the drop down box. bool _preferred; ///< Determined internally if this is a preferred connection. It comes up first in the drop down box.
bool _dynamic; ///< A connection added automatically and not persistent (unless it's edited).
}; };
#endif // LINKCONFIGURATION_H #endif // LINKCONFIGURATION_H
...@@ -325,12 +325,12 @@ void LinkManager::saveLinkConfigurationList() ...@@ -325,12 +325,12 @@ void LinkManager::saveLinkConfigurationList()
{ {
QSettings settings; QSettings settings;
settings.remove(LinkConfiguration::settingsRoot()); settings.remove(LinkConfiguration::settingsRoot());
QString root(LinkConfiguration::settingsRoot());
settings.setValue(root + "/count", _linkConfigurations.count());
int index = 0; int index = 0;
foreach (LinkConfiguration* pLink, _linkConfigurations) { foreach (LinkConfiguration* pLink, _linkConfigurations) {
Q_ASSERT(pLink != NULL); Q_ASSERT(pLink != NULL);
root = LinkConfiguration::settingsRoot(); if(!pLink->isDynamic())
{
QString root = LinkConfiguration::settingsRoot();
root += QString("/Link%1").arg(index++); root += QString("/Link%1").arg(index++);
settings.setValue(root + "/name", pLink->name()); settings.setValue(root + "/name", pLink->name());
settings.setValue(root + "/type", pLink->type()); settings.setValue(root + "/type", pLink->type());
...@@ -338,6 +338,9 @@ void LinkManager::saveLinkConfigurationList() ...@@ -338,6 +338,9 @@ void LinkManager::saveLinkConfigurationList()
// Have the instance save its own values // Have the instance save its own values
pLink->saveSettings(settings, root); pLink->saveSettings(settings, root);
} }
}
QString root(LinkConfiguration::settingsRoot());
settings.setValue(root + "/count", index);
emit linkConfigurationChanged(); emit linkConfigurationChanged();
} }
...@@ -427,6 +430,7 @@ void LinkManager::_updateConfigurationList(void) ...@@ -427,6 +430,7 @@ void LinkManager::_updateConfigurationList(void)
return; return;
} }
bool saveList = false; bool saveList = false;
QStringList currentPorts;
QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts(); QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
// Iterate Comm Ports // Iterate Comm Ports
foreach (QSerialPortInfo portInfo, portList) { foreach (QSerialPortInfo portInfo, portList) {
...@@ -439,6 +443,8 @@ void LinkManager::_updateConfigurationList(void) ...@@ -439,6 +443,8 @@ void LinkManager::_updateConfigurationList(void)
qDebug() << "serialNumber: " << portInfo.serialNumber(); qDebug() << "serialNumber: " << portInfo.serialNumber();
qDebug() << "vendorIdentifier: " << portInfo.vendorIdentifier(); qDebug() << "vendorIdentifier: " << portInfo.vendorIdentifier();
#endif #endif
// Save port name
currentPorts << portInfo.systemLocation();
// Is this a PX4? // Is this a PX4?
if (portInfo.vendorIdentifier() == 9900) { if (portInfo.vendorIdentifier() == 9900) {
SerialConfiguration* pSerial = _findSerialConfiguration(portInfo.systemLocation()); SerialConfiguration* pSerial = _findSerialConfiguration(portInfo.systemLocation());
...@@ -451,6 +457,7 @@ void LinkManager::_updateConfigurationList(void) ...@@ -451,6 +457,7 @@ void LinkManager::_updateConfigurationList(void)
} else { } else {
// Lets create a new Serial configuration automatically // Lets create a new Serial configuration automatically
pSerial = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed())); pSerial = new SerialConfiguration(QString("Pixhawk on %1").arg(portInfo.portName().trimmed()));
pSerial->setDynamic(true);
pSerial->setPreferred(true); pSerial->setPreferred(true);
pSerial->setBaud(115200); pSerial->setBaud(115200);
pSerial->setPortName(portInfo.systemLocation()); pSerial->setPortName(portInfo.systemLocation());
...@@ -458,6 +465,45 @@ void LinkManager::_updateConfigurationList(void) ...@@ -458,6 +465,45 @@ void LinkManager::_updateConfigurationList(void)
saveList = true; saveList = true;
} }
} }
// Is this an FTDI Chip? It could be a 3DR Modem
if (portInfo.vendorIdentifier() == 1027) {
SerialConfiguration* pSerial = _findSerialConfiguration(portInfo.systemLocation());
if (pSerial) {
//-- If this port is configured make sure it has the preferred flag set, unless someone else already has it set.
if(!pSerial->isPreferred() && !saveList) {
pSerial->setPreferred(true);
saveList = true;
}
} else {
// Lets create a new Serial configuration automatically (an assumption at best)
pSerial = new SerialConfiguration(QString("3DR Radio on %1").arg(portInfo.portName().trimmed()));
pSerial->setDynamic(true);
pSerial->setPreferred(true);
pSerial->setBaud(57600);
pSerial->setPortName(portInfo.systemLocation());
addLinkConfiguration(pSerial);
saveList = true;
}
}
}
// Now we go through the current configuration list and make sure any dynamic config has gone away
QList<LinkConfiguration*> _confToDelete;
foreach (LinkConfiguration* pLink, _linkConfigurations) {
Q_ASSERT(pLink != NULL);
// We only care about dynamic links
if(pLink->isDynamic()) {
if(pLink->type() == LinkConfiguration::TypeSerial) {
SerialConfiguration* pSerial = dynamic_cast<SerialConfiguration*>(pLink);
if(!currentPorts.contains(pSerial->portName())) {
_confToDelete.append(pSerial);
}
}
}
}
// Now remove all links that are gone
foreach (LinkConfiguration* pDelete, _confToDelete) {
removeLinkConfiguration(pDelete);
saveList = true;
} }
// Save configuration list, which will also trigger a signal for the UI // Save configuration list, which will also trigger a signal for the UI
if(saveList) { if(saveList) {
...@@ -468,28 +514,24 @@ void LinkManager::_updateConfigurationList(void) ...@@ -468,28 +514,24 @@ void LinkManager::_updateConfigurationList(void)
bool LinkManager::containsLink(LinkInterface* link) bool LinkManager::containsLink(LinkInterface* link)
{ {
bool found = false; bool found = false;
foreach (SharedLinkInterface sharedLink, _links) { foreach (SharedLinkInterface sharedLink, _links) {
if (sharedLink.data() == link) { if (sharedLink.data() == link) {
found = true; found = true;
break; break;
} }
} }
return found; return found;
} }
bool LinkManager::anyConnectedLinks(void) bool LinkManager::anyConnectedLinks(void)
{ {
bool found = false; bool found = false;
foreach (SharedLinkInterface sharedLink, _links) { foreach (SharedLinkInterface sharedLink, _links) {
if (sharedLink.data()->isConnected()) { if (sharedLink.data()->isConnected()) {
found = true; found = true;
break; break;
} }
} }
return found; return found;
} }
...@@ -500,7 +542,6 @@ SharedLinkInterface& LinkManager::sharedPointerForLink(LinkInterface* link) ...@@ -500,7 +542,6 @@ SharedLinkInterface& LinkManager::sharedPointerForLink(LinkInterface* link)
return _links[i]; return _links[i];
} }
} }
// This should never happen // This should never happen
Q_ASSERT(false); Q_ASSERT(false);
return _nullSharedLink; return _nullSharedLink;
......
...@@ -215,21 +215,35 @@ bool SerialConfigurationWindow::setupPortList() ...@@ -215,21 +215,35 @@ bool SerialConfigurationWindow::setupPortList()
void SerialConfigurationWindow::enableFlowControl(bool flow) void SerialConfigurationWindow::enableFlowControl(bool flow)
{ {
_config->setFlowControl(flow ? QSerialPort::HardwareControl : QSerialPort::NoFlowControl); _config->setFlowControl(flow ? QSerialPort::HardwareControl : QSerialPort::NoFlowControl);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
} }
void SerialConfigurationWindow::setParityNone(bool accept) void SerialConfigurationWindow::setParityNone(bool accept)
{ {
if (accept) _config->setParity(QSerialPort::NoParity); if (accept) {
_config->setParity(QSerialPort::NoParity);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
}
} }
void SerialConfigurationWindow::setParityOdd(bool accept) void SerialConfigurationWindow::setParityOdd(bool accept)
{ {
if (accept) _config->setParity(QSerialPort::OddParity); if (accept) {
_config->setParity(QSerialPort::OddParity);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
}
} }
void SerialConfigurationWindow::setParityEven(bool accept) void SerialConfigurationWindow::setParityEven(bool accept)
{ {
if (accept) _config->setParity(QSerialPort::EvenParity); if (accept) {
_config->setParity(QSerialPort::EvenParity);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
}
} }
void SerialConfigurationWindow::setPortName(int index) void SerialConfigurationWindow::setPortName(int index)
...@@ -238,6 +252,8 @@ void SerialConfigurationWindow::setPortName(int index) ...@@ -238,6 +252,8 @@ void SerialConfigurationWindow::setPortName(int index)
QString pname = _ui.portName->itemData(index).toString(); QString pname = _ui.portName->itemData(index).toString();
if (_config->portName() != pname) { if (_config->portName() != pname) {
_config->setPortName(pname); _config->setPortName(pname);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
} }
} }
...@@ -245,14 +261,20 @@ void SerialConfigurationWindow::setBaudRate(int index) ...@@ -245,14 +261,20 @@ void SerialConfigurationWindow::setBaudRate(int index)
{ {
int baud = _ui.baudRate->itemData(index).toInt(); int baud = _ui.baudRate->itemData(index).toInt();
_config->setBaud(baud); _config->setBaud(baud);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
} }
void SerialConfigurationWindow::setDataBits(int bits) void SerialConfigurationWindow::setDataBits(int bits)
{ {
_config->setDataBits(bits); _config->setDataBits(bits);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
} }
void SerialConfigurationWindow::setStopBits(int bits) void SerialConfigurationWindow::setStopBits(int bits)
{ {
_config->setStopBits(bits); _config->setStopBits(bits);
//-- If this was dynamic, it's now edited and persistent
_config->setDynamic(false);
} }
...@@ -42,7 +42,6 @@ MainToolBar::MainToolBar(QWidget* parent) ...@@ -42,7 +42,6 @@ MainToolBar::MainToolBar(QWidget* parent)
, _currentView(ViewNone) , _currentView(ViewNone)
, _batteryVoltage(0.0) , _batteryVoltage(0.0)
, _batteryPercent(0.0) , _batteryPercent(0.0)
, _linkSelected(false)
, _connectionCount(0) , _connectionCount(0)
, _systemArmed(false) , _systemArmed(false)
, _currentHeartbeatTimeout(0) , _currentHeartbeatTimeout(0)
...@@ -152,25 +151,8 @@ void MainToolBar::onAnalyzeView() ...@@ -152,25 +151,8 @@ void MainToolBar::onAnalyzeView()
MainWindow::instance()->loadAnalyzeView(); MainWindow::instance()->loadAnalyzeView();
} }
void MainToolBar::onConnect(QString conf) void MainToolBar::onDisconnect(QString conf)
{ {
// If no connection, the role is "Connect"
if(_connectionCount == 0) {
// Connect Link
if(_currentConfig.isEmpty()) {
MainWindow::instance()->manageLinks();
} else {
// We don't want the combo box updating under our feet
LinkManager::instance()->suspendConfigurationUpdates(true);
// Create a link
LinkInterface* link = LinkManager::instance()->createConnectedLink(_currentConfig);
if(link) {
// Save last used connection
MainWindow::instance()->saveLastUsedConnection(_currentConfig);
}
LinkManager::instance()->suspendConfigurationUpdates(false);
}
} else {
if(conf.isEmpty()) { if(conf.isEmpty()) {
// Disconnect Only Connected Link // Disconnect Only Connected Link
int connectedCount = 0; int connectedCount = 0;
...@@ -197,15 +179,23 @@ void MainToolBar::onConnect(QString conf) ...@@ -197,15 +179,23 @@ void MainToolBar::onConnect(QString conf)
} }
} }
} }
}
} }
void MainToolBar::onLinkConfigurationChanged(const QString& config) void MainToolBar::onConnect(QString conf)
{ {
// User selected a link configuration from the combobox // Connect Link
if(_currentConfig != config) { if(conf.isEmpty()) {
_currentConfig = config; MainWindow::instance()->manageLinks();
_linkSelected = true; } else {
// We don't want the list updating under our feet
LinkManager::instance()->suspendConfigurationUpdates(true);
// Create a link
LinkInterface* link = LinkManager::instance()->createConnectedLink(conf);
if(link) {
// Save last used connection
MainWindow::instance()->saveLastUsedConnection(conf);
}
LinkManager::instance()->suspendConfigurationUpdates(false);
} }
} }
...@@ -378,16 +368,14 @@ void MainToolBar::_updateBatteryRemaining(UASInterface*, double voltage, double, ...@@ -378,16 +368,14 @@ void MainToolBar::_updateBatteryRemaining(UASInterface*, double voltage, double,
void MainToolBar::_updateConfigurations() void MainToolBar::_updateConfigurations()
{ {
bool resetSelected = false;
QString selected = _currentConfig;
QStringList tmpList; QStringList tmpList;
QList<LinkConfiguration*> configs = LinkManager::instance()->getLinkConfigurationList(); QList<LinkConfiguration*> configs = LinkManager::instance()->getLinkConfigurationList();
foreach(LinkConfiguration* conf, configs) { foreach(LinkConfiguration* conf, configs) {
if(conf) { if(conf) {
if(conf->isPreferred()) {
tmpList.insert(0,conf->name());
} else {
tmpList << conf->name(); tmpList << conf->name();
if((!_linkSelected && conf->isPreferred()) || selected.isEmpty()) {
selected = conf->name();
resetSelected = true;
} }
} }
} }
...@@ -396,15 +384,6 @@ void MainToolBar::_updateConfigurations() ...@@ -396,15 +384,6 @@ void MainToolBar::_updateConfigurations()
_linkConfigurations = tmpList; _linkConfigurations = tmpList;
emit configListChanged(); emit configListChanged();
} }
// Selection change?
if((selected != _currentConfig && _linkConfigurations.contains(selected)) ||
(selected.isEmpty())) {
_currentConfig = selected;
emit currentConfigChanged(_currentConfig);
}
if(resetSelected) {
_linkSelected = false;
}
} }
void MainToolBar::_linkConnected(LinkInterface*) void MainToolBar::_linkConnected(LinkInterface*)
......
...@@ -71,7 +71,7 @@ public: ...@@ -71,7 +71,7 @@ public:
Q_INVOKABLE void onFlyView(); Q_INVOKABLE void onFlyView();
Q_INVOKABLE void onAnalyzeView(); Q_INVOKABLE void onAnalyzeView();
Q_INVOKABLE void onConnect(QString conf); Q_INVOKABLE void onConnect(QString conf);
Q_INVOKABLE void onLinkConfigurationChanged(const QString& config); Q_INVOKABLE void onDisconnect(QString conf);
Q_INVOKABLE void onEnterMessageArea(int x, int y); Q_INVOKABLE void onEnterMessageArea(int x, int y);
Q_INVOKABLE QString getMavIconColor(); Q_INVOKABLE QString getMavIconColor();
...@@ -86,7 +86,6 @@ public: ...@@ -86,7 +86,6 @@ public:
Q_PROPERTY(MessageType_t messageType MEMBER _currentMessageType NOTIFY messageTypeChanged) Q_PROPERTY(MessageType_t messageType MEMBER _currentMessageType NOTIFY messageTypeChanged)
Q_PROPERTY(int newMessageCount MEMBER _currentMessageCount NOTIFY newMessageCountChanged) Q_PROPERTY(int newMessageCount MEMBER _currentMessageCount NOTIFY newMessageCountChanged)
Q_PROPERTY(int messageCount MEMBER _messageCount NOTIFY messageCountChanged) Q_PROPERTY(int messageCount MEMBER _messageCount NOTIFY messageCountChanged)
Q_PROPERTY(QString currentConfig MEMBER _currentConfig NOTIFY currentConfigChanged)
Q_PROPERTY(QString systemPixmap MEMBER _systemPixmap NOTIFY systemPixmapChanged) Q_PROPERTY(QString systemPixmap MEMBER _systemPixmap NOTIFY systemPixmapChanged)
Q_PROPERTY(int satelliteCount MEMBER _satelliteCount NOTIFY satelliteCountChanged) Q_PROPERTY(int satelliteCount MEMBER _satelliteCount NOTIFY satelliteCountChanged)
Q_PROPERTY(QStringList connectedList MEMBER _connectedList NOTIFY connectedListChanged) Q_PROPERTY(QStringList connectedList MEMBER _connectedList NOTIFY connectedListChanged)
...@@ -160,8 +159,6 @@ private: ...@@ -160,8 +159,6 @@ private:
double _batteryVoltage; double _batteryVoltage;
double _batteryPercent; double _batteryPercent;
QStringList _linkConfigurations; QStringList _linkConfigurations;
QString _currentConfig;
bool _linkSelected;
int _connectionCount; int _connectionCount;
bool _systemArmed; bool _systemArmed;
QString _currentState; QString _currentState;
......
...@@ -472,32 +472,50 @@ Rectangle { ...@@ -472,32 +472,50 @@ Rectangle {
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.rightMargin: 10 anchors.rightMargin: 10
QGCComboBox { Menu {
id: configList id: connectMenu
width: 200
visible: (mainToolBar.connectionCount === 0 && mainToolBar.configList.length > 0)
anchors.verticalCenter: parent.verticalCenter
model: mainToolBar.configList
onCurrentIndexChanged: {
mainToolBar.onLinkConfigurationChanged(mainToolBar.configList[currentIndex]);
}
Component.onCompleted: { Component.onCompleted: {
mainToolBar.currentConfigChanged.connect(configList.onCurrentConfigChanged) mainToolBar.configListChanged.connect(connectMenu.updateConnectionList);
connectMenu.updateConnectionList();
}
function addMenuEntry(name) {
var label = "Add Connection"
if(name !== "")
label = name;
var mItem = connectMenu.addItem(label);
var menuSlot = function() {mainToolBar.onConnect(name)};
mItem.triggered.connect(menuSlot);
}
function updateConnectionList() {
connectMenu.clear();
for(var i = 0; i < mainToolBar.configList.length; i++) {
connectMenu.addMenuEntry(mainToolBar.configList[i]);
} }
function onCurrentConfigChanged(config) { if(mainToolBar.configList.length > 0) {
var index = configList.find(config); connectMenu.addSeparator();
configList.currentIndex = index; }
// Add "Add Connection" to the list
connectMenu.addMenuEntry("");
} }
} }
QGCButton { QGCButton {
id: connectButton id: connectButton
width: 100 width: 100
visible: (mainToolBar.connectionCount === 0 || mainToolBar.connectionCount === 1) visible: mainToolBar.connectionCount === 0
text: (mainToolBar.configList.length > 0) ? (mainToolBar.connectionCount === 0) ? qsTr("Connect") : qsTr("Disconnect") : qsTr("Add Link") text: qsTr("Connect")
menu: connectMenu
anchors.verticalCenter: parent.verticalCenter
}
QGCButton {
id: disconnectButton
width: 100
visible: mainToolBar.connectionCount === 1
text: qsTr("Disconnect")
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onClicked: { onClicked: {
mainToolBar.onConnect(""); mainToolBar.onDisconnect("");
} }
} }
...@@ -506,12 +524,15 @@ Rectangle { ...@@ -506,12 +524,15 @@ Rectangle {
Component.onCompleted: { Component.onCompleted: {
mainToolBar.connectedListChanged.connect(disconnectMenu.onConnectedListChanged) mainToolBar.connectedListChanged.connect(disconnectMenu.onConnectedListChanged)
} }
function addMenuEntry(name) {
var mItem = disconnectMenu.addItem(name);
var menuSlot = function() {mainToolBar.onDisconnect(name)};
mItem.triggered.connect(menuSlot);
}
function onConnectedListChanged(conList) { function onConnectedListChanged(conList) {
disconnectMenu.clear(); disconnectMenu.clear();
for(var i = 0; i < conList.length; i++) { for(var i = 0; i < conList.length; i++) {
var mItem = disconnectMenu.addItem(conList[i]); disconnectMenu.addMenuEntry(conList[i]);
var menuSlot = function() {mainToolBar.onConnect(mItem.text)};
mItem.triggered.connect(menuSlot);
} }
} }
} }
...@@ -519,10 +540,10 @@ Rectangle { ...@@ -519,10 +540,10 @@ Rectangle {
QGCButton { QGCButton {
id: multidisconnectButton id: multidisconnectButton
width: 100 width: 100
text: qsTr("Disconnect") text: "Disconnect"
visible: (mainToolBar.connectionCount > 1) visible: mainToolBar.connectionCount > 1
anchors.verticalCenter: parent.verticalCenter
menu: disconnectMenu menu: disconnectMenu
anchors.verticalCenter: parent.verticalCenter
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment