chalk

kis_tool_freehand.cpp
1/*
2 * kis_tool_brush.cpp - part of Chalk
3 *
4 * Copyright (c) 2003-2004 Boudewijn Rempt <boud@valdyas.org>
5 * Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be>
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 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21#include <tqevent.h>
22#include <tqlabel.h>
23#include <tqlayout.h>
24#include <tqwidget.h>
25#include <tqrect.h>
26
27#include <kdebug.h>
28#include <tdeaction.h>
29#include <kcommand.h>
30#include <tdelocale.h>
31
32#include "kis_canvas_subject.h"
33#include "kis_undo_adapter.h"
34#include "kis_selection.h"
35#include "kis_painter.h"
36#include "kis_fill_painter.h"
37#include "kis_tool_freehand.h"
38#include "kis_cursor.h"
39#include "kis_button_press_event.h"
40#include "kis_button_release_event.h"
41#include "kis_move_event.h"
42#include "kis_layer.h"
43#include "kis_group_layer.h"
44#include "kis_paint_layer.h"
45#include "kis_canvas.h"
46#include "kis_canvas_painter.h"
47#include "kis_boundary_painter.h"
48#include "kis_brush.h"
49
50KisToolFreehand::KisToolFreehand(TQString transactionText)
51 : super(transactionText),
52 m_dragDist ( 0 ),
53 m_transactionText(transactionText),
54 m_mode( HOVER )
55{
56 m_painter = 0;
57 m_currentImage = 0;
58 m_tempLayer = 0;
59 m_paintIncremental = true;
60 m_paintOnSelection = false;
61 m_paintedOutline = false;
62}
63
64KisToolFreehand::~KisToolFreehand()
65{
66}
67
68void KisToolFreehand::update(KisCanvasSubject *subject)
69{
70 super::update(subject);
71 m_currentImage = m_subject->currentImg();
72}
73
74void KisToolFreehand::buttonPress(KisButtonPressEvent *e)
75{
76 if (!m_subject) return;
77
78 if (!m_subject->currentBrush()) return;
79
80 if (!m_currentImage || !m_currentImage->activeDevice()) return;
81
82 if (e->button() == TQt::LeftButton) {
83
84 m_currentImage->activeDevice()->lock( true );
85 kdDebug() << ">>>>>>>>>>>>>>>>>>>Locking paint device\n";
86
87 // People complain that they can't start brush strokes outside of the image boundaries.
88 // This makes sense, especially when combined with BUG:132759, so commenting out the
89 // next line makes sense.
90 //if (!m_currentImage->bounds().contains(e->pos().floorTQPoint())) return;
91
92 initPaint(e);
93 paintAt(e->pos(), e->pressure(), e->xTilt(), e->yTilt());
94
95 m_prevPos = e->pos();
96 m_prevPressure = e->pressure();
97 m_prevXTilt = e->xTilt();
98 m_prevYTilt = e->yTilt();
99
100 TQRect r = m_painter->dirtyRect();
101 if ( r.isValid() ) {
102 m_dirtyRect = r;
103
104 r = TQRect(r.left()-1, r.top()-1, r.width()+2, r.height()+2); //needed to update selectionvisualization
105 if (!m_paintOnSelection) {
106 m_currentImage->activeLayer()->setDirty(r);
107 }
108 else {
109 m_target->setDirty(r);
110 // Just update the canvas. XXX: After 1.5, find a better way to make sure tools don't set dirty what they didn't touch.
111 m_subject->canvasController()->updateCanvas( r );
112 }
113 }
114 }
115}
116
117void KisToolFreehand::buttonRelease(KisButtonReleaseEvent* e)
118{
119 if (e->button() == TQt::LeftButton && m_mode == PAINT) {
120 endPaint();
121 m_currentImage->activeDevice()->lock( false );
122 kdDebug() << ">>>>>>>>>>>>>>>>>>>UNLocking paint device\n";
123
124 }
125 KisToolPaint::buttonRelease(e);
126}
127
128void KisToolFreehand::move(KisMoveEvent *e)
129{
130 if (m_mode == PAINT) {
131
132 paintLine(m_prevPos, m_prevPressure, m_prevXTilt, m_prevYTilt, e->pos(), e->pressure(), e->xTilt(), e->yTilt());
133
134 m_prevPos = e->pos();
135 m_prevPressure = e->pressure();
136 m_prevXTilt = e->xTilt();
137 m_prevYTilt = e->yTilt();
138
139 TQRect r = m_painter->dirtyRect();
140
141 if (r.isValid()) {
142 m_dirtyRect |= r;
143
144 if (!m_paintOnSelection) {
145 m_currentImage->activeLayer()->setDirty(r);
146 }
147 else {
148 // Just update the canvas
149 r = TQRect(r.left()-1, r.top()-1, r.width()+2, r.height()+2); //needed to update selectionvisualization
150 m_target->setDirty(r);
151 m_subject->canvasController()->updateCanvas( r );
152 }
153 }
154 }
155}
156
157void KisToolFreehand::initPaint(KisEvent *)
158{
159 if (!m_currentImage || !m_currentImage->activeDevice()) return;
160
161 m_mode = PAINT;
162 m_dragDist = 0;
163
164 // Create painter
165 KisPaintDeviceSP device;
166 if (m_currentImage && (device = m_currentImage->activeDevice())) {
167
168 if (m_painter)
169 delete m_painter;
170
171 if (!m_paintIncremental) {
172 if (m_currentImage->undo())
173 m_currentImage->undoAdapter()->beginMacro(m_transactionText);
174
175 KisLayerSupportsIndirectPainting* layer;
176 if ((layer = dynamic_cast<KisLayerSupportsIndirectPainting*>(
177 m_currentImage->activeLayer().data()))) {
178
179 // Hack for the painting of single-layered layers using indirect painting,
180 // because the group layer would not have a correctly synched cache (
181 // because of an optimization that would happen, having this layer as
182 // projection).
183 KisLayer* l = layer->layer();
184 KisPaintLayer* pl = dynamic_cast<KisPaintLayer*>(l);
185 if (l->parent() && (l->parent()->parent() == 0)
186 && (l->parent()->childCount() == 1)
187 && l->parent()->paintLayerInducesProjectionOptimization(pl)) {
188 // If there's a mask, device could've been the mask. The induce function
189 // should catch this, but better safe than sorry
190 l->parent()->resetProjection(pl->paintDevice());
191 }
192
193 m_target = new KisPaintDevice(m_currentImage->activeLayer(),
194 device->colorSpace());
195 layer->setTemporaryTarget(m_target);
196 layer->setTemporaryCompositeOp(m_compositeOp);
197 layer->setTemporaryOpacity(m_opacity);
198
199 if (device->hasSelection())
200 m_target->setSelection(device->selection());
201 }
202 } else {
203 m_target = device;
204 }
205 if(m_target->hasSelection()) m_target->selection()->startCachingExactRect();
206 m_painter = new KisPainter( m_target );
207 TQ_CHECK_PTR(m_painter);
208 m_source = device;
209 if (currentImage()->undo()) m_painter->beginTransaction(m_transactionText);
210 }
211
212 m_painter->setPaintColor(m_subject->fgColor());
213 m_painter->setBackgroundColor(m_subject->bgColor());
214 m_painter->setBrush(m_subject->currentBrush());
215
216
217 // if you're drawing on a temporary layer, the layer already sets this
218 if (m_paintIncremental) {
219 m_painter->setCompositeOp(m_compositeOp);
220 m_painter->setOpacity(m_opacity);
221 } else {
222 m_painter->setCompositeOp(COMPOSITE_ALPHA_DARKEN);
223 m_painter->setOpacity( OPACITY_OPAQUE );
224
225 }
226
227/* kdDebug() << "target: " << m_target << "( " << m_target->name() << " )"
228 << " source: " << m_source << "( " << m_source->name() << " )"
229 << ", incremental " << m_paintIncremental
230 << ", paint on selection: " << m_paintOnSelection
231 << ", active device has selection: " << device->hasSelection()
232 << ", target has selection: " << m_target->hasSelection()
233 << endl;
234*/
235}
236
237void KisToolFreehand::endPaint()
238{
239 m_mode = HOVER;
240 if (m_currentImage) {
241
242 if (m_painter) {
243 // If painting in mouse release, make sure painter
244 // is destructed or end()ed
245 if (!m_paintIncremental) {
246 if (m_currentImage->undo())
247 m_painter->endTransaction();
248 KisPainter painter( m_source );
249 painter.setCompositeOp(m_compositeOp);
250 if (m_currentImage->undo())
251 painter.beginTransaction(m_transactionText);
252 painter.bitBlt(m_dirtyRect.x(), m_dirtyRect.y(), m_compositeOp, m_target,
253 m_opacity,
254 m_dirtyRect.x(), m_dirtyRect.y(),
255 m_dirtyRect.width(), m_dirtyRect.height());
256
257 KisLayerSupportsIndirectPainting* layer =
258 dynamic_cast<KisLayerSupportsIndirectPainting*>(m_source->parentLayer());
259 layer->setTemporaryTarget(0);
260 m_source->parentLayer()->setDirty(m_dirtyRect);
261
262 if (m_currentImage->undo()) {
263 m_currentImage->undoAdapter()->addCommand(painter.endTransaction());
264 m_currentImage->undoAdapter()->endMacro();
265 }
266 } else {
267 if (m_currentImage->undo())
268 m_currentImage->undoAdapter()->addCommand(m_painter->endTransaction());
269 }
270 }
271 delete m_painter;
272 m_painter = 0;
273 notifyModified();
274 if(m_target->hasSelection()) m_target->selection()->stopCachingExactRect();
275 }
276}
277
278void KisToolFreehand::paintAt(const KisPoint &pos,
279 const double pressure,
280 const double xTilt,
281 const double yTilt)
282{
283 painter()->paintAt(pos, pressure, xTilt, yTilt);
284}
285
286void KisToolFreehand::paintLine(const KisPoint & pos1,
287 const double pressure1,
288 const double xtilt1,
289 const double ytilt1,
290 const KisPoint & pos2,
291 const double pressure2,
292 const double xtilt2,
293 const double ytilt2)
294{
295 m_dragDist = painter()->paintLine(pos1, pressure1, xtilt1, ytilt1, pos2, pressure2, xtilt2, ytilt2, m_dragDist);
296}
297
298
299KisImageSP KisToolFreehand::currentImage()
300{
301 return m_currentImage;
302}
303
304
305void KisToolFreehand::paintOutline(const KisPoint& point) {
306 if (!m_subject) {
307 return;
308 }
309
310 KisCanvasController *controller = m_subject->canvasController();
311
312 if (currentImage() && !currentImage()->bounds().contains(point.floorTQPoint())) {
313 if (m_paintedOutline) {
314 controller->kiscanvas()->update();
315 m_paintedOutline = false;
316 }
317 return;
318 }
319
320 KisCanvas *canvas = controller->kiscanvas();
321 canvas->repaint();
322
323 KisBrush *brush = m_subject->currentBrush();
324 // There may not be a brush present, and we shouldn't crash in that case
325 if (brush) {
326 KisCanvasPainter gc(canvas);
327 TQPen pen(TQt::SolidLine);
328
329 KisPoint hotSpot = brush->hotSpot();
330
331 gc.setRasterOp(TQt::NotROP);
332 gc.setPen(pen);
333 gc.setViewport(0, 0, static_cast<TQ_INT32>(canvas->width() * m_subject->zoomFactor()),
334 static_cast<TQ_INT32>(canvas->height() * m_subject->zoomFactor()));
335 gc.translate((- controller->horzValue()) / m_subject->zoomFactor(),
336 (- controller->vertValue()) / m_subject->zoomFactor());
337
338 KisPoint topLeft = point - hotSpot;
339
340 if (m_subject->currentPaintop().id() == "pen") {
341 // Pen paints on whole pixels only.
342 topLeft = topLeft.roundTQPoint();
343 }
344
345 gc.translate(topLeft.x(), topLeft.y());
346
347 KisBoundaryPainter::paint(brush->boundary(), gc);
348 m_paintedOutline = true;
349 }
350}
351
352
353#include "kis_tool_freehand.moc"
354
Interface for classes that implement a canvas; i.e., a widget where KisImages are painted onto.
virtual TQ_INT32 vertValue() const =0
virtual TQ_INT32 horzValue() const =0
virtual KisCanvas * kiscanvas() const =0
KisCanvasSubject is part of the Observer pattern.
Abstract class that represents the concept of a Layer in Chalk.
Definition kis_layer.h:42
virtual KisGroupLayerSP parent() const
Returns the parent layer of a layer.
A paint device contains the actual pixel data and offers methods to read and write pixels.
This layer is of a type that can be painted on.
KisPainter contains the graphics primitives necessary to draw on a KisPaintDevice.
Definition kis_painter.h:56
A double-based point class that can return it's coordinates approximated to integers.
Definition kis_point.h:28