http://qs321.pair.com?node_id=31325
Category: PerlMonks Related Scripts
Author/Contact Info Shendal
Description: Uses zzamboni's PerlMonks.pm 2.0 to check newest nodes and put them in a graphical tree format.
Features:
- Tree format (expand/collapse)
- Colorization
- Launch browser by double clicking
- User preferences are saved
- Uses existing browser window, if available
#!/usr/bin/perl -w -- # -*-Perl-*-
#
# pmnewnodes
# Shendal, November 2000
#
# Tk browser to look at newest nodes
#
# Requires zzamboni's PerlMonk.pm version 2 or greater
#
# To-do:
#  - right-click currently does nothing. Would be nice to have it do s
+omething
#    Along the same line of thought, perhaps have ctl-click, shift-cli
+ck, etc, 
#    do something as well?
#  - Allow user to set order of top nodes and which to include/exclude
#  - 'Mark All Read' is confusing. I need a more clear way to say, "ma
+rk these 
#    nodes read locally" vs. updating perlmonks.
#  - Colorize nodes written by logged-in user?
#  - Add a way to login in as a user - currently, it relies on a pre-e
+xisting
#    cookie.
#  - Looks like update of PerlMonks date isn't working right...
#
# Version history:
# 0.5 11/2/00
#  - Uses new functions in PerlMonks.pm for keeping track of read/unre
+ad nodes
#  - Modes for a node are: Top (titles), new, read, unread or flagged.
+ Each has
#    its own associated color (user-defineable, naturally).
#  - After double clicking on a node, the thread is marked as read. Th
+is is user
#    configurable via a menu option.
#  - Number of unread nodes is now shown in the status bar
#  - Buttons now use bitmap icons to represent their action instead of
+ words. Added
#    balloon tool tips to show novice users what they do. Simply hover
+ over the icon.
#  - Added previous and next unread buttons.
#  - Option to launch browser on next/previous added (default is on).
#  - Redraw Tree moved to a debug menu enabled by -debug command line 
+option.
#
# 0.4 9/22/00
#  - Now uses a browser window that's already open, if available
#  - Support for colorization added
#  - F5 updates
#
# 0.3 9/7/00
#  - Update no longer collapses all nodes
#
# 0.2 9/7/00
#  - Enabled Mark All Read button
#  - When launching browser, now goes to node_id instead of node's tit
+le
#
# 0.1 9/6/00
#  - initial version
#
use strict; # always
use PerlMonks::NewestNodes;
use SDBM_File;
use Fcntl;
use Getopt::Long;

# Tk libs
use Tk 8.0;
use Tk::Tree;
use Tk::LabEntry;
use Tk::FileSelect;
use Tk::ItemStyle;
use Tk::Balloon;

# Unbuffered output to STDOUT
$|++;

# declarations
my $version = '0.5';
my $p;               # PerlMonks::NewestNodes object
my $Window;          # Window Tk widget
my $tree;            # scrolled tree Tk widget
my $statusbar;       # status bar label widget
my $choosebrowser_w; # choose browser window
my $browserfield;    # choose browser field
my %options;         # options hash
my %styles;          # item styles hash for colorization
my $status_idle = "pmnewnodes version $version idle"; # idle status te
+xt

# list of all the valid types with "nice" names
my %nodetypes = ( 'poem'                 => 'Poems',
          'categorized question' => 'Categorized Questions',
          'monkdiscuss'          => 'Perlmonks Discussion',
          'perlcraft'            => 'Perl Craft',
          'categorized answer'   => 'Categorized Answers',
          'obfuscated'           => 'Obfuscated Perl',
          'snippet'              => 'Code Snippets',
          'sourcecode'           => 'Code Catacombs',
          'review'               => 'Reviews',
          'perlmeditation'       => 'Meditations',
          'perlnews'             => 'Perl News',
          'perlquestion'         => 'Seekers of Perl Wisdom',
          'user'                 => 'User',
          'note'                 => 'Notes & Replies');

