Documentation | Manual: Fonts
Chapter 6: Fonts
In this chapter, we are going to look at Fonts.
This program for this chapter is called FontViewer
.ext.
Imports
Starting with this chapter, we will no longer include the importing of necessary modules in the code blocks. (However, the sample programs will, of course, include the imports.)
FontViewerWindow
We will subclass Window to do the heavy lifting.
- We are going to do most of the work by passing messages around, so we need to set up our unique codes.
- We also put some sizing variables near the top, where they will be easy to change if we decide to do so later. (We will eventually begin using the Layout API to handle sizing, but for now we'll do it this way.)
- Finally, we add some storage for our font list. (We do it in the class instead of in an instance because we know we are only going to create a single instance of this class.
Perl
package FontViewerWindow; our $SET_FONT = code_to_int('setf'); our $NEXT_FONT = code_to_int('nxtf'); our $TOGGLE = code_to_int('tggl'); my @window_frame = (50,50,350,350); my $button_width = 75; my $button_height = 20; my $spacer = 5; my @Fonts;
Python
SET_FONT = code_to_int('setf') NEXT_FONT = code_to_int('nxtf') TOGGLE = code_to_int('tggl') class FontViewerWindow(Window): window_frame = [50,50,350,350] button_width = 75 button_height = 20 spacer = 5 fonts = []
Our subclass does not take any of the normal Window arguments; we pass our own values for those arguments to the base class.
It does, however, take one argument: the Application object, so we can use it later when responding to one of our messages.
Perl
sub new { my ($class, $app) = @_; my $self = $class->SUPER::new( frame => \@window_frame, title => "Font Viewer", type => B_TITLED_WINDOW, flags => B_QUIT_ON_WINDOW_CLOSE, ); $self->{app} = $app;
Python
def __new__(cls, app): self = super(FontViewerWindow, cls).__new__(cls, frame = cls.window_frame, title = "Font Viewer", type = B_TITLED_WINDOW, flags = B_QUIT_ON_WINDOW_CLOSE, ) self.app = app
Our application is going to cycle through all the available fonts. So we go through all the installed fonts, creating a Menu and storing enough information to instantiate each font later.
Perl
my $menu = Haiku::Menu->new("Font"); for my $font (@{ Haiku::Font::get_font_list() }) { my $fname = $font->{name}; my $fflags = $font->{flags}; my $submenu = Haiku::Menu->new($fname); $menu->AddItem($submenu); for my $style (@{ $font->{styles} }) { my $sname = $style->{name}; my $sflags = $style->{flags}; my $fentry = { family => $fname, style => $sname, flags => $fflags | $sflags, index => scalar(@Fonts), }; push @Fonts, $fentry; my $message = Haiku::Message->new($SET_FONT); $message->AddInt32("index" => $fentry->{index}); my $item = Haiku::MenuItem->new( label => $sname, message => $message, ); $submenu->AddItem($item); $fentry->{menuitem} = $item; } }
Python
menu = Menu("Font") for font in Font.get_font_list(): fname = font['name'] fflags = font['flags'] submenu = Menu(fname) menu.AddItem(submenu) for style in font['styles']: sname = style['name'] sflags = style['flags'] fentry = { 'family' : fname, 'style' : sname, 'flags' : fflags | sflags, 'index' : len(self.fonts), } self.fonts.append(fentry) message = Message(SET_FONT) message.AddInt32("index", fentry['index']) item = MenuItem( label = sname, message = message, ) submenu.AddItem(item) fentry['menuitem'] = item
Next we create a Button to allow the pausing and resuming of font cycling.
Perl
my @button_frame = ( $spacer, $spacer, ); $button_frame[2] = $button_frame[0]+$button_width; $button_frame[3] = $button_frame[1]+$button_height; $self->{button} = Haiku::Button->new( frame => \@button_frame, name => 'Pause', label => 'Pause', message => Haiku::Message->new($TOGGLE), ); $self->AddChild($self->{button}); # button may resize its height $button_height = $self->{button}->Bounds()->Height();
Python
button_frame = [ self.spacer, self.spacer, 0,0 ] button_frame[2] = button_frame[0]+self.button_width button_frame[3] = button_frame[1]+self.button_height self.button = Button( frame = button_frame, name = 'Pause', label = 'Pause', message = Message(TOGGLE), ) self.AddChild(self.button) # button may resize its height self.button_height = self.button.Bounds().Height()
We also want to the user to be able to diretly select a font, instead of waiting for the desired font to cycle around, so we add a MenuField to present the fonts, using the Menu we created earlier.
Perl
my @chooser_frame = ( $button_frame[2]+$spacer, $spacer, ); $chooser_frame[2] = $chooser_frame[0]+50; $chooser_frame[3] = $chooser_frame[1]+$button_height; $self->{chooser} = Haiku::MenuField->new( frame =>\@chooser_frame, name => 'FontChooser', label => 'Font', menu => $menu, ); $self->{chooser}->SetDivider(0); $self->AddChild($self->{chooser});
Python
chooser_frame = [ button_frame[2]+self.spacer, self.spacer, 0,0 ] chooser_frame[2] = chooser_frame[0]+50 chooser_frame[3] = chooser_frame[1]+self.button_height self.chooser = MenuField( frame =chooser_frame, name = 'FontChooser', label = 'Font', menu = menu, ) self.chooser.SetDivider(0) self.AddChild(self.chooser)
Finally, we create a TextView to display a sample string in the selected font.
Perl
my @fontview_frame = ( $spacer, $button_frame[3] + 2*$spacer, $window_frame[2] - $window_frame[0] - $spacer, $window_frame[3] - $window_frame[1] - $spacer, ); $self->{fontview} = Haiku::TextView->new( frame => \@fontview_frame, name => "FontView", textRect => [2, 2 ,$fontview_frame[2] - $fontview_frame[0] - 4, 0], resizingMode => B_FOLLOW_ALL, ); $self->{fontview}->SetText(text => "The quick brown fox jumped over the lazy dog"); $self->{fontview}->SetStylable(0); my $scrollview = Haiku::ScrollView->new( name => "FontView", target => $self->{fontview}, resizingMode => B_FOLLOW_ALL, ); $self->AddChild($scrollview); return $self; }
Python
fontview_frame = [ self.spacer, button_frame[3] + 2*self.spacer, self.window_frame[2] - self.window_frame[0] - self.spacer, self.window_frame[3] - self.window_frame[1] - self.spacer, ] self.fontview = TextView( frame = fontview_frame, name = "FontView", textRect = [2, 2 ,fontview_frame[2] - fontview_frame[0] - 4, 0], resizingMode = B_FOLLOW_ALL, ) self.fontview.SetText(text = "The quick brown fox jumped over the lazy dog") self.fontview.SetStylable(0) scrollview = ScrollView( name = "FontView", target = self.fontview, resizingMode = B_FOLLOW_ALL, ) self.AddChild(scrollview) self.current_font = -1; return self
Our Button and our MenuItems have been constructed to send messages to the
Window. We need to override MessageReceived
to handle those messages.
Perl
sub MessageReceived { my ($self, $message) = @_; my $what = $message->what; if ($what == $NEXT_FONT) { my $new_index = 0; if ($self->{current_font} != $#Fonts) { $new_index = $self->{current_font}+1; } $self->set_font($new_index); return; } if ($what == $SET_FONT) { my $new_index = $message->FindInt32("index"); $self->set_font($new_index); return; } if ($what == $TOGGLE) { $self->{app}->toggle_cycling(); if ($self->{app}->is_cycling()) { $self->{button}->SetLabel('Pause'); } else { $self->{button}->SetLabel('Resume'); } return; } $self->SUPER::MessageReceived($message); }
Python
def MessageReceived(self, message): what = message.what if what == NEXT_FONT: new_index = 0 if self.current_font != len(self.fonts)-1: new_index = self.current_font+1 self.set_font(new_index) return if what == SET_FONT: new_index = message.FindInt32("index") self.set_font(new_index) return if what == TOGGLE: self.app.toggle_cycling() if self.app.is_cycling(): self.button.SetLabel('Pause') else: self.button.SetLabel('Resume') return super(FontViewerWindow, self).MessageReceived(message)
We encapsulate the code that actually updates the font into it own function.
Perl
sub set_font { my ($self, $index) = @_; # uncheck old font's menu item $Fonts[ $self->{current_font} ]{menuitem}->SetMarked(0); $self->{current_font} = $index; my $family = $Fonts[$index]{family}; my $style = $Fonts[$index]{style}; my $item = $Fonts[$index]{menuitem}; my $font = Haiku::Font->new(); $font->SetFamilyAndStyle($family, $style); $self->{fontview}->SetFontAndColor(0, 100, $font); $self->SetTitle("FontViewer - $family $style"); $item->SetMarked(1); }
Python
def set_font(self, index): # uncheck old font's menu item self.fonts[ self.current_font ]['menuitem'].SetMarked(0) self.current_font = index family = self.fonts[index]['family'] style = self.fonts[index]['style'] item = self.fonts[index]['menuitem'] font = Font() font.SetFamilyAndStyle(family, style) self.fontview.SetFontAndColor(0, 100, font) self.SetTitle("FontViewer - " + family + " " + style) item.SetMarked(1)
FontViewerApp
There is one more piece left. In MessageReceived
, we responded to a
message that we had not sent, and we called custom functions on the
Application object. Now we will create our Application subclass to implement
the missing message and functions.
Perl
package FontViewerApp; use parent 'Haiku::Application'; my $pulse_rate = 1000000; sub new { my ($class) = @_; my $self = $class->SUPER::new("application/x.vnd-hab.perl.FontViewer"); $self->{window} = FontViewerWindow->new($self); $self->{window}->Show(); $self->toggle_cycling(); return $self; }
Python
class FontViewerApp(Application): pulse_rate = 1000000 def __new__(cls): self = super(FontViewerApp, cls).__new__(cls, "application/x.vnd-hab.python.FontViewer") self.window = FontViewerWindow(self) self.window.Show() self.cycling = 0 self.toggle_cycling() return self
The meat of our Application subclass is the font cycling. The actual updating
is done via the Pulse
method, and pausing/resuming is handled in the custom
functions.
Since NEXT_FONT
doesn't require any data, we can use the shortcut version
of PostMessage
, which takes the message code instead a Message object.
(If Window had its own Pulse
method, we could have handled this all in our
Window subclass, but since it does not, in order to get a regular repeated
message like this, we either have to create a MessageRunner or subclass
Application or one of the View classes.)
Perl
sub Pulse { my ($self) = @_; $self->{window}->PostMessage($FontViewerWindow::NEXT_FONT); } sub toggle_cycling { my ($self) = @_; $self->{is_cycling} = $self->{is_cycling} ? 0 : 1; if ($self->{is_cycling}) { $self->SetPulseRate($pulse_rate); } else { $self->SetPulseRate(0); } } sub is_cycling { return $_[0]->{is_cycling}; }
Python
def Pulse(self): self.window.PostMessage(NEXT_FONT) def toggle_cycling(self): if self.cycling: self.cycling = 0 self.SetPulseRate(0) else: self.cycling = 1 self.SetPulseRate(self.pulse_rate) def is_cycling(self): return self.cycling
Running the app
Since we handled creating and showing the window in our Application subclass, there is not much left to do.