Files
SHS/src/libs/shared/SH_servers_tree_widget.cpp

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);
}