#!/usr/bin/perl # use strict; use warnings; # Use Tk GUI widgets and GridLayout.pm module for calculating widget # positions for the "grid" geometry manager. # use Tk; use GridLayout qw{ :ALL }; # Set up constants for widget creation and the "pack" geometry manager. # use constant { RIDGE => q{ridge}, FLAT => q{flat}, RAISED => q{raised}, Y => q{y}, LEFT => q{left}, RIGHT => q{right}, TOP => q{top}, BOTTOM => q{bottom}, }; # Set up some default GUI appearance options. # my $bgColour = q{LightSteelBlue3}; my %commonFrameOpts = ( -background => $bgColour, -relief => RIDGE, -borderwidth => 2, ); my %commonLabelOpts = ( -foreground => q{NavyBlue}, -background => q{LemonChiffon}, -relief => FLAT, -borderwidth => 2, -padx => 5, -pady => 5, ); my %commonButtonOpts = ( -background => q{grey35}, -foreground => q{yellow2}, -activebackground => q{grey45}, -activeforeground => q{yellow}, -disabledforeground => q{grey55}, ); my %commonRadioButtonOpts = ( -width => 6, -selectcolor => q{red}, -relief => RAISED, -borderwidth => 2, -padx => 5, -pady => 5, ); # Create non-resizeable main window and set title. # my $mainWin = MainWindow->new( -background => $bgColour, ); $mainWin->resizable( 0, 0 ); $mainWin->title( q{Fit columns and rows} ); # Get screen height in pixels and set the font size to suit the # resolution. # my $screenHeight = $mainWin->screenheight(); my $fontSize = 8; $fontSize = 10 if $screenHeight >= 1024; $fontSize = 12 if $screenHeight >= 1536; $mainWin->optionAdd( q{*font} => qq{courier $fontSize} ); # Default to arranging buttons by columns. Create a frame for the label # and the buttons for choosing rows or columns. # my $rowsOrColumns = 1; my $rowColFrame = $mainWin->Frame( %commonFrameOpts, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create label and associated text variable. # my $rsRowColText = \ do { my $dummy }; $rowColFrame->Label( %commonLabelOpts, -textvariable => $rsRowColText, )->pack( -side => TOP, ); # Create a flat-relief frame within the "rows/columns" frame for the # buttons making the choice. # my $chooseRowColFrame = $rowColFrame->Frame( %commonFrameOpts, -relief => FLAT, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create the radiobuttons with the &arrangeButtons callback. # $chooseRowColFrame->Radiobutton( %commonRadioButtonOpts, -text => q{Rows}, -width => 7, -value => 0, -variable => \ $rowsOrColumns, -command => \ &arrangeButtons, )->pack( -side => LEFT, -padx => 5, -pady => 5, ); $chooseRowColFrame->Radiobutton( %commonRadioButtonOpts, -text => q{Columns}, -width => 7, -value => 1, -variable => \ $rowsOrColumns, -command => \ &arrangeButtons, )->pack( -side => LEFT, -padx => 5, -pady => 5, ); # Default to sorting buttons horizontally. Create a frame for the label # and the buttons for choosing sort direction. # my $sortDirection = 0; my $directionFrame = $mainWin->Frame( %commonFrameOpts, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create label and associated text variable. # my $rsDirectionText = \ do { my $dummy }; $directionFrame->Label( %commonLabelOpts, -textvariable => $rsDirectionText, )->pack( -side => TOP, ); # Create a flat-relief frame within the "horizontal/vertical" frame for # the buttons making the choice. # my $chooseDirectionFrame = $directionFrame->Frame( %commonFrameOpts, -relief => FLAT, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create the radiobuttons with the &arrangeButtons callback. # $chooseDirectionFrame->Radiobutton( %commonRadioButtonOpts, -text => q{Horizontal}, -width => 10, -value => 0, -variable => \ $sortDirection, -command => \ &arrangeButtons, )->pack( -side => LEFT, -padx => 5, -pady => 5, ); $chooseDirectionFrame->Radiobutton( %commonRadioButtonOpts, -text => q{Vertical}, -width => 10, -value => 1, -variable => \ $sortDirection, -command => \ &arrangeButtons, )->pack( -side => LEFT, -padx => 5, -pady => 5, ); # Default to arranging 9 buttons, prepare a frame to hold the buttons # that choose how many items to arrange. # my $numItems = 9; my $itemCountFrame = $mainWin->Frame( %commonFrameOpts, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create label and associated text variable. # my $rsItemsText = \ do { my $dummy }; $itemCountFrame->Label( %commonLabelOpts, -textvariable => $rsItemsText, )->pack( -side => TOP, ); # Create a flat-relief frame within the "item count" frame for # the buttons making the choice. # my $chooseItemsFrame = $itemCountFrame->Frame( %commonFrameOpts, -relief => FLAT, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Allow a choice of from 2 to 31 items to display, selected by # radiobuttons arranged in a grid. For this straighforward rectangular # layout of 5 x 6 radiobuttons some simple arithmetic suffices to # calculate the grid positions, no need for the GridLayout module. # for ( 2 .. 31 ) { $chooseItemsFrame->Radiobutton( %commonRadioButtonOpts, -text => $_, -value => $_, -variable => \ $numItems, -width => 3, -command => \ &arrangeButtons, )->grid( -row => ( ( $_ - 2 ) / 5 ), -column => ( ( $_ - 2 ) % 5 ), -padx => 5, -pady => 5, ); } # Default to arranging in 4 columns, create frame for choosing how # many rows or columns to arrange. # my $numRowsOrCols = 4; my $rowColCountFrame = $mainWin->Frame( %commonFrameOpts, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create label and associated text variable. # my $rsRowColCountText = \ do { my $dummy }; $rowColCountFrame->Label( %commonLabelOpts, -textvariable => $rsRowColCountText, )->pack( -side => TOP, ); # Create a flat-relief frame within the "rows/columns count" frame for # the buttons making the choice. # my $chooseColumnsFrame = $rowColCountFrame->Frame( %commonFrameOpts, -relief => FLAT, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Create the radiobuttons, 2 through 7, with the &arrangeButtons callback. # for (2 .. 7) { $chooseColumnsFrame->Radiobutton( %commonRadioButtonOpts, -text => $_, -value => $_, -width => 3, -variable => \ $numRowsOrCols, -command => \ &arrangeButtons, )->pack( -side => LEFT, -padx => 5, -pady => 5, ); } # The items being arranged according to the choices made are simple buttons # here and we keep track of them in an anonymous hash updated by the # &arrangeButtons() subroutine. They will be displayed in the $buttonFrame # frame which we create now. # my $rhButtons = {}; my $buttonFrame = $mainWin->Frame( %commonFrameOpts, )->pack( -side => TOP, -fill => Y, -expand => 1, ); # Finally create a control button frame to hold the "Quit" button. # my $controlButtonFrame = $mainWin->Frame( %commonFrameOpts, -relief => FLAT, )->pack( -side => TOP, ); $controlButtonFrame->Button( %commonButtonOpts, -text => q{Quit}, -command => sub { $mainWin->destroy(); }, )->pack( -side => RIGHT, -padx => 5, -pady => 5, ); # Call arrangeButtons() to display the default button arrangement then # enter the main loop and await events. # arrangeButtons(); MainLoop(); # Subroutine called to arrange (before MainLoop()) or re-arrange (as # a widget callback) buttons depending on choices made on row or # column constraint, row or column count and sorting direction. # # -------------- sub arrangeButtons # -------------- { # Destroy any existing buttons and clear the buttons hash. # foreach my $button ( keys %{ $rhButtons } ) { $rhButtons->{ $button }->destroy() if Exists( $rhButtons->{ $button } ); delete $rhButtons->{ $button }; } # Call routines to set label text for each of the four categories # that the user might change. # setRowOrColumnText(); setDirectionText(); setItemsText(); setRowColCountText(); # Get the button order as an anonymous AoA by calling the appropriate # subroutine depending on whether we are constrained by the number of # rows or of columns and whether buttons are sorted vertically or # horizontally. # my $raButtonOrder = $rowsOrColumns ? ( $sortDirection ? fitToColsVSort( $numItems, $numRowsOrCols ) : fitToColsHSort( $numItems, $numRowsOrCols ) ) : ( $sortDirection ? fitToRowsVSort( $numItems, $numRowsOrCols ) : fitToRowsHSort( $numItems, $numRowsOrCols ) ); # Create the chosen number of buttons, keeping track of them in # the $rhButtons anonymous hash. Place them using the calculated # grid positions. Numbering the buttons from zero makes things # more obvious when looking at the algorithms in the GridLayout # module. The buttons are dumb and do nothing when clicked. # foreach my $buttonNo ( 0 .. $numItems - 1 ) { my $buttonName = q{Button_} . $buttonNo; $rhButtons->{ $buttonName } = $buttonFrame->Button( %commonButtonOpts, -text => qq{Button $buttonNo}, -width => 9, )->grid( -row => $raButtonOrder->[ $buttonNo ]->[ 0 ], -column => $raButtonOrder->[ $buttonNo ]->[ 1 ], -padx => 5, -pady => 5, ); } } # Subroutine to update the sorting direction text label. # # ---------------- sub setDirectionText # ---------------- { ${ $rsDirectionText } = q{... sorting } . ( qw{ horizontally vertically } )[ $sortDirection ] . q{ ...}; } # Subroutine to update the number of items text label. # # ------------ sub setItemsText # ------------ { ${ $rsItemsText } = qq{... fit $numItems items ...}; } # Subroutine to update the number of rows or columns text label. # # ------------------ sub setRowColCountText # ------------------ { ${ $rsRowColCountText } = qq{... to $numRowsOrCols } . ( qw{ rows columns } )[ $rowsOrColumns ]; } # Subroutine to update the arrange by rows or columns text label. # # ------------------ sub setRowOrColumnText # ------------------ { ${ $rsRowColText } = q{By } . ( qw{ Rows Columns } )[ $rowsOrColumns ] . q{ ...}; }