HTML page
15.12. Управление экраном
Проблема
Вы хотите выделять символы повышенной интенсивностью, перехватывать нажатия специальных клавиш или выводить полноэкранные меню, но не желаете беспокоиться о том, на каком устройстве вывода работает пользователь.Решение
Воспользуйтесь модулем Curses с СРАМ, который использует библиотеку curses(3) вашей системы.Комментарий
Библиотека curses обеспечивает простое, эффективное и аппаратно-независимос выполнение полноэкранных операций. С его помощью можно писать высокоуровневый код вывода данных на логическом экране по символам или по строкам. Чтобы результаты вывода появились на экране, вызовите функцию refresh. Вывод, сгенерированный библиотекой, описывает только изменения виртуального экрана с момента последнего вызова refresh. Это особенно существенно для медленных подключений.Работа с модулем Curses демонстрируется программой rep из примера 15.5. Вызовите ее с аргументами, описывающими командную строку запускаемой программы:
% rep ps aux % rep netstat % rep -2.5 Ipq
Сценарий rep в цикле вызывает команду и выводит ее данные на экран, обновляя лишь ту часть, которая изменилась с момента предыдущего запуска. Такой вариант наиболее эффективен при малых изменениях между запусками. В правом нижнем углу экрана выводится текущая дата в инвертированном изображении.
По умолчанию rep ожидает 10 секунд перед повторным запуском команды. Чтобы изменить период задержки, передайте нужное количество секунд (допускается дробное число) в качестве аргумента, как это было сделано выше при вызове Ipq. Кроме того, нажатие любой клавиши во время ожидания приводит к немедленному выполнению команды. Пример 15.5. rep
#!/usr/bin/perl -w
# rep - циклическое выполнение команды
use strict;
use Curses;
my $timeout = 10;
if (@ARGV && $ARGV[0] =- /"-(\d+\.?\d*)$/) {
$timeout = $1;
shift;
}
die "usage: $0 [ -timeout ] cmd args\n" unless @ARGV;
initscr(); # Инициализировать экран noechoO;
cbreak();
nodelay(1); # Чтобы функция getch() выполнялась без блокировки
$SIG{INT} = sub { done("0uch!") };
sub done { endwin(); print "O.Vn"; exit; }
while (1) {
while ((my $key = getch()) ne ERR) { # Возможен ввод
done("See ya") if $key eq 'q' # нескольких символов }
my @data = '(@ARGV) 2>&1'; # Вывод+ошибки
for (my $i =0; $i < $LINES; $i++) {
addstr($i, 0, $data[$i] |] o o x $COLS);
}
standout();
addstr($LINES-1, $COLS - 24, scalar localtime);
standend();
move(0,0);
refresh(); # Обновить экран
my ($in, $out) = ('', '');
vec($in, fileno(STDIN), 1) =1; # Искать символ в stdin
select($out = $in,undef,undef,$timeout);# Ожидание
}
С помощью Curses можно узнать, когда пользователь нажал клавишу со стрелкой или служебную клавишу (например, Ноте или Insert). Обычно это вызывает затруднения, поскольку эти клавиши кодируются несколькими байтами. С Curses все просто:
keypad(1); # Включить режим ввода
$key = getch(); # с цифровой клавиатуры
if ($key eq 'k' || # Режим vi
$key eq "\cP" || # Режим emacs
$key eq KEY_UP) # Стрелка
{
# Обработать клавишу
}
Другие функции Curses позволяют читать текст в определенной позиции экрана, управлять выделением символов и даже работать в нескольких окнах.
Модуль perlmenu, также хранящийся на CPAN, построен на базе низкоуровневого модуля Curses. Он обеспечивает высокоуровневые операции с меню и экранными формами. Приведем пример экранной формы из поставки perlmenu:
Template Entry Demonstration
Address Data Example Record #___
Name: [ _________________________________________________________ ]
Addr: [ _________________________________________________________ ]
City: [__________________] VState: [__] Zip: [\\\\\]
Phone: (\\\) \\\-\\\\ Password: [^^^^^^^^]
Enter all information available.
Edit fields with left/right arrow heys or "delete".
Switch fields with "Tab" or up/down arrow keys.
Indicate completion by pressing "Return".
Refresh screen with "Control-L".
Abort this demo here with "Control-X".
Пользователь вводит текст в соответствующих полях. Обычный текст обозначается символами подчеркивания, числовые данные - символами \, а неотображаемые данные - символами ". Такие обозначения напоминают форматы Perl, за исключением того, что формы предназначены для вывода, а не для ввода данных.
> Смотри также -------------------------------
Man-страница curses(3) вашей системы (если есть); документация по модулям Curses и perlmenu с СРАМ; раздел "Formats" perlform(1); рецепт 3.10.
15.13. Управление другой программой с помощью Expect
Проблема
Вы хотите автоматизировать процесс взаимодействия с полноэкранной программой, которая работает с терминалом, не ограничиваясь STDIN и STDOUT.Решение
Воспользуйтесь модулем Expect с CPAN:use Expect;
$command = expect->spawn("program to run")
or die "Couldn't start program: $!\n";
# Запретить вывод программы в STDOUT
$command->log_stdout(0);
# 10 секунд подождать появления "Password:"
unless ($command->expect(10, "Password")) {
# Тайм-аут
}
# 20 секунд подождать вывода текста, совпадающего с /[IL]login: ?/
unless ($command->expect(20, -re => '[ILJogin: ?')) {
} # Таймаут
# Бесконечно долго ждать появления "invalid"
unless ($command->expect(undef, "invalid")) {
# Произошла ошибка: вероятно, работа программы нарушена
}
# Послать программе "Hello, world" и перевод строки
print $command "Hello, world\n";
# Если программа завершается сама, предоставить ей такую возможность
$command->soft_close();
# Если программа должна быть закрыта извне, завершить ее
$command->hard_close();
Комментарий
Для работы модуля Expect необходимы два других модуля с CPAN: I,0::Pty и IO:Stty. Expect создает псевдотерминал для взаимодействия с программами, которые непременно должны общаться с драйвером терминального устройства. Такая возможность часто используется для изменения пароля в программе passwd. К числу других программ, для которых также необходим настоящий терминал, принадлежат telnet (модуль Net::Telnet из рецепта 18.6 более функционален и обладает улучшенной переносимостью) и ftp.Запустите нужную программу с помощью Expect->spawn, передайте ей имя программы и .аргументы - либо в виде одной строки, либо в виде списка. Expect запускает программу и возвращает либо представляющий ее объект, либо undef, если запустить программу не удалось. Для ожидания вывода программой конкретной строки применяется метод expect. Его первый аргумент равен либо числу секунд, в течение которых ожидается вывод строки, либо undef для бесконечного ожидания. Ожидаемая строка является вторым аргументом expect. Чтобы определить ее с помощью регулярного выражения, передайте в качестве второго аргумента строку "-re", а третьего - строку с шаблоном. Затем можно передать другие строки или шаблоны:
$which = $command->expect(30, "invalid", "succes", "error", "boom");
if ($which) {
# Найдена одна из указанных строк
В скалярном контексте expect возвращает номер аргумента, для которого произошло совпадение. В предыдущем примере expect вернет 1 при выдаче программой строки "invalid", 2 - при выводе "succes" и т. д. Если ни одна строка или шаблон не совпали, expect возвращает false.
В списковом контексте expect возвращает список из пяти элементов. Первый элемент определяет номер совпавшей строки или шаблона (идентично возвращаемому значению в скалярном контексте). Второй элемент - строка с описанием причины возврата из expect. При отсутствии ошибок второй аргумент равен undef. Возможные варианты ошибок: "1:TIMEOUT", "2: EOF", "3: spawn id(. . . ) died" n "4:..." (смысл этих сообщений описан в Expect(3)). Третий элемент в возвращаемом списке expect равен совпавшей строке. Четвертый элемент - текст до совпадения, а пятый - текст после совпадения.
Передача данных программе, находящейся под управлением Expect, сводится к простейшему вызову print. Единственная трудность состоит в том, что терминалы, устройства и сокеты отличаются по тем последовательностям, которые они передают и принимают в качестве разделителя строк, - мы покинули убежище стандартной библиотеки ввода/вывода С, поэтому автоматическое преобразование в "\n" не происходит. Рекомендуем начать с "\r; если не получится, попробуйте "\п" и "\r\n". После завершения работы с запущенной программой у вас есть три возможности. Во-первых, можно продолжить работу с главной программой; вероятно, запущенная программа будет принудительно завершена по завершении главной программы. Однако в этом случае плодятся лишние процессы. Во-вторых, если запущенная программа должна нормально завершиться после вывода всех данных или по некоторому внешнему условию (как, например, telnet при выходе из удаленного командного интерпретатора), вызовите метод soft_close. Если запущенная программа будет работать бесконечно (например, tail -/), вызовите метод hard_close; он уничтожает запущенный процесс.
> Смотри также --------------------------------
Документация по модулям Expect, IO:Pty и IO:Stty от CPAN.
15.14. Создание меню с помощью Tk
Проблема
Требуется создать окно, в верхней части которого находится меню.Решение
Воспользуйтесь элементами Tk Menubutton и Frame:use Tk;
$main = mainwindow->new();
# Создать для меню горизонтальную область
# в верхней части окна.
$menubar = $niain->Frame(-relief => "raised",
-borderwidth => 2) ->pack (-anchor => "nw",
-fill => "x"):
# Создать кнопку с надписью "File" для вызова меню,
$file_menu = $menubar->Menubutton(-text => "File",
-underline => 1)
->pack (-side => "left" );
# Создать команды меню "File"
$file_menu->command(-label => "Print",-command => \&Print);
To же самое можно сделать намного проще, если воспользоваться сокращенной записью
-menuitems:
$file_menu = $menubar->Menubutton(-text => "File",-underlined 1,
-menuitems=> [ [ Button => "Print",-command => \&Print ],
[ Button => "Save",-command => \&Save ] ])
->pack(-side => "left");
Комментарий
Меню приложения можно рассматривать как совокупность четырех компонентов: области (Frame), кнопок меню (Menubutton), меню (Menus) и команд меню (Menu Entries). Область представляет собой горизонтальную полосу в верхней части окна, в котором находится меню. Внутри области находится набор кнопок меню, открывающих различные меню: File, Edit, Format, Buffers и т. д. Когда пользователь щелкает на кнопке меню, на экране появляется соответствующее меню - вертикальный список команд.В меню могут включаться разделители - горизонтальные линии, отделяющие один набор команд от другого.
С командами (например, Print в меню File) ассоциируются фрагменты кода. При выборе команды меню вызывается соответствующая функция. Обычно это делается так:
$file_menu->command(-label => "Quit Immediately",
-command => sub { exit } );
С разделителями действия не связываются:
$file_menu->separator(); Команда-флажок может находиться в установленном (on) или сброшенном (off) состоянии, и с ней ассоциируется некоторая переменная. Если переменная находится в установленном состоянии, рядом с текстом команды-флажка стоит специальная пометка (маркер). Если переменная сброшена, маркер отсутствует. При выборе команды-флажка переменная переходит в противоположное состояние.
$options_menu->checkbutton(-label => "Create Debugging File",
-variable => \$debug,
-onvalue => 1,
-off value => 0);
Группа команд-переключателей ассоциируется с одной переменной. В любой момент времени установленной может быть лишь одна команда-переключатель, ассоциированная с переменной. При выборе команды-переключателя переменное присваивается ассоциированное значение:
$debug_menu->radiobutton(-label => "Level 1",
-variable => \$log_level,
-value => 1):
$debug_menu->radiobutton(-label => "Level 2",
-variable => \$log_level,
-value => 2);
$debug_menu->radiobuttbn(-label => "Level 3",
-variable => \$log_level,
-value ==> 3);
Вложенные меню создаются с помощью каскадных команд. Например, в Netscape Navigator кнопка меню File содержит каскадную команду New, которая открывает подменю с несколькими вариантами. Создать каскадную команду сложнее чем любую другую: вы должны создать каскадную команду, получить ассоциированное с ней новое меню и создать команды в этом меню.
# Шаг 1: создать каскадную команду меню
$format_menu->cascade
(-label => "Font");
# Шаг 2: получить только что созданное меню
$font_menu = $format_menu->cget("-menu");
# Шаг 3: заполнить это меню
$font_menu->radiobutton (-label => "Courier",
-variable => \$font_name,
-value => "courier");
$font_menu=>radiobutton
(-label => "Times Roman",
-variable => \$font_name,
-value => "times");
Отсоединяемый разделитель позволяет перемещать меню, в котором он iici.xu-дится. По умолчанию все кнопки меню и каскадные команды открывают меню, ь верхней части которого находится отсоединяемый разделитель. Чтобы создать меню без него, воспользуйтесь параметром
-tearoff:
$format_menu = $menubar->Menubutton(-text => "Format",
-underline => 1
-tearoff => 0)
->Dack;
$ font_menu = $format_menu->Cascade(-label => "Font ,
-tearoff => 0);
Параметр -menuitems метода Menubutton представляет собой сокращенную [шр-му для создания команд меню. В нем передается ссылка на массив с описаниями команд Menubutton. В свою очередь, каждая команда описывается анонимным массивом. Первые два элемента массива команды определяют тип кнопки ("command","radiobutton", "checkbutton", "cascade" или "tearoff") и название меню.
my $f = $menubar->Menubutton(-text => "File", -underline => 0,
-menuitems =>[ [Button => 'Copy', -command => \&edit_copy ],
[Button => 'Cut', -command => \&edit_cut ],
[Button => 'Paste', -command => \&edit_paste ],
[Button => 'Delete', -command => \&edit_delete ], v[Separator => '' ], [Cascade => 'Object ...', -tearoff => 0,
-menuitems => [
[ Button => "Circle", -command => \&edit_circle ],
[ Button => "Square", -command => \&edit_square ],
[ Button => "Point", -command => \&edit_point ] ]
], ])->grid(-row => 0, -column => 0, -sticky => 'w');
[> Смотри также -------------------------------
Документация по модулю Tk с С PAN.
15.15. Создание диалоговых окон с помощью Tk
Проблема
Требуется создать диалоговое окно, то есть новое окно верхнего уровня с кнопками для его закрытия. Диалоговое окно также может содержать другие элементы -например, надписи и текстовые поля для ввода информации. Например, в диалоговом окне можно ввести регистрационные данные и закрыть его после передачи сведений или в том случае, если пользователь не захочет регистрироваться.Решение
В простых случаях воспользуйтесь элементом Tk::DialogBox:use Tk::DialogBox;
$dialog = $main->DialogBox( -title => "Register This Program",
-buttons => [ "Register", "Cancel" ] );
# Добавьте элементы в диалоговое окно методом
$dialog->Add()
# Позднее, когда понадобится отобразить диалоговое окно
$button = $dialog->Show():
if ($button eq "Register") {
# . . .
} elsif ($button eq "Cancel") {
# ... } else {
# Такого быть не должно
}
Комментарий
Диалоговое окно состоит из набора кнопок (в нижней части) и произвольных элементов (в верхней части). Вызов Show выводит диалоговое окно на экран и возвращает кнопку, выбранную пользователем.Пример 15.6 содержит полную программу, демонстрирующую принципы работы с диалоговыми окнами. Пример 15.6. tksample3
#!/usr/bin/perl -w
# tksample3 - работа с диалоговыми окнами
use Tk;
use Tk::DialogBox;
$main = mainwindow->new();
$dialog = $main->DialogBox( -title => "Register",
-buttons => [ "Register", "Cancel" ], );
# В верхней части окна пользователь вводит имя, при этом
# надпись (Label) действует как подсказка.
$dialog->add("Label", -text => "Name")->pack();
Sentry = $dialog->add("Entry", -width => 35)->pack();
# Диалоговое окно вызывается кнопкой
$main->Button( -text => "Click Here For Registration Form",
-command => \®ister) ->pack(-side => "left");
$main->Button( -text => "Quit",
-command => sub { exit } ) ->pack(-side => "left"); MainLoop;
#
# register
#
# Вызывает диалоговое окно регистрации.
#
sub register { my $button;
my $done = 0;
do {
# Отобразит диалоговое окно.
$button = $dialog->Show;
# Действовать в зависимости от того, какая кнопка была нажата.
if ($button eq "Register") {
my $name = $entry->get;
if (defined($name) && length($name)) { print "Welcome to the fold, $name\n";
$done = 1;
} else {
print "You didn't give me your name!\n";
} } else {
print "Sorry you decided not to register.\n";
$done = 1;
} } until $done;
}
В верхней части диалогового окна расположены два элемента: надпись и текстовое поле. Для ввода дополнительной информации понадобятся другие надписи и текстовые поля. Диалоговые окна часто применяются для вывода предупреждений или сообщений об ошибках. Пример 15.7 показывает, как вывести в диалоговом окне результаты вызова функции warn. Пример 15.7. tksample4
#!/usr/bin/perl -w
# tksample4 - диалоговые окна для предупреждений
use Tk;
use Tk::DialogBox:
my $main;
# Создать обработчик предупреждений, который отображает
# предупреждение в диалоговом окне Tk
BEGIN {
$SIG{__WARN__} = sub { if (defined $main) {
my $dialog = $main->DialogBox( -title => "Warning",
-buttons => [ "Acknowledge" ]);
$dialog->add("Label", -text => $_[0])->pack;
$dialog->Show;
} else { vprint STDOUT joln("\n", о \ "n"'
}
};
}
# Команды вашей программы
$main = mainwindow->new();
$main->Button( -text => "Make A Warning", -command => \&make_warning) ->pack(-side => "left");
$main->Button( -text => "Quit",
-command => sub { exit } ) ->pack(-side => "left'"":
MainLoop:
# Фиктивная подпрограмма для выдачи предупреждения
sub make_warning { my $a;
my $b = 2 * $a;
> Смотри также -----------------------
Страница руководства Tk::DialogBox в документации по модулю Tk с CPAN; страница руководства тепи(п) (если она есть).
15.16. Обработка событий масштабирования в Tk
Проблема
Вы написали программу на базе Tk, но при изменении размеров окна пользователем нарушается порядок элементов.Решение
Перехватывая событие Configure, можно запретить пользователю измен чь размеры окна:use Tk:
$main = mainwindow->new();
$main->bind('
$xe = $main->XEvent;
$main->maxsize($xe->w, $xe->h);
$main->minsize($xe->w, $xe->h);
});
Кроме того, можно определить особенности масштабирования элементов при изменении размеров контейнера с помощью метода pack:
$widget->pack( -fill => "both", -expand => 1 );
$widget->pack( -fill => "x", -expand => 1 );
Комментарий
По умолчанию упакованные элементы изменяют размеры вместе с контейнером - они не масштабируют себя или свое содержимое в соответствии с новым размером. В результате между элементами возникают пустые места, а их содержимое обрезается или искажается. Первое решение - вообще запретить изменение размеров. Мы перехватываем событиеРазные части вашего приложения ведут себя по-разному. Например, главная область Web-броузера при изменении размера окна, вероятно, должна изменить свои размеры в обоих направлениях. Метод pack для такого элемента выглядит так:
$mainarea->pack( -fill => "both", -expand => 1);
Однако меню, расположенное над главной областью, может расшириться по горизонтали, но не по вертикали:
$menubar->pack( -fill => "x", -expand => 1 );
С изменением размеров связана и другая задача - закрепление элементов в определенной точке контейнера. Например, полоса меню закрепляется в левом верхнем углу контейнера следующим образом:
$menubar->pack (-fill => "x",
-expand => 1,
-anchor => "nw" );
Теперь при изменении размеров окна меню останется на своем месте и не будет выровнено по центру пустой области.
> Смотри также -------------------------------
Страницы руководства pack(n), XEvent(3) и XConfigureEvent(1) (если есть).
15.17. Удаление окна сеанса DOS в Perl/Tk для Windows
Проблема
Вы написали программу для Windows-версии Perl и Tk, однако при каждом за пуске программы открывается окно DOS-сеанса.Решение
Запускайте программу из другого сценария Perl. В примере 15.8 содержите пример загрузчика, который запускает программу realprogram без окна DOS. Пример 15.8. loader#!/usr/bin/perl -w
! loader - запуск сценариев Perl без раздражающего окна DOS
use strict;
use Win32;
use Win32::Process;
# Создать объект процесса.
Win32::Process::Create($Win32::Process::Create::ProcessObj,
'C:/perl5/bin/perl.exe', # Местонахождение Perl
'perl realprogram', #,
# He наследовать
DETACHED_PROCESS, #
".") or # Текущий каталог die print_error();
sub print_error() {
return Win32::FormatMessage( Win32::GetLastError() );
}
Комментарий
Программа проще, чем кажется. Окно DOS появляется потому, что интерпре татор Perl был откомпилирован как консольное приложение. Для чтения и STDIN и записи в STDOUT ему нужно окно DOS. Это нормально в приложени ях, работающих в режиме командной строки, но если все общение с пользовате лем организовано с помощью Tk, то окно DOS не понадобится. Загрузчик использует модуль Win32::Process для запуска программы в качеств^ нового процесса. Этот процесс отделяется от текущего, поэтому при завершенш загрузчика окно DOS пропадет вместе с ним. Ваша программа будет прекрасш работать, не отягощенная пережитками прошлого. Если произойдет какая-нибудь беда и программа не запустится, загрузчик ум рет с выдачей сообщения об ошибке Windows.> Смотри также --------------------------------
Документация по модулю Win32::Process, входящая в поставки Perl для систем Microsoft Windows.
15.18. Программа: tcapdemo
Описание
Следующая программа очищает экран и рисует на нем до тех пор, пока не будет прервана. Она показывает, как использовать модуль Term::Cap для очистки экрана, перемещения курсора и записи в любую позицию экрана. В ней также используется рецепт 16.6. Пример 15.9. tcapdemo#!/usr/bin/perl -w
# tcapdemo - прямое позиционирование курсора
use POSIX;
use Term::Cap;
init();
zip();
finish();
exit();
# Инициализация Term::Cap.
# Рисование линий на экране.
# Последующая очистка,
# Две вспомогательные функции. Смысл clear_screen очевиден, а
# clear_end очищает до конца экрана.
sub clear_screen { $tcap->Tputs('cl', 1, *STDOUT) }
sub clear_end { $tcap->Tputs('cd', 1, *STDOUT) }
# Переместить курсор в конкретную позицию.
sub gotoxy {
my($x, $y) = @_;
$tcap->Tgoto('cm', $x, $y, *STDOUT);
}
# Определить скорость терминала через модуль POSIX и использовать
# для инициализации Term::Cap.
sub init {
$| = 1;
$delay = (shift() || 0) * 0.005;
my $termios = posix::termios->new();
$termios->getattr;
my $ospeed = $termios->getospeed;
$tcap = term::cap->Tgetent ({ TERM => undef, OSPEED => $ospeed });
$tcap->Trequire(qw(cl cm cd));
}
# Рисовать линии на экране, пока пользователь
# не нажмет Ctrl-C.
sub zip {
clear_screen();
($maxrow, $maxcol) = ($tcap->{_li} - 1, $tcap->{_co} - 1)
@chars = qw(* - / i \ _ );
sub circle { push(@chars, shift @chars); }
$interrupted = 0;
$SIG{INT} = sub { ++$interrupted };
$col = $row = 0;
($row_sign, $col_sign) = (1,1);
do {
gotoxy($col, $row);
print $chars[0]; vselect(undef, undef, undef, $delay);
$row += $row_sign;
$col += $col_sign;
if ($row == $maxrow) { $row_sign = -1; circle; } elsif ($row == 0 )
{ $row_sign = +1; circle; }
if ($col == $maxcol) { $col_sign = -1; circle; ^ elsif ($col == 0 )
{ $col_sign = +1; circle; }
} until $interrupted;
}
# Очистить экран.
sub finish {
gotoxy(0, $maxrow);
clear_end();
}
[> Смотри также ------------------------------
Документация по стандартному модулю Term::Cap; termcap(5) (если есть).
15.19. Программа: tkshufflepod
Эта короткая программа с помощью Tk выводит список всех заголовков =head' в файле и позволяет изменить порядок их следования перетаскиванием. Клавиша "s" сохраняет изменения, а "g" завершает программу. Двойной щелчок на элемеите списка выводит его содержимое в элементе pod. Текст раздела записывается во временный файл, находящийся в каталоге /tmp; файл удаляется при уничтожении элемента pod.При запуске программе передается имя просматриваемого pod-файла:
% tkshufflepod chap15.pod
Мы часто использовали эту программу при работе над книгой. Исходный текст программы приведен в примере 15.10. Пример 15.10. tkshufflepod
#!/usr/bin/perl -w
# tkshufflepod - изменение порядка разделов =head1 в pod-файле
use Tk;
use strict;
# declare variables
my $podfile; # Имя открываемого файла
my $m; # Главное окно
my $1; # Элемент Listbox
my ($up, $down); # Перемещаемые позиции
my sections; # Список разделов pod
my $all_pod; # Текст pod-файла (используется при чтении)
# Прочитать pod-файл в память и разбить его на разделы,
$podfile = shift || "-";
undef $/;
open(F, "< $podfile") or die "Can't open $podfile : $!\n";
$all_pod =
close(F);
@sections = split(/(?==head1)/, $all_pod);
# Превратить #sections в массив анонимных массивов. Первый элемент
# каждого массива содержит исходный текст сообщения, а второй -
# текст, следующий за =head1 (заголовок раздела).
foreach (#sections) { /(.*)/;
$_ = [ $_, $1 ];
}
# Запустить Tk и вывести список разделов.
$m = mainwindow->new();
$l = $m->Listbox('-width' => 60)->pack('-expand' => 1, '-fill' => 'both');
foreach my $section ((Bisections) { $l->insert("end", $section->[1]);
}
# Разрешить перетаскивание для элемента Listbox.
$l->bind( '
$l->bind( '
# Разрешить просмотр при двойном щелчке
$l->bind( '
# 'q' завершает программу, a 's' сохраняет изменения,
$m->bind( '
' => sub { exit } );
$m->bind( 's' => \&save );
MainLoop;
# down(widget): вызывается, когда пользователь щелкает в Listbox.
sub down {
my $self = shift;
$down = $self->curselection;; ' }
$ up(widget): вызывается, когда пользователь отпускает
# кнопку мыши в Listbox.
sub up {
my $self = shift;
my $elt;
$up = $self->curselection;;
return if $down == $up;
# change selection list
$elt = $sections[$down];
splice(@sections, $down, 1):
splice(@sections, $up, 0, $elt);
$self->delete($down);
$self->insert($up, $sections[$up]->[1]);
}
# save(widget): сохранение списка разделов.
sub save {
my $self = shift;
open(F, "> $podfile")
or die "Can't open $podfile for writing: $! print F map { $_->[0] } @>sections;
close F;
exit;
}
# view(widget): вывод раздела. Использует элемент Pod.
sub view {
my $self = shift;
my $temporary = "/tmp/$$-section.pod";
my $popup;
open(F, "> $temporary")
or warn ("Can't open $temporary : $!\n"), return;
print F $sections[$down]->[0];
close(F);
$popup = $m->Pod('-file' => $temporary);
$popup->bind('' => sub { unlink $temporary } );
}