diff --git a/generators/chm/generator_chm.cpp b/generators/chm/generator_chm.cpp index c4f073ea3..ea78f27f8 100644 --- a/generators/chm/generator_chm.cpp +++ b/generators/chm/generator_chm.cpp @@ -1,449 +1,448 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr SzymaƄski * * Copyright (C) 2008 by Albert Astals Cid * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "generator_chm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include OKULAR_EXPORT_PLUGIN(CHMGenerator, "libokularGenerator_chmlib.json") static QString absolutePath( const QString &baseUrl, const QString &path ) { QString absPath; if ( path.startsWith(QLatin1Char( '/' )) ) { // already absolute absPath = path; } else { QUrl url = QUrl::fromLocalFile( baseUrl ).adjusted(QUrl::RemoveFilename); url.setPath( url.path() + path ); absPath = url.toLocalFile(); } return absPath; } CHMGenerator::CHMGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ) { setFeature( TextExtraction ); m_syncGen=nullptr; m_file=nullptr; m_request = nullptr; } CHMGenerator::~CHMGenerator() { delete m_syncGen; } bool CHMGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) { - m_file = new EBook_CHM(); m_file = EBook::loadFile( fileName ); if (!m_file) { return false; } m_fileName=fileName; QList< EBookTocEntry > topics; m_file->getTableOfContents(topics); // fill m_docSyn QMap lastIndentElement; QMap tmpPageList; int pageNum = 0; for (const EBookTocEntry &e : qAsConst(topics)) { QDomElement item = m_docSyn.createElement(e.name); if (!e.url.isEmpty()) { QString url = e.url.toString(); item.setAttribute(QStringLiteral("ViewportName"), url); if(!tmpPageList.contains(url)) {//add a page only once tmpPageList.insert(url, pageNum); pageNum++; } } item.setAttribute(QStringLiteral("Icon"), e.iconid); if (e.indent == 0) m_docSyn.appendChild(item); else lastIndentElement[e.indent - 1].appendChild(item); lastIndentElement[e.indent] = item; } // fill m_urlPage and m_pageUrl QList pageList; m_file->enumerateFiles(pageList); const QUrl home = m_file->homeUrl(); if (home.path() != QLatin1String("/")) pageList.prepend(home); m_pageUrl.resize(pageNum); for (const QUrl &qurl : qAsConst(pageList)) { QString url = qurl.toString(); const QString urlLower = url.toLower(); if (!urlLower.endsWith(QLatin1String(".html")) && !urlLower.endsWith(QLatin1String(".htm"))) continue; int pos = url.indexOf (QLatin1Char(('#'))); // insert the url into the maps, but insert always the variant without the #ref part QString tmpUrl = pos == -1 ? url : url.left(pos); // url already there, abort insertion if (m_urlPage.contains(tmpUrl)) continue; int foundPage = tmpPageList.value(tmpUrl, -1); if (foundPage != -1 ) { m_urlPage.insert(tmpUrl, foundPage); m_pageUrl[foundPage] = tmpUrl; } else { //add pages not present in toc m_urlPage.insert(tmpUrl, pageNum); m_pageUrl.append(tmpUrl); pageNum++; } } pagesVector.resize(m_pageUrl.count()); m_textpageAddedList.fill(false, pagesVector.count()); m_rectsGenerated.fill(false, pagesVector.count()); if (!m_syncGen) { m_syncGen = new KHTMLPart(); } disconnect( m_syncGen, nullptr, this, nullptr ); for (int i = 0; i < m_pageUrl.count(); ++i) { preparePageForSyncOperation(m_pageUrl.at(i)); pagesVector[ i ] = new Okular::Page (i, m_syncGen->view()->contentsWidth(), m_syncGen->view()->contentsHeight(), Okular::Rotation0 ); } connect( m_syncGen, QOverload<>::of(&KHTMLPart::completed), this, &CHMGenerator::slotCompleted ); connect( m_syncGen, &KParts::ReadOnlyPart::canceled, this, &CHMGenerator::slotCompleted ); return true; } bool CHMGenerator::doCloseDocument() { // delete the document information of the old document delete m_file; m_file=nullptr; m_textpageAddedList.clear(); m_rectsGenerated.clear(); m_urlPage.clear(); m_pageUrl.clear(); m_docSyn.clear(); if (m_syncGen) { m_syncGen->closeUrl(); } return true; } void CHMGenerator::preparePageForSyncOperation(const QString & url) { QString pAddress = QStringLiteral("ms-its:") + m_fileName + QStringLiteral("::") + m_file->urlToPath(QUrl(url)); m_chmUrl = url; m_syncGen->openUrl(QUrl(pAddress)); m_syncGen->view()->layout(); QEventLoop loop; connect( m_syncGen, QOverload<>::of(&KHTMLPart::completed), &loop, &QEventLoop::quit ); connect( m_syncGen, &KParts::ReadOnlyPart::canceled, &loop, &QEventLoop::quit ); // discard any user input, otherwise it breaks the "synchronicity" of this // function loop.exec( QEventLoop::ExcludeUserInputEvents ); } void CHMGenerator::slotCompleted() { if ( !m_request ) return; QImage image( m_request->width(), m_request->height(), QImage::Format_ARGB32 ); image.fill( Qt::white ); QPainter p( &image ); QRect r( 0, 0, m_request->width(), m_request->height() ); bool moreToPaint; m_syncGen->paint( &p, r, 0, &moreToPaint ); p.end(); if ( !m_textpageAddedList.at( m_request->pageNumber() ) ) { additionalRequestData(); m_textpageAddedList[ m_request->pageNumber() ] = true; } m_syncGen->closeUrl(); m_chmUrl = QString(); userMutex()->unlock(); Okular::PixmapRequest *req = m_request; m_request = nullptr; if ( !req->page()->isBoundingBoxKnown() ) updatePageBoundingBox( req->page()->number(), Okular::Utils::imageBoundingBox( &image ) ); req->page()->setPixmap( req->observer(), new QPixmap( QPixmap::fromImage( image ) ) ); signalPixmapRequestDone( req ); } Okular::DocumentInfo CHMGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/x-chm") ); if ( keys.contains( Okular::DocumentInfo::Title ) ) docInfo.set( Okular::DocumentInfo::Title, m_file->title() ); return docInfo; } const Okular::DocumentSynopsis * CHMGenerator::generateDocumentSynopsis() { return &m_docSyn; } bool CHMGenerator::canGeneratePixmap () const { bool isLocked = true; if ( userMutex()->tryLock() ) { userMutex()->unlock(); isLocked = false; } return !isLocked; } void CHMGenerator::generatePixmap( Okular::PixmapRequest * request ) { int requestWidth = request->width(); int requestHeight = request->height(); userMutex()->lock(); QString url= m_pageUrl[request->pageNumber()]; QString pAddress= QStringLiteral("ms-its:") + m_fileName + QStringLiteral("::") + m_file->urlToPath(QUrl(url)); m_chmUrl = url; m_syncGen->view()->resizeContents(requestWidth,requestHeight); m_request=request; // will emit openURL without problems m_syncGen->openUrl ( QUrl(pAddress) ); } void CHMGenerator::recursiveExploreNodes(DOM::Node node,Okular::TextPage *tp) { if (node.nodeType() == DOM::Node::TEXT_NODE && !node.getRect().isNull()) { QString nodeText=node.nodeValue().string(); QRect r=node.getRect(); int vWidth=m_syncGen->view()->width(); int vHeight=m_syncGen->view()->height(); Okular::NormalizedRect *nodeNormRect; #define NOEXP #ifndef NOEXP int x,y,height; int x_next,y_next,height_next; int nodeTextLength = nodeText.length(); if (nodeTextLength==1) { nodeNormRect=new Okular::NormalizedRect (r,vWidth,vHeight); tp->append(nodeText,nodeNormRect,nodeNormRect->bottom,0,(nodeText=="\n")); } else { for (int i=0;iappend(nodeText,nodeNormRect/*,0*/); #endif } DOM::Node child = node.firstChild(); while ( !child.isNull() ) { recursiveExploreNodes(child,tp); child = child.nextSibling(); } } void CHMGenerator::additionalRequestData() { Okular::Page * page=m_request->page(); const bool genObjectRects = !m_rectsGenerated.at( m_request->page()->number() ); const bool genTextPage = !m_request->page()->hasTextPage() && genObjectRects; if (genObjectRects || genTextPage ) { DOM::HTMLDocument domDoc=m_syncGen->htmlDocument(); // only generate object info when generating a full page not a thumbnail if ( genObjectRects ) { QLinkedList< Okular::ObjectRect * > objRects; int xScale=m_syncGen->view()->width(); int yScale=m_syncGen->view()->height(); // getting links DOM::HTMLCollection coll=domDoc.links(); DOM::Node n; QRect r; if (! coll.isNull() ) { int size=coll.length(); for(int i=0;ipage()->setObjectRects( objRects ); m_rectsGenerated[ m_request->page()->number() ] = true; } if ( genTextPage ) { Okular::TextPage *tp=new Okular::TextPage(); recursiveExploreNodes(domDoc,tp); page->setTextPage (tp); } } } Okular::TextPage* CHMGenerator::textPage( Okular::TextRequest * request ) { userMutex()->lock(); const Okular::Page *page = request->page(); m_syncGen->view()->resize(page->width(), page->height()); preparePageForSyncOperation(m_pageUrl[page->number()]); Okular::TextPage *tp=new Okular::TextPage(); recursiveExploreNodes( m_syncGen->htmlDocument(), tp); userMutex()->unlock(); return tp; } QVariant CHMGenerator::metaData( const QString &key, const QVariant &option ) const { if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() ) { const int pos = option.toString().indexOf(QLatin1Char('#')); QString tmpUrl = pos == -1 ? option.toString() : option.toString().left(pos); Okular::DocumentViewport viewport; QMap::const_iterator it = m_urlPage.find(tmpUrl); if (it != m_urlPage.end()) { viewport.pageNumber = it.value(); return viewport.toString(); } } else if ( key == QLatin1String("DocumentTitle") ) { return m_file->title(); } return QVariant(); } /* kate: replace-tabs on; tab-width 4; */ #include "generator_chm.moc" diff --git a/ui/ktreeviewsearchline.cpp b/ui/ktreeviewsearchline.cpp index a03bf0561..1e3573a62 100644 --- a/ui/ktreeviewsearchline.cpp +++ b/ui/ktreeviewsearchline.cpp @@ -1,415 +1,416 @@ /* Copyright (c) 2003 Scott Wheeler Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2006 Hamish Rodda Copyright 2007 Pino Toscano This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktreeviewsearchline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KTreeViewSearchLine::Private { public: Private( KTreeViewSearchLine *_parent ) : parent( _parent ), treeView( nullptr ), caseSensitive( Qt::CaseInsensitive ), regularExpression( false ), activeSearch( false ), queuedSearches( 0 ) { } KTreeViewSearchLine *parent; QTreeView * treeView; Qt::CaseSensitivity caseSensitive; bool regularExpression; bool activeSearch; QString search; int queuedSearches; void rowsInserted(const QModelIndex & parent, int start, int end) const; void treeViewDeleted( QObject *object ); void slotCaseSensitive(); void slotRegularExpression(); void checkItemParentsNotVisible(QTreeView *treeView); - bool checkItemParentsVisible(QTreeView *treeView, const QModelIndex &index); + bool filterItems(QTreeView *treeView, const QModelIndex &index); }; //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::Private::rowsInserted( const QModelIndex & parentIndex, int start, int end ) const { QAbstractItemModel* model = qobject_cast( parent->sender() ); if ( !model ) return; QTreeView* widget = nullptr; if ( treeView->model() == model ) { widget = treeView; } if ( !widget ) return; for ( int i = start; i <= end; ++i ) { widget->setRowHidden( i, parentIndex, !parent->itemMatches( parentIndex, i, parent->text() ) ); } } void KTreeViewSearchLine::Private::treeViewDeleted( QObject *object ) { if ( object == treeView ) { treeView = nullptr; parent->setEnabled( false ); } } void KTreeViewSearchLine::Private::slotCaseSensitive() { if ( caseSensitive == Qt::CaseSensitive) parent->setCaseSensitivity( Qt::CaseInsensitive ); else parent->setCaseSensitivity( Qt::CaseSensitive ); parent->updateSearch(); } void KTreeViewSearchLine::Private::slotRegularExpression() { if ( regularExpression ) parent->setRegularExpression( false ); else parent->setRegularExpression( true ); parent->updateSearch(); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// /** Check whether \p item, its siblings and their descendants should be shown. Show or hide the items as necessary. * * \p item The list view item to start showing / hiding items at. Typically, this is the first child of another item, or the * the first child of the list view. * \return \c true if an item which should be visible is found, \c false if all items found should be hidden. If this function * returns true and \p highestHiddenParent was not 0, highestHiddenParent will have been shown. */ -bool KTreeViewSearchLine::Private::checkItemParentsVisible( QTreeView *treeView, const QModelIndex &index ) +bool KTreeViewSearchLine::Private::filterItems( QTreeView *treeView, const QModelIndex &index ) { bool childMatch = false; const int rowcount = treeView->model()->rowCount( index ); for ( int i = 0; i < rowcount; ++i ) - childMatch |= checkItemParentsVisible( treeView, treeView->model()->index( i, 0, index ) ); + childMatch |= filterItems( treeView, treeView->model()->index( i, 0, index ) ); // Should this item be shown? It should if any children should be, or if it matches. const QModelIndex parentindex = index.parent(); if ( childMatch || parent->itemMatches( parentindex, index.row(), search ) ) { treeView->setRowHidden( index.row(), parentindex, false ); return true; } treeView->setRowHidden( index.row(), parentindex, true ); return false; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// KTreeViewSearchLine::KTreeViewSearchLine( QWidget *parent, QTreeView *treeView ) : KLineEdit( parent ), d( new Private( this ) ) { connect(this, &KTreeViewSearchLine::textChanged, this, &KTreeViewSearchLine::queueSearch); setClearButtonEnabled( true ); setTreeView( treeView ); if ( !treeView ) { setEnabled( false ); } } KTreeViewSearchLine::~KTreeViewSearchLine() { delete d; } Qt::CaseSensitivity KTreeViewSearchLine::caseSensitivity() const { return d->caseSensitive; } bool KTreeViewSearchLine::regularExpression() const { return d->regularExpression; } QTreeView *KTreeViewSearchLine::treeView() const { return d->treeView; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::updateSearch( const QString &pattern ) { d->search = pattern.isNull() ? text() : pattern; updateSearch( d->treeView ); } void KTreeViewSearchLine::updateSearch( QTreeView *treeView ) { if ( !treeView || !treeView->model()->rowCount() ) return; // If there's a selected item that is visible, make sure that it's visible // when the search changes too (assuming that it still matches). QModelIndex currentIndex = treeView->currentIndex(); bool wasUpdateEnabled = treeView->updatesEnabled(); treeView->setUpdatesEnabled( false ); - for ( int i = 0; i < treeView->model()->rowCount(); ++i ) - d->checkItemParentsVisible( treeView, treeView->rootIndex() ); + d->filterItems( treeView, treeView->rootIndex() ); treeView->setUpdatesEnabled( wasUpdateEnabled ); if ( currentIndex.isValid() ) treeView->scrollTo( currentIndex ); } void KTreeViewSearchLine::setCaseSensitivity( Qt::CaseSensitivity caseSensitivity ) { if ( d->caseSensitive != caseSensitivity ) { d->caseSensitive = caseSensitivity; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setRegularExpression( bool value ) { if ( d->regularExpression != value ) { d->regularExpression = value; updateSearch(); emit searchOptionsChanged(); } } void KTreeViewSearchLine::setTreeView( QTreeView *treeView ) { disconnectTreeView( d->treeView ); d->treeView = treeView; connectTreeView( treeView ); setEnabled( treeView != nullptr ); } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// bool KTreeViewSearchLine::itemMatches( const QModelIndex &parentIndex, int row, const QString &pattern ) const { if ( pattern.isEmpty() ) return true; if ( !parentIndex.isValid() && parentIndex != d->treeView->rootIndex() ) return false; // Construct a regular expression object with the right options. - QRegularExpression re( - d->regularExpression ? pattern : QRegularExpression::escape(pattern), - d->caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption ); - QRegularExpressionMatch match; + QRegularExpression re; + if (d->regularExpression) { + re.setPattern(pattern); + re.setPatternOptions(d->caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); + } // If the search column list is populated, search just the columns // specified. If it is empty default to searching all of the columns. QAbstractItemModel *model = d->treeView->model(); const int columncount = model->columnCount( parentIndex ); for ( int i = 0; i < columncount; ++i) { const QString str = model->data( model->index( row, i, parentIndex ), Qt::DisplayRole ).toString(); - match = re.match( str ); - if ( match.hasMatch() ) { - return true; + if (d->regularExpression) { + return str.contains(re); + } else { + return str.contains(pattern, d->caseSensitive? Qt::CaseSensitive : Qt::CaseInsensitive); } } return false; } void KTreeViewSearchLine::contextMenuEvent( QContextMenuEvent *event ) { QMenu *popup = KLineEdit::createStandardContextMenu(); popup->addSeparator(); QMenu *optionsSubMenu = popup->addMenu( i18n("Search Options") ); QAction* caseSensitiveAction = optionsSubMenu->addAction( i18nc("Enable case sensitive search in the side navigation panels", "Case Sensitive"), this, [this] { d->slotCaseSensitive(); } ); caseSensitiveAction->setCheckable( true ); caseSensitiveAction->setChecked( d->caseSensitive ); QAction* regularExpressionAction = optionsSubMenu->addAction( i18nc("Enable regular expression search in the side navigation panels", "Regular Expression"), this, [this] { d->slotRegularExpression(); } ); regularExpressionAction->setCheckable( true ); regularExpressionAction->setChecked( d->regularExpression ); popup->exec( event->globalPos() ); delete popup; } void KTreeViewSearchLine::connectTreeView( QTreeView *treeView ) { if ( treeView ) { connect( treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted ); connect( treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted ); } } void KTreeViewSearchLine::disconnectTreeView( QTreeView *treeView ) { if ( treeView ) { disconnect( treeView, &QTreeView::destroyed, this, &KTreeViewSearchLine::treeViewDeleted ); disconnect( treeView->model(), &QAbstractItemModel::rowsInserted, this, &KTreeViewSearchLine::rowsInserted ); } } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::queueSearch( const QString &search ) { d->queuedSearches++; d->search = search; QTimer::singleShot( 200, this, &KTreeViewSearchLine::activateSearch ); } void KTreeViewSearchLine::activateSearch() { --(d->queuedSearches); if ( d->queuedSearches == 0 ) updateSearch( d->search ); } //////////////////////////////////////////////////////////////////////////////// // private functions //////////////////////////////////////////////////////////////////////////////// void KTreeViewSearchLine::rowsInserted(const QModelIndex & parent, int start, int end) const { d->rowsInserted(parent, start, end); } void KTreeViewSearchLine::treeViewDeleted( QObject *treeView ) { d->treeViewDeleted( treeView ); } //////////////////////////////////////////////////////////////////////////////// // KTreeViewSearchLineWidget //////////////////////////////////////////////////////////////////////////////// class KTreeViewSearchLineWidget::Private { public: Private() : treeView( nullptr ), searchLine( nullptr ) { } QTreeView *treeView; KTreeViewSearchLine *searchLine; }; KTreeViewSearchLineWidget::KTreeViewSearchLineWidget( QWidget *parent, QTreeView *treeView ) : QWidget( parent ), d( new Private ) { d->treeView = treeView; QTimer::singleShot( 0, this, &KTreeViewSearchLineWidget::createWidgets ); } KTreeViewSearchLineWidget::~KTreeViewSearchLineWidget() { delete d; } KTreeViewSearchLine *KTreeViewSearchLineWidget::createSearchLine( QTreeView *treeView ) const { return new KTreeViewSearchLine( const_cast(this), treeView ); } void KTreeViewSearchLineWidget::createWidgets() { QLabel *label = new QLabel( i18n("S&earch:"), this ); label->setObjectName( QStringLiteral("kde toolbar widget") ); searchLine()->show(); label->setBuddy( d->searchLine ); label->show(); QHBoxLayout* layout = new QHBoxLayout( this ); layout->setSpacing( 5 ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->addWidget( label ); layout->addWidget( d->searchLine ); } KTreeViewSearchLine *KTreeViewSearchLineWidget::searchLine() const { if ( !d->searchLine ) d->searchLine = createSearchLine( d->treeView ); return d->searchLine; } #include "moc_ktreeviewsearchline.cpp"