diff options
Diffstat (limited to 'common/gal/cairo/cairo_gal.cpp')
-rw-r--r-- | common/gal/cairo/cairo_gal.cpp | 1074 |
1 files changed, 1074 insertions, 0 deletions
diff --git a/common/gal/cairo/cairo_gal.cpp b/common/gal/cairo/cairo_gal.cpp new file mode 100644 index 0000000..b7ead4c --- /dev/null +++ b/common/gal/cairo/cairo_gal.cpp @@ -0,0 +1,1074 @@ +/* + * 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 + */ + +#include <wx/image.h> +#include <wx/log.h> + +#include <gal/cairo/cairo_gal.h> +#include <gal/cairo/cairo_compositor.h> +#include <gal/definitions.h> + +#include <limits> + +using namespace KIGFX; + + +const float CAIRO_GAL::LAYER_ALPHA = 0.8; + + +CAIRO_GAL::CAIRO_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener, + wxEvtHandler* aPaintListener, const wxString& aName ) : + wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName ) +{ + parentWindow = aParent; + mouseListener = aMouseListener; + paintListener = aPaintListener; + + // Initialize the flags + isGrouping = false; + isInitialized = false; + isDeleteSavedPixels = false; + validCompositor = false; + groupCounter = 0; + + // Connecting the event handlers + Connect( wxEVT_PAINT, wxPaintEventHandler( CAIRO_GAL::onPaint ) ); + + // Mouse events are skipped to the parent + 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 ) ); + Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) ); +#if defined _WIN32 || defined _WIN64 + Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) ); +#endif + + SetSize( aParent->GetSize() ); + screenSize = VECTOR2I( aParent->GetSize() ); + + cursorPixels = NULL; + cursorPixelsSaved = NULL; + initCursor(); + + // Grid color settings are different in Cairo and OpenGL + SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) ); + + // Allocate memory for pixel storage + allocateBitmaps(); + + initSurface(); +} + + +CAIRO_GAL::~CAIRO_GAL() +{ + deinitSurface(); + deleteBitmaps(); + + delete cursorPixels; + delete cursorPixelsSaved; + + ClearCache(); +} + + +void CAIRO_GAL::BeginDrawing() +{ + initSurface(); + + if( !validCompositor ) + setCompositor(); + + compositor->SetMainContext( context ); + compositor->SetBuffer( mainBuffer ); + + // Cairo grouping prevents display of overlapping items on the same layer in the lighter color + cairo_push_group( currentContext ); +} + + +void CAIRO_GAL::EndDrawing() +{ + // Force remaining objects to be drawn + Flush(); + + // Cairo grouping prevents display of overlapping items on the same layer in the lighter color + cairo_pop_group_to_source( currentContext ); + cairo_paint_with_alpha( currentContext, LAYER_ALPHA ); + + // Merge buffers on the screen + compositor->DrawBuffer( mainBuffer ); + compositor->DrawBuffer( overlayBuffer ); + + // This code was taken from the wxCairo example - it's not the most efficient one + // Here is a good place for optimizations + + // Now translate the raw context data from the format stored + // by cairo into a format understood by wxImage. + unsigned char* wxOutputPtr = wxOutput; + + for( size_t count = 0; count < bufferSize; count++ ) + { + unsigned int value = bitmapBuffer[count]; + *wxOutputPtr++ = ( value >> 16 ) & 0xff; // Red pixel + *wxOutputPtr++ = ( value >> 8 ) & 0xff; // Green pixel + *wxOutputPtr++ = value & 0xff; // Blue pixel + } + + wxImage img( screenSize.x, screenSize.y, (unsigned char*) wxOutput, true ); + wxBitmap bmp( img ); + wxClientDC client_dc( this ); + wxBufferedDC dc; + dc.Init( &client_dc, bmp ); + + // Now it is the time to blit the mouse cursor + blitCursor( dc ); + + deinitSurface(); +} + + +void CAIRO_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint ) +{ + cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y ); + cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y ); + isElementAdded = true; +} + + +void CAIRO_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint, + double aWidth ) +{ + if( isFillEnabled ) + { + // Filled tracks mode + SetLineWidth( aWidth ); + + cairo_move_to( currentContext, (double) aStartPoint.x, (double) aStartPoint.y ); + cairo_line_to( currentContext, (double) aEndPoint.x, (double) aEndPoint.y ); + } + else + { + // Outline mode for tracks + VECTOR2D startEndVector = aEndPoint - aStartPoint; + double lineAngle = atan2( startEndVector.y, startEndVector.x ); + double lineLength = startEndVector.EuclideanNorm(); + + cairo_save( currentContext ); + + cairo_translate( currentContext, aStartPoint.x, aStartPoint.y ); + cairo_rotate( currentContext, lineAngle ); + + cairo_arc( currentContext, 0.0, 0.0, aWidth / 2.0, M_PI / 2.0, 3.0 * M_PI / 2.0 ); + cairo_arc( currentContext, lineLength, 0.0, aWidth / 2.0, -M_PI / 2.0, M_PI / 2.0 ); + + cairo_move_to( currentContext, 0.0, aWidth / 2.0 ); + cairo_line_to( currentContext, lineLength, aWidth / 2.0 ); + + cairo_move_to( currentContext, 0.0, -aWidth / 2.0 ); + cairo_line_to( currentContext, lineLength, -aWidth / 2.0 ); + + cairo_restore( currentContext ); + } + + isElementAdded = true; +} + + +void CAIRO_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius ) +{ + // A circle is drawn using an arc + cairo_new_sub_path( currentContext ); + cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, 0.0, 2 * M_PI ); + + isElementAdded = true; +} + + +void CAIRO_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle, + double aEndAngle ) +{ + SWAP( aStartAngle, >, aEndAngle ); + + cairo_new_sub_path( currentContext ); + cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, aStartAngle, aEndAngle ); + + 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 ); + } + + isElementAdded = true; +} + + +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; +} + + +void CAIRO_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA, + const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint ) +{ + cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y ); + cairo_curve_to( currentContext, aControlPointA.x, aControlPointA.y, aControlPointB.x, + aControlPointB.y, aEndPoint.x, aEndPoint.y ); + cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y ); + + isElementAdded = true; +} + + +void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight ) +{ + screenSize = VECTOR2I( aWidth, aHeight ); + + // Recreate the bitmaps + deleteBitmaps(); + allocateBitmaps(); + + if( validCompositor ) + compositor->Resize( aWidth, aHeight ); + + validCompositor = false; + + 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(); +} + + +void CAIRO_GAL::ClearScreen( const COLOR4D& aColor ) +{ + backgroundColor = aColor; + cairo_set_source_rgb( currentContext, aColor.r, aColor.g, aColor.b ); + cairo_rectangle( currentContext, 0.0, 0.0, screenSize.x, screenSize.y ); + cairo_fill( currentContext ); +} + + +void CAIRO_GAL::SetIsFill( bool aIsFillEnabled ) +{ + storePath(); + isFillEnabled = aIsFillEnabled; + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_SET_FILL; + groupElement.boolArgument = aIsFillEnabled; + currentGroup->push_back( groupElement ); + } +} + + +void CAIRO_GAL::SetIsStroke( bool aIsStrokeEnabled ) +{ + storePath(); + isStrokeEnabled = aIsStrokeEnabled; + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_SET_STROKE; + groupElement.boolArgument = aIsStrokeEnabled; + currentGroup->push_back( groupElement ); + } +} + + +void CAIRO_GAL::SetStrokeColor( const COLOR4D& aColor ) +{ + storePath(); + strokeColor = aColor; + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + 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; + currentGroup->push_back( groupElement ); + } +} + + +void CAIRO_GAL::SetFillColor( const COLOR4D& aColor ) +{ + storePath(); + fillColor = aColor; + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + 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; + currentGroup->push_back( groupElement ); + } +} + + +void CAIRO_GAL::SetLineWidth( double aLineWidth ) +{ + storePath(); + + lineWidth = aLineWidth; + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_SET_LINE_WIDTH; + groupElement.arguments[0] = aLineWidth; + currentGroup->push_back( groupElement ); + } + 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 ) ); + } +} + + +void CAIRO_GAL::SetLayerDepth( double aLayerDepth ) +{ + super::SetLayerDepth( aLayerDepth ); + + if( isInitialized ) + { + storePath(); + + cairo_pop_group_to_source( currentContext ); + cairo_paint_with_alpha( currentContext, LAYER_ALPHA ); + + cairo_push_group( currentContext ); + } +} + + +void CAIRO_GAL::Transform( const MATRIX3x3D& aTransformation ) +{ + 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] ); + + cairo_transform( currentContext, &cairoTransformation ); +} + + +void CAIRO_GAL::Rotate( double aAngle ) +{ + storePath(); + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_ROTATE; + groupElement.arguments[0] = aAngle; + currentGroup->push_back( groupElement ); + } + else + { + cairo_rotate( currentContext, aAngle ); + } +} + + +void CAIRO_GAL::Translate( const VECTOR2D& aTranslation ) +{ + storePath(); + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_TRANSLATE; + groupElement.arguments[0] = aTranslation.x; + groupElement.arguments[1] = aTranslation.y; + currentGroup->push_back( groupElement ); + } + else + { + cairo_translate( currentContext, aTranslation.x, aTranslation.y ); + } +} + + +void CAIRO_GAL::Scale( const VECTOR2D& aScale ) +{ + storePath(); + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_SCALE; + groupElement.arguments[0] = aScale.x; + groupElement.arguments[1] = aScale.y; + currentGroup->push_back( groupElement ); + } + else + { + cairo_scale( currentContext, aScale.x, aScale.y ); + } +} + + +void CAIRO_GAL::Save() +{ + storePath(); + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_SAVE; + currentGroup->push_back( groupElement ); + } + else + { + cairo_save( currentContext ); + } +} + + +void CAIRO_GAL::Restore() +{ + storePath(); + + if( isGrouping ) + { + GROUP_ELEMENT groupElement; + groupElement.command = CMD_RESTORE; + currentGroup->push_back( groupElement ); + } + else + { + cairo_restore( currentContext ); + } +} + + +int CAIRO_GAL::BeginGroup() +{ + initSurface(); + + // 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(); + + GROUP group; + int groupNumber = getNewGroupNumber(); + groups.insert( std::make_pair( groupNumber, group ) ); + currentGroup = &groups[groupNumber]; + isGrouping = true; + + return groupNumber; +} + + +void CAIRO_GAL::EndGroup() +{ + storePath(); + isGrouping = false; + + deinitSurface(); +} + + +void CAIRO_GAL::DrawGroup( int aGroupNumber ) +{ + // This method implements a small Virtual Machine - all stored commands + // are executed; nested calling is also possible + + storePath(); + + for( GROUP::iterator it = groups[aGroupNumber].begin(); + 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: + { + // 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( it->arguments[0], minWidth ) ); + } + break; + + + case CMD_STROKE_PATH: + cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g, strokeColor.b ); + cairo_append_path( currentContext, it->cairoPath ); + cairo_stroke( currentContext ); + break; + + case CMD_FILL_PATH: + cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b ); + cairo_append_path( currentContext, it->cairoPath ); + cairo_fill( currentContext ); + 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] ); + cairo_transform( currentContext, &matrix ); + break; + + case CMD_ROTATE: + cairo_rotate( currentContext, it->arguments[0] ); + break; + + case CMD_TRANSLATE: + cairo_translate( currentContext, it->arguments[0], it->arguments[1] ); + break; + + case CMD_SCALE: + cairo_scale( currentContext, it->arguments[0], it->arguments[1] ); + break; + + case CMD_SAVE: + cairo_save( currentContext ); + break; + + case CMD_RESTORE: + cairo_restore( currentContext ); + break; + + case CMD_CALL_GROUP: + DrawGroup( it->intArgument ); + break; + } + } +} + + +void CAIRO_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor ) +{ + storePath(); + + for( GROUP::iterator it = groups[aGroupNumber].begin(); + 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; + } + } +} + + +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 +} + + +void CAIRO_GAL::DeleteGroup( int aGroupNumber ) +{ + storePath(); + + // Delete the Cairo paths + std::deque<GROUP_ELEMENT>::iterator it, end; + + 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; + } + } +} + + +void CAIRO_GAL::SetTarget( RENDER_TARGET aTarget ) +{ + // If the compositor is not set, that means that there is a recaching process going on + // and we do not need the compositor now + if( !validCompositor ) + return; + + // Cairo grouping prevents display of overlapping items on the same layer in the lighter color + if( isInitialized ) + { + storePath(); + + cairo_pop_group_to_source( currentContext ); + cairo_paint_with_alpha( currentContext, LAYER_ALPHA ); + } + + switch( aTarget ) + { + default: + case TARGET_CACHED: + case TARGET_NONCACHED: + compositor->SetBuffer( mainBuffer ); + break; + + case TARGET_OVERLAY: + compositor->SetBuffer( overlayBuffer ); + break; + } + + if( isInitialized ) + cairo_push_group( currentContext ); + + currentTarget = aTarget; +} + + +RENDER_TARGET CAIRO_GAL::GetTarget() const +{ + return currentTarget; +} + + +void CAIRO_GAL::ClearTarget( RENDER_TARGET aTarget ) +{ + // 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; + } + + compositor->ClearBuffer(); + + // Restore the previous state + compositor->SetBuffer( currentBuffer ); +} + + +void CAIRO_GAL::SetCursorSize( unsigned int aCursorSize ) +{ + GAL::SetCursorSize( aCursorSize ); + initCursor(); +} + + +void CAIRO_GAL::DrawCursor( const VECTOR2D& aCursorPosition ) +{ + // Now we should only store the position of the mouse cursor + // The real drawing routines are in blitCursor() + cursorPosition = VECTOR2D( aCursorPosition.x - cursorSize / 2, + aCursorPosition.y - cursorSize / 2 ); +} + + +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 ); +} + + +void CAIRO_GAL::storePath() +{ + if( isElementAdded ) + { + isElementAdded = false; + + if( !isGrouping ) + { + if( isFillEnabled ) + { + cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b ); + cairo_fill_preserve( currentContext ); + } + + if( isStrokeEnabled ) + { + cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g, + strokeColor.b ); + cairo_stroke_preserve( currentContext ); + } + } + 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 ) + { + GROUP_ELEMENT groupElement; + groupElement.cairoPath = cairo_copy_path( currentContext ); + groupElement.command = CMD_STROKE_PATH; + currentGroup->push_back( groupElement ); + } + + if( isFillEnabled ) + { + GROUP_ELEMENT groupElement; + groupElement.cairoPath = cairo_copy_path( currentContext ); + groupElement.command = CMD_FILL_PATH; + currentGroup->push_back( groupElement ); + } + } + + cairo_new_path( currentContext ); + } +} + + +void CAIRO_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) ) +{ + 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 ); +} + + +void CAIRO_GAL::initCursor() +{ + if( cursorPixels ) + delete cursorPixels; + + if( cursorPixelsSaved ) + delete cursorPixelsSaved; + + cursorPixels = new wxBitmap( cursorSize, cursorSize ); + cursorPixelsSaved = new wxBitmap( cursorSize, cursorSize ); + + 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 ); + wxPen pen = wxPen( color ); + cursorShape.SetPen( pen ); + cursorShape.Clear(); + + cursorShape.DrawLine( 0, cursorSize / 2, cursorSize, cursorSize / 2 ); + cursorShape.DrawLine( cursorSize / 2, 0, cursorSize / 2, cursorSize ); +} + + +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 + VECTOR2D cursorScreen = ToScreen( cursorPosition ) - cursorSize / 2.0f; + cursorSave.Blit( 0, 0, cursorSize, cursorSize, &clientDC, cursorScreen.x, cursorScreen.y ); + + // Draw the cursor + clientDC.Blit( cursorScreen.x, cursorScreen.y, cursorSize, cursorSize, + &cursorShape, 0, 0, wxOR ); + + savedCursorPosition.x = (wxCoord) cursorScreen.x; + savedCursorPosition.y = (wxCoord) cursorScreen.y; +} + + +void CAIRO_GAL::allocateBitmaps() +{ + // Create buffer, use the system independent Cairo context backend + stride = cairo_format_stride_for_width( GAL_FORMAT, screenSize.x ); + bufferSize = stride * screenSize.y; + + bitmapBuffer = new unsigned int[bufferSize]; + bitmapBufferBackup = new unsigned int[bufferSize]; + wxOutput = new unsigned char[bufferSize * 3]; +} + + +void CAIRO_GAL::deleteBitmaps() +{ + delete[] bitmapBuffer; + delete[] bitmapBufferBackup; + delete[] wxOutput; +} + + +void CAIRO_GAL::initSurface() +{ + if( isInitialized ) + return; + + // 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 ); + wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) ); +#endif /* __WXDEBUG__ */ + currentContext = context; + + cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL ); + + // Clear the screen + ClearScreen( backgroundColor ); + + // Compute the world <-> screen transformations + ComputeWorldScreenMatrix(); + + 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] ); + + cairo_set_matrix( context, &cairoWorldScreenMatrix ); + + // 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; +} + + +void CAIRO_GAL::deinitSurface() +{ + if( !isInitialized ) + return; + + // Destroy Cairo objects + cairo_destroy( context ); + cairo_surface_destroy( surface ); + + isInitialized = false; +} + + +void CAIRO_GAL::setCompositor() +{ + // Recreate the compositor with the new Cairo context + compositor.reset( new CAIRO_COMPOSITOR( ¤tContext ) ); + compositor->Resize( screenSize.x, screenSize.y ); + + // Prepare buffers + mainBuffer = compositor->CreateBuffer(); + overlayBuffer = compositor->CreateBuffer(); + + validCompositor = true; +} + + +void CAIRO_GAL::drawPoly( const std::deque<VECTOR2D>& aPointList ) +{ + // Iterate over the point list and draw the segments + std::deque<VECTOR2D>::const_iterator it = aPointList.begin(); + + cairo_move_to( currentContext, it->x, it->y ); + + for( ++it; it != aPointList.end(); ++it ) + { + cairo_line_to( currentContext, it->x, it->y ); + } + + isElementAdded = true; +} + + +void CAIRO_GAL::drawPoly( const VECTOR2D aPointList[], int aListSize ) +{ + // Iterate over the point list and draw the segments + const VECTOR2D* ptr = aPointList; + + cairo_move_to( currentContext, ptr->x, ptr->y ); + + for( int i = 0; i < aListSize; ++i ) + { + ++ptr; + cairo_line_to( currentContext, ptr->x, ptr->y ); + } + + isElementAdded = true; +} + + +unsigned int CAIRO_GAL::getNewGroupNumber() +{ + wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(), + wxT( "There are no free slots to store a group" ) ); + + while( groups.find( groupCounter ) != groups.end() ) + { + groupCounter++; + } + + return groupCounter++; +} |