1 # -*- mode: cperl; cperl-indent-level: 4; indent-tabs-mode: nil -*-
6 package BarnOwl::Module::ColorUtils;
10 BarnOwl::Module::ColorUtils
14 This module implements easy to use color suppot for barnowl.
20 ################################################################################
21 #Run this on start and reload. Adds styles, sets style to start.
22 ################################################################################
24 my $config_dir = BarnOwl::get_config_dir();
27 @colorList = ('black','red','green','yellow',
28 'blue','magenta','cyan','white');
29 if ( *BarnOwl::getnumcolors{CODE} ) {
30 for (my $i = 8; $i < BarnOwl::getnumcolors(); $i++) {
38 $BarnOwl::Hooks::startup->add(\&onStart);
40 ################################################################################
41 #Register BarnOwl commands and default keybindings.
42 ################################################################################
47 setcolor => \&cmd_setcolor,
49 summary => "Change the color for this sender (personals) or class/muc (or instance if zephyr -c message)",
50 usage => "setcolor [-i] [-b] <color>",
51 description => "Sets the foreground (or background) color for this kind of message.\n\n"
52 . "The following options are available:\n\n"
53 . " -i Set the color for this instance of this zephyr class.\n\n"
54 . " -b Sets the background color instead of the foreground color.\n\n"
55 . "color may be any of the colors listed in `:show color'; if using a 256-color\n"
56 . "terminal, you may also use an HTML-style color code, #rrggbb, which will\n"
57 . "be matched to the closest color approximation in a 6x6x6 colorcube.\n"
58 . "The following special values are also allowed:\n"
59 . " default uncolors the message\n"
60 . " restore restores the last saved color for this message\n"
64 loadcolors => \&cmd_load,
66 summary => "Load color filter definitions from disk.",
71 savecolors => \&cmd_save,
73 summary => "Save active color filter definitions to disk.",
79 owl::command('bindkey recv "c" command start-command setcolor ');
83 ################################################################################
85 ################################################################################
86 our %currentColorMap = ();
87 our %savedColorMap = ();
88 our %mode2Protocol = ('zephyr' => 'zephyr',
89 'zephyr-personal' => 'zephyr',
93 'loopback' => 'loopback');
95 ################################################################################
97 ################################################################################
98 sub createFilters($) {
99 # Prepare the color filters.
101 return unless (grep(/^[fb]g$/, $fgbg));
103 my %workingColorMap = %{ $currentColorMap{$fgbg} };
105 foreach my $color (@colorList) {
108 #######################################################################
114 foreach my $c (sort keys %{ $workingColorMap{$mode} }) {
116 $c_esc =~ s/([+*])/\\$1/g;
117 my @instances = (sort keys %{ $workingColorMap{$mode}{$c} });
118 my $cHasStar = grep(/\*/, @instances);
120 if ($cHasStar && @instances == 1) {
121 # Collect classes that are only globally colored.
122 push(@class, $c_esc) if ($workingColorMap{$mode}{$c}{'*'} eq $color);
124 # Collect classes that have varying color for instances.
125 if ($cHasStar && $workingColorMap{$mode}{$c}{'*'} eq $color) {
127 foreach my $i (@instances) {
128 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} eq $color));
129 $i =~ s/([+*])/\\$1/g;
130 push(@cInstances, $i);
132 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and not instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
135 foreach my $i (@instances) {
136 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} ne $color));
137 $i =~ s/([+*])/\\$1/g;
138 push(@cInstances, $i);
140 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
145 # Join the collected classes into one big filter.
146 if (scalar(@class) || scalar(@classInst)) {
148 '( type ^'.$mode2Protocol{$mode}.'$ and ( '
149 . ((scalar(@class)) ? 'class ^('.join('|',@class).')(.d)*$ ' : '')
150 . ((scalar(@class) && scalar(@classInst)) ? 'or ' : '')
151 . ((scalar(@classInst)) ? '( '.join(' ) or ( ', @classInst).' ) ' : '')
155 #######################################################################
156 $mode = 'zephyr-personal';
160 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
161 next if ($workingColorMap{$mode}{$sender} ne $color);
162 $sender =~ s/([+*])/\\$1/g;
164 $senders .= ($senders eq "") ? $sender : "|$sender";
168 '( type ^'.$mode2Protocol{$mode}.'$ and ( ( class ^message$ and instance ^personal$ ) or class ^login$ )'
169 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
172 #######################################################################
177 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
178 next if ($workingColorMap{$mode}{$sender} ne $color);
179 $sender =~ s/([+*])/\\$1/g;
181 $senders .= ($senders eq "") ? $sender : "|$sender";
185 '( type ^'.$mode2Protocol{$mode}.'$'
186 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
189 #######################################################################
194 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
195 next if ($workingColorMap{$mode}{$sender} ne $color);
196 $sender =~ s/([+*])/\\$1/g;
198 $senders .= ($senders eq "") ? $sender : "|$sender";
202 '( type ^'.$mode2Protocol{$mode}.'$'
203 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
206 #######################################################################
209 push(@strs, '( type ^'.$mode2Protocol{$mode}.'$ )') if (($workingColorMap{$mode} || '') eq $color);
211 #######################################################################
213 my $filter = 'ColorUtils::'.$color.(($fgbg eq 'bg') ? '-bg' : '');
214 my $filterspec = "$filter ".(($fgbg eq 'bg') ? '-b' : '-c')." $color ";
216 BarnOwl::filter("$filterspec ( "
217 . join(' or ', @strs)
220 next if (owl::getview() eq $filter);
221 BarnOwl::_remove_filter($filter);
228 return 0 if ($c < 26);
229 return 1 if ($c < 77);
230 return 2 if ($c < 128);
231 return 3 if ($c < 179);
232 return 4 if ($c < 230);
236 sub find_color($$$) {
237 my $r = normalize_rgb(shift);
238 my $g = normalize_rgb(shift);
239 my $b = normalize_rgb(shift);
240 return 16 + (36 * $r) + (6 * $g) + $b;
247 shift; #strip setcolor from argument list.
250 'backgroud' => \$fgbg,
251 'instance' => \$inst,
254 return unless ((scalar @ARGV) > 0);
255 my $color = shift @ARGV;
257 if ($color =~ /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) {
258 $color = find_color(hex($1),hex($2),hex($3));
260 if ($color eq 'default') {
262 } elsif ($color eq 'restore') {
263 restore($inst, $fgbg);
265 die("setcolor: invalid color ($color)\n") unless grep(/$color/,@colorList);
266 setColor($color, $inst, $fgbg);
283 ################################################################################
284 ## Color toggling functions
285 ################################################################################
287 # Return 1 for things that would qualify a zephyr as personal.
289 return 1 if ($m->recipient ne "" and $m->recipient !~ /^@/);
290 return 1 if lc($m->class) eq "login";
296 my $fgbg = (shift || 0) ? 'bg' : 'fg';
297 my $m = owl::getcurmsg();
299 my $type = lc($m->type);
300 if ($type eq 'zephyr') {
301 if (isZPersonal($m)) {
302 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
303 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
305 my $class = lc($m->class);
306 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
308 if ($instance eq '*') {
309 $currentColorMap{$fgbg}{$type}{$class}{$instance} = 'default';
311 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
314 } elsif ($type eq 'aim' || $type eq 'jabber') {
315 my $sender = (lc($m->direction) eq 'in') ? $m->sender : $m->recipient;
316 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
318 delete $currentColorMap{$fgbg}{$type}{$sender};
319 } elsif ($type eq 'loopback') {
320 delete $currentColorMap{$fgbg}{$type};
329 my $fgbg = (shift || 0) ? 'bg' : 'fg';
330 my $m = owl::getcurmsg();
333 my $type = lc($m->type);
334 if ($type eq 'zephyr') {
335 if (isZPersonal($m)) {
336 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
337 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $color;
339 my $class = lc($m->class);
340 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
341 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $color;
343 } elsif ($type eq 'aim' || $type eq 'jabber') {
344 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
345 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
346 $currentColorMap{$fgbg}{$type}{$sender} = $color;
347 } elsif ($type eq 'loopback') {
348 $currentColorMap{$fgbg}{$type} = $color;
356 my $fgbg = (shift || 0) ? 'bg' : 'fg';
357 my $m = owl::getcurmsg();
359 my $type = lc($m->type);
362 if ($type eq 'zephyr') {
363 if (isZPersonal($m)) {
364 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
365 if ($oldColor = ($savedColorMap{$fgbg}{'zephyr-personal'}{$sender}) || '') {
366 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $oldColor;
368 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
371 my $class = lc($m->class);
372 my $instance = lc($m->instance);
375 || ($class eq 'message')
376 || ((($savedColorMap{$fgbg}{$type}{$class}{'*'} || '') eq ($currentColorMap{$fgbg}{$type}{$class}{'*'} || ''))
377 && (($savedColorMap{$fgbg}{$type}{$class}{$instance} || '') ne ($currentColorMap{$fgbg}{$type}{$class}{$instance} || ''))))
381 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$class}{$instance} || '')) {
382 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $oldColor;
384 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
387 } elsif ($type eq 'aim' || $type eq 'jabber') {
388 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
389 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
390 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$sender} || '')) {
391 $currentColorMap{$fgbg}{$type}{$sender} = $oldColor;
393 delete $currentColorMap{$fgbg}{$type}{$sender};
395 } elsif ($type eq 'loopback') {
397 if ($oldColor = ($savedColorMap{$fgbg}{$type} || '')) {
398 $currentColorMap{$fgbg}{$type} = $oldColor;
400 delete $currentColorMap{$fgbg}{$type};
409 return unless (grep(/^[fb]g$/, $fgbg));
411 createFilters($fgbg);
412 if ( *BarnOwl::refresh_view{CODE} ) {
413 BarnOwl::refresh_view();
415 my $filter = owl::command("getview");
416 my $style = owl::command("getstyle");
417 owl::command("view -f $filter ".($style?"-s $style":""));
421 ################################################################################
422 ## Saving/Loading functions
423 ################################################################################
426 return unless (grep(/^[fb]g$/, $fgbg));
429 open(COLORS, ">$config_dir/colormap_bg");
431 open(COLORS, ">$config_dir/colormap");
435 print COLORS "MODE: $type\n";
436 foreach my $c (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
437 foreach my $i (sort keys %{ $currentColorMap{$fgbg}{$type}{$c} }) {
439 || !($currentColorMap{$fgbg}{$type}{$c}{$i} eq ($currentColorMap{$fgbg}{$type}{$c}{'*'} || '')
440 || !$currentColorMap{$fgbg}{$type}{$c}{$i})) {
441 print COLORS "$c,$i,".($currentColorMap{$fgbg}{$type}{$c}{$i} ? $currentColorMap{$fgbg}{$type}{$c}{$i} : 'default')."\n";
446 $type = 'zephyr-personal';
447 print COLORS "MODE: $type\n";
448 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
449 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
453 print COLORS "MODE: $type\n";
454 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
455 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
459 print COLORS "MODE: $type\n";
460 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
461 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
465 print COLORS "MODE: $type\n";
466 print COLORS ($currentColorMap{$fgbg}{$type} ? $currentColorMap{$fgbg}{$type} : 'default')."\n";
474 return unless (grep(/^[fb]g$/, $fgbg));
476 $currentColorMap{$fgbg} = {};
477 $savedColorMap{$fgbg} = {};
479 # Parse the color file.
481 open(COLORS, "<$config_dir/colormap_bg") || return;
484 open(COLORS, "<$config_dir/colormap") || return;
490 foreach my $line (<COLORS>) {
492 if ($line =~ /^MODE: (.*)$/) {
493 if (lc($1) eq "zephyr") {
495 } elsif (lc($1) eq "zephyr-personal") {
496 $mode = 'zephyr-personal';
497 } elsif (lc($1) eq "aim") {
499 } elsif (lc($1) eq "jabber") {
501 } elsif (lc($1) eq "loopback") {
506 } elsif ($mode eq 'zephyr' && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
507 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
508 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
509 } elsif ($mode eq 'zephyr-personal' && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
510 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
511 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
512 } elsif (($mode eq 'aim' || $mode eq 'jabber') && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
513 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
514 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
515 } elsif ($mode eq 'loopback' && $line =~ /^(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
516 $currentColorMap{$fgbg}{$mode} = lc($2);
517 $savedColorMap{$fgbg}{$mode} = lc($2);