moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / ktouch / src / ktouchslideline.cpp
blob3bbef6aaa6609548a18f37aa53f6af9067a1210a
1 /***************************************************************************
2 * ktouchslideline.h *
3 * ----------------- *
4 * Copyright (C) 2000 by Håvard Frøiland, 2004 by Andreas Nicolai *
5 * ghorwin@users.sourceforge.net *
6 * *
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"
16 #include <qpainter.h>
17 #include <qpixmap.h>
18 #include <kdebug.h>
20 #include <cmath>
21 #include <algorithm>
23 #include "prefs.h"
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)
33 : QWidget( parent ),
34 m_rightJustify(false),
35 m_teacherPixmap(NULL),
36 m_studentPixmap(NULL),
37 m_slideTimer(this),
38 m_shift(0),
39 m_enterCharWidth(0),
40 m_spaceCharWidth(0),
41 m_frameWidth(0),
42 m_teacherTextWidth(0),
43 m_teacherFrameX(0),
44 m_teacherFrameXEnd(0),
45 m_studentFrameX(0),
46 m_studentFrameXEnd(0),
47 m_cursorVisible(false),
48 m_cursorTimer(this),
49 m_cursorXPos(0),
50 m_cursorYPos(0),
51 m_cursorHeight(0)
53 // set widget defaults (note: teacher and student text is empty after creation)
54 setMinimumHeight(50);
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)
77 m_rightJustify=true;
78 else
79 m_rightJustify=false;
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) {
86 m_studentText=text;
87 updateLines();
90 void KTouchSlideLine::setFont(const QFont& font) {
91 if (Prefs::overrideLectureFont())
92 m_font = Prefs::font();
93 else
94 m_font = font;
95 resizeEvent(NULL); // because we need to recreate the pixmap sizes
99 // *** Public slots
101 void KTouchSlideLine::setCursorTimerEnabled(bool on) {
102 if (on) m_cursorTimer.start(600);
103 else m_cursorTimer.stop();
104 m_cursorVisible=false;
105 drawCursor();
109 // *** Private slots
111 void KTouchSlideLine::toggleCursor() {
112 m_cursorVisible=!m_cursorVisible;
113 drawCursor();
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());
134 else
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
144 drawCursor();
149 // *** Protected member functions (event implementation)
151 void KTouchSlideLine::paintEvent(QPaintEvent*) {
152 if (m_studentPixmap==NULL || m_teacherPixmap==NULL)
153 resizeEvent(NULL);
154 else
155 slide();
158 void KTouchSlideLine::resizeEvent ( QResizeEvent * ) {
159 if (m_teacherText.isEmpty()) return; // can happen during startup
160 // kdDebug() << "[KTouchSlideLine::resizeEvent]" << endl;
161 resizeFont();
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);
178 QPainter painter;
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);
191 else
193 painter.drawText(textRect, QPainter::AlignRight | QPainter::AlignVCenter, m_teacherText);
194 drawEnterChar(&painter, INNER_MARGIN - m_enterCharWidth, h/2, m_enterCharWidth);
196 painter.end();
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;
203 updateLines();
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() {
217 QPainter p(this);
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;
246 return w;
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).
256 bool error;
257 if (teacherLen>=studentLen && m_teacherText.left(studentLen)==m_studentText) error=false;
258 else error=true;
259 // now let's draw the students pixmap
260 QPainter painter;
261 painter.begin (m_studentPixmap, this);
262 if (Prefs::colorOnError()) {
263 // draw the student line depending on the colour settings
264 if (error) {
265 m_cursorBackground = Prefs::errorBackgroundColor();
266 painter.fillRect (m_studentPixmap->rect(), m_cursorBackground);
267 m_cursorColor = Prefs::errorTextColor();
268 painter.setPen( m_cursorColor );
270 else {
271 m_cursorBackground = Prefs::studentBackgroundColor();
272 painter.fillRect (m_studentPixmap->rect(), QBrush(m_cursorBackground) );
273 m_cursorColor = Prefs::studentTextColor();
274 painter.setPen( m_cursorColor );
277 else {
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) );
284 // draw the text
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());
288 if(m_rightJustify)
289 painter.drawText(textRect, QPainter::AlignRight | QPainter::AlignVCenter, m_studentText);
290 else
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;
318 painter.end();
320 m_cursorVisible = true;
321 m_cursorTimer.start(800);
322 slide();