The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package ApplicationWindow;

use Qt 2.0;
import Qt::app;
use Qt::slots (
    'newDoc()',
    'load()',
    'loadFile(cstring)',
    'save()',
    'print()',
    'closeDoc()',
    'toggleMenuBar()',
    'toggleStatusBar()',
    'toggleToolBar()'
);

@ISA = qw(Qt::MainWindow);

sub readxpm {
    my $xpm = shift;
    open(XPM, $xpm) || return [];
    $str = join('', <XPM>);
    close(XPM);
    $str =~ s/^.*\{(.*)\}.*/\@xpm = ($1)/s;
    my @xpm;
    eval "$str";
    return \@xpm;
}

$fileopen = readxpm("fileopen.xpm");
$filesave = readxpm("filesave.xpm");
$fileprint = readxpm("fileprint.xpm");

my $fileOpenText = "Click this button to open a new file.\n\n" .
"You can also select the Open command from the File menu.";
my $fileSaveText = "Click this button to save the file you are " .
"editing.  You will be prompted for a file name.\n\n" .
"You can also select the Save command from the File menu.\n\n" .
"Note that implementing this function is left as an exercise for the reader.";
my $filePrintText = "Click this button to print the file you " .
"are editing.\n\n" .
"You can also select the Print command from the File menu.";

sub new {
    my $self = shift->SUPER::new(undef, "example application main window");
    my $printer = $self->{'printer'} = Qt::Printer->new;
    $printer->setMinMax(1, 10);

    my $fileTools = $self->{'fileTools'} =
	Qt::ToolBar->new($self, 'file operations');

    my $openIcon = $self->{'openIcon'} = Qt::Pixmap->new($fileopen);
    my $fileOpen = Qt::ToolButton->new($openIcon, "Open File", undef,
				       $self, 'load()',
				       $fileTools, "open file");

    my $saveIcon = $self->{'saveIcon'} = Qt::Pixmap->new($filesave);
    my $fileSave = Qt::ToolButton->new($saveIcon, "Save File", undef,
				       $self, 'save()',
				       $fileTools, "save file");

    my $printIcon = $self->{'printIcon'} = Qt::Pixmap->new($fileprint);
    my $filePrint = Qt::ToolButton->new($printIcon, "Print File", undef,
					$self, 'print()',
					$fileTools, "print file");

    Qt::WhatsThis::whatsThisButton($fileTools);
    Qt::WhatsThis::add($fileOpen, $fileOpenText, 0);
    Qt::WhatsThis::add($fileSave, $fileSaveText, 0);
    Qt::WhatsThis::add($filePrint, $filePrintText, 0);

    my $file = Qt::PopupMenu->new;
    $self->menuBar()->insertItem('&File', $file);

    $file->insertItem("New", $self, 'newDoc()', Qt::CTRL+Qt::Key_N);
    $file->insertItem($openIcon, "Open", $self, 'load()', Qt::CTRL+Qt::Key_O);
    $file->insertItem($saveIcon, "Save", $self, 'save()', Qt::CTRL+Qt::Key_S);
    $file->insertSeparator();
    $file->insertItem($printIcon, "Print", $self, 'print()', Qt::CTRL+Qt::Key_P);
    $file->insertSeparator();
    $file->insertItem("Close", $self, 'closeDoc()', Qt::CTRL+Qt::Key_W);
    $file->insertItem("Quit", $app, 'quit()', Qt::CTRL+Qt::Key_Q);

    my $controls = $self->{'controls'} = Qt::PopupMenu->new;
    $self->menuBar()->insertItem("&Controls", $controls);

    my $mb = $self->{'mb'} = 
	$controls->insertItem("Menu bar", $self, 'toggleMenuBar()', Qt::CTRL+Qt::Key_M);
    # Now an accelerator for when the menubar is invisible!
    my $a = Qt::Accel->new($self);
    $a->connectItem($a->insertItem(Qt::CTRL+Qt::Key_M), $self, 'toggleMenuBar()');

    my $tb = $self->{'tb'} =
	$controls->insertItem("Tool bar", $self, 'toggleToolBar()', Qt::CTRL+Qt::Key_T);
    my $sb = $self->{'sb'} =
	$controls->insertItem("Status bar", $self, 'toggleStatusBar()', Qt::CTRL+Qt::Key_B);
    $controls->setCheckable(1);
    $controls->setItemChecked($mb, 1);
    $controls->setItemChecked($tb, 1);
    $controls->setItemChecked($sb, 1);

    my $e = $self->{'e'} = Qt::MultiLineEdit->new($self, "editor");
    $e->setFocus();
    $self->setCentralWidget($e);
    $self->statusBar()->message("Ready", 2000);

    return $self;
}

