<?xml version="1.0" encoding="windows-1252"?>
<node id="19420" title="mchattk" created="2000-06-22 12:10:35" updated="2005-08-14 20:34:03">
<type id="1748">
sourcecode</type>
<author id="17500">
ase</author>
<data>
<field name="doctext">
&lt;code&gt;
#!/usr/bin/perl -w # -*-Perl-*-
#
# NOTE: portions shamefully borrowed/mutilated from Shendal's monkchat
#       some of the comments are Shendal's.
#       My comments are denoted with (ase)
#
# (ase) mchattk
# Adapted from:
# monkchat
# Shendal, June 2000
#
# Special thanks to zzamboni who created PerlMonksChat.pm
# Very special thanks to vroom for creating PerlMonks.org
# Oh, and Larry Wall's okay in my book for making perl
#
# (ase) And thanks to Shendal for the Win32:GUI code
#
# Notes:
#  - When I output to the chatterbox window, the script needs
#    to append the output to the end of the buffer.  Currently,
#    Win32::GUI doesn't have a nice way to do this.  Instead,
#    I have to select the end of the buffer and then do a 
#    ReplaceSel.  It's kludgy, but it works.
# (ase) Tk::Text widget doesn't have this problem fortunately :)
#
# To-do:
#  - while getting data from the website, the gui locks up
#    this is really annoying, but I can't figure out how 
#    to get around it.  I put a status bar there to help
#    let the user know what's going on, but it still locks.
# (ase) This is also a problem with this Tk version...
#
#  - chatterbox doesn't automatically scroll down when new
#    chatter is coming in.  I cannot find the method to 
#    move down on every insert.
# (ase) I've added the $Chatterbox-&gt;see('end') in &amp;printMessage to fix this.
#
#  - hitting return doesn't send message - I'm not sure
#    how to bind this
# (ase) In Tk one just uses $widget-&gt;bind("&lt;Return&gt;",\&amp;sub_name)
#
#  - userlist should probably be double-clickable to get info
#    on selected user (by launching a browser?)
# (ase) not implemented in this tk version yet either
#
# Version history:
#
# 0.9.2 6/16/00
#  - XP progress bar is more accurate: now reports % of way
#    from current level to next
# 0.9.1 6/16/00
#  - fixed private message formatting
#  - text now inserted at bottom of buffer
#  - added /checkoff, /co for checking off private messages
#  - added /msgs to re-print unchecked private messages
#  - sent private messages now appear in chatterbox buffer
#  - added color
# 0.9 6/15/00
#  - initial release
#
use strict;
use Tk 8.0;
use Tk::LabEntry;
use SDBM_File;
use PerlMonksChat;
use Fcntl;

# Version info
my $version     = '0.9.2';
my $status_idle = "mchattk version $version is idle";

# Polling itervals (in milliseconds)
# Set to zero to disable
my $interval_chat      = 15000;	# 15 secs
my $interval_xp        = 600000; # 10 mins
my $interval_userlist  = 60000;	# 1 mins

# Colors (ase) Note: I changed this to a hash for ease of tieing.
my %color;

tie(%color,'SDBM_File',"$ENV{HOME}/.mctk",O_RDWR|O_CREAT,0640);

my %default_color=(default=&gt; 'black',
	private  =&gt; 'purple',
	username =&gt; 'blue',
	message  =&gt; 'green',
	error    =&gt; 'red',
	background =&gt; 'white', #chatwindow backround color
);

#set colors to default unless they have alreayd been set
for my $option (keys %default_color) {
    if ($color{$option} eq "") {
    	$color{$option} = $default_color{$option}
	}
}

# perlmonk levels
# the xp xml ticker doesn't return this, so we'll have to hard code it
my %perlmonk_levels = (1 =&gt; 0,
			2 =&gt; 20,
			3 =&gt; 50,
			4 =&gt; 100,
			5 =&gt; 200,
			6 =&gt; 500,
			7 =&gt; 1000,
			8 =&gt; 1600,
			9 =&gt; 2300,
			10 =&gt; 3000);

# This is the beast that drives everything
my $p;				# perlmonkschat object

# user information
my ($user,$passwd);


# GUI Objects (Tk objects)
my($Window);			# The over-all window object
my($Chatfield);			# object that displays all the chat text
my($Userlist);			# userlist listbox
my($UserlistLabel);		# displays number of users logged in
my($Inputfield);		# object that allows the user to type their own message
my($SayButton);			# send text button
my($Progress);			# progress bar intended to show xp &amp; next level
my($XPLabel);			# displays XP information on the screen
my($Status);			# well, a status bar (ase) in this case a Tk canvas object
my($userinfo_w);		# userinformation window
my($unField,$pwField,$confField);

