Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36276030
kdlogtextwidget.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
15 KB
Subscribers
None
kdlogtextwidget.cpp
View Options
/****************************************************************************
** Copyright (C) 2001-2007 Klarälvdalens Datakonsult AB. All rights reserved.
**
** This file is part of the KD Tools library.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid commercial KD Tools licenses may use this file in
** accordance with the KD Tools Commercial License Agreement provided with
** the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@klaralvdalens-datakonsult.se if any conditions of this
** licensing are not clear to you.
**
**********************************************************************/
#include
<config-kleopatra.h>
#include
"kdlogtextwidget.h"
#include
<QtCore/QBasicTimer>
#include
<QtCore/QHash>
#include
<QtGui/QPainter>
#include
<QtGui/QPaintEvent>
#include
<QtGui/QScrollBar>
#include
<cassert>
#include
<algorithm>
#include
<iterator>
/*!
\class KDLogTextWidget
\brief A high-speed text display widget.
This widget provides very fast display of large amounts of
line-oriented text, as commonly found in application log
viewers. The feature set and implementation are optimized for
frequent appends.
You can set initial text using setLines(), and append lines with
calls to message(). You can limit the number of lines kept in the
view using setHistorySize().
Text formatting is currently limited to per-line text color, but is
expected to be enhanced on client request in upcoming versions. You
can pass the color to use to calls to message().
*/
class
KDLogTextWidget
::
Private
{
friend
class
::
KDLogTextWidget
;
KDLogTextWidget
*
const
q
;
public
:
explicit
Private
(
KDLogTextWidget
*
qq
);
~
Private
();
void
updateCache
()
const
;
void
triggerTimer
()
{
if
(
!
timer
.
isActive
()
)
timer
.
start
(
500
,
q
);
}
void
addPendingLines
();
void
enforceHistorySize
();
void
updateScrollRanges
();
QPair
<
int
,
int
>
visibleLines
(
int
top
,
int
bottom
)
{
return
qMakePair
(
qMax
(
0
,
lineByYCoordinate
(
top
)
),
qMax
(
0
,
1
+
lineByYCoordinate
(
bottom
)
)
);
}
int
lineByYCoordinate
(
int
x
)
const
;
QPoint
scrollOffset
()
const
;
struct
Style
{
QColor
color
;
friend
inline
uint
qHash
(
const
Style
&
style
)
{
return
qHash
(
style
.
color
.
rgba
()
);
}
bool
operator
==
(
const
Style
&
other
)
const
{
return
this
->
color
.
rgba
()
==
other
.
color
.
rgba
();
}
bool
operator
<
(
const
Style
&
other
)
const
{
return
this
->
color
.
rgba
()
<
other
.
color
.
rgba
();
}
};
struct
LineItem
{
QString
text
;
unsigned
int
styleID
;
};
unsigned
int
findOrAddStyle
(
const
Style
&
style
);
private
:
QHash
<
unsigned
int
,
Style
>
styleByID
;
QHash
<
Style
,
unsigned
int
>
idByStyle
;
QVector
<
LineItem
>
lines
,
pendingLines
;
unsigned
int
historySize
;
unsigned
int
minimumVisibleLines
;
unsigned
int
minimumVisibleColumns
;
QBasicTimer
timer
;
mutable
struct
Cache
{
enum
{
Dimensions
=
1
,
FontMetrics
=
2
,
All
=
FontMetrics
|
Dimensions
};
Cache
()
:
dirty
(
All
)
{}
int
dirty
;
struct
{
int
lineSpacing
;
int
ascent
;
int
averageCharWidth
;
QVector
<
int
>
lineWidths
;
}
fontMetrics
;
struct
{
int
indexOfLongestLine
;
int
longestLineLength
;
}
dimensions
;
}
cache
;
};
/*!
Constructor. Creates an empty KDLogTextWidget.
*/
KDLogTextWidget
::
KDLogTextWidget
(
QWidget
*
parent_
)
:
QAbstractScrollArea
(
parent_
),
d
(
new
Private
(
this
)
)
{
}
/*!
Destructor.
*/
KDLogTextWidget
::~
KDLogTextWidget
()
{}
/*!
\property KDLogTextWidget::historySize
Specifies the maximum number of lines this widget will hold before
dropping old lines. The default is INT_MAX (ie. essentially unlimited).
Get this property's value using %historySize(), and set it with
%setHistorySize().
*/
void
KDLogTextWidget
::
setHistorySize
(
unsigned
int
hs
)
{
if
(
hs
==
d
->
historySize
)
return
;
d
->
historySize
=
hs
;
d
->
enforceHistorySize
();
d
->
updateScrollRanges
();
viewport
()
->
update
();
}
unsigned
int
KDLogTextWidget
::
historySize
()
const
{
return
d
->
historySize
;
}
/*!
\property KDLogTextWidget::text
Contains the current %text as a single string. Equivalent to
\code
lines().join( "\n" )
\endcode
*/
QString
KDLogTextWidget
::
text
()
const
{
return
lines
().
join
(
QLatin1String
(
"
\n
"
)
);
}
/*!
\property KDLogTextWidget::lines
Contains the current %text as a string list. The default empty.
Get this property's value using %lines(), and set it with
%setLines().
*/
void
KDLogTextWidget
::
setLines
(
const
QStringList
&
l
)
{
clear
();
Q_FOREACH
(
const
QString
&
s
,
l
)
message
(
s
);
}
QStringList
KDLogTextWidget
::
lines
()
const
{
QStringList
result
;
Q_FOREACH
(
const
Private
::
LineItem
&
li
,
d
->
lines
)
result
.
push_back
(
li
.
text
);
Q_FOREACH
(
const
Private
::
LineItem
&
li
,
d
->
pendingLines
)
result
.
push_back
(
li
.
text
);
return
result
;
}
/*!
\property KDLogTextWidget::minimumVisibleLines
Specifies the number of lines that should be visible at any one
time. The default is 1 (one).
Get this property's value using %minimumVisibleLines(), and set it
using %setMinimumVisibleLines().
*/
void
KDLogTextWidget
::
setMinimumVisibleLines
(
unsigned
int
num
)
{
if
(
num
==
d
->
minimumVisibleLines
)
return
;
d
->
minimumVisibleLines
=
num
;
updateGeometry
();
}
unsigned
int
KDLogTextWidget
::
minimumVisibleLines
()
const
{
return
d
->
minimumVisibleLines
;
}
/*!
\property KDLogTextWidget::minimumVisibleColumns
Specifies the number of columns that should be visible at any one
time. The default is 1 (one). The width is calculated using
QFont::averageCharWidth(), if that is available. Otherwise, the
width of \c M is used.
Get this property's value using %minimumVisibleColumns(), and set it
using %setMinimumVisibleColumns().
*/
void
KDLogTextWidget
::
setMinimumVisibleColumns
(
unsigned
int
num
)
{
if
(
num
==
d
->
minimumVisibleColumns
)
return
;
d
->
minimumVisibleColumns
=
num
;
updateGeometry
();
}
unsigned
int
KDLogTextWidget
::
minimumVisibleColumns
()
const
{
return
d
->
minimumVisibleColumns
;
}
QSize
KDLogTextWidget
::
minimumSizeHint
()
const
{
d
->
updateCache
();
const
QSize
base
=
QAbstractScrollArea
::
minimumSizeHint
();
const
QSize
view
(
d
->
minimumVisibleColumns
*
d
->
cache
.
fontMetrics
.
averageCharWidth
,
d
->
minimumVisibleLines
*
d
->
cache
.
fontMetrics
.
lineSpacing
);
const
QSize
scrollbars
(
verticalScrollBar
()
?
verticalScrollBar
()
->
minimumSizeHint
().
width
()
:
0
,
horizontalScrollBar
()
?
horizontalScrollBar
()
->
minimumSizeHint
().
height
()
:
0
);
return
base
+
view
+
scrollbars
;
}
QSize
KDLogTextWidget
::
sizeHint
()
const
{
if
(
d
->
minimumVisibleLines
>
1
||
d
->
minimumVisibleColumns
>
1
)
return
minimumSizeHint
();
else
return
2
*
minimumSizeHint
();
}
/*!
Clears the text.
\post lines().empty() == true
*/
void
KDLogTextWidget
::
clear
()
{
d
->
timer
.
stop
();
d
->
lines
.
clear
();
d
->
pendingLines
.
clear
();
d
->
styleByID
.
clear
();
d
->
idByStyle
.
clear
();
d
->
cache
.
dirty
=
Private
::
Cache
::
All
;
viewport
()
->
update
();
}
/*!
Appends \a str to the view, highlighting the line in \a color.
\post lines().back() == str (modulo trailing whitespace and contained newlines)
*/
void
KDLogTextWidget
::
message
(
const
QString
&
str
,
const
QColor
&
color
)
{
const
Private
::
Style
s
=
{
color
};
const
Private
::
LineItem
li
=
{
str
,
d
->
findOrAddStyle
(
s
)
};
d
->
pendingLines
.
push_back
(
li
);
d
->
triggerTimer
();
}
/*!
\overload
Uses the default text color set in this widget's palette.
*/
void
KDLogTextWidget
::
message
(
const
QString
&
str
)
{
const
Private
::
LineItem
li
=
{
str
,
0
};
d
->
pendingLines
.
push_back
(
li
);
d
->
triggerTimer
();
}
void
KDLogTextWidget
::
paintEvent
(
QPaintEvent
*
e
)
{
d
->
updateCache
();
QPainter
p
(
viewport
()
);
p
.
translate
(
-
d
->
scrollOffset
()
);
const
QRect
visible
=
p
.
matrix
().
inverted
().
mapRect
(
e
->
rect
()
);
const
QPair
<
int
,
int
>
visibleLines
=
d
->
visibleLines
(
visible
.
top
(),
visible
.
bottom
()
);
assert
(
visibleLines
.
first
<=
visibleLines
.
second
);
const
Private
::
Style
defaultStyle
=
{
p
.
pen
().
color
()
};
const
Private
::
Cache
&
cache
=
d
->
cache
;
// ### unused optimization: paint lines by styles to minimise pen changes.
for
(
unsigned
int
i
=
visibleLines
.
first
,
end
=
visibleLines
.
second
;
i
!=
end
;
++
i
)
{
const
Private
::
LineItem
&
li
=
d
->
lines
[
i
];
assert
(
!
li
.
styleID
||
d
->
styleByID
.
contains
(
li
.
styleID
)
);
const
Private
::
Style
&
st
=
li
.
styleID
?
d
->
styleByID
[
li
.
styleID
]
:
defaultStyle
;
p
.
setPen
(
st
.
color
);
p
.
drawText
(
0
,
i
*
cache
.
fontMetrics
.
lineSpacing
+
cache
.
fontMetrics
.
ascent
,
li
.
text
);
}
}
void
KDLogTextWidget
::
timerEvent
(
QTimerEvent
*
e
)
{
if
(
e
->
timerId
()
==
d
->
timer
.
timerId
()
)
{
d
->
addPendingLines
();
d
->
timer
.
stop
();
}
else
{
QAbstractScrollArea
::
timerEvent
(
e
);
}
}
void
KDLogTextWidget
::
changeEvent
(
QEvent
*
e
)
{
QAbstractScrollArea
::
changeEvent
(
e
);
d
->
cache
.
dirty
|=
Private
::
Cache
::
FontMetrics
;
}
void
KDLogTextWidget
::
resizeEvent
(
QResizeEvent
*
)
{
d
->
updateScrollRanges
();
}
KDLogTextWidget
::
Private
::
Private
(
KDLogTextWidget
*
qq
)
:
q
(
qq
),
styleByID
(),
idByStyle
(),
lines
(),
pendingLines
(),
historySize
(
0xFFFFFFFF
),
minimumVisibleLines
(
1
),
minimumVisibleColumns
(
1
),
timer
(),
cache
()
{
}
KDLogTextWidget
::
Private
::~
Private
()
{}
void
KDLogTextWidget
::
Private
::
updateCache
()
const
{
if
(
cache
.
dirty
>=
Cache
::
FontMetrics
)
{
const
QFontMetrics
&
fm
=
q
->
fontMetrics
();
cache
.
fontMetrics
.
lineSpacing
=
fm
.
lineSpacing
();
cache
.
fontMetrics
.
ascent
=
fm
.
ascent
();
#if QT_VERSION < 0x040200
cache
.
fontMetrics
.
averageCharWidth
=
fm
.
width
(
QLatin1Char
(
'M'
)
);
#else
cache
.
fontMetrics
.
averageCharWidth
=
fm
.
averageCharWidth
();
#endif
QVector
<
int
>
&
lw
=
cache
.
fontMetrics
.
lineWidths
;
lw
.
clear
();
lw
.
reserve
(
lines
.
size
()
);
Q_FOREACH
(
const
LineItem
&
li
,
lines
)
lw
.
push_back
(
fm
.
width
(
li
.
text
)
);
}
if
(
cache
.
dirty
>=
Cache
::
Dimensions
)
{
const
QVector
<
int
>
&
lw
=
cache
.
fontMetrics
.
lineWidths
;
const
QVector
<
int
>::
const_iterator
it
=
std
::
max_element
(
lw
.
begin
(),
lw
.
end
()
);
if
(
it
==
lw
.
end
()
)
{
cache
.
dimensions
.
indexOfLongestLine
=
-1
;
cache
.
dimensions
.
longestLineLength
=
0
;
}
else
{
cache
.
dimensions
.
indexOfLongestLine
=
it
-
lw
.
begin
();
cache
.
dimensions
.
longestLineLength
=
*
it
;
}
}
cache
.
dirty
=
false
;
}
unsigned
int
KDLogTextWidget
::
Private
::
findOrAddStyle
(
const
Style
&
s
)
{
if
(
idByStyle
.
contains
(
s
)
)
{
const
unsigned
int
id
=
idByStyle
[
s
];
assert
(
styleByID
.
contains
(
id
)
);
assert
(
styleByID
[
id
]
==
s
);
return
id
;
}
else
{
static
unsigned
int
nextID
=
0
;
// remember, 0 is reserved
const
unsigned
int
id
=
++
nextID
;
idByStyle
.
insert
(
s
,
id
);
styleByID
.
insert
(
id
,
s
);
return
id
;
}
}
void
KDLogTextWidget
::
Private
::
enforceHistorySize
()
{
const
size_t
numLimes
=
lines
.
size
();
if
(
numLimes
<=
historySize
)
return
;
const
int
remove
=
numLimes
-
historySize
;
lines
.
erase
(
lines
.
begin
(),
lines
.
begin
()
+
remove
);
// can't quickly update the dimensions if the fontMetrics aren't uptodate.
if
(
cache
.
dirty
&
Cache
::
FontMetrics
)
{
cache
.
dirty
|=
Cache
::
Dimensions
;
return
;
}
QVector
<
int
>
&
lw
=
cache
.
fontMetrics
.
lineWidths
;
assert
(
lw
.
size
()
>
remove
);
lw
.
erase
(
lw
.
begin
(),
lw
.
begin
()
+
remove
);
if
(
cache
.
dirty
&
Cache
::
Dimensions
)
return
;
if
(
cache
.
dimensions
.
indexOfLongestLine
>=
remove
)
cache
.
dimensions
.
indexOfLongestLine
-=
remove
;
else
cache
.
dirty
|=
Cache
::
Dimensions
;
}
static
void
set_scrollbar_properties
(
QScrollBar
&
sb
,
int
document
,
int
viewport
,
int
singleStep
)
{
const
int
min
=
0
;
const
int
max
=
std
::
max
(
0
,
document
-
viewport
);
const
int
value
=
sb
.
value
();
const
bool
wasAtEnd
=
(
value
==
sb
.
maximum
()
&&
value
!=
0
);
sb
.
setRange
(
min
,
max
);
sb
.
setPageStep
(
viewport
);
sb
.
setSingleStep
(
singleStep
);
sb
.
setValue
(
wasAtEnd
?
sb
.
maximum
()
:
value
);
}
void
KDLogTextWidget
::
Private
::
updateScrollRanges
()
{
updateCache
();
if
(
QScrollBar
*
const
sb
=
q
->
verticalScrollBar
()
)
{
const
int
document
=
lines
.
size
()
*
cache
.
fontMetrics
.
lineSpacing
;
const
int
viewport
=
q
->
viewport
()
->
height
();
const
int
singleStep
=
cache
.
fontMetrics
.
lineSpacing
;
set_scrollbar_properties
(
*
sb
,
document
,
viewport
,
singleStep
);
}
if
(
QScrollBar
*
const
sb
=
q
->
horizontalScrollBar
()
)
{
const
int
document
=
cache
.
dimensions
.
longestLineLength
;
const
int
viewport
=
q
->
viewport
()
->
width
();
const
int
singleStep
=
cache
.
fontMetrics
.
lineSpacing
;
// rather randomly chosen
set_scrollbar_properties
(
*
sb
,
document
,
viewport
,
singleStep
);
}
}
void
KDLogTextWidget
::
Private
::
addPendingLines
()
{
if
(
pendingLines
.
empty
()
)
return
;
const
unsigned
int
oldNumLines
=
lines
.
size
();
lines
+=
pendingLines
;
// if the cache isn't dirty, we can quickly update it without
// invalidation:
if
(
!
cache
.
dirty
)
{
// update fontMetrics:
const
QFontMetrics
&
fm
=
q
->
fontMetrics
();
QVector
<
int
>
plw
;
plw
.
reserve
(
pendingLines
.
size
()
);
Q_FOREACH
(
const
LineItem
&
li
,
pendingLines
)
plw
.
push_back
(
fm
.
width
(
li
.
text
)
);
// update dimensions:
const
QVector
<
int
>::
const_iterator
it
=
std
::
max_element
(
plw
.
constBegin
(),
plw
.
constEnd
()
);
if
(
*
it
>=
cache
.
dimensions
.
longestLineLength
)
{
cache
.
dimensions
.
longestLineLength
=
*
it
;
cache
.
dimensions
.
indexOfLongestLine
=
oldNumLines
+
(
it
-
plw
.
constBegin
()
);
}
}
pendingLines
.
clear
();
enforceHistorySize
();
updateScrollRanges
();
q
->
viewport
()
->
update
();
}
int
KDLogTextWidget
::
Private
::
lineByYCoordinate
(
int
y
)
const
{
updateCache
();
if
(
cache
.
fontMetrics
.
lineSpacing
==
0
)
return
-1
;
const
int
raw
=
y
/
cache
.
fontMetrics
.
lineSpacing
;
if
(
raw
<
0
)
return
-1
;
if
(
raw
>=
lines
.
size
()
)
return
lines
.
size
()
-
1
;
return
raw
;
}
static
int
get_scrollbar_offset
(
const
QScrollBar
*
sb
)
{
return
sb
?
sb
->
value
()
:
0
;
}
QPoint
KDLogTextWidget
::
Private
::
scrollOffset
()
const
{
return
QPoint
(
get_scrollbar_offset
(
q
->
horizontalScrollBar
()
),
get_scrollbar_offset
(
q
->
verticalScrollBar
()
)
);
}
#include
"moc_kdlogtextwidget.cpp"
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Sun, Feb 22, 6:38 PM (4 h, 47 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
38/0a/3197e6abe0588910d53656a22aaa
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment