123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- /*=========================================================================
- Library: CTK
- Copyright (c) Kitware Inc.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0.txt
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- =========================================================================*/
- // Qt includes
- #include <QDebug>
- #include <QScrollBar>
- #include <QTextBlock>
- #include <QAbstractTextDocumentLayout>
- // CTK includes
- #include "ctkFittedTextBrowser.h"
- #include "ctkFittedTextBrowser_p.h"
- static const char moreAnchor[] = "more";
- static const char lessAnchor[] = "less";
- //-----------------------------------------------------------------------------
- ctkFittedTextBrowserPrivate::ctkFittedTextBrowserPrivate(ctkFittedTextBrowser& object)
- :q_ptr(&object)
- {
- this->Collapsible = false;
- this->Collapsed = true;
- this->FullTextSetter = ctkFittedTextBrowserPrivate::Text;
- this->ShowMoreText = object.tr("More...");
- this->ShowLessText = object.tr("Hide details.");
- QString ShowLessText;
- }
- //-----------------------------------------------------------------------------
- ctkFittedTextBrowserPrivate::~ctkFittedTextBrowserPrivate()
- {
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowserPrivate::collapsibleText()
- {
- Q_Q(ctkFittedTextBrowser);
- bool html = (this->FullTextSetter == ctkFittedTextBrowserPrivate::Html || this->FullText.indexOf("<html>") >= 0);
- if (html)
- {
- return this->collapsibleHtml();
- }
- else
- {
- return this->collapsiblePlainText();
- }
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowserPrivate::collapseLinkText()
- {
- Q_Q(ctkFittedTextBrowser);
- if (this->Collapsed)
- {
- return QString(" <a href=\"#") + moreAnchor + "\">" + this->ShowMoreText + "</a>";
- }
- else
- {
- return QString(" <a href=\"#") + lessAnchor + "\">" + this->ShowLessText + "</a>";
- }
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowserPrivate::collapsiblePlainText()
- {
- Q_Q(ctkFittedTextBrowser);
- int teaserEndPosition = this->FullText.indexOf("\n");
- if (teaserEndPosition < 0)
- {
- return this->FullText;
- }
- QString finalText;
- finalText.append("<html>");
- finalText.append(this->Collapsed ? this->FullText.left(teaserEndPosition) : this->FullText);
- finalText.append(this->collapseLinkText());
- finalText.append("</html>");
- // Remove line break to allow continuation of line.
- finalText.replace(finalText.indexOf('\n'), 1, ' ');
- // In plain text line breaks were indicated by newline, but we now use html,
- // so line breaks must use <br>
- finalText.replace("\n", "<br>");
- return finalText;
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowserPrivate::collapsibleHtml()
- {
- Q_Q(ctkFittedTextBrowser);
- const QString lineBreak("<br>");
- int teaserEndPosition = this->FullText.indexOf(lineBreak);
- if (teaserEndPosition < 0)
- {
- return this->FullText;
- }
- QString finalText = this->FullText;
- if (this->Collapsed)
- {
- finalText = finalText.left(teaserEndPosition) + this->collapseLinkText();
- // By truncating the full text we might have deleted the closing </html> tag
- // restore it now.
- if (finalText.contains("<html") && !finalText.contains("</html"))
- {
- finalText.append("</html>");
- }
- }
- else
- {
- // Remove <br> to allow continuation of line and avoid extra space
- // when <p> element is used as well.
- finalText.replace(finalText.indexOf(lineBreak), lineBreak.size(), " ");
- // Add link text before closing </body> or </html> tag
- if (finalText.contains("</body>"))
- {
- finalText.replace("</body>", this->collapseLinkText() + "</body>");
- }
- else if (finalText.contains("</html>"))
- {
- finalText.replace("</html>", this->collapseLinkText() + "</html>");
- }
- else
- {
- finalText.append(this->collapseLinkText());
- }
- }
- return finalText;
- }
- //-----------------------------------------------------------------------------
- ctkFittedTextBrowser::ctkFittedTextBrowser(QWidget* _parent)
- : QTextBrowser(_parent)
- , d_ptr(new ctkFittedTextBrowserPrivate(*this))
- {
- this->connect(this, SIGNAL(textChanged()), SLOT(heightForWidthMayHaveChanged()));
- QSizePolicy newSizePolicy = QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
- this->setSizePolicy(newSizePolicy);
- this->connect(this, SIGNAL(anchorClicked(QUrl)), SLOT(anchorClicked(QUrl)));
- }
- //-----------------------------------------------------------------------------
- ctkFittedTextBrowser::~ctkFittedTextBrowser()
- {
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::heightForWidthMayHaveChanged()
- {
- this->updateGeometry();
- }
- //-----------------------------------------------------------------------------
- int ctkFittedTextBrowser::heightForWidth(int _width) const
- {
- QTextDocument* doc = this->document();
- qreal savedWidth = doc->textWidth();
- // Fudge factor. This is the difference between the frame and the
- // viewport.
- int fudge = 2 * this->frameWidth();
- int horizontalScrollbarHeight = 0;
- if (this->horizontalScrollBar()->isVisible())
- {
- horizontalScrollbarHeight = this->horizontalScrollBar()->height() + fudge;
- }
- // Do the calculation assuming no scrollbars
- doc->setTextWidth(_width - fudge);
- int noScrollbarHeight =
- doc->documentLayout()->documentSize().height() + fudge;
- // (If noScrollbarHeight is greater than the maximum height we'll be
- // allowed, then there will be scrollbars, and the actual required
- // height will be even higher. But since in this case we've already
- // hit the maximum height, it doesn't matter that we underestimate.)
- // Get minimum height (even if string is empty): one line of text
- int _minimumHeight = QFontMetrics(doc->defaultFont()).lineSpacing() + fudge;
- int ret = qMax(noScrollbarHeight, _minimumHeight) + horizontalScrollbarHeight;
- doc->setTextWidth(savedWidth);
- return ret;
- }
- //-----------------------------------------------------------------------------
- QSize ctkFittedTextBrowser::minimumSizeHint() const {
- QSize s(this->size().width(), 0);
- if (s.width() == 0)
- {
- //s.setWidth(400); // arbitrary value
- return QTextBrowser::minimumSizeHint();
- }
- s.setHeight(this->heightForWidth(s.width()));
- return s;
- }
- //-----------------------------------------------------------------------------
- QSize ctkFittedTextBrowser::sizeHint() const {
- return this->minimumSizeHint();
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::resizeEvent(QResizeEvent* e)
- {
- this->QTextBrowser::resizeEvent(e);
- if (e->size().height() != this->heightForWidth(e->size().width()))
- {
- this->heightForWidthMayHaveChanged();
- }
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setText(const QString &text)
- {
- Q_D(ctkFittedTextBrowser);
- d->FullTextSetter = ctkFittedTextBrowserPrivate::Text;
- if (d->Collapsible)
- {
- d->FullText = text;
- QTextBrowser::setHtml(d->collapsibleText());
- }
- else
- {
- QTextBrowser::setText(text);
- }
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setPlainText(const QString &text)
- {
- Q_D(ctkFittedTextBrowser);
- d->FullTextSetter = ctkFittedTextBrowserPrivate::PlainText;
- if (d->Collapsible)
- {
- d->FullText = text;
- QTextBrowser::setHtml(d->collapsibleText());
- }
- else
- {
- QTextBrowser::setPlainText(text);
- }
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setHtml(const QString &text)
- {
- Q_D(ctkFittedTextBrowser);
- d->FullTextSetter = ctkFittedTextBrowserPrivate::Html;
- // always save the original text as well because use may make the widget
- // collapsible at any time
- d->FullText = text;
- if (d->Collapsible)
- {
- QTextBrowser::setHtml(d->collapsibleText());
- }
- else
- {
- QTextBrowser::setHtml(text);
- }
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::anchorClicked(const QUrl &url)
- {
- Q_D(ctkFittedTextBrowser);
- if (url.path().isEmpty())
- {
- if (url.fragment() == moreAnchor)
- {
- this->setCollapsed(false);
- }
- else if (url.fragment() == lessAnchor)
- {
- this->setCollapsed(true);
- }
- }
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setCollapsed(bool collapsed)
- {
- Q_D(ctkFittedTextBrowser);
- if (d->Collapsed == collapsed)
- {
- // no change
- return;
- }
- d->Collapsed = collapsed;
- if (d->Collapsible)
- {
- QTextBrowser::setHtml(d->collapsibleText());
- }
- }
- //-----------------------------------------------------------------------------
- bool ctkFittedTextBrowser::collapsed() const
- {
- Q_D(const ctkFittedTextBrowser);
- return d->Collapsed;
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setCollapsible(bool collapsible)
- {
- Q_D(ctkFittedTextBrowser);
- if (d->Collapsible == collapsible)
- {
- // no change
- return;
- }
- d->Collapsible = collapsible;
- if (collapsible)
- {
- QTextBrowser::setHtml(d->collapsibleText());
- }
- else
- {
- switch (d->FullTextSetter)
- {
- case ctkFittedTextBrowserPrivate::Text: QTextBrowser::setText(d->FullText); break;
- case ctkFittedTextBrowserPrivate::PlainText: QTextBrowser::setPlainText(d->FullText); break;
- case ctkFittedTextBrowserPrivate::Html: QTextBrowser::setHtml(d->FullText); break;
- default: QTextBrowser::setText(d->FullText); break;
- }
- }
- }
- //-----------------------------------------------------------------------------
- bool ctkFittedTextBrowser::collapsible() const
- {
- Q_D(const ctkFittedTextBrowser);
- return d->Collapsible;
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setShowMoreText(const QString &text)
- {
- Q_D(ctkFittedTextBrowser);
- if (d->ShowMoreText == text)
- {
- // no change
- return;
- }
- d->ShowMoreText = text;
- if (d->Collapsible)
- {
- QTextBrowser::setHtml(d->collapsibleText());
- }
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowser::showMoreText() const
- {
- Q_D(const ctkFittedTextBrowser);
- return d->ShowMoreText;
- }
- //-----------------------------------------------------------------------------
- void ctkFittedTextBrowser::setShowLessText(const QString &text)
- {
- Q_D(ctkFittedTextBrowser);
- if (d->ShowLessText == text)
- {
- // no change
- return;
- }
- d->ShowLessText = text;
- if (d->Collapsible)
- {
- QTextBrowser::setHtml(d->collapsibleText());
- }
- }
- //-----------------------------------------------------------------------------
- QString ctkFittedTextBrowser::showLessText() const
- {
- Q_D(const ctkFittedTextBrowser);
- return d->ShowLessText;
- }
|