# Status vars
my ($prect,$ptext); #XP canvas items

# here we go!
&amp;initWindow;
&amp;initChat;
MainLoop();

################################################################################
#
# initWindow
#
# Initialize the GUI window
#
sub initWindow {

  $Window = MainWindow-&gt;new(
			    -title  =&gt; "Perlmonks Chat",
			    -width  =&gt; 600,
			    -height =&gt; 400,
				  );
  my $menubar = $Window-&gt;Menu;
  $Window-&gt;configure(-menu =&gt; $menubar);

  my $file_mb = $menubar-&gt;cascade(-label =&gt; '~File',-tearoff =&gt; 0);
  my $update_mb = $menubar-&gt;cascade(-label =&gt; '~Update',-tearoff =&gt; 0);
  my $options_mb = $menubar-&gt;cascade(-label =&gt; '~Options',-tearoff =&gt; 0);

  $file_mb-&gt;command(-label         =&gt; 'Exit',
		    -underline =&gt; 1,
		    -command   =&gt; sub {exit(0)} );

  $update_mb-&gt;command(-label =&gt; 'Chatterbox',
		      -underline =&gt; 0,
		      -command =&gt; \&amp;updChatterbox_Click);

  $update_mb-&gt;command(-label =&gt; 'XP',
		      -underline =&gt; 0,
		      -command =&gt; \&amp;updXP_Click);

  $update_mb-&gt;command(-label =&gt; 'Userlist',
		      -underline =&gt; 0,
		      -command =&gt; \&amp;updUserlist_Click);

  $update_mb-&gt;separator();

  $update_mb-&gt;command(-label =&gt; 'Username/passwd',
		      -underline =&gt; 9,
		      -command =&gt; \&amp;updUsername_Click);

  $options_mb-&gt;command(-label=&gt; 'Chat Background',
                       -underline =&gt; 0,
                       -command=&gt; 
        sub { $Chatfield-&gt;configure(-bg=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;cget(-bg),
                            -title =&gt; "Background Color"))
            }
                      );

  $options_mb-&gt;command(-label=&gt; 'Default text',
                       -underline =&gt; 0,
                       -command=&gt; 
	    sub { $Chatfield-&gt;tagConfigure('default',-foreground=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;tagCget('default',-foreground),
                            -title =&gt; "Default Text Color"));
            }
                      );

  $options_mb-&gt;command(-label=&gt; 'Private text',
                       -underline =&gt; 0,
                       -command=&gt; 
	    sub { $Chatfield-&gt;tagConfigure('private',-foreground=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;tagCget('private',-foreground),
                            -title =&gt; "Received Private /msg Text Color"));
            }
                      );

  $options_mb-&gt;command(-label=&gt; 'Username text',
                       -underline =&gt; 0,
                       -command=&gt; 
	    sub { $Chatfield-&gt;tagConfigure('username',-foreground=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;tagCget('username',-foreground),
                            -title =&gt; "Username Text Color"));
            }
                      );

  $options_mb-&gt;command(-label=&gt; 'Message text',
                       -underline =&gt; 0,
                       -command=&gt; 
	    sub { $Chatfield-&gt;tagConfigure('message',-foreground=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;tagCget('message',-foreground),
                            -title =&gt; "Sent Private /msg Text Color"));
            }
                      );

  $options_mb-&gt;command(-label=&gt; 'Error text',
                       -underline =&gt; 0,
                       -command=&gt; 
	    sub { $Chatfield-&gt;tagConfigure('error',-foreground=&gt;$Window-&gt;
                chooseColor(-initialcolor=&gt; $Chatfield-&gt;tagCget('error',-foreground),
                            -title =&gt; "Error Text Color"));
            }
                      );

  $options_mb-&gt;separator();

  $options_mb-&gt;command(-label=&gt; 'Save Settings',
                       -underline=&gt; 0,
                       -command=&gt;\&amp;save_settings);

  $options_mb-&gt;command(-label=&gt; 'Reset to defaults',
                       -underline=&gt; 0,
                       -command=&gt;\&amp;reset_settings);

  my $uframe=$Window-&gt;Frame()-&gt;pack(-side=&gt;'top');
  my $lframe=$uframe-&gt;Frame()-&gt;pack(-side=&gt;'left');
  my $rframe=$uframe-&gt;Frame()-&gt;pack(-side=&gt;'left',-anchor=&gt;'n');
  my $dframe=$Window-&gt;Frame()-&gt;pack(-side=&gt;'top');
  my $d2frame=$Window-&gt;Frame()-&gt;pack(-side=&gt;'bottom');

  $Chatfield = $lframe-&gt;Scrolled("Text",
				    -width    =&gt; 50,
				    -height   =&gt; 20,
				    -bg =&gt; $color{'background'},
				 -wrap =&gt; 'word',
				    -state =&gt; 'disabled',
				 -scrollbars =&gt; 'osoe',
				   )-&gt;pack(-side=&gt;'top');

  my $itfont = $Chatfield-&gt;fontCreate('fontitalic',
                                     -family =&gt; 'courier',
                                     -size=&gt;'9',
                                     -slant=&gt;'italic');
  #(ase) configure color tags
  foreach(keys %color) {
	$Chatfield-&gt;tagConfigure($_,-foreground=&gt;$color{$_});
	}

  $Chatfield-&gt;tagConfigure('italic',-font=&gt;'fontitalic');

  $UserlistLabel = $rframe-&gt;Label(
				     -text     =&gt; "Getting userlist...",
				     -relief   =&gt; "sunken",
				    )-&gt;pack(-side=&gt;'top',-fill=&gt;'x');

  $Userlist = $rframe-&gt;Scrolled("Listbox",
				  -width    =&gt; 10,
				  -height   =&gt; 12,
                  -scrollbars =&gt; 'osoe',
				  -selectmode =&gt; 'single',
				 )-&gt;pack(-side=&gt;'top',-fill=&gt;'x');

  $Inputfield = $dframe-&gt;Entry(
				      -width    =&gt; 50,
				     )-&gt;pack(-side=&gt;'left',-fill=&gt;'x',-pady=&gt;4);

  $Inputfield-&gt;bind("&lt;Return&gt;", \&amp;Say_Click);

  $SayButton = $dframe-&gt;Button(
				  -text     =&gt; "Say",
			      -command =&gt; \&amp;Say_Click
				 )-&gt;pack(-side=&gt;'left');

  $XPLabel = "Getting XP info...";

  $Status = $d2frame-&gt;Label(
			      -text  =&gt; $status_idle,
			      -relief   =&gt; 'sunken',
			     )-&gt;pack(-side=&gt;'left',-fill=&gt;'x');

  $Progress = $d2frame-&gt;Canvas(-height=&gt;21,
                              -width=&gt;251,
                              -relief=&gt;'sunken',
                              -borderwidth=&gt;2)-&gt;pack(-side=&gt;'left');
  $prect = $Progress-&gt;createRectangle(0,0,250,20,-fill=&gt; 'red',-outline=&gt;'red');
  $ptext = $Progress-&gt;createText(125,10,-text=&gt;$XPLabel);
}

