" Dont judge those who try and fail, judge those who fail to try "

Beginning KDevelop Programming Version 3.x

Chapter One :- Introducing KDevelop Chapter Two :- The KDE Application Chapter Three :- Common Widgets Chapter Four :- Containers And Views Chapter Five :- Database Programming With MySQL Chapter Six :- Input And Display Chapter Seven :- KDE Display Widgets Chapter Eight :- KDE Buttons And Input Chapter Nine :- KDE Containers And Views Chapter Ten :- Custom Widgets Chapter Eleven :- Events Chapter Twelve :- Drawing Chapter Thirteen :- Global Information and Configuration Files Chapter Fourteen :- A Simple Editor Application

Appendices

Appendix A :- Upgrading KDevelop

Downloads

PDF

Beginning KDevelop Programming ( 13.4 mb )

ZIP

Chapter One Source ( 1.2mb ) Chapter Two Source ( 3.8 mb ) Chapter Three Source ( 6.9 mb ) Chapter Four Source ( 6.4 mb ) Chapter Five Source ( 6.6 mb ) Chapter Six Source ( 3.6 mb ) Chapter Seven Source ( 2.2 mb ) Chapter Eight Source ( 2.2 mb ) Chapter Nine Source ( 1.1 mb ) Chapter Ten Source ( 5.3 mb ) Chapter Eleven Source ( 1.6 mb ) Chapter Twelve Source ( 5.2 mb ) Chapter Thirteen Source ( 3.8 mb ) Chapter Fourteen Source ( 616 kb )

Contributions

Contributors Page

Contacts

Author

pseudonym67@Hotmail.com

Page Designer

andy@2scopedesign.co.uk

Chapter 12 :- Drawing

In this chapter we take a closer look at drawing as it is kind of a compulsary subject for one of these things but one that most people hardly use in there careers. Drawing code can often be very long and look very complicated but as you've seen in various chapters in this book it isn't really as hard as it looks. By now you have already seen how to draw you own button and how to draw text to directly onto a form and in this chapter we will expand on what we have learned so far an add a few details about KDE while we do it.

KDE Colours

To start with we are going to look at how the KDE colour scheme works, as you'll see this is largely a function of the Qt libraries that KDE is built from. The main point of interest to start with is the class KGlobalSettings which you will find in the help. It works from the idea that you don't specify a specific colour to do your drawing but you select a system colour that then draws the in the selected colour. You saw this in the KSquareButton class with the code,

painter->setBrush( backgroundColor().dark() );

Which returns the background colour and then calls the QColor dark() function. With the KGlobalSettings class we can access the colours specified by the KDE system.


KDEChapterTwelveMenusRunning1.pngThe project starts with just six text labels on a form, it is a drawing example after all. These text labels are for the KDE system fonts so let's look at them first.

The code for the fonts is set up in the constructor so if you change the system fonts you will need to restart the application for the change. It is a pretty basic procedure to display the fonts. We assign a few strings to display and then get the fonts

QFont currentFont = KGlobalSettings::generalFont();
QFont fixedFont = KGlobalSettings::fixedFont();
QFont toolBarFont = KGlobalSettings::toolBarFont();
QFont menuFont = KGlobalSettings::menuFont();
QFont windowTitleFont = KGlobalSettings::windowTitleFont();
QFont taskBarFont = KGlobalSettings::taskbarFont();
QFont largeFont = KGlobalSettings::largeFont();

then set the fonts on the labels with the code,

currentFontLabel->setFont( currentFont );
fixedFontLabel->setFont( fixedFont );
toolBarFontLabel->setFont( toolBarFont );
menuFontLabel->setFont( menuFont );
windowTitleFontLabel->setFont( windowTitleFont );
taskBarFontLabel->setFont( taskBarFont );
largeFontLabel->setFont( largeFont );

by calling setFont on each label we get,


on my system. The system colours on the other hand are all shown during the paintEvent function and this at first looks quite complex. There are two aspects to this complexity in drawing code the first is the calculation code which at first glance can be quite confusing and the second is the wealth of unfamiliar functions and the number of parameters they take. So for this example there is precisely one drawing function that you haven't seen before and two font related functions which means that in about 150 lines of code there are about three lines that should be completely new, the rest is simple a case of wash, rinse and repeat.

We'll start with the basic maths which calulates where we are going to place the text and the rectangles that we are using to display the colours.

