cairo_gal.cpp 29 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
 * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
 *
 * CAIRO_GAL - Graphics Abstraction Layer for Cairo
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

Maciej Suminski's avatar
Maciej Suminski committed
27
#include <wx/image.h>
28 29 30
#include <wx/log.h>

#include <gal/cairo/cairo_gal.h>
31
#include <gal/cairo/cairo_compositor.h>
32
#include <gal/definitions.h>
33

34 35
#include <limits>

36
using namespace KIGFX;
37

38 39 40 41

const float CAIRO_GAL::LAYER_ALPHA = 0.8;


42
CAIRO_GAL::CAIRO_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
43
        wxEvtHandler* aPaintListener, const wxString& aName ) :
44
    wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName )
45 46 47 48 49
{
    parentWindow  = aParent;
    mouseListener = aMouseListener;
    paintListener = aPaintListener;

50
    // Initialize the flags
51 52 53
    isGrouping          = false;
    isInitialized       = false;
    isDeleteSavedPixels = false;
54
    validCompositor     = false;
55
    groupCounter        = 0;
56 57

    // Connecting the event handlers
58
    Connect( wxEVT_PAINT,       wxPaintEventHandler( CAIRO_GAL::onPaint ) );
Maciej Suminski's avatar
Maciej Suminski committed
59

60
    // Mouse events are skipped to the parent
61 62 63 64 65 66 67 68 69 70
    Connect( wxEVT_MOTION,          wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_DOWN,       wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_UP,         wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_DCLICK,     wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_DOWN,     wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_UP,       wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_DCLICK,   wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_DOWN,      wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_UP,        wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_DCLICK,    wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
71
    Connect( wxEVT_MOUSEWHEEL,      wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
72
#if defined _WIN32 || defined _WIN64
73
    Connect( wxEVT_ENTER_WINDOW,    wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
74
#endif
75

76
    SetSize( aParent->GetSize() );
77
    screenSize = VECTOR2I( aParent->GetSize() );
78 79 80

    cursorPixels = NULL;
    cursorPixelsSaved = NULL;
81
    initCursor();
82

Maciej Suminski's avatar
Maciej Suminski committed
83 84 85
    // Grid color settings are different in Cairo and OpenGL
    SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) );

86
    // Allocate memory for pixel storage
87
    allocateBitmaps();
88 89

    initSurface();
90 91 92 93 94
}


CAIRO_GAL::~CAIRO_GAL()
{
95
    deinitSurface();
96
    deleteBitmaps();
97

Maciej Suminski's avatar
Maciej Suminski committed
98 99 100
    delete cursorPixels;
    delete cursorPixelsSaved;

101
    ClearCache();
102 103 104
}


Maciej Suminski's avatar
Maciej Suminski committed
105
void CAIRO_GAL::BeginDrawing()
106 107
{
    initSurface();
108

109 110 111 112 113
    if( !validCompositor )
        setCompositor();

    compositor->SetMainContext( context );
    compositor->SetBuffer( mainBuffer );
114

115 116
    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
    cairo_push_group( currentContext );
117 118 119 120 121 122 123 124
}


void CAIRO_GAL::EndDrawing()
{
    // Force remaining objects to be drawn
    Flush();

125 126
    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
    cairo_pop_group_to_source( currentContext );
127
    cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
128 129 130 131

    // Merge buffers on the screen
    compositor->DrawBuffer( mainBuffer );
    compositor->DrawBuffer( overlayBuffer );
132

133 134 135
    // This code was taken from the wxCairo example - it's not the most efficient one
    // Here is a good place for optimizations

136
    // Now translate the raw context data from the format stored
137 138
    // by cairo into a format understood by wxImage.
    unsigned char* wxOutputPtr = wxOutput;
139

140 141 142
    for( size_t count = 0; count < bufferSize; count++ )
    {
        unsigned int value = bitmapBuffer[count];
143 144 145
        *wxOutputPtr++ = ( value >> 16 ) & 0xff;  // Red pixel
        *wxOutputPtr++ = ( value >> 8 ) & 0xff;   // Green pixel
        *wxOutputPtr++ = value & 0xff;            // Blue pixel
146 147
    }

148
    wxImage      img( screenSize.x, screenSize.y, (unsigned char*) wxOutput, true );
149 150 151 152 153
    wxBitmap     bmp( img );
    wxClientDC   client_dc( this );
    wxBufferedDC dc;
    dc.Init( &client_dc, bmp );

154 155 156
    // Now it is the time to blit the mouse cursor
    blitCursor( dc );

157
    deinitSurface();
158 159 160
}


161
void CAIRO_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
162
{
163 164
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
165 166 167 168
    isElementAdded = true;
}


169 170
void CAIRO_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
                             double aWidth )