################################################################################
#
# initChat
#
# Initialize the chat interface
#
sub initChat {
  $p = PerlMonksChat-&gt;new();
  $p-&gt;add_cookies;
  $p-&gt;login($user,$passwd) if $user;
  $Window-&gt;repeat($interval_chat,\&amp;updChatterbox_Click)   if ($interval_chat);
  $Window-&gt;repeat($interval_xp,\&amp;updXP_Click)             if ($interval_xp);
  $Window-&gt;repeat($interval_userlist,\&amp;updUserlist_Click) if ($interval_chat);
  &amp;updChatterbox_Click;		# seed the chatterbox
  &amp;updXP_Click;			# seed the XP info
  &amp;updUserlist_Click;		# seed the Userlist area
}

################################################################################
#
# Say_Click
#
# What to do when the user clicks the say button
#
sub Say_Click {
  $Status-&gt;configure(-text=&gt;"Sending data...");
  my($text) = $Inputfield-&gt;get();
  $Inputfield-&gt;delete(0,'end');
  if ($text =~ /^\s*\/msg\s+(\S+)\s*(.+)$/i) {
    $p-&gt;send($text);
    printMessage("Sent private msg to $1: $2");
  } elsif ($text =~ /^\/?(checkoff|co)\s+/ &amp;&amp; (my @ids=($text=~/(\d+)/g))) {
    my(%msgs) = $p-&gt;personal_messages;
    $p-&gt;checkoff(map { (sort keys %msgs)[$_-1] } @ids);
    printMessage("* Checked off private msgs");
  } elsif ($text =~ /^\s*\/msgs\s*$/) {
    if (my %msgs=$p-&gt;personal_messages) {
      my($msg_num) = 1;
      foreach (sort keys %msgs) {
	printMessage("($msg_num) $msgs{$_}",'private');
	$msg_num++;
      }
    } else {
      printMessage("* No personal messages");
    }
  } else {
    $p-&gt;send($text);
    &amp;updChatterbox_Click;
  }
  $Status-&gt;configure(-text=&gt;$status_idle);
}

