File Coverage

blib/lib/Tk/AstroCatalog.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Tk::AstroCatalog;
2              
3             =head1 NAME
4              
5             Tk::SourceCatalog - creates a self-standing sources catalog widget
6              
7             =head1 SYNOPSIS
8              
9             use Tk::AstroCatalog;
10              
11             $cat = new Tk::AstroCatalog($MW, $addCmd, $upDate, $onDestroy);
12              
13             =head1 DESCRIPTION
14              
15             Tk::AstroCatalog creates a non-editable text widget, displaying
16             sources from a default catalog or user-selected catalog file.
17              
18             =cut
19              
20 1     1   6946 use 5.004;
  1         6  
  1         88  
21 1     1   9 use strict;
  1         10  
  1         143  
22 1     1   1896 use Math::Trig qw/pi/;
  1         30168  
  1         6860  
23 1     1   16 use Carp;
  1         3  
  1         92  
24 1     1   1096 use Astro::Catalog;
  0            
  0            
25             use Astro::Catalog::Star;
26             use Astro::Coords 0.12;
27             use Tk;
28             use Tk::FileSelect;
29              
30             my $locateBug = 0;
31             my $BUSY = 0;
32             my @COLOR_LIST = ('#ffAAAA', '#00ff00', '#ff55ff', '#ffff00', '#00ffff',
33             '#ff00ff', '#ffffff', '#ff5555', '#55ff55', '#55ffff', '#ffff55');
34             my $COLOR_INDEX = 0;
35              
36             use vars qw/$VERSION $FORMAT/;
37              
38             $VERSION = '4.31';
39              
40             # Kluge - this is the format of the catalog to be read
41             # Needs to be given as an option on the FileSelect widget.
42             $FORMAT = 'JCMT';
43              
44             =head1 PUBLIC METHODS
45              
46             Methods available in this class:
47              
48             =over 4
49              
50             =item new
51              
52             Create a new Tk::AstroCatalog object. A new catalog object will be
53             created. Callbacks must be specified for -addCmd and -upDate; a
54             warning is issued for -onDestroy when it is missing.
55              
56             $cat = new Tk::AstroCatalog($MW,
57             -addCmd => $addCmd,
58             -upDate => $upDate,
59             -onDestroy => $onDestroy);
60              
61             Additionally a pre-existing Astro::Catalog object can be supplied
62             using the "-catalog" option.
63              
64             $cat = new Tk::AstroCatalog($MW,
65             -addCmd => $addCmd,
66             -upDate => $upDate
67             -catalog => $cat,
68             );
69              
70             The "-transient" option can be used if only a single value is required
71             from the widget. Default behaviour is for the widget to be
72             permanent. The "-transient" button does not have a "Done" button on
73             the screen (ie no button to close the window without a selection)
74              
75             The "-addCmd" callback is triggered whenever a source is selected
76             from the widget. If the widget is transient the widget will be
77             closed after the first add is triggered.
78              
79             The "-onDestroy" callback is triggered when the "Done" button is
80             pressed.
81              
82             The "-upDate" method is triggered whenever the contents of the
83             catalog widget are refreshed/updated.
84              
85             It makes more sense for this widget to work like Tk::FileSelect
86             when used in transient mode since we want to get the answer back
87             rather than enter an event loop.
88              
89             The "-customColumns" method can be used to add additional columns
90             to the display. This is an array of hashes specifying the
91             title, width and generator function for each column. This generating
92             function will be called with an Astro::Catalog::Item and must
93             return a string of the given width.
94              
95             -customColumns => [{title => 'Example',
96             width => 7,
97             generator => sub {
98             my $item = shift;
99             return sprintf('%7s', 'test');
100             }},
101             ]
102              
103             =cut
104              
105             ###############################################################
106             # SourceCatalog creates a windows that displays the contents
107             # of a catalog and allows the user to select as many entries
108             # in it as the user wishes.
109             #
110             sub new {
111             my $class = shift;
112             croak "CatWin usage: Missing args \n" unless (@_);
113             my $MW = shift;
114             my %defaults = (
115             -default => 'defaults',
116             -transient => 0,
117             @_);
118              
119             # use Data::Dumper;
120             # print Dumper(\%defaults);
121             croak "Tk::AstroCatalog -addCmd option missing \n" unless(exists $defaults{'-addCmd'});
122             croak "Tk::AstroCatalog -upDate option missing \n" unless(exists $defaults{'-upDate'});
123             warn "Tk::AstroCatalog -onDestroy option missing \n" unless(exists $defaults{'-onDestroy'});
124              
125             my $self = {};
126              
127             if (exists $defaults{'-catalog'}) {
128             $self->{CatClass} = ref($defaults{'-catalog'});
129             $self->{Catalog} = $defaults{'-catalog'};
130             } else {
131             # use default settings
132             $self->{CatClass} = 'Astro::Catalog';
133             $self->{Catalog} = $self->{CatClass}->new();
134             }
135              
136             $self->{UpDate} = undef;
137             $self->{Reset} = undef;
138             $self->{AddCommand} = undef;
139             $self->{Toplevel} = $MW->Toplevel;
140             $self->{Selected} = [];
141             $self->{Text} = undef;
142             $self->{File} = 'default';
143             $self->{Transient} = $defaults{'-transient'};
144             $self->{RefLabel} = '';
145              
146             if (exists $defaults{'-customColumns'}) {
147             # Store whole hash rather than just generator function
148             # in case we want to add other ways of specifying custom columns.
149             my $cols = $self->{CustomColumns} = $defaults{'-customColumns'};
150             croak "Tk::AstroCatalog -customColumns must be an array ref"
151             unless 'ARRAY' eq ref $cols;
152              
153             my $headings = '';
154             foreach my $col (@$cols) {
155             $headings .= sprintf('%-'.$col->{'width'}.'s ', $col->{'title'});
156             }
157              
158             $self->{CustomHeadings} = $headings;
159             $self->{CustomWidth} = length($headings);
160             }
161             else {
162             $self->{CustomColumns} = undef;
163             $self->{CustomHeadings} = '';
164             $self->{CustomWidth} = 0;
165             }
166              
167              
168             bless $self, $class;
169             $self->Reset($defaults{'-onDestroy'}) if exists $defaults{'-onDestroy'};
170             $self->AddCommand($defaults{'-addCmd'});
171             $self->UpDate($defaults{'-upDate'});
172              
173             $self->makeCatalog();
174             return $self;
175             }
176              
177             #
178             # Common data manipulation functions
179             #
180              
181             =item Catalog
182              
183             Returns and sets the Astro::Catalog object.
184              
185             $catalog = $cat->Catalog();
186             $cat->Catalog(new Astro::Catalog(...));
187              
188             =cut
189              
190             sub Catalog {
191             my $self = shift;
192             if(@_)
193             {
194             my $cat = shift;
195             if (UNIVERSAL::isa($cat,'Astro::Catalog'))
196             {
197             $self->{Catalog} = $cat;
198             }
199             else
200             {
201             croak "Tk::AstroCatalog: Catalog must be of type Astro::Catalog \n";
202             }
203             }
204             return $self->{Catalog};
205             }
206              
207             =item AddCommand
208              
209             returns and sets the AddCommand callback code for the catalog
210              
211             $addCommand = $cat->AddCommand();
212             $cat->AddCommand($addCommand);
213              
214             =cut
215              
216             sub AddCommand
217             {
218             my $self = shift;
219             if(@_)
220             {
221             my $cmd = shift;
222             if (ref($cmd) eq 'CODE')
223             {
224             $self->{AddCommand} = $cmd;
225             }
226             else
227             {
228             croak "CatWin: AddCommand must be of type Code Ref \n";
229             }
230             }
231             return $self->{AddCommand};
232             }
233              
234             =item UpDate
235              
236             returns and sets the UpDate callback code for the catalog
237              
238             $update = $cat->UpDate();
239             $cat->UpDate($update);
240              
241             Called whenever the contents of the text widget are redisplayed.
242             The first argument will be the current object.
243              
244             =cut
245              
246             sub UpDate
247             {
248             my $self = shift;
249             if(@_)
250             {
251             my $cmd = shift;
252             if (ref($cmd) eq 'CODE')
253             {
254             $self->{upDate} = $cmd;
255             }
256             else
257             {
258             croak "CatWin: upDate must be of type Code Ref \n";
259             }
260             }
261             return $self->{upDate};
262             }
263              
264             =item Reset
265              
266             returns and sets the onDestroy callback code for the catalog
267              
268             $reset = $cat->Reset();
269             $cat->Reset($reset);
270              
271             =cut
272              
273             sub Reset
274             {
275             my $self = shift;
276             if(@_)
277             {
278             my $cmd = shift;
279             if (ref($cmd) eq 'CODE')
280             {
281             $self->{Reset} = $cmd;
282             }
283             else
284             {
285             croak "CatWin: Reset must be of type Code Ref \n";
286             }
287             }
288             return $self->{Reset};
289             }
290              
291             =item Toplevel
292              
293             returns and sets the name of the Toplevel
294              
295             $toplevel = $cat->Toplevel();
296             $cat->Toplevel($top);
297              
298             =cut
299              
300             sub Toplevel
301             {
302             my $self = shift;
303             if(@_)
304             {
305             $self->{Toplevel} = shift;
306             }
307             return $self->{Toplevel};
308             }
309              
310             =item Transient
311              
312             returns and sets whether the widget should be destroyed after the
313             next Add.
314              
315             $toplevel = $cat->Transient();
316             $cat->Transient($top);
317              
318             =cut
319              
320             sub Transient
321             {
322             my $self = shift;
323             if(@_)
324             {
325             $self->{Transient} = shift;
326             }
327             return $self->{Transient};
328             }
329              
330             =item Text
331              
332             returns and sets the name of the Text
333              
334             $text = $cat->Text();
335             $cat->Text($text);
336              
337             =cut
338              
339             sub Text {
340             my $self = shift;
341             if(@_)
342             {
343             my $cat = shift;
344             if (UNIVERSAL::isa($cat,'Tk::Frame'))
345             {
346             $self->{Text} = $cat;
347             }
348             else
349             {
350             croak "CatWin: Text widget must be of type Tk::Frame \n";
351             }
352             }
353             return $self->{Text};
354             }
355              
356             =item RefLabel
357              
358             Configure the text displayed in the reference label widget.
359             Usually a summary of the reference position.
360              
361             $self->RefLabel
362              
363             Returns a reference to a scalar that can be used to associate
364             the value with a widget.
365              
366             =cut
367              
368             sub RefLabel {
369             my $self = shift;
370             if (@_) {
371             $self->{RefLabel} = shift;
372             }
373             return \$self->{RefLabel};
374             }
375              
376             =item CatClass
377              
378             returns and sets the name of the CatClass
379              
380             $class = $cat->CatClass();
381             $cat->CatClass($class);
382              
383             =cut
384              
385             sub CatClass {
386             my $self = shift;
387             if(@_)
388             {
389             $self->{CatClass} = shift;
390             }
391             return $self->{CatClass};
392             }
393              
394             =item Selected
395              
396             returns the Selected array or the indexed value of this array
397              
398             @selected = $cat->Selected();
399             $value = $cat->Selected($index);
400              
401             =cut
402              
403             sub Selected
404             {
405             my $self = shift;
406             if(@_)
407             {
408             my $index = shift;
409             if(@_)
410             {
411             $self->{Selected}->[$index] = shift;
412             }
413             return $self->{Selected}->[$index];
414             }
415             return $self->{Selected};
416             }
417              
418             =item file
419              
420             returns and sets the File name
421              
422             $file = $cat->file();
423             $cat->file($filename);
424              
425             =cut
426              
427             sub file
428             {
429             my $self = shift;
430             if (@_)
431             {
432             $self->{File} = shift;
433             }
434             return $self->{File};
435             }
436              
437             =item makeCatalog
438              
439             makeCatalog creates a window that displays the
440             contents of a catalog and allows the user to select as
441             many entries as the user wishes.
442              
443             $catalog = $cat->makeCatalog();
444             $catalog = $cat->makeCatalog($selected);
445              
446             =cut
447              
448             sub makeCatalog
449             {
450             my $self = shift;
451             my $selected = $self->{Selected};
452             my $Top = $self->Toplevel;
453             $Top->geometry('+600+437');
454             $Top->title('Source Plot: Catalog Window');
455             $Top->resizable(0,0);
456              
457             print "made the catalog window\n" if $locateBug;
458              
459             my @Sources;
460             my $topFrame = $Top->Frame(-relief=>'groove', -borderwidth =>2, -width =>50)->pack(-padx=>10, -fill => 'x', -ipady=>3, -pady => 10);
461              
462             # create the header
463             my $headFrame = $topFrame->Frame(-relief=>'flat', -borderwidth =>2)->grid(-row=>0, -sticky=>'nsew', -ipadx => 3);
464             my $head = $topFrame->Text(
465             -wrap => 'none',
466             -relief => 'flat',
467             -foreground => 'midnightblue',
468             -width => 90 + $self->{'CustomWidth'},
469             -height => 1,
470             -font => '-*-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*',
471             -takefocus => 0
472             )->grid (-sticky=>'ew', -row =>0);
473             my $title = sprintf "%5s %-16s %-12s %-13s %-4s %-3s %-3s %-5s %s%s",
474             'Index', 'Name', 'Ra', 'Dec', 'Epoc', 'Az', 'El', 'Dist',
475             $self->{'CustomHeadings'}, "Comment";
476             $head->insert ('end', $title);
477             $head->configure(-state=>'disabled');
478              
479             print "just about to make the scrollable text\n" if $locateBug;
480              
481             # create the text scrollable window
482             my $T = $topFrame->Scrolled('Text',
483             -scrollbars => 'e',
484             -wrap => 'none',
485             -width => 100 + $self->{'CustomWidth'},
486             -height => 15,
487             -font => '-*-Courier-Medium-R-Normal--*-120-*-*-*-*-*-*',
488             -setgrid => 1,
489             )->grid(qw/-sticky nsew/);
490             $T->bindtags(qw/widget_demo/); # remove all bindings but dummy "widget_demo"
491             $self->Text($T);
492             print "just before creating the done button\n" if $locateBug;
493              
494             # KLUGE with a global reference label for now
495             my $RefLabel = $topFrame->Label( -textvariable => $self->RefLabel,
496             -width => 64,
497             )->grid(-sticky=>'nsew',-row=>2);
498              
499             # Create button frame
500             my $buttonF2 = $Top->Frame->pack(-padx=>10, -fill =>'x');
501             my $buttonF = $Top->Frame->pack(-padx=>10, -pady=>10);
502              
503             # create the Done button if we are not transient
504             if (!$self->Transient) {
505             my $dBut = $buttonF->Button(
506             -text => 'Done',
507             -command => sub{ $self->destroy }
508             )->pack(-side=>'right');
509             }
510              
511             # create the Add button
512             my $addBut = $buttonF->Button( -text=>'Add',
513             -relief => 'raised',
514             -width => 7,
515             -command => sub {
516             my $callback = $self->AddCommand;
517             my $selected = $self->Selected;
518             # turn off tags
519             foreach my $one (@$selected) {
520             # KLUGE source does not have index attribute
521             $T->tag('configure', 'd'.$one->{index}, -foreground => 'blue');
522             }
523             #$callback->(@$selected);
524             $callback->($selected);
525              
526             if ($self->Transient) {
527             # game over (should be a sub)
528             $self->destroy;
529             }
530             })->pack(-side=>'right', -padx=>20);
531              
532             # create the Search button
533             my $searchBut;
534             $searchBut = $buttonF->Button( -text=>'Search',
535             -relief => 'raised',
536             -width => 7,
537             -command => sub {
538             $searchBut->configure(-state=>'disabled');
539             $self->getSource($self->Toplevel->Toplevel,$searchBut);
540             })->pack(-side=>'right');
541              
542             # declared for the catalog file
543             my $catEnt;
544              
545             # create the Rescan button
546             my $rescanBut = $buttonF->Button( -text=>'Rescan',
547             -relief => 'raised',
548             -width => 7,
549             -command => sub {
550             $self->file($catEnt->get);
551             # reset current array to original list
552             $self->Catalog->reset_list;
553             $self->fillWithSourceList ('full');
554             })->pack(-side=>'right', -padx =>'20');
555              
556             # create the Sort menu
557             my $sortmenu = $buttonF->Menubutton(-text=>'Sort by', -relief=>'raised', -width=>7);
558             $sortmenu->command(-label=>'Unsorted', -command=> sub {
559             $self->Catalog->sort_catalog('unsorted');
560             $self->fillWithSourceList ('full');
561             });
562             $sortmenu->command(-label=>'Id', -command=> sub {
563             $self->Catalog->sort_catalog('id');
564             $self->fillWithSourceList ('full');
565             });
566             $sortmenu->command(-label=>'Ra', -command=> sub {
567             $self->Catalog->sort_catalog('ra');
568             $self->fillWithSourceList ('full');
569             });
570             $sortmenu->command(-label=>'Dec', -command=> sub {
571             $self->Catalog->sort_catalog('dec');
572             $self->fillWithSourceList ('full');
573             });
574             $sortmenu->command(-label=>'Az', -command=> sub {
575             $self->Catalog->sort_catalog('az');
576             $self->fillWithSourceList ('full');
577             });
578             $sortmenu->command(-label=>'El', -command=> sub {
579             $self->Catalog->sort_catalog('el');
580             $self->fillWithSourceList ('full');
581             });
582             # add sort by distance if we have a reference position
583             if ($self->Catalog->reference) {
584             $sortmenu->command(-label=>'Distance', -command=> sub {
585             $self->Catalog->sort_catalog('distance');
586             $self->fillWithSourceList ('full');
587             });
588             $sortmenu->command(-label=>'Distance in Az', -command=> sub {
589             $self->Catalog->sort_catalog('distance_az');
590             $self->fillWithSourceList ('full');
591             });
592             }
593              
594              
595             $sortmenu->pack(-side=>'right', -padx=>'20');
596              
597             # create the catalog menu button
598             my $catB = $buttonF2->Menubutton( -text=>'Catalogs', -relief => 'raised', -width => 8);
599             $catB->command(-label =>'Default Catalog', -command=> sub{
600             $self->file ('default');
601             $catEnt->delete ('0','end');
602             $catEnt->insert(0,$self->file);
603             # $MW->update;
604             # No filename for default
605             $self->Catalog($self->CatClass->new(
606             Format => $FORMAT,
607             ));
608             $self->fillWithSourceList ('full');
609             });
610             $catB->command(-label =>'File Catalog', -command=> sub{
611             my $dir;
612             chomp($dir = `pwd`);
613             my $win = $Top->FileSelect(-directory => $dir);;
614             my $file = $win->Show;
615             if (defined $file && $file ne '') {
616             $catEnt->delete ('0','end');
617             $catEnt->insert('0', $file);
618              
619             # Get the current catalogue properties [should be a sub]
620             my $oldcat = $self->Catalog;
621             my ($refc, $canobs);
622             if (defined $oldcat) {
623             $refc = $oldcat->reference;
624             $canobs = $oldcat->auto_filter_observability;
625             }
626              
627             $self->file($file);
628             $self->Catalog($self->CatClass->new(File =>$self->file,
629             Format => $FORMAT
630             ));
631              
632             # Propogate previous info
633             $self->Catalog->reference( $refc ) if defined $refc;
634             $self->Catalog->auto_filter_observability( $canobs );
635             $self->Catalog->reset_list;
636              
637             $self->fillWithSourceList ('full');
638             }
639             });
640             $catB->pack (-side=>'left',-padx =>10);
641              
642             # Create the catalog file label
643             $buttonF2->Label (
644             -text => "Catalog file:",
645             )->pack(-side=>'left');
646             $catEnt = $buttonF2->Entry(-relief=>'sunken',
647             -width=>37)->pack(-side=>'left', -padx =>10);
648             $catEnt->bind('' =>sub {
649             # Get the current catalogue properties [should be a sub]
650             my $oldcat = $self->Catalog;
651             my ($refc, $canobs);
652             if (defined $oldcat) {
653             $refc = $oldcat->reference;
654             $canobs = $oldcat->auto_filter_observability;
655             }
656              
657             $self->file($catEnt->get);
658             if ($catEnt->get eq 'default') {
659             $self->Catalog($self->CatClass->new(
660             Format => $FORMAT
661             ));
662             } else {
663             $self->Catalog($self->CatClass->new(File => $self->file,
664             Format => $FORMAT
665             ));
666             }
667             # Propogate previous info
668             $self->Catalog->reference( $refc ) if defined $refc;
669             $self->Catalog->auto_filter_observability( $canobs );
670             $self->Catalog->reset_list;
671              
672             $self->fillWithSourceList ('full');
673             });
674             $catEnt->insert(0,$self->file);
675              
676             print "made it past all the buttons and just about to fill...\n" if $locateBug;
677             # if we do not have a catalog yet create one
678             unless ($self->Catalog) {
679             $self->file($catEnt->get);
680             $self->Catalog($self->CatClass->new( File => $self->file,
681             Format => $FORMAT
682             ));
683             }
684             $self->fillWithSourceList ('full');
685              
686             return $self;
687              
688             }
689              
690             =item destroy
691              
692             Remove the widget from display. Leaves calling the
693             Reset handler to the DESTROY method.
694              
695             =cut
696              
697             sub destroy {
698             my $self = shift;
699             my $Top = $self->Toplevel;
700             $Top->destroy() if defined $Top && Exists($Top);
701             }
702              
703             =item DESTROY
704              
705             Object destructor. Triggers when the object is destroyed.
706             Guarantees to destroy the Toplevel widget and does trigger
707             the onDestroy callback.
708              
709             =cut
710              
711             sub DESTROY {
712             my $self = shift;
713             my $callback = $self->Reset;
714             $callback->() if defined $callback;
715             my $Top = $self->Toplevel;
716             $Top->destroy() if defined $Top && Exists($Top);
717             }
718              
719             =item fillWithSourceList
720              
721             fills a text widget with the list of current sources
722              
723             $cat->fillWithSourceList();
724             $cat->fillWithSourceList($text,$selected,$task,$index);
725             $cat->fillWithSourceList($text,$selected,$task);
726             $cat->fillWithSourceList($text,$selected);
727              
728             Also triggers the UpDate method.
729              
730             =cut
731              
732             ############################################################
733             #
734             # fills a Text box with the list of current sources
735             #
736             sub fillWithSourceList {
737             my(@bold, @normal);
738             my $self = shift;
739             my $T = $self->Text;
740             my $selected = $self->Selected;
741             my $task = shift;
742             my $index = shift;
743             my @entered = ();
744             my($line,$itag);
745              
746             # Retrieve the objects
747             # forcing the reference time
748             $self->Catalog->force_ref_time;
749             my @stars = $self->Catalog->stars;
750             my @sources = map { $_->coords } @stars;
751              
752             # Enable infobox for access
753             $T->configure(-state=>'normal');
754              
755             # Clear the existing widgets
756             if (defined $task && $task eq 'full') {
757             $T->delete('1.0','end');
758             foreach my $source (@sources) {
759             # KLUGE source does not have index attribute
760             if (exists $source->{index} && defined $source->{index}) {
761             $T->tagDelete('d'.$source->{index});
762             }
763             }
764              
765             # And clear the current selection
766             @$selected = ();
767              
768             }
769              
770             # Set up display styles
771             if ($T->depth > 1) {
772             @bold = (-background => "#eeeeee", qw/-relief raised -borderwidth 1/);
773             @normal = (-background => undef, qw/-relief flat/);
774             } else {
775             @bold = (qw/-foreground white -background black/);
776             @normal = (-foreground => undef, -background => undef);
777             }
778             $T->tag(qw/configure normal -foreground blue/);
779             $T->tag(qw/configure inactive -foreground black/);
780             $T->tag(qw/configure selected -foreground red/);
781             foreach ( @COLOR_LIST ){
782             $T->tag('configure',$_, -foreground => $_);
783             }
784              
785             # Get a reference coordinate from the object
786             my $ref = $self->Catalog->reference;
787              
788             # write the label
789             if ($ref) {
790             my ($az, $el) = $ref->azel();
791             my $summary = sprintf("%-15s Az: %3.0f El: %3.0f", $ref->name,
792             $az->degrees, $el->degrees );
793             $self->RefLabel("Reference position: $summary");
794             } else {
795             # blank it
796             $self->RefLabel( '' );
797             }
798              
799             # Insert the current values
800             if (defined $task && $task eq 'full') {
801             my $len = @sources;
802             for ($index=0; $index < $len; $index++) {
803             my $source = $sources[$index];
804             # KLUGE source does not have index attribute
805             $source->{index} = $index;
806             # KLUGE - source summary should add az, el and we should
807             # add distance
808             my $distance = " --- ";
809             if ($ref) {
810             my $d = $ref->distance($source);
811             if (defined $d) {
812             $distance = sprintf("%5.0f", $d->degrees);
813             } else {
814             $distance = " Inf";
815             }
816             }
817             my $custom = '';
818             if ($self->{'CustomColumns'}) {
819             $custom = join(' ', map {$_->{'generator'}->($stars[$index])}
820             @{$self->{'CustomColumns'}}) . ' ';
821             }
822             $line = sprintf("%-4d %s %3.0f %3.0f %s %s%s",$index, $source->summary(),
823             $source->az(format=>'d'),
824             $source->el(format=>'d'),
825             $distance,
826             $custom,
827             $source->comment
828             );
829             if ($self->isWithin ($source, @$selected)) {
830             $self->inswt("$line\n","d$index",'selected');
831             } else {
832             # KLUGE - source does not really have active or color attributes
833             # KLUGE2 - "active" is never set!
834             if ($source->{active}) {
835             if ($source->{color} ne '') {
836             $self->inswt("$line\n","d$index",$source->{color});
837             } else {
838             $self->inswt("$line\n","d$index",'normal');
839             }
840             } else {
841             $self->inswt("$line\n","d$index",'inactive');
842             }
843             }
844             }
845              
846             $len = @sources;
847             for ($itag=0; $itag < $len; $itag++) {
848             my $dtag = "d$itag";
849             $T->tag('bind', $dtag, '' =>
850             sub {
851             shift->tag('configure', $dtag, @bold);
852             }
853             );
854             $T->tag('bind', $dtag, '' =>
855             sub {
856             shift->tag('configure', $dtag, @normal);
857             }
858             );
859             $T->tag('bind', $dtag, '' =>
860             sub {
861             if (!$BUSY){
862             if (! $self->isWithin ($sources[substr($dtag,1,99)], @$selected) ) {
863             shift->tag('configure', $dtag, -foreground => 'red');
864             push (@$selected, $sources[substr($dtag,1,99)]);
865             } else {
866             # KLUGE - no color support in class
867             if ($sources[substr($dtag,1,99)]->{color} ne '') {
868             shift->tag('configure', $dtag, -foreground => $sources[substr($dtag,1,99)]->color());
869             } else {
870             shift->tag('configure', $dtag, -foreground => 'blue');
871             }
872             $self->remove ($sources[substr($dtag,1,99)], $selected);
873             }
874             }
875             }
876             );
877             $T->tag('bind', $dtag, '' => sub {
878             $BUSY = 1;
879             my $source = $sources[substr($dtag,1,99)];
880             push (@$selected, $source);
881             my $T = shift;
882             # my $callback = $self->UpDate;
883             # $callback->();
884             my $callback = $self->AddCommand;
885             # turn off tags
886             foreach $source (@$selected) {
887             # KLUGE source does not have index attribute
888             $T->tag('configure', 'd'.$source->{index}, -foreground => 'blue');
889             }
890             print " ref(@$selected) is selected \n" if $locateBug;
891             my @array = [1..2];
892             # $callback->(@array);
893             $callback->($selected);
894             $BUSY = 0;
895             @$selected = ();
896              
897             $self->destroy if $self->Transient;
898              
899             });
900             }
901             }
902              
903             $T->mark(qw/set insert 1.0/);
904              
905             # Disable access to infobox
906             $T->configure(-state=>'disabled');
907              
908             # Trigger an update callback
909             $self->UpDate->( $self );
910             }
911              
912             =item color
913              
914             returns a color from @COLOR_LIST and increments the latter's index
915              
916             $color = $cat->color();
917              
918             =cut
919              
920             ############################################################
921             # returns a color
922             #
923             sub getColor {
924             my $color = $COLOR_LIST[$COLOR_INDEX];
925             my $len = @COLOR_LIST;
926             $COLOR_INDEX++;
927             $COLOR_INDEX = $COLOR_INDEX % $len;
928             return $color;
929             }
930              
931             =item error
932              
933             Displays an error message in Tk
934              
935             $cat->error('Error message');
936              
937             =cut
938              
939             ############################################################
940             # Displays an error message in Tk
941             #
942             sub error {
943             my $MW = shift;
944             my $errWin = $MW->Toplevel(-borderwidth=>10);
945             $errWin->title('Observation Log Error!');
946             $errWin->resizable(0,0);
947             $errWin->Button(
948             -text => 'Ok',
949             -command => sub{
950             destroy $errWin;
951             })->pack(-side=>'bottom');
952             my $message = shift;
953             $errWin->Label (
954             -text => "\nError!\n\n ".$message." \n",
955             -relief=>'sunken'
956             )->pack(-side=>'bottom', -pady => 10);
957             $errWin->title(shift) if @_;
958             $MW->update;
959             $errWin->grab;
960             }
961              
962             =item inswt
963              
964             inswt inserts text into a given text widget and applies
965             one or more tags to that text.
966              
967             Parameters:
968             $text - Text to insert (it's inserted at the "insert" mark)
969             $args - One or more tags to apply to text. If this is empty
970             then all tags are removed from the text.
971              
972             $cat->inswt($text, $args);
973              
974             =cut
975              
976             ####################################################################
977             #
978             # Insert_With_Tags
979             #
980             # The procedure below inserts text into a given text widget and applies
981             # one or more tags to that text.
982             #
983             # Parameters:
984             # $text - Text to insert (it's inserted at the "insert" mark)
985             # $args - One or more tags to apply to text. If this is empty
986             # then all tags are removed from the text.
987             #
988             # Returns: Nothing
989             #
990             sub inswt {
991              
992             my $self = shift;
993             my $w = $self->Text;
994             my($text, @args) = @_;
995             my $start = $w->index('insert');
996              
997             $w->insert('insert', $text);
998             foreach my $tag ($w->tag('names', $start)) {
999             $w->tag('remove', $tag, $start, 'insert');
1000             }
1001             foreach my $i (@args) {
1002             $w->tag('add', $i, $start, 'insert');
1003             }
1004              
1005             } # end inswt
1006              
1007              
1008             =item getSource
1009              
1010             getSource prompts the user to enter source coords and name
1011             and filters the catalog based on the input provided.
1012              
1013             Takes the new top level widget to use, and the search button
1014             to be re-activated when this window closes.
1015              
1016             $obj = $cat->getSource($toplevel, $search_button);
1017              
1018             =cut
1019              
1020             sub getSource {
1021             my $self = shift;
1022             my $Top = shift;
1023             my $searchButton = shift;
1024             my @Epocs = ('RJ', 'RB');
1025             my %distances = (
1026             '15 degrees' => 15.0,
1027             '5 degrees' => 5.0,
1028             '1 degree' => 1.0,
1029             '30\'' => 0.5,
1030             '15\'' => 0.25,
1031             '5\'' => 1.0 / 12,
1032             '1\'' => 1.0 / 60,
1033             '30\'\'' => 0.5 / 60,
1034             '15\'\'' => 0.25 / 60,
1035             '5\'\'' => 1.0 / 12 / 60,
1036             '1\'\'' => 1.0 / 3600,
1037             );
1038             my $name;
1039              
1040             $Top->title('Source Plot');
1041             $Top->resizable(0,0);
1042             my $topFrame = $Top->Frame(-relief=>'groove', -borderwidth =>2, -width =>50)->pack(-padx=>10, -fill => 'x', -ipady=>10, -pady => 10);
1043              
1044             $topFrame->Label (
1045             -text => "Name:"
1046             )->grid(-column=>0, -row=>0);
1047             my $nameEnt = $topFrame->Entry(-relief=>'sunken',
1048             -width=>15)->grid(-column=>1, -row=>0, -padx =>10, -pady=>3);
1049              
1050             $topFrame->Label (
1051             -text => "Ra:"
1052             )->grid(-column=>0, -row=>1);
1053             my $raEnt = $topFrame->Entry(-relief=>'sunken',
1054             -width=>15)->grid(-column=>1, -row=>1, -padx =>10, -pady=>3);
1055              
1056             $topFrame->Label (
1057             -text => "Dec:"
1058             )->grid(-column=>0, -row=>2);
1059             my $decEnt = $topFrame->Entry(-relief=>'sunken',
1060             -width=>15)->grid(-column=>1, -row=>2, -padx =>10, -pady=>3);
1061              
1062             $topFrame->Label(-text => 'Distance:')->grid(-column => 0, -row => 3);
1063             my $distEnt = '1\'';
1064             my $distB = $topFrame->Menubutton(-text => $distEnt, -relief => 'raised',
1065             -width => 15);
1066             foreach my $dist (sort {$distances{$b} <=> $distances{$a}} keys %distances) {
1067             $distB->command(-label => $dist, -command => sub {
1068             $distB->configure(-text => $dist);
1069             $distEnt = $dist;
1070             });
1071             }
1072             $distB->grid(-column => 1, -row => 3, -padx => 10, -pady => 5, -sticky => 'w');
1073              
1074             $topFrame->Label (
1075             -text => "Epoc:"
1076             )->grid(-column=>0, -row=>4, -padx =>5, -pady=>5);
1077             my $epocEnt = 'RJ';
1078             my $epocB = $topFrame->Menubutton(-text => $epocEnt, -relief => 'raised',
1079             -width => 15);
1080             foreach $name (@Epocs) {
1081             $epocB->command(-label =>$name, -command=> sub{
1082             $epocB->configure( -text => $name );
1083             $epocEnt = $name;
1084             });
1085             }
1086             $epocB->grid(-column=>1, -row=>4, -padx =>10, -pady=>5, -sticky=>'w');
1087              
1088             my $buttonF = $Top->Frame->pack(-padx=>10, -pady=>10);
1089             $buttonF->Button(
1090             -text => 'Ok',
1091             -command => sub{
1092             my $name = $nameEnt->get(); undef $name if $name eq '';
1093             my $ra = $raEnt->get(); undef $ra if $ra eq '';
1094             my $dec = $decEnt->get(); undef $dec if $dec eq '';
1095              
1096             my $dec_tol = pi * $distances{$distEnt} / 180;
1097             my $ra_tol = $dec_tol * 15;
1098              
1099             # Filter by name if a name was specified.
1100              
1101             $self->Catalog()->filter_by_id($name) if defined $name;
1102              
1103             # Use Astro::Catalog's coordinate filter by distance
1104             # if possible.
1105              
1106             if (defined $ra and defined $dec) {
1107              
1108             my $coord = new Astro::Coords(ra => $ra, dec => $dec,
1109             type => $epocEnt eq 'RB' ? 'B1950' : 'J2000');
1110              
1111             $self->Catalog()->filter_by_distance($dec_tol,
1112             $coord);
1113             }
1114             elsif (defined $ra or defined $dec) {
1115             # Searching by RA or Dec alone isn't implemented
1116             # by Astro::Catalog, so use a callback filter.
1117              
1118             $ra = Astro::Coords::Angle::Hour->new(
1119             $ra, range => '2PI')->radians()
1120             if defined $ra;
1121             $dec = Astro::Coords::Angle->new($dec)->radians()
1122             if defined $dec;
1123              
1124             $self->Catalog()->filter_by_cb(sub {
1125             my $item = shift;
1126             my $coord = $item->coords();
1127             my ($item_ra, $item_dec) = map {$_->radians()}
1128             $epocEnt eq 'RB' ? $coord->radec1950()
1129             : $coord->radec();
1130              
1131             return ((! defined $ra or
1132             abs($item_ra - $ra) <= $ra_tol)
1133             and (! defined $dec or
1134             abs($item_dec - $dec) <= $dec_tol));
1135             });
1136             }
1137              
1138             $self->fillWithSourceList ('full');
1139             $Top->destroy();
1140             }
1141             )->pack(-side=>'right');
1142             $buttonF->Button(
1143             -text => 'Cancel',
1144             -command => sub{
1145             $Top->destroy();
1146             }
1147             )->pack(-side=>'right');
1148              
1149             $Top->bind('', sub {
1150             my $widget = shift;
1151             return unless $widget == $Top;
1152             $searchButton->configure(-state =>'normal');
1153             });
1154              
1155             $Top->update;
1156             $Top->grab;
1157             return;
1158             }
1159              
1160             =item isWithin
1161              
1162             isWithin returns a boolean value as to whether an element is
1163             within the array specified.
1164              
1165             $obj = $cat->isWithin($element, @array);
1166              
1167             =cut
1168              
1169             sub isWithin {
1170             my $self = shift;
1171             my $element = shift;
1172             my @array = @_;
1173             my $len = @array;
1174             foreach (@array) {
1175             # KLUGE - need an isEqual method rather than this. Will break
1176             # for none RA/Dec coordinates. Had to remove epoch check
1177             if ($element->name() eq $_->name() && $element->ra() eq $_->ra() && $element->dec() eq $_->dec()) {
1178             return 1;
1179             }
1180             }
1181             return 0;
1182             }
1183              
1184             =item remove
1185              
1186             Removes the item passed from the array specified.
1187              
1188             $obj = $cat->remove($element, @array);
1189              
1190             =cut
1191              
1192             sub remove {
1193             my $self = shift;
1194             my $element = shift;
1195             my $array = shift;
1196             my $len = @$array;
1197             my @temp;
1198             my $flag = 0;
1199              
1200             # KLUGE - epcc no longer required
1201             for (my $index = 0; $index < $len; $index++) {
1202             if ($element->name() eq $$array[$index]->name() && $element->ra() eq $$array[$index]->ra() && $element->dec() eq $$array[$index]->dec() ) {
1203             $flag = -1;
1204             } else {
1205             $temp[$index+$flag] = $$array[$index];
1206             }
1207             }
1208             @$array = @temp;
1209              
1210             }
1211              
1212             =back
1213              
1214             =head1 SEE ALSO
1215              
1216             L, L, L
1217              
1218             =head1 COPYRIGHT
1219              
1220             Copyright (C) 2013 Science & Technology Facilities Council.
1221             Copyright (C) 1999-2002,2004 Particle Physics and Astronomy Research Council.
1222             All Rights Reserved.
1223              
1224             =head1 AUTHOR
1225              
1226             Major subroutines and layout originally designed by Casey Best
1227             (University of Victoria) with modifications to create independent
1228             composite widget by Tim Jenness and Pam Shimek (University of
1229             Victoria)
1230              
1231             Revamped for Astro::Catalog by Tim Jenness.
1232              
1233             =cut
1234              
1235             1;