int nCenter = width() / 2;
int nQuarter = nCenter / 2;
int nThreeQuarters = nCenter + nQuarter;
int nStartHeight = 240;
int nStartWidth = 10;
int nDefaultHeight = 20;
int nRectWidth = nQuarter - 20;
int nRectHeight = height() / 10;
int nFirstRectLine = nStartHeight + ( nDefaultHeight *2 );
int nSecondTextHeight = nStartHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nSecondRectLine = nSecondTextHeight + nDefaultHeight;
int nThirdTextHeight = nSecondTextHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nThirdRectLine = nThirdTextHeight + nDefaultHeight;
int nFourthTextHeight = nThirdTextHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nFourthRectLine = nFourthTextHeight + nDefaultHeight;

Frightening huh? First we find a few basic positions on the widget.

int nCenter = width() / 2;
int nQuarter = nCenter / 2;
int nThreeQuarters = nCenter + nQuarter;

The nCenter variable is the exact center of the widget, the nQuarter is exactly half that and the nThreeQuarters is three quarter of the way across the widget. Now we have a few basic points lets define some defaults.

int nStartHeight = 240;
int nStartWidth = 10;
int nDefaultHeight = 20;

The nStartHeight is where we start drawing text below the text labels for the fonts. The nStartWidth gives us a little space from the left hand edge of the widget and the nDefaultHeight is the value we will use as spacer between the text and the rectangles we are going to draw.

The final variables we set with are original values are

int nRectWidth = nQuarter - 20;
int nRectHeight = height() / 10;

These define the height and the width of the rectangles that we are going to draw. The variables are defined like this so that the rectangles that we draw are based on the size of the widget that we are drawing to, this means that if you resize the program the rectangles will resize along with the widget.

The variables,

int nFirstRectLine = nStartHeight + ( nDefaultHeight *2 );
int nSecondTextHeight = nStartHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nSecondRectLine = nSecondTextHeight + nDefaultHeight;
int nThirdTextHeight = nSecondTextHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nThirdRectLine = nThirdTextHeight + nDefaultHeight;
int nFourthTextHeight = nThirdTextHeight + ( nDefaultHeight * 2 ) + nRectHeight + 20;
int nFourthRectLine = nFourthTextHeight + nDefaultHeight;

are all set to variables that we have already initialised and calulate the heights to start drawing the text and the rectangles.

Once we have set up the variables we need to set the title for this section on the widget.

QFontMetrics generalFontMetrics( KGlobalSettings::generalFont() );
int nGeneralFontWidth = generalFontMetrics.width( strTitle );
painter.drawText( nCenter - ( nGeneralFontWidth / 2 ), 50, strTitle );

We use the QFontMetrics class to get the information for the current font which is the standard or default font which is returned by calling KGlobalSettings::generalFont(). Once we have got the font information we can calculate the exact width of the string we are using for the title by passing it to the QFontMetrics width function. Then we draw the string to the center of the widget using drawText.

The rest of the function is a repetition of this code,

painter.drawText( nStartWidth, nStartHeight, strColours );
painter.drawText( nStartWidth, nStartHeight + nDefaultHeight, strToolBarHighlightColour );
        
painter.fillRect( nStartWidth, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::toolBarHighlightColor() ) );
        
int nFirstFontWidth = generalFontMetrics.width( strToolBarHighlightColour ) + 	20;
if( nFirstFontWidth > nQuarter )
{
    painter.drawText( nFirstFontWidth, nStartHeight + nDefaultHeight, strInactiveTitleColour );
    painter.fillRect( nFirstFontWidth, nFirstRectLine, nRectWidth, nRectHeight, 	
	QBrush( KGlobalSettings::inactiveTitleColor() ) );
}
else
{
    painter.drawText( nQuarter, nStartHeight + nDefaultHeight, 	strInactiveTitleColour );
    painter.fillRect( nQuarter, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::inactiveTitleColor() ) );
}
        
int nSecondFontWidth = nFirstFontWidth + generalFontMetrics.width( strInactiveTextColour ) + 20;
if( nSecondFontWidth > nCenter )
{
    painter.drawText( nSecondFontWidth, nStartHeight + nDefaultHeight, 	strInactiveTextColour );
    painter.fillRect( nSecondFontWidth, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::inactiveTextColor() ) );
}
else
{
    painter.drawText( nCenter, nStartHeight + nDefaultHeight, strInactiveTextColour );
    painter.fillRect( nCenter, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::inactiveTextColor() ) );
}
        
int nThirdFontWidth = nSecondFontWidth + generalFontMetrics.width( strActiveTitleColour ) + 20;
        
