/* Drawing Area * * GtkDrawingArea is a blank area where you can draw custom displays * of various kinds. * * This demo has two drawing areas. The checkerboard area shows * how you can just draw something; all you have to do is write * a signal handler for expose_event, as shown here. * * The "scribble" area is a bit more advanced, and shows how to handle * events such as button presses and mouse motion. Click the mouse * and drag in the scribble area to draw squiggles. Resize the window * to clear the area. */ #include <gtkmm.h> class Example_DrawingArea : public Gtk::Window { public: Example_DrawingArea(); virtual ~Example_DrawingArea(); protected: //signal handlers: bool on_drawingarea_checkerboard_expose_event(GdkEventExpose* event); bool on_drawingarea_scribble_expose_event(GdkEventExpose* event); bool on_drawingarea_scribble_configure_event(GdkEventConfigure* event); bool on_drawingarea_scribble_motion_notify_event(GdkEventMotion* event); bool on_drawingarea_scribble_button_press_event(GdkEventButton* event); void scribble_draw_brush(int x, int y); //Member widgets: Gtk::Frame m_Frame_Checkerboard, m_Frame_Scribble; Gtk::VBox m_VBox; Gtk::Label m_Label_Checkerboard, m_Label_Scribble; Gtk::DrawingArea m_DrawingArea_Checkerboard, m_DrawingArea_Scribble; Glib::RefPtr<Gdk::Pixmap> m_refPixmap_Scribble; }; //Called by DemoWindow; Gtk::Window* do_drawingarea() { return new Example_DrawingArea(); } Example_DrawingArea::Example_DrawingArea() : m_VBox(false, 8) { set_title("Drawing Area"); set_border_width(8); m_VBox.set_border_width(8); add(m_VBox); /* * Create the checkerboard area */ m_Label_Checkerboard.set_markup("<u>Checkerboard pattern</u>"); m_VBox.pack_start(m_Label_Checkerboard, Gtk::PACK_SHRINK); m_Frame_Checkerboard.set_shadow_type(Gtk::SHADOW_IN); m_VBox.pack_start(m_Frame_Checkerboard); /* set a minimum size */ m_DrawingArea_Checkerboard.set_size_request(100, 100); m_Frame_Checkerboard.add(m_DrawingArea_Checkerboard); m_DrawingArea_Checkerboard.signal_expose_event().connect( sigc::mem_fun(*this, &Example_DrawingArea::on_drawingarea_checkerboard_expose_event)); /* * Create the scribble area */ m_Label_Scribble.set_markup("<u>Scribble area</u>"); m_VBox.pack_start(m_Label_Scribble, Gtk::PACK_SHRINK); m_Frame_Scribble.set_shadow_type(Gtk::SHADOW_IN); m_VBox.pack_start(m_Frame_Scribble); /* set a minimum size */ m_DrawingArea_Scribble.set_size_request(100, 100); m_Frame_Scribble.add(m_DrawingArea_Scribble); /* Signals used to handle backing pixmap */ m_DrawingArea_Scribble.signal_expose_event().connect( sigc::mem_fun(*this, &Example_DrawingArea::on_drawingarea_scribble_expose_event)); m_DrawingArea_Scribble.signal_configure_event().connect( sigc::mem_fun(*this, &Example_DrawingArea::on_drawingarea_scribble_configure_event)); /* Event signals */ m_DrawingArea_Scribble.signal_motion_notify_event().connect( sigc::mem_fun(*this, &Example_DrawingArea::on_drawingarea_scribble_motion_notify_event)); m_DrawingArea_Scribble.signal_button_press_event().connect( sigc::mem_fun(*this, &Example_DrawingArea::on_drawingarea_scribble_button_press_event)); /* Ask to receive events the drawing area doesn't normally * subscribe to. */ m_DrawingArea_Scribble.add_events(Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK); show_all(); } Example_DrawingArea::~Example_DrawingArea() { } bool Example_DrawingArea::on_drawingarea_checkerboard_expose_event(GdkEventExpose*) { enum { CHECK_SIZE = 10, SPACING = 2 }; /* At the start of an expose handler, a clip region of event->area * is set on the window, and event->area has been cleared to the * widget's background color. The docs for * gdk_window_begin_paint_region() give more details on how this * works. */ /* It would be a bit more efficient to keep these * GC's around instead of recreating on each expose, but * this is the lazy/slow way. */ Glib::RefPtr<Gdk::GC> refGC1 = Gdk::GC::create(m_DrawingArea_Checkerboard.get_window()); Gdk::Color color; color.set_red(30000); color.set_green(0); color.set_blue(30000); refGC1->set_rgb_fg_color(color); Glib::RefPtr<Gdk::GC> refGC2 = Gdk::GC::create(m_DrawingArea_Checkerboard.get_window()); color.set_red(65535); color.set_green(65535); color.set_blue(65535); refGC2->set_rgb_fg_color(color); gint xcount = 0; gint i = SPACING; while (i < m_DrawingArea_Checkerboard.get_allocation().get_width()) { gint j = SPACING; gint ycount = xcount % 2; /* start with even/odd depending on row */ while (j < m_DrawingArea_Checkerboard.get_allocation().get_height()) { Glib::RefPtr<Gdk::GC> refGC; if (ycount % 2) refGC = refGC1; else refGC = refGC2; /* If we're outside event->area, this will do nothing. * It might be mildly more efficient if we handled * the clipping ourselves, but again we're feeling lazy. */ m_DrawingArea_Checkerboard.get_window()->draw_rectangle(refGC, true, i, j, CHECK_SIZE, CHECK_SIZE); j += CHECK_SIZE + SPACING; ++ycount; } i += CHECK_SIZE + SPACING; ++xcount; } /* return true because we've handled this event, so no * further processing is required. */ return true; } bool Example_DrawingArea::on_drawingarea_scribble_expose_event(GdkEventExpose* event) { /* We use the "foreground GC" for the widget since it already exists, * but honestly any GC would work. The only thing to worry about * is whether the GC has an inappropriate clip region set. */ m_DrawingArea_Scribble.get_window()->draw_drawable( m_DrawingArea_Scribble.get_style()->get_fg_gc(m_DrawingArea_Scribble.get_state()), m_refPixmap_Scribble, // Only copy the area that was exposed: event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return false; } bool Example_DrawingArea::on_drawingarea_scribble_configure_event(GdkEventConfigure*) { m_refPixmap_Scribble = Gdk::Pixmap::create( m_DrawingArea_Scribble.get_window(), m_DrawingArea_Scribble.get_allocation().get_width(), m_DrawingArea_Scribble.get_allocation().get_height(), -1); /* Initialize the pixmap to white */ m_refPixmap_Scribble->draw_rectangle( m_DrawingArea_Scribble.get_style()->get_white_gc(), true, 0, 0, m_DrawingArea_Scribble.get_allocation().get_width(), m_DrawingArea_Scribble.get_allocation().get_height()); /* We've handled the configure event, no need for further processing. */ return true; } bool Example_DrawingArea::on_drawingarea_scribble_motion_notify_event(GdkEventMotion* event) { if(!m_refPixmap_Scribble) return false; // paranoia check, in case we haven't gotten a configure event /* This call is very important; it requests the next motion event. If you * don't call Gdk::Window::get_pointer() you'll only get a single motion * event. The reason is that we specified Gdk::POINTER_MOTION_HINT_MASK to * Gtk::Widget::add_events(). If we hadn't specified that, we could just use * event->x, event->y as the pointer location. But we'd also get deluged in * events. By requesting the next event as we handle the current one, we * avoid getting a huge number of events faster than we can cope. */ if(event && event->window) { const Glib::RefPtr<Gdk::Window> refWindow = Glib::wrap((GdkWindowObject*) event->window, true); // true == take_copy if(refWindow) { int x = 0, y = 0; Gdk::ModifierType state = Gdk::ModifierType(0); refWindow->get_pointer(x, y, state); if((state & Gdk::BUTTON1_MASK) != 0) scribble_draw_brush(x, y); } } // We've handled it, stop processing. return true; } bool Example_DrawingArea::on_drawingarea_scribble_button_press_event(GdkEventButton* event) { if(!m_refPixmap_Scribble) return false; // paranoia check, in case we haven't gotten a configure event if(event->button == 1) scribble_draw_brush(int(event->x), int(event->y)); // We've handled the event, stop processing. return true; } /* Draw a rectangle on the screen. */ void Example_DrawingArea::scribble_draw_brush(int x, int y) { const Gdk::Rectangle update_rect (x - 3, y - 3, 6, 6); // Paint to the pixmap, where we store our state. // m_refPixmap_Scribble->draw_rectangle( m_DrawingArea_Scribble.get_style()->get_black_gc(), true, update_rect.get_x(), update_rect.get_y(), update_rect.get_width(), update_rect.get_height()); // Now invalidate the affected region of the drawing area. m_DrawingArea_Scribble.get_window()->invalidate_rect(update_rect, false); }