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