Commit 1c110043 authored by Robert Knight's avatar Robert Knight

* Fix Match Case / Match RegExp / Highlight Matches boxes in the search bar

* Highlight matching text in the active session when the text in the search bar changes,
  match case, match regexp options work as well.
* Ensure that newly created session views are given the focus
* Remember visibility of search bar for each session and hide/show the bar as appropriate
  when switching between sessions.
* Fix incorrect use of delete[] in emulation ( memory allocated with malloc() , so free() must be used )
* Treat empty regular expressions as non-matching in RegExpFilter ( fix infinite recursion )
* Prevent overdrawing of adjacent hotspots 
* Added some experimental / non-working code to do with scrolling in the displays
  This breaks the scroll-bar and causes crashes at the moment.  It needs
  to be re-worked.


svn path=/branches/work/konsole-split-view/; revision=637999
parent 8e6b1b1b
......@@ -149,6 +149,7 @@ void Filter::getLineColumn(int position , int& startLine , int& startColumn)
{
for (int i = 0 ; i < _linePositions.count() ; i++)
{
//qDebug() << "line position at " << i << " = " << _linePositions[i];
int nextLine = 0;
if ( i == _linePositions.count()-1 )
......@@ -160,7 +161,7 @@ void Filter::getLineColumn(int position , int& startLine , int& startColumn)
nextLine = _linePositions[i+1];
}
if ( _linePositions[i] <= position && position <= nextLine )
if ( _linePositions[i] <= position && position < nextLine )
{
startLine = i;
startColumn = position - _linePositions[i];
......@@ -296,6 +297,10 @@ void RegExpFilter::process()
int pos = 0;
QString& text = buffer();
// empty regexp does not match
if ( _searchText.isEmpty() )
return;
while(pos >= 0)
{
pos = _searchText.indexIn(text,pos);
......@@ -308,15 +313,21 @@ void RegExpFilter::process()
int startColumn = 0;
int endColumn = 0;
//qDebug() << "pos from " << pos << " to " << pos + _searchText.matchedLength();
getLineColumn(pos,startLine,startColumn);
getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn);
//qDebug() << "start " << startLine << " / " << startColumn;
//qDebug() << "end " << endLine << " / " << endColumn;
RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn,
endLine,endColumn);
spot->setCapturedTexts(_searchText.capturedTexts());
addHotSpot( spot );
pos += _searchText.matchedLength();
}
}
}
......
......@@ -36,9 +36,9 @@
IncrementalSearchBar::IncrementalSearchBar(Features features , QWidget* parent)
: QWidget(parent)
, _foundMatch(false)
, _matchCase(false)
, _matchRegExp(false)
, _highlightMatches(true)
, _matchCaseBox(0)
, _matchRegExpBox(0)
, _highlightBox(0)
, _searchEdit(0)
, _continueLabel(0)
{
......@@ -85,34 +85,31 @@ IncrementalSearchBar::IncrementalSearchBar(Features features , QWidget* parent)
findPrev->setToolTip("Find the previous match for the current search phrase");
connect( findPrev , SIGNAL(clicked()) , this , SIGNAL(findPreviousClicked()) );
QCheckBox* highlightMatches = 0;
if ( features & HighlightMatches )
{
highlightMatches = new QCheckBox( i18n("Highlight Matches") , this );
highlightMatches->setObjectName("highlight-matches-box");
highlightMatches->setToolTip("Sets whether matching text should be highlighted");
highlightMatches->setChecked(_highlightMatches);
connect( highlightMatches , SIGNAL(toggled(bool)) , this ,
_highlightBox = new QCheckBox( i18n("Highlight Matches") , this );
_highlightBox->setObjectName("highlight-matches-box");
_highlightBox->setToolTip("Sets whether matching text should be highlighted");
_highlightBox->setChecked(true);
connect( _highlightBox , SIGNAL(toggled(bool)) , this ,
SIGNAL(highlightMatchesToggled(bool)) );
}
QCheckBox* matchCase = 0;
if ( features & MatchCase )
{
matchCase = new QCheckBox( i18n("Match Case") , this );
matchCase->setObjectName("match-case-box");
matchCase->setToolTip("Sets whether the searching is case sensitive");
connect( matchCase , SIGNAL(toggled(bool)) , this , SIGNAL(matchCaseToggled(bool)) );
_matchCaseBox = new QCheckBox( i18n("Match Case") , this );
_matchCaseBox->setObjectName("match-case-box");
_matchCaseBox->setToolTip("Sets whether the searching is case sensitive");
connect( _matchCaseBox , SIGNAL(toggled(bool)) , this , SIGNAL(matchCaseToggled(bool)) );
}
QCheckBox* matchRegExp = 0;
if ( features & RegExp )
{
matchRegExp = new QCheckBox( i18n("Match Regular Expression") , this );
matchRegExp->setObjectName("match-regexp-box");
matchRegExp->setToolTip("Sets whether the search phrase is interpreted as normal text or"
_matchRegExpBox = new QCheckBox( i18n("Match Regular Expression") , this );
_matchRegExpBox->setObjectName("match-regexp-box");
_matchRegExpBox->setToolTip("Sets whether the search phrase is interpreted as normal text or"
" as a regular expression");
connect( matchRegExp , SIGNAL(toggled(bool)) , this , SIGNAL(matchRegExpToggled(bool)) );
connect( _matchRegExpBox , SIGNAL(toggled(bool)) , this , SIGNAL(matchRegExpToggled(bool)) );
}
QProgressBar* _progress = new QProgressBar(this);
......@@ -130,9 +127,9 @@ IncrementalSearchBar::IncrementalSearchBar(Features features , QWidget* parent)
layout->addWidget(findPrev);
// optional features
if ( features & HighlightMatches ) layout->addWidget(highlightMatches);
if ( features & MatchCase ) layout->addWidget(matchCase);
if ( features & RegExp ) layout->addWidget(matchRegExp);
if ( features & HighlightMatches ) layout->addWidget(_highlightBox);
if ( features & MatchCase ) layout->addWidget(_matchCaseBox);
if ( features & RegExp ) layout->addWidget(_matchRegExpBox);
layout->addWidget(_progress);
layout->addWidget(_continueLabel);
......@@ -149,15 +146,36 @@ QString IncrementalSearchBar::searchText()
}
bool IncrementalSearchBar::highlightMatches()
{
return _highlightMatches;
if ( !_highlightBox )
{
return true;
}
else
{
return _highlightBox->isChecked();
}
}
bool IncrementalSearchBar::matchCase()
{
return _matchCase;
if ( !_matchCaseBox )
{
return false;
}
else
{
return _matchCaseBox->isChecked();
}
}
bool IncrementalSearchBar::matchRegExp()
{
return _matchRegExp;
if ( !_matchRegExpBox )
{
return false;
}
else
{
return _matchRegExpBox->isChecked();
}
}
bool IncrementalSearchBar::eventFilter(QObject* watched , QEvent* event)
......@@ -190,7 +208,7 @@ void IncrementalSearchBar::setFoundMatch( bool match )
{
//FIXME - Hard coded colour used here - is there a better alternative?
if ( !match )
if ( !match && !_searchEdit->text().isEmpty() )
{
_searchEdit->setStyleSheet( "QLineEdit{ background-color: #FF7777 }" );
}
......
......@@ -23,6 +23,7 @@
// Qt
#include <QWidget>
class QCheckBox;
class QLabel;
class QLineEdit;
class QProgressBar;
......@@ -103,6 +104,13 @@ public:
/**
* Sets an indicator for the user as to whether or not a match for the
* current search text was found in the document.
*
* The indicator will not be shown if the search text is empty ( because
* the user has not yet entered a query ).
*
* @param match True if a match was found or false otherwise. If true,
* and the search text is non-empty, an indicator that no matches were
* found will be shown.
*/
void setFoundMatch( bool match );
......@@ -116,11 +124,20 @@ public:
/** Returns the current search text */
QString searchText();
/** Returns whether matches for the current search text should be highlighted in the document */
/**
* Returns whether matches for the current search text should be highlighted in the document.
* Always returns true if the highlight matches checkbox is not visible.
*/
bool highlightMatches();
/** Returns whether matching for the current search text should be case sensitive */
/**
* Returns whether matching for the current search text should be case sensitive.
* Always returns false if the match case checkbox is not visible.
*/
bool matchCase();
/** Returns whether the current search text should be treated as plain text or a regular expression */
/**
* Returns whether the current search text should be treated as plain text or a regular expression
* Always returns false if the match regular expression checkbox is not visible.
*/
bool matchRegExp();
signals:
......@@ -154,9 +171,9 @@ protected:
private:
bool _foundMatch;
bool _matchCase;
bool _matchRegExp;
bool _highlightMatches;
QCheckBox* _matchCaseBox;
QCheckBox* _matchRegExpBox;
QCheckBox* _highlightBox;
QLineEdit* _searchEdit;
QLabel* _continueLabel;
......
......@@ -39,7 +39,8 @@ SessionController::SessionController(TESession* session , TEWidget* view, QObjec
, _view(view)
, _previousState(-1)
, _viewUrlFilter(0)
, _historyToggleAction(0)
, _searchFilter(0)
, _searchToggleAction(0)
{
// handle user interface related to session (menus etc.)
setXMLFile("sessionui.rc");
......@@ -104,18 +105,40 @@ bool SessionController::eventFilter(QObject* watched , QEvent* event)
return false;
}
void SessionController::removeSearchFilter()
{
if (!_searchFilter)
return;
_view->filterChain()->removeFilter(_searchFilter);
delete _searchFilter;
_searchFilter = 0;
}
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
{
// disconnect the existing search bar
if ( _searchBar )
{
disconnect( this , 0 , _searchBar , 0 );
disconnect( _searchBar , 0 , this , 0 );
}
_searchBar = searchBar;
// remove any existing search filter
removeSearchFilter();
connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
// connect new search bar
_searchBar = searchBar;
if ( _searchBar )
{
connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
// if the search bar was previously active
// then re-enter search mode
searchHistory( _searchToggleAction->isChecked() );
}
}
IncrementalSearchBar* SessionController::searchBar() const
{
......@@ -169,9 +192,9 @@ void SessionController::setupActions()
connect( action , SIGNAL(toggled(bool)) , this , SLOT(monitorSilence(bool)) );
// History
_historyToggleAction = new KToggleAction(i18n("Search History"),this);
_historyToggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_F) );
action = collection->addAction("search-history" , _historyToggleAction);
_searchToggleAction = new KToggleAction(i18n("Search History"),this);
_searchToggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_F) );
action = collection->addAction("search-history" , _searchToggleAction);
connect( action , SIGNAL(toggled(bool)) , this , SLOT(searchHistory(bool)) );
action = collection->addAction("find-next");
......@@ -275,20 +298,73 @@ void SessionController::clearAndReset()
}
void SessionController::searchClosed()
{
_historyToggleAction->setChecked(false);
_searchToggleAction->setChecked(false);
}
// searchHistory() may be called either as a result of clicking a menu item or
// as a result of changing the search bar widget
void SessionController::searchHistory(bool showSearchBar)
{
if ( _searchBar )
{
_searchBar->setVisible(showSearchBar);
if (!showSearchBar)
if (showSearchBar)
{
Q_ASSERT( !_searchFilter );
_searchFilter = new RegExpFilter();
_view->filterChain()->addFilter(_searchFilter);
connect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
SLOT(searchTextChanged(const QString&)) );
// invoke search for matches for the current search text
const QString& currentSearchText = _searchBar->searchText();
if (!currentSearchText.isEmpty())
{
searchTextChanged(currentSearchText);
}
//SessionTask* task = new SearchHistoryTask();
//task->setAutoDelete(true);
//task->addSession( _session );
//task->execute();
}
else
{
disconnect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
SLOT(searchTextChanged(const QString&)) );
removeSearchFilter();
_view->setFocus( Qt::ActiveWindowFocusReason );
}
}
}
void SessionController::searchTextChanged(const QString& text)
{
Q_ASSERT( _searchBar );
Qt::CaseSensitivity caseHandling = _searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive;
QRegExp::PatternSyntax syntax = _searchBar->matchRegExp() ? QRegExp::RegExp : QRegExp::FixedString;
QRegExp regExp( text.trimmed() , caseHandling , syntax );
_searchFilter->setRegExp(regExp);
_view->processFilters();
// color search bar to indicate whether a match was found
if ( _searchFilter->hotSpots().count() > 0 )
{
_searchBar->setFoundMatch(true);
}
else
{
_searchBar->setFoundMatch(false);
}
// TODO - Optimise by only updating affected regions
_view->update();
}
void SessionController::findNextInHistory()
{
qDebug() << "find next";
......
......@@ -22,6 +22,7 @@ class TESession;
class TEWidget;
class IncrementalSearchBar;
class UrlFilter;
class RegExpFilter;
// SaveHistoryTask
class TerminalCharacterDecoder;
......@@ -115,6 +116,7 @@ private slots:
// other
void sessionStateChanged(TESession* session,int state);
void sessionTitleChanged();
void searchTextChanged(const QString& text);
void searchClosed(); // called when the user clicks on the
// history search bar's close button
......@@ -122,6 +124,7 @@ private slots:
void debugProcess();
private:
void setupActions();
void removeSearchFilter(); // remove and delete the current search filter if set
private:
TESession* _session;
......@@ -131,8 +134,9 @@ private:
int _previousState;
UrlFilter* _viewUrlFilter;
RegExpFilter* _searchFilter;
KToggleAction* _historyToggleAction;
KToggleAction* _searchToggleAction;
static KIcon _activityIcon;
static KIcon _silenceIcon;
......@@ -263,6 +267,7 @@ public:
virtual void execute();
signals:
/**
* Emitted when a match for the regular expression is found in a session's output.
......@@ -276,9 +281,6 @@ signals:
*/
void foundMatch(TESession* session , int startLine , int startColumn , int endLine , int endColumn );
private:
private:
QRegExp _regExp;
};
......
......@@ -546,29 +546,31 @@ void TEScreen::effectiveRendition()
*/
ca* TEScreen::getCookedImage()
ca* TEScreen::getCookedImage(const ScreenCursor& viewCursor)
{
const int viewHistoryCursor = viewCursor.cursor();
/*kDebug() << "sel_begin=" << sel_begin << "(" << sel_begin/columns << "," << sel_begin%columns << ")"
<< " sel_TL=" << sel_TL << "(" << sel_TL/columns << "," << sel_TL%columns << ")"
<< " sel_BR=" << sel_BR << "(" << sel_BR/columns << "," << sel_BR%columns << ")"
<< " histcursor=" << histCursor << endl;*/
<< " histcursor=" << viewHistoryCursor << endl;*/
int x,y;
ca* merged = (ca*)malloc((lines*columns+1)*sizeof(ca));
merged[lines*columns] = defaultChar;
for (y = 0; (y < lines) && (y < (hist->getLines()-histCursor)); y++)
for (y = 0; (y < lines) && (y < (hist->getLines()-viewHistoryCursor)); y++)
{
int len = qMin(columns,hist->getLineLen(y+histCursor));
int len = qMin(columns,hist->getLineLen(y+viewHistoryCursor));
int yp = y*columns;
hist->getCells(y+histCursor,0,len,merged+yp);
hist->getCells(y+viewHistoryCursor,0,len,merged+yp);
for (x = len; x < columns; x++) merged[yp+x] = defaultChar;
if (sel_begin !=-1)
for (x = 0; x < columns; x++)
{
#ifdef REVERSE_WRAPPED_LINES
if (hist->isLINE_WRAPPED(y+histCursor))
if (hist->isLINE_WRAPPED(y+viewHistoryCursor))
reverseRendition(&merged[p]);
#endif
if (isSelected(x,y)) {
......@@ -577,19 +579,23 @@ ca* TEScreen::getCookedImage()
}
}
}
if (lines >= hist->getLines()-histCursor)
if (lines >= hist->getLines()-viewHistoryCursor)
{
for (y = (hist->getLines()-histCursor); y < lines ; y++)
for (y = (hist->getLines()-viewHistoryCursor); y < lines ; y++)
{
int yp = y*columns;
int yr = (y-hist->getLines()+histCursor)*columns;
int yr = (y-hist->getLines()+viewHistoryCursor)*columns;
for (x = 0; x < columns; x++)
{ int p = x + yp; int r = x + yr;
// sanity checks
assert( p >= 0 );
assert( p < (lines*columns+1) );
merged[p] = screenLines[r/columns].value(r%columns,defaultChar);
#ifdef REVERSE_WRAPPED_LINES
if (lineProperties[y- hist->getLines() +histCursor] & LINE_WRAPPED)
if (lineProperties[y- hist->getLines() +viewHistoryCursor] & LINE_WRAPPED)
reverseRendition(&merged[p]);
#endif
if (sel_begin != -1 && isSelected(x,y))
......@@ -604,32 +610,34 @@ ca* TEScreen::getCookedImage()
for (int i = 0; i < lines*columns; i++)
reverseRendition(&merged[i]); // for reverse display
}
// if (getMode(MODE_Cursor) && (cuY+(hist->getLines()-histCursor) < lines)) // cursor visible
// if (getMode(MODE_Cursor) && (cuY+(hist->getLines()-viewHistoryCursor) < lines)) // cursor visible
int loc_ = loc(cuX, cuY+hist->getLines()-histCursor);
int loc_ = loc(cuX, cuY+hist->getLines()-viewHistoryCursor);
if(getMode(MODE_Cursor) && loc_ < columns*lines)
merged[loc(cuX,cuY+(hist->getLines()-histCursor))].r|=RE_CURSOR;
merged[loc(cuX,cuY+(hist->getLines()-viewHistoryCursor))].r|=RE_CURSOR;
return merged;
}
QVector<LineProperty> TEScreen::getCookedLineProperties()
QVector<LineProperty> TEScreen::getCookedLineProperties(const ScreenCursor& viewCursor)
{
const int viewHistoryCursor = viewCursor.cursor();
QVector<LineProperty> result(lines);
for (int y = 0; (y < lines) && (y < (hist->getLines()-histCursor)); y++)
for (int y = 0; (y < lines) && (y < (hist->getLines()-viewHistoryCursor)); y++)
{
//TODO Support for line properties other than wrapped lines
//result[y]=hist->isLINE_WRAPPED(y+histCursor);
//result[y]=hist->isLINE_WRAPPED(y+viewHistoryCursor);
if (hist->isWrappedLine(y+histCursor))
if (hist->isWrappedLine(y+viewHistoryCursor))
{
result[y] = result[y] | LINE_WRAPPED;
}
}
if (lines >= hist->getLines()-histCursor)
for (int y = (hist->getLines()-histCursor); y < lines ; y++)
result[y]=lineProperties[y- hist->getLines() +histCursor];
if (lines >= hist->getLines()-viewHistoryCursor)
for (int y = (hist->getLines()-viewHistoryCursor); y < lines ; y++)
result[y]=lineProperties[y- hist->getLines() +viewHistoryCursor];
return result;
}
......@@ -1229,7 +1237,7 @@ void TEScreen::clearSelection()
sel_begin = -1;
}
void TEScreen::setSelectionStart(const int x, const int y, const bool mode)
void TEScreen::setSelectionStart(/*const ScreenCursor& viewCursor ,*/ const int x, const int y, const bool mode)
{
// kDebug(1211) << "setSelBeginXY(" << x << "," << y << ")" << endl;
sel_begin = loc(x,y+histCursor) ;
......@@ -1242,7 +1250,7 @@ void TEScreen::setSelectionStart(const int x, const int y, const bool mode)
columnmode = mode;
}
void TEScreen::setSelectionEnd(const int x, const int y)
void TEScreen::setSelectionEnd(/*const ScreenCursor& viewCursor ,*/ const int x, const int y)
{
// kDebug(1211) << "setSelExtentXY(" << x << "," << y << ")" << endl;
if (sel_begin == -1) return;
......@@ -1263,7 +1271,7 @@ void TEScreen::setSelectionEnd(const int x, const int y)
}
}
bool TEScreen::isSelected(const int x,const int y)
bool TEScreen::isSelected(/* const ScreenCursor& viewCursor ,*/ const int x,const int y)
{
if (columnmode) {
int sel_Left,sel_Right;
......@@ -1411,12 +1419,10 @@ void TEScreen::copyLineToStream(int line , int start, int count,
ca* data = screenLines[line-hist->getLines()].data();
int length = screenLines[line-hist->getLines()].count();
count = qMin(count,length);
//retrieve line from screen image
for (int i=0;i < count;i++)
for (int i=start;i < qMin(start+count,length);i++)
{
characterBuffer[i] = data[start+i];
characterBuffer[i-start] = data[i];
}
}
......@@ -1431,7 +1437,11 @@ void TEScreen::copyLineToStream(int line , int start, int count,
decoder->decodeLine( (ca*) characterBuffer , count, 0 , stream);
}
void TEScreen::writeToStream(QTextStream* stream , TerminalCharacterDecoder* decoder) {
// Method below has been removed because of its reliance on 'histCursor'
// and I want to restrict the methods which have knowledge of the scroll position
// to just those which deal with selection and supplying final screen images.
//
/*void TEScreen::writeToStream(QTextStream* stream , TerminalCharacterDecoder* decoder) {
sel_begin = 0;
sel_BR = sel_begin;
sel_TL = sel_begin;
......@@ -1440,7 +1450,7 @@ void TEScreen::writeToStream(QTextStream* stream , TerminalCharacterDecoder* dec
writeSelectionToStream(stream,decoder);
clearSelection();
}
}*/
void TEScreen::writeToStream(QTextStream* stream, TerminalCharacterDecoder* decoder, int from, int to)
{
......@@ -1573,3 +1583,30 @@ void TEScreen::setLineProperty(LineProperty property , bool enable)
lineProperties[cuY] &= ~property;
}
}
ScreenCursor::ScreenCursor()
: _cursor(0)
{
}
void ScreenCursor::setCursor(int line)
{
if ( line > 0 )
_cursor = line;
}
int ScreenCursor::cursor() const
{
return _cursor;
}
bool ScreenCursor::atEnd() const
{
// not implemented yet
assert(0);
}
bool ScreenCursor::operator==(const ScreenCursor& other) const
{
return other.cursor() == cursor();
}
......@@ -932,6 +932,13 @@ void TEWidget::scrollImage(int lines , const QRect& /*region*/)
scroll( 0 , font_h * (-lines) , scrollRect );
}
void TEWidget::processFilters()
{
_filterChain->reset();
_filterChain->addImage(image,lines,columns);
_filterChain->process();
}