File indexing completed on 2024-04-28 09:41:53
0001 // SPDX-License-Identifier: GPL-3.0-or-later 0002 /* 0003 Copyright 2017 - 2023 Martin Koller, kollix@aon.at 0004 0005 This file is part of liquidshell. 0006 0007 liquidshell is free software: you can redistribute it and/or modify 0008 it under the terms of the GNU General Public License as published by 0009 the Free Software Foundation, either version 3 of the License, or 0010 (at your option) any later version. 0011 0012 liquidshell is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 GNU General Public License for more details. 0016 0017 You should have received a copy of the GNU General Public License 0018 along with liquidshell. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include <DeviceList.hxx> 0022 #include <IconButton.hxx> 0023 #include <Battery.hxx> 0024 0025 #include <Solid/DeviceNotifier> 0026 #include <Solid/DeviceInterface> 0027 #include <Solid/StorageAccess> 0028 #include <Solid/StorageVolume> 0029 #include <Solid/StorageDrive> 0030 #include <Solid/Block> 0031 #include <Solid/Battery> 0032 0033 #include <QHBoxLayout> 0034 #include <QIcon> 0035 #include <QStandardPaths> 0036 #include <QDir> 0037 #include <QFileInfo> 0038 #include <QScrollBar> 0039 #include <QDebug> 0040 0041 #include <KLocalizedString> 0042 #include <KDesktopFile> 0043 #include <KDesktopFileActions> 0044 #include <KConfigGroup> 0045 #include <KService> 0046 0047 #include <kio_version.h> 0048 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0049 # include <KIO/CommandLauncherJob> 0050 # include <KIO/JobUiDelegateFactory> 0051 #endif 0052 #include <KRun> 0053 0054 #include <kio/global.h> 0055 0056 //-------------------------------------------------------------------------------- 0057 0058 DeviceItem::DeviceItem(Solid::Device dev, const QVector<DeviceAction> &deviceActions) 0059 : device(dev) 0060 { 0061 setFrameShape(QFrame::StyledPanel); 0062 0063 QVBoxLayout *vbox = new QVBoxLayout(this); 0064 QHBoxLayout *hbox = new QHBoxLayout; 0065 vbox->addLayout(hbox); 0066 0067 QLabel *iconLabel = new QLabel; 0068 iconLabel->setPixmap(QIcon::fromTheme(device.icon()).pixmap(32)); 0069 iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0070 0071 newFlagLabel = new QLabel; 0072 newFlagLabel->setPixmap(QIcon::fromTheme("emblem-important").pixmap(16)); 0073 newFlagLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0074 newFlagLabel->hide(); 0075 0076 textLabel = new QLabel; 0077 textLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); 0078 textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0079 0080 hbox->addWidget(iconLabel, 0, Qt::AlignLeft | Qt::AlignVCenter); 0081 hbox->addWidget(newFlagLabel, 0, Qt::AlignCenter); 0082 hbox->addWidget(textLabel, 0, Qt::AlignVCenter); 0083 0084 Solid::StorageAccess *storage = device.as<Solid::StorageAccess>(); 0085 0086 if ( storage ) 0087 { 0088 connect(storage, &Solid::StorageAccess::teardownDone, this, &DeviceItem::teardownDone); 0089 connect(storage, &Solid::StorageAccess::setupDone, this, &DeviceItem::setupDone); 0090 0091 mountBusyTimer.setInterval(500); 0092 connect(&mountBusyTimer, &QTimer::timeout, this, 0093 [this]() { mountButton->setVisible(!mountButton->isVisible()); }); 0094 0095 mountButton = new QToolButton; 0096 mountButton->setIconSize(QSize(32, 32)); 0097 0098 connect(mountButton, &QToolButton::clicked, mountButton, 0099 [this]() 0100 { 0101 statusLabel->hide(); 0102 mountButton->setEnabled(false); 0103 mountBusyTimer.start(); 0104 0105 Solid::StorageAccess *storage = device.as<Solid::StorageAccess>(); 0106 0107 if ( storage->isAccessible() ) // mounted -> unmount it 0108 storage->teardown(); 0109 else 0110 storage->setup(); 0111 } 0112 ); 0113 0114 0115 hbox->addWidget(mountButton); 0116 } 0117 0118 statusLabel = new QLabel; 0119 0120 if ( device.is<Solid::Battery>() ) 0121 { 0122 hbox->addWidget(statusLabel, 0, Qt::AlignVCenter); 0123 0124 chargeIcon = new QLabel; 0125 hbox->addWidget(chargeIcon, 0, Qt::AlignVCenter); 0126 0127 Solid::Battery *battery = device.as<Solid::Battery>(); 0128 0129 if ( battery->chargePercent() >= 0 ) 0130 { 0131 statusLabel->setText(QString::number(battery->chargePercent()) + '%'); 0132 chargeIcon->setPixmap(Battery::getStatusIcon(battery->chargePercent(), 0133 battery->chargeState() == Solid::Battery::Charging).pixmap(22)); 0134 } 0135 0136 connect(battery, &Solid::Battery::chargePercentChanged, this, 0137 [this, battery]() 0138 { 0139 statusLabel->setText(QString::number(battery->chargePercent()) + '%'); 0140 chargeIcon->setPixmap(Battery::getStatusIcon(battery->chargePercent(), 0141 battery->chargeState() == Solid::Battery::Charging).pixmap(22)); 0142 }); 0143 } 0144 else 0145 { 0146 statusLabel->setWordWrap(true); 0147 statusLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0148 statusLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); 0149 vbox->addWidget(statusLabel); 0150 statusLabel->hide(); 0151 0152 statusTimer.setSingleShot(true); 0153 statusTimer.setInterval(60000); 0154 connect(&statusTimer, &QTimer::timeout, statusLabel, &QLabel::hide); 0155 } 0156 0157 fillData(); 0158 0159 // append actions 0160 for (const DeviceAction &action : deviceActions) 0161 { 0162 QHBoxLayout *hbox = new QHBoxLayout; 0163 hbox->addSpacing(iconLabel->sizeHint().width()); 0164 0165 IconButton *button = new IconButton; 0166 button->setIcon(QIcon::fromTheme(action.action.icon())); 0167 button->setText(action.action.text() + " (" + QFileInfo(action.path).baseName() + ")"); 0168 0169 connect(button, &IconButton::clicked, button, 0170 [action, this]() 0171 { 0172 QString command = action.action.exec(); 0173 0174 if ( device.is<Solid::Block>() ) 0175 command.replace("%d", device.as<Solid::Block>()->device()); 0176 0177 command.replace("%i", device.udi()); 0178 0179 Solid::StorageAccess *storage = device.as<Solid::StorageAccess>(); 0180 if ( storage ) 0181 { 0182 if ( !storage->isAccessible() ) 0183 { 0184 statusLabel->hide(); 0185 storage->setup(); 0186 pendingCommand = command; 0187 return; 0188 } 0189 0190 command.replace("%f", storage->filePath()); 0191 } 0192 0193 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0194 auto *job = new KIO::CommandLauncherJob(command); 0195 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); 0196 job->start(); 0197 #else 0198 KRun::runCommand(command, this); 0199 #endif 0200 window()->hide(); 0201 } 0202 ); 0203 0204 hbox->addWidget(button); 0205 0206 vbox->addLayout(hbox); 0207 } 0208 } 0209 0210 //-------------------------------------------------------------------------------- 0211 0212 DeviceItem::DeviceItem(const KdeConnect::Device &dev) 0213 { 0214 setFrameShape(QFrame::StyledPanel); 0215 0216 QVBoxLayout *vbox = new QVBoxLayout(this); 0217 QHBoxLayout *hbox = new QHBoxLayout; 0218 vbox->addLayout(hbox); 0219 0220 QLabel *iconLabel = new QLabel; 0221 iconLabel->setPixmap(dev->icon.pixmap(32)); 0222 iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0223 0224 newFlagLabel = new QLabel; 0225 newFlagLabel->setPixmap(QIcon::fromTheme("emblem-important").pixmap(16)); 0226 newFlagLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0227 newFlagLabel->hide(); 0228 0229 textLabel = new QLabel; 0230 textLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); 0231 textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0232 0233 hbox->addWidget(iconLabel, 0, Qt::AlignLeft | Qt::AlignVCenter); 0234 hbox->addWidget(newFlagLabel, 0, Qt::AlignCenter); 0235 hbox->addWidget(textLabel, 0, Qt::AlignVCenter); 0236 0237 statusLabel = new QLabel; 0238 hbox->addWidget(statusLabel, 0, Qt::AlignVCenter); 0239 0240 chargeIcon = new QLabel; 0241 hbox->addWidget(chargeIcon, 0, Qt::AlignVCenter); 0242 0243 ringButton = new QToolButton; 0244 ringButton->setIcon(QIcon::fromTheme("preferences-desktop-notification-bell")); 0245 connect(ringButton, &QToolButton::clicked, dev.data(), [dev]() { dev->ringPhone(); }); 0246 hbox->addWidget(ringButton, 0, Qt::AlignVCenter); 0247 0248 kdeConnectDeviceChanged(dev); 0249 0250 connect(dev.data(), &KdeConnectDevice::changed, this, [this, dev]() { kdeConnectDeviceChanged(dev); }); 0251 0252 QToolButton *configure = new QToolButton; 0253 configure->setIcon(QIcon::fromTheme("configure")); 0254 connect(configure, &QToolButton::clicked, configure, 0255 [this]() 0256 { 0257 if ( !dialog ) 0258 { 0259 dialog = new KCMultiDialog(nullptr); 0260 dialog->setAttribute(Qt::WA_DeleteOnClose); 0261 0262 KPluginMetaData data("kcm_kdeconnect"); 0263 0264 if ( data.isValid() ) 0265 dialog->addModule(data); 0266 else 0267 dialog->addModule("kcm_kdeconnect"); 0268 0269 dialog->setWindowTitle(i18n("KDE Connect")); 0270 dialog->adjustSize(); 0271 } 0272 dialog->show(); 0273 }); 0274 0275 hbox->addWidget(configure, 0, Qt::AlignVCenter); 0276 0277 textLabel->setText(dev->name); 0278 0279 if ( dev->plugins.contains("kdeconnect_sftp") ) 0280 { 0281 QHBoxLayout *hbox = new QHBoxLayout; 0282 hbox->addSpacing(iconLabel->sizeHint().width()); 0283 0284 IconButton *button = new IconButton; 0285 button->setIcon(QIcon::fromTheme("system-file-manager")); 0286 button->setText(i18n("Open with File Manager")); 0287 0288 connect(button, &IconButton::clicked, 0289 [dev]() { new KRun(QUrl(QLatin1String("kdeconnect://") + dev->id), nullptr); }); 0290 0291 hbox->addWidget(button); 0292 0293 vbox->addLayout(hbox); 0294 } 0295 } 0296 0297 //-------------------------------------------------------------------------------- 0298 0299 void DeviceItem::kdeConnectDeviceChanged(const KdeConnect::Device &dev) 0300 { 0301 textLabel->setText(dev->name); 0302 0303 if ( dev->charge >= 0 ) 0304 { 0305 statusLabel->setText(QString::number(dev->charge) + '%'); 0306 chargeIcon->setPixmap(dev->chargeIcon.pixmap(22)); 0307 statusLabel->show(); 0308 chargeIcon->show(); 0309 } 0310 else 0311 { 0312 statusLabel->hide(); 0313 chargeIcon->hide(); 0314 } 0315 0316 ringButton->setVisible(dev->plugins.contains("kdeconnect_findmyphone")); 0317 } 0318 0319 //-------------------------------------------------------------------------------- 0320 0321 void DeviceItem::markAsNew() 0322 { 0323 newFlagLabel->show(); 0324 QTimer::singleShot(5000, newFlagLabel, &QLabel::hide); 0325 } 0326 0327 //-------------------------------------------------------------------------------- 0328 0329 void DeviceItem::fillData() 0330 { 0331 Solid::StorageAccess *storage = device.as<Solid::StorageAccess>(); 0332 QString text = device.description(); 0333 0334 if ( !device.product().isEmpty() && (device.product() != text) ) 0335 text += " (" + device.product() + ")"; 0336 else if ( !device.vendor().isEmpty() && (device.vendor() != text) ) 0337 text += " (" + device.vendor() + ")"; 0338 0339 Solid::StorageVolume *volume = device.as<Solid::StorageVolume>(); 0340 if ( volume ) 0341 text += " " + KIO::convertSize(volume->size()); 0342 0343 if ( storage && !storage->filePath().isEmpty() ) 0344 text += '\n' + storage->filePath(); 0345 0346 textLabel->setText(text); 0347 0348 if ( mountButton ) 0349 { 0350 if ( storage && storage->isAccessible() ) 0351 mountButton->setIcon(QIcon::fromTheme("media-eject")); 0352 else 0353 { 0354 if ( device.emblems().count() ) 0355 mountButton->setIcon(QIcon::fromTheme(device.emblems()[0])); 0356 else 0357 mountButton->setIcon(QIcon::fromTheme("emblem-unmounted")); 0358 } 0359 0360 if ( storage ) 0361 { 0362 mountButton->setToolTip(storage->isAccessible() ? 0363 i18n("Device is mounted.\nClick to unmount/eject") : 0364 i18n("Device is unmounted.\nClick to mount")); 0365 } 0366 } 0367 } 0368 0369 //-------------------------------------------------------------------------------- 0370 0371 QString DeviceItem::errorToString(Solid::ErrorType error) 0372 { 0373 switch ( error ) 0374 { 0375 case Solid::UnauthorizedOperation: return i18n("Unauthorized Operation"); 0376 case Solid::DeviceBusy: return i18n("Device Busy"); 0377 case Solid::OperationFailed: return i18n("Operation Failed"); 0378 case Solid::UserCanceled: return i18n("User Canceled"); 0379 case Solid::InvalidOption: return i18n("Invalid Option"); 0380 case Solid::MissingDriver: return i18n("Missing Driver"); 0381 0382 default: return QString(); 0383 } 0384 } 0385 0386 //-------------------------------------------------------------------------------- 0387 0388 void DeviceItem::mountDone(Action action, Solid::ErrorType error, QVariant errorData, const QString &udi) 0389 { 0390 Q_UNUSED(udi) 0391 0392 mountBusyTimer.stop(); 0393 mountButton->setEnabled(true); 0394 mountButton->setVisible(true); 0395 0396 if ( error == Solid::NoError ) 0397 { 0398 fillData(); 0399 0400 if ( action == Unmount ) 0401 { 0402 statusLabel->setText(i18n("The device can now be safely removed")); 0403 statusLabel->show(); 0404 statusTimer.start(); 0405 } 0406 } 0407 else 0408 { 0409 QString text = (action == Mount) ? i18n("Mount failed:") : i18n("Unmount failed:"); 0410 statusLabel->setText("<b>" + text + "</b>" + errorToString(error) + "<br>" + errorData.toString()); 0411 statusLabel->show(); 0412 statusTimer.start(); 0413 } 0414 } 0415 0416 //-------------------------------------------------------------------------------- 0417 0418 void DeviceItem::teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi) 0419 { 0420 mountDone(Unmount, error, errorData, udi); 0421 } 0422 0423 //-------------------------------------------------------------------------------- 0424 0425 void DeviceItem::setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi) 0426 { 0427 mountDone(Mount, error, errorData, udi); 0428 0429 if ( !pendingCommand.isEmpty() ) 0430 { 0431 if ( error == Solid::NoError ) 0432 { 0433 Solid::StorageAccess *storage = device.as<Solid::StorageAccess>(); 0434 if ( storage ) // should always be true. paranoid check 0435 { 0436 pendingCommand.replace("%f", storage->filePath()); 0437 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0438 auto *job = new KIO::CommandLauncherJob(pendingCommand); 0439 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); 0440 job->start(); 0441 #else 0442 KRun::runCommand(pendingCommand, this); 0443 #endif 0444 window()->hide(); 0445 } 0446 } 0447 pendingCommand.clear(); 0448 } 0449 } 0450 0451 //-------------------------------------------------------------------------------- 0452 //-------------------------------------------------------------------------------- 0453 //-------------------------------------------------------------------------------- 0454 0455 DeviceList::DeviceList(QWidget *parent) 0456 : QScrollArea(parent) 0457 { 0458 setWindowFlags(windowFlags() | Qt::Tool); 0459 setFrameShape(QFrame::StyledPanel); 0460 setAttribute(Qt::WA_AlwaysShowToolTips); 0461 setWidgetResizable(true); 0462 0463 loadActions(); 0464 0465 QWidget *widget = new QWidget; 0466 0467 vbox = new QVBoxLayout(widget); 0468 vbox->setContentsMargins(QMargins()); 0469 vbox->addStretch(); 0470 vbox->setSizeConstraint(QLayout::SetMinAndMaxSize); 0471 setWidget(widget); 0472 0473 predicate = Solid::Predicate(Solid::DeviceInterface::StorageAccess); 0474 predicate |= Solid::Predicate(Solid::DeviceInterface::StorageDrive); 0475 predicate |= Solid::Predicate(Solid::DeviceInterface::StorageVolume); 0476 predicate |= Solid::Predicate(Solid::DeviceInterface::OpticalDrive); 0477 predicate |= Solid::Predicate(Solid::DeviceInterface::OpticalDisc); 0478 predicate |= Solid::Predicate(Solid::DeviceInterface::PortableMediaPlayer); 0479 predicate |= Solid::Predicate(Solid::DeviceInterface::Camera); 0480 predicate |= Solid::Predicate(Solid::DeviceInterface::Battery); // non-primary batteries, e.g. mouse 0481 0482 QList<Solid::Device> devices = Solid::Device::listFromQuery(predicate); 0483 0484 for (Solid::Device device : devices) 0485 addDevice(device); 0486 0487 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, 0488 this, &DeviceList::deviceAdded); 0489 0490 connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, 0491 this, &DeviceList::deviceRemoved); 0492 0493 connect(&kdeConnect, &KdeConnect::deviceAdded, this, &DeviceList::kdeConnectDeviceAdded); 0494 connect(&kdeConnect, &KdeConnect::deviceRemoved, this, &DeviceList::deviceRemoved); 0495 } 0496 0497 //-------------------------------------------------------------------------------- 0498 0499 QSize DeviceList::sizeHint() const 0500 { 0501 // avoid horizontal scrollbar when the list is higher than 2/3 of the screen 0502 // where a vertical scrollbar will be shown, reducing the available width 0503 // leading to also getting a horizontal scrollbar 0504 QSize s = widget()->sizeHint() + QSize(2 * frameWidth(), 2 * frameWidth()); 0505 s.setWidth(s.width() + verticalScrollBar()->sizeHint().width()); 0506 return s; 0507 } 0508 0509 //-------------------------------------------------------------------------------- 0510 0511 void DeviceList::loadActions() 0512 { 0513 actions.clear(); 0514 0515 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "solid/actions", QStandardPaths::LocateDirectory); 0516 for (const QString &dirPath : dirs) 0517 { 0518 QDir dir(dirPath); 0519 0520 for (const QString &file : dir.entryList(QStringList(QLatin1String("*.desktop")), QDir::Files)) 0521 { 0522 QString path = dir.absoluteFilePath(file); 0523 KDesktopFile cfg(path); 0524 const QString predicateString = cfg.desktopGroup().readEntry("X-KDE-Solid-Predicate"); 0525 0526 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0527 QList<KServiceAction> actionList = KDesktopFileActions::userDefinedServices(KService(path), true); 0528 #else 0529 QList<KServiceAction> actionList = KDesktopFileActions::userDefinedServices(path, true); 0530 #endif 0531 0532 if ( !actionList.isEmpty() && !predicateString.isEmpty() ) 0533 actions.append(DeviceAction(path, Solid::Predicate::fromString(predicateString), actionList[0])); 0534 } 0535 } 0536 } 0537 0538 //-------------------------------------------------------------------------------- 0539 0540 DeviceItem *DeviceList::addDevice(Solid::Device device) 0541 { 0542 if ( items.contains(device.udi()) ) 0543 { 0544 //qDebug() << device.udi() << "already known"; 0545 return nullptr; 0546 } 0547 0548 QVector<DeviceAction> deviceActions; 0549 0550 for (const DeviceAction &action : actions) 0551 { 0552 if ( action.predicate.matches(device) ) 0553 deviceActions.append(action); 0554 } 0555 0556 if ( device.is<Solid::Battery>() && (device.as<Solid::Battery>()->type() != Solid::Battery::PrimaryBattery) ) 0557 { 0558 // show also non-primary batteries 0559 } 0560 else 0561 { 0562 Solid::StorageVolume *volume = device.as<Solid::StorageVolume>(); 0563 Solid::StorageAccess *access = device.as<Solid::StorageAccess>(); 0564 0565 if ( !volume ) // volume can at least be mounted; others need some specific actions 0566 { 0567 if ( deviceActions.isEmpty() ) 0568 { 0569 //qDebug() << device.udi() << "no action found"; 0570 return nullptr; 0571 } 0572 if ( access && access->isAccessible() && !QFileInfo(access->filePath()).isReadable() ) 0573 { 0574 //qDebug() << device.udi() << access->filePath() << "not readable"; 0575 return nullptr; 0576 } 0577 } 0578 else if ( (volume->usage() != Solid::StorageVolume::FileSystem) || volume->isIgnored() ) 0579 { 0580 //qDebug() << device.udi() << (access ? access->filePath() : QString()) << "no filesystem or ignored"; 0581 return nullptr; 0582 } 0583 0584 // show only removable devices 0585 bool isRemovable = (device.is<Solid::StorageDrive>() && 0586 device.as<Solid::StorageDrive>()->isRemovable()) || 0587 (device.parent().is<Solid::StorageDrive>() && 0588 device.parent().as<Solid::StorageDrive>()->isRemovable()); 0589 0590 if ( !isRemovable ) 0591 { 0592 //qDebug() << device.udi() << "not Removable"; 0593 return nullptr; 0594 } 0595 } 0596 0597 DeviceItem *item = new DeviceItem(device, deviceActions); 0598 vbox->insertWidget(vbox->count() - 1, item); // insert before stretch 0599 0600 items.insert(device.udi(), item); 0601 return item; 0602 } 0603 0604 //-------------------------------------------------------------------------------- 0605 0606 void DeviceList::deviceAdded(const QString &dev) 0607 { 0608 Solid::Device device(dev); 0609 0610 if ( !predicate.matches(device) ) 0611 return; 0612 0613 DeviceItem *item = addDevice(device); 0614 0615 // when we added a new device, make sure the DeviceNotifier shows and places this window 0616 if ( item ) 0617 { 0618 item->markAsNew(); 0619 item->show(); 0620 verticalScrollBar()->setValue(verticalScrollBar()->maximum()); 0621 0622 emit deviceWasAdded(); 0623 } 0624 } 0625 0626 //-------------------------------------------------------------------------------- 0627 0628 void DeviceList::deviceRemoved(const QString &dev) 0629 { 0630 if ( items.contains(dev) ) 0631 { 0632 delete items.take(dev); 0633 emit deviceWasRemoved(); 0634 } 0635 } 0636 0637 //-------------------------------------------------------------------------------- 0638 0639 void DeviceList::kdeConnectDeviceAdded(const KdeConnect::Device &device) 0640 { 0641 if ( items.contains(device->id) ) 0642 { 0643 //qDebug() << device->id << "already known"; 0644 return; 0645 } 0646 0647 DeviceItem *item = new DeviceItem(device); 0648 vbox->insertWidget(vbox->count() - 1, item); // insert before stretch 0649 0650 items.insert(device->id, item); 0651 item->markAsNew(); 0652 item->show(); 0653 verticalScrollBar()->setValue(verticalScrollBar()->maximum()); 0654 0655 // when we added a new device, make sure the DeviceNotifier shows and places this window 0656 emit deviceWasAdded(); 0657 } 0658 0659 //--------------------------------------------------------------------------------