The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
# Copyright 2010, 2011, 2012, 2013 Kevin Ryde

# This file is part of Math-Image.
#
# Math-Image is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# Math-Image is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with Math-Image.  If not, see <http://www.gnu.org/licenses/>.


package App::MathImage::Prima::Main;
use 5.004;
use strict;
use warnings;
use FindBin;
use List::Util 'max';
use Locale::TextDomain 1.19 ('App-MathImage');

use Prima;
use Prima::Application name => __('Math-Image');
use Prima::Buttons;
use Prima::ComboBox;
use Prima::Label;
use Prima::Sliders; # SpinEdit
use App::MathImage::Prima::Drawing;
use App::MathImage::Generator;

# uncomment this to run the ### lines
# use Smart::Comments;


use vars '$VERSION', '@ISA';
$VERSION = 110;
@ISA = ('Prima::MainWindow');

sub profile_default {
  my ($class) = @_;
  ### Prima-Drawing profile_default() ...
  return { %{$class->SUPER::profile_default},
           name        => __('Math-Image'),
           transparent => 1, # no clear background

           # color            => cl::White(),
           # backColor        => cl::Black(),

           menuItems =>
           [ [ "~File" =>
               [
                #       <menuitem action='SaveAs'/>
                #       <menuitem action='SetRoot'/>
                [ __('~Print') => 'print_image' ],
                [ __('E~xit')  => 'destroy' ],
               ] ],
             [ ef => "~View" =>
               [ [ ef => "~Path"   => [ _menu_for_path() ]],
                 [ ef => "~Values" => [ _menu_for_values() ]],
                 [ 'centre', __('~Centre'), 'draw_centre' ],
                 [ 'fullscreen', __('~Fullscreen'), 'toggle_fullscreen' ],
               ]],
             # [],  # separator to put Help at the right
             [ "~Help" =>
               [ [ __('~About'), 'help_about' ],
                 [ __('~Program POD'), 'help_program' ],
                 [ __('Pa~th POD'), 'help_path' ],
                 [ __('~Values POD'), 'help_values' ],
                 (defined (Module::Util::find_installed('Browser::Open'))
                  ? [ 'help-oeis', __('~OEIS Web Page'), 'help_oeis' ]
                  : ()),
               ],
             ],
           ],
         };
}

# together with transparent=>1 for no clear background underneath draw widget
sub on_paint {
}


