1/****************************************************************************
2**
3** Copyright (C) 2008-2010 Ralf Habacker <ralf.habacker@freenet.de>
4** Copyright (C) 2010 Patrick Spendrin <ps_ml@gmx.de>
5** All rights reserved.
6**
7** This file is part of the KDE installer for windows
8**
9** This file may be used under the terms of the GNU General Public
10** License version 2.0 as published by the Free Software Foundation
11** and appearing in the file LICENSE.GPL included in the packaging of
12** this file. Please review the following information to ensure GNU
13** General Public Licensing requirements will be met:
14** http://www.trolltech.com/products/qt/opensource.html
15**
16** If you are unsure which license is appropriate for your use, please
17** review the following information:
18** http://www.trolltech.com/products/qt/licensing.html or contact the
19** sales department at sales@trolltech.com.
20**
21** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
22** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23**
24****************************************************************************/
25
26#include "config.h"
27#include "debug.h"
28#include "downloader.h"
29#include "installer.h"
30#include "installerprogress.h"
31#include "installerenginegui.h"
32#include "installerdialogs.h"
33#include "package.h"
34#include "packageinfo.h"
35#include "packagelist.h"
36#include "mirrors.h"
37#include "settings.h"
38#include "uninstaller.h"
39#include "unpacker.h"
40#include "enduserpackageselectorpage.h"
41#include "packagecategorycache.h"
42
43#include <QVector>
44#include <QListWidget>
45#include <QSplitter>
46#include <QTextEdit>
47#include <QTreeWidget>
48#include <QRegExp>
49
50extern InstallerEngineGui *engine;
51typedef enum { C_NAME, C_ACTION, C_AVAILABLE, C_INSTALLED, C_NOTES, /* always leave this item at the end */ C_COLUMNCOUNT } columnvalue;
52
53EndUserPackageSelectorPage::EndUserPackageSelectorPage() : InstallWizardPage(0)
54{
55 ui.setupUi(this);
56 setTitle(windowTitle());
57 if (0/* update*/)
58 setSubTitle(tr("This page shows all available updates for installed package and further available packages. "
59 "You may click into the action column to exclude a package from the update, to installl further packages "
60 "or to remove installed packages. If you are ready, press Next to start the update.")
61 );
62 else
63 setSubTitle(statusTip());
64}
65
66bool EndUserPackageSelectorPage::includePackage(const QString &name, PackageDisplayType displayType)
67{
68 if (displayType == Language && !name.contains("-l10n") )
69 return false;
70 else if (displayType == Spelling && !name.contains("aspell"))
71 return false;
72 else if (displayType == Application
73 && ( name.contains("aspell")
74 || name.contains("-l10n")
75 || name.contains("lib")
76 || name.contains("runtime")
77 )
78 )
79 return false;
80 else
81 return true;
82}
83
84void EndUserPackageSelectorPage::setWidgetData()
85{
86 QTreeWidget *tree = ui.packageList;
87 tree->clear();
88 QVector<QString> labels;
89 QList<QTreeWidgetItem *> items;
90 QString toolTip = "select this checkbox to install or update this package";
91
92 labels.resize( C_COLUMNCOUNT );
93 labels[C_NAME] = tr ( "Package" );
94 labels[C_ACTION] = tr ( "Action" );
95 labels[C_AVAILABLE] = tr ( "Available" );
96 labels[C_INSTALLED] = tr ( "Installed" );
97 labels[C_NOTES] = tr ( "Package notes" );
98
99 tree->setColumnCount ( C_COLUMNCOUNT );
100 tree->setHeaderLabels ( QList<QString>::fromVector( labels ) );
101
102 tree->setIndentation(10);
103
104 // see http://lists.trolltech.com/qt-interest/2006-06/thread00441-0.html
105 // and Task Tracker Entry 106731
106 //tree->setAlignment(Center);
107
108 // adding top level items
109 QList<QTreeWidgetItem *> categoryList;
110 QHash<QString, Package*> packageList;
111
112 QStringList selectedCategories;
113 Q_FOREACH(QString category, categoryCache.categories())
114 {
115 QStringList a = category.split(":");
116 if (activeCategories.contains(a[0]))
117 selectedCategories << a[0];
118 }
119
120 Q_FOREACH(QString categoryName, selectedCategories)
121 {
122 // add packages which are installed but for which no config entry is there
123 Q_FOREACH(Package *instPackage, categoryCache.packages(categoryName,*engine->database()))
124 {
125 QString name = instPackage->name();
126 Package *p = engine->packageResources()->getPackage(name);
127
128 if (!p) {
129 packageList[PackageInfo::baseName(name)] = instPackage;
130 }
131 }
132 }
133
134 Settings &s = Settings::instance();
135 Q_FOREACH(QString categoryName, selectedCategories)
136 {
137 Q_FOREACH(Package *availablePackage,categoryCache.packages(categoryName,*engine->packageResources()))
138 {
139 QString name = availablePackage->name();
140 if (engine->includePackage(s.compilerType(),name,categoryName))
141 packageList[PackageInfo::baseName(name)] = availablePackage;
142 }
143 }
144
145 // go through all metaPackages now
146 Q_FOREACH(QString metaPackage, engine->globalConfig()->metaPackages().keys())
147 {
148 Package *p = engine->packageResources()->find(metaPackage);
149 if(!p) p = packageList[PackageInfo::baseName(metaPackage)];
150
151 // in case p is 0, we couldn't find a predefined package - e.g. another package has been defined before
152 if(!p || p->hasType(FileTypes::BIN) || p->hasType(FileTypes::LIB)) continue;
153
154 if (!includePackage(p->name(),m_displayType))
155 continue;
156
157 QTreeWidgetItem *item = addPackageToTree(p, 0);
158
159 // if this is no metaPackage, simply ignore it
160 if(p->hasType(FileTypes::META)) {
161 int packageCount = 0;
162
163 // now check whether we have packages for this metaPackage in our packageList and add them as child elements
164 Q_FOREACH(QString name, engine->globalConfig()->metaPackages()[metaPackage])
165 {
166 // if this package is contained in the packageList and inside our metaPackage, we want to make it a child item
167 // of this QTreeWidgetItem
168 if(packageList.keys().contains(name)) {
169 if(addPackageToTree(packageList[name], item) != 0) ++packageCount;
170 packageList.remove( name );
171 }
172 }
173
174 // in case the packageCount is 0 (e.g. there are no childItems available for this metaPackage) simply delete the
175 // item again which we got from addPackageToTree
176 if(packageCount > 0) {
177 categoryList.append(item);
178 engine->setMetaPackageState(*item, C_ACTION);
179 } else {
180 delete item;
181 }
182 }
183 }
184
185 Q_FOREACH(Package *availablePackage,packageList.values())
186 {
187 if (!includePackage(availablePackage->name(),m_displayType))
188 continue;
189 if(availablePackage->hasType(FileTypes::BIN)) {
190 QTreeWidgetItem *item = addPackageToTree(availablePackage, 0);
191 if (item)
192 categoryList.append(item);
193 }
194 }
195 tree->addTopLevelItems ( categoryList );
196 tree->sortItems ( C_NAME, Qt::AscendingOrder );
197 for ( int i = 0; i < tree->columnCount(); i++ )
198 tree->resizeColumnToContents ( i );
199}
200
201QTreeWidgetItem *EndUserPackageSelectorPage::addPackageToTree(Package *availablePackage, QTreeWidgetItem *parent)
202{
203 QVector<QString> data;
204 QString toolTip = "select this checkbox to install or update this package";
205
206 Package *installedPackage = engine->database()->getPackage(availablePackage->name());
207 Package::PackageVersion installedVersion = installedPackage ? installedPackage->installedVersion() : Package::PackageVersion();
208 Package::PackageVersion availableVersion = availablePackage->version();
209 availablePackage->setInstalledVersion(installedVersion);
210
211 if (installedPackage && availableVersion == installedVersion)
212 return 0;
213 data.resize( C_COLUMNCOUNT );
214 data[C_NAME] = PackageInfo::baseName(availablePackage->name());
215 data[C_AVAILABLE] = (availableVersion != installedVersion ? availableVersion.toString() : "");
216 data[C_INSTALLED] = installedVersion.toString();
217 QTreeWidgetItem *item = new QTreeWidgetItem ( parent, QList<QString>::fromVector( data ) );
218 // save real package name for selection code
219 item->setData(C_ACTION, Qt::StatusTipRole, availablePackage->name());
220
221 // in case the package is a meta package, we need to check whether the subpackages are installed -> fix that after this function
222 engine->setEndUserInitialState(*item, availablePackage, installedPackage, C_ACTION);
223 item->setText(C_NOTES, availablePackage->notes());
224 item->setToolTip(C_NAME, toolTip);
225
226 return item;
227}
228
229void EndUserPackageSelectorPage::initializePage()
230{
231 Settings::instance().setFirstRun(false);
232 Settings::instance().setSkipBasicSettings(true);
233 setSettingsButtonVisible(true);
234 InstallerDialogs::instance().downloadProgressDialog(this,true,tr("Downloading Package Lists"));
235 engine->init();
236 InstallerDialogs::instance().downloadProgressDialog(this,false);
237
238 // disabled for now, because it will not install debug packages for already selected packages, need to be fixed
239 ui.installDebugPackagesCheckBox->setVisible(false);
240 ui.installDebugPackagesCheckBox->setEnabled(engine->database()->size() == 0);
241 ui.installDebugPackagesCheckBox->setChecked(Settings::instance().installDebugPackages());
242 engine->setInstallDebugPackages(Settings::instance().installDebugPackages());
243 connect(ui.installDebugPackagesCheckBox,SIGNAL(clicked()),this,SLOT(slotInstallDebugPackages()));
244
245 connect(ui.packageList,SIGNAL(itemClicked(QTreeWidgetItem *, int)),this,SLOT(itemClicked(QTreeWidgetItem *, int)));
246 connect(ui.selectAllCheckBox,SIGNAL(clicked()),this,SLOT(selectAllClicked()));
247
248 connect(ui.applicationPackageButton,SIGNAL(clicked()),this,SLOT(slotApplicationPackageButton()));
249 connect(ui.languagePackageButton,SIGNAL(clicked()),this,SLOT(slotLanguagePackageButton()));
250 connect(ui.spellingPackageButton,SIGNAL(clicked()),this,SLOT(slotSpellingPackageButton()));
251
252 connect(ui.filterEdit,SIGNAL(textChanged(const QString &)),this,SLOT(slotFilterTextChanged(const QString &)));
253
254 activeCategories = engine->globalConfig()->endUserCategories().size() > 0 ? engine->globalConfig()->endUserCategories() : QStringList() << "KDE";
255 setPackageDisplayType(Application);
256 // @TODO remove
257 if (ui.packageList->topLevelItemCount() == 0) {
258 // no items skip page
259 }
260}
261
262void EndUserPackageSelectorPage::preSelectPackages(const QString &package)
263{
264 QTreeWidget *tree = ui.packageList;
265 QString systemCode = QLocale::system().name();
266 QStringList b = systemCode.split('_');
267 QString languageCode = b[0];
268 QStringList searchCodes = QStringList() << systemCode << languageCode;
269 QString pattern = package + "-%1";
270
271 bool found = false;
272 foreach(QString code, searchCodes)
273 {
274 QList<QTreeWidgetItem *> list = tree->findItems (QString(pattern).arg(code), Qt::MatchContains, C_NAME );
275 foreach(QTreeWidgetItem *item, list)
276 {
277 QString name = item->data(C_ACTION, Qt::StatusTipRole).toString();
278 QString availableVersion = item->text ( C_AVAILABLE );
279 Package *availablePackage = engine->getPackageByName ( name, availableVersion );
280 if (!engine->isPackageSelected(availablePackage,FileTypes::BIN))
281 {
282 QString installedVersion = item->text ( C_INSTALLED );
283 Package *installedPackage = engine->database()->getPackage( name,installedVersion.toLatin1() );
284 engine->setNextState(*item, availablePackage, installedPackage, FileTypes::BIN, C_ACTION );
285 qDebug() << "found" << QString(pattern).arg(code);
286 }
287 found = true;
288 }
289 if (found)
290 break;
291 }
292}
293
294void EndUserPackageSelectorPage::setPackageDisplayType(PackageDisplayType type)
295{
296 m_displayType = type;
297 setWidgetData();
298 if (type == Application)
299 {
300 ui.applicationPackageButton->setEnabled(false);
301 ui.languagePackageButton->setEnabled(true);
302 ui.spellingPackageButton->setEnabled(true);
303 ui.selectAllCheckBox->setEnabled(true);
304 }
305 else if (type == Language)
306 {
307 ui.applicationPackageButton->setEnabled(true);
308 ui.languagePackageButton->setEnabled(false);
309 ui.spellingPackageButton->setEnabled(true);
310 ui.selectAllCheckBox->setEnabled(false);
311 if (!engine->isAnyPackageInstalled())
312 preSelectPackages("kde-l10n");
313 }
314 else if (type == Spelling)
315 {
316 ui.applicationPackageButton->setEnabled(true);
317 ui.languagePackageButton->setEnabled(true);
318 ui.spellingPackageButton->setEnabled(false);
319 ui.selectAllCheckBox->setEnabled(false);
320 if (!engine->isAnyPackageInstalled())
321 preSelectPackages("aspell");
322 }
323}
324
325void EndUserPackageSelectorPage::itemClicked(QTreeWidgetItem *item, int column)
326{
327 QString name = item->data( C_ACTION, Qt::StatusTipRole ).toString();
328 QString installedVersion = item->text ( C_INSTALLED );
329 QString availableVersion = item->text ( C_AVAILABLE );
330
331 Package *installedPackage = engine->database()->getPackage( name,installedVersion.toLatin1() );
332 Package *availablePackage = engine->getPackageByName( name, availableVersion );
333
334 if ( !availablePackage && !installedPackage ) {
335 qWarning() << __FUNCTION__ << "neither available or installed package present for package" << name;
336 return;
337 }
338
339 if ( engine->globalConfig()->metaPackages().keys().contains( PackageInfo::baseName(name) ) ) {
340
341 // this is a metaPackage which has no representation in the database
342 // now go through all subpackages
343 Q_FOREACH(QString package, engine->globalConfig()->metaPackages()[PackageInfo::baseName(name)])
344 {
345 int i = 0;
346 QString packageName;
347
348 // try to find the childItems which are connected to this metaPackage
349 for(; i < item->childCount(); ++i)
350 {
351 packageName = item->child(i)->data(C_ACTION, Qt::StatusTipRole).toString();
352 if(package == PackageInfo::baseName(item->child(i)->data(C_ACTION, Qt::StatusTipRole).toString()))
353 break;
354 }
355 if(i == item->childCount()) continue;
356
357 QString _installedVersion = item->child(i)->text ( C_INSTALLED );
358 QString _availableVersion = item->child(i)->text ( C_AVAILABLE );
359
360 Package *ip = engine->database()->getPackage( packageName, _installedVersion.toLatin1() );
361 Package *ap = engine->getPackageByName( packageName, _availableVersion );
362
363 if(!ap && !ip) continue;
364
365 // switch state for the childItem
366 if ( column == C_ACTION) engine->setNextState(*item->child(i), ap, ip, FileTypes::BIN, C_ACTION, true);
367 }
368 }
369
370 if ( column == C_ACTION )
371 {
372 engine->setNextState(*item, availablePackage, installedPackage, FileTypes::BIN, C_ACTION );
373 if(availablePackage->hasType(FileTypes::META))
374 engine->setMetaPackageState( *item, C_ACTION );
375
376 // now check that if we're no top level item, we need to handle also the category icon
377 if (ui.packageList->indexOfTopLevelItem(item) == -1) {
378 engine->setMetaPackageState( *item->parent(), C_ACTION );
379 }
380 }
381 // dependencies are selected later
382}
383
384void EndUserPackageSelectorPage::selectAllClicked()
385{
386 QTreeWidget *tree = ui.packageList;
387 int count = tree->topLevelItemCount();
388 for(int i = 0; i < count; i++)
389 {
390 QTreeWidgetItem *item = tree->topLevelItem(i);
391 QString name = item->data(C_ACTION, Qt::StatusTipRole).toString();
392 if (name.startsWith("aspell-") || name.startsWith("kde-l10n"))
393 continue;
394 itemClicked(item, C_ACTION);
395 }
396}
397
398void EndUserPackageSelectorPage::slotApplicationPackageButton()
399{
400 setPackageDisplayType(Application);
401}
402
403void EndUserPackageSelectorPage::slotLanguagePackageButton()
404{
405 setPackageDisplayType(Language);
406}
407
408void EndUserPackageSelectorPage::slotSpellingPackageButton()
409{
410 setPackageDisplayType(Spelling);
411}
412
413void EndUserPackageSelectorPage::slotFilterTextChanged(const QString &text)
414{
415 QTreeWidget *tree = ui.packageList;
416 if (text.isEmpty())
417 {
418 for(int i = 0; i < tree->topLevelItemCount(); i++)
419 {
420 QTreeWidgetItem *item = tree->topLevelItem (i);
421 item->setHidden(false);
422 for(int j = 0; j < item->childCount(); j++)
423 {
424 item->child(j)->setHidden(false);
425 }
426 }
427 return;
428 }
429
430 QList<QTreeWidgetItem *> list = tree->findItems (text, Qt::MatchContains|Qt::MatchRecursive, C_NAME );
431 Q_FOREACH(QTreeWidgetItem *item, tree->findItems (text, Qt::MatchContains|Qt::MatchRecursive, C_NOTES ))
432 {
433 if (!list.contains(item))
434 list.append(item);
435 }
436
437 Q_FOREACH(QTreeWidgetItem *item, list)
438 {
439 QTreeWidgetItem *parent = item->parent();
440 if (!list.contains(parent) && parent != tree->invisibleRootItem())
441 list.append(parent);
442 }
443
444 for(int i = 0; i < tree->topLevelItemCount(); i++)
445 {
446 QTreeWidgetItem *item = tree->topLevelItem (i);
447 item->setHidden(!list.contains(item));
448 for(int j = 0; j < item->childCount(); j++)
449 {
450 QTreeWidgetItem *child = item->child(j);
451 child->setHidden(!list.contains(child));
452 }
453 }
454}
455
456void EndUserPackageSelectorPage::slotInstallDebugPackages()
457{
458 Settings::instance().setInstallDebugPackages(ui.installDebugPackagesCheckBox->isChecked());
459 engine->setInstallDebugPackages(ui.installDebugPackagesCheckBox->isChecked());
460}
461
462bool EndUserPackageSelectorPage::validatePage()
463{
464 return true;
465}
466
467void EndUserPackageSelectorPage::cleanupPage()
468{
469 disconnect(ui.packageList,SIGNAL(itemClicked(QTreeWidgetItem *, int)),this,SLOT(itemClicked(QTreeWidgetItem *, int)));
470 disconnect(ui.selectAllCheckBox,SIGNAL(clicked()),this,SLOT(selectAllClicked()));
471
472 disconnect(ui.filterEdit,SIGNAL(textChanged(const QString &)),this,SLOT(slotFilterTextChanged(const QString &)));
473
474 disconnect(ui.applicationPackageButton,SIGNAL(clicked()),this,SLOT(slotApplicationPackageButton()));
475 disconnect(ui.languagePackageButton,SIGNAL(clicked()),this,SLOT(slotLanguagePackageButton()));
476 disconnect(ui.spellingPackageButton,SIGNAL(clicked()),this,SLOT(slotSpellingPackageButton()));
477
478 engine->unselectAllPackages();
479}
480
481bool EndUserPackageSelectorPage::isComplete()
482{
483 return true;
484}
485