A Doodle Application with Rectangle (Marquee) Selection

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 466
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

A Doodle Application with Rectangle (Marquee) Selection

Post by New Pagodi »

It looks like the topic of making a selection rectangle has come up several times here and on the list serve. I just spent way longer than I should have looking over the docview and drawing samples to figure out how to get it to work. I thought I'd share the result in case anyone else wants a simpler example of a selection rectangle.

This app lets you draw on the window by holding down the left mouse button. You can then select some of the drawn segments by holding down the right mouse button and dragging the mouse.
d.gif
d.gif (7.46 KiB) Viewed 13927 times
This is a very simple example application, and those are the only two things it does. The work with the selection rectangle is done in the OnRightDown and OnRightUp methods as well as the first half of the OnMotion method.

Code: Select all

#ifdef WX_PRECOMP
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include <wx/app.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/overlay.h>
#include <wx/dcmemory.h>
#include <wx/dcclient.h>
#include <wx/vector.h>

// Represents a line from one point to the other
struct DoodleLine
{
    DoodleLine() { /* leave fields uninitialized */ }

    DoodleLine(const wxPoint& pt1, const wxPoint& pt2)
        : x1(pt1.x), y1(pt1.y), x2(pt2.x), y2(pt2.y){}

    wxInt32 x1,y1,x2,y2;
};

typedef wxVector<DoodleLine> DoodleLines;

// Contains a list of lines: represents a mouse-down doodle
class DoodleSegment
{
public:
    DoodleSegment():m_selected(false){}
    bool IsEmpty() const { return m_lines.empty(); }
    void AddLine(const wxPoint& pt1, const wxPoint& pt2){m_lines.push_back(DoodleLine(pt1, pt2));}
    const DoodleLines& GetLines() const { return m_lines; }
    bool IsSelected() const { return m_selected; }
    void SetSelected(bool b) { m_selected=b; }

private:
    bool m_selected;
    DoodleLines m_lines;
};

typedef wxVector<DoodleSegment> DoodleSegments;

