1
package Padre::Wx::Theme;
10
use Padre::Constant ();
13
use Padre::Wx::Style ();
16
our $VERSION = '0.92';
18
# Locate the directories containing styles
20
CORE_DIRECTORY => Padre::Util::sharedir('themes'),
21
USER_DIRECTORY => File::Spec->catdir(
22
Padre::Constant::CONFIG_DIR,
31
######################################################################
34
# Commands allowed in the style
36
name => [ 2, 'name' ],
37
gui => [ 1, 'class' ],
38
style => [ 1, 'mime' ],
39
include => [ 1, 'mime' ],
40
SetForegroundColour => [ 1, 'color' ],
41
SetBackgroundColour => [ 1, 'color' ],
42
SetCaretLineBackground => [ 1, 'color' ],
43
SetCaretForeground => [ 1, 'color' ],
44
CallTipSetBackground => [ 1, 'color' ],
45
SetWhitespaceBackground => [ 2, 'boolean,color' ],
46
SetWhitespaceForeground => [ 2, 'boolean,color' ],
47
SetSelBackground => [ 2, 'style,color' ],
48
SetSelForeground => [ 1, 'style,color' ],
49
StyleAllBackground => [ 1, 'color' ],
50
StyleAllForeground => [ 1, 'color' ],
51
StyleSetBackground => [ 2, 'style,color' ],
52
StyleSetForeground => [ 2, 'style,color' ],
53
StyleSetBold => [ 2, 'style,boolean' ],
54
StyleSetItalic => [ 2, 'style,boolean' ],
55
StyleSetEOLFilled => [ 2, 'style,boolean' ],
56
StyleSetUnderline => [ 2, 'style,boolean' ],
57
StyleSetSpec => [ 2, 'style,spec' ],
58
SetFoldMarginColour => [ 2, 'boolean,color' ],
59
SetFoldMarginHiColour => [ 2, 'boolean,color' ],
60
MarkerSetForeground => [ 2, 'style,color' ],
61
MarkerSetBackground => [ 2, 'style,color' ],
64
# Fallback path of next best styles if no style exists.
65
# The fallback of last resort is automatically to text/plain
67
'application/x-psgi' => 'application/x-perl',
68
'application/x-php' => 'application/perl', # Temporary solution
69
'application/json' => 'application/javascript',
70
'application/javascript' => 'text/x-c',
71
'text/x-java-source' => 'text/x-c',
72
'text/x-c++src' => 'text/x-c',
73
'text/x-csharp' => 'text/x-c',
80
######################################################################
87
# Scan style directories
88
foreach my $directory ( USER_DIRECTORY, CORE_DIRECTORY ) {
89
next unless -d $directory;
91
# Search the directory
93
unless ( opendir( STYLEDIR, $directory ) ) {
94
die "Failed to read '$directory'";
96
foreach my $file ( readdir STYLEDIR ) {
97
next unless $file =~ s/\.txt\z//;
98
next unless Params::Util::_IDENTIFIER($file);
99
next if $styles{$file};
100
$styles{$file} = File::Spec->catfile(
111
# Get the file name for a named style
115
foreach my $directory ( USER_DIRECTORY, CORE_DIRECTORY ) {
116
my $file = File::Spec->catfile(
120
return $file if -f $file;
128
my $files = $class->files;
130
# Load the label for each file.
131
# Because we resolve the filename again this is slower than
132
# it could be, but the code is simple and easy and will do for now.
134
foreach my $name ( keys %$files ) {
135
$labels{$name} = $class->label( $name, $locale );
145
my $file = $class->file($name);
147
die "The style '$name' does not exist";
150
# Parse the file for name statements
153
my $handle = IO::File->new( $file, 'r' ) or return;
154
while ( defined( my $string = <$handle> ) ) {
158
$string =~ s/^\s*//s;
159
$string =~ s/\s*\z//s;
161
# Skip blanks and comments
162
next unless $string =~ /^\s*[^#]/;
164
# Split the line into a command and params
165
my @list = split /\s+/, $string;
166
my $cmd = shift @list;
168
# We only care about name
169
next unless defined $cmd;
170
last unless $cmd eq 'name';
173
my $lang = shift @list;
174
$label{$lang} = join ' ', @list;
178
# Try to find a usable label
179
return $label{$locale} || $label{'en-gb'} || $name;
183
$_[0]->labels('en-gb');
189
my $file = $class->file($name);
191
die "The style '$name' does not exist";
193
return $class->load($file);
200
######################################################################
201
# Constructor and Accessors
205
my $self = bless { @_, code => {} }, $class;
206
unless ( defined $self->name ) {
207
die "No default en-gb name for style";
209
unless ( defined $self->mime ) {
210
die "No default text/plain style";
219
unless ( -f $file ) {
220
die "Missing or invalid style file '$file'";
224
my $handle = IO::File->new( $file, 'r' ) or return;
225
my $self = $class->parse($handle);
233
my $lang = shift || 'en-gb';
234
return $self->{name}->{$lang};
239
my $mime = shift || 'text/plain';
240
while ( not $self->{mime}->{$mime} ) {
241
if ( $mime eq 'text/plain' ) {
243
# A null seqeunce... I guess...
246
$mime = $FALLBACK{$mime} || 'text/plain';
249
return $self->{mime}->{$mime};
256
######################################################################
261
my $handle = Params::Util::_HANDLE(shift) or die "Not a file handle";
263
# Load the delayed modules
265
require Padre::Locale;
272
while ( defined( my $string = <$handle> ) ) {
276
$string =~ s/^\s*//s;
277
$string =~ s/\s*\z//s;
279
# Skip blanks and comments
280
next unless $string =~ /^\s*[^#]/;
282
# Split the line into a command and params
283
my @list = split /\s+/, $string;
284
my $cmd = shift @list;
285
unless ( defined $PARAM{$cmd} ) {
286
die "Line $line: Unsupported style command '$string'";
288
unless ( @list >= $PARAM{$cmd}->[0] ) {
289
die "Line $line: Insufficient parameters in command '$string'";
292
# Handle special commands
293
if ( $cmd eq 'name' ) {
295
# Does the language exist
296
my $lang = shift @list;
297
unless ( Padre::Locale::rfc4646_exists($lang) ) {
298
die "Line $line: Unknown language in command '$string'";
302
$name{$lang} = join ' ', @list;
304
} elsif ( $cmd eq 'style' or $cmd eq 'gui' ) {
306
# Switch to the new mime type
307
$style = $styles{ $list[0] } = Padre::Wx::Style->new;
309
} elsif ( $cmd eq 'include' ) {
311
# Copy another style as a starting point
312
my $copy = $styles{ $list[0] };
314
die "Line $line: Style '$list[0]' is not defined (yet)";
316
$style->include($copy);
318
} elsif ( $PARAM{$cmd}->[1] eq 'color' ) {
320
# General commands that are passed a single colour
321
my $color = Padre::Wx::color( shift @list );
322
$style->add( $cmd => [ $color ] );
324
} elsif ( $PARAM{$cmd}->[1] eq 'style,color' ) {
326
# Style specific commands that are passed a single color
327
my $id = $class->parse_style( $line, shift @list );
328
my $color = Padre::Wx::color( shift @list );
329
$style->add( $cmd => [ $id, $color ] );
331
} elsif ( $PARAM{$cmd}->[1] eq 'style,boolean' ) {
333
# Style specific commands that are passed a boolean value
334
my $id = $class->parse_style( $line, shift @list );
335
my $boolean = $class->parse_boolean( $line, shift @list );
336
$style->add( $cmd => [ $id, $boolean ] );
338
} elsif ( $PARAM{$cmd}->[1] eq 'style,spec' ) {
340
# Style command that is passed a spec string
341
my $style = $class->parse_style( $line, shift @list );
342
my $spec = shift @list;
344
} elsif ( $PARAM{$cmd}->[1] eq 'boolean,color' ) {
345
my $boolean = $class->parse_boolean( $line, shift @list );
346
my $color = Padre::Wx::color( shift @list );
347
$style->add( $cmd => [ $boolean, $color ] );
350
die "Line $line: Unsupported style command '$string'";
365
if ( defined Params::Util::_NONNEGINT($string) ) {
367
} elsif ( $string =~ /^PADRE_\w+\z/ ) {
368
unless ( Padre::Constant->can($string) ) {
369
die "Line $line: Unknown or unsupported style '$copy'";
371
$string = "Padre::Constant::$string";
372
} elsif ( $string =~ /^\w+\z/ ) {
373
unless ( Wx::Scintilla->can($string) ) {
374
die "Line $line: Unknown or unsupported style '$copy'";
376
$string = "Wx::Scintilla::$string";
378
die "Line $line: Unknown or unsupported style '$copy'";
381
# Capture the numeric form of the constant
383
$string = eval { $string->() };
385
die "Line $line: Unknown or unsupported style '$copy'";
395
unless ( $string eq '0' or $string eq '1' ) {
396
die "Line $line: Boolean value '$string' is not 0 or 1";
405
######################################################################
406
# Compilation and Application
412
# Clear any previous style
413
$self->clear($object);
415
if ( Params::Util::_INSTANCE( $object, 'Padre::Wx::Editor' ) ) {
416
# This is an editor style
417
my $document = $object->{Document} or return;
418
my $mimetype = $document->mimetype or return;
419
$self->mime($mimetype)->apply($object);
422
# This is a GUI style, chase the inheritance tree.
423
# Uses inlined Class::ISA algorithm as in Class::Inspector
424
my $class = Scalar::Util::blessed($object);
425
my @queue = ( $class );
426
my %seen = ( $class => 1 );
427
while ( my $package = shift @queue ) {
429
unshift @queue, grep { ! $seen{$_}++ }
430
map { s/^::/main::/; s/\'/::/g; $_ }
431
( @{"${package}::ISA"} );
433
# Apply the first style that patches
434
my $style = $self->{mime}->{$package} or next;
435
$style->apply($object);
447
if ( Params::Util::_INSTANCE( $object, 'Padre::Wx::Editor' ) ) {
449
# Clears settings back to the editor configuration defaults
450
# To do this we flush absolutely everything and then apply
451
# the basic font settings.
452
$object->StyleResetDefault;
454
# Reset the font from configuration (which Scintilla considers part of
455
# the "style" but Padre doesn't allow to be changed as a "style")
457
my $config = $object->config;
458
my $font = Padre::Wx::editor_font( $config->editor_font );
459
$object->SetFont($font);
460
$object->StyleSetFont( Wx::Scintilla::STYLE_DEFAULT, $font );
462
# Clear all styles back to the default
463
$object->StyleClearAll;
466
# Reset the GUI element colours back to defaults
467
### Disabled as it blacks the directory tree for some reason
468
# $object->SetForegroundColour( Wx::NullColour );
469
# $object->SetBackgroundColour( Wx::NullColour );
477
# Copyright 2008-2011 The Padre development team as listed in Padre.pm.
479
# This program is free software; you can redistribute it and/or
480
# modify it under the same terms as Perl 5 itself.