TIP 11: Tk Menubutton Enhancement: -compound option for menubutton

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Author:         Todd Helfter <tmh@purdue.edu>
State:          Final
Type:           Project
Tcl-Version:    8.4
Vote:           Done
Created:        16-Nov-2000
Post-History:

Abstract

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.

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.

Reference Implementation

This TIP proposes to change the internals of the menubutton.

The changes necessary to accomplish this are:

  1. Extend the structure TkMenuButton in generic/tkMenubutton.h with a new field of type int to hold the value of the compound setting.

  2. Add an enumeration of valid -compound options in generic/tkMenubutton.h.

  3. Modify generic/tkMenuButton.c and unix/tkUnixMenubu.c in such a way to process this new option. Note: The windows port of Tk uses the unix/tkUnixMenubu.c file. So this change is portable to both Unix and windows.

  4. Change tests/menubut.test so that the test for configure options checks for 33 instead of the current 32.

  5. Change doc/menubutton.n to show the new option under widget specific options.

Copyright

This document has been placed in the public domain.

Patch

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),
History