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