if( nThirdFontWidth > nThreeQuarters )
{
    painter.drawText( nThirdFontWidth, nStartHeight + nDefaultHeight, strActiveTitleColour );
    painter.fillRect( nThirdFontWidth, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::activeTitleColor() ) );
}
else
{
    painter.drawText( nThreeQuarters, nStartHeight + nDefaultHeight, strActiveTitleColour );
    painter.fillRect( nThreeQuarters, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::activeTitleColor() ) );
}

This page of code is repeated four times with the only changes being the string that is output and the colour of the rectangles being drawn. Actually if you looked closely at the code above you'll notice that this page itself draws the first line of rectangles and is made up of this code,

painter.drawText( nStartWidth, nStartHeight + nDefaultHeight, 	strToolBarHighlightColour );
        
painter.fillRect( nStartWidth, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::toolBarHighlightColor() ) );
        
int nFirstFontWidth = generalFontMetrics.width( strToolBarHighlightColour ) + 	20;
if( nFirstFontWidth > nQuarter )
{
    painter.drawText( nFirstFontWidth, nStartHeight + nDefaultHeight, strInactiveTitleColour );
    painter.fillRect( nFirstFontWidth, nFirstRectLine, nRectWidth, nRectHeight, 	
	QBrush( KGlobalSettings::inactiveTitleColor() ) );
}
else
{
    painter.drawText( nQuarter, nStartHeight + nDefaultHeight, strInactiveTitleColour );
    painter.fillRect( nQuarter, nFirstRectLine, nRectWidth, nRectHeight, 
	QBrush( KGlobalSettings::inactiveTitleColor() ) );
}

This code draws the label and the first rectangle, before drawing the label and second rectangle. It starts by drawing the text for the first label at the correct width and height and then draws the rectangle directly below it. The second rectangle is drawn differently because we have to first of all calculate where we are going to draw it. We do this by getting the width of the string that labels the rectangle and seeing if it is larger than a quarter of the way across the screen, the reason for this being that if the screen is too small for the text it will overlap and it looks better if we draw outside the bounds of the widget than it does if we try to squash everything in on itself. So if the width of the string is greater we draw it regardless if not we draw it at the required location which in this case is nQuarter in the next case it will be nCenter and in the final case it will be nThreeQuarters.

The end result is that when we run the program we get.


When we run it with the KDE default colours. If we go to the main menu and select Control Center/Appearance and Themes/Colors and select Blue Slate we get


Remember though that as the fonts are set during the constructor you will need to stop and restart the program before you will notice any changes to them. The colours will change whenever the colours are reset and you set the focus to the application which will force it to redraw thus changing the colours.

Tip Of the Day

Probably the most important thing to remember when painting to a widget is never call a function that is going to issue a repaint to the widget. This is the reason why the fonts are set up in the constructor as changing the font forces the widget to repaint itself so drawing the fonts by hand on the widget and then changing the drawing font for each one just means that the widget is constantly getting told to redraw itself. Actually as the different fonts are displayed in labels it would be perfectly acceptable to move them into the paint function and have them update at the same time as the coloured rectangles because the paint messages would be issued to the labels and not to the widget itself.

Drawing Example

In every book like this it is almost an unwritten rule that there will be some mostly pointless drawing example that you will never ever need to write in the real world. The reason these are included is because when you do need to do this sort of thing it can be a complete pain to get right and it does help to give an understanding of how the programs are working. So in the spirit of things we are going to look at some drawing examples the first being the ChapterTwelveDrawing example. This is a deliberately restricted example for two reasons, one I wanted to include owner draw menus and because I use them the design doesn't scale up into a completely workable application and two I wanted to show an example that displays the mechanics of how these work better than I have seen in some other beginners books. This is not to say that the other books are wrong it is just that the mechanics of how things work tend to get lost in the complications of getting everything working correctly.

So the plan is this with the first program we will concentrate on how to get the basics working and in the next program we will aim for a more functional application.

The first program is the ChapterTwelveDrawing example and it looks like this,


It is a plain and simple dialog to start with because we are going to use the surface to draw on and don't want to clutter it up with widgets. The idea here is that we have a minimal interface and do eveything through a single popup menu that is activated when we right click on the qwidget. You have seen a popup menu before but not one quite like this for a start it looks like,


and


Yes it is a single menu with the display being chosen by the check boxes on the menu. Admittedly this could have been better implemented as two seperate menus but the idea is to show what can be done here, not what is necessarily best from a design point of view. Of course the first thing that should strike you about the menus is that apart from the check box widgets they are entirely owner drawn so let's have a look at how it's done.

