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.
19 ################################################################################
21 ################################################################################
25 our %mode2Protocol = ('zephyr' => 'zephyr',
26 'zephyr-personal' => 'zephyr',
30 'loopback' => 'loopback');
33 ################################################################################
34 #Run this on start and reload. Adds styles, sets style to start.
35 ################################################################################
36 my $config_dir = BarnOwl::get_config_dir();
39 %currentColorMap = ();
45 $BarnOwl::Hooks::startup->add(\&onStart);
48 @colorList = ('black','red','green','yellow',
49 'blue','magenta','cyan','white');
50 if ( *BarnOwl::getnumcolors{CODE} ) {
51 for (my $i = 8; $i < BarnOwl::getnumcolors(); $i++) {
57 ################################################################################
58 #Register BarnOwl commands and default keybindings.
59 ################################################################################
64 setcolor => \&cmd_setcolor,
66 summary => "Change the color for this sender (personals) or class/muc (or instance if zephyr -c message)",
67 usage => "setcolor [-i] [-b] [color]",
68 description => "Sets the foreground (or background) color for this kind of message.\n\n"
69 . "The following options are available:\n\n"
70 . " -i Set the color for this instance of this zephyr class.\n\n"
71 . " -b Sets the background color instead of the foreground color.\n\n"
72 . "color may be any of the colors listed in `:show color'; if using a 256-color\n"
73 . "terminal, you may also use an HTML-style color code, #rrggbb, which will\n"
74 . "be matched to the closest color approximation in a 6x6x6 colorcube.\n"
75 . "The following special values are also allowed:\n"
76 . " default uncolors the message\n"
77 . " restore restores the last saved color for this message\n"
78 . "If no color is specified, the current color is displayed.\n"
82 loadcolors => \&cmd_load,
84 summary => "Load color filter definitions from disk.",
89 savecolors => \&cmd_save,
91 summary => "Save active color filter definitions to disk.",
97 owl::command('bindkey recv "c" command start-command setcolor ');
101 ################################################################################
103 ################################################################################
104 sub createFilters($) {
105 # Prepare the color filters.
107 return unless (grep(/^[fb]g$/, $fgbg));
108 my $currentView = owl::getview();
110 my %workingColorMap = %{ $currentColorMap{$fgbg} };
112 foreach my $color (@colorList) {
115 #######################################################################
121 foreach my $c (sort keys %{ $workingColorMap{$mode} }) {
123 $c_esc =~ s/([+*])/\\$1/g;
124 my @instances = (sort keys %{ $workingColorMap{$mode}{$c} });
125 my $cHasStar = grep(/\*/, @instances);
127 if ($cHasStar && @instances == 1) {
128 # Collect classes that are only globally colored.
129 push(@class, $c_esc) if ($workingColorMap{$mode}{$c}{'*'} eq $color);
131 # Collect classes that have varying color for instances.
132 if ($cHasStar && $workingColorMap{$mode}{$c}{'*'} eq $color) {
134 foreach my $i (@instances) {
135 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} eq $color));
136 $i =~ s/([+*])/\\$1/g;
137 push(@cInstances, $i);
139 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and not instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
142 foreach my $i (@instances) {
143 next if (($i eq '*') || ($workingColorMap{$mode}{$c}{$i} ne $color));
144 $i =~ s/([+*])/\\$1/g;
145 push(@cInstances, $i);
147 push(@classInst, 'class ^'.$c_esc.'(.d)*$ and instance ^('.join('|',@cInstances).')(.d)*$') if (@cInstances);
152 # Join the collected classes into one big filter.
153 if (scalar(@class) || scalar(@classInst)) {
155 '( type ^'.$mode2Protocol{$mode}.'$ and ( '
156 . ((scalar(@class)) ? 'class ^('.join('|',@class).')(.d)*$ ' : '')
157 . ((scalar(@class) && scalar(@classInst)) ? 'or ' : '')
158 . ((scalar(@classInst)) ? '( '.join(' ) or ( ', @classInst).' ) ' : '')
162 #######################################################################
163 $mode = 'zephyr-personal';
167 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
168 next if ($workingColorMap{$mode}{$sender} ne $color);
169 $sender =~ s/([+*])/\\$1/g;
171 $senders .= ($senders eq "") ? $sender : "|$sender";
175 '( type ^'.$mode2Protocol{$mode}.'$ and ( ( class ^message$ and instance ^personal$ ) or class ^login$ )'
176 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
179 #######################################################################
184 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
185 next if ($workingColorMap{$mode}{$sender} ne $color);
186 $sender =~ s/([+*])/\\$1/g;
188 $senders .= ($senders eq "") ? $sender : "|$sender";
192 '( type ^'.$mode2Protocol{$mode}.'$'
193 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
196 #######################################################################
201 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
202 next if ($workingColorMap{$mode}{$sender} ne $color);
203 $sender =~ s/([+*])/\\$1/g;
205 $senders .= ($senders eq "") ? $sender : "|$sender";
209 '( type ^'.$mode2Protocol{$mode}.'$'
210 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
213 #######################################################################
218 foreach my $srv (sort keys %{ $workingColorMap{$mode} }) {
221 foreach my $chan (sort keys %{ $workingColorMap{$mode}{$srv} }) {
222 next if ($workingColorMap{$mode}{$srv}{$chan} ne $color);
223 $chan =~ s/([+*])/\\$1/g;
224 push(@channels, $chan);
227 $srv =~ s/([+*])/\\$1/g;
230 ' ( server ^'.$srv.'$ and channel ^('.join('|',@channels).')$ )'
237 '( type ^'.$mode2Protocol{$mode}.'$'
238 . ' and ( '. join(' or ', @servers)
242 #######################################################################
245 push(@strs, '( type ^'.$mode2Protocol{$mode}.'$ )') if (($workingColorMap{$mode} || '') eq $color);
247 #######################################################################
249 my $filter = 'ColorUtils::'.$color.(($fgbg eq 'bg') ? '-bg' : '');
250 my $filterspec = "$filter ".(($fgbg eq 'bg') ? '-b' : '-c')." $color ";
252 BarnOwl::filter("$filterspec ( "
253 . join(' or ', @strs)
256 next if ($currentView eq $filter);
257 BarnOwl::_remove_filter($filter);
264 return 0 if ($c < 26);
265 return 1 if ($c < 77);
266 return 2 if ($c < 128);
267 return 3 if ($c < 179);
268 return 4 if ($c < 230);
272 sub find_color($$$) {
273 my $r = normalize_rgb(shift);
274 my $g = normalize_rgb(shift);
275 my $b = normalize_rgb(shift);
276 return 16 + (36 * $r) + (6 * $g) + $b;
283 shift; #strip setcolor from argument list.
286 'backgroud' => \$fgbg,
287 'instance' => \$inst,
290 if ((scalar @ARGV) <= 0) {
291 BarnOwl::message(sprintf("The current message is colored \"%s\".\n", getColor($inst, $fgbg)));
294 my $color = shift @ARGV;
296 if ($color =~ /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) {
297 $color = find_color(hex($1),hex($2),hex($3));
299 if ($color eq 'default') {
301 } elsif ($color eq 'restore') {
302 restore($inst, $fgbg);
304 die("setcolor: invalid color ($color)\n") unless grep(/$color/,@colorList);
305 setColor($color, $inst, $fgbg);
322 ################################################################################
323 ## Color toggling functions
324 ################################################################################
326 # Return 1 for things that would qualify a zephyr as personal.
328 return 1 if ($m->recipient ne "" and $m->recipient !~ /^@/);
329 return 1 if lc($m->class) eq "login";
335 my $fgbg = (shift || 0) ? 'bg' : 'fg';
336 my $m = owl::getcurmsg();
338 my $type = lc($m->type);
339 if ($type eq 'zephyr') {
340 if (isZPersonal($m)) {
341 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
343 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
345 my $class = lc($m->class);
346 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
348 $instance =~ s/ /./g;
349 if ($instance eq '*') {
350 $currentColorMap{$fgbg}{$type}{$class}{$instance} = 'default';
352 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
355 } elsif ($type eq 'aim' || $type eq 'jabber') {
356 my $sender = (lc($m->direction) eq 'in') ? $m->sender : $m->recipient;
357 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
359 delete $currentColorMap{$fgbg}{$type}{$sender};
360 } elsif ($type eq 'loopback') {
361 delete $currentColorMap{$fgbg}{$type};
370 my $fgbg = (shift || 0) ? 'bg' : 'fg';
371 my $m = owl::getcurmsg();
374 my $type = lc($m->type);
375 if ($type eq 'zephyr') {
376 if (isZPersonal($m)) {
377 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
379 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $color;
381 my $class = lc($m->class);
382 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
384 $instance =~ s/ /./g;
385 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $color;
387 } elsif ($type eq 'aim' || $type eq 'jabber') {
388 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
390 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
391 $currentColorMap{$fgbg}{$type}{$sender} = $color;
392 } elsif ($type eq 'irc') {
393 $currentColorMap{$fgbg}{$type}{$m->server}{$m->channel} = $color;
394 } elsif ($type eq 'loopback') {
395 $currentColorMap{$fgbg}{$type} = $color;
404 my $fgbg = (shift || 0) ? 'bg' : 'fg';
405 my $m = owl::getcurmsg();
408 my $type = lc($m->type);
409 if ($type eq 'zephyr') {
410 if (isZPersonal($m)) {
411 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
413 if (exists($currentColorMap{$fgbg}{'zephyr-personal'}{$sender})) {
414 return $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
417 my $class = lc($m->class);
418 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
420 $instance =~ s/ /./g;
421 if (exists($currentColorMap{$fgbg}{$type}{$class}{$instance})) {
422 return $currentColorMap{$fgbg}{$type}{$class}{$instance};
425 } elsif ($type eq 'aim' || $type eq 'jabber') {
426 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
428 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
429 if (exists($currentColorMap{$fgbg}{$type}{$sender})) {
430 return $currentColorMap{$fgbg}{$type}{$sender};
432 } elsif ($type eq 'irc') {
433 if (exists($currentColorMap{$fgbg}{$type}{$m->server}{$m->channel})) {
434 return $currentColorMap{$fgbg}{$type}{$m->server}{$m->channel};
436 } elsif ($type eq 'loopback') {
437 if (exists($currentColorMap{$fgbg}{$type})) {
438 return $currentColorMap{$fgbg}{$type};
445 my $fgbg = (shift || 0) ? 'bg' : 'fg';
446 my $m = owl::getcurmsg();
448 my $type = lc($m->type);
451 if ($type eq 'zephyr') {
452 if (isZPersonal($m)) {
453 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
455 if ($oldColor = ($savedColorMap{$fgbg}{'zephyr-personal'}{$sender}) || '') {
456 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $oldColor;
458 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
461 my $class = lc($m->class);
462 my $instance = lc($m->instance);
465 || ($class eq 'message')
466 || ((($savedColorMap{$fgbg}{$type}{$class}{'*'} || '') eq ($currentColorMap{$fgbg}{$type}{$class}{'*'} || ''))
467 && (($savedColorMap{$fgbg}{$type}{$class}{$instance} || '') ne ($currentColorMap{$fgbg}{$type}{$class}{$instance} || ''))))
471 $instance =~ s/ /./g;
472 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$class}{$instance} || '')) {
473 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $oldColor;
475 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
478 } elsif ($type eq 'aim' || $type eq 'jabber') {
479 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
480 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
482 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$sender} || '')) {
483 $currentColorMap{$fgbg}{$type}{$sender} = $oldColor;
485 delete $currentColorMap{$fgbg}{$type}{$sender};
487 } elsif ($type eq 'loopback') {
489 if ($oldColor = ($savedColorMap{$fgbg}{$type} || '')) {
490 $currentColorMap{$fgbg}{$type} = $oldColor;
492 delete $currentColorMap{$fgbg}{$type};
501 return unless (grep(/^[fb]g$/, $fgbg));
503 createFilters($fgbg);
504 if ( *BarnOwl::refresh_view{CODE} ) {
505 BarnOwl::refresh_view();
507 my $filter = owl::command("getview");
508 my $style = owl::command("getstyle");
509 owl::command("view -f $filter ".($style?"-s $style":""));
513 ################################################################################
514 ## Saving/Loading functions
515 ################################################################################
518 return unless (grep(/^[fb]g$/, $fgbg));
521 open(COLORS, ">$config_dir/colormap_bg");
523 open(COLORS, ">$config_dir/colormap");
527 print COLORS "MODE: $type\n";
528 foreach my $c (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
529 foreach my $i (sort keys %{ $currentColorMap{$fgbg}{$type}{$c} }) {
531 || !($currentColorMap{$fgbg}{$type}{$c}{$i} eq ($currentColorMap{$fgbg}{$type}{$c}{'*'} || '')
532 || !$currentColorMap{$fgbg}{$type}{$c}{$i})) {
533 print COLORS "$c,$i,"
534 . ($currentColorMap{$fgbg}{$type}{$c}{$i}
535 ? $currentColorMap{$fgbg}{$type}{$c}{$i}
542 $type = 'zephyr-personal';
543 print COLORS "MODE: $type\n";
544 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
546 . ($currentColorMap{$fgbg}{$type}{$s}
547 ? $currentColorMap{$fgbg}{$type}{$s}
553 print COLORS "MODE: $type\n";
554 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
556 . ($currentColorMap{$fgbg}{$type}{$s}
557 ? $currentColorMap{$fgbg}{$type}{$s}
563 print COLORS "MODE: $type\n";
564 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
566 . ($currentColorMap{$fgbg}{$type}{$s}
567 ? $currentColorMap{$fgbg}{$type}{$s}
573 print COLORS "MODE: $type\n";
574 foreach my $srv (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
575 foreach my $chan (sort keys %{ $currentColorMap{$fgbg}{$type}{$srv} }) {
576 print COLORS "$srv,$chan,"
577 . ($currentColorMap{$fgbg}{$type}{$srv}{$chan}
578 ? $currentColorMap{$fgbg}{$type}{$srv}{$chan}
586 print COLORS "MODE: $type\n";
587 print COLORS ($currentColorMap{$fgbg}{$type}
588 ? $currentColorMap{$fgbg}{$type}
598 return unless (grep(/^[fb]g$/, $fgbg));
600 $currentColorMap{$fgbg} = {};
601 $savedColorMap{$fgbg} = {};
603 # Parse the color file.
605 open(COLORS, "<$config_dir/colormap_bg") || return;
608 open(COLORS, "<$config_dir/colormap") || return;
614 foreach my $line (<COLORS>) {
616 if ($line =~ /^MODE: (.*)$/) {
617 if (lc($1) eq "zephyr") {
619 } elsif (lc($1) eq "zephyr-personal") {
620 $mode = 'zephyr-personal';
621 } elsif (lc($1) eq "aim") {
623 } elsif (lc($1) eq "jabber") {
625 } elsif (lc($1) eq "irc") {
627 } elsif (lc($1) eq "loopback") {
632 } elsif ($mode eq 'zephyr' && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
633 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
634 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
635 } elsif ($mode eq 'zephyr-personal' && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
636 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
637 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
638 } elsif (($mode eq 'aim' || $mode eq 'jabber') && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
639 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
640 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
641 } elsif (($mode eq 'irc') && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
642 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
643 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
644 } elsif ($mode eq 'loopback' && $line =~ /^(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
645 $currentColorMap{$fgbg}{$mode} = lc($2);
646 $savedColorMap{$fgbg}{$mode} = lc($2);