1 /***************************************************************************
2                           rkloadlibsdialog  -  description
3                              -------------------
4     begin                : Mon Sep 6 2004
5     copyright            : (C) 2004 - 2018 by Thomas Friedrichsmeier
6     email                : thomas.friedrichsmeier@kdemail.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 #include "rkloadlibsdialog.h"
18 
19 #include <qwidget.h>
20 #include <qlayout.h>
21 #include <QTreeWidget>
22 #include <qlabel.h>
23 #include <qpushbutton.h>
24 #include <qcheckbox.h>
25 #include <qdir.h>
26 #include <qregexp.h>
27 #include <qtimer.h>
28 #include <qtextstream.h>
29 #include <QCloseEvent>
30 #include <QSortFilterProxyModel>
31 #include <QApplication>
32 #include <QLineEdit>
33 #include <QStandardPaths>
34 
35 #include <KLocalizedString>
36 #include <kmessagebox.h>
37 #include <kuser.h>
38 
39 #include "../rkglobals.h"
40 #include "../rbackend/rkrinterface.h"
41 #include "../settings/rksettingsmodulegeneral.h"
42 #include "../settings/rksettings.h"
43 #include "../core/robjectlist.h"
44 #include "../misc/rkprogresscontrol.h"
45 #include "../misc/rkstandardicons.h"
46 #include "../misc/rkcommonfunctions.h"
47 #include "../misc/rkdynamicsearchline.h"
48 
49 #include "../debug.h"
50 
51 #include <stdlib.h>
52 
53 
54 #define GET_CURRENT_LIBLOCS_COMMAND 1
55 
RKLoadLibsDialog(QWidget * parent,RCommandChain * chain,bool modal)56 RKLoadLibsDialog::RKLoadLibsDialog (QWidget *parent, RCommandChain *chain, bool modal) : KPageDialog (parent) {
57 	RK_TRACE (DIALOGS);
58 	RKLoadLibsDialog::chain = chain;
59 	installation_process = 0;
60 
61 	setFaceType (KPageDialog::Tabbed);
62 	setModal (modal);
63 	setWindowTitle (i18n ("Configure Packages"));
64 	setStandardButtons (QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
65 
66 	LoadUnloadWidget *luwidget = new LoadUnloadWidget (this);
67 	addChild (luwidget, i18n ("Load / Unload R packages"));
68 	connect (this, &RKLoadLibsDialog::installedPackagesChanged, luwidget, &LoadUnloadWidget::updateInstalledPackages);
69 
70 	install_packages_widget = new InstallPackagesWidget (this);
71 	install_packages_pageitem = addChild (install_packages_widget, i18n ("Install / Update / Remove R packages"));
72 
73 	configure_pluginmaps_pageitem = addChild (new RKPluginMapSelectionWidget (this), i18n ("Manage RKWard Plugins"));
74 
75 	connect (this, &KPageDialog::currentPageChanged, this, &RKLoadLibsDialog::slotPageChanged);
76 	QTimer::singleShot (0, this, SLOT (slotPageChanged()));
77 	num_child_widgets = 4;
78 	was_accepted = false;
79 
80 	RKGlobals::rInterface ()->issueCommand (".libPaths ()", RCommand::App | RCommand::GetStringVector, QString (), this, GET_CURRENT_LIBLOCS_COMMAND, chain);
81 }
82 
~RKLoadLibsDialog()83 RKLoadLibsDialog::~RKLoadLibsDialog () {
84 	RK_TRACE (DIALOGS);
85 
86 	if (was_accepted) KPageDialog::accept ();
87 	else KPageDialog::reject ();
88 }
89 
addChild(QWidget * child_page,const QString & caption)90 KPageWidgetItem* RKLoadLibsDialog::addChild (QWidget *child_page, const QString &caption) {
91 	RK_TRACE (DIALOGS);
92 
93 	// TODO: Can't convert these signal/slot connections to new syntax, without creating a common base class for the child pages
94 	connect (this, SIGNAL (accepted()), child_page, SLOT (ok()));
95 	connect (button (QDialogButtonBox::Apply), SIGNAL (clicked(bool)), child_page, SLOT (apply()));
96 	connect (this, SIGNAL(rejected()), child_page, SLOT (cancel()));
97 	connect (child_page, &QObject::destroyed, this, &RKLoadLibsDialog::childDeleted);
98 	return addPage (child_page, caption);
99 }
100 
slotPageChanged()101 void RKLoadLibsDialog::slotPageChanged () {
102 	RK_TRACE (DIALOGS);
103 
104 	if (!currentPage ()) return;
105 	QTimer::singleShot (0, currentPage ()->widget (), SLOT (activated()));
106 }
107 
108 //static
showInstallPackagesModal(QWidget * parent,RCommandChain * chain,const QString & package_name)109 void RKLoadLibsDialog::showInstallPackagesModal (QWidget *parent, RCommandChain *chain, const QString &package_name) {
110 	RK_TRACE (DIALOGS);
111 
112 	RKLoadLibsDialog *dialog = new RKLoadLibsDialog (parent, chain, true);
113 	dialog->auto_install_package = package_name;
114 	QTimer::singleShot (0, dialog, SLOT (automatedInstall()));		// to get past the dialog->exec, below
115 	dialog->setCurrentPage (dialog->install_packages_pageitem);
116 	dialog->exec ();
117 	RK_TRACE (DIALOGS);
118 }
119 
120 // static
showPluginmapConfig(QWidget * parent,RCommandChain * chain)121 void RKLoadLibsDialog::showPluginmapConfig (QWidget* parent, RCommandChain* chain) {
122 	RK_TRACE (DIALOGS);
123 
124 	RKLoadLibsDialog *dialog = new RKLoadLibsDialog (parent, chain, false);
125 	dialog->setCurrentPage (dialog->configure_pluginmaps_pageitem);
126 	dialog->show ();
127 }
128 
automatedInstall()129 void RKLoadLibsDialog::automatedInstall () {
130 	RK_TRACE (DIALOGS);
131 
132 	install_packages_widget->initialize ();
133 	install_packages_widget->trySelectPackage (auto_install_package);
134 }
135 
tryDestruct()136 void RKLoadLibsDialog::tryDestruct () {
137 	RK_TRACE (DIALOGS);
138 
139 	if (num_child_widgets <= 0) {
140 		deleteLater ();
141 	}
142 }
143 
childDeleted()144 void RKLoadLibsDialog::childDeleted () {
145 	RK_TRACE (DIALOGS);
146 	--num_child_widgets;
147 	tryDestruct ();
148 }
149 
closeEvent(QCloseEvent * e)150 void RKLoadLibsDialog::closeEvent (QCloseEvent *e) {
151 	RK_TRACE (DIALOGS);
152 	e->accept ();
153 
154 	// do as if cancel button was clicked:
155 	reject ();
156 }
157 
accept()158 void RKLoadLibsDialog::accept () {
159 	was_accepted = true;
160 	hide ();
161 	// will self-destruct once all children are done
162 	emit (accepted());
163 }
164 
reject()165 void RKLoadLibsDialog::reject () {
166 	was_accepted = false;
167 	hide ();
168 	// will self-destruct once all children are done
169 	emit (rejected());
170 }
171 
rCommandDone(RCommand * command)172 void RKLoadLibsDialog::rCommandDone (RCommand *command) {
173 	RK_TRACE (DIALOGS);
174 	if (command->getFlags () == GET_CURRENT_LIBLOCS_COMMAND) {
175 		RK_ASSERT (command->getDataType () == RData::StringVector);
176 		RK_ASSERT (command->getDataLength () > 0);
177 		// NOTE: The problem is that e.g. R_LIBS_USER is not in .libPaths() if it does not exist, yet. But it should be available as an option, of course
178 		library_locations = command->stringVector ();
179 		emit (libraryLocationsChanged (library_locations));
180 	} else {
181 		RK_ASSERT (false);
182 	}
183 }
184 
addLibraryLocation(const QString & new_loc)185 void RKLoadLibsDialog::addLibraryLocation (const QString& new_loc) {
186 	RK_TRACE (DIALOGS);
187 
188 	if (library_locations.contains (new_loc)) return;
189 
190 	if (QDir ().mkpath (new_loc)) RKSettingsModuleRPackages::addLibraryLocation (new_loc, chain);
191 	library_locations.prepend (new_loc);
192 	emit (libraryLocationsChanged (library_locations));
193 }
194 
removePackages(QStringList packages,QStringList from_liblocs)195 bool RKLoadLibsDialog::removePackages (QStringList packages, QStringList from_liblocs) {
196 	RK_TRACE (DIALOGS);
197 
198 	bool as_root = false;
199 	QStringList not_removing;
200 	QStringList not_writable;
201 	QList<int> not_writable_int;
202 	for (int i = 0; i < packages.count (); ++i) {
203 		if (RKSettingsModuleRPackages::essentialPackages ().contains (packages[i])) {
204 			not_removing.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
205 			packages.removeAt (i);
206 			from_liblocs.removeAt (i);
207 			--i;
208 		} else {
209 			QFileInfo fi (from_liblocs[i]);
210 			if (!fi.isWritable ()) {
211 				not_writable.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
212 				not_writable_int.append (i);
213 			}
214 		}
215 	}
216 	if (!not_removing.isEmpty ()) {
217 		KMessageBox::informationList (this, i18n ("The following packages, which you have selected for removal, are essential to the operation of RKWard, and will not be removed. If you are absolutely sure, that you want to remove these packages, please do so on the R command line."), not_removing, i18n ("Not removing certain packages"));
218 	}
219 	if (packages.isEmpty ()) return false;
220 
221 	if (!not_writable.isEmpty ()) {
222 #ifdef Q_OS_WIN
223 		KMessageBox::informationList (this, i18n ("Your current user permissions do not allow removing the following packages. These will be skipped."), not_writable, i18n ("Insufficient user permissions"));
224 		int res = KMessageBox::No;
225 #else
226 		int res = KMessageBox::questionYesNoList (this, i18n ("Your current user permissions do not allow removing the following packages. Do you want to skip these packages, or do you want to proceed with administrator privileges (you will be prompted for the password)?"), not_writable, i18n ("Insufficient user permissions"), KGuiItem ("Become root"), KGuiItem ("Skip these packages"));
227 #endif
228 		if (res == KMessageBox::Yes) as_root = true;
229 		else {
230 			for (int i = not_writable_int.count () - 1; i >= 0; --i) {
231 				packages.removeAt (not_writable_int[i]);
232 				from_liblocs.removeAt (not_writable_int[i]);
233 			}
234 		}
235 	}
236 	if (packages.isEmpty ()) return false;
237 	RK_ASSERT (packages.count () == from_liblocs.count ());
238 
239 	QStringList descriptions;
240 	QString command;
241 	for (int i = 0; i < packages.count (); ++i) {
242 		descriptions.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
243 		// NOTE: the "lib"-parameter to remove.packages is NOT matched to the pkgs-parameter. Therefore, we simply concatenate a bunch of single removals.
244 		command.append ("remove.packages (" + RObject::rQuote (packages[i]) + ", " + RObject::rQuote (from_liblocs[i]) + ")\n");
245 	}
246 
247 	// last check. This may be an annoying third dialog, in the worst case, but at least it can be turned off.
248 	int res = KMessageBox::warningContinueCancelList (this, i18n ("You are about to remove the following packages. Are you sure you want to proceed?"), descriptions, i18n ("About to remove packages"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "remove_packages_warning");
249 	if (res != KMessageBox::Continue) return false;
250 
251 	runInstallationCommand (command, as_root, i18n ("Please stand by while removing selected packages"), i18n ("Removing packages"));
252 
253 	return true;
254 }
255 
256 #ifdef Q_OS_WIN
257 extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
258 #endif
installPackages(const QStringList & packages,QString to_libloc,bool install_suggested_packages,const QStringList & repos)259 bool RKLoadLibsDialog::installPackages (const QStringList &packages, QString to_libloc, bool install_suggested_packages, const QStringList& repos) {
260 	RK_TRACE (DIALOGS);
261 
262 	if (packages.isEmpty ()) return false;
263 
264 	bool as_root = false;
265 	// It is ok, if the selected location does not yet exist. In order to know, whether we can write to it, we have to create it first.
266 	QDir().mkpath (to_libloc);
267 	QString altlibloc = RKSettingsModuleRPackages::addUserLibLocTo (library_locations).value (0);
268 #ifdef Q_OS_WIN
269 	extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
270 	qt_ntfs_permission_lookup++;
271 #endif
272 	QFileInfo fi = QFileInfo (to_libloc);
273 	bool writable = fi.isWritable ();
274 #ifdef Q_OS_WIN
275 	qt_ntfs_permission_lookup--;
276 #endif
277 	if (!writable) {
278 		QString mcaption = i18n ("Selected library location not writable");
279 		QString message = i18n ("<p>The directory you have selected for installation (%1) is not writable with your current user permissions.</p>"
280 			"<p>Would you like to install to %2, instead (you can also press \"Cancel\" and use the \"Configure Repositories\"-button to set up a different directory)?</p>", to_libloc, altlibloc);
281 #ifdef Q_OS_WIN
282 		message.append (i18n ("<p>Alternatively, if you have access to an administrator account on this machine, you can use that to install the package(s), or "
283 			"you could change the permissions of '%1'. Sorry, automatic switching to Administrator is not yet supported in RKWard on Windows.</p>", to_libloc));
284 		int res = KMessageBox::warningContinueCancel (this, message, mcaption, KGuiItem (i18n ("Install to %1", altlibloc)));
285 		if (res == KMessageBox::Continue) to_libloc = altlibloc;
286 #else
287 		message.append (i18n ("<p>Alternatively, if you are the administrator of this machine, you can try to install the packages as root (you'll be prompted for the root password).</p>"));
288 		int res = KMessageBox::warningYesNoCancel (this, message, mcaption, KGuiItem (i18n ("Install to %1", altlibloc)), KGuiItem (i18n ("Become root")));
289 		if (res == KMessageBox::Yes) to_libloc = altlibloc;
290 		if (res == KMessageBox::No) as_root = true;
291 #endif
292 		if (res == KMessageBox::Cancel) return false;
293 	}
294 
295 	addLibraryLocation (to_libloc);
296 
297 	QString command_string = "install.packages (c (\"" + packages.join ("\", \"") + "\")" + ", lib=" + RObject::rQuote (to_libloc);
298 	QString downloaddir = QDir (RKSettingsModuleGeneral::filesPath ()).filePath ("package_archive");
299 	if (RKSettingsModuleRPackages::archivePackages ()) {
300 		QDir (RKSettingsModuleGeneral::filesPath ()).mkdir ("package_archive");
301 		command_string += ", destdir=" + RObject::rQuote (downloaddir);
302 	}
303 	if (install_suggested_packages) command_string += ", dependencies=TRUE";
304 	command_string += ")\n";
305 
306 	QString repos_string = "options (repos= c(";
307 	for (int i = 0; i < repos.count (); ++i) {
308 		if (i) repos_string.append (", ");
309 		repos_string.append (RObject::rQuote (repos[i]));
310 	}
311 	repos_string.append ("))\n");
312 
313 	repos_string.append (RKSettingsModuleRPackages::pkgTypeOption ());
314 
315 	if (as_root) {
316 		KUser user;
317 		command_string.append ("system (\"chown " + user.loginName() + ' ' + downloaddir + "/*\")\n");
318 	}
319 
320 	runInstallationCommand (repos_string + command_string, as_root, i18n ("Please stand by while installing selected packages"), i18n ("Installing packages"));
321 
322 	return true;
323 }
324 
runInstallationCommand(const QString & command,bool as_root,const QString & message,const QString & title)325 void RKLoadLibsDialog::runInstallationCommand (const QString& command, bool as_root, const QString& message, const QString& title) {
326 	RK_TRACE (DIALOGS);
327 
328 	// TODO: won't work with some versions of GCC (which ones exactly)?
329 //	QFile file (QDir (RKSettingsModuleGeneral::filesPath ()).filePath ("install_script.R"));
330 // WORKAROUND:
331 	QDir dir = RKSettingsModuleGeneral::filesPath ();
332 	QFile file (dir.filePath ("install_script.R"));
333 // WORKADOUND END
334 	if (file.open (QIODevice::WriteOnly)) {
335 		QTextStream stream (&file);
336 		stream << command;
337 		stream << "q ()\n";
338 		file.close();
339 	} else {
340 		RK_ASSERT (false);
341 	}
342 
343 	QString R_binary (getenv ("R_BINARY"));
344 	QString call;
345 	QStringList params;
346 #ifdef Q_OS_WIN
347 	RK_ASSERT (!as_root);
348 	call = R_binary;
349 #else
350 	if (as_root) {
351 		call = QStandardPaths::findExecutable ("kdesu");
352 		if (call.isEmpty ()) call = QStandardPaths::findExecutable ("kdesudo");
353 		params << "-t" << "--" << R_binary;
354 	} else {
355 		call = R_binary;
356 	}
357 #endif
358 	params << "--no-save" << "--no-restore" << "--file=" + file.fileName ();
359 
360 	installation_process = new QProcess ();
361 	installation_process->setProcessChannelMode (QProcess::SeparateChannels);
362 
363 	connect (installation_process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &RKLoadLibsDialog::processExited);
364 	connect (installation_process, &QProcess::readyReadStandardOutput, this, &RKLoadLibsDialog::installationProcessOutput);
365 	connect (installation_process, &QProcess::readyReadStandardError, this, &RKLoadLibsDialog::installationProcessError);
366 
367 	RKProgressControl *installation_progress = new RKProgressControl (this, message, title, RKProgressControl::CancellableProgress);
368 	connect (this, &RKLoadLibsDialog::installationComplete, installation_progress, &RKProgressControl::done);
369 	connect (this, &RKLoadLibsDialog::installationOutput, installation_progress, static_cast<void (RKProgressControl::*)(const QString&)>(&RKProgressControl::newOutput));
370 	connect (this, &RKLoadLibsDialog::installationError, installation_progress, &RKProgressControl::newError);
371 
372 	installation_process->start (call, params, QIODevice::ReadWrite | QIODevice::Unbuffered);
373 
374 	if (!installation_progress->doModal (true)) {
375 		installation_process->kill ();
376 		installation_process->waitForFinished (5000);
377 	}
378 
379 	file.remove ();
380 	delete installation_process;
381 	installation_process = 0;
382 	emit (installedPackagesChanged ());
383 }
384 
installationProcessOutput()385 void RKLoadLibsDialog::installationProcessOutput () {
386 	RK_TRACE (DIALOGS);
387 	RK_ASSERT (installation_process);
388 
389 	emit (installationOutput (QString::fromLocal8Bit (installation_process->readAllStandardOutput())));
390 }
391 
installationProcessError()392 void RKLoadLibsDialog::installationProcessError () {
393 	RK_TRACE (DIALOGS);
394 	RK_ASSERT (installation_process);
395 
396 	emit (installationError (QString::fromLocal8Bit (installation_process->readAllStandardError ())));
397 }
398 
processExited(int exitCode,QProcess::ExitStatus exitStatus)399 void RKLoadLibsDialog::processExited (int exitCode, QProcess::ExitStatus exitStatus) {
400 	RK_TRACE (DIALOGS);
401 
402 	if (exitCode || (exitStatus != QProcess::NormalExit)) {
403 		installationError ('\n' + i18n ("Installation process died with exit code %1", exitCode));
404 	}
405 
406 	emit (installationComplete ());
407 }
408 
409 ////////////////////// LoadUnloadWidget ////////////////////////////
410 
411 #define GET_INSTALLED_PACKAGES 1
412 #define GET_LOADED_PACKAGES 2
413 #define LOAD_PACKAGE_COMMAND 3
414 
LoadUnloadWidget(RKLoadLibsDialog * dialog)415 LoadUnloadWidget::LoadUnloadWidget (RKLoadLibsDialog *dialog) : QWidget (0) {
416 	RK_TRACE (DIALOGS);
417 	LoadUnloadWidget::parent = dialog;
418 
419 	QVBoxLayout *mvbox = new QVBoxLayout (this);
420 	mvbox->setContentsMargins (0, 0, 0, 0);
421 
422 	QHBoxLayout *hbox = new QHBoxLayout ();
423 	mvbox->addLayout (hbox);
424 	hbox->setContentsMargins (0, 0, 0, 0);
425 	QVBoxLayout *instvbox = new QVBoxLayout ();
426 	hbox->addLayout (instvbox);
427 	instvbox->setContentsMargins (0, 0, 0, 0);
428 	QVBoxLayout *buttonvbox = new QVBoxLayout ();
429 	hbox->addLayout (buttonvbox);
430 	buttonvbox->setContentsMargins (0, 0, 0, 0);
431 	QVBoxLayout *loadedvbox = new QVBoxLayout ();
432 	hbox->addLayout (loadedvbox);
433 	loadedvbox->setContentsMargins (0, 0, 0, 0);
434 
435 	QLabel *label = new QLabel (i18n ("Installed packages"), this);
436 	installed_view = new QTreeWidget (this);
437 	installed_view->setHeaderLabels (QStringList () << i18n ("Name") << i18n ("Title") << i18n ("Version") << i18n ("Location"));
438 	installed_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
439 	instvbox->addWidget (label);
440 	instvbox->addWidget (installed_view);
441 
442 	load_button = new QPushButton (RKStandardIcons::getIcon (RKStandardIcons::ActionAddRight), i18n ("Load"), this);
443 	connect (load_button, &QPushButton::clicked, this, &LoadUnloadWidget::loadButtonClicked);
444 	detach_button = new QPushButton (RKStandardIcons::getIcon (RKStandardIcons::ActionRemoveLeft), i18n ("Unload"), this);
445 	connect (detach_button, &QPushButton::clicked, this, &LoadUnloadWidget::detachButtonClicked);
446 	buttonvbox->addStretch (1);
447 	buttonvbox->addWidget (load_button);
448 	buttonvbox->addWidget (detach_button);
449 	buttonvbox->addStretch (2);
450 
451 	label = new QLabel (i18n ("Loaded packages"), this);
452 	loaded_view = new QTreeWidget (this);
453 	loaded_view->setHeaderLabel (i18n ("Name"));
454 	loaded_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
455 	loadedvbox->addWidget (label);
456 	loadedvbox->addWidget (loaded_view);
457 
458 	connect (loaded_view, &QTreeWidget::itemSelectionChanged, this, &LoadUnloadWidget::updateButtons);
459 	connect (installed_view, &QTreeWidget::itemSelectionChanged, this, &LoadUnloadWidget::updateButtons);
460 
461 	updateInstalledPackages ();
462 	updateButtons ();
463 }
464 
~LoadUnloadWidget()465 LoadUnloadWidget::~LoadUnloadWidget () {
466 	RK_TRACE (DIALOGS);
467 }
468 
activated()469 void LoadUnloadWidget::activated () {
470 	RK_TRACE (DIALOGS);
471 
472 	installed_view->setFocus ();
473 }
474 
rCommandDone(RCommand * command)475 void LoadUnloadWidget::rCommandDone (RCommand *command) {
476 	RK_TRACE (DIALOGS);
477 	if (command->failed ()) return;
478 	if (command->getFlags () == GET_INSTALLED_PACKAGES) {
479 		RK_ASSERT (command->getDataLength () == 5);
480 
481 		RData::RDataStorage data = command->structureVector ();
482 		QStringList package = data.at (0)->stringVector ();
483 		QStringList title = data.at (1)->stringVector ();
484 		QStringList version = data.at (2)->stringVector ();
485 		QStringList libpath = data.at (3)->stringVector ();
486 
487 		int count = package.size ();
488 		RK_ASSERT (count == title.size ());
489 		RK_ASSERT (count == version.size ());
490 		RK_ASSERT (count == libpath.size ());
491 		for (int i=0; i < count; ++i) {
492 			QTreeWidgetItem* item = new QTreeWidgetItem (installed_view);
493 			item->setText (0, package.at (i));
494 			item->setText (1, title.at (i));
495 			item->setText (2, version.at (i));
496 			item->setText (3, libpath.at (i));
497 		}
498 		installed_view->resizeColumnToContents (0);
499 		installed_view->setSortingEnabled (true);
500 		installed_view->sortItems (0, Qt::AscendingOrder);
501 	} else if (command->getFlags () == GET_LOADED_PACKAGES) {
502 		RK_ASSERT (command->getDataType () == RData::StringVector);
503 		QStringList data = command->stringVector ();
504 		for (int i=0; i < data.size (); ++i) {
505 			QTreeWidgetItem* item = new QTreeWidgetItem (loaded_view);
506 			item->setText (0, data.at (i));
507 			if (RKSettingsModuleRPackages::essentialPackages ().contains (data.at (i))) {
508 				item->setFlags (Qt::NoItemFlags);
509 			}
510 		}
511 		loaded_view->resizeColumnToContents (0);
512 		loaded_view->setSortingEnabled (true);
513 		loaded_view->sortItems (0, Qt::AscendingOrder);
514 		updateCurrentList ();
515 	} else if (command->getFlags () == LOAD_PACKAGE_COMMAND) {
516 		emit (loadUnloadDone ());
517 	} else {
518 		RK_ASSERT (false);
519 	}
520 }
521 
updateInstalledPackages()522 void LoadUnloadWidget::updateInstalledPackages () {
523 	RK_TRACE (DIALOGS);
524 
525 	installed_view->clear ();
526 	loaded_view->clear ();
527 
528 	RKGlobals::rInterface ()->issueCommand (".rk.get.installed.packages ()", RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, GET_INSTALLED_PACKAGES, parent->chain);
529 	RKGlobals::rInterface ()->issueCommand (".packages ()", RCommand::App | RCommand::Sync | RCommand::GetStringVector, QString (), this, GET_LOADED_PACKAGES, parent->chain);
530 }
531 
loadButtonClicked()532 void LoadUnloadWidget::loadButtonClicked () {
533 	RK_TRACE (DIALOGS);
534 
535 	loaded_view->clearSelection ();
536 	QList<QTreeWidgetItem*> sel = installed_view->selectedItems ();
537 	for (int i = 0; i < sel.size (); ++i) {
538 		QString package_name = sel[i]->text (0);
539 
540 		// is this package already loaded?
541 		QList<QTreeWidgetItem*> loaded = loaded_view->findItems (package_name, Qt::MatchExactly, 0);
542 		if (loaded.isEmpty ()) {
543 			QTreeWidgetItem* item = new QTreeWidgetItem (loaded_view);
544 			item->setText (0, package_name);
545 			item->setSelected (true);
546 		}
547 	}
548 }
549 
detachButtonClicked()550 void LoadUnloadWidget::detachButtonClicked () {
551 	RK_TRACE (DIALOGS);
552 
553 	installed_view->clearSelection ();
554 	QList<QTreeWidgetItem*> sel = loaded_view->selectedItems ();
555 	for (int i = 0; i < sel.size (); ++i) {
556 		QString package_name = sel[i]->text (0);
557 
558 		delete (sel[i]);	// remove from list of loaded packages
559 
560 		// select corresponding package in list of available packages
561 		QList<QTreeWidgetItem*> installed = installed_view->findItems (package_name, Qt::MatchExactly, 0);
562 		if (!installed.isEmpty ()) {
563 			//RK_ASSERT (installed.count () == 1); // In fact, several versions of one package can be installed in several library locations
564 			installed[0]->setSelected (true);
565 		}
566 	}
567 }
568 
updateButtons()569 void LoadUnloadWidget::updateButtons () {
570 	RK_TRACE (DIALOGS);
571 
572 	detach_button->setEnabled (!loaded_view->selectedItems ().isEmpty ());
573 	load_button->setEnabled (!installed_view->selectedItems ().isEmpty ());
574 }
575 
ok()576 void LoadUnloadWidget::ok () {
577 	RK_TRACE (DIALOGS);
578 	doLoadUnload ();
579 	deleteLater ();
580 }
581 
updateCurrentList()582 void LoadUnloadWidget::updateCurrentList () {
583 	RK_TRACE (DIALOGS);
584 
585 	prev_packages.clear ();
586 	for (int i = 0; i < loaded_view->topLevelItemCount (); ++i) {
587 		prev_packages.append (loaded_view->topLevelItem (i)->text (0));
588 	}
589 }
590 
doLoadUnload()591 void LoadUnloadWidget::doLoadUnload () {
592 	RK_TRACE (DIALOGS);
593 
594 	RKProgressControl *control = new RKProgressControl (this, i18n ("There has been an error while trying to load / unload packages. See transcript below for details"), i18n ("Error while handling packages"), RKProgressControl::DetailedError);
595 	connect (this, &LoadUnloadWidget::loadUnloadDone, control, &RKProgressControl::done);
596 
597 	// load packages previously not loaded
598 	for (int i = 0; i < loaded_view->topLevelItemCount (); ++i) {
599 		QTreeWidgetItem* loaded = loaded_view->topLevelItem (i);
600 		if (!prev_packages.contains (loaded->text (0))) {
601 			RCommand *command = new RCommand ("library (\"" + loaded->text (0) + "\")", RCommand::App | RCommand::ObjectListUpdate);
602 			control->addRCommand (command);
603 			RKGlobals::rInterface ()->issueCommand (command, parent->chain);
604 		}
605 	}
606 
607 	// detach packages previously attached
608 	QStringList packages_to_remove;
609 	for (QStringList::Iterator it = prev_packages.begin (); it != prev_packages.end (); ++it) {
610 		QList<QTreeWidgetItem*> loaded = loaded_view->findItems ((*it), Qt::MatchExactly, 0);
611 		if (loaded.isEmpty ()) {	// no longer in the list
612 			packages_to_remove.append ("package:" + *it);
613 		}
614 	}
615 	if (!packages_to_remove.isEmpty ()) {
616 		QStringList messages = RObjectList::getObjectList ()->detachPackages (packages_to_remove, parent->chain, control);
617 		if (!messages.isEmpty ()) KMessageBox::sorry (this, messages.join ("\n"));
618 	}
619 
620 	// find out, when we're done
621 	RCommand *command = new RCommand (QString (), RCommand::EmptyCommand, QString (), this, LOAD_PACKAGE_COMMAND);
622 	RKGlobals::rInterface ()->issueCommand (command, parent->chain);
623 
624 	control->doNonModal (true);
625 }
626 
apply()627 void LoadUnloadWidget::apply () {
628 	RK_TRACE (DIALOGS);
629 
630 	doLoadUnload ();
631 	updateCurrentList ();
632 }
633 
cancel()634 void LoadUnloadWidget::cancel () {
635 	RK_TRACE (DIALOGS);
636 	deleteLater ();
637 }
638 
639 /////////////////////// InstallPackagesWidget //////////////////////////
640 #include <QHeaderView>
641 #include <QStyledItemDelegate>
642 
643 /** Responsible for drawing the "category" items */
644 class InstallPackagesDelegate : public QStyledItemDelegate {
645 public:
InstallPackagesDelegate(QTreeView * parent)646 	InstallPackagesDelegate (QTreeView* parent) : QStyledItemDelegate (parent) {
647 		table = parent;
648 		expanded = RKStandardIcons::getIcon (RKStandardIcons::ActionCollapseUp);
649 		collapsed = RKStandardIcons::getIcon (RKStandardIcons::ActionExpandDown);
650 	}
initStyleOption(QStyleOptionViewItem * option,const QModelIndex & index) const651 	void initStyleOption (QStyleOptionViewItem* option, const QModelIndex& index) const override {
652 		QStyledItemDelegate::initStyleOption (option, index);
653 		if (!index.parent ().isValid ()) {
654 			int ccount = index.model ()->rowCount (index);
655 			option->text = option->text + " (" + QString::number (ccount) + ')';
656 			if (ccount) {
657 				option->icon = table->isExpanded (index) ? expanded : collapsed;
658 			} else {
659 				option->icon = QIcon ();    // empty dummy icon to reserve space
660 			}
661 			option->features |= QStyleOptionViewItem::HasDecoration;
662 			option->font.setBold (true);
663 			option->backgroundBrush = table->palette ().mid ();
664 		}
665 	}
666 	QTreeView* table;
667 	QIcon expanded;
668 	QIcon collapsed;
669 };
670 
InstallPackagesWidget(RKLoadLibsDialog * dialog)671 InstallPackagesWidget::InstallPackagesWidget (RKLoadLibsDialog *dialog) : QWidget (0) {
672 	RK_TRACE (DIALOGS);
673 	InstallPackagesWidget::parent = dialog;
674 
675 	QHBoxLayout *hbox = new QHBoxLayout (this);
676 	hbox->setContentsMargins (0, 0, 0, 0);
677 
678 	QVBoxLayout *vbox = new QVBoxLayout ();
679 	vbox->setContentsMargins (0, 0, 0, 0);
680 	hbox->addLayout (vbox);
681 	hbox->setStretchFactor (vbox, 2);
682 
683 	packages_status = new RKRPackageInstallationStatus (this);
684 	packages_view = new QTreeView (this);
685 	packages_view->setSortingEnabled (true);
686 	model = new RKRPackageInstallationStatusSortFilterModel (this);
687 	model->setSourceModel (packages_status);
688 	model->setSortCaseSensitivity (Qt::CaseInsensitive);
689 	packages_view->setModel (model);
690 	packages_view->setItemDelegateForColumn (0, new InstallPackagesDelegate (packages_view));
691 	for (int i = 0; i < model->rowCount (); ++i) {  // the root level captions
692 		packages_view->setFirstColumnSpanned (i, QModelIndex (), true);
693 	}
694 	connect (packages_view, &QTreeView::clicked, this, &InstallPackagesWidget::rowClicked);
695 	packages_view->setRootIsDecorated (false);
696 	packages_view->setIndentation (0);
697 	packages_view->setEnabled (false);
698 	packages_view->setMinimumHeight (packages_view->sizeHintForRow (0) * 15);	// force a decent height
699 	packages_view->setMinimumWidth (packages_view->fontMetrics ().width ("This is to force a sensible min width for the packages view (empty on construction)"));
700 	vbox->addWidget (packages_view);
701 
702 	QPushButton *configure_repos_button = new QPushButton (i18n ("Configure Repositories"), this);
703 	RKCommonFunctions::setTips (i18n ("Many packages are available on CRAN (Comprehensive R Archive Network), and other repositories.<br>Click this to add more sources."), configure_repos_button);
704 	connect (configure_repos_button, &QPushButton::clicked, this, &InstallPackagesWidget::configureRepositories);
705 	vbox->addWidget (configure_repos_button);
706 
707 	QVBoxLayout *buttonvbox = new QVBoxLayout ();
708 	hbox->addLayout (buttonvbox);
709 	buttonvbox->setContentsMargins (0, 0, 0, 0);
710 	QLabel *label = new QLabel (i18n ("Show only packages matching:"), this);
711 	filter_edit = new RKDynamicSearchLine (this);
712 	RKCommonFunctions::setTips (i18n ("<p>You can limit the packages displayed in the list to with names or titles matching a filter string.</p>") + filter_edit->regexpTip (), label, filter_edit);
713 	filter_edit->setModelToFilter (model);
714 	// NOTE: Although the search line sets the filter in the model, automatically, we connect it, here, in order to expand new and updateable sections, when the filter changes.
715 	connect (filter_edit, &RKDynamicSearchLine::searchChanged, this, &InstallPackagesWidget::filterChanged);
716 	rkward_packages_only = new QCheckBox (i18n ("Show only packages providing RKWard dialogs"), this);
717 	RKCommonFunctions::setTips (i18n ("<p>Some but not all R packages come with plugins for RKWard. That means they provide a graphical user-interface in addition to R functions. Check this box to show only such packages.</p><p></p>"), rkward_packages_only);
718 	connect (rkward_packages_only, &QCheckBox::stateChanged, this, &InstallPackagesWidget::filterChanged);
719 	filterChanged ();
720 
721 	mark_all_updates_button = new QPushButton (i18n ("Select all updates"), this);
722 	connect (mark_all_updates_button, &QPushButton::clicked, this, &InstallPackagesWidget::markAllUpdates);
723 
724 	install_params = new PackageInstallParamsWidget (this);
725 	connect (parent, &RKLoadLibsDialog::libraryLocationsChanged, install_params, &PackageInstallParamsWidget::liblocsChanged);
726 
727 	buttonvbox->addWidget (label);
728 	buttonvbox->addWidget (filter_edit);
729 	buttonvbox->addWidget (rkward_packages_only);
730 	buttonvbox->addStretch (1);
731 	buttonvbox->addWidget (mark_all_updates_button);
732 	buttonvbox->addStretch (1);
733 	buttonvbox->addWidget (install_params);
734 	buttonvbox->addStretch (1);
735 }
736 
~InstallPackagesWidget()737 InstallPackagesWidget::~InstallPackagesWidget () {
738 	RK_TRACE (DIALOGS);
739 }
740 
activated()741 void InstallPackagesWidget::activated () {
742 	RK_TRACE (DIALOGS);
743 
744 	filter_edit->setFocus ();
745 	if (!packages_status->initialized ()) {
746 		initialize ();
747 		packages_view->sortByColumn (RKRPackageInstallationStatus::PackageName, Qt::AscendingOrder);
748 	}
749 }
750 
initialize()751 void InstallPackagesWidget::initialize () {
752 	RK_TRACE (DIALOGS);
753 
754 	packages_status->initialize (parent->chain);
755 	packages_view->setEnabled (true);
756 	// Force a good width for the icon column, particularly for MacOS X.
757 	packages_view->header ()->resizeSection (0, packages_view->sizeHintForIndex (model->index (0, 0, model->index (RKRPackageInstallationStatus::NewPackages, 0, QModelIndex ()))).width () + packages_view->indentation ());
758 	for (int i = 1; i <= RKRPackageInstallationStatus::PackageName; ++i) {
759 		packages_view->resizeColumnToContents (i);
760 	}
761 	// For whatever reason, we have to re-set these, here.
762 	for (int i = 0; i < model->rowCount (); ++i) {
763 		packages_view->setFirstColumnSpanned (i, QModelIndex (), true);
764 	}
765 	window()->raise(); // needed on Mac, otherwise the dialog may go hiding behind the main app window, after the progress control window closes, for some reason
766 }
767 
rowClicked(const QModelIndex & row)768 void InstallPackagesWidget::rowClicked (const QModelIndex& row) {
769 	RK_TRACE (DIALOGS);
770 
771 	if (!row.parent ().isValid ()) {
772 		QModelIndex fixed_row = model->index (row.row (), 0, row.parent ());
773 		packages_view->setExpanded (fixed_row, !packages_view->isExpanded (fixed_row));
774 	}
775 }
776 
filterChanged()777 void InstallPackagesWidget::filterChanged () {
778 	RK_TRACE (DIALOGS);
779 
780 	model->setRKWardOnly (rkward_packages_only->isChecked ());
781 	packages_view->expand (model->mapFromSource (packages_status->index(RKRPackageInstallationStatus::UpdateablePackages, 0)));
782 	packages_view->expand (model->mapFromSource (packages_status->index(RKRPackageInstallationStatus::NewPackages, 0)));
783 	// NOTE: filter string already set by RKDynamicSearchLine
784 }
785 
trySelectPackage(const QString & package_name)786 void InstallPackagesWidget::trySelectPackage (const QString &package_name) {
787 	RK_TRACE (DIALOGS);
788 
789 	QModelIndex index = packages_status->markPackageForInstallation (package_name);
790 	if (!index.isValid ()) {
791 		KMessageBox::sorry (0, i18n ("The package requested by the backend (\"%1\") was not found in the package repositories. Maybe the package name was mis-spelled. Or maybe you need to add additional repositories via the \"Configure Repositories\" button.", package_name), i18n ("Package not available"));
792 	} else {
793 		packages_view->scrollTo (model->mapFromSource (index));
794 	}
795 }
796 
markAllUpdates()797 void InstallPackagesWidget::markAllUpdates () {
798 	RK_TRACE (DIALOGS);
799 
800 	QModelIndex index = packages_status->markAllUpdatesForInstallation ();
801 	packages_view->setExpanded (model->mapFromSource (index), true);
802 	packages_view->scrollTo (model->mapFromSource (index));
803 }
804 
doInstall(bool refresh)805 void InstallPackagesWidget::doInstall (bool refresh) {
806 	RK_TRACE (DIALOGS);
807 
808 	bool changed = false;
809 	QStringList remove;
810 	QStringList remove_locs;
811 	packages_status->packagesToRemove (&remove, &remove_locs);
812 	if (!remove.isEmpty ()) {
813 		RK_ASSERT (remove.count () == remove_locs.count ());
814 		changed |= parent->removePackages (remove, remove_locs);
815 	}
816 
817 	QStringList install = packages_status->packagesToInstall ();
818 	if (!install.isEmpty ()) {
819 		QString dest = install_params->installLocation ();
820 		if (!dest.isEmpty ()) {
821 			changed |= parent->installPackages (install, dest, install_params->installSuggestedPackages (), packages_status->currentRepositories ());
822 		}
823 	}
824 
825 	if (changed && refresh) {
826 		packages_status->clearStatus ();
827 		initialize ();
828 	}
829 }
830 
apply()831 void InstallPackagesWidget::apply () {
832 	RK_TRACE (DIALOGS);
833 
834 	doInstall (true);
835 }
836 
ok()837 void InstallPackagesWidget::ok () {
838 	RK_TRACE (DIALOGS);
839 
840 	doInstall (false);
841 	deleteLater ();
842 }
843 
cancel()844 void InstallPackagesWidget::cancel () {
845 	RK_TRACE (DIALOGS);
846 	deleteLater ();
847 }
848 
configureRepositories()849 void InstallPackagesWidget::configureRepositories () {
850 	RK_TRACE (DIALOGS);
851 	RKSettings::configureSettings (RKSettings::PageRPackages, this, parent->chain);
852 }
853 
854 /////////////////////// PackageInstallParamsWidget //////////////////////////
855 
856 #include <qcombobox.h>
857 #include <qfileinfo.h>
858 
PackageInstallParamsWidget(QWidget * parent)859 PackageInstallParamsWidget::PackageInstallParamsWidget (QWidget *parent) : QWidget (parent) {
860 	RK_TRACE (DIALOGS);
861 
862 	QVBoxLayout *vbox = new QVBoxLayout (this);
863 	vbox->setContentsMargins (0, 0, 0, 0);
864 	vbox->addWidget (new QLabel (i18n ("Install packages to:"), this));
865 	libloc_selector = new QComboBox (this);
866 	vbox->addWidget (libloc_selector);
867 
868 	suggested_packages = new QCheckBox (i18n ("Install suggested packages"), this);
869 	suggested_packages->setChecked (false);
870 	RKCommonFunctions::setTips (QString ("<p>%1</p>").arg (i18n ("Some packages \"suggest\" additional packages, which are not strictly necessary for using that package, but which may provide additional related functionality. Check this option to include such additional suggested packages.")), suggested_packages);
871 	vbox->addStretch ();
872 	vbox->addWidget (suggested_packages);
873 }
874 
~PackageInstallParamsWidget()875 PackageInstallParamsWidget::~PackageInstallParamsWidget () {
876 	RK_TRACE (DIALOGS);
877 }
878 
installSuggestedPackages()879 bool PackageInstallParamsWidget::installSuggestedPackages () {
880 	RK_TRACE (DIALOGS);
881 
882 	return suggested_packages->isChecked ();
883 }
884 
installLocation()885 QString PackageInstallParamsWidget::installLocation () {
886 	RK_TRACE (DIALOGS);
887 
888 	return libloc_selector->currentText ();
889 }
890 
liblocsChanged(const QStringList & newlist)891 void PackageInstallParamsWidget::liblocsChanged (const QStringList &newlist) {
892 	RK_TRACE (DIALOGS);
893 
894 	libloc_selector->clear ();
895 	libloc_selector->insertItems (0, RKSettingsModuleRPackages::addUserLibLocTo (newlist));
896 }
897 
898 /////////// RKRPackageInstallationStatus /////////////////
899 
RKRPackageInstallationStatus(QObject * parent)900 RKRPackageInstallationStatus::RKRPackageInstallationStatus (QObject* parent) : QAbstractItemModel (parent) {
901 	RK_TRACE (DIALOGS);
902 	_initialized = false;
903 }
904 
~RKRPackageInstallationStatus()905 RKRPackageInstallationStatus::~RKRPackageInstallationStatus () {
906 	RK_TRACE (DIALOGS);
907 }
908 
markAllUpdatesForInstallation()909 QModelIndex RKRPackageInstallationStatus::markAllUpdatesForInstallation () {
910 	RK_TRACE (DIALOGS);
911 
912 	// inefficient, but so what...
913 	for (int i = updateable_packages_in_installed.count () - 1; i >= 0; --i) {
914 		markPackageForInstallation (installed_packages[updateable_packages_in_installed[i]]);
915 	}
916 	return index (UpdateablePackages, 0, QModelIndex ());
917 }
918 
markPackageForInstallation(const QString & package_name)919 QModelIndex RKRPackageInstallationStatus::markPackageForInstallation (const QString& package_name) {
920 	RK_TRACE (DIALOGS);
921 
922 	// is the package available at all?
923 	QModelIndex pindex;
924 	int row = available_packages.indexOf (package_name);
925 	if (row < 0) return pindex;
926 
927 	// find out, whether it is a new or and updateable package
928 	QModelIndex parent;
929 	int urow = updateable_packages_in_available.indexOf (row);
930 	if (urow >= 0) {
931 		parent = index (UpdateablePackages, 0);
932 		row = urow;
933 	} else {
934 		row = new_packages_in_available.indexOf (row);
935 		parent = index (NewPackages, 0);
936 	}
937 	if (row < 0) {
938 		RK_ASSERT (false);
939 		return pindex;
940 	}
941 
942 	// mark for installation
943 	pindex = index (row, InstallationStatus, parent);
944 	setData (pindex, QVariant (Qt::Checked), Qt::CheckStateRole);
945 	return pindex;
946 }
947 
initialize(RCommandChain * chain)948 void RKRPackageInstallationStatus::initialize (RCommandChain *chain) {
949 	RK_TRACE (DIALOGS);
950 
951 	_initialized = true;	// will be re-set to false, should the command fail / be cancelled
952 
953 	RCommand *command = new RCommand (".rk.get.package.installation.state ()", RCommand::App | RCommand::GetStructuredData);
954 	connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKRPackageInstallationStatus::statusCommandFinished);
955 	RKProgressControl *control = new RKProgressControl (this, i18n ("<p>Please stand by while searching for installed and available packages.</p><p><strong>Note:</strong> This requires a working internet connection, and may take some time, esp. if one or more repositories are temporarily unavailable.</p>"), i18n ("Searching for packages"), RKProgressControl::CancellableProgress | RKProgressControl::AutoCancelCommands);
956 	control->addRCommand (command, true);
957 	RKGlobals::rInterface ()->issueCommand (command, chain);
958 	control->doModal (true);
959 }
960 
statusCommandFinished(RCommand * command)961 void RKRPackageInstallationStatus::statusCommandFinished (RCommand *command) {
962 	RK_TRACE (DIALOGS);
963 
964 	if (!command->succeeded ()) {
965 		RK_ASSERT (false);
966 		_initialized = false;
967 		return;
968 	}
969 	RK_ASSERT (command->getDataType () == RCommand::StructureVector);
970 	RK_ASSERT (command->getDataLength () == 5);
971 
972 	RData::RDataStorage top = command->structureVector ();
973 	RData::RDataStorage available = top[0]->structureVector ();
974 	available_packages = available[0]->stringVector ();
975 	available_titles = available[1]->stringVector ();
976 	available_versions = available[2]->stringVector ();
977 	available_repos = available[3]->stringVector ();
978 	enhance_rk_in_available = available[4]->intVector ();
979 
980 	RData::RDataStorage installed = top[1]->structureVector ();
981 	installed_packages = installed[0]->stringVector ();
982 	installed_titles = installed[1]->stringVector ();
983 	installed_versions = installed[2]->stringVector ();
984 	installed_libpaths = installed[3]->stringVector ();
985 	enhance_rk_in_installed = installed[4]->intVector ();
986 	installed_has_update.fill (false, installed_packages.count ());
987 
988 	new_packages_in_available = top[2]->intVector ();
989 	RData::RDataStorage updateable = top[3]->structureVector ();
990 	updateable_packages_in_installed = updateable[0]->intVector ();
991 	updateable_packages_in_available = updateable[1]->intVector ();
992 
993 	for (int i = updateable_packages_in_installed.count () - 1; i >= 0; --i) {
994 		installed_has_update[updateable_packages_in_installed[i]] = true;
995 	}
996 
997 	current_repos = top[4]->stringVector ();
998 
999 	clearStatus ();
1000 }
1001 
clearStatus()1002 void RKRPackageInstallationStatus::clearStatus () {
1003 	RK_TRACE (DIALOGS);
1004 
1005 	beginResetModel ();
1006 	available_status.fill (NoAction, available_packages.count ());
1007 	installed_status.fill (NoAction, installed_packages.count ());
1008 	endResetModel ();
1009 }
1010 
headerData(int section,Qt::Orientation orientation,int role) const1011 QVariant RKRPackageInstallationStatus::headerData (int section, Qt::Orientation orientation, int role) const {
1012 	if (orientation != Qt::Horizontal) return QVariant ();
1013 
1014 	if ((role == Qt::DecorationRole) && (section == EnhancesRKWard)) return RKStandardIcons::getIcon (RKStandardIcons::RKWardIcon);
1015 
1016 	if (role == Qt::DisplayRole) {
1017 		if (section == InstallationStatus) return QVariant (i18n ("Status"));
1018 		if (section == PackageName) return QVariant (i18n ("Name"));
1019 		if (section == PackageTitle) return QVariant (i18n ("Title"));
1020 		if (section == Version) return QVariant (i18n ("Version"));
1021 		if (section == Location) return QVariant (i18n ("Location"));
1022 	}
1023 	if ((role == Qt::ToolTipRole) || (role == Qt::WhatsThisRole)) {
1024 		if (section == EnhancesRKWard) return QVariant (i18n ("<p>Packages marked with an RKWard icon in this column provide enhancements to RKWard, typically in the form of additional graphical dialogs.</p>"));
1025 		if (section == InstallationStatus) return QVariant (i18n ("<p>You can select packages for installation / removal by checking / unchecking the corresponding boxes in this column.</p>"));
1026 		if (section == PackageName) return QVariant (i18n ("<p>The name of the package.</p>"));
1027 		if (section == PackageTitle) return QVariant (i18n ("<p>A descriptive title for the package. Currently this is not available for packages in non-local repositories.</p>"));
1028 		if (section == Version) return QVariant (i18n ("<p>Installed and / or available version of the package</p>"));
1029 		if (section == Location) return QVariant (i18n ("<p>Location where the package is installed / available</p>"));
1030 	}
1031 	return QVariant ();
1032 }
1033 
rowCount(const QModelIndex & parent) const1034 int RKRPackageInstallationStatus::rowCount (const QModelIndex &parent) const {
1035 	if (!parent.isValid ()) return TOPLEVELITEM_COUNT;	// top level
1036 	if (parent.parent ().isValid ()) return 0;			// model has exactly two levels
1037 
1038 	int row = parent.row ();
1039 	if (row == UpdateablePackages) return updateable_packages_in_available.count ();
1040 	if (row == NewPackages) return new_packages_in_available.count ();
1041 	if (row == InstalledPackages) return installed_packages.count ();
1042 
1043 	RK_ASSERT (false);
1044 	return 0;
1045 }
1046 
data(const QModelIndex & index,int role) const1047 QVariant RKRPackageInstallationStatus::data (const QModelIndex &index, int role) const {
1048 	if (!index.isValid ()) return QVariant ();
1049 	if (!index.parent ().isValid ()) {	// top level item
1050 		int row = index.row ();
1051 		if (row == UpdateablePackages) {
1052 			if (role == Qt::DisplayRole) return QVariant (i18n ("Updateable Packages"));
1053 			if (role == Qt::ToolTipRole) return QVariant (i18n ("Packages for which an update is available. This may include packages which were merely built against a newer version of R."));
1054 		} else if (row == NewPackages) {
1055 			if (role == Qt::DisplayRole) return QVariant (i18n ("New Packages"));
1056 			if (role == Qt::ToolTipRole) return QVariant (i18n ("Packages which are available for installation, but which are not currently installed."));
1057 		} else if (row == InstalledPackages) {
1058 			if (role == Qt::DisplayRole) return QVariant (i18n ("Installed Packages"));
1059 			if (role == Qt::ToolTipRole) return QVariant (i18n ("Packages which are installed locally. Note that updates may be available for these packages."));
1060 		}
1061 	} else if (!index.parent ().parent ().isValid ()) {		// model has exactly two levels
1062 		const int col = index.column ();
1063 		const int prow = index.parent ().row ();
1064 		int arow, irow;		// row numbers in the lists of available_packages / installed_packages
1065 		arow = irow = 0;	// Suppress bogus GCC warning (doesn't seem to understand that irow is not needed for NewPackages, and arow is not needed for InstalledPackages)
1066 		if (prow == UpdateablePackages) {
1067 			arow = updateable_packages_in_available.value (index.row ());
1068 			irow = updateable_packages_in_installed.value (index.row ());
1069 		} else if (prow == NewPackages) {
1070 			arow = new_packages_in_available.value (index.row ());
1071 		} else {
1072 			RK_ASSERT (prow == InstalledPackages);
1073 			irow = index.row ();
1074 		}
1075 
1076 		if (col == InstallationStatus) {
1077 			PackageStatusChange stat;
1078 			if (prow == InstalledPackages) stat = installed_status.value (irow, NoAction);
1079 			else stat = available_status.value (arow, NoAction);
1080 			if (stat == NoAction) {
1081 				if (role == Qt::CheckStateRole) {
1082 					if (prow == InstalledPackages) return Qt::PartiallyChecked;
1083 					return Qt::Unchecked;
1084 				}
1085 			} else if (stat == Install) {
1086 				if (role == Qt::CheckStateRole) return Qt::Checked;
1087 				if (role == Qt::DisplayRole) return QVariant (i18n ("Install"));
1088 			} else {
1089 				if (role == Qt::CheckStateRole) return Qt::Unchecked;
1090 				if (role == Qt::DisplayRole) return QVariant (i18n ("Remove"));
1091 			}
1092 		} else if (col == EnhancesRKWard) {
1093 			if (role == Qt::DisplayRole) return QVariant (QString (" "));	// must have a placeholder, here, or Qt will collapse the column
1094 			if ((role == Qt::DecorationRole) || (role == Qt::UserRole)) {
1095 				bool enhance_rk;
1096 				if (prow == InstalledPackages) enhance_rk = enhance_rk_in_installed.value (irow);
1097 				else enhance_rk = enhance_rk_in_available.value (arow);
1098 				if (role == Qt::UserRole) return QVariant (enhance_rk);
1099 				if (enhance_rk) return RKStandardIcons::getIcon (RKStandardIcons::RKWardIcon);
1100 			}
1101 		} else if (col == PackageName) {
1102 			if (role == Qt::DisplayRole) {
1103 				if (prow == InstalledPackages) return installed_packages.value (irow);
1104 				else return available_packages.value (arow);
1105 			}
1106 		} else if (col == PackageTitle) {
1107 			if (role == Qt::DisplayRole) {
1108 				if (prow == InstalledPackages) return installed_titles.value (irow);
1109 				else return available_titles.value (arow);
1110 			}
1111 		} else if (col == Version) {
1112 			if (role == Qt::DisplayRole) {
1113 				if (prow == InstalledPackages) return installed_versions.value (irow);
1114 				else if (prow == NewPackages) return available_versions.value (arow);
1115 				else return QVariant (installed_versions.value (irow) + " -> " + available_versions.value (arow));
1116 			}
1117 		} else if (col == Location) {
1118 			if (role == Qt::DisplayRole) {
1119 				if (prow == InstalledPackages) return installed_libpaths.value (irow);
1120 				else if (prow == NewPackages) return available_repos.value (arow);
1121 				else {
1122 					RK_ASSERT (prow == UpdateablePackages);
1123 					return QVariant (installed_libpaths.value (irow) + " -> " + available_repos.value (arow));
1124 				}
1125 			}
1126 		}
1127 	}
1128 	return QVariant ();
1129 }
1130 
flags(const QModelIndex & index) const1131 Qt::ItemFlags RKRPackageInstallationStatus::flags (const QModelIndex &index) const {
1132 	qint64 pos = index.internalId ();
1133 	Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
1134 	if (pos >= 0) flags |= Qt::ItemIsUserCheckable;
1135 	if (pos == InstalledPackages) flags |= Qt::ItemIsTristate;
1136 	return flags;
1137 }
1138 
index(int row,int column,const QModelIndex & parent) const1139 QModelIndex RKRPackageInstallationStatus::index (int row, int column, const QModelIndex &parent) const {
1140 	if (!parent.isValid ()) return createIndex (row, column, (quintptr) std::numeric_limits<quintptr>::max);	// toplevel items
1141 	return createIndex (row, column, parent.row ());				// parent.row () identifies, which toplevel item is the parent.
1142 }
1143 
parent(const QModelIndex & index) const1144 QModelIndex RKRPackageInstallationStatus::parent (const QModelIndex& index) const {
1145 	if (index.internalId () == (quintptr) std::numeric_limits<quintptr>::max) return QModelIndex ();
1146 	return (RKRPackageInstallationStatus::index (index.internalId (), 0, QModelIndex ()));
1147 }
1148 
setData(const QModelIndex & index,const QVariant & value,int role)1149 bool RKRPackageInstallationStatus::setData (const QModelIndex &index, const QVariant &value, int role) {
1150 	RK_TRACE (DIALOGS);
1151 
1152 	if (role != Qt::CheckStateRole) return false;
1153 	if (!index.isValid ()) return false;
1154 	if (!index.parent ().isValid ()) return false;
1155 	QModelIndex bindex;
1156 
1157 	PackageStatusChange stat = NoAction;
1158 	int irow = -1;
1159 	int arow = -1;
1160 	if (value.toInt () == Qt::Checked) stat = Install;
1161 	else if (value.toInt () == Qt::Unchecked) stat = Remove;
1162 
1163 	if (index.internalId () == InstalledPackages) {
1164 		irow = index.row ();
1165 		if ((stat == Install) && (installed_status[irow] == Remove)) stat = NoAction;
1166 		if (installed_has_update.value (irow, false)) {
1167 			// NOTE: installed, and updatable packages are coupled
1168 			int urow = updateable_packages_in_installed.indexOf (irow);
1169 			RK_ASSERT (urow >= 0);
1170 			arow = updateable_packages_in_available.value (urow);
1171 			bindex = RKRPackageInstallationStatus::index (urow, InstallationStatus, RKRPackageInstallationStatus::index (UpdateablePackages, 0));
1172 		}
1173 	} else {
1174 		if (stat == Remove) stat = NoAction;
1175 		if (index.internalId () == UpdateablePackages) {
1176 			// NOTE: installed, and updatable packages are coupled
1177 			irow = updateable_packages_in_installed.value (index.row ());
1178 			arow = updateable_packages_in_available.value (index.row ());
1179 			bindex = RKRPackageInstallationStatus::index (irow, InstallationStatus, RKRPackageInstallationStatus::index (InstalledPackages, 0));
1180 		} else {
1181 			arow = new_packages_in_available.value (index.row ());
1182 		}
1183 	}
1184 
1185 	if (irow >= 0) installed_status[irow] = stat;
1186 	if (arow >= 0) available_status[arow] = stat;
1187 
1188 	dataChanged (index, index);
1189 	if (bindex.isValid ()) dataChanged (bindex, bindex);
1190 
1191 	return true;
1192 }
1193 
packagesToInstall() const1194 QStringList RKRPackageInstallationStatus::packagesToInstall () const {
1195 	RK_TRACE (DIALOGS);
1196 
1197 	QStringList ret;
1198 	for (int i = installed_status.count () - 1; i >= 0; --i) {
1199 		if (installed_status[i] == Install) ret.append (installed_packages[i]);
1200 	}
1201 	for (int i = available_status.count () - 1; i >= 0; --i) {
1202 		if (available_status[i] == Install) {
1203 			QString package = available_packages[i];
1204 			if (!ret.contains (package)) ret.append (package);
1205 		}
1206 	}
1207 	return ret;
1208 }
1209 
packagesToRemove(QStringList * packages,QStringList * liblocs)1210 bool RKRPackageInstallationStatus::packagesToRemove (QStringList *packages, QStringList *liblocs) {
1211 	RK_TRACE (DIALOGS);
1212 
1213 	bool anyfound = false;
1214 	for (int i = installed_status.count () - 1; i >= 0; --i) {
1215 		if (installed_status[i] == Remove) {
1216 			packages->append (installed_packages[i]);
1217 			liblocs->append (installed_libpaths[i]);
1218 			anyfound = true;
1219 		}
1220 	}
1221 	return anyfound;
1222 }
1223 
1224 
RKRPackageInstallationStatusSortFilterModel(QObject * parent)1225 RKRPackageInstallationStatusSortFilterModel::RKRPackageInstallationStatusSortFilterModel (QObject* parent) : QSortFilterProxyModel (parent) {
1226 	RK_TRACE (DIALOGS);
1227 	rkward_only = false;
1228 }
1229 
~RKRPackageInstallationStatusSortFilterModel()1230 RKRPackageInstallationStatusSortFilterModel::~RKRPackageInstallationStatusSortFilterModel () {
1231 	RK_TRACE (DIALOGS);
1232 }
1233 
lessThan(const QModelIndex & left,const QModelIndex & right) const1234 bool RKRPackageInstallationStatusSortFilterModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
1235 	if (!left.parent ().isValid ()) {		// Disable sorting for the top level items
1236 		return (left.row () > right.row ());
1237 	}
1238 	if (left.column () == RKRPackageInstallationStatus::EnhancesRKWard) {
1239 		return (!left.data (Qt::UserRole).toBool ());
1240 	}
1241 	return QSortFilterProxyModel::lessThan (left, right);
1242 }
1243 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const1244 bool RKRPackageInstallationStatusSortFilterModel::filterAcceptsRow (int source_row, const QModelIndex &source_parent) const {
1245 	if (!source_parent.isValid ()) return true;		// Never filter the top level item
1246 
1247 	if (rkward_only) {
1248 		bool enhance_rk = source_parent.child (source_row, RKRPackageInstallationStatus::EnhancesRKWard).data (Qt::UserRole).toBool ();
1249 		if (!enhance_rk) return false;
1250 	}
1251 // filter on Name and Title
1252 	QString name = source_parent.child (source_row, RKRPackageInstallationStatus::PackageName).data ().toString ();
1253 	if (name.contains (filterRegExp ())) return true;
1254 	QString title = source_parent.child (source_row, RKRPackageInstallationStatus::PackageTitle).data ().toString ();
1255 	return (title.contains (filterRegExp ()));
1256 }
1257 
setRKWardOnly(bool only)1258 void RKRPackageInstallationStatusSortFilterModel::setRKWardOnly (bool only) {
1259 	RK_TRACE (DIALOGS);
1260 
1261 	bool old_only = rkward_only;
1262 	rkward_only = only;
1263 	if (rkward_only != old_only) invalidate ();
1264 }
1265 
1266 /////////////////////////
1267 #include "../misc/multistringselector.h"
RKPluginMapSelectionWidget(RKLoadLibsDialog * dialog)1268 RKPluginMapSelectionWidget::RKPluginMapSelectionWidget (RKLoadLibsDialog* dialog) : QWidget (dialog) {
1269 	RK_TRACE (DIALOGS);
1270 	model = 0;
1271 	changes_pending = false;
1272 
1273 	QVBoxLayout *vbox = new QVBoxLayout (this);
1274 	vbox->setContentsMargins (0, 0, 0, 0);
1275 	vbox->addWidget (new QLabel (i18n ("Installed plugin groups (.pluginmap files)"), this));
1276 	selector = new RKMultiStringSelectorV2 (QString (), this);
1277 	selector->setAlwaysAddAtBottom (true);
1278 	vbox->addWidget (selector);
1279 }
1280 
~RKPluginMapSelectionWidget()1281 RKPluginMapSelectionWidget::~RKPluginMapSelectionWidget () {
1282 	RK_TRACE (DIALOGS);
1283 }
1284 
activated()1285 void RKPluginMapSelectionWidget::activated () {
1286 	RK_TRACE (DIALOGS);
1287 
1288 	if (!model) {
1289 		model = new RKSettingsModulePluginsModel (this);
1290 		model->init (RKSettingsModulePlugins::knownPluginmaps ());
1291 		selector->setModel (model, 1);
1292 		connect (selector, &RKMultiStringSelectorV2::insertNewStrings, model, &RKSettingsModulePluginsModel::insertNewStrings);
1293 		connect (selector, &RKMultiStringSelectorV2::swapRows, model, &RKSettingsModulePluginsModel::swapRows);
1294 		connect (selector, &RKMultiStringSelectorV2::listChanged, this, &RKPluginMapSelectionWidget::changed);
1295 	}
1296 }
1297 
apply()1298 void RKPluginMapSelectionWidget::apply () {
1299 	RK_TRACE (DIALOGS);
1300 
1301 	if (!changes_pending) return;
1302 	RK_ASSERT (model);
1303 	RKSettingsModulePlugins::PluginMapList new_list = RKSettingsModulePlugins::setPluginMaps (model->pluginMaps ());
1304 	selector->setModel (0); // we don't want any extra change notification for this
1305 	model->init (new_list);
1306 	selector->setModel (model, 1);
1307 	changes_pending = false;
1308 }
1309 
cancel()1310 void RKPluginMapSelectionWidget::cancel () {
1311 	RK_TRACE (DIALOGS);
1312 	deleteLater ();
1313 }
1314 
ok()1315 void RKPluginMapSelectionWidget::ok () {
1316 	RK_TRACE (DIALOGS);
1317 
1318 	if (!changes_pending) return;
1319 	RK_ASSERT (model);
1320 	RKSettingsModulePlugins::setPluginMaps (model->pluginMaps ());
1321 	deleteLater ();
1322 }
1323 
1324