171 172 173
{
    if( isFillEnabled )
    {
174
        // Filled tracks mode
175
        SetLineWidth( aWidth );
176

177 178
        cairo_move_to( currentContext, (double) aStartPoint.x, (double) aStartPoint.y );
        cairo_line_to( currentContext, (double) aEndPoint.x, (double) aEndPoint.y );
179 180 181
    }
    else
    {
182
        // Outline mode for tracks
183 184 185 186
        VECTOR2D startEndVector = aEndPoint - aStartPoint;
        double   lineAngle      = atan2( startEndVector.y, startEndVector.x );
        double   lineLength     = startEndVector.EuclideanNorm();

187
        cairo_save( currentContext );
188

189 190
        cairo_translate( currentContext, aStartPoint.x, aStartPoint.y );
        cairo_rotate( currentContext, lineAngle );
191

Maciej Suminski's avatar
Maciej Suminski committed
192
        cairo_arc( currentContext, 0.0,        0.0, aWidth / 2.0,  M_PI / 2.0, 3.0 * M_PI / 2.0 );
193
        cairo_arc( currentContext, lineLength, 0.0, aWidth / 2.0, -M_PI / 2.0, M_PI / 2.0 );
194

195 196
        cairo_move_to( currentContext, 0.0,        aWidth / 2.0 );
        cairo_line_to( currentContext, lineLength, aWidth / 2.0 );
197

198 199
        cairo_move_to( currentContext, 0.0,        -aWidth / 2.0 );
        cairo_line_to( currentContext, lineLength, -aWidth / 2.0 );
200

201
        cairo_restore( currentContext );
202 203 204 205 206 207
    }

    isElementAdded = true;
}


208
void CAIRO_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
209 210
{
    // A circle is drawn using an arc
211 212
    cairo_new_sub_path( currentContext );
    cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, 0.0, 2 * M_PI );
213

214 215 216 217
    isElementAdded = true;
}


218
void CAIRO_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle,
219 220
                         double aEndAngle )
{
221 222
    SWAP( aStartAngle, >, aEndAngle );

223 224
    cairo_new_sub_path( currentContext );
    cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, aStartAngle, aEndAngle );
225

226 227 228 229 230 231 232 233 234 235 236 237 238
    if( isFillEnabled )
    {
        VECTOR2D startPoint( cos( aStartAngle ) * aRadius + aCenterPoint.x,
                             sin( aStartAngle ) * aRadius + aCenterPoint.y );
        VECTOR2D endPoint( cos( aEndAngle ) * aRadius + aCenterPoint.x,
                           sin( aEndAngle ) * aRadius + aCenterPoint.y );

        cairo_move_to( currentContext, aCenterPoint.x, aCenterPoint.y );
        cairo_line_to( currentContext, startPoint.x, startPoint.y );
        cairo_line_to( currentContext, endPoint.x, endPoint.y );
        cairo_close_path( currentContext );
    }

239 240 241 242
    isElementAdded = true;
}


Maciej Suminski's avatar
Maciej Suminski committed
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
void CAIRO_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{
    // Calculate the diagonal points
    VECTOR2D diagonalPointA( aEndPoint.x,  aStartPoint.y );
    VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );

    // The path is composed from 4 segments
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, diagonalPointA.x, diagonalPointA.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
    cairo_line_to( currentContext, diagonalPointB.x, diagonalPointB.y );
    cairo_close_path( currentContext );

    isElementAdded = true;
}


260 261 262
void CAIRO_GAL::DrawPolyline( std::deque<VECTOR2D>& aPointList )
{
    // Iterate over the point list and draw the segments
263 264 265
    std::deque<VECTOR2D>::const_iterator it = aPointList.begin();

    cairo_move_to( currentContext, it->x, it->y );
Maciej Suminski's avatar
Maciej Suminski committed
266

267
    for( ++it; it != aPointList.end(); ++it )
268
    {
269
        cairo_line_to( currentContext, it->x, it->y );
270 271 272 273 274 275 276 277 278
    }

    isElementAdded = true;
}


