00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "khtmlfind_p.h"
00023
00024 #include "khtml_part.h"
00025 #include "khtmlviewbar.h"
00026 #include "khtmlfindbar.h"
00027
00028 #include "dom/html_document.h"
00029 #include "html/html_documentimpl.h"
00030 #include "rendering/render_text.h"
00031 #include "rendering/render_replaced.h"
00032 #include "misc/htmlhashes.h"
00033 #include "xml/dom_selection.h"
00034
00035 #include "khtmlview.h"
00036
00037 #include <config.h>
00038
00039 #include <QtGui/QClipboard>
00040
00041 #include "rendering/render_form.h"
00042
00043 #define d this
00044
00045 using namespace DOM;
00046
00047 KHTMLFind::KHTMLFind( KHTMLPart *part, KHTMLFind *parent ) :
00048 m_part( part ),
00049 m_find( 0 ),
00050 m_parent( parent ),
00051 m_findDialog( 0 )
00052 {
00053 connect( part, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()) );
00054 }
00055
00056
00057 KHTMLFind::~KHTMLFind()
00058 {
00059 d->m_find = 0;
00060 }
00061
00062 void KHTMLFind::findTextBegin()
00063 {
00064 d->m_findPos = -1;
00065 d->m_findNode = 0;
00066 d->m_findPosEnd = -1;
00067 d->m_findNodeEnd= 0;
00068 d->m_findPosStart = -1;
00069 d->m_findNodeStart = 0;
00070 d->m_findNodePrevious = 0;
00071 delete d->m_find;
00072 d->m_find = 0L;
00073 }
00074
00075 bool KHTMLFind::initFindNode( bool selection, bool reverse, bool fromCursor )
00076 {
00077 if ( m_part->document().isNull() )
00078 return false;
00079
00080 DOM::NodeImpl* firstNode = 0L;
00081 if (m_part->document().isHTMLDocument())
00082 firstNode = m_part->htmlDocument().body().handle();
00083 else
00084 firstNode = m_part->document().handle();
00085
00086 if ( !firstNode )
00087 {
00088
00089 return false;
00090 }
00091 if ( selection && m_part->hasSelection() )
00092 {
00093
00094 const Selection &sel = m_part->caret();
00095 if ( !fromCursor )
00096 {
00097 d->m_findNode = reverse ? sel.end().node() : sel.start().node();
00098 d->m_findPos = reverse ? sel.end().offset() : sel.start().offset();
00099 }
00100 d->m_findNodeEnd = reverse ? sel.start().node() : sel.end().node();
00101 d->m_findPosEnd = reverse ? sel.start().offset() : sel.end().offset();
00102 d->m_findNodeStart = !reverse ? sel.start().node() : sel.end().node();
00103 d->m_findPosStart = !reverse ? sel.start().offset() : sel.end().offset();
00104 d->m_findNodePrevious = d->m_findNodeStart;
00105 }
00106 else
00107 {
00108
00109 if ( !fromCursor )
00110 {
00111 d->m_findNode = firstNode;
00112 d->m_findPos = reverse ? -1 : 0;
00113 }
00114 d->m_findNodeEnd = reverse ? firstNode : 0;
00115 d->m_findPosEnd = reverse ? 0 : -1;
00116 d->m_findNodeStart = !reverse ? firstNode : 0;
00117 d->m_findPosStart = !reverse ? 0 : -1;
00118 d->m_findNodePrevious = d->m_findNodeStart;
00119 if ( reverse )
00120 {
00121
00122 khtml::RenderObject* obj = d->m_findNode ? d->m_findNode->renderer() : 0;
00123 if ( obj )
00124 {
00125
00126 while ( obj->lastChild() )
00127 {
00128 obj = obj->lastChild();
00129 }
00130
00131 while ( !obj->element() && obj->objectAbove() )
00132 {
00133 obj = obj->objectAbove();
00134 }
00135 d->m_findNode = obj->element();
00136 }
00137 }
00138 }
00139 return true;
00140 }
00141
00142 void KHTMLFind::deactivate()
00143 {
00144 kDebug(6050);
00145 d->m_lastFindState.options = d->m_findDialog->options();
00146 d->m_lastFindState.history = d->m_findDialog->findHistory();
00147 if (!m_parent) {
00148 d->m_findDialog->hide();
00149 d->m_findDialog->disconnect();
00150 d->m_findDialog->deleteLater();
00151 }
00152 d->m_findDialog = 0L;
00153
00154
00155 const DOM::Selection sel = m_part->caret();
00156 if(sel.start().node() == sel.end().node())
00157 {
00158 bool isLink = false;
00159
00160
00161 DOM::NodeImpl *parent = sel.start().node();
00162 while ( parent )
00163 {
00164 if ( parent->nodeType() == Node::ELEMENT_NODE && parent->id() == ID_A )
00165 {
00166 isLink = true;
00167 break;
00168 }
00169 parent = parent->parentNode();
00170 }
00171
00172 if(isLink == true)
00173 {
00174 static_cast<DOM::DocumentImpl *>( m_part->document().handle() )->setFocusNode( parent );
00175 }
00176 }
00177 }
00178
00179 void KHTMLFind::slotFindDestroyed()
00180 {
00181 d->m_find = 0;
00182 }
00183
00184 void KHTMLFind::activate()
00185 {
00186
00187 if ( m_part->document().isNull() )
00188 return;
00189
00190
00191 if ( d->m_findDialog && !m_parent )
00192 {
00193 m_part->pBottomViewBar()->showBarWidget( d->m_findDialog );
00194 return;
00195 }
00196
00197
00198 #ifndef QT_NO_CLIPBOARD
00199 disconnect( qApp->clipboard(), SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection()) );
00200 #endif
00201
00202 if (m_parent)
00203 d->m_findDialog = m_parent->findBar();
00204 else
00205 {
00206
00207 d->m_findDialog = new KHTMLFindBar( m_part->widget() );
00208 d->m_findDialog->setHasSelection( m_part->hasSelection() );
00209 d->m_findDialog->setHasCursor( d->m_findNode != 0 );
00210 #if 0
00211 if ( d->m_findNode )
00212 d->m_lastFindState.options |= KFind::FromCursor;
00213 #endif
00214
00215
00216 d->m_findDialog->setFindHistory( d->m_lastFindState.history );
00217 d->m_findDialog->setOptions( d->m_lastFindState.options );
00218 d->m_findDialog->setFocus();
00219
00220 d->m_lastFindState.options = -1;
00221 d->m_lastFindState.last_dir = -1;
00222
00223 m_part->pBottomViewBar()->addBarWidget( d->m_findDialog );
00224 m_part->pBottomViewBar()->showBarWidget( d->m_findDialog );
00225 connect( d->m_findDialog, SIGNAL(searchChanged()), this, SLOT(slotSearchChanged()) );
00226 connect( d->m_findDialog, SIGNAL(findNextClicked()), this, SLOT(slotFindNext()) );
00227 connect( d->m_findDialog, SIGNAL(findPreviousClicked()), this, SLOT(slotFindPrevious()) );
00228 connect( d->m_findDialog, SIGNAL(hideMe()), this, SLOT(deactivate()) );
00229 }
00230 #ifndef QT_NO_CLIPBOARD
00231 connect( qApp->clipboard(), SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection()) );
00232 #endif
00233 if (m_findDialog) {
00234 createNewKFind( m_findDialog->pattern() , 0 , m_findDialog, 0 );
00235 } else if (m_parent && m_parent->find()) {
00236 createNewKFind( m_parent->find()->pattern(), m_parent->find()->options(), static_cast<QWidget*>(m_parent->find()->parent()), 0 );
00237 }
00238 }
00239
00240
00241
00242 static inline KHTMLPart* innerPart( khtml::RenderObject *ro ) {
00243 if (!ro || !ro->isWidget() || ro->isFormElement())
00244 return 0;
00245 KHTMLView* v = qobject_cast<KHTMLView*>( static_cast<khtml::RenderWidget*>(ro)->widget() );
00246 return v ? v->part() : 0;
00247 }
00248 static inline KHTMLPart* innerPartFromNode( DOM::NodeImpl *node ) {
00249 return (node && node->renderer() ? innerPart( node->renderer() ) : 0);
00250 }
00251
00252 void KHTMLFind::createNewKFind( const QString &str, long options, QWidget *parent, KFindDialog *findDialog )
00253 {
00254
00255 if ( m_part->document().isNull() )
00256 return;
00257
00258 if (m_findNode) {
00259 if (KHTMLPart* p = innerPartFromNode(m_findNode)) {
00260 p->clearSelection();
00261 p->findTextBegin();
00262 }
00263 }
00264
00265
00266 delete d->m_find;
00267 d->m_find = new KFind( str, options, parent, findDialog );
00268 d->m_find->closeFindNextDialog();
00269 connect( d->m_find, SIGNAL( highlight( const QString &, int, int ) ),
00270 this, SLOT( slotHighlight( const QString &, int, int ) ) );
00271 connect( d->m_find, SIGNAL( destroyed() ),
00272 this, SLOT( slotFindDestroyed() ) );
00273
00274
00275
00276 if ( !findDialog )
00277 {
00278 d->m_lastFindState.options = options;
00279 initFindNode( options & KFind::SelectedText,
00280 options & KFind::FindBackwards,
00281 options & KFind::FromCursor );
00282 }
00283 }
00284
00285 bool KHTMLFind::findTextNext( bool reverse )
00286 {
00287 if (!d->m_find)
00288 {
00289
00290 activate();
00291
00292
00293 if (!d->m_find)
00294 return false;
00295
00296
00297
00298 if (!m_parent && (!d->m_findDialog || !d->m_findDialog->restoreLastPatternFromHistory()))
00299 return false;
00300 }
00301
00302 m_part->view()->updateFindAheadTimeout();
00303 long options = 0;
00304 if ( d->m_findDialog )
00305 {
00306
00307
00308
00309
00310 if ( (d->m_find->pattern() != d->m_findDialog->pattern()) ) {
00311 d->m_find->setPattern( d->m_findDialog->pattern() );
00312 d->m_find->resetCounts();
00313 }
00314
00315
00316 options = d->m_findDialog->options();
00317 if ( d->m_lastFindState.options != options )
00318 {
00319 d->m_find->setOptions( options );
00320
00321 if ( options & KFind::SelectedText )
00322 Q_ASSERT( m_part->hasSelection() );
00323
00324 long difference = d->m_lastFindState.options ^ options;
00325 if ( difference & (KFind::SelectedText | KFind::FromCursor ) )
00326 {
00327
00328 (void) initFindNode( options & KFind::SelectedText,
00329 options & KFind::FindBackwards,
00330 options & KFind::FromCursor );
00331 }
00332 d->m_lastFindState.options = options;
00333 }
00334 } else {
00335
00336 options = d->m_lastFindState.options;
00337 }
00338
00339
00340 if( reverse )
00341 options = options ^ KFind::FindBackwards;
00342
00343
00344 if( d->m_find->options() != options )
00345 d->m_find->setOptions( options );
00346
00347
00348
00349
00350 if( d->m_lastFindState.last_dir != -1
00351 && bool( d->m_lastFindState.last_dir ) != bool( options & KFind::FindBackwards ))
00352 {
00353 qSwap( d->m_findNodeEnd, d->m_findNodeStart );
00354 qSwap( d->m_findPosEnd, d->m_findPosStart );
00355 qSwap( d->m_findNode, d->m_findNodePrevious );
00356
00357
00358 khtml::RenderObject* obj = d->m_findNode ? d->m_findNode->renderer() : 0;
00359 khtml::RenderObject* end = d->m_findNodeEnd ? d->m_findNodeEnd->renderer() : 0;
00360 if ( obj == end )
00361 obj = 0L;
00362 else if ( obj )
00363 {
00364 do {
00365 obj = (options & KFind::FindBackwards) ? obj->objectAbove() : obj->objectBelow();
00366 } while ( obj && ( !obj->element() || obj->isInlineContinuation() ) );
00367 }
00368 if ( obj )
00369 d->m_findNode = obj->element();
00370 else {
00371
00372 (void) initFindNode( options & KFind::SelectedText,
00373 options & KFind::FindBackwards,
00374 options & KFind::FromCursor );
00375 }
00376 }
00377 d->m_lastFindState.last_dir = ( options & KFind::FindBackwards ) ? 1 : 0;
00378
00379 int numMatchesOld = m_find->numMatches();
00380 KFind::Result res = KFind::NoMatch;
00381 khtml::RenderObject* obj = d->m_findNode ? d->m_findNode->renderer() : 0;
00382 khtml::RenderObject* end = d->m_findNodeEnd ? d->m_findNodeEnd->renderer() : 0;
00383
00384 while( res == KFind::NoMatch )
00385 {
00386 if ( d->m_find->needData() )
00387 {
00388 if ( !obj ) {
00389
00390 break;
00391 }
00392
00393
00394
00395
00396
00397 d->m_stringPortions.clear();
00398 bool newLine = false;
00399 QString str;
00400 DOM::NodeImpl* lastNode = d->m_findNode;
00401 while ( obj && !newLine )
00402 {
00403
00404 QString s;
00405 if ( obj->renderName() == QLatin1String("RenderTextArea") )
00406 {
00407 s = static_cast<khtml::RenderTextArea *>(obj)->text();
00408 s = s.replace(0xa0, ' ');
00409 }
00410 else if ( obj->renderName() == QLatin1String("RenderLineEdit") )
00411 {
00412 khtml::RenderLineEdit *parentLine= static_cast<khtml::RenderLineEdit *>(obj);
00413 if (parentLine->widget()->echoMode() == QLineEdit::Normal)
00414 s = parentLine->widget()->text();
00415 s = s.replace(0xa0, ' ');
00416 }
00417 else if ( obj->isText() )
00418 {
00419 bool isLink = false;
00420
00421
00422 if ( options & KHTMLPart::FindLinksOnly )
00423 {
00424 DOM::NodeImpl *parent = obj->element();
00425 while ( parent )
00426 {
00427 if ( parent->nodeType() == Node::ELEMENT_NODE && parent->id() == ID_A )
00428 {
00429 isLink = true;
00430 break;
00431 }
00432 parent = parent->parentNode();
00433 }
00434 }
00435 else
00436 {
00437 isLink = true;
00438 }
00439
00440 if ( isLink )
00441 {
00442 s = static_cast<khtml::RenderText *>(obj)->data().string();
00443 s = s.replace(0xa0, ' ');
00444 }
00445 }
00446 else if ( KHTMLPart *p = innerPart(obj) )
00447 {
00448 if (p->pFindTextNextInThisFrame(reverse))
00449 {
00450 numMatchesOld++;
00451 res = KFind::Match;
00452 lastNode = obj->element();
00453 break;
00454 }
00455
00456 }
00457 else if ( obj->isBR() )
00458 s = '\n';
00459 else if ( !obj->isInline() && !str.isEmpty() )
00460 s = '\n';
00461
00462 if ( lastNode == d->m_findNodeEnd )
00463 s.truncate( d->m_findPosEnd );
00464 if ( !s.isEmpty() )
00465 {
00466 newLine = s.indexOf( '\n' ) != -1;
00467 if( !( options & KFind::FindBackwards ))
00468 {
00469
00470 d->m_stringPortions.append( StringPortion( str.length(), lastNode ) );
00471 str += s;
00472 }
00473 else
00474 {
00475 for( QList<StringPortion>::Iterator it = d->m_stringPortions.begin();
00476 it != d->m_stringPortions.end();
00477 ++it )
00478 (*it).index += s.length();
00479 d->m_stringPortions.prepend( StringPortion( 0, lastNode ) );
00480 str.prepend( s );
00481 }
00482 }
00483
00484 if ( obj == end )
00485 obj = 0L;
00486 else
00487 {
00488
00489
00490 do {
00491
00492
00493
00494 obj = (options & KFind::FindBackwards) ? obj->objectAbove() : obj->objectBelow();
00495 } while ( obj && ( !obj->element() || obj->isInlineContinuation() ) );
00496 }
00497 if ( obj )
00498 lastNode = obj->element();
00499 else
00500 lastNode = 0;
00501 }
00502
00503 if ( !str.isEmpty() )
00504 {
00505 d->m_find->setData( str, d->m_findPos );
00506 }
00507 d->m_findPos = -1;
00508 d->m_findNodePrevious = d->m_findNode;
00509 d->m_findNode = lastNode;
00510 }
00511 if ( !d->m_find->needData() && !(res == KFind::Match) )
00512 {
00513
00514 res = d->m_find->find();
00515 }
00516 }
00517
00518 if ( res == KFind::NoMatch )
00519 {
00520 kDebug(6050) << "No more matches.";
00521 if ( !(options & KHTMLPart::FindNoPopups) && d->m_find->shouldRestart() )
00522 {
00523 kDebug(6050) << "Restarting";
00524 initFindNode( false, options & KFind::FindBackwards, false );
00525 d->m_find->resetCounts();
00526 findTextNext( reverse );
00527 }
00528 else
00529 {
00530 kDebug(6050) << "Finishing";
00531
00532
00533 initFindNode( false, options & KFind::FindBackwards, false );
00534 d->m_find->resetCounts();
00535 d->m_part->clearSelection();
00536 }
00537 kDebug(6050) << "Dialog closed.";
00538 }
00539
00540 if ( m_findDialog != 0 )
00541 {
00542 m_findDialog->setFoundMatch( res == KFind::Match );
00543 m_findDialog->setAtEnd( m_find->numMatches() < numMatchesOld );
00544 }
00545
00546 return res == KFind::Match;
00547 }
00548
00549 void KHTMLFind::slotHighlight( const QString& , int index, int length )
00550 {
00551
00552 QList<StringPortion>::Iterator it = d->m_stringPortions.begin();
00553 const QList<StringPortion>::Iterator itEnd = d->m_stringPortions.end();
00554 QList<StringPortion>::Iterator prev = it;
00555
00556 while ( it != itEnd && (*it).index <= index )
00557 {
00558 prev = it;
00559 ++it;
00560 }
00561 Q_ASSERT ( prev != itEnd );
00562 DOM::NodeImpl* node = (*prev).node;
00563 Q_ASSERT( node );
00564
00565 Selection sel(Position(node, index - (*prev).index));
00566
00567 khtml::RenderObject* obj = node->renderer();
00568 khtml::RenderTextArea *renderTextArea = 0L;
00569 khtml::RenderLineEdit *renderLineEdit = 0L;
00570
00571 Q_ASSERT( obj );
00572 if ( obj )
00573 {
00574 int x = 0, y = 0;
00575
00576 if ( obj->renderName() == QLatin1String("RenderTextArea") )
00577 renderTextArea = static_cast<khtml::RenderTextArea *>(obj);
00578 if ( obj->renderName() == QLatin1String("RenderLineEdit") )
00579 renderLineEdit = static_cast<khtml::RenderLineEdit *>(obj);
00580 if ( !renderLineEdit && !renderTextArea )
00581
00582
00583 {
00584 int dummy;
00585 static_cast<khtml::RenderText *>(node->renderer())
00586 ->caretPos( sel.start().offset(), false, x, y, dummy, dummy );
00587
00588 if ( x != -1 || y != -1 )
00589 {
00590 int gox = m_part->view()->contentsX();
00591 if (x+50 > m_part->view()->contentsX() + m_part->view()->visibleWidth())
00592 gox = x - m_part->view()->visibleWidth() + 50;
00593 if (x-10 < m_part->view()->contentsX())
00594 gox = x - m_part->view()->visibleWidth() - 10;
00595 if (gox < 0) gox = 0;
00596 m_part->view()->setContentsPos(gox, y-50);
00597 }
00598 }
00599 }
00600
00601 it = prev;
00602 while ( it != itEnd && (*it).index < index + length )
00603 {
00604 prev = it;
00605 ++it;
00606 }
00607 Q_ASSERT ( prev != itEnd );
00608
00609 sel.moveTo(sel.start(), Position((*prev).node, index + length - (*prev).index));
00610
00611 #if 0
00612 kDebug(6050) << "slotHighlight: " << d->m_selectionStart.handle() << "," << d->m_startOffset << " - " <<
00613 d->m_selectionEnd.handle() << "," << d->m_endOffset << endl;
00614 it = d->m_stringPortions.begin();
00615 for ( ; it != d->m_stringPortions.end() ; ++it )
00616 kDebug(6050) << " StringPortion: from index=" << (*it).index << " -> node=" << (*it).node;
00617 #endif
00618 if ( renderTextArea )
00619 renderTextArea->highLightWord( length, sel.end().offset()-length );
00620 else if ( renderLineEdit )
00621 renderLineEdit->highLightWord( length, sel.end().offset()-length );
00622 else
00623 {
00624 m_part->setCaret( sel );
00625
00626 if (sel.end().node()->renderer() )
00627 {
00628 int x, y, height, dummy;
00629 static_cast<khtml::RenderText *>(sel.end().node()->renderer())
00630 ->caretPos( sel.end().offset(), false, x, y, dummy, height );
00631
00632 }
00633 }
00634 m_part->emitSelectionChanged();
00635
00636 }
00637
00638 void KHTMLFind::slotSelectionChanged()
00639 {
00640 if ( d->m_findDialog )
00641 d->m_findDialog->setHasSelection( m_part->hasSelection() );
00642 }
00643
00644 void KHTMLFind::slotSearchChanged()
00645 {
00646 createNewKFind( m_findDialog->pattern(), m_findDialog->options(), m_findDialog, 0 );
00647 findTextNext();
00648 }
00649
00650 void KHTMLFind::slotFindNext()
00651 {
00652 findTextNext();
00653 }
00654
00655 void KHTMLFind::slotFindPrevious()
00656 {
00657 findTextNext( true );
00658 }