1 /***************************************************************************
4 * Copyright (C) 2000 by Håvard Frøiland, 2004 by Andreas Nicolai *
5 * ghorwin@users.sourceforge.net *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 ***************************************************************************/
13 #include "ktouchslideline.h"
14 #include "ktouchslideline.moc"
25 // don't use defines here... most of the time they are pure evil :-)
26 const int LINE_SPACING
= 2; // the distance between the teacher and student line
27 const int HORIZONTAL_MARGIN
= 30; // the horizontal distance from the lines to the widget border
28 const int VERTICAL_MARGIN
= 10; // the vertical distance from the lines to the widget border
29 const int INNER_MARGIN
= 20; // the margin inside the line boxes
30 const int DEAD_BORDER
= 40; // the width of the no scrolling region near the border
32 KTouchSlideLine::KTouchSlideLine(QWidget
*parent
)
34 m_rightJustify(false),
35 m_teacherPixmap(NULL
),
36 m_studentPixmap(NULL
),
42 m_teacherTextWidth(0),
44 m_teacherFrameXEnd(0),
46 m_studentFrameXEnd(0),
47 m_cursorVisible(false),
53 // set widget defaults (note: teacher and student text is empty after creation)
55 setMaximumHeight(150);
56 setCursorTimerEnabled(true);
58 connect( &m_cursorTimer
, SIGNAL(timeout()), this, SLOT(toggleCursor()) );
59 connect( &m_slideTimer
, SIGNAL(timeout()), this, SLOT(slide()) );
62 KTouchSlideLine::~KTouchSlideLine() {
63 delete m_teacherPixmap
;
64 delete m_studentPixmap
;
67 void KTouchSlideLine::applyPreferences() {
68 // only take font if "override lecture font" is set
69 if (Prefs::overrideLectureFont())
70 m_font
= Prefs::font();
71 resizeEvent(NULL
); // because we need to recreate the pixmap sizes
72 // note: resizeFont() will be called implicitly by resizeEvent()
75 void KTouchSlideLine::setNewText(const QString
& teacherText
, const QString
& studentText
) {
76 if(teacherText
[0].direction()==QChar::DirR
)
80 m_teacherText
=teacherText
;
81 m_studentText
=studentText
;
82 resizeEvent(NULL
); // because we need to recreate the pixmap sizes
85 void KTouchSlideLine::setStudentText(const QString
& text
) {
90 void KTouchSlideLine::setFont(const QFont
& font
) {
91 if (Prefs::overrideLectureFont())
92 m_font
= Prefs::font();
95 resizeEvent(NULL
); // because we need to recreate the pixmap sizes
101 void KTouchSlideLine::setCursorTimerEnabled(bool on
) {
102 if (on
) m_cursorTimer
.start(600);
103 else m_cursorTimer
.stop();
104 m_cursorVisible
=false;
111 void KTouchSlideLine::toggleCursor() {
112 m_cursorVisible
=!m_cursorVisible
;
116 void KTouchSlideLine::slide() {
117 if (m_studentPixmap
==NULL
|| m_teacherPixmap
==NULL
) return;
118 // kdDebug() << "[KTouchSlideLine::slide]" << endl;
119 // calculate new x positions depending on slide speed
120 double speed
= 1.0 + 0.2*Prefs::slidingSpeed();
121 double m_teacherDX
= (m_teacherFrameXEnd
- m_teacherFrameX
)/speed
;
122 double m_studentDX
= (m_studentFrameXEnd
- m_studentFrameX
)/speed
;
123 if (fabs(m_teacherDX
)>1.0) m_teacherFrameX
+= m_teacherDX
;
124 if (fabs(m_studentDX
)>1.0) m_studentFrameX
+= m_studentDX
;
125 if (m_studentFrameX
<m_teacherFrameX
)
126 m_studentFrameX
=m_teacherFrameX
;
127 // now simply copy the required parts of the teacher and student pixmaps onto the widget
128 if(m_rightJustify
==false){
129 bitBlt(this, HORIZONTAL_MARGIN
+ m_shift
, VERTICAL_MARGIN
,
130 m_teacherPixmap
, static_cast<int>(m_teacherFrameX
), 0, m_frameWidth
, m_teacherPixmap
->height());
131 bitBlt(this, HORIZONTAL_MARGIN
+ m_shift
, height() - VERTICAL_MARGIN
- m_studentPixmap
->height(),
132 m_studentPixmap
, static_cast<int>(m_studentFrameX
), 0, m_frameWidth
, m_studentPixmap
->height());
136 bitBlt(this, HORIZONTAL_MARGIN
+ m_shift
, VERTICAL_MARGIN
,
137 m_teacherPixmap
, m_teacherPixmap
->width()-static_cast<int>(m_teacherFrameX
)-m_frameWidth
, 0, m_frameWidth
, m_teacherPixmap
->height());
138 bitBlt(this, HORIZONTAL_MARGIN
+ m_shift
, height() - VERTICAL_MARGIN
- m_studentPixmap
->height(),
139 m_studentPixmap
, m_studentPixmap
->width()-static_cast<int>(m_studentFrameX
)-m_frameWidth
, 0, m_frameWidth
, m_studentPixmap
->height());
141 // restart slide timer if necessary
142 if (m_teacherDX
!=0 || m_studentDX
!=0)
143 m_slideTimer
.start(100, true); // start singleshot timer to slide again
149 // *** Protected member functions (event implementation)
151 void KTouchSlideLine::paintEvent(QPaintEvent
*) {
152 if (m_studentPixmap
==NULL
|| m_teacherPixmap
==NULL
)
158 void KTouchSlideLine::resizeEvent ( QResizeEvent
* ) {
159 if (m_teacherText
.isEmpty()) return; // can happen during startup
160 // kdDebug() << "[KTouchSlideLine::resizeEvent]" << endl;
162 // delete old pixmaps because we have to change its size
163 delete m_teacherPixmap
;
164 delete m_studentPixmap
;
165 int h
= (height() - 2*VERTICAL_MARGIN
- LINE_SPACING
)/2;
166 // calculate teacher text width (in pixel)
167 QFontMetrics
fontMetrics( m_font
);
168 m_teacherTextWidth
= fontMetrics
.boundingRect(m_teacherText
).width(); // store text length in pixel
169 // calculate some space for the enter character (includes a small gap between text and enter arrow).
170 m_enterCharWidth
= fontMetrics
.height(); // change that formula if you don't like the enter char width
171 // calculate text needed for teacher line including margins, enter character
172 int w
= m_teacherTextWidth
+ m_enterCharWidth
+ 2*INNER_MARGIN
;
173 // we need to know the size of a space char very accurately, so let's calculate it the hard way.
174 m_spaceCharWidth
= fontMetrics
.boundingRect("x x").width() - fontMetrics
.boundingRect("x x").width();
175 // Now let's create the teachers pixmap and print the text into. We have to do this only when the teacher
176 // text changes or the widget is resized, so we can safely do this outside the paintEvent() function.
177 m_teacherPixmap
= new QPixmap(w
,h
);
179 painter
.begin (m_teacherPixmap
, this);
180 painter
.setFont( m_font
);
181 //painter.setColor( KTouchConfig().m_teacherTextColor );
182 painter
.fillRect( m_teacherPixmap
->rect(), QBrush(Prefs::teacherBackgroundColor()) );
183 painter
.setPen( Prefs::teacherTextColor() );
184 // create a rectangle for the text drawing
185 QRect
textRect(INNER_MARGIN
, 0, w
-2*INNER_MARGIN
, h
);
186 if(m_rightJustify
==false)
188 painter
.drawText(textRect
, QPainter::AlignLeft
| QPainter::AlignVCenter
, m_teacherText
);
189 drawEnterChar(&painter
, w
- INNER_MARGIN
- m_enterCharWidth
, h
/2, m_enterCharWidth
);
193 painter
.drawText(textRect
, QPainter::AlignRight
| QPainter::AlignVCenter
, m_teacherText
);
194 drawEnterChar(&painter
, INNER_MARGIN
- m_enterCharWidth
, h
/2, m_enterCharWidth
);
197 // Let's now create the students pixmap, which will be drawn in the paintEvent (because it changes frequently).
198 // We use 5 times as much space as the teacher widget -> see paintEvent for explaination
199 m_studentPixmap
= new QPixmap(5*w
,h
);
200 // And finally calculate and store the vertical cursor information
201 m_cursorHeight
= fontMetrics
.height();
202 m_cursorYPos
= height() - VERTICAL_MARGIN
- (m_studentPixmap
->height() + m_cursorHeight
)/2;
204 update(); // here we need a full update!
209 // *** Private member functions (event implementation)
211 void KTouchSlideLine::resizeFont() {
212 // this formula sets the font height to 65% of the line height
213 m_font
.setPointSize(static_cast<int>( (height()-2*VERTICAL_MARGIN
-LINE_SPACING
)/2*0.65) );
216 void KTouchSlideLine::drawCursor() {
218 if (m_cursorVisible
) p
.setPen( m_cursorColor
);
219 else p
.setPen( m_cursorBackground
);
220 int myX
= m_cursorXPos
+ m_studentFrameXEnd
- static_cast<int>(m_studentFrameX
);
221 if(m_rightJustify
==true )
223 /*the small distance between the beging of the pixmap and the cursor:*/
224 int dx
=myX
/*location of cursor*/ - (HORIZONTAL_MARGIN
+ m_shift
)/*start of pixmap*/;
226 myX
=(HORIZONTAL_MARGIN
+ m_shift
/*start of pixmap*/)+ m_frameWidth
-dx
+3 ;
228 if (myX
>HORIZONTAL_MARGIN
&& myX
<width()-HORIZONTAL_MARGIN
)
229 p
.drawLine(myX
, m_cursorYPos
, myX
, m_cursorYPos
+ m_cursorHeight
);
232 void KTouchSlideLine::drawEnterChar(QPainter
*painter
, int cursorPos
, int y
, int enterWidth
) {
233 int gap
= std::min(2,static_cast<int>(0.2*enterWidth
));
234 int enterHeight
= static_cast<int>(0.4*enterWidth
);
235 int arrowSize
= static_cast<int>(enterWidth
/4.0); // mind the difference between 4 and 4.0
236 painter
->drawLine(cursorPos
+enterWidth
, y
, cursorPos
+enterWidth
, y
-enterHeight
); // vertical line
237 painter
->drawLine(cursorPos
+gap
, y
, cursorPos
+enterWidth
, y
); // arrow
238 painter
->drawLine(cursorPos
+gap
, y
, cursorPos
+gap
+arrowSize
, y
+arrowSize
); // arrow
239 painter
->drawLine(cursorPos
+gap
, y
, cursorPos
+gap
+arrowSize
, y
-arrowSize
); // arrow
242 int KTouchSlideLine::textWidth(const QFontMetrics
& fontMetrics
, const QString
& text
) {
243 int w
=fontMetrics
.boundingRect(text
).width();
244 if (text
.length()>0 && text
[text
.length()-1]==' ')
245 w
+= m_spaceCharWidth
;
249 void KTouchSlideLine::updateLines() {
250 if (m_teacherText
.isEmpty()) return; // can happen during startup, but we MUST NOT allow an empty teacher text here
251 int teacherLen
= m_teacherText
.length();
252 int studentLen
= m_studentText
.length();
253 // We need to know whether the students text has been typed correctly or not.
254 // We could set this in the main widget but then we would have an additional connectivity
255 // and potential error source (and it's not time critical anyway... the drawing stuff is).
257 if (teacherLen
>=studentLen
&& m_teacherText
.left(studentLen
)==m_studentText
) error
=false;
259 // now let's draw the students pixmap
261 painter
.begin (m_studentPixmap
, this);
262 if (Prefs::colorOnError()) {
263 // draw the student line depending on the colour settings
265 m_cursorBackground
= Prefs::errorBackgroundColor();
266 painter
.fillRect (m_studentPixmap
->rect(), m_cursorBackground
);
267 m_cursorColor
= Prefs::errorTextColor();
268 painter
.setPen( m_cursorColor
);
271 m_cursorBackground
= Prefs::studentBackgroundColor();
272 painter
.fillRect (m_studentPixmap
->rect(), QBrush(m_cursorBackground
) );
273 m_cursorColor
= Prefs::studentTextColor();
274 painter
.setPen( m_cursorColor
);
278 // use always student text colors
279 m_cursorColor
= Prefs::studentTextColor();
280 painter
.setPen( m_cursorColor
);
281 m_cursorBackground
= Prefs::studentBackgroundColor();
282 painter
.fillRect( m_studentPixmap
->rect(), QBrush(m_cursorBackground
) );
285 painter
.setFont( m_font
);
286 QFontMetrics fontMetrics
= painter
.fontMetrics();
287 QRect
textRect(INNER_MARGIN
, 0, m_studentPixmap
->width()-2*INNER_MARGIN
, m_studentPixmap
->height());
289 painter
.drawText(textRect
, QPainter::AlignRight
| QPainter::AlignVCenter
, m_studentText
);
291 painter
.drawText(textRect
, QPainter::AlignLeft
| QPainter::AlignVCenter
, m_studentText
);
292 // and calculate the cursor position (local coordinates) in the student pixmap
293 // the cursor position is the distance from the beginning of the student pixmap (text margin included)
294 int studentTextLength
=textWidth(fontMetrics
,m_studentText
);
295 int studentCursorPos
= INNER_MARGIN
+ studentTextLength
;
296 // Ok, the text is drawn, now let's calculate the information for the slide() function
297 int allowedWidth
= width() - 2*HORIZONTAL_MARGIN
; // the maximum width available in the widget
298 if (m_teacherPixmap
->width() <= allowedWidth
) {
299 // line is shorter then the space: calculate m_shift to draw it centered
300 m_shift
= (allowedWidth
-m_teacherPixmap
->width())/2;
301 allowedWidth
=m_teacherPixmap
->width();
303 else m_shift
=0; // no shift, let's slide
304 // store the frame size that will be copied
305 m_frameWidth
= allowedWidth
;
307 // calculate the relative cursor positions in the output line
308 QString typedText
= m_teacherText
.left(studentLen
);
309 int typedTextLength
=textWidth(fontMetrics
, typedText
);
310 double CPosFactor
= std::min(1.0, static_cast<double>(typedTextLength
)/m_teacherTextWidth
);
311 // calculate the local coordinate of the cursor in the teacher line
312 int teacherCursorPos
= INNER_MARGIN
+ typedTextLength
;
313 // now calculate the horizontal offset
314 int xDistance
= INNER_MARGIN
+ static_cast<int>( CPosFactor
*(allowedWidth
-2*INNER_MARGIN
-m_enterCharWidth
) );
315 m_cursorXPos
= HORIZONTAL_MARGIN
+ m_shift
+ xDistance
+ std::min(2,static_cast<int>(fontMetrics
.height()*0.1));
316 m_teacherFrameXEnd
= teacherCursorPos
- xDistance
;
317 m_studentFrameXEnd
= studentCursorPos
- xDistance
;
320 m_cursorVisible
= true;
321 m_cursorTimer
.start(800);