Owner Draw Menus.

We've already seen how versatile popup menu's can be and a quick glance at the help page for KPopupMenu and/or QPopupMenu shows us that we can use pictures as menu items but what if we want to be more expressive and do something that isn't already catered for in that case we can use the QCustomMenuItem class. This class is the class object that is used as a single menu item. The QCustomMenuItem class contains all the information required to qualify as a menu item while allowing us to specify exactly what happens when the menu item is drawn. So given the two views of the menu above we need six distinct classes. Five classes are to draw the shapes and one class to draw the coloured rectangles.

You should note that the check boxes are standard check boxes added in the normal way with,

coloursCheckBox = new QCheckBox( popupMenu, "Colours" );
coloursCheckBox->setText( "Colours" );
coloursCheckBox->setChecked( false );
popupMenu->insertItem( coloursCheckBox, ColoursCheckBoxItem );

The signals are then connected using,

connect( coloursCheckBox, SIGNAL( clicked() ), this, SLOT( coloursClicked() ) );

To start with the standard rectangle we would define the class as,

class KRectMenuItem : public QCustomMenuItem
{
public:
        KRectMenuItem();
        virtual ~KRectMenuItem();
        
virtual void paint( QPainter *p, const QColorGroup& cg, bool act, bool enabled,
		 int x, int y, int w, int h );
        virtual QSize sizeHint();
};

As you can see the KRectMenuItem contains only a constructor, a destructor and the two functions required to control the drawing, these being the sizeHint function which reserves space in the menu and the paint function. The important one for us being the paint function,

void KRectMenuItem::paint( QPainter *p, const QColorGroup& /*cg*/, bool /*act*/, bool /*enabled*/,
		 int x, int y, int w, int h )
{
        p->drawRect( x, y, w*2, h-2 );
}

When the paint function is called it passes more than enough information than we need here for what we are trying to do. We are given a valid painter to the menu item that we are painting and the colours available in the QColorGroup object. The active and the enabled parameters are the menus status while the x and y are the starting point for our menu item, with the w being the width and the h being the height. In all the calculations for the owner draw menu items shown here we use w*2 as we are drawing over the space reserved for the short cut keys as well.

Given this information we can see that in order to draw an owner drawn round rect we need to declare an identical class and in the constructor use the function.

p->drawRoundRect( x, y, w*2, h-2 );

and for the ellipse,

p->drawEllipse( x, y, w*2, h-2 );

In order to draw the KLineMenuItem we have to be a little more creative,

p->drawLine( x, y+10, x+( w*2 ), y+10 );

increasing the height given in the y parameter to center the line drawing and then using x+( w*2 ) to specify exactly where we want the line drawing to. With the KTriangleMenuItem we have to be a lot more creative,

QPointArray points;
points.setPoints( 4, x + w, y, x, y + ( h-2 ), x + ( w*2 ), y + ( h-2 ), x + w, y );
p->drawPolyline( points );

Here we need to create a QPointsArray and then declared four distinct points, these being x+w, y which is exactly halfway across the top of the drawing space at the height of y, then x, y + ( h -2 ), which is the point at the x at the height of y + ( h-2 ) which in English translates to the bottom left hand corner as you look at it. Then the next point is the bottom right hand corner as you look at it which translates to the code x + ( w*2 ), y + ( h-2 ), with the final point being back at the top in the middle of the menu item or x + w, y. once the points are set we pass the points array to the drawPolyline function.

The KColourRectMenuItem is dealt with in exactly the same way first we define the class as,

class KColourRectMenuItem : public QCustomMenuItem
{
public:
        KColourRectMenuItem();
        KColourRectMenuItem( QColor colour );
        virtual ~KColourRectMenuItem();
        
virtual void paint( QPainter *p, const QColorGroup& cg, bool act, bool enabled,
		 int x, int y, int w, int h );
        virtual QSize sizeHint();
        
private:
        QColor cColour;
};

Then the paint function contains the code,

p->fillRect( x, y, w*2, h-2, QBrush( cColour ) );

which draw a rectangle the same as the drawRect function and fills it with the passed in colour in a standard brush. The standard brush is a solid pattern to see the available brush styles see the QBrush reference documentation.

Once they are created the new custom menu items are added to the popup menu exactly as you would expect.

rectMenuItem = new KRectMenuItem();
popupMenu->insertItem( rectMenuItem, RectMenuItem );

