420 lines
12 KiB
C++
420 lines
12 KiB
C++
#include "SH_servers_tree_widget.h"
|
|
#include "ui_SH_servers_tree_widget.h"
|
|
#include "SH_client_channel.h"
|
|
#include "SH_server_base.h"
|
|
#include "SH_base.h"
|
|
#include <QMetaEnum>
|
|
#include <QInputDialog>
|
|
#include <QSettings>
|
|
#include <QScrollBar>
|
|
#include <piqt.h>
|
|
#include "ccm_SHS_shared.h"
|
|
|
|
const char property_alive [] = "SHS_is_alive";
|
|
const char property_address[] = "SHS_address";
|
|
const int role_server_info = Qt::UserRole;
|
|
const int role_compatible = Qt::UserRole + 1;
|
|
REGISTER_VARIANT(ServerAddress);
|
|
|
|
|
|
enum Column {
|
|
cServer,
|
|
cHostname,
|
|
cOS,
|
|
cChannel,
|
|
cIP,
|
|
cVersions,
|
|
};
|
|
enum Icon {
|
|
iIncompatible,
|
|
iOff,
|
|
iOnline,
|
|
iOnlineEmpty,
|
|
};
|
|
|
|
|
|
SHServersTreeWidget::SHServersTreeWidget(QWidget * parent): QWidget(parent) {
|
|
qRegisterMetaType<PacketServerInfo>();
|
|
ui = new Ui::SHServersTreeWidget();
|
|
ui->setupUi(this);
|
|
ui->tree->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
#ifdef ANDROID
|
|
ui->tree->setStyleSheet("font-size: 18pt;");
|
|
#endif
|
|
#ifdef MOBILE_VIEW
|
|
grabGesture(Qt::TapAndHoldGesture);
|
|
connect(ui->tree, &QTreeWidget::itemClicked, this, [this](){on_tree_doubleClicked(QModelIndex());});
|
|
#endif
|
|
popup_menu.addAction(ui->actionRemove);
|
|
icon_state[iIncompatible] = QIcon(":/icons/led_off.png");
|
|
icon_state[iOff] = QIcon(":/icons/status-off.png");
|
|
icon_state[iOnline] = QIcon(":/icons/status-green.png");
|
|
icon_state[iOnlineEmpty] = QIcon(":/icons/status-on.png");
|
|
bc_channel = new SHClientChannel();
|
|
bc_channel->setID(generateID());
|
|
CONNECTU_QUEUED(bc_channel, broadcastReceiveEvent, this, broadcastReceiveEvent, this);
|
|
timer_pip = startTimer(10);
|
|
loadServers();
|
|
loading = false;
|
|
}
|
|
|
|
|
|
SHServersTreeWidget::~SHServersTreeWidget() {
|
|
clear();
|
|
delete bc_channel;
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::setPultView() {
|
|
ui->tree->setColumnHidden(cHostname, true);
|
|
ui->tree->setColumnHidden(cOS , true);
|
|
ui->tree->setColumnHidden(cChannel , true);
|
|
ui->tree->setColumnHidden(cVersions, true);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::selectCurrentServer() {
|
|
if (!currentCompatible()) return;
|
|
auto si = currentServer();
|
|
if (!si.isValid()) return;
|
|
emit serverSelected(si);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::timerEvent(QTimerEvent * e) {
|
|
if (e) {
|
|
if (e->timerId() == timer_pip) {
|
|
maybeCallQueuedEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
QString SHServersTreeWidget::queryAddress() {
|
|
//if (!channel) return QString();
|
|
QSettings settings("SHS", "ServersTreeWidget");
|
|
QString addr = settings.value("last_address").toString();
|
|
bool ok = false;
|
|
addr = QInputDialog::getText(this, tr("Add server"), tr("Enter address:"), QLineEdit::Normal, addr, &ok);
|
|
if (!ok) return QString();
|
|
return addr;
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::loadServers() {
|
|
clear();
|
|
QSettings settings("SHS", "ServersTreeWidget");
|
|
auto sl = piqDeserialize<PIVector<PacketServerInfo>>(settings.value("servers").toByteArray());
|
|
for (const auto & s: sl) addServer(s);
|
|
fill();
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::saveServers(const PacketServerInfo & to_remove) {
|
|
auto tsl = servers_;
|
|
QSettings settings("SHS", "ServersTreeWidget");
|
|
auto ssl = piqDeserialize<PIVector<PacketServerInfo>>(settings.value("servers").toByteArray());
|
|
for (const auto & s: ssl) {
|
|
bool contains = false;
|
|
for (const auto & t: tsl) {
|
|
if (t.address.hash() == s.address.hash()) {
|
|
contains = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!contains)
|
|
tsl << s;
|
|
}
|
|
tsl.removeWhere([to_remove](const PacketServerInfo & i){return to_remove.address.hash() == i.address.hash();});
|
|
settings.setValue("servers", piqSerialize(tsl));
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::procGesture(QGesture * g) {
|
|
if (!g) return;
|
|
switch (g->gestureType()) {
|
|
case Qt::TapAndHoldGesture: {
|
|
QTapAndHoldGesture * pg = (QTapAndHoldGesture*)g;
|
|
if (pg->state() == Qt::GestureStarted)
|
|
qApp->postEvent(ui->tree->viewport(), new QContextMenuEvent(QContextMenuEvent::Mouse, ui->tree->viewport()->mapFromGlobal(QCursor::pos())));
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::clear() {
|
|
PIMutexLocker _ml(smutex);
|
|
qDeleteAll(channels.values());
|
|
channels.clear();
|
|
servers_.clear();
|
|
ui->tree->clear();
|
|
changed = false;
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::fill() {
|
|
if (!changed) return;
|
|
changed = false;
|
|
QMetaEnum ose = SHPlatform::staticMetaObject.enumerator(SHPlatform::staticMetaObject.indexOfEnumerator("OSType"));
|
|
QMetaEnum cte = SHNetworkTypes::staticMetaObject.enumerator(SHNetworkTypes::staticMetaObject.indexOfEnumerator("ChannelType"));
|
|
int vpos = ui->tree->verticalScrollBar()->value();
|
|
ui->tree->clear();
|
|
PIMutexLocker _ml(smutex);
|
|
for (const PacketServerInfo & i: servers_) {
|
|
QTreeWidgetItem * ti = new QTreeWidgetItem();
|
|
ti->setData(cServer, role_server_info, QVariant::fromValue(i));
|
|
ti->setData(cServer, role_compatible, i.compatible());
|
|
ti->setIcon(cServer, icon_state.value(iIncompatible));
|
|
ti->setText(cServer, PI2QString(i.name));
|
|
ti->setText(cHostname, PI2QString(i.identify.hostname));
|
|
ti->setText(cOS, QString("%1 (%2)").arg(ose.valueToKey(i.identify.os_type)).arg(PI2QString(i.identify.os_version)));
|
|
ti->setText(cChannel, cte.valueToKey(i.channel));
|
|
ti->setText(cIP, PI2QString(i.address.ipAddress()));
|
|
ti->setText(cVersions, PI2QString(i.displayVersion()));
|
|
ui->tree->addTopLevelItem(ti);
|
|
}
|
|
setIcons();
|
|
ui->tree->verticalScrollBar()->setValue(vpos);
|
|
ui->tree->header()->resizeSections(QHeaderView::ResizeToContents);
|
|
if (!loading) saveServers();
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::setIcons() {
|
|
for (int i = 0; i < ui->tree->topLevelItemCount(); ++i) {
|
|
auto si = ui->tree->topLevelItem(i)->data(cServer, role_server_info).value<PacketServerInfo>();
|
|
auto * ch = channels.value(si.address.hash(), nullptr);
|
|
bool is_compat = ui->tree->topLevelItem(i)->data(cServer, role_compatible).toBool();
|
|
bool is_alive = false;
|
|
if (ch) is_alive = ch->property(property_alive).toBool();
|
|
int icon = iOff;
|
|
if (is_alive) {
|
|
if (!is_compat)
|
|
icon = iIncompatible;
|
|
else {
|
|
if (si.name.isEmpty()) icon = iOnlineEmpty;
|
|
else icon = iOnline;
|
|
}
|
|
}
|
|
ui->tree->topLevelItem(i)->setIcon(cServer, icon_state.value(icon));
|
|
}
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::on_tree_customContextMenuRequested(const QPoint & pos) {
|
|
auto * ti = ui->tree->currentItem();
|
|
if (!ti) return;
|
|
popup_menu.popup(ui->tree->mapToGlobal(pos));
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::on_tree_doubleClicked(QModelIndex) {
|
|
if (!currentCompatible()) return;
|
|
auto info = currentServer();
|
|
if (!info.isValid() || info.connect_address.isEmpty()) return;
|
|
emit serverSelected(info);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::on_buttonAddServer_clicked() {
|
|
#ifdef Q_OS_ANDROID
|
|
QSettings settings("SHS", "ServersTreeWidget");
|
|
emit addressRequest(settings.value("last_address").toString());
|
|
#else
|
|
QString addr = queryAddress();
|
|
addressEntered(addr);
|
|
#endif
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::on_buttonUpdate_clicked() {
|
|
auto it = channels.makeIterator();
|
|
while (it.next()) {
|
|
auto * ch = it.value();
|
|
if (!ch) continue;
|
|
ch->setProperty(property_alive, false);
|
|
ch->dataStop();
|
|
ch->connectToServer(ch->property(property_address).value<ServerAddress>());
|
|
}
|
|
setIcons();
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::on_actionRemove_triggered() {
|
|
auto * ti = ui->tree->currentItem();
|
|
if (!ti) return;
|
|
auto si = ti->data(cServer, role_server_info).value<PacketServerInfo>();
|
|
uint addr_hash = si.address.hash();
|
|
auto * ch = channels.value(addr_hash, nullptr);
|
|
if (ch) {
|
|
ch->dataStop();
|
|
delete ch;
|
|
}
|
|
channels.remove(addr_hash);
|
|
servers_.removeWhere([addr_hash](const PacketServerInfo & i){return i.address.hash() == addr_hash;});
|
|
delete ti;
|
|
saveServers(si);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::addressEntered(QString addr) {
|
|
if (addr.isEmpty()) return;
|
|
QSettings settings("SHS", "ServersTreeWidget");
|
|
settings.setValue("last_address", addr);
|
|
requesting = true;
|
|
PacketServerInfo info;
|
|
info.connect_address = Q2PIString(addr);
|
|
info.address = parseServerAddress(addr);
|
|
addServer(info);
|
|
/*channel->dataStop();
|
|
channel->connectToServer(addr);*/
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::connected() {
|
|
if (!requesting) return;
|
|
channel->dataSend(SHNetworkTypes::makeHeader(SHNetworkTypes::ServerInfoRequest));
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::receiveEvent(PIByteArray data) {
|
|
if (!requesting) return;
|
|
piCout << "SHServersTreeWidget::receiveEvent" << data.toHex();
|
|
PacketHeader hdr = SHNetworkTypes::takeHeader(data);
|
|
if (hdr.type < 0) return;
|
|
switch ((SHNetworkTypes::PacketType)hdr.type) {
|
|
case SHNetworkTypes::ServerInfo: {
|
|
PacketServerInfo info;
|
|
data >> info;
|
|
info.address = channel->address();
|
|
info.connect_address = channel->address().fullAddress();
|
|
info.channel = channel->address().channel;
|
|
addServer(info);
|
|
channel->dataStop();
|
|
requesting = false;
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::addServer(const PacketServerInfo & info) {
|
|
if (info.connect_address.isEmpty()) return;
|
|
PIMutexLocker _ml(smutex);
|
|
for (const PacketServerInfo & i: servers_) {
|
|
if (i.address.hash() == info.address.hash())
|
|
return;
|
|
}
|
|
if (!loading) {
|
|
QMetaObject::invokeMethod(this, "fill", Qt::QueuedConnection);
|
|
}
|
|
servers_ << info;
|
|
changed = true;
|
|
auto *& channel(channels[info.address.hash()]);
|
|
if (channel) return;
|
|
channel = new SHClientChannel(true);
|
|
channel->setID(generateID());
|
|
channel->setProperty(property_address, PIVariant::fromValue(info.address));
|
|
CONNECTL(channel, connected, ([this,channel](int){
|
|
QMetaObject::invokeMethod(this, [channel](){
|
|
//piCout << "tree" << "connected";
|
|
channel->dataSend(SHNetworkTypes::makeHeader(SHNetworkTypes::ServerInfoRequest));
|
|
}, Qt::QueuedConnection);
|
|
}));
|
|
CONNECTL(channel, dataReceived, ([this,channel,info](PIByteArray data){
|
|
QMetaObject::invokeMethod(this, [this,channel,info,data](){
|
|
PIByteArray rec_data = data;
|
|
PacketHeader hdr = SHNetworkTypes::takeHeader(rec_data);
|
|
//piCout << "tree" << "received" << hdr.type;
|
|
if (hdr.type == SHNetworkTypes::ServerInfo) {
|
|
PacketServerInfo rec_info;
|
|
rec_data >> rec_info;
|
|
uint cur_addr_hash = info.address.hash();
|
|
PIMutexLocker _ml(smutex);
|
|
for (int i = 0; i < servers_.size_s(); ++i) {
|
|
//piCout << "tree" << "check" << servers_[i].address.fullAddress() << info.address.fullAddress();
|
|
if (servers_[i].address.hash() == cur_addr_hash) {
|
|
servers_[i].name = rec_info.name;
|
|
servers_[i].identify = rec_info.identify;
|
|
servers_[i].channel = channel->address().channel;
|
|
channel->setProperty(property_alive, true);
|
|
//piCout << "tree" << "update" << rec_info.name;
|
|
break;
|
|
}
|
|
}
|
|
changed = true;
|
|
QMetaObject::invokeMethod(this, "fill", Qt::QueuedConnection);
|
|
//piCout << "tree" << "fill request";
|
|
}
|
|
channel->stop();
|
|
}, Qt::QueuedConnection);
|
|
}));
|
|
channel->connectToServer(info);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::startBroadcast() {
|
|
bc_channel->start();
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::stopBroadcast() {
|
|
bc_channel->stop();
|
|
}
|
|
|
|
|
|
PIVector<PacketServerInfo> SHServersTreeWidget::servers() const {
|
|
PIMutexLocker _ml(smutex);
|
|
PIVector<PacketServerInfo> ret = servers_;
|
|
return ret;
|
|
}
|
|
|
|
|
|
PacketServerInfo SHServersTreeWidget::currentServer() const {
|
|
QTreeWidgetItem * ci = ui->tree->currentItem();
|
|
if (!ci)
|
|
return PacketServerInfo();
|
|
return ci->data(cServer, role_server_info).value<PacketServerInfo>();
|
|
}
|
|
|
|
|
|
bool SHServersTreeWidget::currentCompatible() const {
|
|
QTreeWidgetItem * ci = ui->tree->currentItem();
|
|
if (!ci)
|
|
return false;
|
|
return ci->data(cServer, role_compatible).toBool();
|
|
|
|
}
|
|
|
|
|
|
bool SHServersTreeWidget::event(QEvent * e) {
|
|
if (e->type() == QEvent::FontChange || e->type() == QEvent::Polish) {
|
|
double icon_scale = iconSizeMul;
|
|
#ifndef MOBILE_VIEW
|
|
icon_scale *= 0.7;
|
|
#endif
|
|
ui->tree->setIconSize(preferredIconSize(icon_scale, this));
|
|
ui->buttonAddServer->setIconSize(preferredIconSize(icon_scale, this));
|
|
ui->buttonUpdate->setIconSize(preferredIconSize(icon_scale, this));
|
|
}
|
|
if (e->type() == QEvent::Gesture) {
|
|
for (QGesture * g: ((QGestureEvent*)e)->gestures())
|
|
procGesture(g);
|
|
}
|
|
return QWidget::event(e);
|
|
}
|
|
|
|
|
|
void SHServersTreeWidget::broadcastReceiveEvent(PIByteArray data) {
|
|
PacketHeader hdr = SHNetworkTypes::takeHeader(data);
|
|
//piCout << "rec" << hdr.type;
|
|
if (hdr.type != SHNetworkTypes::Reply) return;
|
|
PacketServerInfo info;
|
|
data >> info;
|
|
info.channel = SHNetworkTypes::TCP;
|
|
info.address = parseServerAddress(PI2QString(info.connect_address));
|
|
addServer(info);
|
|
}
|