GUI.pm 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # ------------------------------------------------------------------------------
  2. # (C) Crown copyright Met Office. All rights reserved.
  3. # For further details please refer to the file COPYRIGHT.txt
  4. # which you should have received as part of this distribution.
  5. # ------------------------------------------------------------------------------
  6. use strict;
  7. use warnings;
  8. package Fcm::Interactive::InputGetter::GUI;
  9. use base qw{Fcm::Interactive::InputGetter};
  10. use Tk;
  11. ################################################################################
  12. # Returns the geometry string for the pop up message box
  13. sub get_geometry {
  14. my ($self) = @_;
  15. return $self->{geometry};
  16. }
  17. ################################################################################
  18. # Invokes the getter
  19. sub invoke {
  20. my ($self) = @_;
  21. my $answer;
  22. local $| = 1;
  23. # Create a main window
  24. my $mw = MainWindow->new();
  25. $mw->title($self->get_title());
  26. # Define the default which applies if the dialog box is just closed or
  27. # the user selects 'cancel'
  28. $answer = $self->get_default() ? $self->get_default() : q{};
  29. if (defined($self->get_type()) && $self->get_type() =~ qr{\A yn}ixms) {
  30. # Create a yes-no(-all) dialog box
  31. # If TYPE is YNA then add a third button: 'all'
  32. my $buttons = $self->get_type() =~ qr{a \z}ixms ? 3 : 2;
  33. # Message of the dialog box
  34. $mw->Label('-text' => $self->get_message())->grid(
  35. '-row' => 0,
  36. '-column' => 0,
  37. '-columnspan' => $buttons,
  38. '-padx' => 10,
  39. '-pady' => 10,
  40. );
  41. # The "yes" button
  42. my $y_b = $mw->Button(
  43. '-text' => 'Yes',
  44. '-underline' => 0,
  45. '-command' => sub {$answer = 'y'; $mw->destroy()},
  46. )
  47. ->grid('-row' => 1, '-column' => 0, '-padx' => 5, '-pady' => 5);
  48. # The "no" button
  49. my $n_b = $mw->Button (
  50. '-text' => 'No',
  51. '-underline' => 0,
  52. '-command' => sub {$answer = 'n'; $mw->destroy()},
  53. )
  54. ->grid('-row' => 1, '-column' => 1, '-padx' => 5, '-pady' => 5);
  55. # The "all" button
  56. my $a_b;
  57. if ($buttons == 3) {
  58. $a_b = $mw->Button(
  59. '-text' => 'All',
  60. '-underline' => 0,
  61. '-command' => sub {$answer = 'a'; $mw->destroy()},
  62. )
  63. ->grid('-row' => 1, '-column' => 2, '-padx' => 5, '-pady' => 5);
  64. }
  65. # Keyboard binding
  66. if ($buttons == 3) {
  67. $mw->bind('<Key>' => sub {
  68. my $button
  69. = $Tk::event->K() eq 'Y' || $Tk::event->K() eq 'y' ? $y_b
  70. : $Tk::event->K() eq 'N' || $Tk::event->K() eq 'n' ? $n_b
  71. : $Tk::event->K() eq 'A' || $Tk::event->K() eq 'a' ? $a_b
  72. : undef
  73. ;
  74. if (defined($button)) {
  75. $button->invoke();
  76. }
  77. });
  78. }
  79. else {
  80. $mw->bind('<Key>' => sub {
  81. my $button
  82. = $Tk::event->K() eq 'Y' || $Tk::event->K() eq 'y' ? $y_b
  83. : $Tk::event->K() eq 'N' || $Tk::event->K() eq 'n' ? $n_b
  84. : undef
  85. ;
  86. if (defined($button)) {
  87. $button->invoke();
  88. }
  89. });
  90. }
  91. # Handle the situation when the user attempts to quit the window
  92. $mw->protocol('WM_DELETE_WINDOW', sub {
  93. if (self->get_default()) {
  94. $answer = $self->get_default();
  95. }
  96. $mw->destroy();
  97. });
  98. }
  99. else {
  100. # Create a dialog box to obtain an input string
  101. # Message of the dialog box
  102. $mw->Label('-text' => $self->get_message())->grid(
  103. '-row' => 0,
  104. '-column' => 0,
  105. '-padx' => 5,
  106. '-pady' => 5,
  107. );
  108. # Entry box for the user to type in the input string
  109. my $entry = $answer;
  110. my $input_e = $mw->Entry(
  111. '-textvariable' => \$entry,
  112. '-width' => 40,
  113. )
  114. ->grid(
  115. '-row' => 0,
  116. '-column' => 1,
  117. '-sticky' => 'ew',
  118. '-padx' => 5,
  119. '-pady' => 5,
  120. );
  121. my $b_f = $mw->Frame->grid(
  122. '-row' => 1,
  123. '-column' => 0,
  124. '-columnspan' => 2,
  125. '-sticky' => 'e',
  126. );
  127. # An OK button to accept the input string
  128. my $ok_b = $b_f->Button (
  129. '-text' => 'OK',
  130. '-command' => sub {$answer = $entry; $mw->destroy()},
  131. )
  132. ->grid('-row' => 0, '-column' => 0, '-padx' => 5, '-pady' => 5);
  133. # A Cancel button to reject the input string
  134. my $cancel_b = $b_f->Button(
  135. '-text' => 'Cancel',
  136. '-command' => sub {$answer = undef; $mw->destroy()},
  137. )
  138. ->grid('-row' => 0, '-column' => 1, '-padx' => 5, '-pady' => 5);
  139. # Keyboard binding
  140. $mw->bind ('<Key>' => sub {
  141. if ($Tk::event->K eq 'Return' or $Tk::event->K eq 'KP_Enter') {
  142. $ok_b->invoke();
  143. }
  144. elsif ($Tk::event->K eq 'Escape') {
  145. $cancel_b->invoke();
  146. }
  147. });
  148. # Allow the entry box to expand
  149. $mw->gridColumnconfigure(1, '-weight' => 1);
  150. # Set initial focus on the entry box
  151. $input_e->focus();
  152. $input_e->icursor('end');
  153. }
  154. $mw->geometry($self->get_geometry());
  155. # Switch on "always on top" property for $mw
  156. $mw->property(
  157. qw/set _NET_WM_STATE ATOM/,
  158. 32,
  159. ['_NET_WM_STATE_STAYS_ON_TOP'],
  160. ($mw->toplevel()->wrapper())[0],
  161. );
  162. MainLoop();
  163. return $answer;
  164. }
  165. 1;
  166. __END__
  167. =head1 NAME
  168. Fcm::Interactive::InputGetter::GUI
  169. =head1 SYNOPSIS
  170. use Fcm::Interactive;
  171. $answer = Fcm::Interactive::get_input(
  172. title => 'My title',
  173. message => 'Would you like to ...?',
  174. type => 'yn',
  175. default => 'n',
  176. );
  177. =head1 DESCRIPTION
  178. This is a solid implementation of
  179. L<Fcm::Interactive::InputGetter|Fcm::Interactive::InputGetter>. It gets a user
  180. reply from a TK pop up message box.
  181. =head1 METHODS
  182. See L<Fcm::Interactive::InputGetter|Fcm::Interactive::InputGetter> for a list of
  183. inherited methods.
  184. =over 4
  185. =item new($args_ref)
  186. As in L<Fcm::Interactive::InputGetter|Fcm::Interactive::InputGetter>, but also
  187. accept a I<geometry> element for setting the geometry string of the pop up
  188. message box.
  189. =item get_geometry()
  190. Returns the geometry string for the pop up message box.
  191. =back
  192. =head1 TO DO
  193. Tidy up the logic of invoke(). Separate the logic for YN/A box and string input
  194. box, probably using a strategy pattern. Factor out the logic for the display
  195. and the return value.
  196. =head1 SEE ALSO
  197. L<Fcm::Interactive|Fcm::Interactive>,
  198. L<Fcm::Interactive::InputGetter|Fcm::Interactive::InputGetter>,
  199. L<Fcm::Interactive::InputGetter::CLI|Fcm::Interactive::InputGetter::CLI>
  200. =head1 COPYRIGHT
  201. E<169> Crown copyright Met Office. All rights reserved.
  202. =cut