gtkmm – example 23

This is part of a more general work dedicated to gtkmm accessible here. This example shows how to display an image (Pixbuf) into a cairo drawing area. The image can be moved, scaled, fitted or centered with the mouse. Scale is done with the scroll button of the mouse. Move is controlled with the left button. The right button display a popup menu with several options as illustrated below.

capture

Download


Source code

main.cpp

#include 
#include 


int main(int argc, char* argv[])
{
	// Initialize gtkmm and create the main window
	Glib::RefPtr app = Gtk::Application::create(argc, argv, "www.lucidarme.me");
	Gtk::Window window;
	
	// Create the drawing and add an image from file
	ScrollImage Img;    	
	Img.set_image_from_file("gtk.png");
	
	// Insert the drawing in the window
	window.add(Img);
	// Resize the window
	window.resize(600,400);
	// Set the window title
	window.set_title("ScrollImage");
	// Show the drawing
	Img.show();
	
	// Start main loop
	return app->run(window);
}

scrollimage.h

#ifndef SCROLLIMAGE_H
#define SCROLLIMAGE_H

#include 
#include 


// Minimun scale (low boundary)
#define			MIN_SCALE			0.05



class ScrollImage : public Gtk::DrawingArea
{
public:
	
	
	/*!
	 * \brief ScrollImage				Constructor, initialize the widget.
	 */
	ScrollImage();
	
	
	/*!
	 * \brief set_image_from_file		Load the image to display.
	 * \param filename					File name of the image.
	 */
	void set_image_from_file(const std::string& filename);
	
	
	
	
	
protected:
	
	/*!
	 * \brief on_draw					Override the draw handler : update display.
	 *									Don't call this function to update display, call queue_draw().
	 * \param cr						Cairo context.
	 * \return							Always false.
	 */
	virtual bool on_draw(const Cairo::RefPtr& cr);
	
	
	/*!
	 * \brief displayTarget				Display a little target at coordinates x,y.
	 * \param cr						Cairo context.
	 * \param x							x-coordinate of the center of the target.
	 * \param y							y-coordinate of the center of the target.
	 */
	void displayTarget(const Cairo::RefPtr& cr,double x, double y);
	
	
	/*!
	 * \brief on_scroll_event			This function is called when the mouse scroll changes.
	 *									This is a default handler for the signal signal_scroll_event().
	 * \param event						Properties of the event.
	 * \return							Always true (the event is processed).
	 */
	bool on_scroll_event(GdkEventScroll *event);
	
	
	/*!
	 * \brief on_button_press_event		This function is called when a mouse button is pressed.
	 *									This is a default handler for the signal signal_button_press_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_button_press_event(GdkEventButton *event);
	
	
	/*!
	 * \brief on_button_release_event	This function is called when a mouse button is released
	 *									This is a default handler for the signal signal_button_release_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_button_release_event(GdkEventButton *event);
	
	
	/*!
	 * \brief on_motion_notify_event	This function is called when the pointer is moved
	 *									This is a default handler for the signal signal_motion_notify_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_motion_notify_event(GdkEventMotion*event);
	
	
	
protected:
	/*!
	 * \brief resetView					Reset the view (best image fit in the window).
	 * \param winWidth					Width of the window (in pixels).
	 * \param winHeight					Height of the window (in pixels).
	 * \param imgWidth					Width of the image (in pixels).
	 * \param imgHeight					Height of the image (in pixels).
	 */
	void                        fitImage(int winWidth,int winHeight, int imgWidth, int imgHeight);
	
	
	/*!
	 * \brief fit						Set the fit flag to one and update display
	 *									Next call of on_draw will fit the image according to the drawing area.
	 */
	void                        fit();
	
	
	/*!
	 * \brief reset						Reset display (center the image and set scale to 1:1) and update display
	 */
	void                        reset();
	
	
	/*!
	 * \brief resetScale				Set scale to 1:1 and update display
	 */
	void                        resetScale();
	
	
	/*!
	 * \brief setCenter					Center the image on the pointer position from last click
	 */
	void                        setCenter();
	
	/*!
	 * \brief showHideTarget			Show or hide the center target and update display
	 */
	void                        showHideTarget();
	
	
private:
	
	// Image to display
	Glib::RefPtr   image;
	
	// Scale of the image
	double                      scale;
	
	// Coordinates of the image point to place at the center of the window (focussed pixel)
	double                      imgFocusX,imgFocusY;
	
	// Used to memorize last mouse coordinates
	int                         lastXMouse, lastYMouse;
	
	// Flags
	bool                        resetFlag;
	bool                        moveFlag;
	bool                        targetFlag;
	
	// Popup menu and submenus
	
