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 ################################################################################
25 @colorList = ('black','red','green','yellow',
26 'blue','magenta','cyan','white');
27 if ( *BarnOwl::getnumcolors{CODE} ) {
28 for (my $i = 8; $i < BarnOwl::getnumcolors(); $i++) {
36 $BarnOwl::Hooks::startup->add(\&onStart);
38 ################################################################################
39 #Register BarnOwl commands and default keybindings.
40 ################################################################################
45 setcolor => \&cmd_setcolor,
47 summary => "Change the color for this sender (personals) or class/muc (or instance if zephyr -c message)",
48 usage => "setcolor [-i] [-b] <color>",
49 description => "Sets the foreground (or background) color for this kind of message.\n\n"
50 . "The following options are available:\n\n"
51 . " -i Set the color for this instance of this zephyr class.\n\n"
52 . " -b Sets the background color instead of the foreground color.\n\n"
53 . "color may be any of the colors listed in `:show color'; if using a 256-color\n"
54 . "terminal, you may also use an HTML-style color code, #rrggbb, which will\n"
55 . "be matched to the closest color approximation in a 6x6x6 colorcube.\n"
56 . "The following special values are also allowed:\n"
57 . " default uncolors the message\n"
58 . " restore restores the last saved color for this message\n"
62 loadcolors => \&cmd_load,
64 summary => "Load color filter definitions from disk.",
69 savecolors => \&cmd_save,
71 summary => "Save active color filter definitions to disk.",
77 owl::command('bindkey recv "c" command start-command setcolor ');
81 ################################################################################
83 ################################################################################
84 our %currentColorMap = ();
85 our %savedColorMap = ();
86 our %mode2Protocol = ('zephyr' => 'zephyr',
87 'zephyr-personal' => 'zephyr',
91 'loopback' => 'loopback');
93 ################################################################################
95 ################################################################################
96 sub createFilters($) {
97 # Prepare the color filters.
99 return unless (grep(/^[fb]g$/, $fgbg));
101 my %workingColorMap = %{ $currentColorMap{$fgbg} };
103 foreach my $color (@colorList) {
106 #######################################################################
112 foreach my $c (sort keys %{ $workingColorMap{$mode} }) {
114 $c_esc =~ s/([+*])/\\$1/g;
115 my @instances = (sort keys %{ $workingColorMap{$mode}{$c} });
116 my $cHasStar = grep(/\*/, @instances);
118 if ($cHasStar && @instances == 1) {
119 # Collect classes that are only globally colored.
120 push(@class, $c_esc) if ($workingColorMap{$mode}{$c}{'*'} eq $color);
122 # Collect classes that have varying color for instances.
123 if ($cHasStar && $workingColorMap{$mode}{$c}{'*'} eq $color) {
125 foreach my $i (@instances) {
126 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} eq $color));
127 $i =~ s/([+*])/\\$1/g;
128 push(@cInstances, $i);
130 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and not instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
133 foreach my $i (@instances) {
134 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} ne $color));
135 $i =~ s/([+*])/\\$1/g;
136 push(@cInstances, $i);
138 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
143 # Join the collected classes into one big filter.
144 if (scalar(@class) || scalar(@classInst)) {
146 '( type ^'.$mode2Protocol{$mode}.'$ and ( '
147 . ((scalar(@class)) ? 'class ^('.join('|',@class).')(.d)*$ ' : '')
148 . ((scalar(@class) && scalar(@classInst)) ? 'or ' : '')
149 . ((scalar(@classInst)) ? '( '.join(' ) or ( ', @classInst).' ) ' : '')
153 #######################################################################
154 $mode = 'zephyr-personal';
158 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
159 next if ($workingColorMap{$mode}{$sender} ne $color);
160 $sender =~ s/([+*])/\\$1/g;
162 $senders .= ($senders eq "") ? $sender : "|$sender";
166 '( type ^'.$mode2Protocol{$mode}.'$ and ( ( class ^message$ and instance ^personal$ ) or class ^login$ )'
167 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
170 #######################################################################
175 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
176 next if ($workingColorMap{$mode}{$sender} ne $color);
177 $sender =~ s/([+*])/\\$1/g;
179 $senders .= ($senders eq "") ? $sender : "|$sender";
183 '( type ^'.$mode2Protocol{$mode}.'$'
184 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
187 #######################################################################
192 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
193 next if ($workingColorMap{$mode}{$sender} ne $color);
194 $sender =~ s/([+*])/\\$1/g;
196 $senders .= ($senders eq "") ? $sender : "|$sender";
200 '( type ^'.$mode2Protocol{$mode}.'$'
201 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
204 #######################################################################
207 push(@strs, '( type ^'.$mode2Protocol{$mode}.'$ )') if (($workingColorMap{$mode} || '') eq $color);
209 #######################################################################
211 my $filter = 'ColorUtils::'.$color.(($fgbg eq 'bg') ? '-bg' : '');
212 my $filterspec = "$filter ".(($fgbg eq 'bg') ? '-b' : '-c')." $color ";
214 BarnOwl::filter("$filterspec ( "
215 . join(' or ', @strs)
218 next if (owl::getview() eq $filter);
219 BarnOwl::_remove_filter($filter);
226 return 0 if ($c < 26);
227 return 1 if ($c < 77);
228 return 2 if ($c < 128);
229 return 3 if ($c < 179);
230 return 4 if ($c < 230);
234 sub find_color($$$) {
235 my $r = normalize_rgb(shift);
236 my $g = normalize_rgb(shift);
237 my $b = normalize_rgb(shift);
238 return 16 + (36 * $r) + (6 * $g) + $b;
245 shift; #strip setcolor from argument list.
248 'backgroud' => \$fgbg,
249 'instance' => \$inst,
252 my $color = shift @ARGV;
254 if ($color =~ /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) {
255 $color = find_color(hex($1),hex($2),hex($3));
257 if ($color eq 'default') {
259 } elsif ($color eq 'restore') {
260 restore($inst, $fgbg);
262 die("setcolor: invalid color ($color)\n") unless grep(/$color/,@colorList);
263 setColor($color, $inst, $fgbg);
280 ################################################################################
281 ## Color toggling functions
282 ################################################################################
284 # Return 1 for things that would qualify a zephyr as personal.
286 return 1 if ($m->recipient ne "" and $m->recipient !~ /^@/);
287 return 1 if lc($m->class) eq "login";
293 my $fgbg = (shift || 0) ? 'bg' : 'fg';
294 my $m = owl::getcurmsg();
296 my $type = lc($m->type);
297 if ($type eq 'zephyr') {
298 if (isZPersonal($m)) {
299 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
300 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
302 my $class = lc($m->class);
303 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
305 if ($instance eq '*') {
306 $currentColorMap{$fgbg}{$type}{$class}{$instance} = 'default';
308 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
311 } elsif ($type eq 'aim' || $type eq 'jabber') {
312 my $sender = (lc($m->direction) eq 'in') ? $m->sender : $m->recipient;
313 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
315 delete $currentColorMap{$fgbg}{$type}{$sender};
316 } elsif ($type eq 'loopback') {
317 delete $currentColorMap{$fgbg}{$type};
326 my $fgbg = (shift || 0) ? 'bg' : 'fg';
327 my $m = owl::getcurmsg();
330 my $type = lc($m->type);
331 if ($type eq 'zephyr') {
332 if (isZPersonal($m)) {
333 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
334 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $color;
336 my $class = lc($m->class);
337 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
338 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $color;
340 } elsif ($type eq 'aim' || $type eq 'jabber') {
341 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
342 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
343 $currentColorMap{$fgbg}{$type}{$sender} = $color;
344 } elsif ($type eq 'loopback') {
345 $currentColorMap{$fgbg}{$type} = $color;
353 my $fgbg = (shift || 0) ? 'bg' : 'fg';
354 my $m = owl::getcurmsg();
356 my $type = lc($m->type);
359 if ($type eq 'zephyr') {
360 if (isZPersonal($m)) {
361 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
362 if ($oldColor = ($savedColorMap{$fgbg}{'zephyr-personal'}{$sender}) || '') {
363 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $oldColor;
365 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
368 my $class = lc($m->class);
369 my $instance = lc($m->instance);
372 || ($class eq 'message')
373 || ((($savedColorMap{$fgbg}{$type}{$class}{'*'} || '') eq ($currentColorMap{$fgbg}{$type}{$class}{'*'} || ''))
374 && (($savedColorMap{$fgbg}{$type}{$class}{$instance} || '') ne ($currentColorMap{$fgbg}{$type}{$class}{$instance} || ''))))
378 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$class}{$instance} || '')) {
379 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $oldColor;
381 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
384 } elsif ($type eq 'aim' || $type eq 'jabber') {
385 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
386 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
387 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$sender} || '')) {
388 $currentColorMap{$fgbg}{$type}{$sender} = $oldColor;
390 delete $currentColorMap{$fgbg}{$type}{$sender};
392 } elsif ($type eq 'loopback') {
394 if ($oldColor = ($savedColorMap{$fgbg}{$type} || '')) {
395 $currentColorMap{$fgbg}{$type} = $oldColor;
397 delete $currentColorMap{$fgbg}{$type};
406 return unless (grep(/^[fb]g$/, $fgbg));
408 createFilters($fgbg);
409 if ( *BarnOwl::refresh_view{CODE} ) {
410 BarnOwl::refresh_view();
412 my $filter = owl::command("getview");
413 my $style = owl::command("getstyle");
414 owl::command("view -f $filter ".($style?"-s $style":""));
418 ################################################################################
419 ## Saving/Loading functions
420 ################################################################################
423 return unless (grep(/^[fb]g$/, $fgbg));
426 open(COLORS, ">$ENV{HOME}/.owl/colormap_bg");
428 open(COLORS, ">$ENV{HOME}/.owl/colormap");
432 print COLORS "MODE: $type\n";
433 foreach my $c (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
434 foreach my $i (sort keys %{ $currentColorMap{$fgbg}{$type}{$c} }) {
436 || !($currentColorMap{$fgbg}{$type}{$c}{$i} eq ($currentColorMap{$fgbg}{$type}{$c}{'*'} || '')
437 || !$currentColorMap{$fgbg}{$type}{$c}{$i})) {
438 print COLORS "$c,$i,".($currentColorMap{$fgbg}{$type}{$c}{$i} ? $currentColorMap{$fgbg}{$type}{$c}{$i} : 'default')."\n";
443 $type = 'zephyr-personal';
444 print COLORS "MODE: $type\n";
445 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
446 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
450 print COLORS "MODE: $type\n";
451 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
452 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
456 print COLORS "MODE: $type\n";
457 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
458 print COLORS "$s,".($currentColorMap{$fgbg}{$type}{$s} ? $currentColorMap{$fgbg}{$type}{$s} : 'default')."\n";
462 print COLORS "MODE: $type\n";
463 print COLORS ($currentColorMap{$fgbg}{$type} ? $currentColorMap{$fgbg}{$type} : 'default')."\n";
471 return unless (grep(/^[fb]g$/, $fgbg));
473 $currentColorMap{$fgbg} = {};
474 $savedColorMap{$fgbg} = {};
476 # Parse the color file.
478 open(COLORS, "<$ENV{HOME}/.owl/colormap_bg") || return;
481 open(COLORS, "<$ENV{HOME}/.owl/colormap") || return;
487 foreach my $line (<COLORS>) {
489 if ($line =~ /^MODE: (.*)$/) {
490 if (lc($1) eq "zephyr") {
492 } elsif (lc($1) eq "zephyr-personal") {
493 $mode = 'zephyr-personal';
494 } elsif (lc($1) eq "aim") {
496 } elsif (lc($1) eq "jabber") {
498 } elsif (lc($1) eq "loopback") {
503 } elsif ($mode eq 'zephyr' && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
504 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
505 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
506 } elsif ($mode eq 'zephyr-personal' && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
507 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
508 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
509 } elsif (($mode eq 'aim' || $mode eq 'jabber') && $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 'loopback' && $line =~ /^(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
513 $currentColorMap{$fgbg}{$mode} = lc($2);
514 $savedColorMap{$fgbg}{$mode} = lc($2);