void CAIRO_GAL::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
{
    // Iterate over the point list and draw the polygon
279 280 281
    std::deque<VECTOR2D>::const_iterator it = aPointList.begin();

    cairo_move_to( currentContext, it->x, it->y );
Maciej Suminski's avatar
Maciej Suminski committed
282

283
    for( ++it; it != aPointList.end(); ++it )
284
    {
285
        cairo_line_to( currentContext, it->x, it->y );
286 287 288 289 290 291
    }

    isElementAdded = true;
}


Maciej Suminski's avatar
Maciej Suminski committed
292 293
void CAIRO_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
                           const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint )
294
{
295
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
Maciej Suminski's avatar
Maciej Suminski committed
296 297
    cairo_curve_to( currentContext, aControlPointA.x, aControlPointA.y, aControlPointB.x,
                    aControlPointB.y, aEndPoint.x, aEndPoint.y );
298
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
299 300 301 302 303

    isElementAdded = true;
}


Maciej Suminski's avatar
Maciej Suminski committed
304
void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
305
{
306
    screenSize = VECTOR2I( aWidth, aHeight );
307

Maciej Suminski's avatar
Maciej Suminski committed
308 309 310 311
    // Recreate the bitmaps
    deleteBitmaps();
    allocateBitmaps();

312 313
    if( validCompositor )
        compositor->Resize( aWidth, aHeight );
314

315 316
    validCompositor = false;

Maciej Suminski's avatar
Maciej Suminski committed
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    SetSize( wxSize( aWidth, aHeight ) );
}


bool CAIRO_GAL::Show( bool aShow )
{
    bool s = wxWindow::Show( aShow );

    if( aShow )
        wxWindow::Raise();

    return s;
}


void CAIRO_GAL::Flush()
{
    storePath();
335 336 337
}


338
void CAIRO_GAL::ClearScreen( const COLOR4D& aColor )
339
{
340 341
    backgroundColor = aColor;
    cairo_set_source_rgb( currentContext, aColor.r, aColor.g, aColor.b );
Maciej Suminski's avatar
Maciej Suminski committed
342 343
    cairo_rectangle( currentContext, 0.0, 0.0, screenSize.x, screenSize.y );
    cairo_fill( currentContext );
344 345 346 347 348 349 350 351 352 353
}


void CAIRO_GAL::SetIsFill( bool aIsFillEnabled )
{
    storePath();
    isFillEnabled = aIsFillEnabled;

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
354
        GROUP_ELEMENT groupElement;
355 356
        groupElement.command = CMD_SET_FILL;
        groupElement.boolArgument = aIsFillEnabled;
357
        currentGroup->push_back( groupElement );
358 359 360 361 362 363 364 365 366 367 368
    }
}


void CAIRO_GAL::SetIsStroke( bool aIsStrokeEnabled )
{
    storePath();
    isStrokeEnabled = aIsStrokeEnabled;

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
369
        GROUP_ELEMENT groupElement;
370 371
        groupElement.command = CMD_SET_STROKE;
        groupElement.boolArgument = aIsStrokeEnabled;
372
        currentGroup->push_back( groupElement );
373 374 375 376
    }
}


377
void CAIRO_GAL::SetStrokeColor( const COLOR4D& aColor )
378 379 380 381 382 383
{
    storePath();
    strokeColor = aColor;

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
384
        GROUP_ELEMENT groupElement;
385 386 387 388 389
        groupElement.command = CMD_SET_STROKECOLOR;
        groupElement.arguments[0] = strokeColor.r;
        groupElement.arguments[1] = strokeColor.g;
        groupElement.arguments[2] = strokeColor.b;
        groupElement.arguments[3] = strokeColor.a;
390
        currentGroup->push_back( groupElement );
391 392 393 394
    }
}


395
void CAIRO_GAL::SetFillColor( const COLOR4D& aColor )
396 397 398 399 400 401
{
    storePath();
    fillColor = aColor;

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
402
        GROUP_ELEMENT groupElement;
403 404 405 406 407
        groupElement.command = CMD_SET_FILLCOLOR;
        groupElement.arguments[0] = fillColor.r;
        groupElement.arguments[1] = fillColor.g;
        groupElement.arguments[2] = fillColor.b;
        groupElement.arguments[3] = fillColor.a;
408
        currentGroup->push_back( groupElement );
409 410 411 412 413 414 415 416 417
    }
}


void CAIRO_GAL::SetLineWidth( double aLineWidth )
{
    storePath();

    lineWidth = aLineWidth;
418

419 420
    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
421
        GROUP_ELEMENT groupElement;
422 423
        groupElement.command = CMD_SET_LINE_WIDTH;
        groupElement.arguments[0] = aLineWidth;
424
        currentGroup->push_back( groupElement );
425
    }