roundRectMenuItem = new KRoundRectMenuItem();
popupMenu->insertItem( roundRectMenuItem, RoundRectMenuItem );

ellipseMenuItem = new KEllipseMenuItem();
popupMenu->insertItem( ellipseMenuItem, EllipseMenuItem );

Each menu item is created and then added to the QPopupMenu with the insertItem function which takes the newly created menu item and an identifier that is returned when a menu item is selected on the popup menu.

The identifiers are defined in the ChapterTwelveDrawingWidget.h class file as,

enum PopupMenuIdentifiers{ ShapesCheckBoxItem, ColoursCheckBoxItem, RectMenuItem, RoundRectMenuItem,
	 EllipseMenuItem, LineMenuItem, TriangleMenuItem, BlackColourMenuItem, WhiteColourMenuItem,
	 DarkGreyColourMenuItem, GreyColourMenuItem, LightGreyColourMenuItem, RedColourMenuItem,
	 GreenColourMenuItem, BlueColourMenuItem, CyanColourMenuItem, MagentaColourMenuItem,
	 YellowColourMenuItem, DarkRedColourMenuItem, DarkGreenColourMenuItem, DarkBlueColourMenuItem,
	 DarkCyanColourMenuItem, DarkMagentaColourMenuItem, DarkYellowColourMenuItem  };

These are used not only to identify what colour or shape has been selected but also to set what is being displayed, so initially the colour menu items are set up as.

blackRectMenuItem = new KColourRectMenuItem();
popupMenu->insertItem( blackRectMenuItem, BlackColourMenuItem );
popupMenu->setItemVisible( BlackColourMenuItem, false );

whiteRectMenuItem = new KColourRectMenuItem( Qt::white );
popupMenu->insertItem( whiteRectMenuItem, WhiteColourMenuItem );
popupMenu->setItemVisible( WhiteColourMenuItem, false );

As you can see we add the colour menu items to the QPopupMenu but set them to be hidden by default and then we use the check boxes to set which menu items we are going to see.