################################################################################
#
# Exit_Click
#
# What to do when the user clicks the exit menu option
#
sub Exit_Click { exit(0); }


################################################################################
#
# updChatterbox_Click;
#
# Checks for new chat messages
#
sub updChatterbox_Click {
  $Status-&gt;configure(-text=&gt;"Checking for new chat messages...");
  my($msg_num) = 1;
  foreach ($p-&gt;getnewlines(1)) {
    if (s/^\(\d+\)/\($msg_num\)/) { 
      $msg_num++;
      printMessage("$_",'private');
    } elsif (s/^&lt;(\S+)&gt;//) {
      printuser($1);
      printMessage("$_",'default');
    } else {
      printMessage("$_",'italic');
    }
  }
  $Status-&gt;configure(-text=&gt;$status_idle);
}

  sub printuser {
    my($user) = shift;
    printMessage('&lt;','default',1);
    printMessage("$user",'username',1);
    printMessage('&gt;','default',1);
  }

################################################################################
#
# updXP_Click
#
# Find user's current XP level and what the next level will be
#
sub updXP_Click {
  $Status-&gt;configure(-text=&gt;"Checking for new XP information...");
  my(%xp)=$p-&gt;xp;
  if (%xp) {
    my($position) = int(( ($xp{xp}-$perlmonk_levels{$xp{level}}) /
			  ($xp{xp} - $perlmonk_levels{$xp{level}} + $xp{xp2nextlevel}) ) * 100);
     $Progress-&gt;delete($prect);
     $prect=$Progress-&gt;createRectangle(0,0,$position*2.5-1,20,-fill=&gt;'green',
         -outline=&gt;'green');
    my($XPLabelStr) = "Level: $xp{level}, XP: $xp{xp}, "
      . "To next: $xp{xp2nextlevel} ($position%), Votes left: $xp{votesleft}";
      $Progress-&gt;delete($ptext);
      $ptext=$Progress-&gt;createText(125,10,-text=&gt;$XPLabelStr);
  } else {
      $Progress-&gt;delete($ptext);
      $ptext=$Progress-&gt;createText(125,10,-text=&gt;"Could not get your XP info");
  }
  $Status-&gt;configure(-text=&gt;$status_idle);
}

################################################################################
#
# updUserlist_Click
#
# Updates the userlist listbox
#
sub updUserlist_Click {
  $Status-&gt;configure(-text=&gt;"Checking userlist...");
  $Userlist-&gt;delete(0,'end');
  my(%users)=$p-&gt;users;
  if (%users) {
    my $num_users = 0;
    foreach (sort keys(%users)) {
      $Userlist-&gt;insert('end',"$_"); $num_users++;
    }
    $UserlistLabel-&gt;configure(-text=&gt;"# Users: $num_users");
  } else {
    printError("Ack!  Noone's logged in!");
    $UserlistLabel-&gt;configure(-text=&gt;"# Users: zero!");
  }
  $Status-&gt;configure(-text=&gt;$status_idle);
}

