Add missing includes
[quassel.git] / src / qtui / settingspages / bufferviewsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "bufferviewsettingspage.h"
22
23 #include <algorithm>
24 #include <utility>
25
26 #include <QMessageBox>
27
28 #include "buffermodel.h"
29 #include "bufferviewconfig.h"
30 #include "bufferviewfilter.h"
31 #include "client.h"
32 #include "clientbufferviewmanager.h"
33 #include "icon.h"
34 #include "network.h"
35 #include "networkmodel.h"
36 #include "util.h"
37
38 BufferViewSettingsPage::BufferViewSettingsPage(QWidget* parent)
39     : SettingsPage(tr("Interface"), tr("Custom Chat Lists"), parent)
40 {
41     ui.setupUi(this);
42     // Hide the hide inactive networks feature on older cores (which won't save the setting)
43     if (!Client::isCoreFeatureEnabled(Quassel::Feature::HideInactiveNetworks))
44         ui.hideInactiveNetworks->hide();
45
46     ui.renameBufferView->setIcon(icon::get("edit-rename"));
47     ui.addBufferView->setIcon(icon::get("list-add"));
48     ui.deleteBufferView->setIcon(icon::get("edit-delete"));
49
50     reset();
51
52     ui.bufferViewList->setSortingEnabled(true);
53     ui.settingsGroupBox->setEnabled(false);
54     ui.bufferViewPreview->setEnabled(false);
55
56     coreConnectionStateChanged(Client::isConnected());  // need a core connection!
57     connect(Client::instance(), &Client::coreConnectionStateChanged, this, &BufferViewSettingsPage::coreConnectionStateChanged);
58     connect(ui.bufferViewList->selectionModel(),
59             &QItemSelectionModel::selectionChanged,
60             this,
61             &BufferViewSettingsPage::bufferViewSelectionChanged);
62
63     connect(ui.onlyStatusBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
64     connect(ui.onlyChannelBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
65     connect(ui.onlyQueryBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
66     connect(ui.addNewBuffersAutomatically, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
67     connect(ui.sortAlphabetically, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
68     connect(ui.hideInactiveBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
69     connect(ui.hideInactiveNetworks, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
70     connect(ui.networkSelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::widgetHasChanged);
71     connect(ui.minimumActivitySelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::widgetHasChanged);
72     connect(ui.showSearch, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
73
74     connect(ui.networkSelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::enableStatusBuffers);
75 }
76
77 BufferViewSettingsPage::~BufferViewSettingsPage()
78 {
79     reset();
80 }
81
82 void BufferViewSettingsPage::reset()
83 {
84     ui.bufferViewList->clear();
85     ui.deleteBufferView->setEnabled(false);
86
87     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIter = _changedBufferViews.begin();
88     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIterEnd = _changedBufferViews.end();
89     BufferViewConfig* config;
90     while (changedConfigIter != changedConfigIterEnd) {
91         config = changedConfigIter.value();
92         changedConfigIter = _changedBufferViews.erase(changedConfigIter);
93         config->deleteLater();
94     }
95
96     QList<BufferViewConfig*>::iterator newConfigIter = _newBufferViews.begin();
97     QList<BufferViewConfig*>::iterator newConfigIterEnd = _newBufferViews.end();
98     while (newConfigIter != newConfigIterEnd) {
99         config = *newConfigIter;
100         newConfigIter = _newBufferViews.erase(newConfigIter);
101         config->deleteLater();
102     }
103
104     _deleteBufferViews.clear();
105
106     _useBufferViewHint = false;
107
108     setChangedState(false);
109 }
110
111 void BufferViewSettingsPage::load()
112 {
113     bool useBufferViewHint = _useBufferViewHint;
114     int bufferViewHint = _bufferViewHint;
115     reset();
116
117     if (!Client::bufferViewManager())
118         return;
119
120     const QList<BufferViewConfig*> bufferViewConfigs = Client::bufferViewManager()->bufferViewConfigs();
121     foreach (BufferViewConfig* bufferViewConfig, bufferViewConfigs) {
122         addBufferView(bufferViewConfig);
123     }
124
125     _ignoreWidgetChanges = true;
126     // load network selector
127     ui.networkSelector->clear();
128     ui.networkSelector->addItem(tr("All"));
129     ui.networkSelector->setItemData(0, qVariantFromValue<NetworkId>(NetworkId()));
130     const Network* net;
131     foreach (NetworkId netId, Client::networkIds()) {
132         net = Client::network(netId);
133         ui.networkSelector->addItem(net->networkName());
134         ui.networkSelector->setItemData(ui.networkSelector->count() - 1, qVariantFromValue<NetworkId>(net->networkId()));
135     }
136     _ignoreWidgetChanges = false;
137
138     if (!useBufferViewHint || !selectBufferViewById(bufferViewHint))
139         ui.bufferViewList->setCurrentRow(0);
140 }
141
142 void BufferViewSettingsPage::save()
143 {
144     setEnabled(false);
145
146     BufferViewConfig* currentConfig = bufferView(ui.bufferViewList->currentRow());
147     if (currentConfig) {
148         _useBufferViewHint = true;
149         _bufferViewHint = currentConfig->bufferViewId();
150     }
151
152     QVariantList newConfigs;
153     QVariantList deleteConfigs;
154     QVariantList changedConfigs;
155
156     foreach (int bufferId, _deleteBufferViews) {
157         deleteConfigs << bufferId;
158     }
159     _deleteBufferViews.clear();
160     if (Client::bufferViewManager()) {
161         Client::bufferViewManager()->requestDeleteBufferViews(deleteConfigs);
162     }
163
164     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIter = _changedBufferViews.begin();
165     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIterEnd = _changedBufferViews.end();
166     BufferViewConfig *config, *changedConfig;
167     while (changedConfigIter != changedConfigIterEnd) {
168         config = changedConfigIter.key();
169         changedConfig = changedConfigIter.value();
170         changedConfigIter = _changedBufferViews.erase(changedConfigIter);
171         config->requestUpdate(changedConfig->toVariantMap());
172         changedConfig->deleteLater();
173     }
174
175     QList<BufferViewConfig*>::iterator newConfigIter = _newBufferViews.begin();
176     QList<BufferViewConfig*>::iterator newConfigIterEnd = _newBufferViews.end();
177     while (newConfigIter != newConfigIterEnd) {
178         config = *newConfigIter;
179         newConfigIter = _newBufferViews.erase(newConfigIter);
180         newConfigs << config->toVariantMap();
181         config->deleteLater();
182     }
183     if (Client::bufferViewManager()) {
184         Client::bufferViewManager()->requestCreateBufferViews(newConfigs);
185     }
186
187     load();
188     setEnabled(true);
189 }
190
191 void BufferViewSettingsPage::coreConnectionStateChanged(bool state)
192 {
193     setEnabled(state);
194     if (state) {
195         load();
196         connect(Client::bufferViewManager(),
197                 selectOverload<int>(&BufferViewManager::bufferViewConfigAdded),
198                 this,
199                 selectOverload<int>(&BufferViewSettingsPage::addBufferView));
200     }
201     else {
202         reset();
203     }
204 }
205
206 void BufferViewSettingsPage::addBufferView(BufferViewConfig* config)
207 {
208     auto* item = new QListWidgetItem(config->bufferViewName(), ui.bufferViewList);
209     item->setData(Qt::UserRole, qVariantFromValue<QObject*>(qobject_cast<QObject*>(config)));
210     connect(config, &SyncableObject::updatedRemotely, this, &BufferViewSettingsPage::updateBufferView);
211     connect(config, &QObject::destroyed, this, &BufferViewSettingsPage::bufferViewDeleted);
212     ui.deleteBufferView->setEnabled(ui.bufferViewList->count() > 1);
213 }
214
215 void BufferViewSettingsPage::addBufferView(int bufferViewId)
216 {
217     // we are informed about a new bufferview from Client::bufferViewManager()
218     Q_ASSERT(Client::bufferViewManager());
219     addBufferView(Client::bufferViewManager()->bufferViewConfig(bufferViewId));
220     selectBufferViewById(bufferViewId);
221 }
222
223 void BufferViewSettingsPage::bufferViewDeleted()
224 {
225     auto* config = static_cast<BufferViewConfig*>(sender());
226     QObject* obj;
227     for (int i = 0; i < ui.bufferViewList->count(); i++) {
228         obj = ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>();
229         if (config == static_cast<BufferViewConfig*>(obj)) {
230             QListWidgetItem* item = ui.bufferViewList->takeItem(i);
231             delete item;
232             break;
233         }
234     }
235     ui.deleteBufferView->setEnabled(ui.bufferViewList->count() > 1);
236 }
237
238 void BufferViewSettingsPage::newBufferView(const QString& bufferViewName)
239 {
240     // id's of newly created bufferviews are negative (-1, -2... -n)
241     int fakeId = -1 * (_newBufferViews.count() + 1);
242     auto* config = new BufferViewConfig(fakeId);
243     config->setBufferViewName(bufferViewName);
244     config->setInitialized();
245     QList<BufferId> bufferIds;
246     if (config->addNewBuffersAutomatically()) {
247         if (config->sortAlphabetically()) {
248             bufferIds = Client::networkModel()->allBufferIdsSorted();
249         }
250         else {
251             bufferIds = Client::networkModel()->allBufferIds();
252             std::sort(bufferIds.begin(), bufferIds.end());
253             config->setProperty("OriginalBufferList", toVariantList<BufferId>(bufferIds));
254         }
255     }
256     config->setBufferList(bufferIds);
257
258     _newBufferViews << config;
259     addBufferView(config);
260     ui.bufferViewList->setCurrentRow(listPos(config));
261 }
262
263 int BufferViewSettingsPage::listPos(BufferViewConfig* config)
264 {
265     QObject* obj;
266     for (int i = 0; i < ui.bufferViewList->count(); i++) {
267         obj = ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>();
268         if (config == qobject_cast<BufferViewConfig*>(obj))
269             return i;
270     }
271     return -1;
272 }
273
274 BufferViewConfig* BufferViewSettingsPage::bufferView(int listPos)
275 {
276     if (listPos < ui.bufferViewList->count() && listPos >= 0) {
277         auto* obj = ui.bufferViewList->item(listPos)->data(Qt::UserRole).value<QObject*>();
278         return qobject_cast<BufferViewConfig*>(obj);
279     }
280     else {
281         return nullptr;
282     }
283 }
284
285 bool BufferViewSettingsPage::selectBufferViewById(int bufferViewId)
286 {
287     BufferViewConfig* config;
288     for (int i = 0; i < ui.bufferViewList->count(); i++) {
289         config = qobject_cast<BufferViewConfig*>(ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>());
290         if (config && config->bufferViewId() == bufferViewId) {
291             ui.bufferViewList->setCurrentRow(i);
292             return true;
293         }
294     }
295     return false;
296 }
297
298 void BufferViewSettingsPage::updateBufferView()
299 {
300     auto* config = qobject_cast<BufferViewConfig*>(sender());
301     if (!config)
302         return;
303
304     int itemPos = listPos(config);
305     if (itemPos == -1) {
306         qWarning() << "BufferViewSettingsPage::updateBufferView(): view is unknown:" << config->bufferViewId();
307         return;
308     }
309     ui.bufferViewList->item(itemPos)->setText(config->bufferViewName());
310     if (itemPos == ui.bufferViewList->currentRow())
311         loadConfig(config);
312 }
313
314 void BufferViewSettingsPage::enableStatusBuffers(int networkIdx)
315 {
316     // we don't show a status buffer if we show multiple networks as selecting
317     // the network is the same as selecting the status buffer.
318     ui.onlyStatusBuffers->setEnabled(networkIdx != 0);
319 }
320
321 void BufferViewSettingsPage::on_addBufferView_clicked()
322 {
323     if (!Client::bufferViewManager())
324         return;
325
326     QStringList existing;
327     foreach (BufferViewConfig* bufferConfig, Client::bufferViewManager()->bufferViewConfigs()) {
328         existing << bufferConfig->bufferViewName();
329     }
330
331     BufferViewEditDlg dlg(QString(), existing, this);
332     if (dlg.exec() == QDialog::Accepted) {
333         newBufferView(dlg.bufferViewName());
334         setChangedState(true);
335     }
336 }
337
338 void BufferViewSettingsPage::on_renameBufferView_clicked()
339 {
340     if (ui.bufferViewList->selectedItems().isEmpty())
341         return;
342
343     if (!Client::bufferViewManager())
344         return;
345
346     BufferViewConfig* config = bufferView(ui.bufferViewList->currentRow());
347     if (!config)
348         return;
349
350     QStringList existing;
351     foreach (BufferViewConfig* bufferConfig, Client::bufferViewManager()->bufferViewConfigs()) {
352         existing << bufferConfig->bufferViewName();
353     }
354
355     BufferViewEditDlg dlg(config->bufferViewName(), existing, this);
356     if (dlg.exec() == QDialog::Accepted) {
357         BufferViewConfig* changedConfig = cloneConfig(config);
358         changedConfig->setBufferViewName(dlg.bufferViewName());
359         ui.bufferViewList->item(listPos(config))->setText(dlg.bufferViewName());
360         setChangedState(true);
361     }
362 }
363
364 void BufferViewSettingsPage::on_deleteBufferView_clicked()
365 {
366     if (ui.bufferViewList->selectedItems().isEmpty())
367         return;
368
369     QListWidgetItem* currentItem = ui.bufferViewList->item(ui.bufferViewList->currentRow());
370     QString viewName = currentItem->text();
371     int viewId = bufferView(ui.bufferViewList->currentRow())->bufferViewId();
372     int ret = QMessageBox::question(this,
373                                     tr("Delete Chat List?"),
374                                     tr("Do you really want to delete the chat list \"%1\"?").arg(viewName),
375                                     QMessageBox::Yes | QMessageBox::No,
376                                     QMessageBox::No);
377
378     if (ret == QMessageBox::Yes) {
379         ui.bufferViewList->removeItemWidget(currentItem);
380         auto* config = qobject_cast<BufferViewConfig*>(currentItem->data(Qt::UserRole).value<QObject*>());
381         delete currentItem;
382         if (viewId >= 0) {
383             _deleteBufferViews << viewId;
384             setChangedState(true);
385         }
386         else if (config) {
387             QList<BufferViewConfig*>::iterator iter = _newBufferViews.begin();
388             while (iter != _newBufferViews.end()) {
389                 if (*iter == config) {
390                     iter = _newBufferViews.erase(iter);
391                     break;
392                 }
393                 else {
394                     ++iter;
395                 }
396             }
397             delete config;
398             if (_deleteBufferViews.isEmpty() && _changedBufferViews.isEmpty() && _newBufferViews.isEmpty())
399                 setChangedState(false);
400         }
401     }
402 }
403
404 void BufferViewSettingsPage::bufferViewSelectionChanged(const QItemSelection& current, const QItemSelection& previous)
405 {
406     Q_UNUSED(previous)
407
408     if (!current.isEmpty()) {
409         ui.settingsGroupBox->setEnabled(true);
410         ui.bufferViewPreview->setEnabled(true);
411
412         loadConfig(configForDisplay(bufferView(ui.bufferViewList->currentRow())));
413     }
414     else {
415         ui.settingsGroupBox->setEnabled(false);
416         ui.bufferViewPreview->setEnabled(false);
417     }
418 }
419
420 void BufferViewSettingsPage::loadConfig(BufferViewConfig* config)
421 {
422     if (!config)
423         return;
424
425     _ignoreWidgetChanges = true;
426     ui.onlyStatusBuffers->setChecked(BufferInfo::StatusBuffer & config->allowedBufferTypes());
427     ui.onlyChannelBuffers->setChecked(BufferInfo::ChannelBuffer & config->allowedBufferTypes());
428     ui.onlyQueryBuffers->setChecked(BufferInfo::QueryBuffer & config->allowedBufferTypes());
429     ui.addNewBuffersAutomatically->setChecked(config->addNewBuffersAutomatically());
430     ui.sortAlphabetically->setChecked(config->sortAlphabetically());
431     ui.hideInactiveBuffers->setChecked(config->hideInactiveBuffers());
432     ui.hideInactiveNetworks->setChecked(config->hideInactiveNetworks());
433     ui.showSearch->setChecked(config->showSearch());
434
435     int networkIndex = 0;
436     for (int i = 0; i < ui.networkSelector->count(); i++) {
437         if (ui.networkSelector->itemData(i).value<NetworkId>() == config->networkId()) {
438             networkIndex = i;
439             break;
440         }
441     }
442     ui.networkSelector->setCurrentIndex(networkIndex);
443
444     int activityIndex = 0;
445     int minimumActivity = config->minimumActivity();
446     while (minimumActivity) {
447         activityIndex++;
448         minimumActivity = minimumActivity >> 1;
449     }
450     ui.minimumActivitySelector->setCurrentIndex(activityIndex);
451
452     ui.bufferViewPreview->setFilteredModel(Client::bufferModel(), config);
453
454     _ignoreWidgetChanges = false;
455 }
456
457 void BufferViewSettingsPage::saveConfig(BufferViewConfig* config)
458 {
459     if (!config)
460         return;
461
462     int allowedBufferTypes = 0;
463     if (ui.onlyStatusBuffers->isChecked())
464         allowedBufferTypes |= BufferInfo::StatusBuffer;
465     if (ui.onlyChannelBuffers->isChecked())
466         allowedBufferTypes |= BufferInfo::ChannelBuffer;
467     if (ui.onlyQueryBuffers->isChecked())
468         allowedBufferTypes |= BufferInfo::QueryBuffer;
469     config->setAllowedBufferTypes(allowedBufferTypes);
470
471     config->setAddNewBuffersAutomatically(ui.addNewBuffersAutomatically->isChecked());
472     config->setSortAlphabetically(ui.sortAlphabetically->isChecked());
473     config->setHideInactiveBuffers(ui.hideInactiveBuffers->isChecked());
474     config->setHideInactiveNetworks(ui.hideInactiveNetworks->isChecked());
475     config->setNetworkId(ui.networkSelector->itemData(ui.networkSelector->currentIndex()).value<NetworkId>());
476     config->setShowSearch(ui.showSearch->isChecked());
477
478     int minimumActivity = 0;
479     if (ui.minimumActivitySelector->currentIndex() > 0)
480         minimumActivity = 1 << (ui.minimumActivitySelector->currentIndex() - 1);
481     config->setMinimumActivity(minimumActivity);
482
483     QList<BufferId> bufferIds = fromVariantList<BufferId>(config->property("OriginalBufferList").toList());
484     if (config->sortAlphabetically())
485         Client::networkModel()->sortBufferIds(bufferIds);
486
487     if (!_newBufferViews.contains(config) || config->addNewBuffersAutomatically())
488         config->setBufferList(bufferIds);
489 }
490
491 void BufferViewSettingsPage::widgetHasChanged()
492 {
493     if (_ignoreWidgetChanges)
494         return;
495     setChangedState(testHasChanged());
496 }
497
498 bool BufferViewSettingsPage::testHasChanged()
499 {
500     saveConfig(cloneConfig(bufferView(ui.bufferViewList->currentRow())));
501
502     if (!_newBufferViews.isEmpty())
503         return true;
504
505     bool changed = false;
506     QHash<BufferViewConfig*, BufferViewConfig*>::iterator iter = _changedBufferViews.begin();
507     QHash<BufferViewConfig*, BufferViewConfig*>::iterator iterEnd = _changedBufferViews.end();
508     while (iter != iterEnd) {
509         if (&(iter.key()) == &(iter.value())) {
510             iter.value()->deleteLater();
511             iter = _changedBufferViews.erase(iter);
512         }
513         else {
514             changed = true;
515             ++iter;
516         }
517     }
518     return changed;
519 }
520
521 BufferViewConfig* BufferViewSettingsPage::cloneConfig(BufferViewConfig* config)
522 {
523     if (!config || config->bufferViewId() < 0)
524         return config;
525
526     if (_changedBufferViews.contains(config))
527         return _changedBufferViews[config];
528
529     auto* changedConfig = new BufferViewConfig(-1, this);
530     changedConfig->fromVariantMap(config->toVariantMap());
531     changedConfig->setInitialized();
532     _changedBufferViews[config] = changedConfig;
533     connect(config, &BufferViewConfig::bufferAdded, changedConfig, &BufferViewConfig::addBuffer);
534     connect(config, &BufferViewConfig::bufferMoved, changedConfig, &BufferViewConfig::moveBuffer);
535     connect(config, &BufferViewConfig::bufferRemoved, changedConfig, &BufferViewConfig::removeBuffer);
536     // connect(config, &BufferViewConfig::addBufferRequested, changedConfig, &BufferViewConfig::addBuffer);
537     // connect(config, &BufferViewConfig::moveBufferRequested, changedConfig, &BufferViewConfig::moveBuffer);
538     // connect(config, &BufferViewconfig::removeBufferRequested, changedConfig, &BufferViewConfig::removeBuffer);
539
540     changedConfig->setProperty("OriginalBufferList", toVariantList<BufferId>(config->bufferList()));
541     // if this is the currently displayed view we have to change the config of the preview filter
542     auto* filter = qobject_cast<BufferViewFilter*>(ui.bufferViewPreview->model());
543     if (filter && filter->config() == config)
544         filter->setConfig(changedConfig);
545     ui.bufferViewPreview->setConfig(changedConfig);
546
547     return changedConfig;
548 }
549
550 BufferViewConfig* BufferViewSettingsPage::configForDisplay(BufferViewConfig* config)
551 {
552     if (_changedBufferViews.contains(config))
553         return _changedBufferViews[config];
554     else
555         return config;
556 }
557
558 /**************************************************************************
559  * BufferViewEditDlg
560  *************************************************************************/
561 BufferViewEditDlg::BufferViewEditDlg(const QString& old, QStringList exist, QWidget* parent)
562     : QDialog(parent)
563     , existing(std::move(exist))
564 {
565     ui.setupUi(this);
566
567     if (old.isEmpty()) {
568         // new buffer
569         setWindowTitle(tr("Add Chat List"));
570         on_bufferViewEdit_textChanged("");  // disable ok button
571     }
572     else {
573         ui.bufferViewEdit->setText(old);
574     }
575 }
576
577 void BufferViewEditDlg::on_bufferViewEdit_textChanged(const QString& text)
578 {
579     ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty() || existing.contains(text));
580 }