426 427 428 429 430 431 432 433
    else
    {
        // Make lines appear at least 1 pixel wide, no matter of zoom
        double x = 1.0, y = 1.0;
        cairo_device_to_user_distance( currentContext, &x, &y );
        double minWidth = std::min( fabs( x ), fabs( y ) );
        cairo_set_line_width( currentContext, std::max( aLineWidth, minWidth ) );
    }
434 435 436
}


437 438 439 440
void CAIRO_GAL::SetLayerDepth( double aLayerDepth )
{
    super::SetLayerDepth( aLayerDepth );

441 442 443
    if( isInitialized )
    {
        storePath();
444

445
        cairo_pop_group_to_source( currentContext );
446
        cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
447 448

        cairo_push_group( currentContext );
449
    }
450 451 452
}


453
void CAIRO_GAL::Transform( const MATRIX3x3D& aTransformation )
454 455 456 457 458 459 460 461 462 463 464
{
    cairo_matrix_t cairoTransformation;

    cairo_matrix_init( &cairoTransformation,
                       aTransformation.m_data[0][0],
                       aTransformation.m_data[1][0],
                       aTransformation.m_data[0][1],
                       aTransformation.m_data[1][1],
                       aTransformation.m_data[0][2],
                       aTransformation.m_data[1][2] );

465
    cairo_transform( currentContext, &cairoTransformation );
466 467 468 469 470 471 472 473 474
}


void CAIRO_GAL::Rotate( double aAngle )
{
    storePath();

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
475
        GROUP_ELEMENT groupElement;
476 477
        groupElement.command = CMD_ROTATE;
        groupElement.arguments[0] = aAngle;
478
        currentGroup->push_back( groupElement );
479
    }
480 481 482 483
    else
    {
        cairo_rotate( currentContext, aAngle );
    }
484 485 486
}


487
void CAIRO_GAL::Translate( const VECTOR2D& aTranslation )
488 489 490 491 492
{
    storePath();

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
493
        GROUP_ELEMENT groupElement;
494 495 496
        groupElement.command = CMD_TRANSLATE;
        groupElement.arguments[0] = aTranslation.x;
        groupElement.arguments[1] = aTranslation.y;
497
        currentGroup->push_back( groupElement );
498
    }
499 500 501 502
    else
    {
        cairo_translate( currentContext, aTranslation.x, aTranslation.y );
    }
503 504 505
}


506
void CAIRO_GAL::Scale( const VECTOR2D& aScale )
507 508 509 510 511
{
    storePath();

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
512
        GROUP_ELEMENT groupElement;
513 514 515
        groupElement.command = CMD_SCALE;
        groupElement.arguments[0] = aScale.x;
        groupElement.arguments[1] = aScale.y;
516
        currentGroup->push_back( groupElement );
517
    }
518 519 520 521
    else
    {
        cairo_scale( currentContext, aScale.x, aScale.y );
    }
522 523 524 525 526 527 528 529 530
}


void CAIRO_GAL::Save()
{
    storePath();

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
531
        GROUP_ELEMENT groupElement;
532
        groupElement.command = CMD_SAVE;
533
        currentGroup->push_back( groupElement );
534
    }
535 536 537 538
    else
    {
        cairo_save( currentContext );
    }
539 540 541 542 543 544 545 546 547
}


void CAIRO_GAL::Restore()
{
    storePath();

    if( isGrouping )
    {
Maciej Suminski's avatar
Maciej Suminski committed
548
        GROUP_ELEMENT groupElement;
549
        groupElement.command = CMD_RESTORE;
550
        currentGroup->push_back( groupElement );
551
    }
552 553 554 555
    else
    {
        cairo_restore( currentContext );
    }
556 557 558 559 560
}


int CAIRO_GAL::BeginGroup()
{
561 562
    initSurface();

563 564 565
    // If the grouping is started: the actual path is stored in the group, when
    // a attribute was changed or when grouping stops with the end group method.
    storePath();
566

Maciej Suminski's avatar
Maciej Suminski committed
567
    GROUP group;
568
    int groupNumber = getNewGroupNumber();
569 570
    groups.insert( std::make_pair( groupNumber, group ) );
    currentGroup = &groups[groupNumber];
571
    isGrouping   = true;
572 573

    return groupNumber;
574 575 576 577 578 579 580
}


