GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
documentation.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2018-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <QAction>
31 #include <QApplication>
32 #include <QCompleter>
33 #include <QDesktopServices>
34 #include <QDir>
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QFontDatabase>
38 #include <QHelpContentWidget>
39 #include <QHelpIndexWidget>
40 #if defined (HAVE_NEW_QHELPINDEXWIDGET_API) \
41  || defined (HAVE_QHELPENGINE_DOCUMENTSFORIDENTIFIER)
42 # include <QHelpLink>
43 #endif
44 #include <QHelpSearchEngine>
45 #include <QHelpSearchQueryWidget>
46 #include <QHelpSearchResultWidget>
47 #include <QLabel>
48 #include <QLineEdit>
49 #include <QMessageBox>
50 #include <QTabWidget>
51 #include <QTimer>
52 #include <QVBoxLayout>
53 
54 #include "documentation.h"
56 #include "gui-preferences-global.h"
57 #include "gui-preferences-dc.h"
58 #include "gui-preferences-sc.h"
59 #include "octave-qobject.h"
60 #include "shortcut-manager.h"
61 
62 #include "defaults.h"
63 #include "file-ops.h"
64 #include "oct-env.h"
65 
67 
68 // The documentation splitter, which is the main widget
69 // of the doc dock widget
71 : QSplitter (Qt::Horizontal, p),
72  m_octave_qobj (oct_qobj), m_doc_widget (this),
73  m_tool_bar (new QToolBar (this)),
74  m_query_string (QString ()),
75  m_indexed (false),
76  m_current_ref_name (QString ()),
77  m_prev_pages_menu (new QMenu (this)),
78  m_next_pages_menu (new QMenu (this)),
79  m_prev_pages_count (0),
80  m_next_pages_count (0),
81  m_findnext_shortcut (new QShortcut (this)),
82  m_findprev_shortcut (new QShortcut (this))
83 {
84  // Get original collection
85  QString collection = getenv ("OCTAVE_QTHELP_COLLECTION");
86  if (collection.isEmpty ())
89  + "octave_interpreter.qhc");
90 
91  // Setup the help engine with the original collection, use a writable copy
92  // of the original collection and load the help data
93  m_help_engine = new QHelpEngine (collection, this);
94 
95  // Mark help as readonly to avoid error if collection file is stored in a
96  // readonly location
97  m_help_engine->setProperty ("_q_readonly",
98  QVariant::fromValue<bool> (true));
99 
101  m_collection
102  = QString::fromStdString (sys::tempnam (tmpdir.toStdString (),
103  "oct-qhelp-"));
104 
105  if (m_help_engine->copyCollectionFile (m_collection))
106  m_help_engine->setCollectionFile (m_collection);
107  else
108 #ifdef ENABLE_DOCS
109  // FIXME: Perhaps a better way to do this would be to keep a count
110  // in the GUI preferences file. After issuing this warning 3 times
111  // it would be disabled. The count would need to be reset when a new
112  // version of Octave is installed.
113  QMessageBox::warning (this, tr ("Octave Documentation"),
114  tr ("Could not copy help collection to temporary\n"
115  "file. Search capabilities may be affected.\n"
116  "%1").arg (m_help_engine->error ()));
117 #endif
118 
119  connect(m_help_engine->searchEngine (), SIGNAL(indexingFinished ()),
120  this, SLOT(load_index ()));
121  connect(m_help_engine, SIGNAL(setupFinished ()),
122  m_help_engine->searchEngine (), SLOT(reindexDocumentation ()));
123 
124  if (! m_help_engine->setupData())
125  {
126 #ifdef ENABLE_DOCS
127  QMessageBox::warning (this, tr ("Octave Documentation"),
128  tr ("Could not setup the data required for the\n"
129  "documentation viewer. Only help texts in\n"
130  "the Command Window will be available."));
131 #endif
132 
133  disconnect (m_help_engine, 0, 0, 0);
134 
135  delete m_help_engine;
136  m_help_engine = nullptr;
137  }
138 
139  // The browser
140  QWidget *browser_find = new QWidget (this);
141  m_doc_browser = new documentation_browser (m_help_engine, browser_find);
142  connect (m_doc_browser, &documentation_browser::cursorPositionChanged,
144 
145  // Tool bar
146  construct_tool_bar ();
147 
148  // Find bar
149  QWidget *find_footer = new QWidget (browser_find);
150  QLabel *find_label = new QLabel (tr ("Find:"), find_footer);
151  m_find_line_edit = new QLineEdit (find_footer);
152  connect (m_find_line_edit, &QLineEdit::returnPressed,
153  this, [=] () { find (); });
154  connect (m_find_line_edit, &QLineEdit::textEdited,
156  QToolButton *forward_button = new QToolButton (find_footer);
157  forward_button->setText (tr ("Search forward"));
158  forward_button->setToolTip (tr ("Search forward"));
159  resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
160  forward_button->setIcon (rmgr.icon ("go-down"));
161  connect (forward_button, &QToolButton::pressed,
162  this, [=] () { find (); });
163  QToolButton *backward_button = new QToolButton (find_footer);
164  backward_button->setText (tr ("Search backward"));
165  backward_button->setToolTip (tr ("Search backward"));
166  backward_button->setIcon (rmgr.icon ("go-up"));
167  connect (backward_button, &QToolButton::pressed,
169  QHBoxLayout *h_box_find_footer = new QHBoxLayout (find_footer);
170  h_box_find_footer->addWidget (find_label);
171  h_box_find_footer->addWidget (m_find_line_edit);
172  h_box_find_footer->addWidget (forward_button);
173  h_box_find_footer->addWidget (backward_button);
174  h_box_find_footer->setMargin (2);
175  find_footer->setLayout (h_box_find_footer);
176 
177  QVBoxLayout *v_box_browser_find = new QVBoxLayout (browser_find);
178  v_box_browser_find->addWidget (m_tool_bar);
179  v_box_browser_find->addWidget (m_doc_browser);
180  v_box_browser_find->addWidget (find_footer);
181  browser_find->setLayout (v_box_browser_find);
182 
183  notice_settings (rmgr.get_settings ());
184 
185  m_findnext_shortcut->setContext (Qt::WidgetWithChildrenShortcut);
186  connect (m_findnext_shortcut, &QShortcut::activated,
187  this, [=] () { find (); });
188  m_findprev_shortcut->setContext (Qt::WidgetWithChildrenShortcut);
189  connect (m_findprev_shortcut, &QShortcut::activated,
191 
192  find_footer->hide ();
193  m_search_anchor_position = 0;
194 
195  if (m_help_engine)
196  {
197 #if defined (HAVE_NEW_QHELPINDEXWIDGET_API)
198  // Starting in Qt 5.15, help engine uses filters instead of old API
199  m_help_engine->setUsesFilterEngine (true);
200 #endif
201  // Layout contents, index and search
202  QTabWidget *navi = new QTabWidget (this);
203  navi->setTabsClosable (false);
204  navi->setMovable (true);
205 
206  // Contents
207  QHelpContentWidget *content = m_help_engine->contentWidget ();
208  content->setObjectName ("documentation_tab_contents");
209  navi->addTab (content, tr ("Contents"));
210 
211  connect (m_help_engine->contentWidget (),
212  &QHelpContentWidget::linkActivated,
213  m_doc_browser, [=] (const QUrl& url) {
214  m_doc_browser->handle_index_clicked (url); });
215 
216  // Index
217  QHelpIndexWidget *index = m_help_engine->indexWidget ();
218 
219  m_filter = new QComboBox (this);
220  m_filter->setToolTip (tr ("Enter text to search the indices"));
221  m_filter->setEditable (true);
222  m_filter->setInsertPolicy (QComboBox::NoInsert);
223  m_filter->setMaxCount (10);
224  m_filter->setMaxVisibleItems (10);
225  m_filter->setSizeAdjustPolicy (QComboBox::AdjustToMinimumContentsLengthWithIcon);
226  QSizePolicy sizePol (QSizePolicy::Expanding, QSizePolicy::Preferred);
227  m_filter->setSizePolicy (sizePol);
228  m_filter->completer ()->setCaseSensitivity (Qt::CaseSensitive);
229  QLabel *filter_label = new QLabel (tr ("Search"));
230 
231  QWidget *filter_all = new QWidget (navi);
232  QHBoxLayout *h_box_index = new QHBoxLayout (filter_all);
233  h_box_index->addWidget (filter_label);
234  h_box_index->addWidget (m_filter);
235  h_box_index->setMargin (2);
236  filter_all->setLayout (h_box_index);
237 
238  QWidget *index_all = new QWidget (navi);
239  index_all->setObjectName ("documentation_tab_index");
240  QVBoxLayout *v_box_index = new QVBoxLayout (index_all);
241  v_box_index->addWidget (filter_all);
242  v_box_index->addWidget (index);
243  index_all->setLayout (v_box_index);
244 
245  navi->addTab (index_all, tr ("Function Index"));
246 
247 #if defined (HAVE_NEW_QHELPINDEXWIDGET_API)
248  connect (m_help_engine->indexWidget (),
249  &QHelpIndexWidget::documentActivated,
250  this, [=] (const QHelpLink &link) {
251  m_doc_browser->handle_index_clicked (link.url); });
252 #else
253  connect (m_help_engine->indexWidget (),
254  &QHelpIndexWidget::linkActivated,
256 #endif
257 
258  connect (m_filter, &QComboBox::editTextChanged,
260 
261  connect (m_filter->lineEdit (), &QLineEdit::editingFinished,
263 
264  // Bookmarks (own class)
265  m_bookmarks
266  = new documentation_bookmarks (this, m_doc_browser, m_octave_qobj, navi);
267  navi->addTab (m_bookmarks, tr ("Bookmarks"));
268 
269  connect (m_action_bookmark, &QAction::triggered,
270  m_bookmarks, [=] () { m_bookmarks->add_bookmark (); });
271 
272  // Search
273  QHelpSearchEngine *search_engine = m_help_engine->searchEngine ();
274  QHelpSearchQueryWidget *search = search_engine->queryWidget ();
275  QHelpSearchResultWidget *result = search_engine->resultWidget ();
276  QWidget *search_all = new QWidget (navi);
277  QVBoxLayout *v_box_search = new QVBoxLayout (search_all);
278  v_box_search->addWidget (search);
279  v_box_search->addWidget (result);
280  search_all->setLayout (v_box_search);
281  search_all->setObjectName ("documentation_tab_search");
282  navi->addTab (search_all, tr ("Search"));
283 
286 
287  connect (search_engine, &QHelpSearchEngine::searchingStarted,
289  connect (search_engine, &QHelpSearchEngine::searchingFinished,
291 
292  connect (search_engine->resultWidget (),
293  &QHelpSearchResultWidget::requestShowLink,
295 
296  // Fill the splitter
297  insertWidget (0, navi);
298  insertWidget (1, browser_find);
299  setStretchFactor (1, 1);
300  }
301 }
302 
304 {
305  // Cleanup temporary file and directory
306  QFile file (m_collection);
307  if (file.exists ())
308  {
309  QFileInfo finfo (file);
310  QString bname = finfo.fileName ();
311  QDir dir = finfo.absoluteDir ();
312  dir.setFilter (QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden);
313  QStringList namefilter;
314  namefilter.append ("*" + bname + "*");
315  for (const auto& fi : dir.entryInfoList (namefilter))
316  {
317  std::string file_name = fi.absoluteFilePath ().toStdString ();
318  sys::recursive_rmdir (file_name);
319  }
320 
321  file.remove();
322  }
323 }
324 
325 QAction * documentation::add_action (const QIcon& icon, const QString& text,
326  const char *member, QWidget *receiver,
327  QToolBar *tool_bar)
328 {
329  QAction *a;
330  QWidget *r = this;
331  if (receiver != nullptr)
332  r = receiver;
333 
334  a = new QAction (icon, text, this);
335 
336  if (member)
337  connect (a, SIGNAL (triggered ()), r, member);
338 
339  if (tool_bar)
340  tool_bar->addAction (a);
341 
342  m_doc_widget->addAction (a); // important for shortcut context
343  a->setShortcutContext (Qt::WidgetWithChildrenShortcut);
344 
345  return a;
346 }
347 
349 {
350  // Home, Previous, Next
353  = add_action (rmgr.icon ("go-home"), tr ("Go home"), SLOT (home (void)),
355 
357  = add_action (rmgr.icon ("go-previous"), tr ("Go back"),
358  SLOT (backward (void)), m_doc_browser, m_tool_bar);
359  m_action_go_prev->setEnabled (false);
360 
361  // popdown menu with prev pages files
362  QToolButton *popdown_button_prev_pages = new QToolButton ();
363  popdown_button_prev_pages->setToolTip (tr ("Previous pages"));
364  popdown_button_prev_pages->setMenu (m_prev_pages_menu);
365  popdown_button_prev_pages->setPopupMode (QToolButton::InstantPopup);
366  popdown_button_prev_pages->setToolButtonStyle (Qt::ToolButtonTextOnly);
367  popdown_button_prev_pages->setCheckable (false);
368  popdown_button_prev_pages->setArrowType(Qt::DownArrow);
369  m_tool_bar->addWidget (popdown_button_prev_pages);
370 
372  = add_action (rmgr.icon ("go-next"), tr ("Go forward"),
373  SLOT (forward (void)), m_doc_browser, m_tool_bar);
374  m_action_go_next->setEnabled (false);
375 
376  // popdown menu with prev pages files
377  QToolButton *popdown_button_next_pages = new QToolButton ();
378  popdown_button_next_pages->setToolTip (tr ("Next pages"));
379  popdown_button_next_pages->setMenu (m_next_pages_menu);
380  popdown_button_next_pages->setPopupMode (QToolButton::InstantPopup);
381  popdown_button_next_pages->setToolButtonStyle (Qt::ToolButtonTextOnly);
382  popdown_button_next_pages->setArrowType(Qt::DownArrow);
383  m_tool_bar->addWidget (popdown_button_next_pages);
384 
385  connect (m_doc_browser, &documentation_browser::backwardAvailable,
386  m_action_go_prev, &QAction::setEnabled);
387  connect (m_doc_browser, &documentation_browser::backwardAvailable,
388  popdown_button_prev_pages, &QToolButton::setEnabled);
389  connect (m_doc_browser, &documentation_browser::forwardAvailable,
390  m_action_go_next, &QAction::setEnabled);
391  connect (m_doc_browser, &documentation_browser::forwardAvailable,
392  popdown_button_next_pages, &QToolButton::setEnabled);
393  connect (m_doc_browser, &documentation_browser::historyChanged,
395 
396  // Init prev/next menus
397  for (int i = 0; i < max_history_entries; ++i)
398  {
399  m_prev_pages_actions[i] = new QAction (this);
400  m_prev_pages_actions[i]->setVisible (false);
401  m_next_pages_actions[i] = new QAction (this);
402  m_next_pages_actions[i]->setVisible (false);
403  m_prev_pages_menu->addAction (m_prev_pages_actions[i]);
404  m_next_pages_menu->addAction (m_next_pages_actions[i]);
405  }
406 
407  connect (m_prev_pages_menu, &QMenu::triggered,
409  connect (m_next_pages_menu, &QMenu::triggered,
411 
412  // Find
413  m_tool_bar->addSeparator ();
415  = add_action (rmgr.icon ("edit-find"), tr ("Find"),
416  SLOT (activate_find (void)), this, m_tool_bar);
417 
418  // Zoom
419  m_tool_bar->addSeparator ();
421  = add_action (rmgr.icon ("view-zoom-in"), tr ("Zoom in"),
422  SLOT (zoom_in (void)), m_doc_browser, m_tool_bar);
424  = add_action (rmgr.icon ("view-zoom-out"), tr ("Zoom out"),
425  SLOT (zoom_out (void)), m_doc_browser, m_tool_bar);
427  = add_action (rmgr.icon ("view-zoom-original"), tr ("Zoom original"),
428  SLOT (zoom_original (void)), m_doc_browser, m_tool_bar);
429 
430  // Bookmarks (connect slots later)
431  m_tool_bar->addSeparator ();
433  = add_action (rmgr.icon ("bookmark-new"), tr ("Bookmark current page"),
434  nullptr, nullptr, m_tool_bar);
435 }
436 
438 {
439  if (! m_help_engine)
440  return;
441 
442  QString query_string;
443 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
444  QString queries
445  = m_help_engine->searchEngine ()->queryWidget ()->searchInput ();
446  query_string = queries;
447 #else
448  // FIXME: drop this part when support for Qt4 is dropped
450  = m_help_engine->searchEngine ()->queryWidget ()->query ();
451  if (queries.count ())
452  query_string = queries.first ().wordList.join (" ");
453  else
454  query_string = "";
455 #endif
456 
457  if (query_string.isEmpty ())
458  return;
459 
460  // Get quoted search strings first, then take first string as fall back
461  QRegExp rx ("\"([^\"]*)\"");
462  if (rx.indexIn (query_string, 0) != -1)
463  m_internal_search = rx.cap (1);
464  else
465 #if defined (HAVE_QT_SPLITBEHAVIOR_ENUM)
466  m_internal_search = query_string.split (" ", Qt::SkipEmptyParts).first ();
467 #else
468  m_internal_search = query_string.split (" ", QString::SkipEmptyParts).first ();
469 #endif
470 
471  m_help_engine->searchEngine ()->search (queries);
472 }
473 
475 {
476  qApp->setOverrideCursor(QCursor(Qt::WaitCursor));
477 }
478 
480 {
481  if (! m_help_engine)
482  return;
483 
484  if (! m_internal_search.isEmpty ())
485  {
487 
488  QHelpSearchEngine *search_engine = m_help_engine->searchEngine ();
489  if (search_engine)
490  {
491 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
492  QVector<QHelpSearchResult> res
493  = search_engine->searchResults (0, search_engine->searchResultCount ());
494 #else
496  = search_engine->hits (0, search_engine->hitCount ());
497 #endif
498 
499  if (res.count ())
500  {
501  QUrl url;
502 
503  if (res.count () == 1)
504 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
505  url = res.front ().url ();
506 #else
507  url = res.front ().first;
508 #endif
509  else
510  {
511  // Remove the quotes we added
512  QString search_string = m_internal_search;
513 
514  for (auto r = res.begin (); r != res.end (); r++)
515  {
516 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
517  QString title = r->title ().toLower ();
518  QUrl tmpurl = r->url ();
519 #else
520  QString title = r->second.toLower ();
521  QUrl tmpurl = r->first;
522 #endif
523  if (title.contains (search_string.toLower ()))
524  {
525  if (title.indexOf (search_string.toLower ()) == 0)
526  {
527  url = tmpurl;
528  break;
529  }
530  else if (url.isEmpty ())
531  url = tmpurl;
532  }
533  }
534  }
535 
536  if (! url.isEmpty ())
537  {
538  connect (this, &documentation::show_single_result,
540 
541  emit show_single_result (url);
542  }
543  }
544  }
545 
546  m_internal_search = QString ();
547  }
548 
549  qApp->restoreOverrideCursor();
550 }
551 
553 {
554  // Open url with matching text
556 
557  // Select all occurrences of matching text
559 
560  // Open search widget with matching text as search string
561  m_find_line_edit->setText (m_query_string);
562  m_find_line_edit->parentWidget ()->show ();
563 
564  // If no occurrence can be found go to the top of the page
565  if (! m_doc_browser->find (m_find_line_edit->text ()))
566  m_doc_browser->moveCursor (QTextCursor::Start);
567  else
568  {
569  // Go to to first occurrence of search text. Going to the end and then
570  // search backwards until the last occurrence ensures the search text
571  // is visible in the first line of the visible part of the text.
572  m_doc_browser->moveCursor (QTextCursor::End);
573  while (m_doc_browser->find (m_find_line_edit->text (),
574  QTextDocument::FindBackward));
575  }
576 }
577 
578 void documentation::select_all_occurrences (const QString& text)
579 {
580  // Get highlight background and text color
581  QPalette pal = QApplication::palette ();
582  QTextCharFormat format;
583  QColor col = pal.color (QPalette::Highlight);
584  col.setAlphaF (0.25);
585  format.setBackground (QBrush (col));
586  format.setForeground (QBrush (pal.color (QPalette::Text)));
587 
588  // Create list for extra selected items
590  m_doc_browser->moveCursor (QTextCursor::Start);
591 
592  // Find all occurrences and add them to the selection
593  while ( m_doc_browser->find (text) )
594  {
595  QTextEdit::ExtraSelection selected_item;
596  selected_item.cursor = m_doc_browser->textCursor ();
597  selected_item.format = format;
598  selected.append (selected_item);
599  }
600 
601  // Apply selection and move back to the beginning
602  m_doc_browser->setExtraSelections (selected);
603  m_doc_browser->moveCursor (QTextCursor::Start);
604 }
605 
607 {
608  // If m_help_engine is not defined, the objects accessed by this method
609  // are not valid. Thus, just return in this case.
610  if (! m_help_engine)
611  return;
612 
613  // Icon size in the toolbar.
614  int size_idx = settings->value (global_icon_size).toInt ();
615  size_idx = (size_idx > 0) - (size_idx < 0) + 1; // Make valid index from 0 to 2
616 
617  QStyle *st = style ();
618  int icon_size = st->pixelMetric (global_icon_sizes[size_idx]);
619  m_tool_bar->setIconSize (QSize (icon_size, icon_size));
620 
621  // Shortcuts
623 
634 
635  // Settings for the browser
637 }
638 
640 {
643 
646 }
647 
649 {
650  if (m_doc_browser->hasFocus ())
651  {
652  m_doc_browser->copy();
653  }
654 }
655 
657 
659 
661 {
662  m_indexed = true;
663 
664  // Show index if no other page is required.
665  if (m_current_ref_name.isEmpty ())
666  m_doc_browser->setSource
667  (QUrl ("qthelp://org.octave.interpreter-1.0/doc/octave.html/index.html"));
668  else
670 
671  m_help_engine->contentWidget ()->expandToDepth (0);
672 }
673 
674 void documentation::load_ref (const QString& ref_name)
675 {
676  if (! m_help_engine || ref_name.isEmpty ())
677  return;
678 
679  m_current_ref_name = ref_name;
680 
681  if (! m_indexed)
682  return;
683 
684 #if defined (HAVE_QHELPENGINE_DOCUMENTSFORIDENTIFIER)
685  QList<QHelpLink> found_links
686  = m_help_engine->documentsForIdentifier (ref_name);
687 #else
688  QMap<QString, QUrl> found_links
689  = m_help_engine->linksForIdentifier (ref_name);
690 #endif
691 
692  QTabWidget *navi = static_cast<QTabWidget *> (widget (0));
693 
694  if (found_links.count() > 0)
695  {
696  // First search in the function index
697 #if defined (HAVE_QHELPENGINE_DOCUMENTSFORIDENTIFIER)
698  QUrl first_url = found_links.constFirst().url;
699 #else
700  QUrl first_url = found_links.constBegin().value ();
701 #endif
702 
703  m_doc_browser->setSource (first_url);
704 
705  // Switch to function index tab
706  m_help_engine->indexWidget()->filterIndices (ref_name);
707  QWidget *index_tab
708  = navi->findChild<QWidget *> ("documentation_tab_index");
709  navi->setCurrentWidget (index_tab);
710  }
711  else
712  {
713  // Use full text search to provide the best match
714  QHelpSearchEngine *search_engine = m_help_engine->searchEngine ();
715  QHelpSearchQueryWidget *search_query = search_engine->queryWidget ();
716 
717 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
718  QString query = ref_name;
719  query.prepend ("\"").append ("\"");
720 #else
722  query << QHelpSearchQuery (QHelpSearchQuery::DEFAULT,
723  QStringList (QString("\"") + ref_name + QString("\"")));
724 #endif
725  m_internal_search = ref_name;
726  search_engine->search (query);
727 
728  // Switch to search tab
729 #if defined (HAVE_QHELPSEARCHQUERYWIDGET_SEARCHINPUT)
730  search_query->setSearchInput (query);
731 #else
732  search_query->setQuery (query);
733 #endif
734  QWidget *search_tab
735  = navi->findChild<QWidget *> ("documentation_tab_search");
736  navi->setCurrentWidget (search_tab);
737  }
738 }
739 
741 {
742  if (m_find_line_edit->parentWidget ()->isVisible ())
743  {
744  m_find_line_edit->parentWidget ()->hide ();
745  m_doc_browser->setFocus ();
746  }
747  else
748  {
749  m_find_line_edit->parentWidget ()->show ();
750  m_find_line_edit->selectAll ();
751  m_find_line_edit->setFocus ();
752  }
753 }
754 
755 void documentation::filter_update (const QString& expression)
756 {
757  if (! m_help_engine)
758  return;
759 
760  QString wildcard;
761  if (expression.contains (QLatin1Char('*')))
762  wildcard = expression;
763 
764  m_help_engine->indexWidget ()->filterIndices(expression, wildcard);
765 }
766 
768 {
769  QString text = m_filter->currentText (); // get current text
770  int index = m_filter->findText (text); // and its actual index
771 
772  if (index > -1)
773  m_filter->removeItem (index); // remove if already existing
774 
775  m_filter->insertItem (0, text); // (re)insert at beginning
776  m_filter->setCurrentIndex (0);
777 }
778 
780 {
781  find (true);
782 }
783 
784 void documentation::find (bool backward)
785 {
786  if (! m_help_engine)
787  return;
788 
789  QTextDocument::FindFlags find_flags;
790  if (backward)
791  find_flags = QTextDocument::FindBackward;
792 
793  if (! m_doc_browser->find (m_find_line_edit->text (), find_flags))
794  {
795  // Nothing was found, restart search from the begin or end of text
796  QTextCursor textcur = m_doc_browser->textCursor ();
797  if (backward)
798  textcur.movePosition (QTextCursor::End);
799  else
800  textcur.movePosition (QTextCursor::Start);
801  m_doc_browser->setTextCursor (textcur);
802  m_doc_browser->find (m_find_line_edit->text (), find_flags);
803  }
804 
806 }
807 
808 void documentation::find_forward_from_anchor (const QString& text)
809 {
810  if (! m_help_engine)
811  return;
812 
813  // Search from the current position
814  QTextCursor textcur = m_doc_browser->textCursor ();
815  textcur.setPosition (m_search_anchor_position);
816  m_doc_browser->setTextCursor (textcur);
817 
818  if (! m_doc_browser->find (text))
819  {
820  // Nothing was found, restart search from the beginning
821  textcur.movePosition (QTextCursor::Start);
822  m_doc_browser->setTextCursor (textcur);
823  m_doc_browser->find (text);
824  }
825 }
826 
828 {
829  if (! m_help_engine)
830  return;
831 
832  m_search_anchor_position = m_doc_browser->textCursor ().position ();
833 }
834 
836 {
837  if (! m_help_engine)
838  return;
839 
840  if (m_doc_browser->hasFocus ())
842 }
843 
844 void documentation::registerDoc (const QString& qch)
845 {
846  if (m_help_engine)
847  {
848  QString ns = m_help_engine->namespaceName (qch);
849  bool do_setup = true;
850  if (m_help_engine->registeredDocumentations ().contains (ns))
851  {
852  if (m_help_engine->documentationFileName (ns) == qch)
853  do_setup = false;
854  else
855  {
856  m_help_engine->unregisterDocumentation (ns);
857  m_help_engine->registerDocumentation (qch);
858  }
859  }
860  else if (! m_help_engine->registerDocumentation (qch))
861  {
862  QMessageBox::warning (this, tr ("Octave Documentation"),
863  tr ("Unable to register help file %1.").
864  arg (qch));
865  return;
866  }
867 
868  if (do_setup)
869  m_help_engine->setupData();
870  }
871 }
872 
873 void documentation::unregisterDoc (const QString& qch)
874 {
875  if (! m_help_engine)
876  return;
877 
878  QString ns = m_help_engine->namespaceName (qch);
879  if (m_help_engine
880  && m_help_engine->registeredDocumentations ().contains (ns)
881  && m_help_engine->documentationFileName (ns) == qch)
882  {
883  m_help_engine->unregisterDocumentation (ns);
884  m_help_engine->setupData ();
885  }
886 }
887 
889 {
890  if (m_prev_pages_count != m_doc_browser->backwardHistoryCount ())
891  {
892  update_history (m_doc_browser->backwardHistoryCount (),
894  m_prev_pages_count = m_doc_browser->backwardHistoryCount ();
895  }
896 
897  if (m_next_pages_count != m_doc_browser->forwardHistoryCount ())
898  {
899  update_history (m_doc_browser->forwardHistoryCount (),
901  m_next_pages_count = m_doc_browser->forwardHistoryCount ();
902  }
903 }
904 
905 void documentation::update_history (int new_count, QAction **actions)
906 {
907  // Which menu has to be updated?
908  int prev_next = -1;
909  QAction *a = m_action_go_prev;
910  if (actions == m_next_pages_actions)
911  {
912  prev_next = 1;
913  a = m_action_go_next;
914  }
915 
916  // Get maximal count limited by array size
917  int count = qMin (new_count, int (max_history_entries));
918 
919  // Fill used menu entries
920  for (int i = 0; i < count; i++)
921  {
922  QString title
923  = title_and_anchor (m_doc_browser->historyTitle (prev_next*(i+1)),
924  m_doc_browser->historyUrl (prev_next*(i+1)));
925 
926  if (i == 0)
927  a->setText (title); // set tool tip for prev/next buttons
928 
929  actions[i]->setText (title);
930  actions[i]->setData (m_doc_browser->historyUrl (prev_next*(i+1)));
931  actions[i]->setEnabled (true);
932  actions[i]->setVisible (true);
933  }
934 
935  // Hide unused menu entries
936  for (int j = count; j < max_history_entries; j++)
937  {
938  actions[j]->setEnabled (false);
939  actions[j]->setVisible (false);
940  }
941 }
942 
944 {
945  m_doc_browser->setSource (a->data ().toUrl ());
946 }
947 
948 // Utility functions
949 
950 QString documentation::title_and_anchor (const QString& title, const QUrl& url)
951 {
952  QString retval = title;
953  QString u = url.toString ();
954 
955  retval.remove (QRegExp ("\\s*\\(*GNU Octave \\(version [^\\)]*\\)[: \\)]*"));
956 
957  // Since the title only contains the section name and not the
958  // specific anchor, extract the latter from the url and append
959  // it to the title
960  if (u.contains ('#'))
961  {
962  // Get the anchor from the url
963  QString anchor = u.split ('#').last ();
964  // Remove internal string parts
965  anchor.remove (QRegExp ("^index-"));
966  anchor.remove (QRegExp ("^SEC_"));
967  anchor.remove (QRegExp ("^XREF"));
968  anchor.remove ("Concept-Index_cp_letter-");
969  anchor.replace ("-", " ");
970 
971  // replace encoded special chars by their unencoded versions
972  QRegExp rx = QRegExp ("_00([0-7][0-9a-f])");
973  int pos = 0;
974  while ((pos = rx.indexIn(anchor, pos)) != -1)
975  {
976  anchor.replace ("_00"+rx.cap (1), QChar (rx.cap (1).toInt (nullptr, 16)));
977  pos += rx.matchedLength();
978  }
979 
980  if (retval != anchor)
981  retval = retval + ": " + anchor;
982  }
983 
984  return retval;
985 }
986 
987 //
988 // The documentation browser
989 //
990 
992  : QTextBrowser (p), m_help_engine (he), m_zoom_level (max_zoom_level+1)
993 {
994  setOpenLinks (false);
995  connect (this, &documentation_browser::anchorClicked,
996  this, [=] (const QUrl& url) { handle_index_clicked (url); });
997 
998  // Make sure we have access to one of the monospace fonts listed in
999  // octave.css for rendering formated code blocks
1000  QStringList fonts = {"Fantasque Sans Mono", "FreeMono", "Courier New",
1001  "Cousine", "Courier"};
1002 
1003  bool load_default_font = true;
1004 
1005  for (int i = 0; i < fonts.size (); ++i)
1006  {
1007  QFont font (fonts.at (i));
1008  if (font.exactMatch ())
1009  {
1010  load_default_font = false;
1011  break;
1012  }
1013  }
1014 
1015  if (load_default_font)
1016  {
1017  QString fonts_dir =
1018  QString::fromStdString (sys::env::getenv ("OCTAVE_FONTS_DIR")
1020 
1021  QStringList default_fonts = {"FreeMono", "FreeMonoBold",
1022  "FreeMonoBoldOblique", "FreeMonoOblique"};
1023 
1024  for (int i = 0; i < default_fonts.size (); ++i)
1025  {
1026  QString fontpath =
1027  fonts_dir + default_fonts.at(i) + QString (".otf");
1028  QFontDatabase::addApplicationFont (fontpath);
1029  }
1030  }
1031 }
1032 
1034  const QString&)
1035 {
1036  if (url.scheme () == "qthelp")
1037  setSource (url);
1038  else
1039  QDesktopServices::openUrl (url);
1040 }
1041 
1043 {
1044  // Zoom level only at startup, not when other settings have changed
1046  {
1047  m_zoom_level = settings->value (dc_browser_zoom_level).toInt ();
1048  zoomIn (m_zoom_level);
1049  }
1050 }
1051 
1052 QVariant documentation_browser::loadResource (int type, const QUrl& url)
1053 {
1054  if (m_help_engine && url.scheme () == "qthelp")
1055  return QVariant (m_help_engine->fileData(url));
1056  else
1057  return QTextBrowser::loadResource(type, url);
1058 }
1059 
1061 {
1063 
1064  settings->sync ();
1065 }
1066 
1068 {
1070  {
1071  zoomIn ();
1072  m_zoom_level++;
1073  }
1074 }
1075 
1077 {
1079  {
1080  zoomOut ();
1081  m_zoom_level--;
1082  }
1083 }
1084 
1086 {
1087  zoomIn (- m_zoom_level);
1088  m_zoom_level = 0;
1089 }
1090 
1092 {
1093  if (we->modifiers () == Qt::ControlModifier)
1094  {
1095 #if defined (HAVE_QWHEELEVENT_ANGLEDELTA)
1096  if (we->angleDelta().y () > 0)
1097 #else
1098  if (we->delta() > 0)
1099 #endif
1100  zoom_in ();
1101  else
1102  zoom_out ();
1103 
1104  we->accept ();
1105  }
1106  else
1107  QTextEdit::wheelEvent (we);
1108 }
1109 
OCTAVE_END_NAMESPACE(octave)
Base class for Octave interfaces that use Qt.
resource_manager & get_resource_manager(void)
shortcut_manager & get_shortcut_manager(void)
void save_settings(gui_settings *settings)
Documentation browser derived from Textbrowser.
Definition: documentation.h:51
documentation_browser(QHelpEngine *help_engine, QWidget *parent=nullptr)
void zoom_out(void)
Zooming in and out while taking care of the zoom level.
void save_settings(gui_settings *settings)
void wheelEvent(QWheelEvent *we)
void zoom_in(void)
Zooming in and out while taking care of the zoom level.
int m_zoom_level
Store the current zoom level.
Definition: documentation.h:84
virtual QVariant loadResource(int type, const QUrl &url)
QHelpEngine * m_help_engine
Definition: documentation.h:81
void notice_settings(const gui_settings *settings)
void zoom_original(void)
Zooming in and out while taking care of the zoom level.
void handle_index_clicked(const QUrl &url, const QString &keyword=QString())
The documentation main class derived from QSplitter.
Definition: documentation.h:99
void load_index(void)
QAction * m_action_zoom_out
void pasteClipboard(void)
void find_forward_from_anchor(const QString &text)
QAction * m_action_go_next
void select_all_occurrences(const QString &text)
Select all occurrences of a string in the doc browser.
void show_single_result(const QUrl &)
QShortcut * m_findprev_shortcut
QAction * m_action_go_prev
QToolBar * m_tool_bar
void global_search(void)
void find(bool backward=false)
QString m_current_ref_name
QAction * m_action_zoom_original
documentation_bookmarks * m_bookmarks
void global_search_finished(int hits)
QLineEdit * m_find_line_edit
QMenu * m_prev_pages_menu
QAction * m_action_bookmark
void notice_settings(const gui_settings *settings)
QString title_and_anchor(const QString &title, const QUrl &url)
int m_search_anchor_position
void record_anchor_position(void)
QString m_internal_search
base_qobject & m_octave_qobj
QWidget * m_doc_widget
void handle_search_result_clicked(const QUrl &url)
QAction * m_action_find
QAction * add_action(const QIcon &icon, const QString &text, const char *member, QWidget *receiver=nullptr, QToolBar *tool_bar=nullptr)
void copyClipboard(void)
void update_history_menus(void)
void save_settings(void)
void open_hist_url(QAction *a)
void load_ref(const QString &name=QString())
QHelpEngine * m_help_engine
void activate_find(void)
void filter_update(const QString &expression)
QString m_query_string
QShortcut * m_findnext_shortcut
void handle_cursor_position_change(void)
QAction * m_action_zoom_in
documentation_browser * m_doc_browser
QAction * m_action_go_home
void unregisterDoc(const QString &name)
void filter_update_history(void)
void global_search_started(void)
void registerDoc(const QString &name)
QComboBox * m_filter
QAction * m_next_pages_actions[max_history_entries]
void selectAll(void)
QMenu * m_next_pages_menu
void update_history(int new_count, QAction **actions)
void find_backward(void)
void construct_tool_bar(void)
QAction * m_prev_pages_actions[max_history_entries]
QString m_collection
gui_settings * get_settings(void) const
QIcon icon(const QString &icon_name, bool octave_only=false, const QString &icon_alt_name=QString())
void set_shortcut(QAction *action, const sc_pref &scpref, bool enable=true)
void shortcut(QShortcut *sc, const sc_pref &scpref)
static octave_idx_type find(octave_idx_type i, octave_idx_type *pp)
Definition: colamd.cc:106
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
std::string oct_doc_dir(void)
Definition: defaults.cc:331
void warning(const char *fmt,...)
Definition: error.cc:1054
std::string tempnam(const std::string &dir, const std::string &pfx)
Definition: file-ops.cc:697
int link(const std::string &old_name, const std::string &new_name)
Definition: file-ops.cc:491
std::string dir_sep_str(void)
Definition: file-ops.cc:240
int recursive_rmdir(const std::string &name)
Definition: file-ops.cc:608
const gui_pref dc_browser_zoom_level("documentation_widget/browser_zoom_level", QVariant(0))
const QStyle::PixelMetric global_icon_sizes[3]
const gui_pref global_icon_size("toolbar_icon_size", QVariant(0))
const sc_pref sc_doc_go_home(sc_doc+":go_home", Qt::AltModifier+Qt::Key_Home)
const sc_pref sc_doc_go_back(sc_doc+":go_back", QKeySequence::Back)
const sc_pref sc_edit_view_zoom_normal(sc_edit_view_zoom+"_normal", CTRL+Qt::Key_Period)
const sc_pref sc_edit_edit_find_replace(sc_edit_edit_find+"_replace", QKeySequence::Find)
const sc_pref sc_doc_bookmark(sc_doc+":bookmark", CTRL+Qt::Key_D)
const sc_pref sc_edit_edit_find_next(sc_edit_edit_find+"_next", QKeySequence::FindNext)
const sc_pref sc_edit_edit_find_previous(sc_edit_edit_find+"_previous", QKeySequence::FindPrevious)
const sc_pref sc_edit_view_zoom_in(sc_edit_view_zoom+"_in", QKeySequence::ZoomIn)
const sc_pref sc_doc_go_next(sc_doc+":go_next", QKeySequence::Forward)
const sc_pref sc_edit_view_zoom_out(sc_edit_view_zoom+"_out", QKeySequence::ZoomOut)
static std::list< std::string > search(const std::string &path, const std::string &original_name, bool all)
Definition: kpse.cc:495
static std::string get_temp_directory(void)
T * r
Definition: mx-inlines.cc:773
QString fromStdString(const std::string &s)
static double we[256]
Definition: randmtzig.cc:466
static double fi[256]
Definition: randmtzig.cc:464
const QString key
std::size_t format(std::ostream &os, const char *fmt,...)
Definition: utils.cc:1473