chalk

kcurve.cpp
1/* ============================================================
2 * Copyright 2004-2005 by Gilles Caulier
3 * Copyright 2005 by Casper Boemann (reworked to be generic)
4 *
5 * This program is free software; you can redistribute it
6 * and/or modify it under the terms of the GNU General
7 * Public License as published by the Free Software Foundation;
8 * either version 2, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * ============================================================ */
17
18// C++ includes.
19
20#include <cmath>
21#include <cstdlib>
22
23// TQt includes.
24
25#include <tqpixmap.h>
26#include <tqpainter.h>
27#include <tqpoint.h>
28#include <tqpen.h>
29#include <tqevent.h>
30#include <tqtimer.h>
31#include <tqrect.h>
32#include <tqfont.h>
33#include <tqfontmetrics.h>
34
35// KDE includes.
36
37#include <kdebug.h>
38#include <kcursor.h>
39#include <tdelocale.h>
40
41// Local includes.
42
43#include "kcurve.h"
44
45KCurve::KCurve(TQWidget *parent, const char *name, WFlags f)
46 : TQWidget(parent, name, f)
47{
48 m_grab_point = NULL;
49 m_readOnlyMode = false;
50 m_guideVisible = false;
51 m_dragging = false;
52 m_pix = NULL;
53
54 setMouseTracking(true);
55 setPaletteBackgroundColor(TQt::NoBackground);
56 setMinimumSize(150, 50);
57 TQPair<double,double> *p = new TQPair<double,double>;
58 p->first = 0.0; p->second=0.0;
59 m_points.append(p);
60 p = new TQPair<double,double>;
61 p->first = 1.0; p->second=1.0;
62 m_points.append(p);
63 m_points.setAutoDelete(true);
64 setFocusPolicy(TQWidget::StrongFocus);
65}
66
67KCurve::~KCurve()
68{
69 if (m_pix) delete m_pix;
70}
71
72void KCurve::reset(void)
73{
74 m_grab_point = NULL;
75 m_guideVisible = false;
76 repaint(false);
77}
78
79void KCurve::setCurveGuide(TQColor color)
80{
81 m_guideVisible = true;
82 m_colorGuide = color;
83 repaint(false);
84}
85
86void KCurve::setPixmap(TQPixmap pix)
87{
88 if (m_pix) delete m_pix;
89 m_pix = new TQPixmap(pix);
90 repaint(false);
91}
92
93void KCurve::keyPressEvent(TQKeyEvent *e)
94{
95 if(e->key() == TQt::Key_Delete || e->key() == TQt::Key_Backspace)
96 {
97 TQPair<double,double> *closest_point=NULL;
98 if(m_grab_point)
99 {
100 //first find closest point to get focus afterwards
101 TQPair<double,double> *p = m_points.first();
102 double distance = 1000; // just a big number
103 while(p)
104 {
105 if(p!=m_grab_point)
106 if (fabs (m_grab_point->first - p->first) < distance)
107 {
108 distance = fabs(m_grab_point->first - p->first);
109 closest_point = p;
110 }
111 p = m_points.next();
112 }
113 m_points.remove(m_grab_point);
114 }
115 m_grab_point = closest_point;
116 repaint(false);
117 }
118 else
119 TQWidget::keyPressEvent(e);
120}
121
122void KCurve::paintEvent(TQPaintEvent *)
123{
124 int x, y;
125 int wWidth = width();
126 int wHeight = height();
127
128 x = 0;
129 y = 0;
130
131 // Drawing selection or all histogram values.
132 // A TQPixmap is used for enable the double buffering.
133
134 TQPixmap pm(size());
135 TQPainter p1;
136 p1.begin(&pm, this);
137
138 // draw background
139 if(m_pix)
140 {
141 p1.scale(1.0*wWidth/m_pix->width(), 1.0*wHeight/m_pix->height());
142 p1.drawPixmap(0, 0, *m_pix);
143 p1.resetXForm();
144 }
145 else
146 pm.fill();
147
148 // Draw grid separators.
149 p1.setPen(TQPen(TQt::gray, 1, TQt::SolidLine));
150 p1.drawLine(wWidth/3, 0, wWidth/3, wHeight);
151 p1.drawLine(2*wWidth/3, 0, 2*wWidth/3, wHeight);
152 p1.drawLine(0, wHeight/3, wWidth, wHeight/3);
153 p1.drawLine(0, 2*wHeight/3, wWidth, 2*wHeight/3);
154
155 // Draw curve.
156 double curvePrevVal = getCurveValue(0.0);
157 p1.setPen(TQPen(TQt::black, 1, TQt::SolidLine));
158 for (x = 0 ; x < wWidth ; x++)
159 {
160 double curveX;
161 double curveVal;
162
163 curveX = (x + 0.5) / wWidth;
164
165 curveVal = getCurveValue(curveX);
166
167 p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight),
168 x, wHeight - int(curveVal * wHeight));
169
170 curvePrevVal = curveVal;
171 }
172 p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight),
173 x, wHeight - int(getCurveValue(1.0) * wHeight));
174
175 // Drawing curve handles.
176 if ( !m_readOnlyMode )
177 {
178 TQPair<double,double> *p = m_points.first();
179
180 while(p)
181 {
182 double curveX = p->first;
183 double curveY = p->second;
184
185 if(p == m_grab_point)
186 {
187 p1.setPen(TQPen(TQt::red, 3, TQt::SolidLine));
188 p1.drawEllipse( int(curveX * wWidth) - 2,
189 wHeight - 2 - int(curveY * wHeight), 4, 4 );
190 }
191 else
192 {
193 p1.setPen(TQPen(TQt::red, 1, TQt::SolidLine));
194
195 p1.drawEllipse( int(curveX * wWidth) - 3,
196 wHeight - 3 - int(curveY * wHeight), 6, 6 );
197 }
198
199 p = m_points.next();
200 }
201 }
202
203 p1.end();
204 bitBlt(this, 0, 0, &pm);
205}
206
207void KCurve::mousePressEvent ( TQMouseEvent * e )
208{
209 if (m_readOnlyMode) return;
210
211 TQPair<double,double> *closest_point=NULL;
212 double distance;
213
214 if (e->button() != TQt::LeftButton)
215 return;
216
217 double x = e->pos().x() / (float)width();
218 double y = 1.0 - e->pos().y() / (float)height();
219
220 distance = 1000; // just a big number
221
222 TQPair<double,double> *p = m_points.first();
223 int insert_pos,pos=0;
224 while(p)
225 {
226 if (fabs (x - p->first) < distance)
227 {
228 distance = fabs(x - p->first);
229 closest_point = p;
230 if(x < p->first)
231 insert_pos = pos;
232 else
233 insert_pos = pos + 1;
234 }
235 p = m_points.next();
236 pos++;
237 }
238
239
240 if(closest_point == NULL)
241 {
242 closest_point = new TQPair<double,double>;
243 closest_point->first = x;
244 closest_point->second = y;
245 m_points.append(closest_point);
246 }
247 else if(distance * width() > 5)
248 {
249 closest_point = new TQPair<double,double>;
250 closest_point->first = x;
251 closest_point->second = y;
252 m_points.insert(insert_pos, closest_point);
253 }
254 else
255 if(fabs(y - closest_point->second) * width() > 5)
256 return;
257
258
259 m_grab_point = closest_point;
260 m_grabOffsetX = m_grab_point->first - x;
261 m_grabOffsetY = m_grab_point->second - y;
262 m_grab_point->first = x + m_grabOffsetX;
263 m_grab_point->second = y + m_grabOffsetY;
264 m_dragging = true;
265
266 setCursor( KCursor::crossCursor() );
267
268 // Determine the leftmost and rightmost points.
269 m_leftmost = 0;
270 m_rightmost = 1;
271
272 p = m_points.first();
273 while(p)
274 {
275 if (p != m_grab_point)
276 {
277 if(p->first> m_leftmost && p->first < x)
278 m_leftmost = p->first;
279 if(p->first < m_rightmost && p->first > x)
280 m_rightmost = p->first;
281 }
282 p = m_points.next();
283 }
284 repaint(false);
285}
286
287void KCurve::mouseReleaseEvent ( TQMouseEvent * e )
288{
289 if (m_readOnlyMode) return;
290
291 if (e->button() != TQt::LeftButton)
292 return;
293
294 setCursor( KCursor::arrowCursor() );
295 m_dragging = false;
296 repaint(false);
297 emit modified();
298}
299
300void KCurve::mouseMoveEvent ( TQMouseEvent * e )
301{
302 if (m_readOnlyMode) return;
303
304 double x = e->pos().x() / (float)width();
305 double y = 1.0 - e->pos().y() / (float)height();
306
307 if (m_dragging == false) // If no point is selected set the the cursor shape if on top
308 {
309 double distance = 1000;
310 double ydistance = 1000;
311 TQPair<double,double> *p = m_points.first();
312 while(p)
313 {
314 if (fabs (x - p->first) < distance)
315 {
316 distance = fabs(x - p->first);
317 ydistance = fabs(y - p->second);
318 }
319 p = m_points.next();
320 }
321
322 if (distance * width() > 5 || ydistance * height() > 5)
323 setCursor( KCursor::arrowCursor() );
324 else
325 setCursor( KCursor::crossCursor() );
326 }
327 else // Else, drag the selected point
328 {
329 setCursor( KCursor::crossCursor() );
330
331 x += m_grabOffsetX;
332 y += m_grabOffsetY;
333
334 if (x <= m_leftmost)
335 x = m_leftmost + 1E-4; // the addition so we can grab the dot later.
336
337 if(x >= m_rightmost)
338 x = m_rightmost - 1E-4;
339
340 if(y > 1.0)
341 y = 1.0;
342
343 if(y < 0.0)
344 y = 0.0;
345
346 m_grab_point->first = x;
347 m_grab_point->second = y;
348
349 emit modified();
350 }
351
352 repaint(false);
353}
354
355double KCurve::getCurveValue(double x)
356{
357 return getCurveValue(m_points, x);
358}
359
360double KCurve::getCurveValue(TQPtrList<TQPair<double,double> > &curve, double x)
361{
362 double t;
363 TQPair<double,double> *p;
364 TQPair<double,double> *p0,*p1,*p2,*p3;
365 double c0,c1,c2,c3;
366 double val;
367
368 if(curve.count() == 0)
369 return 0.5;
370
371 // First find curve segment
372 p = curve.first();
373 if(x < p->first)
374 return p->second;
375
376 p = curve.last();
377 if(x >= p->first)
378 return p->second;
379
380 // Find the four control points (two on each side of x)
381 p = curve.first();
382 while(x >= p->first)
383 {
384 p = curve.next();
385 }
386 curve.prev();
387
388 if((p0 = curve.prev()) == NULL)
389 p1 = p0 = curve.first();
390 else
391 p1 = curve.next();
392
393 p2 = curve.next();
394 if( (p = curve.next()) )
395 p3 = p;
396 else
397 p3 = p2;
398
399 // Calculate the value
400 t = (x - p1->first) / (p2->first - p1->first);
401 c2 = (p2->second - p0->second) * (p2->first-p1->first) / (p2->first-p0->first);
402 c3 = p1->second;
403 c0 = -2*p2->second + 2*c3 + c2 + (p3->second - p1->second) * (p2->first - p1->first) / (p3->first - p1->first);
404 c1 = p2->second - c3 - c2 - c0;
405 val = ((c0*t + c1)*t + c2)*t + c3;
406
407 if(val < 0.0)
408 val = 0.0;
409 if(val > 1.0)
410 val = 1.0;
411 return val;
412}
413
414TQPtrList<TQPair<double,double> > KCurve::getCurve()
415{
416 TQPtrList<TQPair<double,double> > outlist;
417 TQPair<double,double> *p;
418 TQPair<double,double> *outpoint;
419
420 p = m_points.first();
421 while(p)
422 {
423 outpoint = new TQPair<double,double>(p->first, p->second);
424 outlist.append(outpoint);
425 p = m_points.next();
426 }
427 return outlist;
428}
429
430void KCurve::setCurve(TQPtrList<TQPair<double,double> >inlist)
431{
432 TQPair<double,double> *p;
433 TQPair<double,double> *inpoint;
434
435 m_points.clear();
436
437 inpoint = inlist.first();
438 while(inpoint)
439 {
440 p = new TQPair<double,double>(inpoint->first, inpoint->second);
441 m_points.append(p);
442 inpoint = inlist.next();
443 }
444}
445
446void KCurve::leaveEvent( TQEvent * )
447{
448}
449
450#include "kcurve.moc"