1 /***************************************************************************
2                           rkhelpsearchwindow  -  description
3                              -------------------
4     begin                : Fri Feb 25 2005
5     copyright            : (C) 2005, 2006, 2007, 2009, 2010, 2011 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 
18 #include "rkhelpsearchwindow.h"
19 
20 #include <KLocalizedString>
21 #include <QUrl>
22 #include <kmessagebox.h>
23 
24 #include <qcheckbox.h>
25 #include <qcombobox.h>
26 #include <QTreeView>
27 #include <qlineedit.h>
28 #include <qlayout.h>
29 #include <qlabel.h>
30 #include <qpushbutton.h>
31 #include <QHBoxLayout>
32 #include <QFocusEvent>
33 #include <QVBoxLayout>
34 #include <QSortFilterProxyModel>
35 
36 #include "../rbackend/rkrinterface.h"
37 #include "../rbackend/rcommandreceiver.h"
38 #include "../rbackend/rksessionvars.h"
39 #include "../debug.h"
40 #include "../rkglobals.h"
41 #include "../rkward.h"
42 #include "../core/robject.h"
43 #include "../misc/rkcommonfunctions.h"
44 #include "../misc/rkdummypart.h"
45 #include "../misc/rkstandardicons.h"
46 
47 #define GET_HELP 1
48 #define HELP_SEARCH 2
49 
50 // result columns
51 #define COL_TYPE 0
52 #define COL_TOPIC 1
53 #define COL_TITLE 2
54 #define COL_PACKAGE 3
55 #define COL_COUNT 4
56 
57 RKHelpSearchWindow* RKHelpSearchWindow::main_help_search = 0;
58 
RKHelpSearchWindow(QWidget * parent,bool tool_window,const char * name)59 RKHelpSearchWindow::RKHelpSearchWindow (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, SearchHelpWindow, tool_window, name) {
60 	RK_TRACE (APP);
61 	setPart (new RKDummyPart (0, this));
62 	initializeActivationSignals ();
63 	setFocusPolicy (Qt::ClickFocus);
64 
65 	QVBoxLayout* main_layout = new QVBoxLayout (this);
66 	QHBoxLayout* selection_layout = new QHBoxLayout ();
67 	main_layout->addLayout (selection_layout);
68 	selection_layout->setContentsMargins (0, 0, 0, 0);
69 
70 	QVBoxLayout* labels_layout = new QVBoxLayout ();
71 	selection_layout->addLayout (labels_layout);
72 	labels_layout->setContentsMargins (0, 0, 0, 0);
73 	QLabel *label = new QLabel (i18n ("Find:"), this);
74 	label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum);
75 	labels_layout->addWidget (label);
76 	label = new QLabel (i18n ("Fields:"), this);
77 	label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum);
78 	labels_layout->addWidget (label);
79 
80 	QVBoxLayout* main_settings_layout = new QVBoxLayout ();
81 	selection_layout->addLayout (main_settings_layout);
82 	main_settings_layout->setContentsMargins (0, 0, 0, 0);
83 	field = new QComboBox (this);
84 	field->setEditable (true);
85 	field->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
86 	connect (field->lineEdit (), &QLineEdit::returnPressed, this, &RKHelpSearchWindow::slotFindButtonClicked);
87 	main_settings_layout->addWidget (field);
88 
89 	QHBoxLayout* fields_packages_layout = new QHBoxLayout ();
90 	main_settings_layout->addLayout (fields_packages_layout);
91 	fields_packages_layout->setContentsMargins (0, 0, 0, 0);
92 	fieldsList = new QComboBox (this);
93 	fieldsList->setEditable (false);
94 	fieldsList->addItem (i18n ("All"), "c(\"alias\", \"concept\", \"title\",\"keyword\")");
95 	fieldsList->addItem (i18n ("All but keywords"), "c(\"alias\", \"concept\", \"title\")");
96 	fieldsList->addItem (i18n ("Keywords"), "c(\"keyword\")");
97 	fieldsList->addItem (i18n ("Title"), "c(\"title\")");
98 	fields_packages_layout->addWidget (fieldsList);
99 
100 	label = new QLabel (i18n ("Package:"), this);
101 	label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Minimum);
102 	fields_packages_layout->addWidget (label);
103 
104 	packagesList = new QComboBox (this);
105 	packagesList->setEditable (false);
106 	fields_packages_layout->addWidget (packagesList);
107 	connect (RKSessionVars::instance (), &RKSessionVars::installedPackagesChanged, this, &RKHelpSearchWindow::updateInstalledPackages);
108 	updateInstalledPackages ();
109 
110 	QVBoxLayout* checkboxes_layout = new QVBoxLayout ();
111 	selection_layout->addLayout (checkboxes_layout);
112 	checkboxes_layout->setContentsMargins (0, 0, 0, 0);
113 	caseSensitiveCheckBox = new QCheckBox (i18n ("Case sensitive"), this);
114 	checkboxes_layout->addWidget (caseSensitiveCheckBox);
115 	fuzzyCheckBox = new QCheckBox (i18n ("Fuzzy matching"), this);
116 	fuzzyCheckBox->setChecked (true);
117 	checkboxes_layout->addWidget (fuzzyCheckBox);
118 
119 	findButton = new QPushButton (i18n ("Find"), this);
120 	findButton->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
121 	connect (findButton, &QPushButton::clicked, this, &RKHelpSearchWindow::slotFindButtonClicked);
122 	selection_layout->addWidget (findButton);
123 
124 	results = new RKHelpSearchResultsModel (this);
125 	proxy_model = new QSortFilterProxyModel (this);
126 	proxy_model->setSourceModel (results);
127 	results_view = new QTreeView (this);
128 	results_view->setRootIsDecorated (false);
129 	results_view->setModel (proxy_model);
130 	results_view->setSortingEnabled (true);
131 	connect (results_view, &QTreeView::doubleClicked, this, &RKHelpSearchWindow::resultDoubleClicked);
132 	main_layout->addWidget (results_view);
133 
134 	setCaption (i18n ("Help search"));
135 }
136 
~RKHelpSearchWindow()137 RKHelpSearchWindow::~RKHelpSearchWindow () {
138 	RK_TRACE (APP);
139 }
140 
focusInEvent(QFocusEvent * e)141 void RKHelpSearchWindow::focusInEvent (QFocusEvent *e) {
142 	RK_TRACE (APP);
143 
144 	RKMDIWindow::focusInEvent (e);
145 	if (e->reason () != Qt::MouseFocusReason) {
146 		field->setFocus ();
147 	}
148 }
149 
getContextHelp(const QString & context_line,int cursor_pos)150 void RKHelpSearchWindow::getContextHelp (const QString &context_line, int cursor_pos) {
151 	RK_TRACE (APP);
152 	QString result = RKCommonFunctions::getCurrentSymbol (context_line, cursor_pos);
153 	if (result.isEmpty ()) return;
154 
155 	getFunctionHelp (result);
156 }
157 
getFunctionHelp(const QString & function_name,const QString & package,const QString & type)158 void RKHelpSearchWindow::getFunctionHelp (const QString &function_name, const QString &package, const QString &type) {
159 	RK_TRACE (APP);
160 
161 	// we use .rk.getHelp() instead of plain help() to receive an error, if no help could be found
162 	QString command = ".rk.getHelp(";
163 	if (type == "demo") command = "rk.demo(";
164 	else if (type == "vignette") command = "print (vignette(";
165 
166 	command.append (RObject::rQuote (function_name));
167 	if (!package.isEmpty ()) command.append (", package=" + RObject::rQuote (package));
168 	command.append (")");
169 	if (type == "vignette") command.append (")");
170 
171 	RKGlobals::rInterface ()->issueCommand (command, RCommand::App | RCommand::GetStringVector, i18n ("Find HTML help for %1", function_name), this, GET_HELP);
172 }
173 
slotFindButtonClicked()174 void RKHelpSearchWindow::slotFindButtonClicked () {
175 	RK_TRACE (APP);
176 
177 	if (field->currentText ().isEmpty ()) {
178 		return;
179 	}
180 
181 	QString agrep = "FALSE";
182 	if (fuzzyCheckBox->isChecked ()) {
183 		agrep="NULL";
184 	}
185 
186 	QString ignoreCase = "TRUE";
187 	if(caseSensitiveCheckBox->isChecked ()) {
188 		ignoreCase="FALSE";
189 	}
190 
191 	QString package = ", package=";
192 	if (packagesList->currentIndex () == 0) {
193 		package.append ("NULL");	// all installed packages; actually we could also use package.clear(), here.
194 	} else if (packagesList->currentIndex () == 1) {
195 		package.append (".packages()");	// all loaded packages
196 	} else {
197 		package.append ("\"" + packagesList->currentText () + "\"");
198 	}
199 
200 	QString fields = fieldsList->itemData (fieldsList->currentIndex ()).toString ();
201 
202 	QString s = ".rk.get.search.results (" + RObject::rQuote (field->currentText ()) + ", agrep=" + agrep + ", ignore.case=" + ignoreCase + package + ", fields=" + fields + ')';
203 
204 	RKGlobals::rInterface ()->issueCommand (s, RCommand::App | RCommand::Sync | RCommand::GetStringVector, QString (), this, HELP_SEARCH, 0);
205 	setEnabled (false);
206 	field->addItem (field->currentText ());
207 }
208 
resultDoubleClicked(const QModelIndex & index)209 void RKHelpSearchWindow::resultDoubleClicked (const QModelIndex& index) {
210 	RK_TRACE (APP);
211 
212 	if (!index.isValid ()) return;
213 
214 	int row = proxy_model->mapToSource (index).row ();
215 	QString topic = results->data (results->index (row, COL_TOPIC)).toString ();
216 	if (topic.isEmpty ()) return;
217 	QString package = results->data (results->index (row, COL_PACKAGE)).toString ();
218 	QString type = results->resultsType (row);
219 
220 	getFunctionHelp (topic, package, type);
221 }
222 
updateInstalledPackages()223 void RKHelpSearchWindow::updateInstalledPackages () {
224 	RK_TRACE (APP);
225 
226 	QString old_value = packagesList->currentText ();
227 
228 	packagesList->clear ();
229 	packagesList->addItem (i18n("All installed packages"));
230 	packagesList->addItem (i18n("All loaded packages"));
231 	packagesList->insertSeparator (2);
232 	packagesList->addItems (RKSessionVars::instance ()->installedPackages ());
233 
234 	int index = 0;
235 	if (!old_value.isEmpty ()) index = packagesList->findText (old_value);
236 	if (index < 0) index = 0;
237 	packagesList->setCurrentIndex (index);
238 }
239 
rCommandDone(RCommand * command)240 void RKHelpSearchWindow::rCommandDone (RCommand *command) {
241 	RK_TRACE (APP);
242 	if (command->getFlags () == HELP_SEARCH) {
243 		QStringList res;
244 		if (command->failed ()) {
245 			RK_ASSERT (false);
246 		} else {
247 			RK_ASSERT (command->getDataType () == RData::StringVector);
248 			res = command->stringVector ();
249 		}
250 		results->setResults (res);
251 
252 		for (int i = 0; i < COL_COUNT; ++i) results_view->resizeColumnToContents (i);
253 		setEnabled(true);
254 	} else if (command->getFlags () == GET_HELP) {
255 		if (command->failed ()) {
256 			KMessageBox::sorry (this, i18n ("No help found on '%1'. Maybe the corresponding package is not installed/loaded, or maybe you mistyped the command. Try using Help->Search R Help for more options.", command->command ().section ('\"', 1, 1)), i18n ("No help found"));
257 		}
258 	} else {
259 		RK_ASSERT (false);
260 	}
261 }
262 
263 //////////////// RKHelpResultsModel ////////////////
264 
RKHelpSearchResultsModel(QObject * parent)265 RKHelpSearchResultsModel::RKHelpSearchResultsModel (QObject *parent) : QAbstractTableModel (parent) {
266 	RK_TRACE (APP);
267 
268 	result_count = 0;
269 }
270 
~RKHelpSearchResultsModel()271 RKHelpSearchResultsModel::~RKHelpSearchResultsModel () {
272 	RK_TRACE (APP);
273 }
274 
setResults(const QStringList & results)275 void RKHelpSearchResultsModel::setResults (const QStringList &results) {
276 	RK_TRACE (APP);
277 
278 	RK_ASSERT ((results.size () % 4) == 0);
279 	beginResetModel ();
280 
281 	result_count = results.size () / 4;
282 	topics = results.mid (0, result_count);
283 	titles = results.mid (result_count, result_count);
284 	packages = results.mid (result_count*2, result_count);
285 	types = results.mid (result_count*3, result_count);
286 
287 	endResetModel ();
288 }
289 
rowCount(const QModelIndex & parent) const290 int RKHelpSearchResultsModel::rowCount (const QModelIndex& parent) const {
291 	// don't trace
292 	if (parent.isValid ()) return 0;
293 	return result_count;
294 }
295 
columnCount(const QModelIndex & parent) const296 int RKHelpSearchResultsModel::columnCount (const QModelIndex& parent) const {
297 	// don't trace
298 	if (parent.isValid ()) return 0;
299 	return COL_COUNT;
300 }
301 
resultsType(int row)302 QString RKHelpSearchResultsModel::resultsType (int row) {
303 	RK_TRACE (APP);
304 
305 	if (row >= result_count) {
306 		RK_ASSERT (false);
307 		return QString ();
308 	}
309 	return types[row];
310 }
311 
data(const QModelIndex & index,int role) const312 QVariant RKHelpSearchResultsModel::data (const QModelIndex& index, int role) const {
313 	// don't trace
314 
315 	// easier typing
316 	int row = index.row ();
317 	int col = index.column ();
318 	if (result_count && (row < result_count)) {
319 		if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
320 			if (col == COL_TOPIC) return topics[row];
321 			if (col == COL_TITLE) return titles[row];
322 			if (col == COL_PACKAGE) return packages[row];
323 			if (col == COL_TYPE) return types[row];
324 		} else if ((col == 0) && (role == Qt::DecorationRole)) {
325 			if (types[row] == "help") return RKStandardIcons::getIcon (RKStandardIcons::WindowHelp);
326 			if (types[row] == "demo") return RKStandardIcons::getIcon (RKStandardIcons::WindowCommandEditor);
327 			if (types[row] == "vignette") return RKStandardIcons::getIcon (RKStandardIcons::DocumentPDF);
328 		}
329 	} else {
330 		RK_ASSERT (false);
331 	}
332 
333 	return QVariant ();
334 }
335 
headerData(int section,Qt::Orientation orientation,int role) const336 QVariant RKHelpSearchResultsModel::headerData (int section, Qt::Orientation orientation, int role) const {
337 	// don't trace
338 
339 	if (orientation == Qt::Horizontal) {
340 		if (role == Qt::DisplayRole) {
341 			if (section == COL_TOPIC) return (i18n ("Topic"));
342 			if (section == COL_TITLE) return (i18n ("Title"));
343 			if (section == COL_PACKAGE) return (i18n ("Package"));
344 			if (section == COL_TYPE) return (i18n ("Type"));
345 		}
346 	}
347 
348 	return QVariant ();
349 }
350 
351