Kstars

portselector.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "portselector.h"
8
9#include "indipropertyswitch.h"
10#include "ksnotification.h"
11#include "ekos_debug.h"
12#include "kspaths.h"
13#include "indi/driverinfo.h"
14#include "indi/clientmanager.h"
15#include "Options.h"
16
17#include <QDialogButtonBox>
18#include <QPushButton>
19
20namespace Selector
21{
22
23const QStringList Device::BAUD_RATES = { "9600", "19200", "38400", "57600", "115200", "230400" };
24const QString Device::ACTIVE_STYLESHEET = "background-color: #004400;";
25//const QString Device::ACTIVE_STYLESHEET = "border-color: #007700; font-weight:bold;";
26//////////////////////////////////////////////////////////////////////////////////////////
27///
28//////////////////////////////////////////////////////////////////////////////////////////
29Device::Device(const QSharedPointer<ISD::GenericDevice> &device, QGridLayout *grid,
30 uint8_t row) : m_Name(device->getDeviceName()), m_Device(device), m_Grid(grid), m_Row(row)
31{
32 ColorCode[IPS_IDLE] = Qt::gray;
33 ColorCode[IPS_OK] = Qt::green;
34 ColorCode[IPS_BUSY] = Qt::yellow;
35 ColorCode[IPS_ALERT] = Qt::red;
36
37 connect(m_Device.get(), &ISD::GenericDevice::propertyUpdated, this, &Device::updateProperty);
38
39 if (initGUI())
40 syncGUI();
41}
42
43//////////////////////////////////////////////////////////////////////////////////////////
44///
45//////////////////////////////////////////////////////////////////////////////////////////
46bool Device::initGUI()
47{
48 INDI::PropertySwitch connectionMode = m_Device->getBaseDevice().getSwitch("CONNECTION_MODE");
49 if (!connectionMode.isValid())
50 return false;
51
52 m_LED = new KLed;
53 m_LED->setLook(KLed::Sunken);
54 m_LED->setState(KLed::On);
55 m_LED->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
56 m_Grid->addWidget(m_LED, m_Row, 0, Qt::AlignVCenter);
57
58 m_Label = new QLabel;
59 m_Label->setText(m_Name);
60 m_Label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
61 m_Grid->addWidget(m_Label, m_Row, 1, Qt::AlignLeft);
62
63 // Serial Button
64 m_SerialB = new QPushButton(i18n("Serial"));
65 m_SerialB->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
66 m_SerialB->setToolTip(i18n("Select <b>Serial</b> if your device is connected via Serial to USB adapter."));
67 m_Grid->addWidget(m_SerialB, m_Row, 2);
68 connect(m_SerialB, &QPushButton::clicked, this, [this]()
69 {
70 INDI::PropertySwitch connectionMode = m_Device->getBaseDevice().getSwitch("CONNECTION_MODE");
71 connectionMode.reset();
72 connectionMode[0].setState(ISS_ON);
73 m_Device->sendNewProperty(connectionMode);
74 });
75
76 // Network Button
77 m_NetworkB = new QPushButton(i18n("Network"));
78 m_NetworkB->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
79 m_NetworkB->setToolTip(i18n("Select <b>Network</b> if your device is connected via Ethernet or WiFi."));
80 m_Grid->addWidget(m_NetworkB, m_Row, 3);
81 connect(m_NetworkB, &QPushButton::clicked, this, [this]()
82 {
83 INDI::PropertySwitch connectionMode = m_Device->getBaseDevice().getSwitch("CONNECTION_MODE");
84 connectionMode.reset();
85 connectionMode[1].setState(ISS_ON);
86 m_Device->sendNewProperty(connectionMode);
87 });
88
89 if (connectionMode.findWidgetByName("CONNECTION_SERIAL"))
90 {
91 if (m_Device->getBaseDevice().getProperty("SYSTEM_PORTS").isValid())
92 {
93 m_Ports = new QComboBox;
94 m_Ports->setEditable(true);
96 m_Ports->setToolTip(i18n("Select Serial port"));
97
98 INDI::PropertySwitch systemPorts = m_Device->getBaseDevice().getSwitch("SYSTEM_PORTS");
99 for (auto &oneSwitch : systemPorts)
100 m_Ports->addItem(oneSwitch.getLabel());
101 connect(m_Ports, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this,
102 [this](int index)
103 {
104 // Double check property still exists.
105 INDI::PropertySwitch systemPorts = m_Device->getBaseDevice().getProperty("SYSTEM_PORTS");
106 if (systemPorts.isValid())
107 {
108 systemPorts.reset();
109 systemPorts[index].setState(ISS_ON);
110 m_Device->sendNewProperty(systemPorts);
111 }
112 });
113 // When combo box text changes
114 connect(m_Ports->lineEdit(), &QLineEdit::editingFinished, this, [this]()
115 {
116 INDI::PropertyText port = m_Device->getBaseDevice().getText("DEVICE_PORT");
117 port[0].setText(m_Ports->lineEdit()->text().toLatin1().constData());
118 m_Device->sendNewProperty(port);
119 });
120 }
121
122 if (m_Device->getBaseDevice().getProperty("DEVICE_BAUD_RATE").isValid())
123 {
124 m_BaudRates = new QComboBox;
125 m_BaudRates->addItems(BAUD_RATES);
126 m_BaudRates->setToolTip(i18n("Select Baud rate"));
127 connect(m_BaudRates, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this,
128 [this](int index)
129 {
130 INDI::PropertySwitch systemBauds = m_Device->getBaseDevice().getSwitch("DEVICE_BAUD_RATE");
131 systemBauds.reset();
132 systemBauds[index].setState(ISS_ON);
133 m_Device->sendNewProperty(systemBauds);
134 });
135 }
136 }
137 else
138 m_SerialB->setEnabled(false);
139
140 if (connectionMode.findWidgetByName("CONNECTION_TCP"))
141 {
142 m_HostName = new QLineEdit;
143 m_HostName->setPlaceholderText(i18n("Host name or IP address."));
144 m_HostName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
145 connect(m_HostName, &QLineEdit::editingFinished, this, [this]()
146 {
147 INDI::PropertyText server = m_Device->getBaseDevice().getText("DEVICE_ADDRESS");
148 server[0].setText(m_HostName->text().toLatin1().constData());
149 m_Device->sendNewProperty(server);
150 });
151
152 m_HostPort = new QLineEdit;
153 m_HostPort->setPlaceholderText(i18n("Port"));
154 connect(m_HostPort, &QLineEdit::editingFinished, this, [this]()
155 {
156 auto server = m_Device->getProperty("DEVICE_ADDRESS").getText();
157 server->at(1)->setText(m_HostPort->text().toLatin1().constData());
158 m_Device->sendNewProperty(server);
159 });
160
161 m_HostProtocolTCP = new QPushButton("TCP");
162 connect(m_HostProtocolTCP, &QPushButton::clicked, this, [this]()
163 {
164 auto connectionType = m_Device->getProperty("CONNECTION_TYPE").getSwitch();
165 connectionType->reset();
166 connectionType->at(0)->setState(ISS_ON);
167 m_Device->sendNewProperty(connectionType);
168 });
169
170 m_HostProtocolUDP = new QPushButton("UDP");
171 connect(m_HostProtocolUDP, &QPushButton::clicked, this, [this]()
172 {
173 auto connectionType = m_Device->getProperty("CONNECTION_TYPE").getSwitch();
174 connectionType->reset();
175 connectionType->at(1)->setState(ISS_ON);
176 m_Device->sendNewProperty(connectionType);
177 });
178 }
179 else
180 m_NetworkB->setEnabled(false);
181
182 m_ConnectB = new QPushButton;
183 m_ConnectB->setFixedSize(QSize(28, 28));
184 m_ConnectB->setToolTip(i18n("Connect"));
185 m_ConnectB->setIcon(QIcon::fromTheme("network-connect"));
186 m_Grid->addWidget(m_ConnectB, m_Row, 8);
187 connect(m_ConnectB, &QPushButton::clicked, m_Device.get(), &ISD::GenericDevice::Connect);
188
189 m_DisconnectB = new QPushButton;
190 m_DisconnectB->setFixedSize(QSize(28, 28));
191 m_DisconnectB->setToolTip(i18n("Disconnect"));
192 m_DisconnectB->setIcon(QIcon::fromTheme("network-disconnect"));
193 m_Grid->addWidget(m_DisconnectB, m_Row, 9);
194 connect(m_DisconnectB, &QPushButton::clicked, m_Device.get(), &ISD::GenericDevice::Disconnect);
195
196 setConnectionMode(static_cast<ConnectionMode>(connectionMode.findOnSwitchIndex()));
197
198 return true;
199}
200
201//////////////////////////////////////////////////////////////////////////////////////////
202///
203//////////////////////////////////////////////////////////////////////////////////////////
204void Device::syncGUI()
205{
206 // Check if initGUI failed before?
207 if (m_LED == nullptr)
208 {
209 // Try to initiGUI again
210 initGUI();
211
212 // If it failed again, return.
213 if (m_LED == nullptr)
214 return;
215 }
216
217 m_LED->setColor(ColorCode.value(m_Device->getState("CONNECTION")));
218
219 auto connectionMode = m_Device->getProperty("CONNECTION_MODE").getSwitch();
220
221 if (connectionMode->findOnSwitchIndex() == CONNECTION_SERIAL)
222 {
223 m_ActiveConnectionMode = CONNECTION_SERIAL;
224 m_SerialB->setStyleSheet(ACTIVE_STYLESHEET);
225 m_NetworkB->setStyleSheet(QString());
226
227 if (m_Ports)
228 {
229 auto port = m_Device->getProperty("DEVICE_PORT").getText();
230 m_Ports->setEditText(port->at(0)->getText());
231 }
232
233 if (m_BaudRates)
234 {
235 auto systemBauds = m_Device->getProperty("DEVICE_BAUD_RATE").getSwitch();
236 m_BaudRates->setCurrentIndex(systemBauds->findOnSwitchIndex());
237 }
238 }
239 else
240 {
241 m_ActiveConnectionMode = CONNECTION_NETWORK;
242 m_SerialB->setStyleSheet(QString());
243 m_NetworkB->setStyleSheet(ACTIVE_STYLESHEET);
244
245 if (m_Device->getProperty("DEVICE_ADDRESS").isValid())
246 {
247 auto server = m_Device->getProperty("DEVICE_ADDRESS").getText();
248 m_HostName->setText(server->at(0)->getText());
249 m_HostPort->setText(server->at(1)->getText());
250 }
251
252 if (m_Device->getProperty("CONNECTION_TYPE").isValid())
253 {
254 auto connectionType = m_Device->getBaseDevice().getSwitch("CONNECTION_TYPE").getSwitch();
255 m_HostProtocolTCP->setStyleSheet(connectionType->findOnSwitchIndex() == 0 ? ACTIVE_STYLESHEET : QString());
256 m_HostProtocolUDP->setStyleSheet(connectionType->findOnSwitchIndex() == 1 ? ACTIVE_STYLESHEET : QString());
257 }
258 }
259}
260
261//////////////////////////////////////////////////////////////////////////////////////////
262///
263//////////////////////////////////////////////////////////////////////////////////////////
264uint8_t Device::systemPortCount() const
265{
266 if (m_Ports)
267 return m_Ports->count();
268 else
269 return 0;
270}
271
272//////////////////////////////////////////////////////////////////////////////////////////
273///
274//////////////////////////////////////////////////////////////////////////////////////////
275void Device::setConnectionMode(ConnectionMode mode)
276{
277 m_ActiveConnectionMode = mode;
278
279 if (mode == CONNECTION_SERIAL)
280 {
281 if (m_HostName)
282 {
283 m_Grid->removeWidget(m_HostName);
284 m_HostName->setParent(nullptr);
285 m_Grid->removeWidget(m_HostPort);
286 m_HostPort->setParent(nullptr);
287 m_Grid->removeWidget(m_HostProtocolTCP);
288 m_HostProtocolTCP->setParent(nullptr);
289 m_Grid->removeWidget(m_HostProtocolUDP);
290 m_HostProtocolUDP->setParent(nullptr);
291 }
292
293 m_Grid->addWidget(m_Ports, m_Row, 4, 1, 2);
294 m_Grid->addWidget(m_BaudRates, m_Row, 6, 1, 2);
295 m_Grid->setColumnStretch(4, 2);
296 }
297 else if (mode == CONNECTION_NETWORK)
298 {
299 if (m_Ports)
300 {
301 m_Grid->removeWidget(m_Ports);
302 m_Ports->setParent(nullptr);
303 m_Grid->removeWidget(m_BaudRates);
304 m_BaudRates->setParent(nullptr);
305 }
306
307 m_Grid->addWidget(m_HostName, m_Row, 4);
308 m_Grid->addWidget(m_HostPort, m_Row, 5);
309 m_Grid->addWidget(m_HostProtocolTCP, m_Row, 6);
310 m_Grid->addWidget(m_HostProtocolUDP, m_Row, 7);
311 m_Grid->setColumnStretch(4, 2);
312 }
313}
314
315//////////////////////////////////////////////////////////////////////////////////////////
316///
317//////////////////////////////////////////////////////////////////////////////////////////
318void Device::updateProperty(INDI::Property prop)
319{
320 if (prop.isNameMatch("CONNECTION_MODE"))
321 {
322 auto svp = prop.getSwitch();
323 setConnectionMode(static_cast<ConnectionMode>(svp->findOnSwitchIndex()));
324 syncGUI();
325 }
326 else if (prop.isNameMatch("CONNECTION_TYPE") || prop.isNameMatch("SYSTEM_PORTS") ||
327 prop.isNameMatch("DEVICE_BAUD_RATE") || prop.isNameMatch("DEVICE_PORT"))
328 syncGUI();
329 else if (prop.isNameMatch("CONNECTION") && m_ConnectB)
330 {
331 if (m_Device->isConnected())
332 {
333 m_ConnectB->setStyleSheet(ACTIVE_STYLESHEET);
334 m_DisconnectB->setStyleSheet(QString());
335 }
336 else
337 {
338 m_ConnectB->setStyleSheet(QString());
339 m_DisconnectB->setStyleSheet(ACTIVE_STYLESHEET);
340 }
341 }
342}
343
344//////////////////////////////////////////////////////////////////////////////////////////
345///
346//////////////////////////////////////////////////////////////////////////////////////////
347void Device::setConnected()
348{
349 syncGUI();
350}
351
352//////////////////////////////////////////////////////////////////////////////////////////
353///
354//////////////////////////////////////////////////////////////////////////////////////////
355void Device::setDisconnected()
356{
357 syncGUI();
358}
359
360//////////////////////////////////////////////////////////////////////////////////////////
361///
362//////////////////////////////////////////////////////////////////////////////////////////
363Dialog::Dialog(QWidget *parent) : QDialog(parent)
364{
365 QVBoxLayout *mainLayout = new QVBoxLayout(this);
366 QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
367
368 QPushButton *connectAll = new QPushButton(i18n("Connect All"), this);
369 buttonBox->addButton(connectAll, QDialogButtonBox::AcceptRole);
371
372 connect(buttonBox, &QDialogButtonBox::accepted, this, &Dialog::accept);
373 connect(buttonBox, &QDialogButtonBox::rejected, this, &Dialog::reject);
374
375 m_Layout = new QGridLayout;
376
377 mainLayout->addLayout(m_Layout);
378 mainLayout->addStretch();
379 mainLayout->addWidget(buttonBox);
380
381 setWindowTitle(i18nc("@title:window", "Port Selector"));
382#ifdef Q_OS_MACOS
383 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
384#endif
385}
386
387//////////////////////////////////////////////////////////////////////////////////////////
388///
389//////////////////////////////////////////////////////////////////////////////////////////
390void Dialog::addDevice(const QSharedPointer<ISD::GenericDevice> &device)
391{
392 auto pos = std::find_if(m_Devices.begin(), m_Devices.end(), [device](std::unique_ptr<Device> &oneDevice)
393 {
394 return oneDevice->name() == device->getDeviceName();
395 });
396
397 // New
398 if (pos == m_Devices.end())
399 {
400 std::unique_ptr<Device> dev(new Device(device, m_Layout, m_Devices.size()));
401 m_Devices.push_back(std::move(dev));
402 }
403 // Existing device
404 else
405 {
406 (*pos)->syncGUI();
407 }
408
409}
410
411//////////////////////////////////////////////////////////////////////////////////////////
412///
413//////////////////////////////////////////////////////////////////////////////////////////
414void Dialog::removeDevice(const QString &name)
415{
416 m_Devices.erase(std::remove_if(m_Devices.begin(), m_Devices.end(), [name](const auto & oneDevice)
417 {
418 return oneDevice->name() == name;
419 }), m_Devices.end());
420}
421
422//////////////////////////////////////////////////////////////////////////////////////////
423/// Should Port Selector be shown automatically?
424/// If we only have ONE device with ONE port, then no need to bother the user
425/// with useless selection.
426//////////////////////////////////////////////////////////////////////////////////////////
427bool Dialog::shouldShow() const
428{
429 if (m_Devices.empty())
430 return false;
431
432 uint32_t systemPortCount = 0;
433 uint32_t serialDevices = 0;
434 uint32_t networkDevices = 0;
435
436 for (const auto &oneDevice : m_Devices)
437 {
438 if (oneDevice->systemPortCount() > 0)
439 systemPortCount = oneDevice->systemPortCount();
440
441 if (oneDevice->activeConnectionMode() == CONNECTION_SERIAL)
442 serialDevices++;
443 else if (oneDevice->activeConnectionMode() == CONNECTION_NETWORK)
444 networkDevices++;
445 }
446
447 // If we just have a single serial device with single port, no need to toggle port selector.
448 // If have have one or more network devices, we must show the dialog for the first time.
449 if (networkDevices == 0 && serialDevices <= 1 && systemPortCount <= 1)
450 return false;
451
452 // Otherwise, show dialog
453 return true;
454}
455}
void setLook(Look look)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void clicked(bool checked)
void addLayout(QLayout *layout, int stretch)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void activated(int index)
void addItems(const QStringList &texts)
void setEditable(bool editable)
QPushButton * addButton(StandardButton button)
QIcon fromTheme(const QString &name)
void setText(const QString &)
void editingFinished()
void setPlaceholderText(const QString &)
AlignVCenter
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setFixedSize(const QSize &s)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 12:02:37 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.