################################################################################
#
# updUsername_Click
#
# Updates the username/password cookie
#
sub updUsername_Click {
  $Status-&gt;configure(-text=&gt;"Updating user information...");

   if (!$userinfo_w) {
     $userinfo_w = $Window-&gt;Toplevel(-takefocus=&gt;1,
            					     -title  =&gt; "Update user info");
     $userinfo_w-&gt;withdraw();
     $userinfo_w-&gt;transient($Window);

     $unField = $userinfo_w-&gt;LabEntry(
 					 -label =&gt; "Username:",
 					 -width  =&gt; 25,
 					 -labelPack =&gt; [-side =&gt; 'left' ]
 					)-&gt;pack;

     $pwField = $userinfo_w-&gt;LabEntry(
 					 -label   =&gt; "Password:",
 					 -width    =&gt; 25,
 					 -show =&gt; '*',
 					 -labelPack =&gt; [-side =&gt; 'left' ]
 					)-&gt;pack;

     $confField = $userinfo_w-&gt;LabEntry(
 					   -label   =&gt; "Confirm:",
 					   -width    =&gt; 25,
 					   -show =&gt; '*',
 					   -labelPack =&gt; [-side =&gt; 'left' ]
 					  )-&gt;pack;

     $userinfo_w-&gt;Button(
 						-text     =&gt; "Cancel",
 						-command=&gt;
 			sub { $userinfo_w-&gt;grabRelease;
 			      $userinfo_w-&gt;withdraw;
 			    }
 					       )-&gt;pack(-side =&gt;'right',-padx=&gt;5,-pady=&gt;2);

     $userinfo_w-&gt;Button (
 					    -text     =&gt; "Ok",
 						-command=&gt; \&amp;Ok_Click
 					   )-&gt;pack(-side =&gt; 'left',-padx=&gt;5,-pady=&gt;2);
   }

  $userinfo_w-&gt;Popup;
  $unField-&gt;focusForce;
  $userinfo_w-&gt;protocol('WM_DELETE_WINDOW',sub {;}); #handle window 'x' button
  $userinfo_w-&gt;grabGlobal;

  $Status-&gt;configure(-text=&gt;$status_idle);
}

  sub Ok_Click { 
  	my ($un,$pw,$co) = ($unField-&gt;Text,$pwField-&gt;Text,$confField-&gt;Text);
     unless ($un &amp;&amp; $pw &amp;&amp; $co) {
       printError("All fields required. Nothing changed.");
	   $userinfo_w-&gt;grabRelease;
 	   $userinfo_w-&gt;withdraw;
       return;
     }
     if ($pw ne $co) {
       printError("Password and confirmation did not match. Nothing changed.");
	   $userinfo_w-&gt;grabRelease;
 	   $userinfo_w-&gt;withdraw;
     } else {
       $p-&gt;login($un,$pw);
	   $userinfo_w-&gt;grabRelease;
 	   $userinfo_w-&gt;withdraw;
     }
   }

################################################################################
#
# printMessage and printError
#
# Prints an error or message to the chatterbox
#
sub printMessage {
  my($msg) = shift;
  my($color) = shift || 'message';
  my($omit_return) = shift;
  $msg .= "\n" unless $omit_return;
  $Chatfield-&gt;configure(-state=&gt;'normal');
  $Chatfield-&gt;insert('end',$msg,$color);
  $Chatfield-&gt;see('end');
  $Chatfield-&gt;configure(-state=&gt;'disabled');
}
sub printError {
  my($error) = shift;
  printMessage("ERROR: $error",'error')
}

# save color settings
sub save_settings {
    for my $option (keys %color) {
      $color{$option}=$Chatfield-&gt;tagCget($option,-foreground) unless $option eq 'background';
    }
    $color{'background'}=$Chatfield-&gt;cget(-bg);
}

# reset color settings to default values
sub reset_settings {
  foreach(keys %default_color) {
	$Chatfield-&gt;tagConfigure($_,-foreground=&gt;$color{$_}) unless $_ eq 'background';
	}
  $Chatfield-&gt;configure(-bg =&gt; $default_color{'background'});
  save_settings;
}
&lt;/code&gt;

</field>
<field name="codedescription">
Tk Version of [Shendal]'s Win32::GUI chatterbox client.&lt;br&gt;
Added features: 
&lt;ul&gt;
&lt;li&gt;Color options configurable and persistent between sessions via a [tie]'d [SDBM_File]&lt;/li&gt;
&lt;li&gt;Should work on any platform with [Tk] 8.0 module&lt;/li&gt;
&lt;/ul&gt;
To do:
&lt;ul&gt;
&lt;li&gt;There is still the issue of the GUI blocking when accessing the network. &lt;b&gt;Update:&lt;/b&gt; a new version (again based on [Shendal]'s work (see reply below) is in the works... Check back soon.&lt;/li&gt;
&lt;li&gt;Somehow the Tk status bar [Shendal] used to help aleviate the item above doesn't seem to work to well in the Tk version.. It doesn't seem to update properly&lt;/li&gt;
&lt;/ul&gt;
Notes: &lt;ul&gt;&lt;li&gt;Requires [ZZamboni]'s PerlMonkChat module
&lt;/li&gt;
&lt;li&gt;Use/Abuse freely but, please let me know if you fix/add to it so I can use it too.&lt;/li&gt;
&lt;/ul&gt;
</field>
<field name="codecategory">
Chatterbox Clients</field>
<field name="codeauthor">
[ase] alevenson@uswest.net</field>
</data>
</node>