# No destructor needed, $self->{'printer'} deleted on destruction

sub newDoc {
    my $self = shift;
    my $ed = ApplicationWindow->new;
    $ed->resize(400, 400);
    $ed->show();
}

sub loadFile { shift->load(@_) }

sub load {
    my $self = shift;
    my $e = $self->{'e'};
    my $fileName = shift;
    unless($fileName) {
	my $fn = Qt::FileDialog::getOpenFileName(undef, undef, $self);
	if(defined $fn) {
	    $self->load($fn);
	} else {
	    $self->statusBar()->message("Loading aborted", 2000);
	}
	return;
    }

    return unless open(F, $fileName);

    $e->setAutoUpdate(0);
    $e->clear();

    while(defined($s = <F>)) {
	chomp($s);
	$e->append($s);
    }
    close(F);

    $e->setAutoUpdate(1);
    $e->repaint();
    $self->setCaption($fileName);
    $self->statusBar()->message("Loaded document $fileName", 2000);
}

sub save {
    my $self = shift;
    $self->statusBar()->message("File->Save is not implemented");
    Qt::MessageBox::information(undef, "Note", "Left as an exercise for the user.");
}

sub print {
    my $self = shift;
    my $e = $self->{'e'};
    my $printer = $self->{'printer'};
    my $MARGIN = 10;
    my $pageNo = 1;

    if($printer->setup($self)) {
	$self->statusBar()->message("Printing...");
	my $p = Qt::Painter->new;
	$p->begin($printer);
	$p->setFont($e->font());
	my $yPos = 0;
	my $fm = $p->fontMetrics();
	my $metrics = Qt::PaintDeviceMetrics->new($printer);
	for(my $i = 0; $i < $e->numLines(); $i++) {
	    if($MARGIN + $yPos > $metrics->height() - $MARGIN) {
		$self->statusBar()->message("Printing (page $pageNo)...");
		$pageNo++;
		$printer->newPage();
		$yPos = 0;
	    }
	    $p->drawText($MARGIN, $MARGIN + $yPos,
			 $metrics->width(), $fm->lineSpacing(),
			 Qt::ExpandTabs | Qt::DontClip,
			 $e->textLine($i));
	    $yPos += $fm->lineSpacing();
	}
	$p->end();
	$self->statusBar()->message("Printing completed", 2000);
    } else {
	$self->statusBar()->message("Printing aborted", 2000);
    }
}

sub closeDoc {
    my $self = shift;
    $self->close(1);
}

sub toggleMenuBar {
    my $self = shift;
    my $controls = $self->{'controls'};
    if($self->menuBar()->isVisible()) {
	$self->menuBar()->hide();
	$controls->setItemChecked($self->{'mb'}, 0);
    } else {
	$self->menuBar()->show();
	$controls->setItemChecked($self->{'mb'}, 1);
    }
}

sub toggleToolBar {
    my $self = shift;
    my $controls = $self->{'controls'};
    my $fileTools = $self->{'fileTools'};
    if($fileTools->isVisible()) {
	$fileTools->hide();
	$controls->setItemChecked($self->{'tb'}, 0);
    } else {
	$fileTools->show();
	$controls->setItemChecked($self->{'tb'}, 1);
    }
}

sub toggleStatusBar {
    my $self = shift;
    my $controls = $self->{'controls'};
    if($self->statusBar()->isVisible()) {
	$self->statusBar()->hide();
	$controls->setItemChecked($self->{'sb'}, 0);
    } else {
	$self->statusBar()->show();
	$controls->setItemChecked($self->{'sb'}, 1);
    }
}