class DoodleFrame: public wxFrame
{
    public:
        DoodleFrame(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Doodles"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 481,466 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL);
    private:
        void OnMotion( wxMouseEvent& event );
        void OnPaint( wxPaintEvent& event );
        void OnLeftDown( wxMouseEvent& event );
        void OnLeftUp( wxMouseEvent& event );
        void OnRightDown( wxMouseEvent& event );
        void OnRightUp( wxMouseEvent& event );
        void OnLeaveWindow( wxMouseEvent& event );

        void DrawSegment(const DoodleSegment&,bool);
        void CancelSelections();
        void EndDrawing();

        wxPanel* m_canvas;
        wxBitmap m_canvasBitmap;
        bool m_rubberBand,m_drawing;
        int m_canvasWidth,m_canvasHeight;
        wxPoint m_currentpoint;
        wxOverlay m_overlay;
        DoodleSegment m_currentSegment;
        DoodleSegments m_doodleSegments;
};

DoodleFrame::DoodleFrame(wxWindow* parent, wxWindowID id , const wxString& title , const wxPoint& pos, const wxSize& size , long style ): wxFrame( parent, id, title, pos, size, style )
{
    this->SetSizeHints( wxDefaultSize, wxDefaultSize );

    wxBoxSizer* bSizer1 = new wxBoxSizer( wxVERTICAL );
    m_canvas = new wxPanel( this );
    bSizer1->Add( m_canvas, 1, wxEXPAND, 5 );
    this->SetSizer( bSizer1 );
    this->Layout();

    m_canvasWidth = m_canvas->GetSize().GetWidth();
    m_canvasHeight = m_canvas->GetSize().GetHeight();;

    m_canvasBitmap = wxBitmap(m_canvasWidth,m_canvasHeight,24) ;
    wxMemoryDC dc(m_canvasBitmap);
    dc.SetPen(*wxWHITE_PEN);
    dc.SetBrush(*wxWHITE_BRUSH);
    dc.DrawRectangle(0,0,m_canvasWidth,m_canvasHeight);

    m_rubberBand=false;
    m_drawing=false;

    // Connect Events
    m_canvas->Bind( wxEVT_LEAVE_WINDOW, &DoodleFrame::OnLeaveWindow, this );
    m_canvas->Bind( wxEVT_LEFT_DOWN, &DoodleFrame::OnLeftDown, this );
    m_canvas->Bind( wxEVT_LEFT_UP, &DoodleFrame::OnLeftUp, this );
    m_canvas->Bind( wxEVT_MOTION, &DoodleFrame::OnMotion, this );
    m_canvas->Bind( wxEVT_PAINT, &DoodleFrame::OnPaint, this );
    m_canvas->Bind( wxEVT_RIGHT_DOWN, &DoodleFrame::OnRightDown, this );
    m_canvas->Bind( wxEVT_RIGHT_UP, &DoodleFrame::OnRightUp, this );
}

void DoodleFrame::DrawSegment(const DoodleSegment& ds,bool sel)
{
    wxMemoryDC dc( m_canvasBitmap );
    DoodleLines dls=ds.GetLines();
    const wxInt32 count = dls.size();

    dc.SetPen(sel?*wxRED_PEN:*wxBLACK_PEN);
    for ( int i = 0; i < count; i++ )
    {
        const DoodleLine& line = dls[i];
        dc.DrawLine( line.x1, line.y1, line.x2, line.y2 );
        m_canvas->Refresh( false, &wxRect(line.x1, line.y1, line.x2, line.y2) );
    }
}

void DoodleFrame::CancelSelections()
{
    const wxInt32 count = m_doodleSegments.size();

    for ( int i = 0; i < count; i++ )
    {
        DoodleSegment& ds=m_doodleSegments[i];
        if( ds.IsSelected() )
        {
            ds.SetSelected(false);
            DrawSegment( ds, false );
        }
    }
}

void DoodleFrame::EndDrawing()
{
    m_drawing=false;
    if( !m_currentSegment.IsEmpty() )
    {
        m_doodleSegments.push_back(m_currentSegment);
        m_currentSegment = DoodleSegment();
    }
}

void DoodleFrame::OnLeftDown( wxMouseEvent& event )
{
    m_drawing=true;
    m_currentpoint = wxPoint( event.GetX() , event.GetY() ) ;
    CancelSelections();
}

void DoodleFrame::OnLeftUp( wxMouseEvent& event )
{
    EndDrawing();
}

void DoodleFrame::OnLeaveWindow( wxMouseEvent& event )
{
    if(m_drawing)EndDrawing();
}

void DoodleFrame::OnRightDown( wxMouseEvent& event )
{
    m_rubberBand=true;
    m_currentpoint = wxPoint( event.GetX() , event.GetY() );

    CancelSelections();
    m_canvas->CaptureMouse();
}

void DoodleFrame::OnRightUp( wxMouseEvent& event )
{
    if ( m_rubberBand )
    {
        //remove the selection rectangle from the overlay
        wxDCOverlay( m_overlay, &wxClientDC( m_canvas ) ).Clear();
        m_overlay.Reset();

        //housekeeping
        m_rubberBand = false;
        m_canvas->ReleaseMouse();

        //process the selection: run through the segments and redraw any that
        //lie within the selection rectangle
        wxRect selection( m_currentpoint , wxPoint( event.GetX() , event.GetY() ) ) ;
        const wxInt32 count = m_doodleSegments.size();

        for ( int i = 0; i < count; i++ )
        {
            DoodleSegment& ds=m_doodleSegments[i];
            const DoodleLines& dl=ds.GetLines();
            const DoodleLine& line = dl[0];
            const DoodleLine& line2 = dl[dl.size()-1];
            wxPoint strt(line.x1,line.y1);
            wxPoint endd(line2.x2,line2.y2);

            if( selection.Contains(strt) && selection.Contains(endd) )
            {
                DrawSegment(ds,true);
                ds.SetSelected(true);
            }
        }
    }
}

void DoodleFrame::OnMotion( wxMouseEvent& event )
{
    int x = event.GetX();
    int y = event.GetY();

    if(m_rubberBand)
    {
        //draw a selection rectangle on the overlay
        wxClientDC dc( m_canvas );

        wxDCOverlay( m_overlay, &dc ).Clear();
        dc.SetPen( wxPen( *wxLIGHT_GREY, 2 ) );
        dc.SetBrush( *wxTRANSPARENT_BRUSH );
        dc.DrawRectangle( wxRect( m_currentpoint , wxPoint(x,y) ) );
    }
    else if(m_drawing)
    {
        if(x<m_canvasWidth && y<m_canvasHeight)
        {
            wxPoint np = wxPoint(x,y);

            //draw the line on the canvas
            wxMemoryDC(m_canvasBitmap).DrawLine(m_currentpoint,np);
            m_canvas->Refresh( false, &wxRect(m_currentpoint,np) );

            //add the line to the current doodle segment
            m_currentSegment.AddLine(m_currentpoint,np);
            m_currentpoint=np;
        }
        else
        {
            EndDrawing();
        }
    }
}

void DoodleFrame::OnPaint( wxPaintEvent& event )
{
    wxPaintDC(m_canvas).DrawBitmap(m_canvasBitmap,0,0);
}

class DoodleApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            DoodleFrame* frame = new DoodleFrame(0L);
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(DoodleApp);
dkaip
Super wx Problem Solver
Super wx Problem Solver
Posts: 334
Joined: Wed Jan 20, 2010 1:15 pm

Re: A Doodle Application with Rectangle (Marquee) Selection

Post by dkaip »

Code give error: taking address of temporary [-fpermissive]|at.

Code: Select all

m_canvas->Refresh( false, &wxRect(line.x1, line.y1, line.x2, line.y2) );
Why is that?
dkaip
Super wx Problem Solver
Super wx Problem Solver
Posts: 334
Joined: Wed Jan 20, 2010 1:15 pm

Re: A Doodle Application with Rectangle (Marquee) Selection

Post by dkaip »

Now work fine...

Code: Select all

#ifdef WX_PRECOMP
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include <wx/app.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/overlay.h>
#include <wx/dcmemory.h>
#include <wx/dcclient.h>
#include <wx/vector.h>

// Represents a line from one point to the other
struct DoodleLine
{
    DoodleLine() { /* leave fields uninitialized */ }

