1 /*
2     This file is part of the Okteta Kasten Framework, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2009, 2012 Alex Richardson <alex.richardson@gmx.de>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 #include "structureaddremovewidget.hpp"
9 #include "../structurestool.hpp"
10 #include "../structuresmanager.hpp"
11 #include "../structuredefinitionfile.hpp"
12 #include "../structlogging.hpp"
13 
14 #include <QPushButton>
15 #include <QLabel>
16 #include <QLayout>
17 #include <QTreeWidget>
18 #include <QTreeWidgetItem>
19 
20 #include <QRegExp>
21 #include <KLocalizedString>
22 #include <KConfigDialogManager>
23 
24 using namespace Kasten;
25 
StructureAddRemoveWidget(const QStringList & selected,Kasten::StructuresTool * tool,QWidget * parent)26 StructureAddRemoveWidget::StructureAddRemoveWidget(const QStringList& selected, Kasten::StructuresTool* tool, QWidget* parent)
27     : QWidget(parent)
28     , mTool(tool)
29 {
30     QHBoxLayout* baseLayout;
31     QVBoxLayout* tree1Layout;
32     QVBoxLayout* tree2Layout;
33     QVBoxLayout* leftRightLayout;
34     QVBoxLayout* upDownLayout;
35 
36     baseLayout = new QHBoxLayout();
37     baseLayout->setContentsMargins(0, 0, 0, 0);
38 
39     tree1Layout = new QVBoxLayout();
40     mTree1Label = new QLabel(i18nc("@info:label", "Installed structures:"), this);
41     tree1Layout->addWidget(mTree1Label);
42     mTreeAvailable = new QTreeWidget(this);
43     mTreeAvailable->setHeaderHidden(true);
44     mTreeAvailable->setSelectionMode(QAbstractItemView::ExtendedSelection);
45     mTreeAvailable->setColumnCount(2);
46     mTreeAvailable->setColumnHidden(1, true);
47     tree1Layout->addWidget(mTreeAvailable);
48 
49     tree2Layout = new QVBoxLayout();
50     mTree2Label = new QLabel(i18nc("@info:label", "Used structures:"), this);
51     tree2Layout->addWidget(mTree2Label);
52     mTreeSelected = new QTreeWidget(this);
53     mTreeSelected->setHeaderHidden(true);
54     mTreeSelected->setSelectionMode(QAbstractItemView::ExtendedSelection);
55     mTreeSelected->setColumnCount(2);
56     mTreeSelected->setColumnHidden(1, true);
57     tree2Layout->addWidget(mTreeSelected);
58 
59     leftRightLayout = new QVBoxLayout();
60     leftRightLayout->addStretch();
61     mRightButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-right")), QString(), this);
62     leftRightLayout->addWidget(mRightButton);
63     mLeftButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), QString(), this);
64     leftRightLayout->addWidget(mLeftButton);
65     leftRightLayout->addStretch();
66 
67     upDownLayout = new QVBoxLayout();
68     upDownLayout->addStretch();
69     mUpButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-up")), QString(), this);
70     upDownLayout->addWidget(mUpButton);
71     mDownButton = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-down")), QString(), this);
72     upDownLayout->addWidget(mDownButton);
73     upDownLayout->addStretch();
74 
75     baseLayout->addLayout(tree1Layout);
76     baseLayout->addLayout(leftRightLayout);
77     baseLayout->addLayout(tree2Layout);
78     baseLayout->addLayout(upDownLayout);
79     setLayout(baseLayout);
80 
81     connect(mLeftButton, &QPushButton::pressed, this, &StructureAddRemoveWidget::moveLeft);
82     connect(mRightButton, &QPushButton::pressed, this, &StructureAddRemoveWidget::moveRight);
83     connect(mUpButton, &QPushButton::pressed, this, &StructureAddRemoveWidget::moveUp);
84     connect(mDownButton, &QPushButton::pressed, this, &StructureAddRemoveWidget::moveDown);
85 
86     buildAvailableList();
87 
88     // already loaded defs:
89     QRegExp regex(QStringLiteral("'(.+)':'(.+)'"));
90     for (const QString& s : selected) {
91         int pos = regex.indexIn(s);
92         if (pos > -1) {
93             QString pluginName = regex.cap(1);
94             QString structName = regex.cap(2);
95             if (structName == QLatin1String("*")) {
96                 // add all of them
97                 for (int i = 0; i < mTreeAvailable->topLevelItemCount(); i++) {
98                     QTreeWidgetItem* avail = mTreeAvailable->topLevelItem(i);
99                     if (avail->text(0) != pluginName) {
100                         continue;
101                     }
102                     for (int i = 0; i < avail->childCount(); i++) {
103                         QTreeWidgetItem* selStruct = avail->child(i);
104                         QTreeWidgetItem* item = new QTreeWidgetItem(mTreeSelected,
105                                                                     QStringList { selStruct->text(0), pluginName });
106                         mTreeSelected->addTopLevelItem(item);
107                     }
108 
109                     break;
110                 }
111             } else {
112                 QTreeWidgetItem* item = new QTreeWidgetItem(mTreeSelected,
113                                                             QStringList { structName, pluginName });
114                 mTreeSelected->addTopLevelItem(item);
115             }
116         }
117     }
118 
119     syncData();
120 }
121 
122 StructureAddRemoveWidget::~StructureAddRemoveWidget() = default;
123 
values() const124 QStringList StructureAddRemoveWidget::values() const
125 {
126     return mValues;
127 }
128 
buildAvailableList()129 void StructureAddRemoveWidget::buildAvailableList()
130 {
131     const auto loadedDefs = mTool->manager()->structureDefs();
132     QList<QTreeWidgetItem*> availableItems;
133     for (StructureDefinitionFile* def : loadedDefs) {
134         if (!def->isValid()) {
135             continue;
136         }
137         QString pluginName = def->pluginInfo().pluginName();
138         if (!def->pluginInfo().isPluginEnabled()) {
139             continue;
140         }
141         QTreeWidgetItem* item = new QTreeWidgetItem(mTreeAvailable,
142                                                     QStringList { def->pluginInfo().pluginName(), pluginName });
143         const auto structureNames = def->structureNames();
144         for (const QString& name : structureNames) {
145             QTreeWidgetItem* subItem = new QTreeWidgetItem(item,
146                                                            QStringList { name, pluginName });
147             item->addChild(subItem);
148         }
149 
150         availableItems.append(item);
151     }
152 
153     mTreeAvailable->addTopLevelItems(availableItems);
154 
155 }
156 
moveLeft()157 void StructureAddRemoveWidget::moveLeft()
158 {
159     const QList<QTreeWidgetItem*> selected = mTreeSelected->selectedItems();
160     bool changed = false;
161     for (QTreeWidgetItem* item : selected) {
162         delete mTreeSelected->takeTopLevelItem(
163             mTreeSelected->indexOfTopLevelItem(item));
164         changed = true;
165     }
166 
167     if (changed) {
168         syncData();
169     }
170 }
171 
moveRight()172 void StructureAddRemoveWidget::moveRight()
173 {
174     const QList<QTreeWidgetItem*> selected = mTreeAvailable->selectedItems();
175     bool changed = false;
176     for (const QTreeWidgetItem* item : selected) {
177         if (!item->parent()) {
178             continue;     // maybe sometime add all subitems
179         }
180         QTreeWidgetItem* moveOver = new QTreeWidgetItem(mTreeSelected,
181                                                         QStringList { item->text(0), item->text(1) });
182         // item name then parent name then path
183         mTreeSelected->addTopLevelItem(moveOver);
184         changed = true;
185     }
186 
187     if (changed) {
188         syncData();
189     }
190 
191 }
192 
moveUp()193 void StructureAddRemoveWidget::moveUp()
194 {
195     const QList<QTreeWidgetItem*> selected = mTreeSelected->selectedItems();
196     bool changed = false;
197     int firstIndex = -1;
198     for (QTreeWidgetItem* item : selected) {
199         int idx = mTreeSelected->indexOfTopLevelItem(item);
200         int newIdx = qMax(0, idx - 1);
201         mTreeSelected->insertTopLevelItem(newIdx,
202                                           mTreeSelected->takeTopLevelItem(idx));
203         // only first index
204         firstIndex = firstIndex == -1 ? newIdx : firstIndex;
205     }
206 
207     if (changed) {
208         syncData();
209     }
210     if (firstIndex != -1) {
211         mTreeSelected->setCurrentItem(mTreeSelected->topLevelItem(firstIndex));
212     }
213 }
214 
moveDown()215 void StructureAddRemoveWidget::moveDown()
216 {
217     const QList<QTreeWidgetItem*> selected = mTreeSelected->selectedItems();
218     bool changed = false;
219     int firstIndex = -1;
220     int maxItmCount = mTreeSelected->topLevelItemCount();
221     for (QTreeWidgetItem* item : selected) {
222         int idx = mTreeSelected->indexOfTopLevelItem(item);
223         int newIdx = qMin(idx + 1, maxItmCount - 1);
224         mTreeSelected->insertTopLevelItem(newIdx,
225                                           mTreeSelected->takeTopLevelItem(idx));
226         // only first index
227         firstIndex = firstIndex == -1 ? newIdx : firstIndex;
228     }
229 
230     if (changed) {
231         syncData();
232     }
233     if (firstIndex != -1) {
234         mTreeSelected->setCurrentItem(mTreeSelected->topLevelItem(firstIndex));
235     }
236 }
syncData()237 void StructureAddRemoveWidget::syncData()
238 {
239     const auto topLevelItemCount = mTreeSelected->topLevelItemCount();
240     QStringList strings;
241     strings.reserve(topLevelItemCount);
242     for (int i = 0; i < topLevelItemCount; ++i) {
243         QTreeWidgetItem* item = mTreeSelected->topLevelItem(i);
244         QString dataStr = QStringLiteral("\'%1\':\'%2\'").arg(item->text(1), item->text(0));
245         strings.append(dataStr);
246     }
247 
248     qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "selection changed to: " << strings;
249     mValues = strings;
250 }
251 
updateAvailable()252 void StructureAddRemoveWidget::updateAvailable()
253 {
254     // rebuild available tree
255     mTreeAvailable->clear();
256     buildAvailableList();
257 
258     // remove any structs that references not loaded files
259     QStringList plugins;
260     const auto loadedDefs = mTool->manager()->structureDefs();
261     for (const StructureDefinitionFile* def : loadedDefs) {
262         QString pluginName = def->pluginInfo().pluginName();
263         if (def->pluginInfo().isValid() && !def->pluginInfo().isPluginEnabled()) {
264             continue;
265         }
266         plugins << pluginName;
267     }
268 
269     bool changed = false;
270     QList<QTreeWidgetItem*> toRemove;
271     qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "paths = " << plugins;
272     for (int i = 0; i < mTreeSelected->topLevelItemCount(); ++i) {
273         QTreeWidgetItem* item = mTreeSelected->topLevelItem(i);
274         // text(1) is plugin name
275         if (!plugins.contains(item->text(1))) {
276             qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES)
277                 << "removed item: " << QStringLiteral("\'%1\':\'%2\'").arg(item->text(1),
278                                                                            item->text(0));
279 
280             changed = true;
281             toRemove.append(item);
282         } else {
283             qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES)
284                 << "item " << QStringLiteral("\'%1\':\'%2\'").arg(item->text(1),
285                                                                   item->text(0)) << "still loaded -> keep";
286         }
287     }
288 
289     for (QTreeWidgetItem* itm : qAsConst(toRemove)) {
290         qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES)
291             << "item " << QStringLiteral("\'%1\':\'%2\'").arg(itm->text(1),
292                                                               itm->text(0)) << "removed";
293         delete mTreeSelected->takeTopLevelItem(mTreeSelected->indexOfTopLevelItem(itm));
294     }
295 
296     if (changed) {
297         syncData();
298     }
299 }
300