void ChapterTwelveDrawingWidget::coloursClicked()
{
  bColours = true;
  popupMenu->changeTitle( 1, "Colours" );
  // remove the shapes
  popupMenu->setItemVisible( RectMenuItem, false );
  popupMenu->setItemVisible( RoundRectMenuItem, false );
  popupMenu->setItemVisible( EllipseMenuItem, false );
  popupMenu->setItemVisible( LineMenuItem, false );
  popupMenu->setItemVisible( TriangleMenuItem, false );

With the above code showing the start of the coloursClicked function which hides all the shape menu items and unhides all the colour menu items.

Drawing The Shapes

In order that we can draw the shapes on the widget we must first track them when they are drawn by the user of the application. This is done by installing and eventFilter,

installEventFilter( this );

and the capturing the mouse button presses and movements.

bool ChapterTwelveDrawingWidget::eventFilter( QObject *watched, QEvent *e )
{
  if( watched == this  )
  {
    if( e->type() == QEvent::MouseButtonPress )
    {
    }
    if( e->type() == QEvent::MouseButtonRelease )
    {
    }
    if( e->type() == QEvent::MouseMove )
    {
    }
  }

  return false;
}

As you can see from the skeleton code above we are interested in the button press the button release and the mouse move events. The button press events are used to control the popup menu and to start the drawing code. The code is,

QMouseEvent *mouseEvent = static_cast< QMouseEvent * >( e );
if( mouseEvent->button() == Qt::LeftButton )
{
  bDrawOutline = true;
  startPosition = mouseEvent->pos();
}

if( mouseEvent->button() == Qt::RightButton )
{
  DisplayPopup();
}

return true;

Pressing the right mouse button on the widget calls the DisplayPopup function which displays the popup menu with it's custom objects,

nPopupReturn = popupMenu->exec( QCursor::pos() );
/// selected a shape
if( bColours == false )
{
  nDrawItem = nPopupReturn;
}
else /// selected a colour
{
  switch( nPopupReturn )
  {
    case BlackColourMenuItem : cDrawingColour = Qt::black; break;
    case WhiteColourMenuItem : cDrawingColour = Qt::white; break;
    case DarkGreyColourMenuItem : cDrawingColour = Qt::darkGray; break;
    case GreyColourMenuItem : cDrawingColour = Qt::gray; break;
    case LightGreyColourMenuItem : cDrawingColour = Qt::lightGray; break;
    case RedColourMenuItem : cDrawingColour = Qt::red; break;
    case GreenColourMenuItem : cDrawingColour = Qt::green; break;
    case BlueColourMenuItem : cDrawingColour = Qt::blue; break;
    case CyanColourMenuItem : cDrawingColour = Qt::cyan; break;
    case MagentaColourMenuItem : cDrawingColour = Qt::magenta; break;
    case YellowColourMenuItem : cDrawingColour = Qt::yellow; break;
    case DarkRedColourMenuItem : cDrawingColour = Qt::darkRed; break;
    case DarkGreenColourMenuItem : cDrawingColour = Qt::darkGreen; break;
    case DarkBlueColourMenuItem : cDrawingColour = Qt::darkBlue; break;
    case DarkCyanColourMenuItem : cDrawingColour = Qt::darkCyan; break;
    case DarkMagentaColourMenuItem : cDrawingColour = Qt::darkMagenta; break;
    case DarkYellowColourMenuItem : cDrawingColour = Qt::darkYellow; break;
  };
}

The result is the stored in the a class variable either the nDrawItem or the cDrawingColour depending on which section of the menu is being displayed.

Pressing the left mouse button doesn't call any functions it simply sets the boolean variable bDrawOutline to true and stores the current mouse position. In order to make something happen the user has to press the left mouse button and then move the mouse whilst holding the button down, thus giving us the MouseMove event which we respond to with the code,

if( bDrawOutline == true )
{
  QMouseEvent *mouseEvent = static_cast< QMouseEvent * >( e );
  DrawOutLine( mouseEvent->pos() );
}

return true;

The DrawOutLine function draws a dotted outline of the currently selected shape so the user can see the size and position,

QPainter painter( this );
int nWidth = currentPos.x() - startPosition.x();
int nHeight = currentPos.y() - startPosition.y();

QRect rect( startPosition.x(), startPosition.y(), nWidth, nHeight );
if( nDrawItem == LineMenuItem )
{
  painter.eraseRect( rect );
  repaint( rect );
}
else
{
  repaint( rect );
}

painter.setPen( Qt::DotLine );
        
switch( nDrawItem )
{
  case RectMenuItem: 
  {
    painter.drawRect( rect );
    painter.drawRect( startPosition.x(), startPosition.y(), nWidth, nHeight );
  }; break;
  case RoundRectMenuItem:
  {
    painter.drawRect( rect );
    painter.drawRoundRect( rect );
  }; break;
  case EllipseMenuItem:
  {
    painter.drawRect( rect );
    painter.drawEllipse( rect );
  }; break;
  case LineMenuItem:
  {
    painter.drawLine( startPosition.x(), startPosition.y(), currentPos.x(), currentPos.y() );
  }; break;
  case TriangleMenuItem:
  {
    painter.drawRect( rect );
    QPointArray points;
    points.setPoints( 4, startPosition.x() + nWidth/2, startPosition.y(), startPosition.x(),
	 startPosition.y() + ( nHeight-2 ), startPosition.x() + ( nWidth ), startPosition.y()
	 + ( nHeight-2 ), startPosition.x() + 	nWidth/2, startPosition.y() );
    painter.drawPolyline( points );
  }; break;
}

We start by calculating the current rectangle from the start position which was saved when the mouse button was first pressed down and then we erase that rectangle on the widget with a call to repaint. This erases the old dotted line drawing for the shape we are drawing so that we can then redraw the it with the new size. To see how this works properly comment out the repaint and run the program then. You can see that the line drawing function is treated differently this is because of how we are trying to reduce the amount of drawing that we are doing at anyone point, in order to reduce screen flicker. There are other, better ways of reducing screen flicker that we will look at shortly.

This is what drawing a triangle will look like at the start.


And this drawing a triangle over other shapes,


The shape itself is not drawn until after the mouse button is released. The code executed on the MouseButtonRelease event is,

QMouseEvent *mouseEvent = static_cast< QMouseEvent * >( e );
if( mouseEvent->button() == Qt::LeftButton )
{
  bDrawOutline = false;
  /// save the current shape and repaint the drawing area
  KShape save;
  if( nDrawItem != LineMenuItem )
  {
    save.setX( startPosition.x() );
    save.setY( startPosition.y() );
    save.setWidth( mouseEvent->x() - startPosition.x() );
    save.setHeight( mouseEvent->y() - startPosition.y() );
    save.setShape( nDrawItem );
    save.setColour( cDrawingColour );
  }
  else
  {
    save.setX( startPosition.x() );
    save.setY( startPosition.y() );
    save.setWidth( mouseEvent->x() );
    save.setHeight( mouseEvent->y() );
    save.setShape( nDrawItem );
    save.setColour( cDrawingColour );
  }

  shapeList.append( save );
  repaint();

  return true;

All the MouseButtonRelease does is save the current shape and position, with the extra code being to handle the line where we are saving specific points and not the whole rectangle which is required to draw the other shapes. The shape details are saved in the KShape class which as we have seen previously is a simple data class that looks like,

class KShape
{
public:
  KShape();
  KShape( int x, int y, int width, int height, int shape, QColor colour );
  virtual ~KShape();

  inline int x(){ return nX; };
  inline void setX( int x ){ nX = x; };
  inline int y(){ return nY; };
  inline void setY( int y ){ nY = y; };
  inline int width(){ return nWidth; };
  inline void setWidth( int width ){ nWidth = width; };
  inline int height(){ return nHeight; };
  inline void setHeight( int height ){ nHeight = height; };
  inline int shape(){ return nShape; };
  inline void setShape( int shape ){ nShape = shape; };
  inline QColor colour(){ return cColour; };
  inline void setColour( QColor colour ){ cColour = colour; };
private:
  int nX;
  int nY;
  int nWidth;
  int nHeight;
  int nShape;
  QColor cColour;
};

As previously the data class is then stored in a collection which is defined as,

typedef QValueList< KShape > ShapeList;

The shapes are then appended to the collection and the MouseButtonRelease code then issues a repaint call which will issue a paintEvent to the widget.

The paintEvent function is then pretty much what we should expect,

void ChapterTwelveDrawingWidget::paintEvent( QPaintEvent */*paintEvent*/ )
{
  QPainter painter( this );
  /// draw the saved shapes
  ShapeList::iterator it;
  KShape shape;
  painter.setPen( Qt::SolidLine );
  for( it = shapeList.begin(); it != shapeList.end(); it++ )
  {
    shape = ( KShape )( *it );
    painter.setBrush( shape.colour() );
    painter.setPen( shape.colour() );
    switch( shape.shape() )
    {

We set the current pen to a solid line and then iterate through ShapeList collection of KShapes using the switch statement to identify which shape is to be drawn and then drawing it in the currently selected colour.


We can then draw things like,


if you really want to.

Menus And Double Buffering

In the previous example the demonstration code had obvious limitations which were largely caused by the way in which the menus for the application were chosen, using only the popup menus it was clear that trying to implement more options would have led to a more and more unmanageable menu system that would just get confusing and irritating for the user. So for the next application we will add the menus by hand to see what is required when we want to write an application that looks like a proper program. Another problem is that the more images you draw on the widget the more the application flickers when the widget is drawn, although this has been kept to a minimum it is still noticable and basically no flicker is good. So the next program the ChapterTwelveMenus project also includes double buffering in such a way that you can run the program and turn it on and off to see the improvement for yourself. But first to the menus.

Menus

The first thing you should notice about the ChapterTwelveMenus project is that this is the point where we start to write applications that look like real applications, by having menus etc. The project looks like,


For the purposes of this project we have four menu options and we add them all to the project by hand. To do this we need these objects,

KMenuBar *menuBar;
KColoursMenu *coloursMenu;
KDrawingMenu *drawingMenu;
KPopupMenu *viewPopup;
KPopupMenu *filePopup;

This introduces three new classes that need a little explanation firstly the KColoursMenu and the KDrawingMenu classes are classes that I have written to encapsulate what we have already done before in that they control the options for the colours and the drawing options from the previous ChapterTwelveDrawing project. The header for the KColoursMenu class looks like this,

class KColoursMenu : public KPopupMenu
{
public:
    KColoursMenu();

    ~KColoursMenu();
    
    inline void setDrawingColour( QColor colour ){ cDrawingColour = colour; };
    inline QColor drawingColour(){ return cDrawingColour; };
        
private:
        KColourRectMenuItem *blackRectMenuItem;
        KColourRectMenuItem *whiteRectMenuItem;
        KColourRectMenuItem *darkGreyRectMenuItem;
        KColourRectMenuItem *greyRectMenuItem;
        KColourRectMenuItem *lightGreyRectMenuItem;
        KColourRectMenuItem *redRectMenuItem;
        KColourRectMenuItem *greenRectMenuItem;
        KColourRectMenuItem *blueRectMenuItem;
        KColourRectMenuItem *cyanRectMenuItem;
        KColourRectMenuItem *magentaRectMenuItem;
        KColourRectMenuItem *yellowRectMenuItem;
        KColourRectMenuItem *darkRedRectMenuItem;
        KColourRectMenuItem *darkGreenRectMenuItem;
        KColourRectMenuItem *darkBlueRectMenuItem;
        KColourRectMenuItem *darkCyanRectMenuItem;
        KColourRectMenuItem *darkMagentaRectMenuItem;
        KColourRectMenuItem *darkYellowRectMenuItem;
        QColor cDrawingColour;
};

As you can see it simple encapsulates the colour items from the previous example and the implementation is identical also with the constructor containing code along the lines of,

blackRectMenuItem = new KColourRectMenuItem();
insertItem( blackRectMenuItem, BlackColourMenuItem );
        
whiteRectMenuItem = new KColourRectMenuItem( Qt::white );
insertItem( whiteRectMenuItem, WhiteColourMenuItem );
        
darkGreyRectMenuItem = new KColourRectMenuItem( Qt::darkGray );
insertItem( darkGreyRectMenuItem, DarkGreyColourMenuItem );

As you can imagine the KDrawingMenu is likewise an implementation of the Drawing options available in the previous project.

The KMenuBar on the other hand is completely new and draws a blank menu for us across the top of the widget. We do this with the code,

menuBar = new KMenuBar( this );

The way the menus work is that you start out with a KMenuBar and then you add the popup menus to it and then add the actual menu options to the popupmenu, so with the file menu we add the file KPopupMenu object filePopup with the code,

filePopup = new KPopupMenu();
menuBar->insertItem( strFile, filePopup );

Actions

The preferred way of doing things in KDE is with actions. An action is an object set up to define a menu option and what image and text it shows. The action is then added to the menu or the toolbar and yes the same action object can be used for both. A call to connect is then used to define what happens when that action is chosen as a menu or toolbar option. We will see how they work with toolbars later but for now we will just define them to work with a menu. So taking the first one first the File New menu option is set up with the code.

fileNewAction = new QAction( strFileNew, 0, filePopup );
fileNewAction->setIconSet( BarIconSet( "filenew.png" ) );
fileNewAction->setStatusTip( strFileNewStatusTip );
connect( fileNewAction, SIGNAL( activated() ), this, SLOT( fileNew() ) );
fileNewAction->addTo( filePopup );

To start with we create the fileNewAction object giving it the string “File New”, we don't specify a keyboard accelerator but we do specify the filePopup menu as the parent.

We then set the icon using the BarIconSet function which is a function exported by the KDE core library and is documented as a related function of KIconLoader. This function gets the icon file named and displays it on the menu. On Suse 10 the icons are to be found in root/opt


The default KDE icons are shown here but the BarIconSet function will get whatever files you have selected in the Control Center/Apperance And Themes/Icons section. Next we connect the actions activated signal to our fileNew function before adding the action to the filePopup menu.

The rest of the file menu is set up in exactly the same way, create the Action setting the text in the constructor and then adding the icon for the menu item before setting the tool tip and then connecting it to a function.


The view menu is slightly different in that the view menu contains two items,


These both work as check box options that are used within the code, we'll look at what they do later first of all you set up a check box menu item with the code,

viewPopup = new KPopupMenu();
menuBar->insertItem( strView, viewPopup );
doubleBufferAction = new QAction( strDoubleBuffer, 0, viewPopup );
doubleBufferAction->setToggleAction( true );
doubleBufferAction->setOn( true );
doubleBufferAction->setStatusTip( strDoubleBufferStatusTip );
connect( doubleBufferAction, SIGNAL( toggled( bool ) ), this,
	 SLOT( 	toggleDoubleBuffer( bool ) ) ); 
doubleBufferAction->addTo( viewPopup );

We set up the menu the same as previously by creating a KPopupMenu item and adding it to the menu bar, then we call the setToggleAction function on the action to true and in the case of the double buffer action set it to true by default.

For the colours and the drawing menus there is a difference as we have already created the classes to hold the menus.

coloursMenu = new KColoursMenu();
menuBar->insertItem( i18n( "Colours" ), coloursMenu );
coloursAction = new QAction( coloursMenu, "ColoursAction" );
connect( coloursMenu, SIGNAL( activated( int ) ), this, SLOT( coloursClicked( 	int ) ) ); 

Here we create the entire menu as one object and as it is derived from KPopupMenu we insert it into the QMenuBar as normal. The difference is that when we create the action we create it for the whole menu not for each specific menu option which means