    DoodleLine(const wxPoint& pt1, const wxPoint& pt2)
        : x1(pt1.x), y1(pt1.y), x2(pt2.x), y2(pt2.y){}

    wxInt32 x1,y1,x2,y2;
};

typedef wxVector<DoodleLine> DoodleLines;

// Contains a list of lines: represents a mouse-down doodle
class DoodleSegment
{
public:
    DoodleSegment():m_selected(false){}
    bool IsEmpty() const { return m_lines.empty(); }
    void AddLine(const wxPoint& pt1, const wxPoint& pt2){m_lines.push_back(DoodleLine(pt1, pt2));}
    const DoodleLines& GetLines() const { return m_lines; }
    bool IsSelected() const { return m_selected; }
    void SetSelected(bool b) { m_selected=b; }

private:
    bool m_selected;
    DoodleLines m_lines;
};

typedef wxVector<DoodleSegment> DoodleSegments;

class DoodleFrame: public wxFrame
{
    public:
        DoodleFrame(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Doodles"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 481,466 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL);
    private:
        void OnMotion( wxMouseEvent& event );
        void OnPaint( wxPaintEvent& event );
        void OnLeftDown( wxMouseEvent& event );
        void OnLeftUp( wxMouseEvent& event );
        void OnRightDown( wxMouseEvent& event );
        void OnRightUp( wxMouseEvent& event );
        void OnLeaveWindow( wxMouseEvent& event );

        void DrawSegment(const DoodleSegment&,bool);
        void CancelSelections();
        void EndDrawing();

        wxPanel* m_canvas;
        wxBitmap m_canvasBitmap;
        bool m_rubberBand,m_drawing;
        int m_canvasWidth,m_canvasHeight;
        wxPoint m_currentpoint;
        wxOverlay m_overlay;
        DoodleSegment m_currentSegment;
        DoodleSegments m_doodleSegments;
};

DoodleFrame::DoodleFrame(wxWindow* parent, wxWindowID id , const wxString& title , const wxPoint& pos, const wxSize& size , long style ): wxFrame( parent, id, title, pos, size, style )
{
    this->SetSizeHints( wxDefaultSize, wxDefaultSize );

    wxBoxSizer* bSizer1 = new wxBoxSizer( wxVERTICAL );
    m_canvas = new wxPanel( this );
    bSizer1->Add( m_canvas, 1, wxEXPAND, 5 );
    this->SetSizer( bSizer1 );
    this->Layout();

    m_canvasWidth = m_canvas->GetSize().GetWidth();
    m_canvasHeight = m_canvas->GetSize().GetHeight();;

    m_canvasBitmap = wxBitmap(m_canvasWidth,m_canvasHeight,24) ;
    wxMemoryDC dc(m_canvasBitmap);
    dc.SetPen(*wxWHITE_PEN);
    dc.SetBrush(*wxWHITE_BRUSH);
    dc.DrawRectangle(0,0,m_canvasWidth,m_canvasHeight);

    m_rubberBand=false;
    m_drawing=false;

    // Connect Events
    m_canvas->Bind( wxEVT_LEAVE_WINDOW, &DoodleFrame::OnLeaveWindow, this );
    m_canvas->Bind( wxEVT_LEFT_DOWN, &DoodleFrame::OnLeftDown, this );
    m_canvas->Bind( wxEVT_LEFT_UP, &DoodleFrame::OnLeftUp, this );
    m_canvas->Bind( wxEVT_MOTION, &DoodleFrame::OnMotion, this );
    m_canvas->Bind( wxEVT_PAINT, &DoodleFrame::OnPaint, this );
    m_canvas->Bind( wxEVT_RIGHT_DOWN, &DoodleFrame::OnRightDown, this );
    m_canvas->Bind( wxEVT_RIGHT_UP, &DoodleFrame::OnRightUp, this );
}

void DoodleFrame::DrawSegment(const DoodleSegment& ds,bool sel)
{
    wxMemoryDC dc( m_canvasBitmap );
    DoodleLines dls=ds.GetLines();
    const wxInt32 count = dls.size();

    dc.SetPen(sel?*wxRED_PEN:*wxBLACK_PEN);
    for ( int i = 0; i < count; i++ )
    {
        const DoodleLine& line = dls[i];
        dc.DrawLine( line.x1, line.y1, line.x2, line.y2 );
        wxRect a(line.x1, line.y1, line.x2, line.y2);
        m_canvas->Refresh( false, &a );//error: taking address of temporary [-fpermissive]|
    }
}

void DoodleFrame::CancelSelections()
{
    const wxInt32 count = m_doodleSegments.size();

    for ( int i = 0; i < count; i++ )
    {
        DoodleSegment& ds=m_doodleSegments[i];
        if( ds.IsSelected() )
        {
            ds.SetSelected(false);
            DrawSegment( ds, false );
        }
    }
}

void DoodleFrame::EndDrawing()
{
    m_drawing=false;
    if( !m_currentSegment.IsEmpty() )
    {
        m_doodleSegments.push_back(m_currentSegment);
        m_currentSegment = DoodleSegment();
    }
}

void DoodleFrame::OnLeftDown( wxMouseEvent& event )
{
    m_drawing=true;
    m_currentpoint = wxPoint( event.GetX() , event.GetY() ) ;
    CancelSelections();
}

void DoodleFrame::OnLeftUp( wxMouseEvent& event )
{
    EndDrawing();
}

void DoodleFrame::OnLeaveWindow( wxMouseEvent& event )
{
    if(m_drawing)EndDrawing();
}

void DoodleFrame::OnRightDown( wxMouseEvent& event )
{
    m_rubberBand=true;
    m_currentpoint = wxPoint( event.GetX() , event.GetY() );

    CancelSelections();
    m_canvas->CaptureMouse();
}

void DoodleFrame::OnRightUp( wxMouseEvent& event )
{
    if ( m_rubberBand )
    {
        wxClientDC a(m_canvas);
        a.Clear();
        //remove the selection rectangle from the overlay
        wxDCOverlay( m_overlay, &a);
        m_overlay.Reset();

        //housekeeping
        m_rubberBand = false;
        m_canvas->ReleaseMouse();

        //process the selection: run through the segments and redraw any that
        //lie within the selection rectangle
        wxRect selection( m_currentpoint , wxPoint( event.GetX() , event.GetY() ) ) ;
        const wxInt32 count = m_doodleSegments.size();

        for ( int i = 0; i < count; i++ )
        {
            DoodleSegment& ds=m_doodleSegments[i];
            const DoodleLines& dl=ds.GetLines();
            const DoodleLine& line = dl[0];
            const DoodleLine& line2 = dl[dl.size()-1];
            wxPoint strt(line.x1,line.y1);
            wxPoint endd(line2.x2,line2.y2);

            if( selection.Contains(strt) && selection.Contains(endd) )
            {
                DrawSegment(ds,true);
                ds.SetSelected(true);
            }
        }
    }
}

void DoodleFrame::OnMotion( wxMouseEvent& event )
{
    int x = event.GetX();
    int y = event.GetY();

    if(m_rubberBand)
    {
        //draw a selection rectangle on the overlay
        wxClientDC dc( m_canvas );

        wxDCOverlay( m_overlay, &dc ).Clear();
        dc.SetPen( wxPen( *wxLIGHT_GREY, 2 ) );
        dc.SetBrush( *wxTRANSPARENT_BRUSH );
        dc.DrawRectangle( wxRect( m_currentpoint , wxPoint(x,y) ) );
    }
    else if(m_drawing)
    {
        if(x<m_canvasWidth && y<m_canvasHeight)
        {
            wxPoint np = wxPoint(x,y);

            //draw the line on the canvas
            wxMemoryDC(m_canvasBitmap).DrawLine(m_currentpoint,np);
            wxRect a(m_currentpoint,np);
            m_canvas->Refresh( false, &a);

            //add the line to the current doodle segment
            m_currentSegment.AddLine(m_currentpoint,np);
            m_currentpoint=np;
        }
        else
        {
            EndDrawing();
        }
    }
}

void DoodleFrame::OnPaint( wxPaintEvent& event )
{
    wxPaintDC(m_canvas).DrawBitmap(m_canvasBitmap,0,0);
}

class DoodleApp : public wxApp
{
    public:
        virtual bool OnInit()
        {
            DoodleFrame* frame = new DoodleFrame(0L);
            frame->Show();
            return true;
        }
};

wxIMPLEMENT_APP(DoodleApp);
Post Reply