void CAIRO_GAL::EndGroup()
{
    storePath();
    isGrouping = false;
581 582

    deinitSurface();
583 584 585
}


Maciej Suminski's avatar
Maciej Suminski committed
586
void CAIRO_GAL::DrawGroup( int aGroupNumber )
587
{
Maciej Suminski's avatar
Maciej Suminski committed
588 589
    // This method implements a small Virtual Machine - all stored commands
    // are executed; nested calling is also possible
590 591 592

    storePath();

Maciej Suminski's avatar
Maciej Suminski committed
593
    for( GROUP::iterator it = groups[aGroupNumber].begin();
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
         it != groups[aGroupNumber].end(); ++it )
    {
        switch( it->command )
        {
        case CMD_SET_FILL:
            isFillEnabled = it->boolArgument;
            break;

        case CMD_SET_STROKE:
            isStrokeEnabled = it->boolArgument;
            break;

        case CMD_SET_FILLCOLOR:
            fillColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
                                 it->arguments[3] );
            break;

        case CMD_SET_STROKECOLOR:
            strokeColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
                                   it->arguments[3] );
            break;

        case CMD_SET_LINE_WIDTH:
617 618 619
            {
                // Make lines appear at least 1 pixel wide, no matter of zoom
                double x = 1.0, y = 1.0;
620
                cairo_device_to_user_distance( currentContext, &x, &y );
621
                double minWidth = std::min( fabs( x ), fabs( y ) );
622
                cairo_set_line_width( currentContext, std::max( it->arguments[0], minWidth ) );
623
            }
624 625
            break;

626

627
        case CMD_STROKE_PATH:
628 629 630
            cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g, strokeColor.b );
            cairo_append_path( currentContext, it->cairoPath );
            cairo_stroke( currentContext );
631 632 633
            break;

        case CMD_FILL_PATH:
634 635 636
            cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
            cairo_append_path( currentContext, it->cairoPath );
            cairo_fill( currentContext );
637 638 639 640 641 642
            break;

        case CMD_TRANSFORM:
            cairo_matrix_t matrix;
            cairo_matrix_init( &matrix, it->arguments[0], it->arguments[1], it->arguments[2],
                               it->arguments[3], it->arguments[4], it->arguments[5] );
643
            cairo_transform( currentContext, &matrix );
644 645 646
            break;

        case CMD_ROTATE:
647
            cairo_rotate( currentContext, it->arguments[0] );
648 649 650
            break;

        case CMD_TRANSLATE:
651
            cairo_translate( currentContext, it->arguments[0], it->arguments[1] );
652 653 654
            break;

        case CMD_SCALE:
655
            cairo_scale( currentContext, it->arguments[0], it->arguments[1] );
656 657 658
            break;

        case CMD_SAVE:
659
            cairo_save( currentContext );
660 661 662
            break;

        case CMD_RESTORE:
663
            cairo_restore( currentContext );
664 665 666 667 668 669 670 671 672 673
            break;

        case CMD_CALL_GROUP:
            DrawGroup( it->intArgument );
            break;
        }
    }
}


674 675 676 677
void CAIRO_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
{
    storePath();

Maciej Suminski's avatar
Maciej Suminski committed
678
    for( GROUP::iterator it = groups[aGroupNumber].begin();
679 680 681 682 683 684 685 686 687 688 689 690 691
         it != groups[aGroupNumber].end(); ++it )
    {
        if( it->command == CMD_SET_FILLCOLOR || it->command == CMD_SET_STROKECOLOR )
        {
            it->arguments[0] = aNewColor.r;
            it->arguments[1] = aNewColor.g;
            it->arguments[2] = aNewColor.b;
            it->arguments[3] = aNewColor.a;
        }
    }
}


692 693 694 695 696 697 698
void CAIRO_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
{
    // Cairo does not have any possibilities to change the depth coordinate of stored items,
    // it depends only on the order of drawing
}


Maciej Suminski's avatar
Maciej Suminski committed
699
void CAIRO_GAL::DeleteGroup( int aGroupNumber )
700 701
{
    storePath();
Maciej Suminski's avatar
Maciej Suminski committed
702 703

    // Delete the Cairo paths
Maciej Suminski's avatar
Maciej Suminski committed
704
    std::deque<GROUP_ELEMENT>::iterator it, end;
Maciej Suminski's avatar
Maciej Suminski committed
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758

    for( it = groups[aGroupNumber].begin(), end = groups[aGroupNumber].end(); it != end; ++it )
    {
        if( it->command == CMD_FILL_PATH || it->command == CMD_STROKE_PATH )
        {
            cairo_path_destroy( it->cairoPath );
        }
    }

    // Delete the group
    groups.erase( aGroupNumber );
}


