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