# URL to use for links into perlmonks & cpan
my $perlmonksURL      = 'http://www.perlmonks.org/index.pl?node=';
my $perlmonksURL_id   = 'http://www.perlmonks.org/index.pl?node_id=';

# options hash
tie(%options,'SDBM_File',"$ENV{HOME}/.monknewnodes",O_RDWR|O_CREAT,064
+0);
my %default_options = (browser        => undef,
               background     => 'white',
               auto_mark_read => 1,
               auto_launch    => 1,
               color_top      => 'black',
               color_unread   => 'black',
               color_read     => '#adadad',
               color_new      => 'blue',
               color_flag     => 'red',
               );

# images
my %bitmaps = ('previous' => '#define previous_width 16
#define previous_height 16
static unsigned char previous_bits[] = {
   0x08, 0x10, 0x08, 0x18, 0x08, 0x1c, 0x08, 0x1e, 0x08, 0x1f, 0x88, 0
+x1f,
   0xc8, 0x1f, 0xe8, 0x1f, 0xc8, 0x1f, 0x88, 0x1f, 0x08, 0x1f, 0x08, 0
+x1e,
   0x08, 0x1c, 0x08, 0x18, 0x08, 0x10, 0x00, 0x00};
',
           'next' => '#define next_width 16
#define next_height 16
static unsigned char next_bits[] = {
   0x08, 0x10, 0x18, 0x10, 0x38, 0x10, 0x78, 0x10, 0xf8, 0x10, 0xf8, 0
+x11,
   0xf8, 0x13, 0xf8, 0x17, 0xf8, 0x13, 0xf8, 0x11, 0xf8, 0x10, 0x78, 0
+x10,
   0x38, 0x10, 0x18, 0x10, 0x08, 0x10, 0x00, 0x00};
',
           'update' => '#define update_width 16
#define update_height 16
static unsigned char update_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xf0, 0x07, 0x38, 0x0e, 0x1c, 0
+x1c,
   0x0c, 0x1c, 0x0e, 0x1c, 0x0e, 0x1c, 0x86, 0xff, 0x06, 0x7f, 0x06, 0
+x3e,
   0x0c, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00};
',
           'mark' => '#define /home/dharrel/mark.bmp_width 16
#define /home/dharrel/mark.bmp_height 16
static unsigned char /home/dharrel/mark.bmp_bits[] = {
   0x00, 0x00, 0x00, 0x70, 0x00, 0x3c, 0x00, 0x0e, 0x00, 0x07, 0x00, 0
+x03,
   0x80, 0x01, 0x9c, 0x01, 0xbe, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0xe0, 0
+x00,
   0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00};
',
           'markthread' => '#define markthread_width 16
#define markthread_height 16
static unsigned char markthread_bits[] = {
   0x00, 0x00, 0x00, 0x38, 0x00, 0x1e, 0x00, 0x07, 0x80, 0x03, 0x80, 0
+x01,
   0xc0, 0x08, 0xce, 0x08, 0x5f, 0x08, 0x7c, 0x7f, 0x78, 0x08, 0x70, 0
+x08,
   0x60, 0x08, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00};
',
           'markall' => '#define markall_width 16
#define markall_height 16
static unsigned char markall_bits[] = {
   0x00, 0x00, 0x00, 0x70, 0x00, 0x3c, 0x00, 0x0e, 0x00, 0x07, 0x00, 0
+x03,
   0x80, 0x01, 0x9c, 0x01, 0xbe, 0x00, 0xf8, 0xa4, 0xf0, 0xaa, 0xe0, 0
+xae,
   0xc0, 0xaa, 0xc0, 0xaa, 0x00, 0x00, 0x00, 0x00};
',
           'expandall' => '#define expandall_width 16