	// Main popup menu
	Gtk::Menu                   m_Menu_Popup;
	Gtk::MenuItem               MenuItemFit;
	Gtk::MenuItem               MenuItemRstView;
	Gtk::MenuItem               MenuItemRstScale;
	Gtk::MenuItem               MenuItemSetCenter;
	Gtk::MenuItem               MenuItemTarget;
	Gtk::SeparatorMenuItem      MenuItemLine1;
	Gtk::SeparatorMenuItem      MenuItemLine2;
	
};

#endif // SCROLLIMAGE_H

scrollimage.cpp

#include "scrollimage.h"


// Constructor
ScrollImage::ScrollImage()
{
	// Set masks for mouse events
	add_events(Gdk::BUTTON_PRESS_MASK |
			   Gdk::BUTTON_RELEASE_MASK |
			   Gdk::SCROLL_MASK |
			   Gdk::SMOOTH_SCROLL_MASK |
			   Gdk::POINTER_MOTION_MASK);

	// ::: Create popup menu :::

	// Add and connect action set pointer as center
	MenuItemSetCenter.set_label("Set center here");
	MenuItemSetCenter.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::setCenter));
	m_Menu_Popup.append(MenuItemSetCenter);

	// Add a separator
	m_Menu_Popup.append(MenuItemLine1);

	// Add and connect action Fit image to drawing area
	MenuItemFit.set_label("Fit image");
	MenuItemFit.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::fit));
	m_Menu_Popup.append(MenuItemFit);

	// Add and connect action reset (center and set scale to 1)
	MenuItemRstView.set_label("Reset view");
	MenuItemRstView.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::reset));
	m_Menu_Popup.append(MenuItemRstView);

	// Add and connect action reset scale to 1
	MenuItemRstScale.set_label("Reset scale");
	MenuItemRstScale.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::resetScale));
	m_Menu_Popup.append(MenuItemRstScale);

	// Add a separator
	m_Menu_Popup.append(MenuItemLine2);

	// Add and connect action reset scale to 1
	MenuItemTarget.set_label("Hide center target");
	MenuItemTarget.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::showHideTarget));
	m_Menu_Popup.append(MenuItemTarget);

	// Show the menu
	m_Menu_Popup.show_all();
	// Connect the menu to this Widget
	m_Menu_Popup.accelerate(*this);


	// ::: Initialize view and flags :::
	fit();
	moveFlag=false;
	targetFlag=true;
}



// Load an image from file
void ScrollImage::set_image_from_file(const std::string& filename)
{
	image = Gdk::Pixbuf::create_from_file(filename);
}



