Haiku API Bindings
Files
Not logged in

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.