#define expandall_height 16
static unsigned char expandall_bits[] = {
   0x00, 0x00, 0xfe, 0x03, 0x02, 0x02, 0x22, 0x1e, 0x22, 0x12, 0xfa, 0
+x72,
   0x22, 0x52, 0x22, 0x52, 0x02, 0x52, 0x02, 0x52, 0xfe, 0x53, 0x08, 0
+x50,
   0xf8, 0x5f, 0x20, 0x40, 0xe0, 0x7f, 0x00, 0x00};
',
           'collapseall' => '#define collapseall_width 16
#define collapseall_height 16
static unsigned char collapseall_bits[] = {
   0x00, 0x00, 0xfe, 0x03, 0x02, 0x02, 0x02, 0x1e, 0x02, 0x12, 0xfa, 0
+x72,
   0x02, 0x52, 0x02, 0x52, 0x02, 0x52, 0x02, 0x52, 0xfe, 0x53, 0x08, 0
+x50,
   0xf8, 0x5f, 0x20, 0x40, 0xe0, 0x7f, 0x00, 0x00};
',
           );

# set options to default unless they have alreayd been set
foreach (keys %default_options) {
    next if (/^browser$/);
    unless (defined $_ && defined $options{$_} ) {
    $options{$_} = $default_options{$_};
    }
}

# parse options
use vars '$debug'; # debug mode
GetOptions("debug" => \$debug)
    or die "Only supported option is -debug.\n";

######################################################################
+##########
# go
&initWindow;
&getInfo;
&buildTree;
MainLoop();