void CAIRO_GAL::ClearCache()
{
    for( int i = groups.size() - 1; i >= 0; --i )
    {
        DeleteGroup( i );
    }
}


void CAIRO_GAL::SaveScreen()
{
    // Copy the current bitmap to the backup buffer
    int offset = 0;

    for( int j = 0; j < screenSize.y; j++ )
    {
        for( int i = 0; i < stride; i++ )
        {
            bitmapBufferBackup[offset + i] = bitmapBuffer[offset + i];
            offset += stride;
        }
    }
}


void CAIRO_GAL::RestoreScreen()
{
    int offset = 0;

    for( int j = 0; j < screenSize.y; j++ )
    {
        for( int i = 0; i < stride; i++ )
        {
            bitmapBuffer[offset + i] = bitmapBufferBackup[offset + i];
            offset += stride;
        }
    }
}


Maciej Suminski's avatar
Maciej Suminski committed
759
void CAIRO_GAL::SetTarget( RENDER_TARGET aTarget )
Maciej Suminski's avatar
Maciej Suminski committed
760 761 762
{
    // If the compositor is not set, that means that there is a recaching process going on
    // and we do not need the compositor now
763
    if( !validCompositor )
Maciej Suminski's avatar
Maciej Suminski committed
764 765 766
        return;

    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
767 768 769 770 771
    if( isInitialized )
    {
        storePath();

        cairo_pop_group_to_source( currentContext );
772
        cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
773
    }
Maciej Suminski's avatar
Maciej Suminski committed
774 775 776 777 778 779 780 781 782 783 784 785 786 787

    switch( aTarget )
    {
    default:
    case TARGET_CACHED:
    case TARGET_NONCACHED:
        compositor->SetBuffer( mainBuffer );
        break;

    case TARGET_OVERLAY:
        compositor->SetBuffer( overlayBuffer );
        break;
    }

788 789
    if( isInitialized )
        cairo_push_group( currentContext );
790 791 792 793 794

    currentTarget = aTarget;
}


Maciej Suminski's avatar
Maciej Suminski committed
795
RENDER_TARGET CAIRO_GAL::GetTarget() const
796 797
{
    return currentTarget;
Maciej Suminski's avatar
Maciej Suminski committed
798 799 800
}


Maciej Suminski's avatar
Maciej Suminski committed
801
void CAIRO_GAL::ClearTarget( RENDER_TARGET aTarget )
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
{
    // Save the current state
    unsigned int currentBuffer = compositor->GetBuffer();

    switch( aTarget )
    {
    // Cached and noncached items are rendered to the same buffer
    default:
    case TARGET_CACHED:
    case TARGET_NONCACHED:
        compositor->SetBuffer( mainBuffer );
        break;

    case TARGET_OVERLAY:
        compositor->SetBuffer( overlayBuffer );
        break;
    }
819

820 821 822 823 824 825 826
    compositor->ClearBuffer();

    // Restore the previous state
    compositor->SetBuffer( currentBuffer );
}


827 828 829 830 831 832 833
void CAIRO_GAL::SetCursorSize( unsigned int aCursorSize )
{
    GAL::SetCursorSize( aCursorSize );
    initCursor();
}


834
void CAIRO_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
835
{
836
    // Now we should only store the position of the mouse cursor
837
    // The real drawing routines are in blitCursor()
838 839
    cursorPosition = VECTOR2D( aCursorPosition.x - cursorSize / 2,
                               aCursorPosition.y - cursorSize / 2 );
Maciej Suminski's avatar
Maciej Suminski committed
840
}
841 842


Maciej Suminski's avatar
Maciej Suminski committed
843 844 845 846 847 848
void CAIRO_GAL::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
    cairo_set_source_rgb( currentContext, gridColor.r, gridColor.g, gridColor.b );
    cairo_stroke( currentContext );
849 850 851 852 853 854 855 856 857 858 859 860 861
}


