1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include "curvewidget.h"
8 
9 #include <cmath>
10 
11 #include <QApplication>
12 #include <QCursor>
13 #include <QEvent>
14 #include <QHBoxLayout>
15 #include <QMessageBox>
16 #include <QPainter>
17 #include <QPixmap>
18 #include <QPushButton>
19 #include <QSpacerItem>
20 #include <QTextStream>
21 #include <QToolTip>
22 #include <QVBoxLayout>
23 
24 
25 #include "commonstrings.h"
26 #include "iconmanager.h"
27 #include "prefsfile.h"
28 #include "prefsmanager.h"
29 #include "scclocale.h"
30 #include "ui/customfdialog.h"
31 #include "ui/scmessagebox.h"
32 #include "util.h"
33 #include "util_color.h"
34 
35 
36 
KCurve(QWidget * parent)37 KCurve::KCurve(QWidget *parent) : QWidget(parent),
38 	m_leftmost(0.0),
39 	m_rightmost(0.0),
40 	m_pos(0),
41 	m_dragging(false),
42 	m_linear(false),
43 	m_grabOffsetX(0.0),
44 	m_grabOffsetY(0.0)
45 {
46 	setMouseTracking(true);
47 	setMinimumSize(150, 150);
48 	m_points.resize(0);
49 	m_points.addPoint(0.0, 0.0);
50 	m_points.addPoint(1.0, 1.0);
51 	setFocusPolicy(Qt::StrongFocus);
52 }
53 
~KCurve()54 KCurve::~KCurve()
55 {
56 }
57 
paintEvent(QPaintEvent *)58 void KCurve::paintEvent(QPaintEvent *)
59 {
60 	int    x = 0;
61 	int    wWidth = width() - 1;
62 	int    wHeight = height() - 1;
63 	// Drawing selection or all histogram values.
64 	QPainter p1;
65 	p1.begin(this);
66 	//  draw background
67 	p1.fillRect(QRect(0, 0, wWidth, wHeight), QColor(255, 255, 255));
68 	// Draw grid separators.
69 	p1.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
70 	p1.drawLine(wWidth/4, 0, wWidth/4, wHeight);
71 	p1.drawLine(wWidth/2, 0, wWidth/2, wHeight);
72 	p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight);
73 	p1.drawLine(0, wHeight/4, wWidth, wHeight/4);
74 	p1.drawLine(0, wHeight/2, wWidth, wHeight/2);
75 	p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4);
76 
77 	// Draw curve.
78 	double curvePrevVal = getCurveValue(0.0);
79 	p1.setPen(QPen(Qt::black, 1, Qt::SolidLine));
80 	for (x = 0 ; x < wWidth ; x++)
81 	{
82 		double curveX;
83 		double curveVal;
84 		//		curveX = (x + 0.5) / wWidth;
85 		curveX = x / static_cast<double>(wWidth);
86 		curveVal = getCurveValue(curveX);
87 		p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x,     wHeight - int(curveVal * wHeight));
88 		curvePrevVal = curveVal;
89 	}
90 	p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x,     wHeight - int(getCurveValue(1.0) * wHeight));
91 	for (int dh = 0; dh < m_points.size(); dh++)
92 	{
93 		FPoint p = m_points.point(dh);
94 		if (p == m_grab_point)
95 		{
96 			p1.setPen(QPen(Qt::red, 3, Qt::SolidLine));
97 			p1.drawEllipse( int(p.x() * wWidth) - 2, wHeight - 2 - int(p.y() * wHeight), 4, 4 );
98 		}
99 		else
100 		{
101 			p1.setPen(QPen(Qt::red, 1, Qt::SolidLine));
102 			p1.drawEllipse( int(p.x() * wWidth) - 3, wHeight - 3 - int(p.y() * wHeight), 6, 6 );
103 		}
104 	}
105 	p1.end();
106 }
107 
keyPressEvent(QKeyEvent * e)108 void KCurve::keyPressEvent(QKeyEvent *e)
109 {
110 	if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)
111 	{
112 		if (m_points.size() > 2)
113 		{
114 			FPoint closest_point = m_points.point(0);
115 			FPoint p = m_points.point(0);
116 			int pos = 0;
117 			int cc = 0;
118 			double distance = 1000; // just a big number
119 			while (cc < m_points.size())
120 			{
121 				p = m_points.point(cc);
122 				if (fabs (m_grab_point.x() - p.x()) < distance)
123 				{
124 					distance = fabs(m_grab_point.x() - p.x());
125 					closest_point = p;
126 					m_pos = pos;
127 				}
128 				cc++;
129 				pos++;
130 			}
131 			FPointArray cli;
132 			cli.putPoints(0, m_pos, m_points);
133 			cli.putPoints(cli.size(), m_points.size()-m_pos-1, m_points, m_pos+1);
134 			m_points.resize(0);
135 			m_points = cli.copy();
136 			m_grab_point = closest_point;
137 			repaint();
138 			emit modified();
139 			QWidget::keyPressEvent(e);
140 		}
141 	}
142 }
143 
mousePressEvent(QMouseEvent * e)144 void KCurve::mousePressEvent ( QMouseEvent * e )
145 {
146 	FPoint closest_point = FPoint();
147 	double distance;
148 	if (e->button() != Qt::LeftButton)
149 		return;
150 	double x = e->pos().x() / (float)width();
151 	double y = 1.0 - e->pos().y() / (float)height();
152 	distance = 1000; // just a big number
153 	FPoint p = m_points.point(0);
154 	int insert_pos =0;
155 	int pos = 0;
156 	int cc = 0;
157 	while (cc < m_points.size())
158 	{
159 		p = m_points.point(cc);
160 		if (fabs (x - p.x()) < distance)
161 		{
162 			distance = fabs(x - p.x());
163 			closest_point = p;
164 			insert_pos = pos;
165 		}
166 		cc++;
167 		pos++;
168 	}
169 	m_pos = insert_pos;
170 	m_grab_point = closest_point;
171 	m_grabOffsetX = m_grab_point.x() - x;
172 	m_grabOffsetY = m_grab_point.y() - y;
173 	m_grab_point = FPoint(x + m_grabOffsetX, y + m_grabOffsetY);
174 	double curveVal = getCurveValue(x);
175 	if (distance * width() > 5)
176 	{
177 		m_dragging = false;
178 		if (fabs(y - curveVal) * width() > 5)
179 			return;
180 		if (m_points.size() < 14)
181 		{
182 			if (x > closest_point.x())
183 				m_pos++;
184 			FPointArray cli;
185 			cli.putPoints(0, m_pos, m_points);
186 			cli.resize(cli.size()+1);
187 			cli.putPoints(cli.size()-1, 1, x, curveVal);
188 			cli.putPoints(cli.size(), m_points.size()-m_pos, m_points, m_pos);
189 			m_points.resize(0);
190 			m_points = cli.copy();
191 			m_dragging = true;
192 			m_grab_point = m_points.point(m_pos);
193 			m_grabOffsetX = m_grab_point.x() - x;
194 			m_grabOffsetY = m_grab_point.y() - curveVal;
195 			m_grab_point = FPoint(x + m_grabOffsetX, curveVal + m_grabOffsetY);
196 			setCursor(QCursor(Qt::CrossCursor));
197 		}
198 	}
199 	else
200 	{
201 		if (fabs(y - closest_point.y()) * width() > 5)
202 			return;
203 		m_dragging = true;
204 		setCursor(QCursor(Qt::CrossCursor));
205 	}
206 	// Determine the leftmost and rightmost points.
207 	m_leftmost = 0;
208 	m_rightmost = 1;
209 	cc = 0;
210 	while (cc < m_points.size())
211 	{
212 		p = m_points.point(cc);
213 		if (p != m_grab_point)
214 		{
215 			if (p.x() > m_leftmost && p.x() < x)
216 				m_leftmost = p.x();
217 			if (p.x() < m_rightmost && p.x() > x)
218 				m_rightmost = p.x();
219 		}
220 		cc++;
221     }
222 	repaint();
223 	emit modified();
224 }
225 
mouseReleaseEvent(QMouseEvent * e)226 void KCurve::mouseReleaseEvent ( QMouseEvent * e )
227 {
228 	if (e->button() != Qt::LeftButton)
229 		return;
230 	setCursor(QCursor(Qt::ArrowCursor));
231 	m_dragging = false;
232 	repaint();
233 	emit modified();
234 }
235 
mouseMoveEvent(QMouseEvent * e)236 void KCurve::mouseMoveEvent ( QMouseEvent * e )
237 {
238 	double x = e->pos().x() / (float)width();
239 	double y = 1.0 - e->pos().y() / (float)height();
240 
241 	if (!m_dragging)   // If no point is selected set the the cursor shape if on top
242 	{
243 		double distance = 1000;
244 		double ydistance = 1000;
245 		int cc = 0;
246 		while (cc < m_points.size())
247 		{
248 			FPoint p = m_points.point(cc);
249 			if (fabs (x - p.x()) < distance)
250 			{
251 				distance = fabs(x - p.x());
252 				ydistance = fabs(y - p.y());
253 			}
254 			cc++;
255 		}
256 		if (distance * width() > 5 || ydistance * height() > 5)
257 			setCursor(QCursor(Qt::ArrowCursor));
258 		else
259 			setCursor(QCursor(Qt::CrossCursor));
260 	}
261 	else  // Else, drag the selected point
262 	{
263 		setCursor(QCursor(Qt::CrossCursor));
264 		x += m_grabOffsetX;
265 		y += m_grabOffsetY;
266 		if (x <= m_leftmost)
267 			x = m_leftmost + 1E-4; // the addition so we can grab the dot later.
268 		if (x >= m_rightmost)
269 			x = m_rightmost - 1E-4;
270 		if (y > 1.0)
271 			y = 1.0;
272 		if (y < 0.0)
273 			y = 0.0;
274 		m_grab_point = FPoint(x, y);
275 		m_points.setPoint( m_pos, m_grab_point);
276 		repaint();
277 		emit modified();
278 	}
279 }
280 
getCurveValue(double x)281 double KCurve::getCurveValue(double x)
282 {
283 	return getCurveYValue(m_points, x, m_linear);
284 }
285 
getCurve()286 FPointArray KCurve::getCurve()
287 {
288 	return m_points.copy();
289 }
290 
setCurve(const FPointArray & inlist)291 void KCurve::setCurve(const FPointArray& inlist)
292 {
293 	m_points_back = m_points.copy();
294 	m_points.resize(0);
295 	m_points = inlist.copy();
296 	repaint();
297 	emit modified();
298 }
299 
resetCurve()300 void KCurve::resetCurve()
301 {
302 	m_points.resize(0);
303 	m_points = m_points_back.copy();
304 	repaint();
305 	emit modified();
306 }
307 
setLinear(bool setter)308 void KCurve::setLinear(bool setter)
309 {
310 	m_linear = setter;
311 	repaint();
312 	emit modified();
313 }
314 
isLinear()315 bool KCurve::isLinear()
316 {
317 	return m_linear;
318 }
319 
CurveWidget(QWidget * parent)320 CurveWidget::CurveWidget( QWidget* parent ) : QWidget( parent )
321 {
322 	CurveWidgetLayout = new QHBoxLayout(this);
323 	CurveWidgetLayout->setContentsMargins(0, 0, 0, 0);
324 	CurveWidgetLayout->setSpacing(6);
325 
326 	layout1 = new QVBoxLayout;
327 	layout1->setContentsMargins(0, 0, 0, 0);
328 	layout1->setSpacing(6);
329 
330 	invertButton = new QPushButton( this );
331 	invertButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
332 	invertButton->setIcon( IconManager::instance().loadIcon("invert.png") );
333 	invertButton->setIconSize(QSize(22, 22));
334 	layout1->addWidget( invertButton );
335 
336 	resetButton = new QPushButton( this );
337 	resetButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
338 	resetButton->setIcon( IconManager::instance().loadIcon("reload.png") );
339 	resetButton->setIconSize(QSize(22, 22));
340 	layout1->addWidget( resetButton );
341 	linearButton = new QPushButton( this );
342 	QIcon ic;
343 	ic.addPixmap(IconManager::instance().loadPixmap("curvebezier.png"), QIcon::Normal, QIcon::Off);
344 	ic.addPixmap(IconManager::instance().loadPixmap("curvelinear.png"), QIcon::Normal, QIcon::On);
345 	linearButton->setIcon(ic);
346 	linearButton->setCheckable( true );
347 	linearButton->setChecked(false);
348 	linearButton->setIconSize(QSize(22, 22));
349 	linearButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
350 	layout1->addWidget( linearButton );
351 	spacer1 = new QSpacerItem( 2, 3, QSizePolicy::Minimum, QSizePolicy::Expanding );
352 	layout1->addItem( spacer1 );
353 
354 	loadButton = new QPushButton( this );
355 	loadButton->setIcon( IconManager::instance().loadIcon("22/document-open.png") );
356 	loadButton->setIconSize(QSize(22, 22));
357 	loadButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
358 	layout1->addWidget( loadButton );
359 
360 	saveButton = new QPushButton( this );
361 	saveButton->setIcon( IconManager::instance().loadIcon("22/document-save-as.png") );
362 	saveButton->setIconSize(QSize(22, 22));
363 	saveButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
364 	layout1->addWidget( saveButton );
365 	CurveWidgetLayout->addLayout( layout1 );
366 
367 	cDisplay = new KCurve(this);
368 	cDisplay->setMinimumSize( QSize( 150, 150 ) );
369 	CurveWidgetLayout->addWidget( cDisplay );
370 	languageChange();
371 
372 	connect(invertButton, SIGNAL(clicked()), this, SLOT(doInvert()));
373 	connect(resetButton, SIGNAL(clicked()), this, SLOT(doReset()));
374 	connect(linearButton, SIGNAL(clicked()), this, SLOT(doLinear()));
375 	connect(loadButton, SIGNAL(clicked()), this, SLOT(doLoad()));
376 	connect(saveButton, SIGNAL(clicked()), this, SLOT(doSave()));
377 }
378 
doInvert()379 void CurveWidget::doInvert()
380 {
381 	FPointArray curve = cDisplay->getCurve();
382 	for (int a = 0; a < curve.size(); a++)
383 	{
384 		FPoint p = curve.point(a);
385 		curve.setPoint(a, p.x(), 1.0 - p.y());
386 	}
387 	cDisplay->setCurve(curve);
388 }
389 
doReset()390 void CurveWidget::doReset()
391 {
392 	cDisplay->resetCurve();
393 }
394 
doLinear()395 void CurveWidget::doLinear()
396 {
397 	cDisplay->setLinear(linearButton->isChecked());
398 }
399 
setLinear(bool setter)400 void CurveWidget::setLinear(bool setter)
401 {
402 	cDisplay->setLinear(setter);
403 	linearButton->setChecked(setter);
404 }
405 
doLoad()406 void CurveWidget::doLoad()
407 {
408 	QString fileName;
409 	PrefsContext* dirs = PrefsManager::instance().prefsFile->getContext("dirs");
410 	QString wdir = dirs->get("curves", ".");
411 	CustomFDialog dia(this, wdir, tr("Open"), tr("Curve Files (*.scu *.SCU);;All Files (*)"), fdHidePreviewCheckBox | fdExistingFiles);
412 	if (dia.exec() == QDialog::Accepted)
413 		fileName = dia.selectedFile();
414 	else
415 		return;
416 	if (!fileName.isEmpty())
417 	{
418 		dirs->set("curves", fileName.left(fileName.lastIndexOf("/")));
419 		QFile f(fileName);
420 		if (f.open(QIODevice::ReadOnly))
421 		{
422 			QTextStream fp(&f);
423 			int numVals;
424 			double xval, yval;
425 			FPointArray curve;
426 			curve.resize(0);
427 			fp >> numVals;
428 			for (int nv = 0; nv < numVals; nv++)
429 			{
430 				QString s;
431 				fp >> s;
432 				xval = ScCLocale::toDoubleC(s);
433 				fp >> s;
434 				yval = ScCLocale::toDoubleC(s);
435 				curve.addPoint(xval, yval);
436 			}
437 			cDisplay->setCurve(curve);
438 			int lin;
439 			fp >> lin;
440 			cDisplay->setLinear(lin);
441 		}
442 	}
443 }
444 
doSave()445 void CurveWidget::doSave()
446 {
447 	QString fileName;
448 	QString wdir = PrefsManager::instance().prefsFile->getContext("dirs")->get("curves", ".");
449 	CustomFDialog dia(this, wdir, tr("Save as"), tr("Curve Files (*.scu *.SCU);;All Files (*)"), fdHidePreviewCheckBox | fdNone);
450 	if (dia.exec() == QDialog::Accepted)
451 		fileName = dia.selectedFile();
452 	else
453 		return;
454 	if (!fileName.isEmpty())
455 	{
456 		if (!fileName.endsWith(".scu"))
457 			fileName += ".scu";
458 		PrefsManager::instance().prefsFile->getContext("dirs")->set("curves", fileName.left(fileName.lastIndexOf("/")));
459 		if (overwrite(this, fileName))
460 		{
461 			QString efval = "";
462 			FPointArray Vals = cDisplay->getCurve();
463 			QString tmp;
464 			tmp.setNum(Vals.size());
465 			efval += tmp;
466 			for (int p = 0; p < Vals.size(); p++)
467 			{
468 				const FPoint& pv = Vals.point(p);
469 				efval += QString(" %1 %2").arg(pv.x()).arg(pv.y());
470 			}
471 			if (cDisplay->isLinear())
472 				efval += " 1";
473 			else
474 				efval += " 0";
475 			QFile fx(fileName);
476 			if (fx.open(QIODevice::WriteOnly))
477 			{
478 				QTextStream tsx(&fx);
479 				tsx << efval;
480 				fx.close();
481 			}
482 			else
483 				ScMessageBox::warning(this, CommonStrings::trWarning, tr("Cannot write the file: \n%1").arg(fileName));
484 		}
485 	}
486 }
487 
changeEvent(QEvent * e)488 void CurveWidget::changeEvent(QEvent *e)
489 {
490 	if (e->type() == QEvent::LanguageChange)
491 	{
492 		languageChange();
493 	}
494 	else
495 		QWidget::changeEvent(e);
496 }
497 
languageChange()498 void CurveWidget::languageChange()
499 {
500 	invertButton->setText( QString() );
501 	resetButton->setText( QString() );
502 	loadButton->setText( QString() );
503 	saveButton->setText( QString() );
504 	invertButton->setToolTip("");
505 	resetButton->setToolTip("");
506 	linearButton->setToolTip("");
507 	loadButton->setToolTip("");
508 	saveButton->setToolTip("");
509 	invertButton->setToolTip( tr( "Inverts the curve" ) );
510 	resetButton->setToolTip( tr( "Resets the curve" ) );
511 	linearButton->setToolTip( tr( "Switches between linear and cubic interpolation of the curve" ) );
512 	loadButton->setToolTip( tr( "Loads a curve" ) );
513 	saveButton->setToolTip( tr( "Saves this curve" ) );
514 }
515 
516 
517