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.