// Mouse wheel event detected : update scale
bool ScrollImage::on_scroll_event(GdkEventScroll *event)
{
	// Compute the new scale according to mouse scroll
	double newScale=scale*(1-event->delta_y/20);
	if (newScalex - get_allocated_width()/2.;
	double DeltaY=event->y - get_allocated_height()/2.;
	imgFocusX=imgFocusX + DeltaX/scale - DeltaX/newScale ;
	imgFocusY=imgFocusY + DeltaY/scale - DeltaY/newScale ;;

	// Update scale and redraw the widget
	scale=newScale;
	queue_draw();

	// Event has been handled
	return true;
}


// Mouse button pressed : process mouse button event
bool ScrollImage::on_button_press_event(GdkEventButton *event)
{

	// Check if the event is a left button click.
	if (event->button == 1)
	{
		// Memorize pointer position
		lastXMouse=event->x;
		lastYMouse=event->y;
		// Start moving the view
		moveFlag=true;
		// Event has been handled
		return true;
	}

	// Check if the event is a right button click.
	if(event->button == 3)
	{
		// Memorize mouse coordinates
		lastXMouse=event->x;
		lastYMouse=event->y;
		// Display the popup menu
		m_Menu_Popup.popup(event->button, event->time);
		// The event has been handled.
		return true;
	}
	// Event has not been handled
	return false;
}



// Mouse button released : process mouse button event
bool ScrollImage::on_button_release_event(GdkEventButton *event)
{
	// Check if it is the left button
	if (event->button==1 && moveFlag)
	{
		// End of motion
		moveFlag=false;
		// Update image focus
		imgFocusX -= (event->x-lastXMouse)/scale;
		imgFocusY -= (event->y-lastYMouse)/scale;
		// Update display
		queue_draw();
		return true;
	}
	// Event has been handled
	return false;
}



// Mouse pointer moved : process event
bool ScrollImage::on_motion_notify_event (GdkEventMotion*event)
{

	// If the left button is pressed, move the view
	if (moveFlag)
	{
		// Get mouse coordinates
		int XMouse=event->x;
		int YMouse=event->y;

		// Update image focus
		imgFocusX -= (XMouse-lastXMouse)/scale;
		imgFocusY -= (YMouse-lastYMouse)/scale;

		// Memorize new position of the pointer
		lastXMouse=XMouse;
		lastYMouse=YMouse;

		// Update view
		queue_draw();
		return true;
	}
	// Event has been handled
	return false;
}


// Reset view (scale is set to 1:1 and image is centered)
void ScrollImage::reset()
{
	// Set scale to 1:1
	scale=1;
	// Update image focus
	imgFocusX = image->get_width()/2.;
	imgFocusY = image->get_height()/2.;
	// Update display
	queue_draw();
}


// Reset scale to 1:1
void ScrollImage::resetScale()
{
	scale=1;
	// Update display
	queue_draw();
}


// Center the dispay at the pointer coordinates
void ScrollImage::setCenter()
{
	// Update image focus
	imgFocusX += (lastXMouse - get_allocated_width()/2.)/scale;
	imgFocusY += (lastYMouse - get_allocated_height()/2.)/scale;
	// Update display
	queue_draw();
}

// Best fit of the image in the display
void ScrollImage::fit()
{
	// Set reset flag to true, next call of on_draw will reset display
	resetFlag=true;
	queue_draw();
}


// Hide or show the target center
void ScrollImage::showHideTarget()
{
	// Invert flag
	targetFlag=!targetFlag;
	// Update popup menu according to current status
	if (targetFlag)
		MenuItemTarget.set_label("Hide center target");
	else
		MenuItemTarget.set_label("Show center target");
	// Update display
	queue_draw();
}


// Reset view to fit in the drawing area
void ScrollImage::fitImage(int winWidth,int winHeight, int imgWidth, int imgHeight)
{
	// Compute ratio of the window and the image
	double winRatio=(double)winWidth/winHeight;
	double imgRatio=(double)imgWidth/imgHeight;

	// Check what is the best fit for the image according to the ratio
	if (imgRatio& cr)
{

	// Get the window size
	int winWidth=get_allocated_width();
	int winHeight=get_allocated_height();

	// Get the image size
	int imgWidth=image->get_width();
	int imgHeight=image->get_height();

	// If requested, reset view
	if (resetFlag) fitImage(winWidth,winHeight,imgWidth,imgHeight);

	// Create a new image for display filled with grey
	Glib::RefPtr display = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,false,8,winWidth,winHeight);
	display->fill(0x5F5F5F00);

	// Compute offset of the source image
	double OffsetX=winWidth/2-imgFocusX*scale;
	double OffsetY=winHeight/2-imgFocusY*scale;
	// Compute top left coordinate of the image in the area
	double Min_X=std::max(0.,OffsetX);
	double Min_Y=std::max(0.,OffsetY);
	// Compute bottom right coordinates of the image in the area
	double Max_X=std::min((double)winWidth,winWidth/2+(imgWidth-imgFocusX)*scale);
	double Max_Y=std::min((double)winHeight,winHeight/2+(imgHeight-imgFocusY)*scale);
	// Compute width and height
	double Width=Max_X-Min_X;
	double Height=Max_Y-Min_Y;

	// Scale image
	image->scale(display,
				 Min_X,Min_Y,
				 Width,Height,
				 OffsetX,OffsetY,
				 scale,scale,Gdk::INTERP_TILES);

	// Display the image in the drawing area
	Gdk::Cairo::set_source_pixbuf(cr,display,0,0);
	cr->rectangle(0,0,winWidth,winHeight);
	cr->fill();

	// Display the center target if requested
	if (targetFlag) displayTarget(cr,winWidth/2.,winHeight/2.);

	// Event has been handled
	return false;
}


// Display a target at coordinates x,y
void ScrollImage::displayTarget(const Cairo::RefPtr& cr,double x, double y)
{
	// Set color to black
	cr->set_source_rgba(0,0,0,0.5);

	// Display black quaters
	cr->arc(x,y,15,0,M_PI/2.);
	cr->line_to(x,y);
	cr->fill();
	cr->arc(x,y,15,M_PI,3.*M_PI/2.);
	cr->line_to(x,y);
	cr->fill();

	// Set color to white
	cr->set_source_rgba(1,1,1,0.5);

	// Display white quaters
	cr->arc(x,y,15,M_PI/2.,M_PI);
	cr->line_to(x,y);
	cr->fill();
	cr->arc(x,y,15,3.*M_PI/2.,0);
	cr->line_to(x,y);
	cr->fill();

	// Set color to black
	cr->set_source_rgba(0,0,0,0.8);

	// Display the cross
	cr->move_to(x-20,y);
	cr->line_to(x+20,y);
	cr->stroke();
	cr->move_to(x,y-20);
	cr->line_to(x,y+20);
	cr->stroke();

	// Display the circle
	cr->arc(x,y,15,0,2*M_PI);
	cr->stroke();
}

Leave a Reply

Your email address will not be published. Required fields are marked *