\documentclass[]{article}
\usepackage{amsmath,graphicx,supertabular,hyperref,tabularx,ifthen}
\title{TIP \#11: Tk Menubutton Enhancement: -compound option for menubutton}
\date{November 16, 2000}
\author{Todd Helfter}
\urlstyle{sf}
\setlength{\parskip}{1ex}
\setlength{\parindent}{0pt}
\def\tipversion$#1${\texttt{\$#1\$}}
\def\tiplangle#1{\ensuremath{<}}
\def\tiprangle#1{\ensuremath{>}}
\def\tipbar#1{\ensuremath{|}}
\def\tipmail#1#2{\(\langle\){\small\expandafter\url{#1@#2}}\(\rangle\)}
\ifx\pdfoutput\undefined
\newcommand{\tipimage}[2]{\typeout{Make sure you download #1.eps}\ifthenelse{\lengthtest{0.8\textwidth>#2}\and\lengthtest{0pt<#2}}{\includegraphics{#1.eps}}{\includegraphics[width=0.8\textwidth]{#1.eps}}}
\newcommand{\tipxref}[1]{}
\newcommand{\tipxrefend}{}
\else
\newcommand{\tipimage}[2]{\typeout{Make sure you create #1.pdf}\ifthenelse{\lengthtest{0.8\textwidth>#2}\and\lengthtest{0pt<#2}}{\includegraphics{#1.pdf}}{\includegraphics[width=0.8\textwidth]{#1.pdf}}}
\pdfcatalog{/PageMode /UseOutlines}
\newcommand{\tipxref}[1]{\pdfannotlink attr {/C [0.5 0.5 1.0] /Border [0 0 1]} goto name {#1}}
\newcommand{\tipxrefend}{\pdfendlink}
\fi
\newenvironment{tipabstract}{\begin{abstract}}{\end{abstract}}
\begin{document}\maketitle
\begin{center}\begin{tabularx}{\linewidth}{|r@{: }X|}\hline
\textbf{TIP \#11}&\textbf{Tk Menubutton Enhancement: -compound option for menubutton}\\\hline
Author&

Todd Helfter \tipmail{tmh}{purdue.edu}
\\
Created&Thursday, \(\text{16}^{\text{th}}\) November 2000\\
Type&Project\\
Tcl Version&\(8.4\)\\
State&Final\\
Vote&Done\\
Version&\tipversion$Revision: 1.5 $\\
Post History&\\
\hline\end{tabularx}\end{center}
\thispagestyle{empty}\pagestyle{empty}
\begin{tipabstract}

This TIP describes how to change the menubutton in the Tk core to add a -compound option to display both text and images. This behavior already exists in the button widget.
\end{tipabstract}
\tableofcontents\setcounter{page}{0}\clearpage\pagestyle{plain}

\section{Rationale}

In order to have a menubutton with both text and images, this change is needed. This change facilitates the use of an image for the menubutton face with text on top. Like the button widget, the -compound option will accept these values: none, center, left, right, top, bottom.

\section{Reference Implementation}

This TIP proposes to change the internals of the menubutton.

The changes necessary to accomplish this are:
\begin{enumerate}
\item{}
Extend the structure \textit{TkMenuButton} in \textit{generic/tkMenubutton.h} with a new field of type \textit{int} to hold the value of the compound setting.
\setcounter{enumi}{1}
\item{}
Add an enumeration of valid -compound options in \textit{generic/tkMenubutton.h}.
\setcounter{enumi}{2}
\item{}
Modify \textit{generic/tkMenuButton.c} and \textit{unix/tkUnixMenubu.c} in such a way to process this new option. Note: The windows port of Tk uses the \textit{unix/tkUnixMenubu.c} file. So this change is portable to both Unix and windows.
\setcounter{enumi}{3}
\item{}
Change \textit{tests/menubut.test} so that the test for configure options checks for 33 instead of the current 32.
\setcounter{enumi}{4}
\item{}
Change \textit{doc/menubutton.n} to show the new option under widget specific options.
\end{enumerate}

\section{Copyright}

This document has been placed in the public domain.

\section{Patch}
\begingroup\small\begin{verbatim}
Index: doc/menubutton.n
===================================================================
RCS file: /cvsroot/tk/doc/menubutton.n,v
retrieving revision 1.3
diff -c -r1.3 menubutton.n
*** menubutton.n	2000/08/25 06:58:32	1.3
--- menubutton.n	2000/11/16 14:37:15
***************
*** 26,31 ****
--- 26,39 ----
  \-disabledforeground	\-padx
  .SE
  .SH "WIDGET-SPECIFIC OPTIONS"
+ .OP \-compound compound Compound
+ Specifies whether the menubutton should display both an image and text,
+ and if so, where the image should be placed relative to the text.
+ Valid values for this option are \fBbottom\fR, \fBcenter\fR,
+ \fBleft\fR, \fBnone\fR, \fBright\fR and \fBtop\fR.  The default value
+ is \fBnone\fR, meaning that the menubutton will display either an image or
+ text, depending on the values of the \fB\-image\fR and \fB\-bitmap\fR
+ options.
  .VS
  .OP \-direction direction Height
  Specifies where the menu is going to be popup up. \fBabove\fR tries to
Index: generic/tkMenubutton.c
===================================================================
RCS file: /cvsroot/tk/generic/tkMenubutton.c,v
retrieving revision 1.4
diff -c -r1.4 tkMenubutton.c
*** tkMenubutton.c	1999/04/24 01:50:49	1.4
--- tkMenubutton.c	2000/11/16 14:37:16
***************
*** 37,42 ****
--- 37,51 ----
  };
  
  /*
+  * The following table defines the legal values for the -compound option.
+  * It is used with the "enum compound" declaration in tkButton.h
+  */
+ 
+ static char *compoundStrings[] = {
+     "bottom", "center", "left", "none", "right", "top", (char *) NULL
+ };
+ 
+ /*
   * Information used for parsing configuration specs:
   */
  
***************
*** 113,118 ****
--- 122,130 ----
      {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
	 DEF_MENUBUTTON_RELIEF, -1, Tk_Offset(TkMenuButton, relief), 
	   0, 0, 0},
+     {TK_OPTION_STRING_TABLE, "-compound", "compound", "Compound",
+          DEF_BUTTON_COMPOUND, -1, Tk_Offset(TkMenuButton, compound), 0,
+          (ClientData) compoundStrings, 0},
      {TK_OPTION_STRING_TABLE, "-state", "state", "State",
	 DEF_MENUBUTTON_STATE, -1, Tk_Offset(TkMenuButton, state),
	 0, (ClientData) stateStrings, 0},
Index: generic/tkMenubutton.h
===================================================================
RCS file: /cvsroot/tk/generic/tkMenubutton.h,v
retrieving revision 1.5
diff -c -r1.5 tkMenubutton.h
*** tkMenubutton.h	1999/04/16 01:51:19	1.5
--- tkMenubutton.h	2000/11/16 14:37:16
***************
*** 25,30 ****
--- 25,39 ----
  #endif
  
  /*
+  * Legal values for the "compound" field of TkButton records.
+  */
+ 
+ enum compound {
+     COMPOUND_BOTTOM, COMPOUND_CENTER, COMPOUND_LEFT, COMPOUND_NONE,
+         COMPOUND_RIGHT, COMPOUND_TOP
+ };
+ 
+ /*
   * Legal values for the "orient" field of TkMenubutton records.
   */
  
***************
*** 161,166 ****
--- 170,179 ----
      /*
	* Miscellaneous information:
	*/
+ 
+     int compound;               /* Value of -compound option; specifies whether
+                                  * the button should show both an image and
+                                  * text, and, if so, how. */
  
      enum direction direction;	/* Direction for where to pop the menu.
				  * Valid directions are "above", "below",
Index: tests/menubut.test
===================================================================
RCS file: /cvsroot/tk/tests/menubut.test,v
retrieving revision 1.5
diff -c -r1.5 menubut.test
*** menubut.test	1999/04/21 21:53:29	1.5
--- menubut.test	2000/11/16 14:37:18
***************
*** 138,144 ****
  } {3}
  test menubutton-3.7 {ButtonWidgetCmd procedure, "configure" option} {
      llength [.mb configure]
! } {32}
  test menubutton-3.8 {ButtonWidgetCmd procedure, "configure" option} {
      list [catch {.mb configure -gorp} msg] $msg
  } {1 {unknown option "-gorp"}}
--- 138,144 ----
  } {3}
  test menubutton-3.7 {ButtonWidgetCmd procedure, "configure" option} {
      llength [.mb configure]
! } {33}
  test menubutton-3.8 {ButtonWidgetCmd procedure, "configure" option} {
      list [catch {.mb configure -gorp} msg] $msg
  } {1 {unknown option "-gorp"}}
Index: unix/tkUnixMenubu.c
===================================================================
RCS file: /cvsroot/tk/unix/tkUnixMenubu.c,v
retrieving revision 1.4
diff -c -r1.4 tkUnixMenubu.c
*** tkUnixMenubu.c	1999/09/21 06:43:01	1.4
--- tkUnixMenubu.c	2000/11/16 14:37:18
***************
*** 75,83 ****
      Pixmap pixmap;
      int x = 0;			/* Initialization needed only to stop
				  * compiler warning. */
!     int y;
      register Tk_Window tkwin = mbPtr->tkwin;
!     int width, height;
  
      mbPtr->flags &= ~REDRAW_PENDING;
      if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
--- 75,85 ----
      Pixmap pixmap;
      int x = 0;			/* Initialization needed only to stop
				  * compiler warning. */
!     int y = 0;
      register Tk_Window tkwin = mbPtr->tkwin;
!     int width, height, fullWidth, fullHeight;
!     int imageXOffset, imageYOffset, textXOffset, textYOffset;
!     int haveImage = 0, haveText = 0;
  
      mbPtr->flags &= ~REDRAW_PENDING;
      if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
***************
*** 96,101 ****
--- 98,112 ----
	 border = mbPtr->normalBorder;
      }
  
+     if (mbPtr->image != None) {
+ 	Tk_SizeOfImage(mbPtr->image, &width, &height);
+ 	haveImage = 1;
+     } else if (mbPtr->bitmap != None) {
+ 	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
+ 	haveImage = 1;
+     }
+     haveText = (mbPtr->textWidth != 0 && mbPtr->textHeight != 0);
+ 
      /*
	* In order to avoid screen flashes, this procedure redraws
	* the menu button in a pixmap, then copies the pixmap to the
***************
*** 107,141 ****
	     Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
      Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
	     Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
- 
-     /*
-      * Display image or bitmap or text for button.
-      */
  
!     if (mbPtr->image != None) {
! 	Tk_SizeOfImage(mbPtr->image, &width, &height);
! 
! 	imageOrBitmap:
! 	TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0, 
! 		width + mbPtr->indicatorWidth, height, &x, &y);
! 	if (mbPtr->image != NULL) {
! 	    Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
! 		    x, y);
! 	} else {
! 	    XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
! 		    gc, 0, 0, (unsigned) width, (unsigned) height, x, y, 1);
! 	}
!     } else if (mbPtr->bitmap != None) {
! 	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
! 	goto imageOrBitmap;
      } else {
! 	TkComputeAnchor(mbPtr->anchor, tkwin, mbPtr->padX, mbPtr->padY,
! 		mbPtr->textWidth + mbPtr->indicatorWidth,
! 		mbPtr->textHeight, &x, &y);
! 	Tk_DrawTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout, x, y,
! 		0, -1);
! 	Tk_UnderlineTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout,
! 		x, y, mbPtr->underline);
      }
  
      /*
--- 118,223 ----
	     Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
      Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
	     Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
  
!     imageXOffset = 0;
!     imageYOffset = 0;
!     textXOffset = 0;
!     textYOffset = 0;
!     fullWidth = 0;
!     fullHeight = 0;
! 
!     if (mbPtr->compound != COMPOUND_NONE && haveImage && haveText) {
! 
!         switch ((enum compound) mbPtr->compound) {
!             case COMPOUND_TOP:
!             case COMPOUND_BOTTOM: {
!                 /* Image is above or below text */
!                 if (mbPtr->compound == COMPOUND_TOP) {
!                     textYOffset = height + mbPtr->padY;
!                 } else {
!                     imageYOffset = mbPtr->textHeight + mbPtr->padY;
!                 }
!                 fullHeight = height + mbPtr->textHeight + mbPtr->padY;
!                 fullWidth = (width > mbPtr->textWidth ? width :
!                         mbPtr->textWidth);
!                 textXOffset = (fullWidth - mbPtr->textWidth)/2;
!                 imageXOffset = (fullWidth - width)/2;
!                 break;
!             }
!             case COMPOUND_LEFT:
!             case COMPOUND_RIGHT: {
!                 /* Image is left or right of text */
!                 if (mbPtr->compound == COMPOUND_LEFT) {
!                     textXOffset = width + mbPtr->padX;
!                 } else {
!                     imageXOffset = mbPtr->textWidth + mbPtr->padX;
!                 }
!                 fullWidth = mbPtr->textWidth + mbPtr->padX + width;
!                 fullHeight = (height > mbPtr->textHeight ? height :
!                         mbPtr->textHeight);
!                 textYOffset = (fullHeight - mbPtr->textHeight)/2;
!                 imageYOffset = (fullHeight - height)/2;
!                 break;
!             }
!             case COMPOUND_CENTER: {
!                 /* Image and text are superimposed */
!                 fullWidth = (width > mbPtr->textWidth ? width :
!                         mbPtr->textWidth);
!                 fullHeight = (height > mbPtr->textHeight ? height :
!                         mbPtr->textHeight);
!                 textXOffset = (fullWidth - mbPtr->textWidth)/2;
!                 imageXOffset = (fullWidth - width)/2;
!                 textYOffset = (fullHeight - mbPtr->textHeight)/2;
!                 imageYOffset = (fullHeight - height)/2;
!                 break;
!             }
!             case COMPOUND_NONE: {break;}
!         }
! 
!         TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0,
!                 mbPtr->indicatorWidth + fullWidth, fullHeight,
! 		&x, &y);
! 
!         if (mbPtr->image != NULL) {
!             Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
!                     x + imageXOffset, y + imageYOffset);
!         }
!         if (mbPtr->bitmap != None) {
!             XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
!                     gc, 0, 0, (unsigned) width, (unsigned) height, 
! 		    x + imageXOffset, y + imageYOffset, 1);
!         }
!         if (haveText) {
!             Tk_DrawTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout, 
! 		    x  + textXOffset, y + textYOffset ,
!                     0, -1);
!             Tk_UnderlineTextLayout(mbPtr->display, pixmap, gc, 
! 		    mbPtr->textLayout, x + textXOffset, y + textYOffset ,
! 		    mbPtr->underline);
!         }
      } else {
!        if (mbPtr->image != NULL) {
!            TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0,
!                    width + mbPtr->indicatorWidth, height, &x, &y);
!            Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
!                    x + imageXOffset, y + imageYOffset);
!        } else if (mbPtr->bitmap != None) {
!            TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0,
!                    width + mbPtr->indicatorWidth, height, &x, &y);
!            XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
!                    gc, 0, 0, (unsigned) width, (unsigned) height, 
! 		   x + imageXOffset, y + imageYOffset, 1);
!        } else {
!            TkComputeAnchor(mbPtr->anchor, tkwin, mbPtr->padX, mbPtr->padY,
!                    mbPtr->textWidth + mbPtr->indicatorWidth,
!                    mbPtr->textHeight, &x, &y);
!            Tk_DrawTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout, 
! 		   x  + textXOffset, y + textYOffset ,
!                    0, -1);
!            Tk_UnderlineTextLayout(mbPtr->display, pixmap, gc, 
! 		   mbPtr->textLayout, x + textXOffset, y + textYOffset ,
! 		   mbPtr->underline);
!         }
      }
  
      /*
***************
*** 252,305 ****
      TkMenuButton *mbPtr;	/* Widget record for menu button. */
  {
      int width, height, mm, pixels;
  
      mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth;
      if (mbPtr->image != None) {
	 Tk_SizeOfImage(mbPtr->image, &width, &height);
! 	if (mbPtr->width > 0) {
! 	    width = mbPtr->width;
! 	}
! 	if (mbPtr->height > 0) {
! 	    height = mbPtr->height;
! 	}
      } else if (mbPtr->bitmap != None) {
	 Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
! 	if (mbPtr->width > 0) {
! 	    width = mbPtr->width;
! 	}
! 	if (mbPtr->height > 0) {
! 	    height = mbPtr->height;
! 	}
!     } else {
	 Tk_FreeTextLayout(mbPtr->textLayout);
	 mbPtr->textLayout = Tk_ComputeTextLayout(mbPtr->tkfont, mbPtr->text,
		 -1, mbPtr->wrapLength, mbPtr->justify, 0, &mbPtr->textWidth,
		 &mbPtr->textHeight);
! 	width = mbPtr->textWidth;
! 	height = mbPtr->textHeight;
! 	if (mbPtr->width > 0) {
! 	    width = mbPtr->width * Tk_TextWidth(mbPtr->tkfont, "0", 1);
! 	}
! 	if (mbPtr->height > 0) {
! 	    Tk_FontMetrics fm;
  
! 	    Tk_GetFontMetrics(mbPtr->tkfont, &fm);
! 	    height = mbPtr->height * fm.linespace;
	 }
! 	width += 2*mbPtr->padX;
! 	height += 2*mbPtr->padY;
      }
  
      if (mbPtr->indicatorOn) {
! 	mm = WidthMMOfScreen(Tk_Screen(mbPtr->tkwin));
! 	pixels = WidthOfScreen(Tk_Screen(mbPtr->tkwin));
! 	mbPtr->indicatorHeight= (INDICATOR_HEIGHT * pixels)/(10*mm);
! 	mbPtr->indicatorWidth = (INDICATOR_WIDTH * pixels)/(10*mm)
! 		+ 2*mbPtr->indicatorHeight;
! 	width += mbPtr->indicatorWidth;
      } else {
! 	mbPtr->indicatorHeight = 0;
! 	mbPtr->indicatorWidth = 0;
      }
  
      Tk_GeometryRequest(mbPtr->tkwin, (int) (width + 2*mbPtr->inset),
--- 334,446 ----
      TkMenuButton *mbPtr;	/* Widget record for menu button. */
  {
      int width, height, mm, pixels;
+     int  avgWidth, txtWidth, txtHeight;
+     int haveImage = 0, haveText = 0;
+     Tk_FontMetrics fm;
  
      mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth;
+ 
+     width = 0;
+     height = 0;
+     txtWidth = 0;
+     txtHeight = 0;
+     avgWidth = 0;
+ 
      if (mbPtr->image != None) {
	 Tk_SizeOfImage(mbPtr->image, &width, &height);
! 	haveImage = 1;
      } else if (mbPtr->bitmap != None) {
	 Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
! 	haveImage = 1;
!     }
! 
!     if (haveImage == 0 || mbPtr->compound != COMPOUND_NONE) {
	 Tk_FreeTextLayout(mbPtr->textLayout);
+ 
	 mbPtr->textLayout = Tk_ComputeTextLayout(mbPtr->tkfont, mbPtr->text,
		 -1, mbPtr->wrapLength, mbPtr->justify, 0, &mbPtr->textWidth,
		 &mbPtr->textHeight);
! 	txtWidth = mbPtr->textWidth;
! 	txtHeight = mbPtr->textHeight;
!         avgWidth = Tk_TextWidth(mbPtr->tkfont, "0", 1);
!         Tk_GetFontMetrics(mbPtr->tkfont, &fm);
!         haveText = (txtWidth != 0 && txtHeight != 0);
!     }
! 
!     /*
!      * If the menubutton is compound (ie, it shows both an image and text),
!      * the new geometry is a combination of the image and text geometry.
!      * We only honor the compound bit if the menubutton has both text and
!      * an image, because otherwise it is not really a compound menubutton.
!      */
  
!     if (mbPtr->compound != COMPOUND_NONE && haveImage && haveText) {
!         switch ((enum compound) mbPtr->compound) {
!             case COMPOUND_TOP:
!             case COMPOUND_BOTTOM: {
!                 /* Image is above or below text */
!                 height += txtHeight + mbPtr->padY;
!                 width = (width > txtWidth ? width : txtWidth);
!                 break;
!             }
!             case COMPOUND_LEFT:
!             case COMPOUND_RIGHT: {
!                 /* Image is left or right of text */
!                 width += txtWidth + mbPtr->padX;
!                 height = (height > txtHeight ? height : txtHeight);
!                 break;
!             }
!             case COMPOUND_CENTER: {
!                 /* Image and text are superimposed */
!                 width = (width > txtWidth ? width : txtWidth);
!                 height = (height > txtHeight ? height : txtHeight);
!                 break;
!             }
!             case COMPOUND_NONE: {break;}
!         }
!         if (mbPtr->width > 0) {
!             width = mbPtr->width;
!         }
!         if (mbPtr->height > 0) {
!             height = mbPtr->height;
!         }
!         width += 2*mbPtr->padX;
!         height += 2*mbPtr->padY;
!     } else {
! 	if (haveImage) {
!             if (mbPtr->width > 0) {
!                 width = mbPtr->width;
!             }
!             if (mbPtr->height > 0) {
!                 height = mbPtr->height;
!             }
! 	} else {
! 	    width = txtWidth;
! 	    height = txtHeight;
!             if (mbPtr->width > 0) {
!                 width = mbPtr->width * avgWidth;
!             }
!             if (mbPtr->height > 0) {
!                 height = mbPtr->height * fm.linespace;
!             }
	 }
!     }
! 
!     if (! haveImage) {
!         width += 2*mbPtr->padX;
!         height += 2*mbPtr->padY;
      }
  
      if (mbPtr->indicatorOn) {
!         mm = WidthMMOfScreen(Tk_Screen(mbPtr->tkwin));
!         pixels = WidthOfScreen(Tk_Screen(mbPtr->tkwin));
!         mbPtr->indicatorHeight= (INDICATOR_HEIGHT * pixels)/(10*mm);
!         mbPtr->indicatorWidth = (INDICATOR_WIDTH * pixels)/(10*mm)
!     	    + 2*mbPtr->indicatorHeight;
!         width += mbPtr->indicatorWidth;
      } else {
!         mbPtr->indicatorHeight = 0;
!         mbPtr->indicatorWidth = 0;
      }
  
      Tk_GeometryRequest(mbPtr->tkwin, (int) (width + 2*mbPtr->inset),
\end{verbatim}
\endgroup

\section{Colophon}

\textit{TIP AutoGenerator --- written by Donal K. Fellows}
\end{document}