sub init {
  my ($self, %profile) = @_;
  ### Prima Main init() ...

  my $visible = (exists $profile{'visible'} ? $profile{'visible'} : 1);
  $profile{'visible'} = 0;
  %profile = $self-> SUPER::init(%profile);

  my $gui_options = delete $profile{'gui_options'};
  my $gen_options = delete $profile{'gen_options'} || {};
  {
    my %default_gen_options = %{App::MathImage::Generator->default_options};
    delete @default_gen_options{'width','height'}; # hash slice
    $gen_options = { %default_gen_options,
                     %$gen_options };
  }
  ### $gen_options

  my $toolbar = $self->{'toolbar'}
    = $self->insert ('Widget',
                     pack => { in => $self,
                               side => 'top',
                               fill => 'x',
                               expand => 0,
                               anchor => 'n',
                             },
                    );

  $toolbar->insert ('Button',
                    text => __('Randomize'),
                    pack => { side => 'left' },
                    hint  => __('Choose a random path, values, scale, etc.
Click repeatedly to see interesting things.'),
                    onClick  => sub {
                      my ($button) = @_;
                      $self->{'draw'}->gen_options (App::MathImage::Generator->random_options);
                      _update($self);
                    },
                   );

  my $combobox_height = do {
    my $combo = Prima::ComboBox->create;
    my $height = $combo->editHeight;
    $combo->destroy;
    $height
  };

  my $path_combo = $self->{'path_combo'}
    = $toolbar->insert ('ComboBox',
                        pack   => { side => 'left',
                                    fill => 'none',
                                    expand => 0 },
                        hint  => __('The path for where to place values in the plane.'),
                        style => cs::DropDown,
                        # override dodgy height when style set
                        height => $combobox_height,
                        items => [ map { $_ } App::MathImage::Generator->path_choices ],
                        onChange  => sub {
                          my ($combo) = @_;
                          ### Main path_combo onChange
                          my $path = $combo->text;
                          $self->{'draw'}->gen_options (path => $path);
                          _update ($self);
                        },
                       );

  $self->{'values_combo'}
    = $toolbar->insert ('ComboBox',
                        pack  => { side => 'left',
                                   fill => 'none',
                                   expand => 0 },
                        style  => cs::DropDown,
                        # override dodgy height when style set
                        height => $combobox_height,
                        hint   => __('The values to display.'),
                        items => [ map { $_ } App::MathImage::Generator->values_choices ],
                        onChange  => sub {
                          my ($combo) = @_;
                          ### Main values combo onChange
                          my $values = $combo->text;
                          $self->{'draw'}->gen_options (values => $values);
                          _update ($self);
                        },
                       );

  # {
  #   my $max = 0;
  #   foreach (0 .. $self->{'values_combo'}->{list}->count() - 1) {
  #     ### wid: $self->{'values_combo'}->{list}->get_item_width($_)
  #     $max = max ($max, $self->{'values_combo'}->{list}->get_item_width($_));
  #   }
  #   ### $max
  #   $self->{'values_combo'}->width($max+10);
  # }

  {
    $toolbar->insert ('Label',
                      text => __('Scale'),
                      pack => { side => 'left' },);
    $self->{'scale_spin'}
      = $toolbar->insert ('SpinEdit',
                          pack => { side => 'left' },
                          min => 1,
                          step => 1,
                          pageStep => 10,
                          onChange  => sub {
                            my ($spin) = @_;
                            ### Prima-Main scale onChange: $spin->value
                            my $scale = $spin->value;
                            $self->{'draw'}->gen_options (scale => $scale);
                          },
                         );
  }

  $self->{'draw'} = $self->insert
    ('App::MathImage::Prima::Drawing',
     name   => 'Drawing',
     width  => (defined $gen_options->{'width'} ? $gen_options->{'width'} :-1),
     height => (defined $gen_options->{'height'}? $gen_options->{'height'}:-1),
     pack   => { expand => 1, fill => 'both' },
     delegations  => ['MouseMove'],
     gen_options  => $gen_options,
    );

  my $statusbar = $self->{'statusbar'} = $self->insert
    ('Label',
     text => '',
     pack => { in => $self,
               side => 'top',
               fill => 'x',
               expand => 0,
               anchor => 'n',
             });

  _update ($self);

  {
    my @size = $self->size;
    ### @size
    my ($screen_width, $screen_height) = $::application->size;
    if (! defined $gen_options->{'width'}) {
      ### $screen_width
      $size[0] = $screen_width * .8;
    }
    if (! defined $gen_options->{'height'}) {
      ### $screen_height
      $size[1] = $screen_height * .8;
    }
    $self->set (size => \@size);
    ### @size
  }

  # don't resize toplevel window for changes in children
  $self->packPropagate(0);

  $self->visible ($visible);
  return $self;
}

sub _update {
  my ($self) = @_;
  ### Prima-Main _update() ...

  my $gen_options = $self->{'draw'}->gen_options;

  my $menu = $self->menu;
  foreach my $path (App::MathImage::Generator->path_choices) {
    $menu->uncheck("path-$path");
  }
  $menu->check("path-$gen_options->{'path'}");

  foreach my $values (App::MathImage::Generator->values_choices) {
    $menu->uncheck("values-$values");
  }
  $menu->check("values-$gen_options->{'values'}");

  my $path = $gen_options->{'path'};
  if ($self->{'path_combo'}->text ne $path) {
    ### path_combo set text() ...
    $self->{'path_combo'}->text ($path);
  }

  if ($path eq 'SquareSpiral') {
    $self->{'path_wider_spin'}
      ||= $self->{'toolbar'}->insert ('SpinEdit',
                                      pack => { side => 'left',
                                                after => $self->{'path_combo'},
                                              },
                                      min => 0,
                                      step => 1,
                                      pageStep => 1,
                                      hint => __('Wider path.'),
                                      onChange  => sub {
                                        my ($spin) = @_;
                                        ### Main wider onChange ...
                                        my $wider = $spin->value;
                                        $self->{'draw'}->path_parameters (wider => $wider);
                                      },
                                     );
    $self->{'path_wider_spin'}->value ($self->{'draw'}->path_parameters->{'wider'});
  } else {
    if (my $spin = delete $self->{'path_wider_spin'}) {
      ### path_wider_spin destroy ...
      $spin->destroy;
    }
  }

  my $values = $gen_options->{'values'};
  if ($self->{'values_combo'}->text ne $values) {
    ### values_combo set text() ...
    $self->{'values_combo'}->text ($values);
  }

  {
    my $url = _oeis_url($self);
    ### $url
    $menu->enabled('help-oeis', defined($url));
  }

  $self->{'scale_spin'}->value ($gen_options->{'scale'});
  ### Prima-Main _update() done ...
}


my %_values_to_mnemonic =
  (primes          => __('_Primes'),
   TwinPrimes      => __('_Twin Primes'),
   Squares         => __('S_quares'),
   Pronic          => __('Pro_nic'),
   triangular      => __('Trian_gular'),
   cubes           => __('_Cubes'),
   Tetrahedral     => __('_Tetrahedral'),
   Perrin          => __('Perr_in'),
   Padovan         => __('Pado_van'),
   Fibonacci       => __('_Fibonacci'),
   FractionDigits  => __('F_raction Digits'),
   Polygonal       => __('Pol_ygonal Numbers'),
   PiBits          => __('_Pi Bits'),
   Ln2Bits         => __x('_Log Natural {logarg} Bits', logarg => 2),
   Ln3Bits         => __x('_Log Natural {logarg} Bits', logarg => 3),
   Ln10Bits        => __x('_Log Natural {logarg} Bits', logarg => 10),
   odd             => __('_Odd Integers'),
   even            => __('_Even Integers'),
   all             => __('_All Integers'),
  );
sub _values_to_mnemonic {
  my ($str) = @_;
  $str = ($_values_to_mnemonic{$str} || nick_to_display($str));
  $str =~ tr/_/~/;
  return $str;
}
sub _menu_for_values {
  my ($self) = @_;

  return map {
    my $values = $_;
    [ "*values-$_",
      _values_to_mnemonic($_),
      \&_values_menu_action,
    ]
  } App::MathImage::Generator->values_choices;
}
sub _values_menu_action {
  my ($self, $itemname) = @_;
  ### Values menu item name: $itemname
  $itemname =~ s/^values-//;
  $self->{'draw'}->gen_options (values => $itemname);
  _update($self);
}

my %_path_to_mnemonic =
  (SquareSpiral    => __('_Square Spiral'),
   SacksSpiral     => __('_Sacks Spiral'),
   VogelFloret     => __('_Vogel Floret'),
   DiamondSpiral   => __('_Diamond Spiral'),
   PyramidRows     => __('_Pyramid Rows'),
   PyramidSides    => __('_Pyramid Sides'),
   HexSpiral       => __('_Hex Spiral'),
   HexSpiralSkewed => __('_Hex Spiral Skewed'),
   KnightSpiral    => __('_Knight Spiral'),
   Corner          => __('_Corner'),
   Diagonals       => __('_Diagonals'),
   Rows            => __('_Rows'),
   Columns         => __('_Columns'),
  );
sub _path_to_mnemonic {
  my ($str) = @_;
  return ($_values_to_mnemonic{$str} || nick_to_display($str));
}
sub _menu_for_path {
  my ($self) = @_;

  return map {
    my $path = $_;
    [ "*path-$_", _path_to_mnemonic($_), \&_path_menu_action,
    ]
  } App::MathImage::Generator->path_choices;
}
sub _path_menu_action {
  my ($self, $itemname) = @_;
  ### _path_menu_action(): "@_"
  $itemname =~ s/^path-//;
  $self->{'draw'}->gen_options (path => $itemname);
  _update($self);
}

sub nick_to_display {
  my ($nick) = @_;
  return join (' ',
               map {ucfirst}
               split(/[-_ ]+
                    |(?<=\D)(?=\d)
                    |(?<=\d)(?=\D)
                    |(?<=[[:lower:]])(?=[[:upper:]])
                     /x,
                     $nick));
}



#------------------------------------------------------------------------------
# fullscreen

sub toggle_fullscreen {
  my ($self, $itemname) = @_;
  ### toggle_fullscreen(), current: $self->menu->checked($itemname)
  $self->windowState ($self->menu->checked($itemname)
                      ? ws::Normal() : ws::Maximized()); # opposite
}
sub on_windowstate {
  my ($self, $windowstate) = @_;
  ### on_windowstate(): $windowstate
  $self->menu->checked('fullscreen', $windowstate == ws::Maximized());
}

#------------------------------------------------------------------------------
sub help_about {
  my ($self) = @_;
  require App::MathImage::Prima::About;
  App::MathImage::Prima::About->popup;
}
sub help_program {
  my ($self) = @_;
  $::application->open_help (File::Spec->catfile ($FindBin::Bin,
                                                  $FindBin::Script));
}
sub help_path {
  my ($self) = @_;
  my $path = $self->{'draw'}->gen_options->{'path'};
  if (my $module = App::MathImage::Generator->path_choice_to_class ($path)) {
    $::application->open_help ($module);
  }
}
sub help_values {
  my ($self) = @_;
  my $values = $self->{'draw'}->gen_options->{'values'};
  if (my $module = App::MathImage::Generator->values_choice_to_class($values)){
    $::application->open_help ($module);
  }
}
sub help_oeis {
  my ($self) = @_;
  if (my $url = _oeis_url($self)) {
    require Browser::Open;
    Browser::Open::open_browser ($url);
  }
}
sub _oeis_url {
  my ($self) = @_;
  my ($values_seq, $anum);
  return (($values_seq = $self->{'draw'}->gen_object->values_seq)
          && ($anum = $values_seq->oeis_anum)
          && "http://oeis.org/$anum");
}

sub draw_centre {
  my ($self) = @_;
  my $draw = $self->{'draw'};
  my $gen_options = $draw->gen_options;
  if ($gen_options->{'x_offset'} || $gen_options->{'y_offset'}) {
    $draw->gen_options->{'x_offset'} = 0;
    $draw->gen_options->{'y_offset'} = 0;
    $draw->redraw;
  }
}

sub Drawing_MouseMove {
  my ($self, $modifiers, $x, $y) = @_;
  ### Draw_Mousemove() ...
  my $draw = $self->{'draw'};
  # Generator based on 0 at top, so reverse $y
  my $message = $draw->gen_object->xy_message ($x,
                                               $draw->height-1 - $y);
  ### $message
  my $statusbar = $self->{'statusbar'};
  $statusbar->text ($message);
}

# sub save_as {
#   my ($action, $self) = @_;
#   require App::MathImage::Prima::SaveDialog;
#   my $dialog = ($self->{'save_dialog'}
#                 ||= App::MathImage::Prima::SaveDialog->new
#                 (draw => $self->{'draw'},
#                  transient_for => $self));
#   $dialog->present;
# }
# sub setroot {
#   my ($action, $self) = @_;
#   Prima::Ex::WidgetCursor->busy;
# 
#   my $draw = $self->{'draw'};
#   my $rootwin = Prima::Gdk->get_default_root_window;
#   my ($width, $height) = $rootwin->get_size;
# 
#   require App::MathImage::Generator;
#   my $gen = App::MathImage::Generator->new
#     (values     => $draw->get('values'),
#      path       => $draw->get('path'),
#      scale      => $draw->get('scale'),
#      fraction   => $draw->get('fraction'),
#      width      => $width,
#      height     => $height,
#      foreground => $draw->style->fg($self->state),
#      background => $draw->style->bg($self->state));
# 
#   require Image::Base::Prima::Drawable;
#   my $image = Image::Base::Prima::Drawable->new
#     (-for_window => $rootwin,
#      -width      => $width,
#      -height     => $height);
#   $gen->draw_Image ($image);
# 
#   $rootwin->set_back_pixmap ($image->get('-pixmap'));
#   $rootwin->clear;
# }


#------------------------------------------------------------------------------
# printer

sub print_image {
  my ($self) = @_;
  require Prima::PrintDialog;
  my $dialog = Prima::PrintSetupDialog->create;
  if ($dialog->execute) {
    _draw_to_printer($self);
  }
}

sub _draw_to_printer {
  my ($self) = @_;
  my $printer = $::application->get_printer;
  if (! $printer->begin_doc(__('Math-Image'))) {
    warn "Print begin_doc() failed: $@\n";
    return;
  }

  my $draw = $self->{'draw'};
  my $gen_options = $draw->gen_options;
  my $gen = App::MathImage::Generator->new
    (step_time       => 0.25,
     step_figures    => 1000,
     %$gen_options,
     #      foreground => $self->style->fg($self->state)->to_string,
     #      background => $background_colorobj->to_string,
    );

  my $printer_width = $printer->width;
  my $printer_height = $printer->height;

  $printer->font->size(12);  # in points
  # ### fonts: $printer->fonts

  my $str = $gen->description . "\n\n";
  my $str_height = $printer->draw_text
    ($str, 0,10, $printer_width,$printer_height-10,
     dt::Left | dt::NewLineBreak | tw::WordBreak | dt::UseExternalLeading
     | dt::QueryHeight);
  ### $str
  ### $str_height
  ### font height: $printer->font->height

  ### clipRect is: $printer->clipRect
  # $printer->clipRect (0, 0, $printer_width, $printer_height);
  # $printer->translate (0, 20);  # up from bottom of page
  my $factor = max ($printer_width / $draw->width,
                    ($printer_height-10-5) / $draw->height);
  $gen->{'scale'} *= $factor,

  ### draw width: $draw->width
  ### draw height: $draw->height
  ### printer width: $printer->width
  ### printer height: $printer->height
  ### $factor
  ### $gen_options

  require Image::Base::Prima::Drawable;
  my $image = Image::Base::Prima::Drawable->new (-drawable => $printer);
  ### printer width:  $image->get('-width')
  ### printer height: $image->get('-height')

  $gen->draw_Image ($image);
  ### printer end_doc() ...


  $printer->translate (0, 0);  # up from bottom of page
  $printer->color (cl::White);
  $printer->bar (0, $printer_height-10-$str_height-5,
                  $printer_width, $printer_height);

  $printer->color (cl::Black);
  $printer->draw_text
    ($str, 0,10, $printer_width,$printer_height-10,
     dt::Left | dt::NewLineBreak | tw::WordBreak | dt::UseExternalLeading
     | dt::QueryHeight);

  $printer->end_doc;
  ### printer done ...
}


#------------------------------------------------------------------------------
# command line

sub command_line {
  my ($class, $mathimage) = @_;
  ### Prima command_line(): $mathimage

  my $gen_options = $mathimage->{'gen_options'};
  my $mainwin = $class->new
    (gui_options => $mathimage->{'gui_options'},
     gen_options => $gen_options);
  Prima->run;
  return 0;
}

1;
__END__