void CAIRO_GAL::storePath()
{
    if( isElementAdded )
    {
        isElementAdded = false;

        if( !isGrouping )
        {
            if( isFillEnabled )
            {
862 863
                cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
                cairo_fill_preserve( currentContext );
864 865 866 867
            }

            if( isStrokeEnabled )
            {
Maciej Suminski's avatar
Maciej Suminski committed
868 869
                cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g,
                                      strokeColor.b );
870
                cairo_stroke_preserve( currentContext );
871 872 873 874 875 876 877 878 879
            }
        }
        else
        {
            // Copy the actual path, append it to the global path list
            // then check, if the path needs to be stroked/filled and
            // add this command to the group list;
            if( isStrokeEnabled )
            {
Maciej Suminski's avatar
Maciej Suminski committed
880
                GROUP_ELEMENT groupElement;
881
                groupElement.cairoPath = cairo_copy_path( currentContext );
882
                groupElement.command   = CMD_STROKE_PATH;
883
                currentGroup->push_back( groupElement );
884 885 886 887
            }

            if( isFillEnabled )
            {
Maciej Suminski's avatar
Maciej Suminski committed
888
                GROUP_ELEMENT groupElement;
889
                groupElement.cairoPath = cairo_copy_path( currentContext );
890
                groupElement.command   = CMD_FILL_PATH;
891
                currentGroup->push_back( groupElement );
892 893 894
            }
        }

895
        cairo_new_path( currentContext );
896 897 898 899
    }
}


900
void CAIRO_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
Maciej Suminski's avatar
Maciej Suminski committed
901 902 903 904 905 906 907 908 909 910 911
{
    PostPaint();
}


void CAIRO_GAL::skipMouseEvent( wxMouseEvent& aEvent )
{
    // Post the mouse event to the event listener registered in constructor, if any
    if( mouseListener )
        wxPostEvent( mouseListener, aEvent );
}
912 913


914
void CAIRO_GAL::initCursor()
915
{
916 917 918 919 920 921
    if( cursorPixels )
        delete cursorPixels;

    if( cursorPixelsSaved )
        delete cursorPixelsSaved;

922 923
    cursorPixels      = new wxBitmap( cursorSize, cursorSize );
    cursorPixelsSaved = new wxBitmap( cursorSize, cursorSize );
924 925 926 927 928 929

    wxMemoryDC cursorShape( *cursorPixels );

    cursorShape.SetBackground( *wxTRANSPARENT_BRUSH );
    wxColour color( cursorColor.r * cursorColor.a * 255, cursorColor.g * cursorColor.a * 255,
                    cursorColor.b * cursorColor.a * 255, 255 );
Maciej Suminski's avatar
Maciej Suminski committed
930
    wxPen pen = wxPen( color );
931 932 933
    cursorShape.SetPen( pen );
    cursorShape.Clear();

934 935
    cursorShape.DrawLine( 0, cursorSize / 2, cursorSize, cursorSize / 2 );
    cursorShape.DrawLine( cursorSize / 2, 0, cursorSize / 2, cursorSize );
936 937 938
}


939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
void CAIRO_GAL::blitCursor( wxBufferedDC& clientDC )
{
    if( !isCursorEnabled )
        return;

    wxMemoryDC cursorSave( *cursorPixelsSaved );
    wxMemoryDC cursorShape( *cursorPixels );

    if( !isDeleteSavedPixels )
    {
        // Restore pixels that were overpainted by the previous cursor
        clientDC.Blit( savedCursorPosition.x, savedCursorPosition.y,
                       cursorSize, cursorSize, &cursorSave, 0, 0 );
    }
    else
    {
        isDeleteSavedPixels = false;
    }

    // Store pixels that are going to be overpainted
959
    VECTOR2D cursorScreen = ToScreen( cursorPosition ) - cursorSize / 2.0f;
960
    cursorSave.Blit( 0, 0, cursorSize, cursorSize, &clientDC, cursorScreen.x, cursorScreen.y );
961 962

    // Draw the cursor
963
    clientDC.Blit( cursorScreen.x, cursorScreen.y, cursorSize, cursorSize,
964 965
                   &cursorShape, 0, 0, wxOR );

966 967
    savedCursorPosition.x = (wxCoord) cursorScreen.x;
    savedCursorPosition.y = (wxCoord) cursorScreen.y;
968 969 970
}


Maciej Suminski's avatar
Maciej Suminski committed
971
void CAIRO_GAL::allocateBitmaps()
972
{
Maciej Suminski's avatar
Maciej Suminski committed
973 974 975
    // Create buffer, use the system independent Cairo context backend
    stride     = cairo_format_stride_for_width( GAL_FORMAT, screenSize.x );
    bufferSize = stride * screenSize.y;
976

Maciej Suminski's avatar
Maciej Suminski committed
977 978 979
    bitmapBuffer        = new unsigned int[bufferSize];
    bitmapBufferBackup  = new unsigned int[bufferSize];
    wxOutput            = new unsigned char[bufferSize * 3];
980 981 982
}


