Calculate minimum gadget sizes (all OS)
Posted: Thu Jul 30, 2009 2:35 pm
				
				When developing crossplatform programs, one problem is the difference in font sizes among the OS. But even when developing only for Windows, it is a good practice to not assume a fixed font size as that will look bad for people who use a larger default font as well.
The following procedure is used by the PureBasic IDE to adjust gadget sizes to fit the text they contain. This allows the IDE dialogs to look good with any font size and also allows translated text to fit even if it is longer than the default text.
Of course with this you need a more complex code to arrange/resize the gadgets, but as you can see below when this is done you can even change the font (or gadget content) on the fly and easily resize the gadgets to fit.
Code example:
			The following procedure is used by the PureBasic IDE to adjust gadget sizes to fit the text they contain. This allows the IDE dialogs to look good with any font size and also allows translated text to fit even if it is longer than the default text.
Of course with this you need a more complex code to arrange/resize the gadgets, but as you can see below when this is done you can even change the font (or gadget content) on the fly and easily resize the gadgets to fit.
Code: Select all
; 
;  Calculates the size required to display a Gadget properly.
;
;  Supported Gadgets:
;    Button, Checkbox, Option, Text, String, ComboBox, Image 
;
;  Note:
;    For Gadgets with variable content (String, ComboBox), only the returned height
;    is useful, as the width will only be an absolute minimum value.
;
;  The 'Flags' parameter gives gadget flags to include in the calculation.
;  Currently only #PB_Text_Border makes a difference there.   
;
CompilerIf Defined(Max, #PB_Procedure) = 0
  Procedure Max(a, b)
    If a > b
      ProcedureReturn a
    Else
      ProcedureReturn b
    EndIf
  EndProcedure
CompilerEndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  Structure PB_Gadget
    *Gadget.GtkWidget
    *Container.GtkWidget
    *VT
    UserData.i
    GadgetData.i[4]
  EndStructure
CompilerEndIf
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  Structure OSX_Rect
    top.w
    left.w
    bottom.w
    right.w
  EndStructure
  
  #noErr = 0
CompilerEndIf
; Stores the result in *Width\l and *Height\l
;
Procedure GetRequiredSize(Gadget, *Width.LONG, *Height.LONG, Flags = 0)
  CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
  
    DC = GetDC_(GadgetID(Gadget))
    oldFont = SelectObject_(DC, GetGadgetFont(Gadget)) 
    Size.SIZE
  
    Select GadgetType(Gadget)
    
      Case #PB_GadgetType_Text
        Text$ = RemoveString(GetGadgetText(Gadget), Chr(10))
        count = CountString(Text$, Chr(13)) + 1
        empty = 0
        maxheight = 0 
        For index = 1 To count 
          Line$ = StringField(Text$, index, Chr(13))
          If Line$ = ""
            empty + 1
          Else 
            GetTextExtentPoint32_(DC, @Line$, Len(Line$), @LineSize.SIZE)
            Size\cx = Max(Size\cx, LineSize\cx)
            Size\cy + LineSize\cy
            maxheight = Max(maxheight, LineSize\cy)
          EndIf
        Next index            
        Size\cy + empty * maxheight  
        
        If Flags & #PB_Text_Border
          Size\cx + GetSystemMetrics_(#SM_CXEDGE) * 2
          Size\cy + GetSystemMetrics_(#SM_CYEDGE) * 2
        Else           
          Size\cx + 2
          Size\cy + 2
        EndIf
      Case #PB_GadgetType_CheckBox, #PB_GadgetType_Option
        Text$ = GetGadgetText(Gadget)
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx + 20
        Size\cy = Max(Size\cy+2, 20)
        
      Case #PB_GadgetType_Button
        Text$ = GetGadgetText(Gadget)
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx + GetSystemMetrics_(#SM_CXEDGE)*2
        Size\cy = Max(Size\cy+GetSystemMetrics_(#SM_CYEDGE)*2, 24)
        Size\cx + 10
        
      Case #PB_GadgetType_String
        Text$ = GetGadgetText(Gadget) + "Hg" 
        GetTextExtentPoint32_(DC, @Text$, Len(Text$), @Size.SIZE)
        Size\cx = GetSystemMetrics_(#SM_CXEDGE)*2 
        Size\cy = Max(Size\cy+GetSystemMetrics_(#SM_CXEDGE)*2, 20)
        
      Case #PB_GadgetType_ComboBox
        GetTextExtentPoint32_(DC, @"Hg", 2, @Size.SIZE)
        Size\cy = Max(Size\cy + 8, 21)
        Size\cx = Size\cy  
        
      Case #PB_GadgetType_Image
        Size\cx = GadgetWidth(Gadget)
        Size\cy = GadgetHeight(Gadget)
        
    EndSelect
    
    SelectObject_(DC, oldFont)
    ReleaseDC_(GadgetID(Gadget), DC)
    *Width\l  = Size\cx
    *Height\l = Size\cy
  
  CompilerCase #PB_OS_Linux
  
    *Gadget.PB_Gadget = IsGadget(Gadget)
    
    If *Gadget And *Gadget\Container And GadgetType(Gadget) <> #PB_GadgetType_ComboBox
      gtk_widget_size_request_(*Gadget\Container, @RealSize.GtkRequisition)
      gtk_widget_set_size_request_(*Gadget\Container, -1, -1)
      gtk_widget_size_request_(*Gadget\Container, @Size.GtkRequisition)      
      gtk_widget_set_size_request_(*Gadget\Container, RealSize\Width, RealSize\Height) 
    Else
      gtk_widget_size_request_(GadgetID(Gadget), @RealSize.GtkRequisition)
      gtk_widget_set_size_request_(GadgetID(Gadget), -1, -1)              
      gtk_widget_size_request_(GadgetID(Gadget), @Size.GtkRequisition)    
      gtk_widget_set_size_request_(GadgetID(Gadget), RealSize\Width, RealSize\Height)
    EndIf
    
    If GadgetType(Gadget) = #PB_GadgetType_ComboBox Or GadgetType(Gadget) = #PB_GadgetType_String
      *Width\l  = 20 
    Else
      *Width\l  = Size\Width
    EndIf
        
    *Height\l = Size\Height    
  CompilerCase #PB_OS_MacOS
  
    Type = GadgetType(Gadget)
    
    If Type = #PB_GadgetType_Image    
      *Width\l = GadgetWidth(Gadget)
      *Height\l = GadgetHeight(Gadget)
      
    ElseIf Type = #PB_GadgetType_Text
      realwidth   = GadgetWidth(Gadget)
      
      *Width\l = 40
      *Height\l = 20      
      
      ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, 1000, #PB_Ignore) 
      
      If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) = #noErr
        Height = Rect\bottom - Rect\top
        If Height > 0
        
          Min = 0
          Max = 1000
          
          While Max > Min+2
            Mid = (Min + Max) / 2
            
            ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, Mid, #PB_Ignore) 
            
            If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) <> #noErr
              ProcedureReturn 
            EndIf
            
            If Rect\bottom - Rect\top > Height
              Min = Mid
            Else
              Max = Mid
            EndIf
          Wend
          
          *Width\l = Rect\right - Rect\left + 2 
          *Height\l = Max(Height, 20) 
        EndIf
          
      EndIf 
      
      ResizeGadget(Gadget, #PB_Ignore, #PB_Ignore, realwidth, #PB_Ignore)
    Else 
      If GetBestControlRect_(GadgetID(Gadget), @Rect.OSX_Rect, @BaseLine.w) = #noErr
        *Width\l = Rect\right - Rect\left
        *Height\l = Max(Rect\bottom - Rect\top, 20) 
        
        If Type = #PB_GadgetType_Button Or Type = #PB_GadgetType_String
          *Height\l = Max(*Height\l, 24)        
        EndIf        
      Else 
        *Width\l = 40
        *Height\l = 20
      EndIf
      
      If Type = #PB_GadgetType_String Or Type  = #PB_GadgetType_ComboBox        
        *Width\l = 30 
      EndIf
    
    EndIf
  
  CompilerEndSelect
  
EndProcedure
; convinience wrappers if only one size is needed
;
Procedure GetRequiredWidth(Gadget, Flags = 0)
  Protected Width.l, Height.l
  GetRequiredSize(Gadget, @Width, @Height, Flags)
  ProcedureReturn Width
EndProcedure 
Procedure GetRequiredHeight(Gadget, Flags = 0)
  Protected Width.l, Height.l
  GetRequiredSize(Gadget, @Width, @Height, Flags)
  ProcedureReturn Height
EndProcedure Code: Select all
Enumeration
  #Button_1
  #Button_2
  #Button_3
  #Checkbox_1
  #Checkbox_2
  #Checkbox_3  
  #String
  #ComboBox
  #ChangeFont
EndEnumeration
Procedure Resize()
  GetRequiredSize(#Button_1, @Button1Width.l, @Button1Height.l)
  GetRequiredSize(#Button_2, @Button2Width.l, @Button2Height.l)
  GetRequiredSize(#Button_3, @Button3Width.l, @Button3Height.l)
  
  Height = Max(Button1Height, Max(Button2Height, Button3Height))
  x = 10
  y = 10
  ResizeGadget(#Button_1, x, y, Button1Width, Height): x + Button1Width + 5
  ResizeGadget(#Button_2, x, y, Button2Width, Height): x + Button2Width + 5
  ResizeGadget(#Button_3, x, y, Button3Width, Height): x + Button3Width + 5
  
  y + Height + 10  
  WindowWidth = x
  
  GetRequiredSize(#Checkbox_1, @Check1Width.l, @Check1Height.l)
  GetRequiredSize(#Checkbox_2, @Check2Width.l, @Check2Height.l)
  GetRequiredSize(#Checkbox_3, @Check3Width.l, @Check3Height.l)
  
  Width = Max(Check1Width, Max(Check2Width, Check3Width))
  ResizeGadget(#Checkbox_1, 10, y, Width, Check1Height): y + Check1Height + 5
  ResizeGadget(#Checkbox_2, 10, y, Width, Check2Height): y + Check2Height + 5
  ResizeGadget(#Checkbox_3, 10, y, Width, Check3Height): y + Check3Height + 5
  
  y + 5
  WindowWidth = Max(WindowWidth, Width+20)
  
  Height = GetRequiredHeight(#String)
  ResizeGadget(#String, 10, y, WindowWidth-20, Height)
  y + Height + 5
  Height = GetRequiredHeight(#ComboBox)
  ResizeGadget(#ComboBox, 10, y, WindowWidth-20, Height)
  y + Height + 10
  
  GetRequiredSize(#ChangeFont, @ButtonWidth.l, @ButtonHeight.l)
  ResizeGadget(#ChangeFont, (WindowWidth-ButtonWidth)/2, y, ButtonWidth, ButtonHeight)
  y + ButtonHeight + 10
  
  ResizeWindow(0, #PB_Ignore, #PB_Ignore, WindowWidth, y)
EndProcedure
If OpenWindow(0, 100, 100, 0, 0, "Size test", #PB_Window_SystemMenu|#PB_Window_Invisible)
  ButtonGadget(#Button_1, 0, 0, 0, 0, "Short")
  ButtonGadget(#Button_2, 0, 0, 0, 0, "-- Longer --")
  ButtonGadget(#Button_3, 0, 0, 0, 0, "------- Longest ------")
  CheckBoxGadget(#Checkbox_1, 0, 0, 0, 0, "Hello world")
  CheckBoxGadget(#Checkbox_2, 0, 0, 0, 0, "The quick brown fox jumped over the lazy dog.")
  CheckBoxGadget(#Checkbox_3, 0, 0, 0, 0, "Whatever")
  StringGadget(#String, 0, 0, 0, 0, "String")
  ComboBoxGadget(#ComboBox, 0, 0, 0, 0)
  AddGadgetItem(#ComboBox, 0, "ComboBox")
  SetGadgetState(#ComboBox, 0)
  ButtonGadget(#ChangeFont, 0, 0, 0, 0, "Change font")
  Resize()
  HideWindow(0, 0)
  
  Repeat
    Event = WaitWindowEvent()
    
    If Event = #PB_Event_Gadget And EventGadget() = #ChangeFont
      If FontRequester("Arial", 20, 0)
        If LoadFont(0, SelectedFontName(), SelectedFontSize(), SelectedFontStyle())
          For Gadget = #Button_1 To #ChangeFont
            SetGadgetFont(Gadget, FontID(0))
          Next Gadget
          Resize()
        EndIf
      EndIf
    EndIf
  Until Event = #PB_Event_CloseWindow
EndIf