Documentation | Manual: Files
Chapter 8: Files
In this chapter, we will learn how to handle files.
This program for this chapter is called Files.ext.
The native file handling capabilities of the host language (i.e., Perl or
Python) are more than sufficient for most needs. There is no reason to expose
the relevant classes (BFile, BDirectory, etc.) via the bindings.
On the other hand, dealing with the file type database and file selection via the UI require that some of the C++ classes be exposed.
(Nodes and Queries are dealt with in their own chapters.)
The code below is abbreviated somewhat from the actual sample file, in order to concentrate on the important points.
TypeFilter
First we create a RefFilter subclass, so we can use in the main class.
Perl
package TypeFilter;
use parent qw(Haiku::RefFilter);
sub new {
my ($class, $type) = @_;
my $self = $class->SUPER::new();
$self->{type} = lc $type;
return $self;
}
sub Filter {
my ($self, $ref, $node, $stat, $type) = @_;
return 1 if -d $ref; # allow directories
return 1 if $self->{type} eq lc $type;
return 0;
}
Python
class TypeFilter(RefFilter):
def __init__(self, type):
self.type = type.lower()
def Filter(self, ref, node, stat, type):
print ref, type, self.type
if isdir(ref):
return 1 # allow directories
if self.type == type.lower():
return 1
return 0
This simple subclass takes a mime type string and creates a filter that will select only files of that type.
FilesWindow
We set up our message codes and create a Roster object so we can launch the FileTypes app.
Perl
package FilesWindow;
use parent qw(Haiku::Window);
my $icon_data = undef; # definition omitted to save space
my $FILE_TYPE_BUTTON = code_to_int('ftyp');
my $LAUNCH_BUTTON = code_to_int('lnch');
my $PANEL_BUTTON = code_to_int('panl');
my $roster = Haiku::Roster->new();
my @window_frame = (50,50,350,350);
Python
class FilesWindow(Window):
icon_data = None; # definition omitted to save space
FILE_TYPE_BUTTON = code_to_int('ftyp')
LAUNCH_BUTTON = code_to_int('lnch')
PANEL_BUTTON = code_to_int('panl')
roster = Roster()
window_frame = [50,50,350,350]
The constructor will pass our window frame to the base class constructor, then begin to create UI elements. The first elements are two buttons: one to toggle the installed state of our file type, and another to launch the FileTypes app.
Perl
sub new {
my ($class) = @_;
my $self = $class->SUPER::new(
frame => \@window_frame,
title => "Files",
type => B_TITLED_WINDOW,
flags => B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE,
);
my $buffer = 5;
my $left = $buffer;
my $top = $buffer;
$self->{install_button} = Haiku::Button->new(
frame => [$left,$top,$left+1,$top+1],
name => "install_button",
label => "Install File Type",
message => Haiku::Message->new($FILE_TYPE_BUTTON),
);
$self->AddChild($self->{install_button});
$self->{install_button}->ResizeToPreferred();
my $type = Haiku::MimeType->new("text/x.QuickText");
if ($type->IsInstalled()) {
$self->{install_button}->SetLabel("Delete File Type");
}
$left = $self->{install_button}->Frame()->right + $buffer;
my $launch_button = Haiku::Button->new(
frame => [$left,$top,$left+1,$top+1],
name => "launch_button",
label => "Launch File Types",
message => Haiku::Message->new($LAUNCH_BUTTON),
);
$self->AddChild($launch_button);
$launch_button->ResizeToPreferred();
Python
def __new__(klass):
self = super(FilesWindow, klass).__new__(
klass,
frame = klass.window_frame,
title = "Files",
type = B_TITLED_WINDOW,
flags = B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE,
)
buffer = 5
left = buffer
top = buffer
self.install_button = Button(
frame = [left,top,left+1,top+1],
name = "install_button",
label = "Install File Type",
message = Message(klass.FILE_TYPE_BUTTON),
)
self.AddChild(self.install_button)
self.install_button.ResizeToPreferred()
type = MimeType("text/x.QuickText")
if type.IsInstalled():
self.install_button.SetLabel("Delete File Type")
left = self.install_button.Frame().right + buffer
launch_button = Button(
frame = [left,top,left+1,top+1],
name = "launch_button",
label = "Launch File Types",
message = Message(klass.LAUNCH_BUTTON),
)
self.AddChild(launch_button)
launch_button.ResizeToPreferred()
Next we will create a Box and populate it with controls that the user can use to customize a FilePanel.
Perl
my $bounds = $self->Bounds();
$left = $buffer;
$top = $self->{install_button}->Frame()->bottom + $buffer;
my $right = $bounds->[2] - $buffer;
my $bottom = $bounds->[3] - $buffer;
my $box = Haiku::Box->new(
frame => [$left,$top,$right,$bottom],
name => "panel_box",
resizingMode => B_FOLLOW_ALL,
);
$self->AddChild($box);
$box->SetLabel("File Panel");
$box->SetViewColor([255,255,255]);
# box to group panel options, with button to launch panel
$buffer += 2;
$left = $buffer;
$top = $buffer + 15;
$self->{open_radio} = Haiku::RadioButton->new(
frame => [$left,$top,$left+1,$top+1],
name => "open_radio",
label => 'Open Panel',
);
$box->AddChild($self->{open_radio});
$self->{open_radio}->ResizeToPreferred();
$self->{open_radio}->SetValue(1);
my $frame = $self->{open_radio}->Frame();
$left = $frame->right + $buffer;
$self->{save_radio} = Haiku::RadioButton->new(
frame => [$left,$top,$left+1,$top+1],
name => "save_radio",
label => 'Save Panel',
);
$box->AddChild($self->{save_radio});
$self->{save_radio}->ResizeToPreferred();
$left = $buffer;
$top = $frame->bottom + $buffer;
$self->{file_flavor} = Haiku::CheckBox->new(
frame => [$left,$top,$left+1,$top+1],
name => "file_flavor",
label => 'Files',
);
$box->AddChild($self->{file_flavor});
$self->{file_flavor}->ResizeToPreferred();
$self->{file_flavor}->SetValue(1);
$frame = $self->{file_flavor}->Frame();
$left = $frame->right + $buffer;
$self->{directory_flavor} = Haiku::CheckBox->new(
frame => [$left,$top,$left+1,$top+1],
name => "directory_flavor",
label => 'Directories',
);
$box->AddChild($self->{directory_flavor});
$self->{directory_flavor}->ResizeToPreferred();
$frame = $self->{directory_flavor}->Frame();
$left = $frame->right + $buffer;
$self->{symlink_flavor} = Haiku::CheckBox->new(
frame => [$left,$top,$left+1,$top+1],
name => "symlink_flavor",
label => 'Symlinks',
);
$box->AddChild($self->{symlink_flavor});
$self->{symlink_flavor}->ResizeToPreferred();
$left = $buffer;
$top = $frame->bottom + $buffer;
$self->{allow_multiple} = Haiku::CheckBox->new(
frame => [$left,$top,$left+1,$top+1],
name => "allow_multiple",
label => 'Allow Multiple Selections',
);
$box->AddChild($self->{allow_multiple});
$self->{allow_multiple}->ResizeToPreferred();
$frame = $self->{allow_multiple}->Frame();
$left = $buffer;
$top = $frame->bottom + $buffer;
$self->{file_type} = Haiku::TextControl->new(
frame => [$left,$top,$left+1,$top+1],
name => "file_type",
label => 'File Type',
text => 'text/plain',
resizingMode => B_FOLLOW_LEFT_RIGHT,
);
$box->AddChild($self->{file_type});
$self->{file_type}->ResizeToPreferred();
$frame = $self->{file_type}->Frame();
my $width = $box->Bounds()->Width() - 2 * $buffer;
$self->{file_type}->ResizeTo($width,$frame->Height());
$left = $buffer;
$top = $frame->bottom + $buffer;
my $panel_button = Haiku::Button->new(
frame => [$left,$top,$left+1,$top+1],
name => "panel_button",
label => "Launch Panel",
message => Haiku::Message->new($PANEL_BUTTON),
);
$box->AddChild($panel_button);
$panel_button->ResizeToPreferred();
Python
bounds = self.Bounds()
left = buffer
top = self.install_button.Frame().bottom + buffer
right = bounds.right - buffer
bottom = bounds.bottom - buffer
box = Box(
frame = [left,top,right,bottom],
name = "panel_box",
resizingMode = B_FOLLOW_ALL,
)
self.AddChild(box)
box.SetLabel("File Panel")
box.SetViewColor([255,255,255])
# box to group panel options, with button to launch panel
buffer += 2
left = buffer
top = buffer + 15
self.open_radio = RadioButton(
frame = [left,top,left+1,top+1],
name = "open_radio",
label = 'Open Panel',
)
box.AddChild(self.open_radio)
self.open_radio.ResizeToPreferred()
self.open_radio.SetValue(1)
frame = self.open_radio.Frame()
left = frame.right + buffer
self.save_radio = RadioButton(
frame = [left,top,left+1,top+1],
name = "save_radio",
label = 'Save Panel',
)
box.AddChild(self.save_radio)
self.save_radio.ResizeToPreferred()
left = buffer
top = frame.bottom + buffer
self.file_flavor = CheckBox(
frame = [left,top,left+1,top+1],
name = "file_flavor",
label = 'Files',
)
box.AddChild(self.file_flavor)
self.file_flavor.ResizeToPreferred()
self.file_flavor.SetValue(1)
frame = self.file_flavor.Frame()
left = frame.right + buffer
self.directory_flavor = CheckBox(
frame = [left,top,left+1,top+1],
name = "directory_flavor",
label = 'Directories',
)
box.AddChild(self.directory_flavor)
self.directory_flavor.ResizeToPreferred()
frame = self.directory_flavor.Frame()
left = frame.right + buffer
self.symlink_flavor = CheckBox(
frame = [left,top,left+1,top+1],
name = "symlink_flavor",
label = 'Symlinks',
)
box.AddChild(self.symlink_flavor)
self.symlink_flavor.ResizeToPreferred()
left = buffer
top = frame.bottom + buffer
self.allow_multiple = CheckBox(
frame = [left,top,left+1,top+1],
name = "allow_multiple",
label = 'Allow Multiple Selections',
)
box.AddChild(self.allow_multiple)
self.allow_multiple.ResizeToPreferred()
frame = self.allow_multiple.Frame()
left = buffer
top = frame.bottom + buffer
self.file_type = TextControl(
frame = [left,top,left+1,top+1],
name = "file_type",
label = 'File Type',
text = 'text/plain',
resizingMode = B_FOLLOW_LEFT_RIGHT,
)
box.AddChild(self.file_type)
self.file_type.ResizeToPreferred()
frame = self.file_type.Frame()
width = box.Bounds().Width() - 2 * buffer
self.file_type.ResizeTo(width,frame.Height())
left = buffer
top = frame.bottom + buffer
panel_button = Button(
frame = [left,top,left+1,top+1],
name = "panel_button",
label = "Launch Panel",
message = Message(klass.PANEL_BUTTON),
)
box.AddChild(panel_button)
panel_button.ResizeToPreferred()
Finally, we create a Messenger to serve as the target for any FilePanel objects that we create.
Perl
$self->{target} = Haiku::Messenger->new($self);
return $self;
}
Python
self.panel = None
self.filter = None
self.target = Messenger(self)
return self
The MessageReceived hook responds to events from the Window's own buttons, but since the Window also serves as the target of the FilePanel's messages, it needs to respond to those as well.
Perl
sub MessageReceived {
my ($self, $message) = @_;
if ($message->what == $FILE_TYPE_BUTTON) {
$self->install_mime_type();
return;
}
if ($message->what == $LAUNCH_BUTTON) {
$roster->Launch("application/x-vnd.Haiku-FileTypes");
return;
}
if ($message->what == $PANEL_BUTTON) {
$self->show_file_panel();
return;
}
if ($message->what == B_REFS_RECEIVED) {
undef $self->{panel};
undef $self->{filter};
my $info = $message->GetInfo("refs");
my @files;
for my $i (0..$info->{count}-1) {
push @files, $message->FindRef("refs", $i);
}
$self->alert(join("\n", 'Would open the following files', @files));
return;
}
if ($message->what == B_SAVE_REQUESTED) {
undef $self->{panel};
undef $self->{filter};
my $directory = $message->FindRef("directory");
my $name = $message->FindString("name");
$self->alert("Would create '$name' in directory '$directory'");
return;
}
if ($message->what == B_CANCEL && $self->{panel}) {
undef $self->{panel};
undef $self->{filter};
$self->alert("Panel dismissed");
return;
}
$self->SUPER::MessageReceived($message);
}
Python
def MessageReceived(self, message):
if message.what == self.FILE_TYPE_BUTTON:
self.install_mime_type()
return
if message.what == self.LAUNCH_BUTTON:
self.roster.Launch("application/x-vnd.Haiku-FileTypes")
return
if message.what == self.PANEL_BUTTON:
self.show_file_panel()
return
if message.what == B_REFS_RECEIVED:
self.panel = None
self.filter = None
info = message.GetInfo("refs")
files = ['Would open the following files']
for i in range(0,info['count']):
files.append(message.FindRef("refs", i))
self.alert("\n".join(files))
return
if message.what == B_SAVE_REQUESTED:
self.panel = None
self.filter = None
directory = message.FindRef("directory")
name = message.FindString("name")
self.alert("Would create '" + name + "' in directory '" + directory + "'")
return
if message.what == B_CANCEL and self.panel:
self.panel = None
self.filter = None
self.alert("Panel dismissed")
return
super(FilesWindow, self).MessageReceived(message)
The MimeType class is used to update the system file types database. The function below (called from MessageReceived) will toggle the installed state of our new file type.
Perl
sub install_mime_type {
my ($self) = @_;
my $type = Haiku::MimeType->new("text/x.QuickText");
if ($type->IsInstalled()) {
$type->Delete();
$self->{install_button}->SetLabel("Install File Type");
return;
}
$type->SetShortDescription("QuickText File");
$type->SetLongDescription("QuickText File");
$type->SetIcon($icon_data);
$type->SetFileExtensions(['qtxt']);
$type->SetPreferredApp("application/x.vnd-hab.QuickText");
$type->SetPreferredApp("application/x-vnd.Haiku.StyledEdit");
$type->Install();
$self->{install_button}->SetLabel("Delete File Type");
}
Python
def install_mime_type(self):
type = MimeType("text/x.QuickText")
if type.IsInstalled():
type.Delete()
self.install_button.SetLabel("Install File Type")
return
type.SetShortDescription("QuickText File")
type.SetLongDescription("QuickText File")
type.SetIcon(self.icon_data)
type.SetFileExtensions(['qtxt'])
type.SetPreferredApp("application/x.vnd-hab.QuickText")
type.SetPreferredApp("application/x-vnd.Haiku.StyledEdit")
type.Install()
self.install_button.SetLabel("Delete File Type")
Of course, if you were installing a file type for your application to use, you would neither hook it to a button nor delete it if were already installed. You would simply check whether it were installed, and if not, install it, most likely before you even instantiated the Application or perhaps in the Application's ReadyToRun hook.
Haiku offers a built-in file panel for selecting files and directories. This function (again called from MessageReceived) launches a FilePanel built according to the values the user specifies. (If the user has specified a mime type, a filter will be created and passed to the FilePanel as well.)
Perl
sub show_file_panel {
my ($self) = @_;
if ($self->{panel}) {
$self->alert("Only one panel at a time may be shown");
return;
}
my $mode = $self->{open_radio}->Value() ? B_OPEN_PANEL : B_SAVE_PANEL;
my $flavor;
$self->{file_flavor}->Value() && ($flavor |= B_FILE_NODE);
$self->{directory_flavor}->Value() && ($flavor |= B_DIRECTORY_NODE);
$self->{symlink_flavor}->Value() && ($flavor |= B_SYMLINK_NODE);
my $allow_multiple = $self->{allow_multiple}->Value();
if (my $type = $self->{file_type}->Text()) {
$self->{filter} = TypeFilter->new($type);
}
$self->{panel} = Haiku::FilePanel->new(
mode => $mode,
target => $self->{target},
flavors => $flavor,
filter => $self->{filter},
modal => 0,
hideWhenDone => 1,
allowMultipleSelection => $allow_multiple,
);
$self->{panel}->Show();
}
Python
def show_file_panel(self):
if self.panel:
self.alert("Only one panel at a time may be shown")
return
mode = None
if self.open_radio.Value():
mode = B_OPEN_PANEL
else:
mode = B_SAVE_PANEL
flavor = 0
if self.file_flavor.Value():
flavor = flavor | B_FILE_NODE
if self.directory_flavor.Value():
flavor = flavor | B_DIRECTORY_NODE
if self.symlink_flavor.Value():
flavor = flavor | B_SYMLINK_NODE
allow_multiple = self.allow_multiple.Value()
type = self.file_type.Text()
if type:
self.filter = TypeFilter(type)
self.panel = FilePanel(
mode = mode,
target = self.target,
flavors = flavor,
filter = self.filter,
modal = 0,
hideWhenDone = 1,
allowMultipleSelection = allow_multiple,
)
self.panel.Show()
Again, in a real application the user would have no (or at least very little) control over the FilePanel options.
The last of the methods called from MessageReceived is used to show the user the results of FilePanel interaction.
Perl
sub alert {
my ($self, $message) = @_;
Haiku::Alert->new(
title => "Attention!",
text => $message,
buttons => ['Ok'],
)->Go();
}
Python
def alert(self, message):
Alert(
title = "Attention!",
text = message,
buttons = ['Ok'],
).Go()
Running the app
Finally we create our Application and Window and run them.