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 ( not body ^CC )'
177 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
180 #######################################################################
185 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
186 next if ($workingColorMap{$mode}{$sender} ne $color);
187 $sender =~ s/([+*])/\\$1/g;
189 $senders .= ($senders eq "") ? $sender : "|$sender";
193 '( type ^'.$mode2Protocol{$mode}.'$'
194 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
197 #######################################################################
202 foreach my $sender (sort keys %{ $workingColorMap{$mode} }) {
203 next if ($workingColorMap{$mode}{$sender} ne $color);
204 $sender =~ s/([+*])/\\$1/g;
206 $senders .= ($senders eq "") ? $sender : "|$sender";
210 '( type ^'.$mode2Protocol{$mode}.'$'
211 . ' and ( sender ^('.$senders.')$ or recipient ^('.$senders.')$ ) )');
214 #######################################################################
219 foreach my $srv (sort keys %{ $workingColorMap{$mode} }) {
222 foreach my $chan (sort keys %{ $workingColorMap{$mode}{$srv} }) {
223 next if ($workingColorMap{$mode}{$srv}{$chan} ne $color);
224 $chan =~ s/([+*])/\\$1/g;
225 push(@channels, $chan);
228 $srv =~ s/([+*])/\\$1/g;
231 ' ( server ^'.$srv.'$ and channel ^('.join('|',@channels).')$ )'
238 '( type ^'.$mode2Protocol{$mode}.'$'
239 . ' and ( '. join(' or ', @servers)
243 #######################################################################
246 push(@strs, '( type ^'.$mode2Protocol{$mode}.'$ )') if (($workingColorMap{$mode} || '') eq $color);
248 #######################################################################
250 my $filter = 'ColorUtils::'.$color.(($fgbg eq 'bg') ? '-bg' : '');
251 my $filterspec = "$filter ".(($fgbg eq 'bg') ? '-b' : '-c')." $color ";
253 BarnOwl::filter("$filterspec ( "
254 . join(' or ', @strs)
257 next if ($currentView eq $filter);
258 BarnOwl::_remove_filter($filter);
265 return 0 if ($c < 26);
266 return 1 if ($c < 77);
267 return 2 if ($c < 128);
268 return 3 if ($c < 179);
269 return 4 if ($c < 230);
273 sub find_color($$$) {
274 my $r = normalize_rgb(shift);
275 my $g = normalize_rgb(shift);
276 my $b = normalize_rgb(shift);
277 return 16 + (36 * $r) + (6 * $g) + $b;
284 shift; #strip setcolor from argument list.
287 'backgroud' => \$fgbg,
288 'instance' => \$inst,
291 if ((scalar @ARGV) <= 0) {
292 BarnOwl::message(sprintf("The current message is colored \"%s\".\n", getColor($inst, $fgbg)));
295 my $color = shift @ARGV;
297 if ($color =~ /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) {
298 $color = find_color(hex($1),hex($2),hex($3));
300 if ($color eq 'default') {
302 } elsif ($color eq 'restore') {
303 restore($inst, $fgbg);
305 die("setcolor: invalid color ($color)\n") unless grep(/$color/,@colorList);
306 setColor($color, $inst, $fgbg);
323 ################################################################################
324 ## Color toggling functions
325 ################################################################################
327 # Return 1 for things that would qualify a zephyr as personal.
329 return 1 if ($m->recipient ne "" and $m->recipient !~ /^@/);
330 return 1 if lc($m->class) eq "login";
336 my $fgbg = (shift || 0) ? 'bg' : 'fg';
337 my $m = owl::getcurmsg();
339 my $type = lc($m->type);
340 if ($type eq 'zephyr') {
341 if (isZPersonal($m)) {
342 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
344 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
346 my $class = lc($m->class);
347 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
349 $instance =~ s/ /./g;
350 if ($instance eq '*') {
351 $currentColorMap{$fgbg}{$type}{$class}{$instance} = 'default';
353 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
356 } elsif ($type eq 'aim' || $type eq 'jabber') {
357 my $sender = (lc($m->direction) eq 'in') ? $m->sender : $m->recipient;
358 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
360 delete $currentColorMap{$fgbg}{$type}{$sender};
361 } elsif ($type eq 'loopback') {
362 delete $currentColorMap{$fgbg}{$type};
371 my $fgbg = (shift || 0) ? 'bg' : 'fg';
372 my $m = owl::getcurmsg();
375 my $type = lc($m->type);
376 if ($type eq 'zephyr') {
377 if (isZPersonal($m)) {
378 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
380 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $color;
382 my $class = lc($m->class);
383 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
385 $instance =~ s/ /./g;
386 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $color;
388 } elsif ($type eq 'aim' || $type eq 'jabber') {
389 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
391 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
392 $currentColorMap{$fgbg}{$type}{$sender} = $color;
393 } elsif ($type eq 'irc') {
394 $currentColorMap{$fgbg}{$type}{$m->server}{$m->channel} = $color;
395 } elsif ($type eq 'loopback') {
396 $currentColorMap{$fgbg}{$type} = $color;
405 my $fgbg = (shift || 0) ? 'bg' : 'fg';
406 my $m = owl::getcurmsg();
409 my $type = lc($m->type);
410 if ($type eq 'zephyr') {
411 if (isZPersonal($m)) {
412 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
414 if (exists($currentColorMap{$fgbg}{'zephyr-personal'}{$sender})) {
415 return $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
418 my $class = lc($m->class);
419 my $instance = ($bInst || ($class eq 'message')) ? lc($m->instance) : '*';
421 $instance =~ s/ /./g;
422 if (exists($currentColorMap{$fgbg}{$type}{$class}{$instance})) {
423 return $currentColorMap{$fgbg}{$type}{$class}{$instance};
426 } elsif ($type eq 'aim' || $type eq 'jabber') {
427 my $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
429 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
430 if (exists($currentColorMap{$fgbg}{$type}{$sender})) {
431 return $currentColorMap{$fgbg}{$type}{$sender};
433 } elsif ($type eq 'irc') {
434 if (exists($currentColorMap{$fgbg}{$type}{$m->server}{$m->channel})) {
435 return $currentColorMap{$fgbg}{$type}{$m->server}{$m->channel};
437 } elsif ($type eq 'loopback') {
438 if (exists($currentColorMap{$fgbg}{$type})) {
439 return $currentColorMap{$fgbg}{$type};
446 my $fgbg = (shift || 0) ? 'bg' : 'fg';
447 my $m = owl::getcurmsg();
449 my $type = lc($m->type);
452 if ($type eq 'zephyr') {
453 if (isZPersonal($m)) {
454 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
456 if ($oldColor = ($savedColorMap{$fgbg}{'zephyr-personal'}{$sender}) || '') {
457 $currentColorMap{$fgbg}{'zephyr-personal'}{$sender} = $oldColor;
459 delete $currentColorMap{$fgbg}{'zephyr-personal'}{$sender};
462 my $class = lc($m->class);
463 my $instance = lc($m->instance);
466 || ($class eq 'message')
467 || ((($savedColorMap{$fgbg}{$type}{$class}{'*'} || '') eq ($currentColorMap{$fgbg}{$type}{$class}{'*'} || ''))
468 && (($savedColorMap{$fgbg}{$type}{$class}{$instance} || '') ne ($currentColorMap{$fgbg}{$type}{$class}{$instance} || ''))))
472 $instance =~ s/ /./g;
473 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$class}{$instance} || '')) {
474 $currentColorMap{$fgbg}{$type}{$class}{$instance} = $oldColor;
476 delete $currentColorMap{$fgbg}{$type}{$class}{$instance};
479 } elsif ($type eq 'aim' || $type eq 'jabber') {
480 $sender = lc((lc($m->direction) eq 'in') ? $m->sender : $m->recipient);
481 $sender = $m->recipient if ($type eq 'jabber' && lc($m->jtype) eq 'groupchat');
483 if ($oldColor = ($savedColorMap{$fgbg}{$type}{$sender} || '')) {
484 $currentColorMap{$fgbg}{$type}{$sender} = $oldColor;
486 delete $currentColorMap{$fgbg}{$type}{$sender};
488 } elsif ($type eq 'loopback') {
490 if ($oldColor = ($savedColorMap{$fgbg}{$type} || '')) {
491 $currentColorMap{$fgbg}{$type} = $oldColor;
493 delete $currentColorMap{$fgbg}{$type};
502 return unless (grep(/^[fb]g$/, $fgbg));
504 createFilters($fgbg);
505 if ( *BarnOwl::refresh_view{CODE} ) {
506 BarnOwl::refresh_view();
508 my $filter = owl::command("getview");
509 my $style = owl::command("getstyle");
510 owl::command("view -f $filter ".($style?"-s $style":""));
514 ################################################################################
515 ## Saving/Loading functions
516 ################################################################################
519 return unless (grep(/^[fb]g$/, $fgbg));
522 open(COLORS, ">$config_dir/colormap_bg");
524 open(COLORS, ">$config_dir/colormap");
528 print COLORS "MODE: $type\n";
529 foreach my $c (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
530 foreach my $i (sort keys %{ $currentColorMap{$fgbg}{$type}{$c} }) {
532 || !($currentColorMap{$fgbg}{$type}{$c}{$i} eq ($currentColorMap{$fgbg}{$type}{$c}{'*'} || '')
533 || !$currentColorMap{$fgbg}{$type}{$c}{$i})) {
534 print COLORS "$c,$i,"
535 . ($currentColorMap{$fgbg}{$type}{$c}{$i}
536 ? $currentColorMap{$fgbg}{$type}{$c}{$i}
543 $type = 'zephyr-personal';
544 print COLORS "MODE: $type\n";
545 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
547 . ($currentColorMap{$fgbg}{$type}{$s}
548 ? $currentColorMap{$fgbg}{$type}{$s}
554 print COLORS "MODE: $type\n";
555 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
557 . ($currentColorMap{$fgbg}{$type}{$s}
558 ? $currentColorMap{$fgbg}{$type}{$s}
564 print COLORS "MODE: $type\n";
565 foreach my $s (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
567 . ($currentColorMap{$fgbg}{$type}{$s}
568 ? $currentColorMap{$fgbg}{$type}{$s}
574 print COLORS "MODE: $type\n";
575 foreach my $srv (sort keys %{ $currentColorMap{$fgbg}{$type} }) {
576 foreach my $chan (sort keys %{ $currentColorMap{$fgbg}{$type}{$srv} }) {
577 print COLORS "$srv,$chan,"
578 . ($currentColorMap{$fgbg}{$type}{$srv}{$chan}
579 ? $currentColorMap{$fgbg}{$type}{$srv}{$chan}
587 print COLORS "MODE: $type\n";
588 print COLORS ($currentColorMap{$fgbg}{$type}
589 ? $currentColorMap{$fgbg}{$type}
599 return unless (grep(/^[fb]g$/, $fgbg));
601 $currentColorMap{$fgbg} = {};
602 $savedColorMap{$fgbg} = {};
604 # Parse the color file.
606 open(COLORS, "<$config_dir/colormap_bg") || return;
609 open(COLORS, "<$config_dir/colormap") || return;
615 foreach my $line (<COLORS>) {
617 if ($line =~ /^MODE: (.*)$/) {
618 if (lc($1) eq "zephyr") {
620 } elsif (lc($1) eq "zephyr-personal") {
621 $mode = 'zephyr-personal';
622 } elsif (lc($1) eq "aim") {
624 } elsif (lc($1) eq "jabber") {
626 } elsif (lc($1) eq "irc") {
628 } elsif (lc($1) eq "loopback") {
633 } elsif ($mode eq 'zephyr' && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
634 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
635 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
636 } elsif ($mode eq 'zephyr-personal' && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
637 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
638 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
639 } elsif (($mode eq 'aim' || $mode eq 'jabber') && $line =~ /^(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
640 $currentColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
641 $savedColorMap{$fgbg}{$mode}{lc($1)} = lc($3);
642 } elsif (($mode eq 'irc') && $line =~ /^(.+),(.+),(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
643 $currentColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
644 $savedColorMap{$fgbg}{$mode}{lc($1)}{lc($2)} = lc($4);
645 } elsif ($mode eq 'loopback' && $line =~ /^(b?)(black|red|green|yellow|blue|magenta|cyan|white|default|[0-9]{1,3})$/i) {
646 $currentColorMap{$fgbg}{$mode} = lc($2);
647 $savedColorMap{$fgbg}{$mode} = lc($2);