Maciej Suminski's avatar
Maciej Suminski committed
983
void CAIRO_GAL::deleteBitmaps()
984
{
Maciej Suminski's avatar
Maciej Suminski committed
985 986 987 988
    delete[] bitmapBuffer;
    delete[] bitmapBufferBackup;
    delete[] wxOutput;
}
989 990


Maciej Suminski's avatar
Maciej Suminski committed
991 992
void CAIRO_GAL::initSurface()
{
993 994
    if( isInitialized )
        return;
995

Maciej Suminski's avatar
Maciej Suminski committed
996 997 998 999 1000 1001
    // Create the Cairo surface
    surface = cairo_image_surface_create_for_data( (unsigned char*) bitmapBuffer, GAL_FORMAT,
                                                   screenSize.x, screenSize.y, stride );
    context = cairo_create( surface );
#ifdef __WXDEBUG__
    cairo_status_t status = cairo_status( context );
1002
    wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
Maciej Suminski's avatar
Maciej Suminski committed
1003 1004
#endif /* __WXDEBUG__ */
    currentContext = context;
1005

Maciej Suminski's avatar
Maciej Suminski committed
1006
    cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );
1007

Maciej Suminski's avatar
Maciej Suminski committed
1008
    // Clear the screen
1009
    ClearScreen( backgroundColor );
1010

Maciej Suminski's avatar
Maciej Suminski committed
1011 1012
    // Compute the world <-> screen transformations
    ComputeWorldScreenMatrix();
1013

Maciej Suminski's avatar
Maciej Suminski committed
1014 1015 1016 1017
    cairo_matrix_init( &cairoWorldScreenMatrix, worldScreenMatrix.m_data[0][0],
                       worldScreenMatrix.m_data[1][0], worldScreenMatrix.m_data[0][1],
                       worldScreenMatrix.m_data[1][1], worldScreenMatrix.m_data[0][2],
                       worldScreenMatrix.m_data[1][2] );
1018

Maciej Suminski's avatar
Maciej Suminski committed
1019
    cairo_set_matrix( context, &cairoWorldScreenMatrix );
1020

Maciej Suminski's avatar
Maciej Suminski committed
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
    // Start drawing with a new path
    cairo_new_path( context );
    isElementAdded = true;

    cairo_set_line_join( context, CAIRO_LINE_JOIN_ROUND );
    cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );

    lineWidth = 0;

    isDeleteSavedPixels = true;
    isInitialized = true;
1032 1033 1034
}


Maciej Suminski's avatar
Maciej Suminski committed
1035
void CAIRO_GAL::deinitSurface()
1036
{
Maciej Suminski's avatar
Maciej Suminski committed
1037 1038
    if( !isInitialized )
        return;
1039

Maciej Suminski's avatar
Maciej Suminski committed
1040 1041 1042 1043 1044
    // Destroy Cairo objects
    cairo_destroy( context );
    cairo_surface_destroy( surface );

    isInitialized = false;
1045 1046 1047
}


Maciej Suminski's avatar
Maciej Suminski committed
1048
void CAIRO_GAL::setCompositor()
1049
{
Maciej Suminski's avatar
Maciej Suminski committed
1050 1051 1052 1053 1054
    // Recreate the compositor with the new Cairo context
    compositor.reset( new CAIRO_COMPOSITOR( &currentContext ) );
    compositor->Resize( screenSize.x, screenSize.y );

    // Prepare buffers
1055 1056 1057 1058
    mainBuffer = compositor->CreateBuffer();
    overlayBuffer = compositor->CreateBuffer();

    validCompositor = true;
1059 1060 1061
}


Maciej Suminski's avatar
Maciej Suminski committed
1062
unsigned int CAIRO_GAL::getNewGroupNumber()
1063
{
Maciej Suminski's avatar
Maciej Suminski committed
1064 1065
    wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(),
                  wxT( "There are no free slots to store a group" ) );
1066

Maciej Suminski's avatar
Maciej Suminski committed
1067 1068 1069 1070
    while( groups.find( groupCounter ) != groups.end() )
    {
        groupCounter++;
    }
1071

Maciej Suminski's avatar
Maciej Suminski committed
1072
    return groupCounter++;
1073
}