######################################################################
+########## 
sub initWindow { $Window = MainWindow->new(-title => "Perlmonks Newest
+Nodes"); 

    # add menubar
    my $menu = $Window->Menu;
    $Window->configure(-menu => $menu);

    # build menubar
    my $file_menu    = $menu->cascade(-label   => '~File',
                      -tearoff => 0);
    my $action_menu  = $menu->cascade(-label   => '~Action',
                      -tearoff => 0);
    my $options_menu = $menu->cascade(-label   => '~Options',
                      -tearoff => 0);
    my $debug_menu   = $menu->cascade(-label   => '~Debug',
                      -tearoff => 0) if ($debug);
    # build individual menus
    $file_menu->command   (-label     => 'Exit',
               -underline => 1,
               -command   => sub { exit; });
    $action_menu->command (-label     => 'Mark Node Read',
               -underline => 10,
               -command   => sub { &mark('read'); } );
    $action_menu->command (-label     => 'Mark Node Unread',
               -underline => 10,
               -command   => sub { &mark('unread'); } );
    $action_menu->command (-label     => 'Mark Node New',
               -underline => 10,
               -command   => sub { &mark('new'); } );
    $action_menu->command (-label     => 'Flag Node',
               -underline => 1,
               -command   => sub { &mark('flag'); } );
    $action_menu->separator();
    $action_menu->command (-label     => 'Mark Thread Read',
               -underline => 5,
               -command   => sub { &markThread('read'); } );
    $action_menu->command (-label     => 'Mark Thread Unread',
               -command   => sub { &markThread('unread'); } );
    $action_menu->command (-label     => 'Mark Thread New',
               -command   => sub { &markThread('new'); } );
    $action_menu->command (-label     => 'Flag Thread',
               -command   => sub { &markThread('flag'); } );
    $action_menu->separator();
    $action_menu->command (-label     => 'Mark All Read',
               -underline => 5,
               -command   => sub { &markAllRead; } );
    $action_menu->command (-label     => 'Update Perlmonks All-read Ti
+me',
               -underline => 7,
               -command   => sub { &markAllRead; $p->clear_nn; } );
    $action_menu->separator();
    $action_menu->command (-label     => 'Expand All',
               -underline => 0,
               -command   => sub { &expandAll; } );
    $action_menu->command (-label     => 'Collapse All',
               -underline => 0,
               -command   => sub { &collapseAll; } );
    $action_menu->separator();
    $action_menu->command (-label     => 'Update',
               -command   => sub { &getInfo; &buildTree; });
    $options_menu->command(-label     => 'Topnode Color',
               -underline => 0,
               -command   => sub { &setColor('top'); } );
    $options_menu->command(-label     => 'Read Color',
               -underline => 0,
               -command   => sub { &setColor('read'); } );
    $options_menu->command(-label     => 'Unread Color',
               -underline => 0,
               -command   => sub { &setColor('unread'); } );
    $options_menu->command(-label     => 'New Color',
               -underline => 0,
               -command   => sub { &setColor('new'); } );
    $options_menu->command(-label     => 'Flagged Color',
               -underline => 0,
               -command   => sub { &setColor('flag'); } );
    $options_menu->command(-label     => 'Background Color',
               -underline => 0,
               -command   => sub { &setBackground; } );
    $options_menu->separator();
    $options_menu->command(-label     => 'Reset Colors to Default',
               -command   => sub { &resetColors; });
    $options_menu->command(-label     => 'Save Color Settings',
               -underline => 0,
               -command   => sub { &saveColors; });
    $options_menu->separator();
    $options_menu->checkbutton(-label       => 'Auto Mark Threads as R
+ead on Dbl-Click',
                   -onvalue     => 1,
                   -offvalue    => 0,
                   -indicatoron => 1,
                   -underline   => 0,
                   -variable    => \$options{auto_mark_read});
    $options_menu->checkbutton(-label       => 'Auto Launch Browser on
+ Next/Previous',
                   -onvalue     => 1,
                   -offvalue    => 0,
                   -indicatoron => 1,
                   -underline   => 0,
                   -variable    => \$options{auto_launch});
    $options_menu->command(-label     => 'Choose Browser',
               -underline => 0,
               -command   => \&chooseBrowser);

    # Only show debug menu in debug mode
    if ($debug) {
    $debug_menu->command (-label     => 'Redraw Tree',
                  -command   => sub { $tree->delete('all'); &getInfo; 
+&buildTree; });
    }

    # create frames
    my $uframe =$Window->Frame()->pack(-side   => 'top',
                    -fill   => 'both',
                    -expand => 1);
    my $mframe =$Window->Frame()->pack(-side   =>'top',
                    -fill   => 'x');
    my $bframe =$Window->Frame()->pack(-side   =>'top',
                    -fill   => 'x');

    # tree
    $tree = $uframe->Scrolled("Tree",
                  -width            => 80,
                  -height           => 5,
                  -background       => "$options{background}",
                  -selectbackground => "$options{background}",
                  -itemtype         => 'text',
                  -separator        => '.',
                  -selectmode       => 'single',
                  -relief           => 'sunken',
                  -scrollbars       => 'osoe',
                  -command          => \&command,
                  )->pack(-side     => 'top',
                      -fill     => 'both',
                      -expand   => 1);
    
    # status bar
    $statusbar = $bframe->Label(-text   => "$status_idle",
                -relief => 'sunken',
                )->pack(-side   => 'left',
                    -fill   => 'x',
                    -expand => 1);

    # button bitmaps
    foreach (keys %bitmaps) { $Window->Bitmap($_,-data => $bitmaps{$_}
+); }

    # buttons
    my $btn_expandAll   = $mframe->Button(-text         => "Expand All
+",
                      -image        => 'expandall',
                      -command      => \&expandAll,
                      -height       => 16,
                      -width        => 16,
                      )->pack(-side => 'left',
                          -padx => 2,
                          -pady => 2);
    my $btn_collapseAll = $mframe->Button(-text         => "Collapse A
+ll",
                      -image        => 'collapseall',
                      -command      => \&collapseAll,
                      -height       => 16,
                      -width        => 16,
                      )->pack(-side => 'left',
                          -padx => 2,
                          -pady => 2);
    my $btn_mark        = $mframe->Button(-text         => "Mark",
                      -image        => 'mark',
                      -command      => sub { &mark('read'); },
                      -height       => 16,
                      -width        => 16,
                      )->pack(-side => 'left',
                          -padx => 2,
                          -pady => 2);
    my $btn_markThread  = $mframe->Button(-text         => "Mark Threa
+d",
                      -image        => 'markthread',
                      -command      => sub { &markThread('read'); },
                      -height       => 16,
                      -width        => 16,
                      )->pack(-side => 'left',
                          -padx => 2,
                          -pady => 2);
    my $btn_markAllRead = $mframe->Button(-text         => "Mark All R
+ead",
                      -image        => 'markall',
                      -command      => sub { &markAllRead; },
                      -height       => 16,
                      -width        => 16,
                      )->pack(-side => 'left',
                          -padx => 2,
                          -pady => 2);
    my $btn_update     = $mframe->Button(-text         => "Update",
                     -image        => 'update',
                     -command      => sub { &getInfo; &buildTree; },
                     -height       => 16,
                     -width        => 16,
                     )->pack(-side => 'left',
                         -padx => 2,
                         -pady => 2);
    my $btn_previous   = $mframe->Button(-text         => "Previous",
                     -image        => 'previous',
                     -command      => sub { &previousUnread; },
                     -height       => 16,
                     -width        => 16,
                     )->pack(-side => 'left',
                         -padx => 2,
                         -pady => 2);
    my $btn_next       = $mframe->Button(-text         => "Next",
                     -image        => 'next',
                     -command      => sub { &nextUnread; },
                     -height       => 16,
                     -width        => 16,
                     )->pack(-side => 'left',
                         -padx => 2,
                         -pady => 2);

    # Button Balloons
    my $balloon = $Window->Balloon;
    $balloon->attach($btn_expandAll,  -balloonmsg => 'Expand All Threa
+ds');
    $balloon->attach($btn_collapseAll,-balloonmsg => 'Collapse All Thr
+eads');
    $balloon->attach($btn_mark,       -balloonmsg => 'Mark Node Read')
+;
    $balloon->attach($btn_markThread, -balloonmsg => 'Mark Thread Read
+');
    $balloon->attach($btn_markAllRead,-balloonmsg => 'Mark All Nodes R
+ead');
    $balloon->attach($btn_update,     -balloonmsg => 'Update from Perl
+Monks.org');
    $balloon->attach($btn_previous,   -balloonmsg => 'Previous Unread 
+Node');
    $balloon->attach($btn_next,       -balloonmsg => 'Next Unread Node
+');

    # define item styles for colorization
    foreach (keys %options) {
    next unless (/^color_/);
    $styles{$_} = $tree->ItemStyle('text',
                       -stylename        => "$_",
                       -foreground       => "$options{$_}",
                       -selectforeground => "$options{$_}",
                       -background       => "$options{background}",
                       -selectbackground => "$options{background}");
    }
   
    # key bindings
    $Window->bind('<F5>',sub { &getInfo; &buildTree; });
}

######################################################################
+##########
sub status {
    my $msg = shift;
    unless ($msg) {
    my $entryPath = 1;
    my $unread = 0;
    while ($tree->info('exists',$entryPath)) {
        my $href = $tree->info('data',$entryPath);
        if (defined $href) {
        $unread++ unless ($p->is_read($tree->info('data',$entryPath)->
+{node_id}));
        }
        $entryPath = $tree->info('next',$entryPath);
    }
    $msg = "$status_idle ($unread unread)";
    }
    $statusbar->configure(-text => "$msg");
    $statusbar->update;
}

######################################################################
+##########
sub getInfo {
    &status("Getting info...");
    unless (defined $p) {
    $p = PerlMonks::NewestNodes->new();
    $p->add_cookies;
    }
    $p->refresh_nn;
    &status;
}

######################################################################
+##########
sub buildTree {
    &status("Building tree...");
    
    my $pos = 1;
    foreach my $nodetype (keys %nodetypes) {
    # if the top node doesn't exist in the tree, add it
    unless ($tree->info('exists',$pos)) {
        $tree->add($pos,
               -text  => "$nodetypes{$nodetype}",
               -style => 'color_top');
    }
    $p->visit_threads("$nodetype",sub { &callback(@_,$pos); } );
    if ($tree->info('children',$pos)) {
        $tree->show('entry',$pos) if ($pos =~ /^\d+$/); # make sure to
+pnode is shown
        if ($tree->getmode($pos) eq 'none') {
        $tree->setmode($pos,'close');
        $tree->close($pos);
        }
    } else {
        $tree->hide('entry',$pos);
    }
    $pos++;
    }
    # set proper mode everywhere
    my $entryPath = 1;
    while ($tree->info('exists',$entryPath)) {
    my $prevEntryPath = $entryPath;
    $entryPath = $tree->info('next',$prevEntryPath);
    next unless ($tree->getmode($prevEntryPath) eq 'none');
    next unless ($tree->info('children',$prevEntryPath));
    $tree->setmode($prevEntryPath,'close');
    $tree->close($prevEntryPath);
    }
    &status;
}

sub callback {
    my $href   = shift;
    my $parent = shift;
    die "Internal hemmorage at callback!\n" unless ($href && $parent);
    my $author = $p->{cache_nn}->{AUTHOR}->{ $href->{author_user} }->{
+content};
    my $date = "$href->{createtime}";
    # reformat date string -- this is a guess, don't know if it's righ
+t
    $date =~ s/^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/$2\/$3\/$1 $
+4:$5:$6/;
    my$item = "$href->{content} ($author) [$date]";

    # check for existence of child before just adding it by checking i
+f 
    # any of the $parent's nodes have the same value for data as the $
+href 
    # we're trying to add
    my $entryPath = 0;
    foreach ($tree->info('children',$parent)) {
    if ($tree->info('data',$_) eq $href) { $entryPath = $_; last; }
    }

    # unless the kid's already there, add it
    unless ($entryPath) {
    my $color = $p->is_read($href->{node_id}) ? 'color_read' : 'color_
+new';
    $entryPath = $tree->addchild($parent,
                     -text  => "$item",
                     -data  => $href,
                     -style => $color);
    $href->{entryPath} = $entryPath;
    }

    # if the kid's got kids, recurse
    if (exists ($href->{kids})) {
    foreach my $kid (@{$href->{kids}}) {
        callback($kid,$entryPath);
    }
    }
}

######################################################################
+##########
sub command {
    # invoked when user double-clicks an entry
    my $entryPath = shift;
    my $href = $tree->info('data',$entryPath);
    return unless ($href);
    $tree->configure(-selectforeground => $options{color_read});
    unless ($^O =~ /MSWin32/i || defined $options{'browser'}) {
    print STDERR "No browser defined\n";
    print STDERR "Use Options->Choose browser menu to define one.\n";
    return -1;
    }
    my $url = $perlmonksURL_id . $href->{node_id};
    &status("Launch browser to \"$url\"!");
    my $browser = $options{'browser'} || '';
    if ($^O =~ /MSWin32/i) {
    my $process;
    $browser =~ s/\\/\\\\/g;
    $browser =~ s/\"//g;
    $browser =~ /\\(\S+)$/;
    my $pgm = $1;
    eval '
        use Win32::Process;
        Win32::Process::Create($process,
                                   "$ENV{SYSTEMROOT}\\\\system32\\\\cm
+d.exe",
                                   "cmd.exe /c start $url",
                                   0,DETACHED_PROCESS,".") ||
          printError("Unable to launch browser: " . Win32::FormatMessa
+ge(Win32::GetLastError()));
    ';
    } else {
    eval '
        my $pid = fork;
            $url =~ s/\s/+/g;
        if ($pid == 0) {
        exec "$browser $url";
        }';
    }
    if ($@) { print STDERR "Error launching: $@\n"; }
    # Mark read if auto_mark_read is set
    &markThread('read',$entryPath) if ($options{'auto_mark_read'});
    &status;
}

######################################################################
+##########
sub expandAll {
    my $entryPath = 1;
    while ($tree->info('exists',$entryPath)) {
    $tree->open($entryPath);
    $entryPath = $tree->info('next',$entryPath);
    }
    $tree->update; # forces scrollbar to update
}

######################################################################
+##########
sub collapseAll {
    my $entryPath = 1;
    while ($tree->info('exists',$entryPath)) {
    $tree->close($entryPath);
    $entryPath = $tree->info('next',$entryPath);
    }
    $tree->selectionClear;
    $tree->update; # forces scrollbar to update
}

######################################################################
+##########
sub mark {
    my $type       = shift;
    my @entryPaths = @_;
    @_ = ();
    push @entryPaths, $tree->info('selection') unless (@entryPaths);
    return unless (@entryPaths);
    &status("Marking nodes...");
    my @topnodes = $tree->info('children');
    foreach my $entryPath (@entryPaths) {
    next if (grep /^$entryPath$/, @topnodes);
    if ($type eq 'read') {
        my $href = $tree->info('data',$entryPath);
        $p->mark_read_nodes($href->{node_id}) if ($href);
        $tree->entryconfigure($entryPath,
                  -style => 'color_read');
    } elsif ($type eq 'unread') {
        my $href = $tree->info('data',$entryPath);
        $p->mark_unread_nodes($href->{node_id}) if ($href);
        $tree->entryconfigure($entryPath,
                  -style => 'color_unread');
    } else {
        $tree->entryconfigure($entryPath,
                  -style => "color_$type");
    }
    }
    &status;
}

######################################################################
+##########
sub markThread {
    my $type = shift;
    my @entryPaths = @_;
    push @entryPaths, $tree->info('selection') unless (@entryPaths);
    return unless (@entryPaths);
    push @entryPaths, &getChildren(@entryPaths);
    &mark($type,@entryPaths);
}

######################################################################
+##########
sub markAllRead {
    &markThread('read',$tree->info('children'));
}

######################################################################
+##########
sub getChildren {
    my @entryPaths = @_;
    return unless (@entryPaths);
    my @kids;
    foreach my $entryPath (@entryPaths) {
    push @kids, $tree->info('children',$entryPath);
    push @kids, &getChildren($tree->info('children',$entryPath));
    }
    return @kids;
}

######################################################################
+##########
sub nextUnread {
    my $entryPath = $tree->info('selection') || 1;
    while ($entryPath = $tree->info('next',$entryPath)) {
    last unless $entryPath;
    my $href = $tree->info('data',$entryPath);
    next unless ($href->{node_id});
    unless ($p->is_read($href->{node_id})) {
        # We found the next unread node, so show it!
        &select($entryPath);
        last;
    }
    }
}

######################################################################
+##########
sub previousUnread {
    my $entryPath = $tree->info('selection');
    unless ($entryPath) {
    # We don't have a selection, so find the last entryPath
    $entryPath = 1;
    while (1) {
        my $next = $tree->info('next',$entryPath);
        last unless $next;
        $entryPath = $next;
    }
    } else {
    $entryPath = $tree->info('prev',$entryPath);
    }
    while ($entryPath) {
    my $href = $tree->info('data',$entryPath);
    if ($href->{node_id} && ! $p->is_read($href->{node_id})) {
        # We found the next unread node, so show it!
        &select($entryPath);
        last;
    }
    $entryPath = $tree->info('prev',$entryPath);
    }
}

######################################################################
+##########
sub select {
    my $entryPath = shift;
    return unless ($tree->info('exists',$entryPath));
    my $parent = $entryPath;
    $tree->open($parent) while ($parent = $tree->info('parent',$parent
+));
    $tree->selectionClear;
    $tree->see($entryPath);
    $tree->selectionSet($entryPath);
    $tree->update;
    &command($entryPath) if ($options{'auto_launch'});
}

######################################################################
+##########
# Prompts the user to select the browser executable
sub chooseBrowser {
    &status("Updating browser information...");
    if (!$choosebrowser_w) {
    $choosebrowser_w = $Window->Toplevel(-takefocus=>1,
                         -title  => "Choose browser executable");
    $choosebrowser_w->withdraw();
    $choosebrowser_w->transient($Window);

    $browserfield = $choosebrowser_w->LabEntry(-label     => "Executab
+le:",
                           -width     => 40,
                           -labelPack => [-side => 'left' ]
                           )->pack;
    $choosebrowser_w->Button(-text     => "Browse",
                 -command  => sub { my $fsref = $choosebrowser_w->File
+Select;
                            my $file  = $fsref->Show;
                            $browserfield->configure(-textvariable=>\$
+file);
                        }
                 )->pack(-side => 'left',
                     -padx => 5,
                     -pady => 2);
    $choosebrowser_w->Button (-text    => "Ok",
                  -command => \&chooseBrowser_ok
                  )->pack(-side => 'right',
                      -padx => 5,
                      -pady => 2);
    $choosebrowser_w->Button(-text    => "Cancel",
                 -command => sub { $choosebrowser_w->grabRelease;
                           $choosebrowser_w->withdraw;
                           }
                 )->pack(-side => 'right',
                     -padx => 5,
                     -pady => 2);
    }
    
    $browserfield->configure(-textvariable=>\$options{'browser'}) if (
+defined $options{'browser'});
    $choosebrowser_w->Popup;
    $browserfield->focus;
    $choosebrowser_w->protocol('WM_DELETE_WINDOW',sub {;}); #handle wi
+ndow 'x' button
    $choosebrowser_w->grabGlobal;
    
    &status;
}

sub chooseBrowser_ok { 
    my $potential_browser = $browserfield->get;
    $potential_browser =~ s/\//\\/g if ($^O =~ /Win32/i);
    unless ($potential_browser) {
    print STDERR "No browser specified.  Nothing changed.\n";
    $choosebrowser_w->grabRelease;
    $choosebrowser_w->withdraw;
    return;
    }
    if (! -e "$potential_browser") {
    print STDERR "Could not locate browser executable.\n";
    } else {
    $options{'browser'} = $potential_browser;
    }
    $choosebrowser_w->grabRelease;
    $choosebrowser_w->withdraw;
}

######################################################################
+##########
sub setColor {
    my $type  = shift;
    my $color = $Window->chooseColor(-initialcolor => $options{"color_
+$type"},
                     -title        => ucfirst($type) . ' Color');
    return unless $color;
    $styles{"color_$type"}->configure(-foreground       => $color,
                      -selectforeground => $color);
}

######################################################################
+##########
sub setBackground {
    my $color = $Window->chooseColor(-initialcolor => $tree->cget(-bg)
+,
                     -title        => 'Background Color');
    $tree->configure(-bg => $color);
    foreach (keys %styles) {
    $styles{$_}->configure(-background       => $color,
                   -selectbackground => $color);
    }
}

######################################################################
+##########
sub resetColors {
    foreach (keys %default_options) {
    next unless (/^color_/);
    $styles{$_}->configure(-foreground       => $default_options{$_},
                   -selectforeground => $default_options{$_},
                   -background       => $default_options{background},
                   -selectbackground => $default_options{background},)
+;
    $options{$_} = $default_options{$_};
    }
    $tree->configure(-bg => $default_options{'background'});
    $options{'background'} = $default_options{'background'};
}

######################################################################
+##########
sub saveColors {
    foreach (keys %styles) {
    $options{$_} = $styles{$_}->cget(-foreground);
    }
    $options{'background'} = $tree->cget(-bg);
}