#! /bin/sh
# \
exec wish "$0" "$@"

#############################################################################
#                                                                           #
#  Copyright (C) 1996-98  Andreas Gelhausen <atte@gecko.North.DE>           #
#                                                                           #
#  This program is free software; you can redistribute it and/or modify     #
#  it under the terms of the GNU General Public License as published by     #
#  the Free Software Foundation; either version 2 of the License, or        #
#  (at your option) any later version.                                      #
#                                                                           #
#  This program is distributed in the hope that it will be useful,          #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#  GNU General Public License for more details.                             #
#                                                                           #
#  You should have received a copy of the GNU General Public License along  #
#  with this program; if not, write to the Free Software Foundation, Inc.,  #
#  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA                 #
#                                                                           #
#############################################################################

global version date
set version         "1.202"
set date         "10.03.98"
set print_debug          0

######################################################################
#            DEFAULT VALUES FOR USER DEFINABLE VARIABLES             #
######################################################################

set normal_style     ""
set bold_style       ""
set special_style    ""
set url_style        "-borderwidth 1 -relief raised -background #cacaca"
set msgid_style      "-borderwidth 1 -relief raised -background #cacaca"
set search_style     "-background #880000 -foreground white"

set button_font      ""
set checkbutton_font ""
set entry_font       ""
set label_font       ""
set listbox_font     ""
set menu_font        ""
set menubutton_font  ""
set radiobutton_font ""
set text_font        ""
set color(list)      ""
set path(dcc_get)    "~/"

set user_styles {
  {{^(\*|\+|\*\*|\=)».*} {-foreground #00aa00} {} {# send private message/notice/action}}
  {{^((<|-)$me(>|-\	|»|\+)|\* $me(	|»|\+)).*} {-foreground #007700} {} {# send message/notice/action to channel}}
  {{^(\*|\+|\*\* |\=)[^ \*\+].*} {-foreground #dd0000} {} {# receive private message/notice/action}}
  {{^(\*\*\*|\[ (notify|noteserv) \]).+ (is on|detected|signs o(n|ff)|gets (|in)visible).*$} {-foreground #cc9900} {} {# notify messages (notify/noteserv)}}
  {{^(.*[^a-zA-Z0-9]|)($me|ircii)(|[^a-zA-Z0-9].*)$} {-foreground #aa0000} {} {# /me or ircII}}
  {{^(\( |)([0-9][0-9][0-9])(| \)).*} {-foreground #440044} {} {# numerics}}
  {{^(\*\*\*|\[ signoff \]).+ has signed off \(([^ .]+\.)+[^ .]+ ([^ .]+\.)+[^ ]+\)$} {-foreground #ff5500} {} {# faked netsplit-signoff}}
  {{^(\*\*\*.|\[ )Net(split|join).*} {-foreground #ff5500} {} {# netsplit or netjoin}}
  {{^(\×\×\×|\+\+\+|\[ (alert|error|failure|note|warning) \]).*} {-foreground #aa0000} {} {# this program wants to tell you something}}
  {{^(\*\*\*|\[ ).*} {-foreground #000066} {} {# three stars messages}}
}

set geometry       ""
set history_max    20
set lines_max      256
set lines_all      0
set ircpath        "irc"

set margin(text)    ""

set beep_on_public_when_present              0
set beep_on_public_when_away                 0
set beep_on_message_when_present             0
set beep_on_message_when_away                1
set beep_on_notice_when_present              0
set beep_on_notice_when_away                 1
set beep_on_invite_when_present              0
set beep_on_invite_when_away                 0
set beep_on_ctrlG_when_present               1
set beep_on_ctrlG_when_away                  1
set show_address_on_message_when_present     0
set show_address_on_message_when_away        1
set show_address_on_message_in_logfile       0
set show_address_on_notice_when_present      0
set show_address_on_notice_when_away         1
set show_address_on_notice_in_logfile        0
set show_time_on_private_when_present        0
set show_time_on_private_when_away           1
set show_time_on_public_when_present         0
set show_time_on_public_when_away            0
set chat_window_on_message_when_present      0
set chat_window_on_message_when_away         0
set chat_window_on_notice_when_present       0
set chat_window_on_notice_when_away          0
set silence                                  0
set request_on_dcc_chat                      0
set request_on_dcc_send                      0
set request_on_invite                        0
set request_on_kick                          0

set auto_popup                     0
set hide_joins                     0
set hide_leaves                    0
set hide_signoffs                  0
set show_commandline               1
set show_topic                     1
set show_userlist                  1
set use_margin                     0
set display_types                  0
set margin_size                   70
set sort_userlist_alphabeticly     0
set sort_userlist_by_channelmodes  0

set react_to_netsplits             1
set react_to_takeover              0
set takeover_users                 3
set takeover_period              300
set takeover_kick_reasons         {}
set takeover_star_patterns        {}

set react_to_ctcp_flood            0
set host_flood_ignore_period     300
set global_flood_ignore_period   120

set preferred_channels {"#tkirc" "#test" "#channel1" "#channel2"}
set preferred_signoffmessages {"I'll be back"}
set preferred_partmessages {"I'll be back"}
set preferred_topics {"Please use your own default topic! =;^)"
		      "Where do you want to join today?"}
set preferred_awayreasons {"Be back later"}
set preferred_kickreasons {"No flooding"}
set preferred_servers {
    {"irc.funet.fi"                     6667 "IRCnet"}
    {"irc.stealth.net"                  6667 "IRCnet"}
    {"irc.fu-berlin.de"                 6667 "IRCnet"}
    {"irc.cerf.net"                     6667 "EFnet"}
    {"efnet.demon.co.uk"                6667 "EFnet"}
    {"irc.nijenrode.nl"                 6667 "EFnet"}
    {"US.Undernet.Org"                  6667 "Undernet"}
    {"UK.Undernet.Org"                  6667 "Undernet"}
    {"DE.Undernet.Org"                  6667 "Undernet"}
    {"irc.dal.net"                      6667 "DALnet"}
}
set preferred_nicknames {}

set nick_completion_mode              2
set nick_completion_suffix          ": "
set nick_completion_prefer_number     5 ; # nick_completion_mode == 1
set nick_completion_prefer_period   600 ; # nick_completion_mode == 2

set notify(methods)    {{"*" "notify"}}
set notify(users)      {{"atte" "atte@gecko.North.DE"}}
set notify(method)     {}
set noteserv(language) {en}
set noteserv(status)   0 ; # 0=nothing, 1=updating
set noteserv(limit)    10 ; # limit for notifies
set note(status)       0 ; # 0=nothing, 1=updating
set note(limit)        10 ; # limit for notifies

set send_away_notice  0
set auto_mark_away    0
set auto_away_period  900
set auto_away_text    ""
set auto_unmark_away  0

set on_urlclick {global margin ; set margin(text) "note" ; print2crap "××× Please set the variable 'on_urlclick' in your 'tkircrc'."}
set on_msgclick {global margin ; set margin(text) "note" ; print2crap "××× Please set the variable 'on_msgclick' in your 'tkircrc'."}

set words_to_complete {}
set tab_aliases {}
set entry_bindings {}
set escape_sign "^"
set show_only_background_channels 0

set CHANNEL_NAME_WIDTH 12

######################################################################
#      USER'S STARTUP AND SUPPORT OF TKIRCRC-FOR TKIRC AND IRC       #
######################################################################

set startup 0
proc on_tkircstart { } {
}
proc on_ircIIstart { } {
}
proc on_connect { } {
}

proc notify_startup { } {
  global server notify noteserv note margin

  set mnum [llength "$notify(methods)"]
  for {set i 0} {$i < $mnum} {incr i} {
    set mentry "[lindex "$notify(methods)" $i]"
    if {[strmatch "[lindex "$mentry" 0]" "$server"]} {
      if {[strcmp "notify" "[lindex "$mentry" 1]"] == 0} {
        # notify
        set margin(text) "note"
        print2crap {*** notification method: 'notify'}
	set notify(method) "notify"

        send2irc "/notify -"
	AddToFilterQueue "*** Notify list cleared"

	for {set j 0} {$j < [llength "$notify(users)"]} {incr j} {
	  set uentry "[lindex "$notify(users)" $j]"
	  if {[string first "*" "[lindex "$uentry" 0]"] == -1} {
	    # nick does not include '*'
	    append nicks "[lindex "$uentry" 0] "
	    AddToFilterQueue "*** [expand "[lindex "$uentry" 0]"] added to the notification list"
	  }
	}
	send2irc "/notify $nicks"

      } elseif {[strcmp "noteserv" "[lindex "$mentry" 1]"] == 0} {
        # noteserv
        set margin(text) "note"
        print2crap {*** notification method: 'noteserv'}
	set notify(method) "noteserv"

        if {[strmatch "login *" "[lindex "$mentry" 2]"]} {
	  # login
	  send2irc "/noteserv [lindex "$mentry" 2]"
	} else {
	  send2irc "/noteserv status"
	}

      } elseif {[strcmp "note" "[lindex "$mentry" 1]"] == 0} {
        # note
        set margin(text) "note"
        print2crap {*** notification method: 'note'}
	set notify(method) "note"

	send2irc "/note stats"

      } else {
	set margin(text) "error"
        print2crap "××× Unknown notify-method '[lindex "$mentry" 1]'"
      }
      return
    }
  }
}

proc ReloadTKircRC {file} {
  global win margin

  set tmp ""
  lappend tmp \
    print_debug crapwindow messagewindow color path \
    normal_style bold_style special_style url_style msgid_style search_style \
    button_font checkbutton_font entry_font label_font listbox_font menu_font \
    menubutton_font radiobutton_font text_font \
    geometry geometry_kick geometry_urls geometry_msgs \
    geometry_notify send_away_notice \
    auto_mark_away auto_away_period auto_away_text auto_unmark_away \
    history_max lines_max ircpath \
    beep_on_public_when_present beep_on_public_when_away \
    beep_on_message_when_present beep_on_message_when_away \
    beep_on_notice_when_present beep_on_notice_when_away \
    beep_on_invite_when_present beep_on_invite_when_away \
    beep_on_ctrlG_when_present beep_on_ctrlG_when_away \
    show_address_on_message_when_present show_address_on_message_when_away \
    show_address_on_message_in_logfile show_address_on_notice_when_present \
    show_address_on_notice_when_away show_address_on_notice_in_logfile \
    show_time_on_private_when_present show_time_on_private_when_away \
    show_time_on_public_when_present show_time_on_public_when_away \
    chat_window_on_message_when_present chat_window_on_message_when_away \
    chat_window_on_notice_when_present chat_window_on_notice_when_away \
    silence request_on_dcc_chat request_on_dcc_send request_on_invite \
    request_on_kick \
    auto_popup hide_joins hide_leaves hide_signoffs \
    show_commandline show_topic show_userlist use_margin \
    sort_userlist_alphabeticly sort_userlist_by_channelmodes \
    display_types margin_size beeptext entry_bindings \
    react_to_netsplits react_to_takeover takeover_users takeover_period \
    takeover_kick_reasons takeover_star_patterns \
    react_to_ctcp_flood host_flood_ignore_period global_flood_ignore_period \
    preferred_nicknames preferred_channels preferred_signoffmessages \
    preferred_partmessages preferred_topics \
    preferred_awayreasons preferred_kickreasons preferred_servers \
    notify noteserv note on_urlclick on_msgclick \
    words_to_complete tab_aliases nick_completion_mode nick_completion_suffix \
    nick_completion_prefer_number nick_completion_prefer_period user_styles \
    escape_sign ircserver channelhop_period \
    show_only_background_channels

  foreach x "$tmp" {
    global $x
    if ![info exists $x] {
      set $x ""
    }
  }

  for {set i 0} {$i < 10} {incr i} {
    global geometry$i auto_popup$i hide_joins$i hide_leaves$i hide_signoffs$i
    global show_commandline$i show_topic$i show_userlist$i use_margin$i
    global display_types$i sort_userlist_alphabeticly$i
    global sort_userlist_by_channelmodes$i
  }

  if {[string length "$file"]} {
    source $file
  } elseif {[file exists "~/.tkirc/tkircrc"]} {
    source "~/.tkirc/tkircrc"
  } elseif {[file exists "~/.tkircrc"]} {
    source "~/.tkircrc"
  }

  if {[file exists "~/.tkirc/autoload/"]} {
    set before "[pwd]"
    cd "~/.tkirc/autoload/"
    set dir "[pwd]"
    foreach x [exec ls $dir] {
      if {[strmatch "*.tcl" "$dir/$x"] && [file isfile "$dir/$x"]} {
        source "$dir/$x"
      }
    }
    cd "$before"
  }

  if {[string length "$escape_sign"] > 1} {
    set escape_sign "^"
    set margin(text) "error"
    print2crap "××× Variable escape_sign set to default (value was too long)"
  }

  InitStyles
  foreach x "background highlightbackground selectbackground foreground highlightcolor selectforeground" {
    if {[info exists color($x)]} {
      setColor . -$x $color($x)
    }
  }
  foreach x "$win(list)" {
    ConfigureStyles $x
  }

  if {[strcmp "$notify(method)" "notify"] == 0} {
    notify_startup
  }
}

######################################################################
#                         GLOBAL VARIABLES                           #
######################################################################

set nickname        ""

set away            ""
set lastnickname    ""
set raw(type)       ""
set raw(lasttype)   ""
set server          ""
set ircserver       ""
set ownaddress      ""

set commandqueue    ""
set sendqueue       ""
set whoqueue        ""
set whoisqueue      ""
set whowasqueue     ""
set filterqueue     ""
set filternext      0
set direct2crap     0
set messagewindow   0
set crapwindow      0
set whofilter       0
set whoisfilter     0
set whowasfilter    0

set beeptext        ""
set beepstate       1
set next(direct)    0

# 0: public, 1: private
set linetype        0

# 'wc' == 'without channel'
set wcnicklist      ""
set wcaddresslist   ""

set notified(nicks)     ""
set notified(addresses) ""

set msghistory      ""
set msghistorynum   0
set msghistory_max  5

set underline_style "-underline on"
# reverse_styles werden nachträglich eingebaut, daher...
set reverse_style      ""

# element of takeover(tries): "<cnum> <domain>"
set takeover(tries)    ""
set takeover(times)    ""

# to detect and to handle netsplits
set split(count)       0
set join(count)        0
set psplit(state)      0
set pjoin(state)       0
set lastOPjoin         ""

# send away notice
set san(nicks)         ""
set san(times)         ""
set san(message)       ""

# zum Auswerten einer away-Bestätigung
set automatic_away     0

# for '/search'
set search_lastview    -1
set search_laststring  ""

# for ignoring CTCPs (flood protection)
set ctcp_list          ""
set ctcp_count         1

# for scanning URLs
set url_list           ""

# for scanning message IDs
set msgid_list         ""

# for scanning banlist on join
set banlist(filter)    ""

# each request window has its own ID
set win(reqcount)      1

set default_entry_bindings {
  {<Shift-Delete> {%W delete insert end ; break}}
  {<Shift-BackSpace> {%W delete 0 insert ; break}}
  {<Shift-Left> {%W icursor 0 ; %W xview 0 ; break}}
  {<Shift-Right> {%W icursor end ; %W xview end ; break}}
  {<Alt-Left> {EntryOneWordLeft %W}}
  {<Alt-Right> {EntryOneWordRight %W}}
  {<Shift-Insert> {InsertFromClipboard %W ; break}}
  {<Button-2> {+%W xview insert}}
  {<Meta-o> {%W insert insert \017}}
  {<Meta-u> {%W insert insert \037}}
  {<Meta-v> {%W insert insert \026}}
  {<Return> {+InitIdleTime}}
}

######################################################################
#                              DEBUG                                 #
######################################################################

proc debug {text} {
  global lines_all lines_max print_debug
  if {$print_debug} {
    puts stdout "$lines_all: $text"
    flush stdout
  }
}

#####################
#  BASIC FUNCTIONS  #
#####################

proc set_client_information { } {
  global version date tcl_version tk_version
  AddToFilterQueue {\*\*\*?Value of CLIENT_INFORMATION *}
  if {$tcl_version == $tk_version} {
    send2irc "/set client_information  tkirc $version ($date) Tcl/Tk $tcl_version : http://home.pages.de/~tkirc/"
  } else {
    send2irc "/set client_information  tkirc $version ($date) Tcl $tcl_version/Tk $tk_version : http://home.pages.de/~tkirc/"
  }
}

proc beep { } {
  global silence

  if !$silence {
    bell
  }
}

proc time {args} {
  if {[llength "$args"] == 1} {
    return "[clock format [lindex "$args" 0] -format "%H:%M"]"
  } else {
    return "[clock format [clock seconds] -format "%H:%M"]"
  }
}

proc longdate {args} {
  if {[llength "$args"] == 1} {
    return "[clock format [lindex "$args" 0] -format "%d.%m.%y  %H:%M:%S"]"
  } else {
    return "[clock format [clock seconds] -format "%d.%m.%y  %H:%M:%S"]"
  }
}

proc linetime { } {
  global away linetype
  global show_time_on_private_when_present show_time_on_private_when_away
  global show_time_on_public_when_present show_time_on_public_when_away
  if {$linetype} {
    if {"$away" == "" && $show_time_on_private_when_present} {
      return " ([time])"
    } elseif {"$away" != "" && $show_time_on_private_when_away} {
      return " ([time])"
    }
  } else {
    if {"$away" == "" && $show_time_on_public_when_present} {
      return " ([time])"
    } elseif {"$away" != "" && $show_time_on_public_when_away} {
      return " ([time])"
    }
  }
  return ""
}

proc myfocus {num args} {
#  set old "[focus]"
  set rc "[focus $args]"
#  print2crap "myfocus: \[$num\] args=\"$args\" old=\"$old\" rc=\"$rc\""
  return "$rc"
}

proc request {textbody args} {

# Example call:
#   request "Do you really want to delete file '$name'?" \
#     "Cancel|puts stdout Cancel" "Delete|puts stdout Delete"

  global win
  incr win(reqcount)
  set path ".req$win(reqcount)"
  if [catch {Toplevel $path -class tkirc-request}] {
    raise $path
  } else {
#    grab set $path
    set width 40 ; set height 1
    wm title $path  " tkirc: Request "
    bind $path <Escape> "grab release $path ; destroy $path"

    Frame $path.f1 -bd 1 -relief sunken
    pack $path.f1 -padx 2 -pady 2 -expand true -side top -fill x

    set newbody "" ; set i 0 ; set j 0
    while { $i < [expr [string length $textbody] - 1] } {
      set tmp [string range $textbody $i end]
      set spacenum [string first " " $tmp]
      if {$spacenum > $width} {
        set width $spacenum
      }
      set tmp [string range $textbody $i [expr $width + $i]]
      set spacenum [string last " " $tmp]
      if {[string length $tmp] < $width} {
        append newbody $tmp
        break
      } elseif {$spacenum == -1} {
        append newbody $tmp
        set i [expr $i + $width]
      } else {
        append newbody [string range $tmp 0 [expr $spacenum - 1]]
        set i [expr $i + $spacenum + 1]
      }
      append newbody "\n"
      incr height
    }
    Label $path.f1.label -width $width -height $height -bd 0 -text "$newbody"
    pack $path.f1.label -side top -pady 3 -expand true

    Frame $path.f2
    pack $path.f2 -ipadx 2 -ipady 2 -padx 2 -pady 2 -side top -fill x
    set i 0
    foreach buttondef $args {
      set trenn [string first "|" "$buttondef"] 
      set text " "
      append text [string range $buttondef 0 [expr $trenn - 1]]
      append text " "
      set action [string range $buttondef [expr $trenn + 1] end]
      append action " ; grab release $path ; destroy $path"

      Button $path.f2.$i -text "$text" -command "$action"
      if {$i == 0} {
        pack $path.f2.$i -side right -pady 2
      } else {
        pack $path.f2.$i -side left -pady 2
      }
      incr i
    }
  }
}

proc StringRequest {textbody default args} {

# Example call:
#   StringRequest "Which user (nickname) do you want to invite?" \
#     "atte" {Cancel|} {Invite|send2irc "/invite #tkirc $string"}

  global win
  incr win(reqcount)
  set path ".req$win(reqcount)"
  if [catch {Toplevel $path -class tkirc-request}] {
    raise $path
  } else {
#    grab set $path
    set width 40 ; set height 1
    wm title $path  " tkirc: Request "
    bind $path <Escape> "grab release $path ; destroy $path"

    Frame $path.f1 -bd 1 -relief sunken
    pack $path.f1 -padx 2 -pady 2 -expand true -side top -fill x

    set newbody "" ; set i 0 ; set j 0
    while { $i < [expr [string length $textbody] - 1] } {
      set tmp [string range $textbody $i end]
      set spacenum [string first " " $tmp]
      if {$spacenum > $width} {
        set width $spacenum
      }
      set tmp [string range $textbody $i [expr $width + $i]]
      set spacenum [string last " " $tmp]
      if {[string length $tmp] < $width} {
        append newbody $tmp
        break
      } elseif {$spacenum == -1} {
        append newbody $tmp
        set i [expr $i + $width]
      } else {
        append newbody [string range $tmp 0 [expr $spacenum - 1]]
        set i [expr $i + $spacenum + 1]
      }
      append newbody "\n"
      incr height
    }
    Label $path.f1.label -width $width -height $height -bd 0 -text "$newbody"
    pack $path.f1.label -side top -pady 3 -expand true

    Frame $path.f2 -bd 1 -relief flat
    pack $path.f2 -padx 2 -pady 2 -expand true -side top -fill x

    Entry $path.f2.entry
    pack $path.f2.entry -side top -fill x -expand true
    $path.f2.entry insert 0 "$default"
    focus $path.f2.entry

    Frame $path.f3
    pack $path.f3 -ipadx 2 -ipady 2 -padx 2 -pady 2 -side top -fill x
    set i 0
    foreach buttondef $args {
      set trenn [string first "|" "$buttondef"] 
      set text " "
      append text [string range $buttondef 0 [expr $trenn - 1]]
      append text " "
      set action ""
      append action "[strreplace "[string range $buttondef [expr $trenn + 1] end]" {$string} "\[$path.f2.entry get\]"]"
      append action " ; grab release $path ; destroy $path"

      Button $path.f3.$i -text "$text" -command "$action"
      if {$i == 0} {
        pack $path.f3.$i -side right -pady 2
      } else {
        pack $path.f3.$i -side left -pady 2
      }
      incr i
    }
  }
}

# line2list: Hier werden normale Textzeilen in Listen umgewandelt,
#            mit denen Tcl/Tk zurechtkommt.
proc line2list {line} {
  set newline ""
  set len [string length "$line"]
  for {set i 0} {$i < $len} {incr i} {
    set c "[string index "$line" $i]"
    switch -- "$c" {
      "\n" { append newline " " }
      "\t" { append newline " " }
      "\"" { append newline "\\\"" }
      "\\" { append newline "\\\\" }
      "\{" { append newline "\\\{" }
      "\}" { append newline "\\\}" }
      "\[" { append newline "\\\[" }
      "\]" { append newline "\\\]" }
      default { append newline "$c" }
    }
  }
  set i [string first " \\\"" " $newline"]
  set list ""
  while {$i != -1} {
    set j [string first "\\\" " "[string range "$newline" [expr $i+1] end] "]
    if {$j == -1} {
      break
    }
    if {$i == 0} {
      append list "\""
    } else {
      append list "[string range "$newline" 0 [expr $i-1]]\""
    }
    append list "[string range "$newline" [expr $i+2] [expr $i+$j]]\""
    set newline "[string range "$newline" [expr $i+$j+3] end]"
    set i [string first " \\\"" " $newline"]
  }
  append list "$newline"
  return "$list"
}

# lIndex, lSearch, lLength und lRange dienen als Ersatz für lindex,
# lsearch, llength und lrange, um mit Klammertexten klar zu kommen.
proc lIndex {line num} {
  return "[lindex "[line2list "$line"]" $num]"
}

proc lSearch {line element} {
  return [lsearch -exact "[string tolower "$line"]" "[string tolower "$element"]"]
}

proc lineSearch {line element} {
  return [lsearch -exact "[string tolower "[line2list "$line"]"]" "[string tolower "$element"]"]
}

proc lLength {line} {
  return [llength "[line2list "$line"]"]
}

proc lRange {line left right} {
  return "[lrange "[line2list "$line"]" $left $right]"
}

# cutwords: Von der linken Seite der Zeile $line werden $num Worte
#           abgeschnitten.
proc cutwords {line num} {
  set cut 0
  if {$num > 0} {
    for {set i 0} {$i < $num} {incr i} {
      while {"[string index "$line" $cut]" == " "} {
	incr cut
      }
      set next [string first " " "[string range "$line" $cut end]"]
      if {$next == -1} {
	return ""
      }
      set cut [expr $cut+$next]
    }
    return "[string range "$line" [expr $cut+1] end]"
  }
  return "$line"
}

proc coloncut {line} {
  set i [string first ":" "$line"]
  if {$i != -1} {
    incr i
    return "[string range "$line" $i end]"
  }
  return "$line"
}

# leftwords: Das Ergebnis beinhaltet nur die linken $num Worte der 
#            Zeile $line.
proc leftwords {line num} {
  set cut 0
  if {$num > 0} {
    for {set i 0} {$i < $num} {incr i} {
      while {"[string index "$line" $cut]" == " "} {
	incr cut
      }
      set next [string first " " "[string range "$line" $cut end]"]
      if {$next == -1} {
	return "$line"
      }
      set cut [expr $cut+$next]
    }
    return "[string range "$line" 0 [expr $cut-1]]"
  }
  return ""
}

# strcmp: Zwei String werden case-insensitiv (unabhängig von der
#         Groß- oder Kleinschreibung) verglichen.
proc strcmp {string1 string2} {
  return [string compare "[string tolower "$string1"]" "[string tolower "$string2"]"]
}

# strmatch: Hier wird geschaut, ob der String $string zu dem Pattern
#           $pattern paßt.
proc strmatch {pattern string} {
  return [string match "[string tolower "$pattern"]" "[string tolower "$string"]"]
}

# strreplace: Jedes Vorkommen des Strings $pre innerhalb des Strings
#             $line wird durch $post ersetzt.
proc strreplace {line pre post} {
  set i [string first "[string tolower "$pre"]" "[string tolower "$line"]"]
  if {$i != -1} {
    set prelen [string length "$pre"]
    set postlen [string length "$post"]
    set newline ""
    set left 0
    while {$i != -1} {
      append newline "[string range "$line" $left [expr $i-1]]$post"
      set line "[string range "$line" [expr $i+$prelen] end]"
      set i [string first "$pre" "$line"]
    }
    return "$newline$line"
  }
  return "$line"
}

# expand: Bestimmten Zeichen wie z.B. runde und eckige Klammern wird
#         ein "\" vorangestellt, damit Tcl keine Probleme damit bekommt.
proc expand {line} {
#  set slist {"\\"   "\""   "\{"   "\}"   "\["   "\]"   "\|"   "\^"}
#  set rlist {"\\\\" "\\\"" "\\\{" "\\\}" "\\\[" "\\\]" "\\\|" "\\\^"}
  set slist { {\\}     "\""   "\\{"  "\\}"  "\\["  "\\]"  {\^}  {\|}  {\$}  }
  set rlist { "\\\\\\" "\\\"" "\\\{" "\\\}" "\\\[" "\\\]" {\\^} {\\|} {\\$} }
#  set slist { {\\}     "\""   "\\{"  "\\}"  "\\["  "\\]"  }
#  set rlist { "\\\\\\" "\\\"" "\\\{" "\\\}" "\\\[" "\\\]" }

  for {set i 0} {$i < [llength "$slist"]} {incr i} {
    regsub -all -- "[lindex "$slist" $i]" "$line" "[lindex "$rlist" $i]" line
  }
  return "$line"
}

# expandescape: Das Escape-Zeichen, das tkirc benutzt, wird um eins
#               erweitert.
proc expandescape {line} {
  global escape_sign
  return "[strreplace "$line" "$escape_sign" "$escape_sign$escape_sign"]"
}

# reduce: Die Auswirkungen von Prozedur "expand" werden rückgängig
#         gemacht. (Siehe auch dort!)
proc reduce {line} {
  set slist {"\\\\" "\\\"" "\\\{" "\\\}" "\\\[" "\\\]" "\\\|" "\\\^"}
  set rlist {"\\"   "\""   "\{"   "\}"   "\["   "\]"   "\|"   "\^"}

  for {set i 0} {$i < [llength "$slist"]} {incr i} {
    set j [string first "[lindex "$slist" $i]" "$line"]
    if {$j != -1} {
      set left 0 ; set newline ""
      while {$j != -1} {
	append newline "[string range "$line" $left [expr $j-1]][lindex "$rlist" $i]"
	set line "[string range "$line" [expr $j+2] end]"
	set j [string first "[lindex "$slist" $i]" "$line"]
      }
      set line "$newline$line"
    }
  }
  return "$line"
}

# cutEscCodes: Möglicherweise vorhandene Steuerzeichen werden aus dem
#              String $line herausgefiltert.
proc cutEscapeCodes {line} {
  set newline ""

  for {set i 0} {$i < [string length "$line"]} {incr i} {
    set char "[string index "$line" $i]"
    if {"$char" > "\x1f"} {
      append newline "$char"
    }
  }
  return "$newline"
}

# Exit: Diese Prozedur läßt tkirc 'sauber' zum Ende kommen. ircII wird
#       beendet, der Prozedur "on_signoff" wird der eigene Signoff 
#       mitgeteilt, und geöffnete Logfiles werden geschlossen.
proc Exit {message} {
  global version log messagewindow nickname

  # ircII und tkirc werden beendet.
  set len [string length "$message"]
  if {$len == 0} {
    set message "ircII/tkirc"
  }
  ExecOnCommands signoff window "$messagewindow" nick "$nickname" address "[AddressOfNick "$nickname"]" message "$message"

  catch {send2irc "/quit $message"} result
  # Alle noch offenen Logfiles werden geschlossen.
  foreach x "$log(list)" {
    puts $log($x,handle) "tkirc: Exit()"
    puts $log($x,handle) "Logfile closed at:  [longdate]\n"
    catch {close $log($x,handle)} result
  }
  exit
}

######################################################################
# BEGIN:                   MULTIPLE SERVERS                          #
######################################################################

proc GETNUM {field} {
  global $field

  set count "$field" ; append count "(count)"
  set list "$field" ; append list "(list)"

  set i [set $count]
  while {1} {
    if {[lsearch -exact "[set $list]" "$i"] == -1} {
      set num $i
      lappend $list $num
      set $list "[lsort -increasing "[set $list]"]"
      break
    }
    incr i
  }
  incr $count
  return $num
}

set win(list)   ""
set win(tojoin) ""

proc ProduceWindow {args} {
  global win
  if {"$args" == ""} {
    for {set i 0} {$i <= [llength $win(list)]} {incr i} {
      if {[lsearch -exact "$win(list)" $i] == -1} {
        set wnum $i
        lappend win(list) $i
        set win(list) "[lsort -increasing "$win(list)"]"
        break
      }
    }
  } elseif {[string compare "$args" "hidden"] == 0} {
    for {set i 500} {$i <= [expr 500+10]} {incr i} {
      if {[lsearch -exact "$win(list)" $i] == -1} {
        set wnum $i
        lappend win(list) $i
        set win(list) "[lsort -increasing "$win(list)"]"
        break
      }
    }
  }
  foreach x "channels query history history2 popupnick" {
    set win($wnum,$x) ""
  }
  foreach x "visible lines hsize taghi touched" {
    set win($wnum,$x) 0
  }
  foreach x "taglo" {
    set win($wnum,$x) 1
  }
  set win($wnum,actual) "*"

  return $wnum
}

proc DeleteWindow {wnum} {
  global win

  foreach x "channels visible lines query history hsize history2 popupnick actual taghi touched taglo" {
    unset win($wnum,$x)
  }
  set i [lsearch -exact "$win(list)" "$wnum"]
  if {$i != -1} {
    set win(list) "[lreplace "$win(list)" $i $i]"
  }
}

proc GetActual {wnum} {
  global win
  if [info exists win($wnum,actual)] {
    return "$win($wnum,actual)"
  }
  return ""
}

set chan(count)  0
set chan(list)   ""
set chan(tojoin) ""

proc ProduceChannel {channel} {
  global chan win crapwindow banlist

  # Dem Kanal wurde noch kein Fenster zugewiesen.
  set i [lSearch "$chan(tojoin)" "$channel"]
  if {$i != -1} {
    # Es sollte ein bestimmtes Fenster genommen werden.
    while {$i != -1} {
      set num [lindex "$win(tojoin)" $i]
      set chan(tojoin) "[lreplace "$chan(tojoin)" $i $i]"
      set win(tojoin) "[lreplace "$win(tojoin)" $i $i]"
      set i [lSearch "$chan(tojoin)" "$channel"]
    }
  } else {
    # Der Kanal sollte gar nicht gejoint werden!
    set num $crapwindow
    return -1
  }
  
  set cnum [GETNUM chan]
  set chan($cnum) "$channel"
  
  foreach x "nicks names cnicks ctimes olist vlist addresses jointimes topic bancomments banpatterns bantimes banusers" {
    set chan($cnum,$x) ""
  }
  foreach x "o v b i m n p s t" {
    set chan($cnum,mode_$x) 0
  }
  foreach x "k l" {
    set chan($cnum,mode_$x) ""
  }
  # raw ist für eventuelle raw-Logfiles
  foreach x "ucount" {
    set chan($cnum,$x) 0
  }
  set chan($cnum,lognum) -1
  set chan($cnum,window) $num
  
  lappend win($num,channels) $cnum
  set win($num,actual) "$channel"
  lappend banlist(filter) "$channel"
  send2irc "/mode $channel b"
  
  UpdateInfos $num
  return $cnum
}

proc DeleteChannel {cnum} {
  global chan win

  set i [lsearch "$chan(list)" $cnum]
  if {$i == -1} {
    return
  }
  set chan(list) "[lreplace "$chan(list)" $i $i]"

  # Falls noch ein Fenster zu diesem Kanal besteht, muß es
  # evtl. aktualisiert werden.
  if {[info exists chan($cnum,window)]} {
    set wnum $chan($cnum,window)
    if {$wnum != -1 && [lsearch -exact "$win(list)" "$wnum"] != -1} {
      set i [lsearch -exact "$win($wnum,channels)" "$cnum"]
      if {$i != -1} {
        set win($wnum,channels) "[lreplace "$win($wnum,channels)" $i $i]"
      }
      if {[strcmp "$win($wnum,actual)" "$chan($cnum)"] == 0} {
        if {[llength "$win($wnum,channels)"] == 0} {
          set win($wnum,actual) "*"
        } else {
	  set win($wnum,actual) "$chan([lindex "$win($wnum,channels)" 0])"
        }
      }
      UpdateInfos $wnum
    }
  }

  set i [lsearch -exact "$chan(tojoin)" "$chan($cnum)"]
  if {$i != -1} {
    set chan(tojoin) "[lreplace "$chan(tojoin)" $i $i]"
    set win(tojoin) "[lreplace "$win(tojoin)" $i $i]"
  }

  # Auf die Namen der Ex-Kanäle wird evtl. noch im Rahmen der Netsplits
  # bzw. Netjoins zugegriffen.
  # unset chan($cnum)

  foreach x "nicks names cnicks ctimes olist vlist addresses jointimes topic bancomments banpatterns bantimes banusers ucount lognum window" {
    unset chan($cnum,$x)
  }
  foreach x "o v b i m n p s t k l" {
    unset chan($cnum,mode_$x)
  }
}

proc GetChannelWindow {channel} {
  global chan win on_args destlog
  set destlog "$channel"
  foreach x "$chan(list)" {
    if {[strcmp "$channel" "$chan($x)"] == 0} {
      if {[lsearch -exact "$win(list)" $chan($x,window)] != -1} {
	return $chan($x,window)
      }
    }
  }
  return -1
}

proc GetChannelNumber {channel} {
  global chan
  foreach x "$chan(list)" {
    if {[strcmp "$channel" "$chan($x)"] == 0} {
      return "$x"
    }
  }
  return -1
}

proc GetDestWin {channel} {
  global chan win on_args crapwindow destlog
  set destlog "$channel"
  set on_args(window) -1
  foreach x "$chan(list)" {
    if {[strcmp "$channel" "$chan($x)"] == 0} {
      if {[lsearch -exact "$win(list)" $chan($x,window)] != -1} {
	set on_args(window) $chan($x,window)
	break
      }
    }
  }
  return $on_args(window)
}

set log(count) 0
set log(list)  ""

proc ProduceLog {source} {
  global log chan

  if {[info exists log([string tolower "$source"])]} {
    # Für diese Quelle existiert bereits ein Logfile.
    return -1
  }

  set num [GETNUM log]
  set log($num) "$source"
  set log([string tolower "$source"]) "$num"
  
  foreach x "file handle type dateswitch rawswitch opendate" {
    set log($num,$x) ""
  }
  set log($num,cnum) [GetChannelNumber "$source"]
  if {$log($num,cnum) != -1} {
    set chan($log($num,cnum),lognum) $num
  }
  return $num
}

proc DeleteLog {num} {
  global log chan

  set source "$log($num)"
  foreach x "file handle type dateswitch rawswitch opendate" {
    unset log($num,$x)
  }
  if {$log($num,cnum) != -1} {
    set chan($log($num,cnum),lognum) -1
  }
  unset log($num,cnum)
  unset log([string tolower "$source"])
  unset log($num)

  set i [lsearch -exact "$log(list)" "$num"]
  if {$i != -1} {
    set log(list) "[lreplace "$log(list)" $i $i]"
  }
}

######################################################################
#   END:                   MULTIPLE SERVERS                          #
######################################################################

###########
#  MODES  #
###########

proc InitUserModes {} {
  global modes away
  foreach x "i s w" {
    set modes(~,$x) 0
  }
  set away ""
}

proc SetChannelModes {cnum changes type address} {
  # type: 0 == Modes waren bereits gesetzt, 1 == Modes wurden gerade geändert
  global chan

  # vorzeichen (0 = -) (1 = +) 
  set vorzeichen 1 ; set prefix "+"

  set pcnt 0
  set flags "[lIndex "$changes" 0]"
  set parameter "[cutwords "$changes" 1]"
  for {set i 0} {$i < [string length "$flags"]} {incr i} {
    set flag "[string index "$flags" $i]"
    switch -exact -- "$flag" {
      "+" {set vorzeichen 1 ; set prefix "+"}
      "-" {set vorzeichen 0 ; set prefix "-"}
    }
    # on_commands nur beim Ändern eines Modes
    if {$type} {
      switch -regexp -- "$flag" {
	"k|l|b|o|v" {
	  ExecOnCommands modechange to "$chan($cnum)" mode "$prefix$flag" \
	   argument "[lIndex "$parameter" $pcnt]"
	}
	"i|m|n|p|s|t" {
	  ExecOnCommands modechange to "$chan($cnum)" mode "$prefix$flag" \
	   argument ""
	}
      }
    }
    switch -regexp -- "$flag" {
      "k" {
	if {$vorzeichen} {
	  set chan($cnum,mode_k) "[lIndex "$parameter" $pcnt]"
	} else {
	  set chan($cnum,mode_k) ""
	}
	incr pcnt
      }
      "l" {
	if {$vorzeichen} {
	  set chan($cnum,mode_l) "[lIndex "$parameter" $pcnt]"
	  incr pcnt
	} else {
	  set chan($cnum,mode_l) ""	  
	}
      }
      "b" {
	if {$vorzeichen} {
	  BanChannelUser $cnum [lIndex "$parameter" $pcnt] "$address" 
	} else {
	  UnbanChannelUser $cnum [lIndex "$parameter" $pcnt]
	}
	incr pcnt
      }
      "o" {
	ChannelUserOp $cnum [lIndex "$parameter" $pcnt] $vorzeichen
	incr pcnt
      }
      "v" {
	ChannelUserVoice $cnum [lIndex "$parameter" $pcnt] $vorzeichen
	incr pcnt
      }
      "i|m|n|p|s" {
	set chan($cnum,mode_$flag) $vorzeichen
      }
      "t" {
	set chan($cnum,mode_t) $vorzeichen
      }
    }
  }
  UpdateTitle $chan($cnum,window)
  UpdateTopic $chan($cnum,window)
}

proc SetUserModes {flags} {
  global nickname modes
  # vorzeichen (0 = -) (1 = +) 
  set vorzeichen 1 ; set prefix "+"

  for {set i 0} {$i < [string length "$flags"]} {incr i} {
    set flag "[string index "$flags" $i]"
    switch -exact -- "$flag" {
      "+" {set vorzeichen 1 ; set prefix "+"}
      "-" {set vorzeichen 0 ; set prefix "-"}
    }
    switch -regexp -- "$flag" {
      "i|r|s|w" {
	set modes(~,$flag) $vorzeichen
	ExecOnCommands modechange to "$nickname" mode "$prefix$flag" \
	    argument ""
      }
    }
  }
}

proc ChangeUserMode {mode} {
  global modes nickname

  if {$modes(~,$mode)} {
    send2irc "/mode $nickname +$mode"
    set modes(~,$mode) 0
  } else {
    send2irc "/mode $nickname -$mode"
    set modes(~,$mode) 1
  }
}

proc ChannelModesWindow {num} {
  global chan cmw_modes cmw_channel margin

  set channel [GetActual $num]
  if {"$channel" != "*"} {
    set cnum [GetChannelNumber "$channel"]
    set cmw_channel "$channel"

    foreach x "i k l m n p s t" {
      set cmw_modes($x) "$chan($cnum,mode_$x)"
    }

    if [catch {Toplevel .channelmodes -class tkirc-request}] {
      raise .channelmodes
    } else {
#      grab set .channelmodes
      wm title .channelmodes  " tkirc: Set modes "
      bind .channelmodes <Escape> "closewindow .channelmodes"

      Label .channelmodes.mid -text "  Set modes of channel $channel:  "
      pack .channelmodes.mid -side top -padx 2 -pady 2

      set f .channelmodes.buttons
      Frame $f
      pack $f -side bottom -fill x -pady 2
      Button $f.commit -text "Commit changes" \
       -command "CommitChannelModeChanges ; closewindow .channelmodes"
      Button $f.cancel -text "Cancel" -command "closewindow .channelmodes"
      pack $f.commit -side left
      pack $f.cancel -side right

      set f .channelmodes.body
      Frame $f -borderwidth 1 -relief sunken
      Frame $f.left -borderwidth 0
      foreach x {{i {invite only}} {p private} {s secret}} {
	Checkbutton $f.left.[lindex "$x" 0] -text [lindex "$x" 1] \
	 -variable cmw_modes([lindex "$x" 0])
	pack $f.left.[lindex "$x" 0] -anchor w
      }
      Frame $f.left.k -borderwidth 0
      pack $f.left.k -pady 2
      Label $f.left.k.label -text " keyword:"
      pack $f.left.k.label -side left
      Entry $f.left.k.entry -width 8
      $f.left.k.entry delete 0 end
      $f.left.k.entry insert end "$cmw_modes(k)"
      pack $f.left.k.entry -side left -fill x
      pack $f.left.k
      pack $f.left -side left -padx 2

      Frame $f.right -borderwidth 0
      foreach x {{n {no messages}} {m {moderated}} {t {topic limits}}} {
	Checkbutton $f.right.[lindex "$x" 0] -text [lindex "$x" 1] \
	 -variable cmw_modes([lindex "$x" 0])
	pack $f.right.[lindex "$x" 0] -anchor w
      }
      Frame $f.right.l -borderwidth 0
      pack $f.right.l -pady 2
      Label $f.right.l.label -text " user limit:"
      pack $f.right.l.label -side left
      Entry $f.right.l.entry -width 8
      $f.right.l.entry delete 0 end
      $f.right.l.entry insert end "$cmw_modes(l)"
      pack $f.right.l.entry -side left
      pack $f.right.l
      pack $f.right -side right -padx 2
      pack $f -side top -expand true -fill x -padx 1 -pady 2
    }
  } else {
    set margin(text) "error"
    print2text $num "××× You have no channel joined in this window"
  }
}

proc CommitChannelModeChanges { } {
  global chan cmw_modes cmw_channel

  set cnum [GetChannelNumber "$cmw_channel"]
  set plus "" ; set minus "" ; set parameters ""
  foreach x "i m n p s t" {
    if {$cmw_modes($x) > $chan($cnum,mode_$x)} {
      append plus "$x"
    } elseif {$cmw_modes($x) < $chan($cnum,mode_$x)} {
      append minus "$x"
    }
  }
  set cmw_modes(k) "[.channelmodes.body.left.k.entry get]"
  set cmw_modes(l) "[.channelmodes.body.right.l.entry get]"

  if {[strcmp "$cmw_modes(k)" "$chan($cnum,mode_k)"]} {
    if {[string length "$chan($cnum,mode_k)"]} {
      send2irc "/mode $cmw_channel -k $chan($cnum,mode_k)"
    }
    if {[string length "$cmw_modes(k)"]} {
      append plus "k"
      append parameters " $cmw_modes(k)"
    } else {
      append minus "k"
      append parameters " $chan($cnum,mode_k)"
    }
  }
  if {[strcmp "$cmw_modes(l)" "$chan($cnum,mode_l)"]} {
    if {[string length "$cmw_modes(l)"]} {
      append plus "l"
      append parameters " $cmw_modes(l)"
    } else {
      append minus "l"
      append parameters " $cmw_modes(l)"
    }
  }
  if {"$plus$minus" != ""} {
    send2irc "/mode $cmw_channel +$plus-$minus$parameters"
  }
}

#############
#  WIDGETS  #
#############

proc closewindow {path} {
  grab release $path
  destroy $path
}

proc setWidgetColor {path} {
  global color

  foreach c "[array names color *back*]" {
    catch {
      $path config -$c $color($c)
    }
  }
  foreach c "[array names color *fore*]" {
    catch {
      $path config -$c $color($c)
    }
  }
}

proc Button {path args} {
  global button_font

  if {[string length "$button_font"]} {
    eval {button $path} -font $button_font $args
  } else {
    eval {button $path} $args
  }
  setWidgetColor $path
}

proc Checkbutton {path args} {
  global checkbutton_font
  if {[string length "$checkbutton_font"]} {
    eval {checkbutton $path} -font $checkbutton_font $args
  } else {
    eval {checkbutton $path} $args
  }
  setWidgetColor $path
}

proc DefaultButton {path args} {
  Frame $path -relief sunken -borderwidth 1
  eval Button $path.default $args
  pack $path.default
}

proc Entry {name args} {
  global default_entry_bindings entry_bindings entry_font
  if {[string length "$entry_font"]} {
    eval {entry $name -relief sunken} -font $entry_font $args
  } else {
    eval {entry $name -relief sunken} $args
  }
  foreach x "$default_entry_bindings" {
    bind $name [lindex "$x" 0] [lindex "$x" 1]
  }
  foreach x "$entry_bindings" {
    bind $name [lindex "$x" 0] [lindex "$x" 1]
  }
  setWidgetColor $name
}

proc Label {name args} {
  global label_font
  if {[string length "$label_font"]} {
    eval {label $name} -font $label_font $args
  } else {
    eval {label $name} $args
  }
  setWidgetColor $name
}

proc Listbox {name args} {
  global listbox_font
  if {[string length "$listbox_font"]} {
    eval {listbox $name} -font $listbox_font $args
  } else {
    eval {listbox $name} $args
  }
  setWidgetColor $name
}

proc Scrollbar {name args} {
  eval {scrollbar $name} $args
  setWidgetColor $name
}

proc Frame {name args} {
  eval {frame $name} $args
  setWidgetColor $name
}

proc Toplevel {name args} {
  set top [catch [eval {toplevel $name} $args]]
  setWidgetColor $name
  return top
}

proc Menu {name args} {
  global menu_font
  if {[string length "$menu_font"]} {
    eval {menu $name} -font $menu_font $args
  } else {
    eval {menu $name} $args
  }
  setWidgetColor $name
}

proc Menubutton {name args} {
  global menubutton_font
  if {[string length "$menubutton_font"]} {
    eval {menubutton $name} -font $menubutton_font $args
  } else {
    eval {menubutton $name} $args
  }
  setWidgetColor $name
}

proc Radiobutton {path args} {
  global radiobutton_font
  if {[string length "$radiobutton_font"]} {
    eval {radiobutton $path} -font $radiobutton_font $args
  } else {
    eval {radiobutton $path} $args
  }
  setWidgetColor $path
}

proc Text {name args} {
  global text_font
  if {[string length "$text_font"]} {
    eval text $name -relief sunken -wrap word -setgrid 0 -font $text_font $args
  } else {
    eval text $name -relief sunken -wrap word -setgrid 0 $args
  }
  bind $name <Delete> "[bind Text <BackSpace>] ; break"
  bind $name <Meta-o> {%W insert insert \017}
  bind $name <Meta-u> {%W insert insert \037}
  bind $name <Meta-v> {%W insert insert \026}
  setWidgetColor $name
}

proc EntryOneWordLeft {widget} {
  set insert "[$widget index insert]"
  set left "[string range "[$widget get]" 0 [expr $insert-1]]"
  set space "[string last " " "$left"]"
  if {$space == -1} {
    $widget icursor 0
  } else {
    $widget icursor [expr $space+1]
  }
}

proc EntryOneWordRight {widget} {
  set insert "[$widget index insert]"
  set right "[string range "[$widget get]" $insert end]"
  set space "[string first " " "$right"]"
  if {$space == -1} {
    $widget icursor end
  } else {
    $widget icursor [expr $insert+$space]
  }
}

proc InsertFromClipboard {widget} {
  if ![catch {selection get -selection CLIPBOARD} result] {
    $widget insert insert "$result"
  }
}

proc InitStyles { } {
  global bold_style reverse_style underline_style special_style
  global styles_list user_styles normal_style

  set styles_list "[list \
      "" \
      "$bold_style" \
      "$reverse_style" \
      "$reverse_style $bold_style" \
      "$underline_style" \
      "$underline_style $bold_style" \
      "$underline_style $reverse_style" \
      "$underline_style $reverse_style $bold_style" \
      "$special_style" \
      "$bold_style $special_style" \
      "$reverse_style $special_style" \
      "$reverse_style $bold_style $special_style" \
      "$underline_style $special_style" \
      "$underline_style $bold_style $special_style" \
      "$underline_style $reverse_style $special_style" \
      "$underline_style $reverse_style $bold_style $special_style"]"

  set ulen [llength "$user_styles"]
  set slen [llength "$styles_list"]
  for {set j 0} {$j < $ulen} {incr j} {
    set tmp "[lindex "[lindex "$user_styles" $j]" 1]"
    for {set i 0} {$i < $slen} {incr i} {
      lappend styles_list "$tmp [lindex "$styles_list" $i]"
    }
  }
}

proc ConfigureStyles {num} {
  global bold_style reverse_style underline_style special_style
  global styles_list user_styles normal_style search_style margin
  global win
  global use_margin$num

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }
  set name [GetPathFromNum $num].body.left.traffic
  set back [$name.text cget -background]
  set fore [$name.text cget -foreground]

  # configure styles to tag
  set slen [llength "$styles_list"]
  for {set i 0} {$i < $slen} {incr i} {
    set tmp "[lindex "$styles_list" $i]"
    if {[set use_margin$num]} {
      eval $name.text tag configure $i -lmargin2 $margin(size) \
       -foreground "$fore" -background "$back" "$tmp"
    } else {
      eval $name.text tag configure $i -lmargin2 0 \
       -foreground "$fore" -background "$back" "$tmp"
    }
  }
  for {set i $win($num,taglo)} {$i <= $win($num,taghi)} {incr i} {
    if {[set use_margin$num]} {
      $name.text tag configure tag_$num\_$i -lmargin2 $margin(size)
    } else {
      $name.text tag configure tag_$num\_$i -lmargin2 0
    }
  }

  # reverse styles werden hier nachträglich eingebaut
  set ulen [llength "$user_styles"]
  for {set j 0} {$j < [expr ($ulen+1)*2]} {incr j} {
    foreach i "2 3 6 7" {
      set stylenum [expr $j * 8 + $i]
      set back [$name.text tag cget $stylenum -foreground]
      set fore [$name.text tag cget $stylenum -background]
      $name.text tag configure $stylenum -lmargin2 $margin(size) -foreground "$fore" -background "$back"
    }
  }
  eval $name.text tag configure search -lmargin2 $margin(size) $search_style
  if {[set use_margin$num]} {
    $name.text configure -tabs "$margin(size) left"
  } else {
    $name.text configure -tabs "0 left"
  }
}

proc listbox_vs {path} {
  # vertical, single
  Frame $path -bd 0
  Scrollbar $path.scroll -width 10 -orient vertical \
    -command [list $path.view yview]
  Listbox $path.view -exportselection false -relief raised \
    -yscrollcommand "$path.scroll set"
  pack $path.view -expand true -side left -fill both
  pack $path.scroll -side left -fill y
}

proc listview {name args} {
  global margin
  Frame $name
  eval {Text $name.text -yscroll [list $name.scroll set] \
   -state disabled} $args

  Scrollbar $name.scroll -width 10 -orient vertical -command [list $name.text yview]
  pack $name.scroll -side right -fill y
  pack $name.text -side left -expand true -fill both
  return $name
}

proc TextPageUp {widget} {
  set top "[lindex [$widget yview] 0]"
  set bottom "[lindex [$widget yview] 1]"
  set diff [expr $bottom - $top]
  if {$top > 0} {
    $widget yview moveto [expr $top - $diff]
  }
}

proc TextPageDown {widget} { 
#  set top "[lindex [$widget yview] 0]"
  set bottom "[lindex [$widget yview] 1]"
#  set diff [expr $bottom - $top]
  if {$bottom < 1} {
    $widget yview moveto $bottom
  }
}

proc textSearch {num string tag} {
  global win search_lastview search_laststring

  # Die gefundenen Texte einer alten Suche werden wieder normal 
  # dargestellt.
  set w [GetPathFromNum $num].body.left.traffic.text
  $w tag remove search 0.0 end
  if {$string == ""} {
    return
  }

  # Gefundene Texte werden hervorgehoben.
  set cur 1.0
  while 1 {
    set cur [$w search -nocase -count length -- $string $cur end]
    if {$cur == ""} {
      break
    }
    $w tag add $tag $cur "$cur + $length char"
    set cur [$w index "$cur + $length char"]
  }

  set lostring "[string tolower "$string"]"
  set startview "[lindex [$w yview] 0]"
  set endview "[lindex [$w yview] 1]"
  set startnum [expr int($startview * $win($num,visible) + 1)]
  set endnum [expr int($endview * $win($num,visible))]

  set skipthisview 1
  if {[strcmp "$search_laststring" "$string"]} {
    set skipthisview 0
  } elseif {[expr [format "%.5g" $startview]-[format "%.5g" $search_lastview]] > 0.00001} {
    set skipthisview 0
  } elseif {$search_lastview > $endview} {
    set skipthisview 0
  }
  set search_laststring "$string"

  if {$skipthisview == 0} {
    # bereits sichtbaren Bereich durchsuchen
    for {set i $startnum} {$i <= $endnum} {incr i} {
      set loline "[string tolower "[$w get $i.0 [expr $i+1].0]"]"
      if {[string first "$lostring" "$loline"] != -1} {
	set search_lastview [format "%.5g" [expr double($i-1) / $win($num,visible)]]
	return
      }
    }
  }

  # Rest des Textes durchsuchen
  if {$endview != 1} {
    for {set i [expr $endnum+1]} {$i <= $win($num,visible)} {incr i} {
      set loline "[string tolower "[$w get $i.0 [expr $i+1].0]"]"
      if {[string first "$lostring" "$loline"] != -1} {
	set search_lastview [format "%.5g" [expr double($i-1) / $win($num,visible)]]
        $w yview moveto $search_lastview
        return
      }
    }
  }

  # Anfang des Textes durchsuchen
  for {set i 1} {$i < $startnum} {incr i} {
    set loline "[string tolower "[$w get $i.0 [expr $i+1].0]"]"
    if {[string first "$lostring" "$loline"] != -1} {
      set search_lastview [format "%.5g" [expr double($i-1) / $win($num,visible)]]
      $w yview moveto $search_lastview
      return
    }
  }

  # string nicht gefunden
  beep
}

proc GetPathFromNum {num} {
  return ".win$num"
}

proc GetMsgWinFromNick {nick type} {
  # type: 0=old, 1=new
  global win messagewindow crapwindow destlog

  if {[string length "$nick"] == 0} {
    set destlog "<crap>"
    return $crapwindow
  }

  set destlog "$nick"
  foreach x "$win(list)" {
    if {[strcmp "$win($x,query)" "$nick"] == 0} {
      return $x
    }
  }
  if {"$type" != "0"} {
    set num [MainWindow -2]
    set win($num,query) $nick
    UpdateTitle $num
    return $num
  }
  return $messagewindow
}

#######################
#  TRAFFIC FUNCTIONS  #
#######################

proc formatted2text {num widget line prestylenum} {
  global win url_style special_style styles_list nickname
  global msgid_style margin

  set style "[lindex "$styles_list" $prestylenum]"

  # URLs
  set i [string first "://" "$line"]
  if {$i != -1} {
    # '://' found
    while {$i != -1} {
      set rightURLfound 0
      set prefix "[string trimleft "[lindex "[split "[string range "$line" 0 [expr $i-1]]" "\"\!\,\:\{\}\[\]\<\>\'\(\) "]" end]" "\t\n"]"

      foreach x "http ftp gopher telnet https wais nntp" {
        set xlen [string length "$x"]

	if {[strcmp "$prefix" "$x"] == 0} {
	  set suffix "[lindex "[split "[string range "$line" [expr $i+3] end]" "\"\{\}\[\]\<\>\' "]" 0]"
	  set suffix "[string trimright "$suffix" "!?.,"]"

	  # Ggf. wird bei runden Klammern geschnitten.
	  set url "$prefix://"
	  while {"$suffix" != ""} {
	    set auf [string first "(" "$suffix"]
	    set zu [string first ")" "$suffix"]
	    if {$zu == -1} {
	      if {$auf != -1} {
		append url "[string range "$suffix" 0 [expr $auf-1]]"
	      } else {
		append url "$suffix"
	      }
	    } elseif {$auf == -1} {
	      if {$zu != -1} {
		append url "[string range "$suffix" 0 [expr $zu-1]]"
	      } else {
		append url "$suffix"
	      }
	    } else {
	      if {$zu < $auf} {
		append url "[string range "$suffix" 0 [expr $zu-1]]"
	      } else {
		append url "[string range "$suffix" 0 $zu]"
		set suffix "[string range "$suffix" [expr $zu+1] end]"
		continue
	      }
	    }
	    break
	  }

	  set rightURLfound 1
	  incr win($num,taghi)

	  # Der Text vor dem URL muß noch eingefügt werden.
	  $widget insert end "[string range "$line" 0 [expr $i-$xlen-1]]" $prestylenum
	  global use_margin$num
	  if {[set use_margin$num]} {
	    eval $widget tag configure tag_$num\_$win($num,taghi) -lmargin2 $margin(size) $style $url_style
	  } else {
	    eval $widget tag configure tag_$num\_$win($num,taghi) -lmargin2 0 $style $url_style
	  }

	  set line "[string range "$line" [expr $i-$xlen+[string length "$url"]] end]"
	  if {[strcmp "$url" "$prefix://"] == 0} {
	    $widget insert end "$url" $prestylenum
	  } else {
	    global url_list
	    set k [lsearch -exact "$url_list" "$url"]
	    if {$k != -1} {
		set url_list "[lreplace "$url_list" [expr $k-1] $k]"
		if {[winfo exists .url]} {
		.url.list.entries delete [expr $k/2]
		}
	    }
	    lappend url_list "[longdate]"
	    lappend url_list "$url"
	    if {[winfo exists .url]} {
		set end "[lindex "[.url.list.entries yview]" 1]"
		.url.list.entries insert end "[longdate]  $url"
		if {$end == 1} {
		.url.list.entries yview end
		}
	    }
	    $widget insert end "$url" tag_$num\_$win($num,taghi)

	    $widget tag bind tag_$num\_$win($num,taghi) <Any-Enter> "$widget tag configure tag_$num\_$win($num,taghi) -background #e4e4e4"
	    $widget tag bind tag_$num\_$win($num,taghi) <Any-Leave> "$widget tag configure tag_$num\_$win($num,taghi) $url_style"
	    $widget tag bind tag_$num\_$win($num,taghi) <ButtonPress> "global selected_url ; set selected_url \{[strreplace "[expand "$url"]" "%" "%%"]\} ; ExecUrlAction"

	    ExecOnCommands url_detect window "$num" url "[expand "$url"]"
	  }
	  break
        }
      }
      if {$rightURLfound} {
        set i [string first "://" "$line"]
      } else {
        break
      }
    }
    $widget insert end "$line" $prestylenum
    return
  }

  # MessageIDs
  if {[string first "@" "$line"] != -1} {
    set cutline "$line"
    set i [string first "<" "$cutline"]
    while {$i != -1} {
      $widget insert end "[string range "$cutline" 0 [expr $i-1]]" $prestylenum
      set possible "[string range "$cutline" $i end]"
      set j [string first ">" "$possible"]
      if {$j != -1} {
	set possible "[string range "$possible" 0 $j]"
	if [regexp -- {^<[^ <>@]+@[^ <>@]+>$} "$possible"] {
	  # dieser Teil ist eine MessageID
          incr win($num,taghi)

	  global use_margin$num
	  if {[set use_margin$num]} {
	    eval $widget tag configure tag_$num\_$win($num,taghi) -lmargin2 $margin(size) $style $msgid_style
	  } else {
	    eval $widget tag configure tag_$num\_$win($num,taghi) -lmargin2 0 $style $msgid_style
	  }
	  global msgid_list
	  set k [lsearch -exact "$msgid_list" "$possible"]
	  if {$k != -1} {
	    set msgid_list "[lreplace "$msgid_list" [expr $k-1] $k]"
            if {[winfo exists .msgid]} {
	      .msgid.list.entries delete [expr $k/2]
	    }
	  }
	  lappend msgid_list "[longdate]"
	  lappend msgid_list "$possible"
	  if {[winfo exists .msgid]} {
	    set end "[lindex "[.msgid.list.entries yview]" 1]"
	    .msgid.list.entries insert end "[longdate]  $possible"
	    if {$end == 1} {
	      .msgid.list.entries yview end
	    }
	  }

  	  $widget insert end "$possible" tag_$num\_$win($num,taghi)
	  set cutline "[string range "$cutline" [expr $i+$j+1] end]"
          $widget tag bind tag_$num\_$win($num,taghi) <Any-Enter> "$widget tag configure tag_$num\_$win($num,taghi) -background #e4e4e4"
          $widget tag bind tag_$num\_$win($num,taghi) <Any-Leave> "$widget tag configure tag_$num\_$win($num,taghi) $msgid_style"
          $widget tag bind tag_$num\_$win($num,taghi) <ButtonPress> "global selected_msgid ; set selected_msgid \{[expand "$possible"]\} ; ExecMsgIDAction"

	  ExecOnCommands msgid_detect window "$num" msgid "[expand "$possible"]"
	} else {
	  # dieser Teil ist keine MessageID
  	  $widget insert end "$possible" $prestylenum
	  set cutline "[string range "$cutline" [expr $i+$j+1] end]"
	}
      } else {
	# kein '> ' vorhanden
        $widget insert end "[string trimright "$possible" " "]" $prestylenum
	set cutline ""
	break
      }
      set i [string first "<" "$cutline"]
    }
    $widget insert end "[string trimright "$cutline" " "]" $prestylenum
    return
  }

  # no URLs and no MessageIDs in line
  $widget insert end "$line" $prestylenum
}

proc print2log {destlog line} {
  global log nickname raw
  set newline ""

  set destlog2 "[string tolower "$destlog"]"
  set source "$destlog2"
  for {set i 0} {$i < 2} {incr i} {
    # Wurde ein Logfile für diese Quelle geöffnet?
    set lodestlog2 "[string tolower "$destlog2"]"
    if {[info exists log($lodestlog2)]} {
      set num "$log($lodestlog2)"

      # Escape-Codes werden herausgefiltert.
      if {[string length "$newline"] == 0} {
	set len [string length "$line"]
	for {set j 0} {$j < $len} {incr j} {
	  set char "[string index "$line" $j]"
	  if {"$char" > "\x1f"} {
	    append newline "$char"
	  } elseif {"$char" == "\x09"} {
	    append newline " "
	  }
	}
      }

      # Das Logfile wird ggf. neu angelegt/geöffnet.
      set handle "$log($num,handle)"
      if ![file exists "$log($num,file)"] {
	close $handle
	
	set handle "[OpenFile "$log($num,file)" a+]"
	if {[string length "$handle"]} {
	  set log($num,handle) "$handle"
	  set log($num,opendate) "[longdate]"
	  puts $handle "\nLogfile opened for $destlog2 at:  [longdate]"
	} else {
	  # Der Parameter für DeleteLog befindet sich in der Variablen
	  # "destlog". Das File konnte nicht geöffnet werden.
	  DeleteLog $num
	  continue
	}
      }

      if {$log($num,dateswitch)} {
        set prefix "[longdate]  "
      } else {
        set prefix ""
      }
      if {[string compare "$destlog2" "<all>"]} {
	# Die aktuelle Zeile wird ins Logfile geschrieben.
	if {[string compare "$destlog2" "<crap>"]} {
	  if {$log($num,rawswitch)} {
	    if {[string length "$raw(line)"]} {
	      puts $handle "$prefix$raw(line)"
	    }
	  } else {
	    puts $handle "$prefix$newline"
	  }
	} else {
	  puts $handle "$prefix$newline"
	}
      } else {
	# Die Quelle wird mit in die Zeile integriert und ins Logfile
	# geschrieben. Query-Log-Messages werden gefiltert.
	if [regexp -- {^[\#\&\+\<].*} "$source"] {
	  if {$log($num,rawswitch) && [string compare "$source" "<crap>"]} {
	    if {[string length "$raw(line)"]} {
  	      puts $handle "[format "$prefix%-10s : %s" "$source" "$raw(line)"]"
	    }
	  } else {
	    puts $handle "[format "$prefix%-10s : %s" "$source" "$newline"]"
	  }
	}
      }
      flush $handle
    }
    # Auf zur zweiten Runde mit "<all>"!
    set destlog2 "<all>"
  }
  if {[string compare "$destlog" "<messages"] == 0} {
    set raw(line) ""
  }
}

proc riddletext {line} {
  set newline ""

  for {set i 0} {$i <= [string length "$line"]} {incr i} {
    set char "[string index "$line" $i]"
    if {"$char" > "\x1f"} {
      append newline "$char"
    } else {
      switch -- "$char" {
	"\x02" {}
	"\x0f" {}
	"\x11" {}
        "\x16" {}
        "\x1f" {}
        "\a" {}
	"\x03" {
	  # mIRC-Farben
	  set j [expr $i+1]
	  if {[regexp {[0-9]} "[string index "$line" $j]"]} {
	    incr i ; incr j
	    if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    if {[regexp {,[0-9]} "[string range "$line" $j [expr $j+1]]"]} {
	      set i [incr j] ; incr j
	      if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	      if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    }
	  }
        }
        default {
          append newline "$char"
        }
      }
    }
  }
  return "$newline"
}

proc print2text {num line args} {
  global win lines_max user_styles nickname style
  global away margin

  set bold_state      0
  set bold            1
  set invers_state    0
  set invers          2
  set underline_state 0
  set underline       4
  set special_state   0
  set special         8

  set beep_count      0

  set len [string length "$line"]
  if {$len == 0} { return }

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }
  set path "[GetPathFromNum $num]"

  if {[string match "*\x0f\x0f" "$line"]} {
    set add_linetime 1
    set line "[string range "$line" 0 [expr $len-3]]"
  } else {
    set add_linetime 0
  }

  if {[string compare "[wm state $path]" "iconic"] == 0} {
    global auto_popup$num
    if {[set auto_popup$num]} {
      wm deiconify $path
    }
    if {$win($num,touched) == 0} {
      set win($num,touched) 1
      UpdateTitle $num
    }
  } elseif {$win($num,touched)} {
    set win($num,touched) 0
    UpdateTitle $num
  }

  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  $widget configure -state normal

  global display_types$num
  if {[set display_types$num]} {
    if {[regexp -- {^[0-9][0-9][0-9]\ .*} "$line"]} {
      set line "\( [string range "$line" 0 2] \)\t[string range "$line" 4 end]"
    } elseif {[regexp -- {^(\*\*\*|\×\×\×)\ .*} "$line"]} {
      if {[string length "$margin(text)"]} {
	set line "\[ $margin(text) \]\t[string range "$line" 4 end]"
	set margin(text) ""
      } else {
	set line "[string range "$line" 0 2]\t[string range "$line" 4 end]"
      }
    } elseif {[regexp -- {^\+\+\+\ .*} "$line"]} {
      set line "+++\t[string range "$line" 4 end]"
    }
  } else {
    if {[regexp -- {^([0-9][0-9][0-9]|\*\*\*|\×\×\×|\+\+\+)\ .*} "$line"]} {
      set line "[string range "$line" 0 2]\t[string range "$line" 4 end]"
    }
  }

  # calculate style number
  set ircstyle 0 ; set userstyle 0
  set loline "[string tolower "$line"]"
  for {set i 0} {$i < [llength "$user_styles"]} {incr i} {
    set regexpr "[lindex "[lindex "$user_styles" $i]" 0]"
    set regexpr "[strreplace "$regexpr" "\$me" "[expand "$nickname"]"]"
    if [regexp -- "[string tolower "$regexpr"]" "$loline"] {
      set userstyle [expr $i + 1]
      set command "[lindex "[lindex "$user_styles" $i]" 2]"
      if {[string length "$command"] && "#" != "[string index "$command" 0]"} {
	eval $command
      }
      break
    }
  }
  set style [expr $userstyle * 16]

  set part ""
  if {$win($num,visible) < 1} {
    incr win($num,visible)
    incr win($num,lines)
  } elseif {$win($num,visible) < [expr $lines_max]} {
    incr win($num,visible)
    incr win($num,lines)
    $widget insert end "\n"
  } else {
    # Wurde in dieser Zeile Styles benutzt, die wieder freigegeben
    # werden können?
    set i [lsearch -exact "[$widget dump 1.0 2.0]" "tag_$num\_$win($num,taglo)"]
    while {$i != -1 && [expr $i%3] == 1} {
      $widget tag delete tag_$num\_$win($num,taglo)
      incr win($num,taglo)
      set i [lsearch -exact "[$widget dump 1.0 2.0]" "tag_$num\_$win($num,taglo)"]
    }
    $widget delete 1.0 2.0
    $widget insert end "\n"
  }

  if {$add_linetime} {
    append line "\x0f[linetime]"
  }
  if {"$args" != ""} {
    global next styles_list normal_style
    $widget insert end "[string range "$line" 0 [expr $next(left)-1]]" $style
    set style_text "[lindex "$styles_list" $style]"

    incr win($num,taghi)
    eval $widget tag configure tag_$num\_$win($num,taghi) $style_text -background "#d9d9d9" $normal_style

    $widget tag bind tag_$num\_$win($num,taghi) <Any-Enter> "$widget tag configure tag_$num\_$win($num,taghi) -background #f0f0f0"
    $widget tag bind tag_$num\_$win($num,taghi) <Any-Leave> "$widget tag configure tag_$num\_$win($num,taghi) -background #d9d9d9 $normal_style"

    set nick "[expand "[string range "$line" $next(left) [expr $next(right)-1]]"]"
    $widget tag bind tag_$num\_$win($num,taghi) <Button-1> "send2irc \"/whois $nick\" ; break"
    $widget tag bind tag_$num\_$win($num,taghi) <Button-2> "send2irc \"/ctcp $nick version\" ; break"
    $widget tag bind tag_$num\_$win($num,taghi) <Button-3> "UserPopup2 $num %W %X %Y $nick ; break"

    $widget insert end "[string range "$line" $next(left) [expr $next(right)-1]]" tag_$num\_$win($num,taghi)
    set start $next(right)
  } else {
    set start 0
  }

  for {set i $start} {$i <= [string length "$line"]} {incr i} {
    set char "[string index "$line" $i]"
    if {"$char" > "\x1f"} {
      append part "$char"
    } else {
      if {[string length "$part"]} {
        formatted2text $num $widget "$part" $style
        set part ""
      }
      switch -- "$char" {
	"\x02" {
          if {$bold_state} {
      	    set style [expr $style - $bold]
	    set bold_state 0
          } else {
            set style [expr $style + $bold]
	    set bold_state 1
          }
        }
	"\x03" {
	  # mIRC-Farben
	  set j [expr $i+1]
	  if {[regexp {[0-9]} "[string index "$line" $j]"]} {
	    if {!$special_state} {
	      set style [expr $style + $special]
	      set special_state 1
	    }
	    incr i ; incr j
	    if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    if {[regexp {,[0-9]} "[string range "$line" $j [expr $j+1]]"]} {
	      set i [incr j] ; incr j
	      if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	      if {[regexp {[0-9]} "[string index "$line" $j]"]} {incr i;incr j}
	    }
	  } else {
	    if {$special_state} {
	      set style [expr $style - $special]
	      set special_state 0
	    } else {
	      set style [expr $style + $special]
	      set special_state 1
	    }
	  }
        }
	"\x0f" {
          if {$bold_state} {
      	    set style [expr $style - $bold]
	    set bold_state 0
          }
          if {$special_state} {
      	    set style [expr $style - $special]
	    set special_state 0
          }
          if {$invers_state} {
      	    set style [expr $style - $invers]
	    set invers_state 0
	  }
	  if {$underline_state} {
	    set style [expr $style - $underline]
	    set underline_state 0
          }
        }
	"\x11" {
	  # PIRCH "fett"
	  if {$special_state} {
	    set style [expr $style - $special]
	    set special_state 0
	  } else {
	    set style [expr $style + $special]
	    set special_state 1
	  }
	}
        "\x16" {
          if {$invers_state} {
      	    set style [expr $style - $invers]
	    set invers_state 0
	  } else {
	    set style [expr $style + $invers]
	    set invers_state 1
          }
        }
        "\x1f" {
	  if {$underline_state} {
	    set style [expr $style - $underline]
	    set underline_state 0
          } else {
      	    set style [expr $style + $underline]
	    set underline_state 1
          }
        }
        "\a" {
	  global beeptext
	  if [string length "$beeptext"] {
	    set prefix "[string range "$line" 0 [expr $i-1]]"
	    set suffix "[string range "$line" [expr $i+1] end]"
	    set line "$prefix$beeptext$suffix"
	    set i [expr $i-1]
	  }
	  if {$beep_count < 3} {
	    if {[string length "$away"]} {
	      global beep_on_ctrlG_when_away
	      if {$beep_on_ctrlG_when_away} {
		beep
	      }
	    } else {
	      global beep_on_ctrlG_when_present
	      if {$beep_on_ctrlG_when_present} {
		beep
	      }
	    }
	    incr beep_count
	  }
        }
        default {
          append part "$char"
        }
      }
    }
  }
  if {[string length "$part"]} {
    formatted2text $num $widget "$part" $style
  }
  $widget configure -state disabled
  if {$end == 1} {
    $widget yview end
  }
#  update idletasks
}

proc print2crap {line} {
  global crapwindow
  print2log "<crap>" "$line"
  print2text $crapwindow "$line"
}

proc print2channels {cnums line} {
  global chan win margin

  set mtext "$margin(text)"
  set windows ""
  foreach x "$cnums" {
    # Existiert der Kanal?
    if {[lsearch -exact "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != "" && [string length "$line"]} {
	if {[lsearch -exact "$win(list)" "$chan($x,window)"] != -1} {
	  # Wurde diese Meldung evtl. schon dort ausgegeben?
	  if {[lsearch -exact "$windows" "$chan($x,window)"] == -1} {
	    set margin(text) "$mtext"
#	    if {[string match "*\x0f\x0f" "$line"]} {
#	      print2text $chan($x,window) "$line[linetime]"
#	    } else {
	      print2text $chan($x,window) "$line"
#	    }
	    lappend windows $chan($x,window)
	  }
	}
      }
      print2log "$chan($x)" "$line"
    }
  }
}

proc belated2channels {nows pres line} {
  # Diese Prozedur ist nur für Netsplits bzw. Netjoins notwendig.
  global chan win margin

  set mtext "$margin(text)"
  set windows ""
  foreach x "$pres" {
    # Existiert der Kanal?
    if {[lsearch -exact "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != ""} {
	if {[lsearch -exact "$win(list)" "$chan($x,window)"] != -1} {
	  # Diese Meldung wurde schon dort ausgegeben!
	  lappend windows $chan($x,window)
	}
      }
      print2log "$chan($x)" "$line"
    }
  }
  foreach x "$nows" {
    # Existiert der Kanal?
    if {[lsearch -exact "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != ""} {
	if {[lsearch -exact "$win(list)" "$chan($x,window)"] != -1} {
	  # Wurde diese Meldung evtl. schon dort ausgegeben?
	  if {[lsearch -exact "$windows" "$chan($x,window)"] == -1} {
	    set margin(text) "$mtext"
	    print2text $chan($x,window) "$line"
	    lappend windows $chan($x,window)
	  }
	}
      }
      print2log "$chan($x)" "$line"
    }
  }
}

proc print2all {line args} {
  global win log crapwindow margin

  set mtext "$margin(text)"
  set logsonly [lsearch -exact "$args" "logsonly"]
  set withoutcrap [lsearch -exact "$args" "withoutcrap"]
  if {$logsonly == -1} {
    foreach x "$win(list)" {
      if {$withoutcrap == -1 || $x != $crapwindow} {
	set margin(text) "$mtext"
	print2text $x "$line"
      }
    }
  }
  foreach x "$log(list)" {
    if {$withoutcrap == -1 || [strcmp "$log($x)" "<crap>"]} {
      print2log $log($x,handle) "$line"
    }
  }
}

proc entry2irc {num} {
  global win

  set entry "[GetPathFromNum $num].cmdline"
  set line "[$entry get]"
  if {$win($num,touched)} {
    set win($num,touched) 0
    UpdateTitle $num
  }
  if {[string length "$line"] == 0} {
    return
  }
  # History und evtl. MsgHistory werden erweitert.
  AddToHistory $num "$line"
  if {[strmatch "/msg *" "$line"]} {
    AddToMsgHistory "[lIndex "$line" 1]"
  }
  send2tkirc $num "$line"

  # Die Kommandozeile wird gelöscht.
  if {[lsearch -exact "$win(list)" $num] != -1} {
    $entry delete 0 end 
  }
}

proc entry2history {num} {
  set entry "[GetPathFromNum $num].cmdline"
  set newline "[$entry get]"

  # Leere Eingaben werden nicht weiter bearbeitet.
  if {[string length "$newline"] == 0} {
    return
  }

  # History und evtl. MsgHistory werden erweitert.
  AddToHistory $num "$newline"
  if {[strmatch "/msg *" "$newline"]} {
    AddToMsgHistory "[lIndex "$newline" 1]"
  }

  $entry delete 0 end 
}

#########################
#  Internet Relay Chat  #
#########################

proc send2tkirc {num line args} {
  global ip win crapwindow

  if {[lsearch -exact "$win(list)" $num] == -1} {
    # Da das angegebene Fenster nicht mehr vorhanden ist, wird
    # die Ausgabe ins crapwindow umgeleitet.
    set num $crapwindow
  }
  if {"$ip" == ""} {
    # Die Pipe zum ircII ist nicht mehr vorhanden.
    return
  }

  set actual "[GetActual $num]"
  if {"$actual" != "*" && "$actual" != ""} {
debug "out: /join [GetActual $num]"
    puts $ip "/join [GetActual $num]"
  }

  set maxchars 225
  set header ""
  foreach x "[split "$line" "\n"]" {
    if {"$header" == "" && "[string index "$line" 0]" == "/"} {
      # Zeile enthält Kommando.
      switch -glob -- "[string tolower "$x"]" {
	"/msg *" {
	  set header "/msg [lIndex "$x" 1] "
	  set x "[cutwords "$x" 2]"
	}
	"/notice *" {
	  set header "/notice [lIndex "$x" 1] "
	  set x "[cutwords "$x" 2]"
	}
	"/me *" {
	  set header "/me "
	  set x "[cutwords "$x" 1]"
	}
	"/describe *" {
	  set header "/describe [lIndex "$x" 1] "
	  set x "[cutwords "$x" 2]"
	}
	default {
	  # Kommando (außer msg und notice)
	  send2irc "[parsecl $num "[string range "$line" 0 $maxchars]"]" $args
	  return
	}
      }
    } else {
      # Zeile enthält kein Kommando.
      if {"$win($num,query)" == ""} {
	# Kein Query vorhanden.
	if {[strcmp "*" "[GetActual $num]"] == 0} {
	  # Kein aktueller Kanal vorhanden.
	  set margin(text) "error"
	  print2text $num "××× You have no channel joined in this window"
	  return
        } else {
	  # Aktueller Kanal ist vorhanden, daher muß evtl. die Liste
	  # der Nickname-Completion aktualisiert werden.
	  if {[string match "*:" "[lIndex "$x" 0]"]} {
	    set cnum [GetChannelNumber "[GetActual $num]"]
	    global chan
	    set nick "[string trim "[lIndex "$x" 0]" " :"]"
	    if [strcmp "$nick" "*"] {
	      set i [lsearch -exact "$chan($cnum,cnicks)" "$nick"]
	      if {$i != -1} {
		set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i]"
		set chan($cnum,ctimes) "[lreplace "$chan($cnum,ctimes)" $i $i]"
		set chan($cnum,cnicks) "[linsert "$chan($cnum,cnicks)" 0 "$nick"]"
		set chan($cnum,ctimes) "[linsert "$chan($cnum,ctimes)" 0 "[clock seconds]"]"
	      }
	    }
	  }
	}
      } else {
	# Query vorhanden.
	set header "/msg [expandescape "$win($num,query)"] "
      }
    }

    # Falls kein Header existiert, ist Vorsicht geboten!
    if {"$header" == ""} {
      set header "/say "
    }

    # Hier wird verhindert, daß getippte Zeilen, die zu lang
    # geraten sind, Probleme bereiten!
    while {[string length "$x"] > $maxchars} {
      set cutnum [string last " " "[string range "$x" 0 $maxchars]"]
      if {$cutnum == -1} {
	set cutnum $maxchars
      }
      send2irc "[parsecl $num "$header[string range "$x" 0 $cutnum]..."]" $args
      set x "[string range "$x" [expr $cutnum+1] end]"
    }
    send2irc "[parsecl $num "$header$x"]" $args
  }
}

proc send2irc {line args} {
  global ip crapwindow sendqueue

  if {"$ip" != "" && "$line" != ""} {
debug "out: $line"
    if {"[lindex "$args" 0]" == ""} {
      puts $ip "$line\n"
      flush $ip
    } else {
      lappend sendqueue "$line"
    }
  }
}

proc irc2text { } {
  global win ip on_args crapwindow destlog nickname
  global commandqueue filterqueue away raw lines_all

  if {[gets $ip line] >= 0} {
    incr lines_all
    update idletasks

    foreach v "[array names on_args *]" {
      unset on_args($v)
    }

    global filternext direct2crap margin next
debug "in : $line"
    if {"[string index "$line" 0]" == "~"} {
      # ~raw <text>
      global raw ; set raw(line) "[string range "$line" 5 end]"

      set destlog "<crap>"
      set on_args(window) $crapwindow
      set filternext 0 ; set direct2crap 0 ; set margin(text) ""

      set next(direct) 0 ; set next(chatwin) 0 ; set next(beep) 0
      set next(towin) "" ; set next(tolog) "" ; set next(pattern) ""
      set next(from) "" ; set next(to) ""
      set next(left) 0 ; set next(right) 0
      set line "[parseraw "$line"]"
      set raw(lasttype) "$raw(type)"
      if {[string length "$line"] == 0} {
	return
      }

    } elseif {"[string index "$line" 0]" == "%"} {
      set destlog "<crap>"
      set on_args(window) $crapwindow
      set filternext 0 ; set direct2crap 0 ; set margin(text) ""

      set line "[parseons "$line"]"
      if {[string length "$line"] == 0} {
	return
      }
      append line "\x0f\x0f"

    } else {
      # Evtl schon durch parseraw() bearbeitete Zeilen werden
      # hier herausgefiltert oder direkt ins CRAP geschickt.
      if {$next(direct)} {
	if {$filternext} {
	  set filternext 0
	  return
	}
	if {[string match "$next(pattern)" "$line"]} {

	  # Handelt es sich um eine öffentliche oder eine
	  # private Message?
	  if {[string length "$next(to)"]} {
	    # Falls es das Fenster nicht mehr gibt, wird diese
	    # Message nicht dargestellt.
	    if {[GetDestWin "$next(to)"] == -1} {
	      return
	    }
	    set destlog "$next(to)"
	    set next(tolog) "$next(towin)"
  	    print2text $on_args(window) "$next(towin)\x0f\x0f"

	  } else {
	    # Soll ein Chat-Fenster geöffnet werden?
	    global away send_away_notice san
	    if {[string length "$away"] && $send_away_notice == 1 \
	     && "[string index "$next(towin)" 0]" != "+"} {
	      # Keine Away-Notice beim Empfang von privaten Notices!
	      set i [lsearch -exact "$san(nicks)" "$next(from)"]
	      if {$i == -1} {
		lappend san(nicks) "$next(from)"
		lappend san(times) "[clock seconds]"
		send2tkirc $crapwindow "/notice [expandescape "$next(from) $nickname is away: $san(message)"]"
	      } elseif {[expr [clock seconds]-[lindex "$san(times)" $i]] > 900} {
		set san(times) "[lreplace "$san(times)" $i $i [clock seconds]]"
		send2tkirc $crapwindow "/notice [expandescape "$next(from) $nickname is away: $san(message)"]"
	      }
	    }
	    set on_args(window) [GetMsgWinFromNick $next(from) $next(chatwin)]
	    set destlog "$next(from)"
	    print2log "<messages>" "$next(tolog)"
	    # Die Message wird ausgegeben.
  	    print2text $on_args(window) "$next(towin)\x0f\x0f" hilitenick
	  }
	  print2log $destlog "$next(tolog)"

	  # Evtl. muß gepiept werden. =:^)
	  if {$next(beep)} {
	    beep
	  }

	  set next(direct) 0
	  return

	} else {
#	  print2crap "+++ pattern '$next(pattern)' doesn't match line '$line'"
	  set destlog "<crap>"
	  set on_args(window) $crapwindow
	}
      } else {
	set destlog "<crap>"
	set on_args(window) $crapwindow
      }
      if {$filternext} {
        set filternext 0
        return
      }
      if {$direct2crap} {
	set direct2crap 0
	print2crap "$line"
	return
      }
    }

    if [regexp -- {^\*\*\*\ .*} "$line"] {
        set line "[parse3stars "[string range "$line" 4 end]"]"
    }

    if {[string length "$line"]} {
      set loline "[string tolower "$line"]"
      # scanning filterqueue (pattern/timestamp)
      for {set i 0} {$i < [llength "$filterqueue"]} {incr i;incr i} {
	if {[string match "[lindex "$filterqueue" $i]" "$loline"]} {
	  set filterqueue "[lreplace "$filterqueue" $i [expr $i+1]]"
	  return
	}
	set date "[lindex "$filterqueue" [expr $i+1]]"
	if {"$date" == ""} {
	  print2crap "BUG: filterqueue=\"$filterqueue\", i=$i"
	} else {
	 if {[expr [clock seconds]-[lindex "$filterqueue" [expr $i+1]]] > 30} {
	   set filterqueue "[lreplace "$filterqueue" $i [expr $i+1]]"
	   set i [expr $i-2]
	 }
	}
      }

      # scanning commandqueue (pattern/command)
      set len [llength "$commandqueue"]
      for {set i 0} {$i < $len} {incr i;incr i} {
	if {[strmatch "[lindex "$commandqueue" $i]" "$loline"]} {
	  set command "[lindex "$commandqueue" [expr $i+1]]"
	  if {[string length "$command"]} {
	    eval $command
	  }
	  set commandqueue "[lreplace "$commandqueue" $i [expr $i+1]]"
	  break
	}
      }
      if {[lsearch -exact "$win(list)" $on_args(window)] != -1} {
#	if {[string match "*\x0f\x0f" "$line"]} {
#          print2text $on_args(window) "$line[linetime]"
#	} else {
          print2text $on_args(window) "$line"
#	}
      }
      print2log $destlog "$line"
    }
  } elseif [eof $ip] {
debug "END OF FILE: $line"
    catch {close $ip} result
    set margin(text) "error"
    print2all "××× $result.  Please restart tkirc!"
  } else {
debug "IP: $line"
  }
}

proc ignore {host} {
  AddToFilterQueue {\*\*\*?Ignoring CTCPS from *}
  AddToFilterQueue {\*\*\*?Ignoring INVITES from *}
  send2irc "/ignore *@$host ctcp invites"
}

proc unignore {host} {
  AddToFilterQueue {\*\*\*?Not ignoring CTCPS from *}
  AddToFilterQueue {\*\*\*?Not ignoring INVITES from *}
  send2irc "/ignore *@$host -ctcp -invites"
}

proc UserNumOfChannel {cnum nick} {
  global chan
  if {$cnum != -1} {
    return [lsearch -exact "$chan($cnum,nicks)" "$nick"]
  }
  return -1
}

proc isOpOnChannel {cnum nick} {
  global chan
  set i [lSearch "$chan($cnum,nicks)" "$nick"]
  if {$i != -1} {
    return [lindex "$chan($cnum,olist)" $i]
  }
  return 0
}

proc hasVoiceOnChannel {cnum nick} {
  global chan
  set i [lSearch "$chan($cnum,nicks)" "$nick"]
  if {$i != -1} {
    return [lindex "$chan($cnum,vlist)" $i]
  }
  return 0
}

#####################
#  FILE OPERATIONS  #
#####################

proc OpenFile {name access} {
  global crapwindow margin

  if {[string match "r*" "$access"]} {
    if {[file exists $name] == 0} {
      set margin(text) "error"
      print2crap "××× File '$name' doesn't exist"
      return ""
    }
    if {[file readable $name] == 0} {
      set margin(text) "error"
      print2crap "××× File '$name' is not readable"
      return ""
    }
  } else {
    if {[file exists $name]} {
      if {[file owned $name] == 0} {
	set margin(text) "error"
        print2crap "××× File '$name' is not yours"
        return ""
      }
      if {[file writable $name] == 0} {
	set margin(text) "error"
        print2crap "××× File '$name' is not writable"
        return ""
      }
    }
  }
  if [catch {open $name $access} file] {
    set margin(text) "error"
    print2crap "××× File '$name' could not be opened"
    return ""
  }
  return "$file"
}

proc SaveBuffer {num tofile} {
  global win crapwindow margin

  if {[string length "$tofile"] == 0} {
    FileRequest " Please select the file to save the \nbuffer in!" "Save" "SaveBuffer $num \:file" "" "" 0
    return
  }
  set file "[OpenFile "$tofile" w]"
  if {[string length "$file"]} {
    set w [GetPathFromNum $num].body.left.traffic.text

    for {set i 1} {$i <= $win($num,visible)} {incr i} {
      set line "[$w get $i.0 [expr $i+1].0]"
      puts -nonewline $file "$line"
    }
    close $file
    set margin(text) "note"
    print2crap "××× Buffer of window $num saved to file '$tofile'"
  }
}

proc FileSelect {win type} {
  # type: 0. scan home
  #       1. scan 'path'
  #       2. single click
  #       3. double click

  set dir ""
  switch -exact -- "$type" {
    {0} {
      set dir "."
    }
    {1} {
      set dir "[$win.dir.entry get]"
      if {[string length "$dir"] == 0} {
	set dir "."
      }
      if ![file isdirectory "$dir"] {
        bell
	$win.list.entries delete 0 end
        return
      }
    }
    {2} {
      set sel "[$win.list.entries curselection]"
      if {[llength "$sel"] == 1} {
	set file "[$win.list.entries get $sel]"
        if [file isfile "$file"] {
	  $win.file.entry delete 0 end
	  $win.file.entry insert end "$file"
	}
      }
      return
    }
    {3} {
      set sel "[$win.list.entries curselection]"
      if {[llength "$sel"] == 1} {
	set dir "[$win.list.entries get $sel]"
        if ![file isdirectory "$dir"] {
	  return
	}
      }
    }
  }

  if {![file readable "$dir"]} {
    bell
  } else {
    cd "$dir"
    set dir "[pwd]"
    $win.dir.entry delete 0 end
    $win.dir.entry insert end "$dir"
    $win.list.entries delete 0 end
    foreach i [exec ls -a $dir] {
      $win.list.entries insert end $i
    }
  }
}

proc FileAccept {win command} {
  global selected_file

  set path "[$win.dir.entry get]"
  if {[string length "$path"]} {

    if {"[$win.list.entries curselection]" == ""} {
      set file "[$win.file.entry get]"
      if {[string length "$file"]} {
        set selected_file "$path/$file"
        eval "[strreplace "$command" "\:file" "[expand "$selected_file"]"]"
      }
    } else {
      foreach sel "[$win.list.entries curselection]" {
        set file "[$win.list.entries get $sel]"

        if {[string length "$file"]} {
          set selected_file "$path/$file"
          eval "[strreplace "$command" "\:file" "[expand "$selected_file"]"]"
	}
      }
    }
    closewindow $win
  }
}

proc FileRequest {title ok command1 command2 filename extended} {
  global selected_file win
  incr win(reqcount)

  set selected_file ""
  set command1 "[expand "$command1"]"
  # command2 darf nicht erweitert werden!
  set path ".file$win(reqcount)"
  if [catch {Toplevel $path -class tkirc-request}] {
    raise $path
  } else {
#    grab set .file
    wm title $path  " tkirc: File request "
    bind $path <Escape> "closewindow $path"

    Label $path.reason -text "$title" -relief sunken -bd 1
    pack $path.reason -fill x -ipady 5 -ipadx 5 -padx 2 -pady 5

    Frame $path.dir
    pack $path.dir -fill x
    Label $path.dir.label -text " Directory: "
    pack $path.dir.label -side left
    Entry $path.dir.entry
    pack $path.dir.entry -side left -expand true -fill x
    bind $path.dir.entry <Return> "FileSelect $path 1"

    set f $path.buttons
    Frame $f
    pack $f -fill x -padx 2 -pady 2 -side bottom
    Button $f.ok -text "$ok" -command "FileAccept $path \"$command1\""
    Button $f.cancel -text "Cancel" -command "closewindow $path; $command2"
    pack $f.ok -side left
    pack $f.cancel -side right

    Frame $path.file
    pack $path.file -fill x -side bottom
    Label $path.file.label -text " File name: "
    pack $path.file.label -side left
    Entry $path.file.entry -width 30
    pack $path.file.entry -side left -expand true -fill x
    $path.file.entry insert end "$filename"
#    bind $path.file.entry <Return> "FileSelect $path 1"

#    Entry $path.selected
#    pack $path.selected -side bottom -fill x -pady 0 -ipady 0
#    $path.selected insert end "$filename"

    set f $path
    Frame $f.list -bd 0
    pack $f.list -expand true -fill both -pady 0 -ipady 0
    if {$extended} {
      Listbox $f.list.entries -width 12 -yscrollcommand "$f.list.scroll set" -exportselection false -relief raised -selectmode extended
    } else {
      Listbox $f.list.entries -width 12 -yscrollcommand "$f.list.scroll set" -exportselection false -relief raised
    }
    Scrollbar $f.list.scroll -width 10 -orient vertical -command [list $f.list.entries yview]
    pack $f.list.entries -expand true -side left -fill both
    pack $f.list.scroll -side left -fill y
    bind $f.list.entries <ButtonPress> "FileSelect $path 2"
    bind $f.list.entries <ButtonRelease> "FileSelect $path 2"
    bind $f.list.entries <B1-Motion> "FileSelect $path 2"
    bind $f.list.entries <Double-Button-1> "FileSelect $path 3"

    bind $path.file.entry <KeyPress> "+$f.list.entries selection clear 0 end"
    myfocus 1 $path.file.entry
  }
  FileSelect $path 0
}

#####################
#  MSGID FUNCTIONS  #
#####################

proc ExecMsgIDAction { } {
  global on_msgclick selected_msgid

  set command "[strreplace "$on_msgclick" "\$msgid" "$selected_msgid"]"
  if {[string length "[info commands "[lindex "$command" 0]"]"]} {
    eval $command
  } else {
    eval exec -- $command &
  }
}

proc MsgIDShow { } {
  global msgid_list selected_msgid

  set i "[.msgid.list.entries curselection]"
  if {"$i" != ""} {
    set selected_msgid "[expand "[lindex "$msgid_list" [expr $i*2+1]]"]"
    ExecMsgIDAction
  }
}

proc MsgIDDelete { } {
  global msgid_list

  set i "[.msgid.list.entries curselection]"
  if {"$i" != ""} {
    .msgid.list.entries delete $i
    set i [expr $i*2]
    set msgid_list "[lreplace "$msgid_list" $i [expr $i+1]]"
  }
}

proc MsgID2Clipboard { } {
  global msgid_list

  set i "[.msgid.list.entries curselection]"
  if {"$i" != ""} {
    set i [expr $i*2+1]
    clipboard clear
    clipboard append -type STRING -- "[lindex "$msgid_list" $i]"
  }
}

proc MsgIDClear { } {
  global msgid_list

  .msgid.list.entries delete 0 end
  set msgid_list ""
}

proc MsgIDSave {tofile} {
  global msgid_list crapwindow margin

  if {[string length "$tofile"] == 0} {
    FileRequest " Please select the file to save the \ndetected message IDs in!" "Save" "MsgIDSave \:file" "" "" 0
    return
  }
  set file "[OpenFile "$tofile" a+]"
  if {[string length "$file"]} {
    set ulen [llength "$msgid_list"]
    for {set i 0} {$i < $ulen} {set i [expr $i+2]} {
      puts $file "[lindex "$msgid_list" $i]  [lindex "$msgid_list" [expr $i+1]]"
    }
    close $file
    set margin(text) "note"
    print2crap "××× All message IDs saved to file '$tofile'"
  }
}

proc MsgIDWindow { } {
  global msgid_list

  if [catch {Toplevel .msgid -class tkirc-request}] {
    raise .msgid
  } else {
    global geometry_msgs
    if {[info exists geometry_msgs]} {
      wm geometry .msgid $geometry_msgs
    }
    wm title .msgid  " tkirc: detected messageIDs "
    bind .msgid <Escape> "closewindow .msgid"

    set f .msgid.buttons
    Frame $f
    pack $f -fill x -pady 2 -side bottom
    Button $f.show -text "Show" -command "MsgIDShow"
    Button $f.delete -text "Delete" -command "MsgIDDelete"
    Button $f.clip -text "MsgID to clipboard" -command "MsgID2Clipboard"
    Button $f.clear -text "Clear list" -command "MsgIDClear"
    Button $f.save -text "Save list" -command "MsgIDSave {}"
    Button $f.exit -text "Close" -command "closewindow .msgid"
    pack $f.show $f.delete $f.clip -side left
    pack $f.exit $f.save $f.clear -side right

    set f .msgid
    Frame $f.list -bd 0
    pack $f.list -expand true -fill both -pady 0 -ipady 0
    Listbox $f.list.entries -width 12 -yscrollcommand "$f.list.scroll set" -exportselection false -relief raised
    Scrollbar $f.list.scroll -width 10 -orient vertical -command [list $f.list.entries yview]
    pack $f.list.entries -expand true -side left -fill both
    pack $f.list.scroll -side left -fill y

    .msgid.list.entries delete 0 end
    set ulen [llength "$msgid_list"]

    for {set i 0} {$i < $ulen} {set i [expr $i+2]} {
      .msgid.list.entries insert end "[lindex "$msgid_list" $i]  [lindex "$msgid_list" [expr $i+1]]"
    }
    bind $f.list.entries <Double-Button-1> "MsgIDShow"
  }
}

##############################################################
#      'showarticle()' can be used in 'action_on_msgid'      #
##############################################################

proc showarticle {host msgid} {
  set path ".art[clock seconds]"
  if [catch {Toplevel $path}] {
    raise $path
  } else {
    wm title $path  " tkirc: show article "
    bind $path <Escape> "closewindow $path"

    Label $path.info -text "   nntp-host: $host   article: $msgid   " -relief sunken -bd 1
    pack $path.info -fill x -ipady 5 -ipadx 5 -padx 2 -pady 5

    set f $path.buttons
    Frame $f
    pack $f -fill x -pady 2 -side bottom
    Button $f.save -text "Save" -command "savearticle $path {} [expand $msgid]"
    pack $f.save -side left
    Button $f.close -text "Close" -command "closewindow $path"
    pack $f.close -side right

    set f $path.list
    Frame $f -bd 0
    eval {Text $f.text -yscroll [list $f.scroll set] -state normal}
    Scrollbar $f.scroll -width 10 -orient vertical -command [list $f.text yview]
    pack $f.scroll -side right -fill y
    pack $f.text -side left -expand true -fill both
    pack $f -expand true -fill both

    $path.list.text delete 0.0 end 

    if [catch {socket "$host" 119} sock] {
      $path.list.text insert end "\nERROR: Can not open socket to host $host port 119."
      return
    }
    fconfigure $sock -blocking 0
    puts $sock "mode reader\narticle $msgid\nquit"
    flush $sock
    fileevent $sock readable "getarticle $path $sock"
  }
}

proc getarticle {path sock} {
  if {[winfo exists $path]} {
    if {[gets $sock line] >= 0} {
      $path.list.text insert end "$line\n"
    } elseif [eof $sock] {
      $path.list.text configure -state disabled
      close $sock
    }
  } else {
    close $sock
  }
  update idletasks
}

proc savearticle {path tofile msgid} {
  global crapwindow

  if {[string length "$tofile"] == 0} {
    FileRequest " Please select the file to save the \n article $msgid in!" "Save" "savearticle $path \:file [expand "$msgid"]" "" "" 0
    return
  }
  set file "[OpenFile "$tofile" a]"
  if {[string length "$file"]} {
    set w $path.list.text

    puts -nonewline $file "[$w get 1.0 end]"
    close $file
    print2crap "+++ NNTP output ($msgid) saved to file '$tofile'"
  }
}

##################
#  ON FUNCTIONS  #
##################

proc ExecOnCommands {type args} {
  global on_$type on_args

  set on_args(event) "$type"
  for {set i 0} {$i < [llength "$args"]} {incr i ; incr i} {
    set on_args([lindex "$args" $i]) "[lindex "$args" [expr $i+1]]"
  }
  foreach x "[lsort -ascii "[info commands "on_event*"]"]" {
    $x
  }

  foreach x "[lsort -ascii "[info globals "on_$type*"]"]" {
    eval [set on_$type]
  }
  foreach x "[lsort -ascii "[info commands "on_$type*"]"]" {
    $x
  }
}

###################
#  URL FUNCTIONS  #
###################

proc ExecUrlAction { } {
  global on_urlclick selected_url

  set command "[strreplace "$on_urlclick" "\$url" "$selected_url"]"
  if {[string length "[info commands "[lindex "$command" 0]"]"]} {
    eval $command
  } else {
    eval exec -- $command &
  }
}

proc URLShow { } {
  global url_list selected_url

  set i "[.url.list.entries curselection]"
  if {"$i" != ""} {
    set selected_url "[expand "[lindex "$url_list" [expr $i*2+1]]"]"
    ExecUrlAction 
  }
}

proc URLDelete { } {
  global url_list

  set i "[.url.list.entries curselection]"
  if {"$i" != ""} {
    .url.list.entries delete $i
    set i [expr $i*2]
    set url_list "[lreplace "$url_list" $i [expr $i+1]]"
  }
}

proc URL2Clipboard { } {
  global url_list

  set i "[.url.list.entries curselection]"
  if {"$i" != ""} {
    set i [expr $i*2+1]
    clipboard clear
    clipboard append -- "[lindex "$url_list" $i]"
  }
}

proc URLClear { } {
  global url_list

  .url.list.entries delete 0 end
  set url_list ""
}

proc URLSave {tofile} {
  global url_list crapwindow margin

  if {[string length "$tofile"] == 0} {
    FileRequest " Please select the file to save the \ndetected URLs in!" "Save" "URLSave \:file" "" "" 0
    return
  }
  set file "[OpenFile "$tofile" a+]"
  if {[string length "$file"]} {
    set ulen [llength "$url_list"]
    for {set i 0} {$i < $ulen} {set i [expr $i+2]} {
      puts $file "[lindex "$url_list" $i]  [lindex "$url_list" [expr $i+1]]"
    }
    close $file
    set margin(text) "note"
    print2crap "××× All URLs saved to file '$tofile'"
  }
}

proc URLWindow { } {
  global url_list

  if [catch {Toplevel .url -class tkirc-request}] {
    raise .url
  } else {
    global geometry_urls
    if {[info exists geometry_urls]} {
      wm geometry .url $geometry_urls
    }
    wm title .url  " tkirc: detected URLs "
    bind .url <Escape> "closewindow .url"

    set f .url.buttons
    Frame $f
    pack $f -fill x -pady 2 -side bottom
    Button $f.show -text "Show" -command "URLShow"
    Button $f.delete -text "Delete" -command "URLDelete"
    Button $f.clip -text "URL to clipboard" -command "URL2Clipboard"
    Button $f.clear -text "Clear list" -command "URLClear"
    Button $f.save -text "Save list" -command "URLSave {}"
    Button $f.exit -text "Close" -command "closewindow .url"
    pack $f.show $f.delete $f.clip -side left
    pack $f.exit $f.save $f.clear -side right

    set f .url
    Frame $f.list -bd 0
    pack $f.list -expand true -fill both -pady 0 -ipady 0
    Listbox $f.list.entries -width 12 -yscrollcommand "$f.list.scroll set" -exportselection false -relief raised
    Scrollbar $f.list.scroll -width 10 -orient vertical -command [list $f.list.entries yview]
    pack $f.list.entries -expand true -side left -fill both
    pack $f.list.scroll -side left -fill y

    .url.list.entries delete 0 end
    set ulen [llength "$url_list"]

    for {set i 0} {$i < $ulen} {set i [expr $i+2]} {
      .url.list.entries insert end "[lindex "$url_list" $i]  [lindex "$url_list" [expr $i+1]]"
    }
    bind $f.list.entries <Double-Button-1> "URLShow"
  }
}

###################
#  Notify Window  #
###################

proc UpdateNotifyWindow {type nick address} {
  global notified

  # Beim Signoff oder evtl. auch bei fehlerhaftem Signon muß
  # der schon vorhandene Nick aus der Liste entfernt werden.
  set i [lSearch "$notified(nicks)" "$nick"]
  if {$i >= 0} {
    set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
    set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
    if {[winfo exists .not]} {
      MultiListbox_Delete .not.list 2 $i
    }
  }

  if {$type > 0} {
    MultiListbox_Insert .not.list 2 end "$nick" "$address"
    lappend notified(nicks) "$nick"
    lappend notified(addresses) "$address"
  }
}

proc NotifyNickRemove { } {
  global crapwindow notified

  set i "[.not.list.frame0.list curselection]"
  if {"$i" != ""} {
    set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
    set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
    MultiListbox_Delete .not.list 2 $i
  }
}

proc NotifyNickSelected {button} {
  global crapwindow

  set i "[.not.list.frame0.list curselection]"
  if {"$i" != ""} {
    set nick "[expandescape "[MultiListbox_Get .not.list 0 $i]"]"
    switch -- "$button" {
      "1" {
	send2tkirc $crapwindow "/whois $nick"
      }
      "2" {
	send2tkirc $crapwindow "/ctcp $nick version"
      }
      "3" {
	send2tkirc $crapwindow "/chat $nick"
      }
    }
  }
}

proc NotifyWindow { } {
  global notified

  if [catch {Toplevel .not -class tkirc-request}] {
    raise .not
  } else {
    wm title .not  " tkirc: notify "
    bind .not <Escape> "closewindow .not"

    global geometry_notify
    if {[info exists geometry_notify]} {
      wm geometry .not $geometry_notify
    }

    Frame .not.buttons ; pack .not.buttons -side bottom \
      -padx 5 -pady 2 -expand true -fill x
    Button .not.buttons.remove -text "Remove" -command "NotifyNickRemove"
    Button .not.buttons.close -text "Close" -command "closewindow .not"
    pack .not.buttons.close -side right
    pack .not.buttons.remove -side left

    MultiListbox .not.list 2 {Nickname 15} {Address 60}
    MultiListbox_Delete .not.list 2 0 end
    set ulen [llength "$notified(nicks)"]

    for {set i 0} {$i < $ulen} {incr i} {
      MultiListbox_Insert .not.list 2 end "[lindex "$notified(nicks)" $i]" \
	"[lindex "$notified(addresses)" $i]"
    }
    pack .not.list -side left -expand true -fill both -ipadx 0 -padx 0

    MultiListbox_Bind .not.list 2 <Double-Button-1> "NotifyNickSelected 1"
    MultiListbox_Bind .not.list 2 <Double-Button-2> "NotifyNickSelected 2"
    MultiListbox_Bind .not.list 2 <Double-Button-3> "NotifyNickSelected 3"
  }
}

proc ServersWindow { } {
  global preferred_servers

  if [catch {Toplevel .servers -class tkirc-request}] {
    raise .servers
  } else {
    global geometry_servers
    if {[info exists geometry_servers]} {
      wm geometry .servers $geometry_servers
    }
    wm title .servers  " tkirc: Servers "
    bind .servers <Escape> "ServerCanceled"
    wm protocol .servers WM_DELETE_WINDOW "ServerCanceled"

    set frame .servers.buttons
    Frame $frame ; pack $frame -side bottom -fill x -pady 2

    Button $frame.close -text "Cancel" -command "ServerCanceled"
    pack $frame.close -side right -padx 2 -pady 2
    DefaultButton $frame.connect -text "Connect" -command "ServerConfirmed"
    pack $frame.connect -side right -padx 2 -pady 2
    bind .servers <Return> "ServerConfirmed"

    Entry .servers.entry
    pack .servers.entry -fill x -padx 2 -pady 2 -side bottom
    myfocus 26 .servers.entry

    MultiListbox .servers.list 3 {Comment 12} {Hostname 40} {Port 7}
    MultiListbox_Delete .servers.list 3 0 end
    set slen [llength "$preferred_servers"]

    for {set i 0} {$i < $slen} {incr i} {
      set x "[lindex "$preferred_servers" $i]"
      set comment "[lindex "$x" 2]"
      if {"$comment" == ""} {
	set comment "<none>"
      }
      MultiListbox_Insert .servers.list 3 end \
	"[lindex "$x" 2]" "[lindex "$x" 0]" "[lindex "$x" 1]"
    }
    pack .servers.list -side left -expand true -fill both -ipadx 0 -padx 0

    MultiListbox_Bind .servers.list 3 <ButtonPress-1> "+ServerSelected"
    MultiListbox_Bind .servers.list 3 <ButtonRelease-1> "+ServerSelected"
    MultiListbox_Bind .servers.list 3 <B1-Motion> "+ServerSelected"
    MultiListbox_Bind .servers.list 3 <Double-ButtonPress-1> {+global server ; if {"$server" == ""} {ServerConfirmed}}
  }
}

proc ServerSelected { } {
  .servers.entry delete 0 end
  set selected "[MultiListbox_Selected .servers.list]"
  if {"$selected" != ""} {
    .servers.entry insert end "[MultiListbox_Get .servers.list 1 $selected] [MultiListbox_Get .servers.list 2 $selected]"
  }
}

proc ServerCanceled { } {
  global ask4server

  closewindow .servers
  if {$ask4server} {
    exit
  }
}

proc ServerConfirmed { } {
  global ask4server ircserver arguments

  set toconnect "[.servers.entry get]"
  if {"$toconnect" != ""} {
    if {$ask4server} {
      if {[llength "$toconnect"] == 2} {
        set toconnect "[lindex "$toconnect" 0]:[lindex "$toconnect" 1]"
      }
      set server "$toconnect"
      append arguments " $toconnect"
      ServerIsAvailable
    } else {
      send2irc "/server $toconnect"
    }
    closewindow .servers
  } else {
    beep
  }
}

##################
#  MultiListbox  #
##################

proc MultiListbox {path lists args} {
  Frame $path -bd 2 -relief sunken

  Frame $path.frameb ; pack $path.frameb -side right -fill y
  Scrollbar $path.frameb.bar -width 10 -orient vertical \
    -command "MultiListbox_Yview $path $lists"
  if {"$args" != ""} {
    Label $path.frameb.label -text " "
    pack $path.frameb.label
  }
  pack $path.frameb.bar -expand true -fill y
  
  for {set i 0} {$i < $lists} {incr i} {
    Frame $path.frame$i ; pack $path.frame$i -side left -expand true -fill both
    if {"$args" != ""} {
      if {[llength "$args"] > $i} {
        Label $path.frame$i.label -text "[lindex "[lindex "$args" $i]" 0]"
      } else {
        Label $path.frame$i.label -text " "
      }
      pack $path.frame$i.label
    }

    Listbox $path.frame$i.list -exportselection false -relief raised \
      -yscrollcommand "MultiListbox_Set $path $i $lists {}"

    if {"[lindex "[lindex "$args" $i]" 1]" != ""} {
      $path.frame$i.list configure -width "[lindex "[lindex "$args" $i]" 1]"
    }

    pack $path.frame$i.list -padx 0 -ipadx 0 -side left -expand true -fill both

    foreach x "1 2 3" {
      bind $path.frame$i.list <B$x-Motion> \
	"MultiListbox_Set $path $i $lists %y"
      bind $path.frame$i.list <ButtonRelease-$x> \
	"MultiListbox_Set $path $i $lists %y"
      bind $path.frame$i.list <ButtonPress-$x> \
	"MultiListbox_Set $path $i $lists %y"
    }
  }
}

proc MultiListbox_Set {path num lists y args} {
  if {"$y" != ""} {
    set selected "[$path.frame$num.list nearest $y]"
  } else {
    set selected "[$path.frame$num.list curselection]"
  }
  set yview "[lindex "[$path.frame$num.list yview]" 0]"

  for {set i 0} {$i < $lists} {incr i} {
    eval $path.frame$i.list yview moveto $yview
    eval $path.frame$i.list selection clear 0 end
    foreach x "$selected" {
      eval $path.frame$i.list selection set $x
    }
  }
  if {"$args" != ""} {
    eval $path.frameb.bar set $args
  }
}

proc MultiListbox_Yview {path lists args} {
  for {set i 0} {$i < $lists} {incr i} {
    eval $path.frame$i.list yview $args
  }
}

proc MultiListbox_Delete {path lists args} {
  if {[winfo exists $path]} {
    for {set i 0} {$i < $lists} {incr i} {
      eval $path.frame$i.list delete $args
    }
  }
}

proc MultiListbox_Selected {path} {
  if {[winfo exists $path]} {
    return "[$path.frame0.list curselection]"
  }
  return ""
}

proc MultiListbox_Insert {path lists place args} {
  if {[winfo exists $path]} {
    set end "[lindex "[$path.frame0.list yview]" 1]"
    for {set i 0} {$i < $lists} {incr i} {
      if {[llength "$args"] > $i} {
        $path.frame$i.list insert $place "[lindex "$args" $i]"
      } else {
        $path.frame$i.list insert $place " "
      }
      if {$end == 1} {
	$path.frame$i.list yview end
      }
    }
  }
}

proc MultiListbox_Get {path list place} {
  if {[winfo exists $path]} {
    return "[$path.frame$list.list get $place]"
  }
  return ""
}

proc MultiListbox_Bind {path lists sequence command} {
  for {set i 0} {$i < $lists} {incr i} {
    bind $path.frame$i.list $sequence "$command"
  }
}

####################
#  MENU FUNCTIONS  #
####################

proc About {num} {
  global version date on_urlclick

  if [catch {Toplevel .about -class tkirc-request}] {
    raise .about
  } else {
#    grab set .about
    wm title .about  " tkirc: About "
    bind .about <Escape> "grab release .about ; destroy .about"

    Frame .about.f1
    pack .about.f1 -ipadx 2 -ipady 2 -padx 2 -pady 2 -side bottom
    Button .about.f1.ok -text "ok" -command "grab release .about ; destroy .about"
    pack .about.f1.ok

    Frame .about.f2 -bd 1 -relief sunken
    pack .about.f2 -padx 2 -pady 2 -ipady 1 -expand true -side top -fill x

    set newbody " tkirc $version ($date) \n - Freely distributable! - \n\n © 1996-98  Andreas 'atte' Gelhausen, \n <atte@gecko.North.DE> \n"
    Label .about.f2.label1 -bd 0 -text "$newbody"
    pack .about.f2.label1 -side top -expand true -ipady 2

    Menubutton .about.f2.url -text " http://home.pages.de/~tkirc/ " -bd 0
    pack .about.f2.url -side top -ipady 0 -pady 0
    set command "[strreplace "$on_urlclick" "\$url" "http://home.pages.de/~tkirc/"]"
    if {[string length "[info commands "[lindex "$command" 0]"]"]} {
      bind .about.f2.url <Button-1> "eval $command"
    } else {
      bind .about.f2.url <Button-1> "eval exec -- $command &"
    }
  }
}

proc ClearTraffic {num} {
  global win

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  set widget "[GetPathFromNum $num].body.left.traffic.text"
  set win($num,visible) 0
  $widget configure -state normal
  $widget delete 0.0 end
  $widget configure -state disabled

  for {set i $win($num,taglo)} {$i <= $win($num,taghi)} {incr i} {
    $widget tag delete tag_$num\_$i
  }

  set win($num,taghi) 0
  set win($num,taglo) 1
}

proc UpdateTopic {num} {
  global chan nickname

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  set path "[GetPathFromNum $num]"
  if {[string compare "$path.body.left.topic.entry" "[focus]"] == 0} {
    return
  }
  if {"[GetActual $num]" == "*"} {
    if {[string length "[$path.body.left.topic.entry get]"]} {
      $path.body.left.topic.entry configure -state normal
      $path.body.left.topic.entry delete 0 end
      $path.body.left.topic.entry configure -state disabled
    }
  } else {
    set cnum [GetChannelNumber "[GetActual $num]"]
    set topic "[cutEscapeCodes "$chan($cnum,topic)"]"
    if {[string compare "$topic" "[$path.body.left.topic.entry get]"]} {
      $path.body.left.topic.entry configure -state normal
      $path.body.left.topic.entry delete 0 end
      $path.body.left.topic.entry insert 0 "$topic"
    }
    if {[isOpOnChannel $cnum "$nickname"] || $chan($cnum,mode_t) == 0} {
      $path.body.left.topic.entry configure -state normal
    } else {
      $path.body.left.topic.entry configure -state disabled
    }
  }
}

proc UpdateTitle {num} {
  global chan nickname win server away
  global messagewindow crapwindow

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  set actual -1
  set ochannels ""
  foreach x "$win($num,channels)" {
    if {[strcmp "$chan($x)" "$win($num,actual)"] == 0} {
      set actual "$x"
    } else {
      if {"$ochannels" == ""} {
	append ochannels "$chan($x)"
      } else {
	append ochannels ",$chan($x)"
      }
    }
  }

  set path "[GetPathFromNum $num]"
  set query "$win($num,query)"

  set title "\[ $num"
  set icontitle "tkirc\[$num"
  if {$messagewindow == $num} {
    append title "m"
    append icontitle "m"
  }
  if {$crapwindow == $num} {
    append title "c"
    append icontitle "c"
  }
  set at ""
  if {$actual != -1} {
    if {[isOpOnChannel $actual "$nickname"]} {
      set at "@"
    }
  }
  append title " \] : "
  append icontitle "\]"
  if {[string length "$server"]} {
    append title "$server"
  } else {
    append title "<no server>"
  }
  append title " : $at$nickname"
  if {"$query" != ""} {
    append title " \[Query: $query\]"
  }
  if {$actual != -1} {
    append title "$away on $chan($actual)"
    if {"$ochannels" != ""} {
      append title " ($ochannels)"
    }
  } else {
    append title "$away on *"
  }
  wm title $path " $title "
  if {$win($num,touched)} {
    set icontitle " + $icontitle + "
  } else {
    set icontitle " - $icontitle - "
  }
  wm iconname $path "$icontitle"
}

proc UpdateAllTitles { } {
  global win
  foreach x "$win(list)" {
    UpdateTitle $x
  }
}

proc PrintModeChars {num text} {
  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  set nochannel  "no channel"
  set flags "o v b k l i m n p s t"
  set path "[GetPathFromNum $num]"

  if {[string length "$text"]} {
    for {set i 0} {$i < 10} {incr i} {
      $path.menu.[lindex "$flags" $i] configure -fg #111111 -text ""
    }
    $path.menu.[lindex "$flags" 10] configure -fg #111111 -text "$text"
  } else {
    for {set i 0} {$i < [llength "$flags"]} {incr i} {
      $path.menu.[lindex "$flags" $i] configure -fg #111111 -text "[string index "$nochannel" $i]"
    }
  }
}

global huibu
if {![info exists huibu]} {
  set huibu 0
}
proc huibu {type} {
  global win huibu nickname

  if {$type == 0} {
    set huibu 0
    return
  } elseif {$type == 1} {
    if {$huibu == 0} {
      set huibu 1
    } else {
      return
    }
  }
  set huibulist {"O h   n o  !"  "d u m d i d u m"  "j i b b i e"
   "C a l l   9 1 1  !" "Call the police!"  "Are you sleepy?"
   "Nobody at home?" "Where's the exit?" "Hello? Operator?" "w o p ?"}

  if {$huibu} {
    set len [llength "$win(list)"]
    if {$len > 0} {
      set num [lindex "$win(list)" [expr [clock seconds]%$len]]
      PrintModeChars $num "[lindex "$huibulist" [expr [clock seconds]%[llength "$huibulist"]]] "
      after 2500 "UpdateInfos $num"
    }
    after [expr 60000*42+1000] "huibu 2"
  }
}

proc UpdateInfos {num} {
  global version date nickname server away chan win
  global CHANNEL_NAME_WIDTH

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  # Gibt es das Fenster überhaupt noch?
  if {[lsearch -exact "$win(list)" "$num"] != -1} {
    set path "[GetPathFromNum $num]"

    set channel [GetActual $num]
    set cnum [GetChannelNumber "$channel"]

    # Bei einem Disconnect wird der Kanalname auf "*" gesetzt.
    if {"$server" == "" && "$channel" != "*" || $cnum == -1} {
      set win($num,actual) "*"
      set channel "*"
    }

    set len [expr $CHANNEL_NAME_WIDTH-2]
    if {[strcmp "$channel" "[$path.body.right.top.channel cget -text]"]} {
      # Der dargestellte Kanalname muß aktualisiert werden.
      if {[string length "$channel"] > $CHANNEL_NAME_WIDTH} {
        $path.body.right.top.channel configure -text "[string range "$channel" 0 [expr $len-1]]»"
      } else {
        $path.body.right.top.channel configure -text "$channel"
      }

      $path.body.right.list.users delete 0 end
      $path.body.right.top.count configure -text "-"

      if {"$channel" != "*"} {
	FillUserList $num $cnum
      }
    }

    if {"$channel" != "*"} {
      foreach x "t s p n m i" {
	if {$chan($cnum,mode_$x)} {
	  $path.menu.$x configure -fg #111111 -text "$x"
	} else {
	  $path.menu.$x configure -fg #888888 -text "$x"
	}
      }

      $path.menu.o configure -text "o=$chan($cnum,mode_o)" -fg #111111
      $path.menu.v configure -text "v=$chan($cnum,mode_v)" -fg #111111
      $path.menu.b configure -text "b=$chan($cnum,mode_b)" -fg #111111

      foreach x "l k" {
	if {"$chan($cnum,mode_$x)" != ""} {
	  $path.menu.$x configure -fg #111111 -text "$x"
	} else {
	  $path.menu.$x configure -fg #888888 -text "$x"
	}
      }
    } else {
      PrintModeChars $num ""
    }
    UpdateTopic $num
    UpdateTitle $num

    # update list of joined channels
    set f $path.body.right.top.channel
    $f configure -menu "" -underline -1
    $f.menu delete 0 end

    set count 0
    set win($num,channels) ""
    foreach x "$chan(list)" {
      if {$num == $chan($x,window)} {
        $f.menu add command -label "$chan($x)"\
	 -command "send2irc \"[expand "/join $chan($x)"]\""
	lappend win($num,channels) "$x"
	incr count
      }
    }
    if {$count > 1} {
      $f configure -menu $f.menu -underline 0
    }
  }
}

proc UpdateAllInfos { } {
  global win
  foreach x "$win(list)" {
    UpdateInfos $x
  }
}

proc NickNotAvailable {nick} {
  global nickname lastnickname preferred_nicknames crapwindow margin

  if {"$lastnickname" == ""} {
    if {[lLength "$preferred_nicknames"]} {
      set nicks [strreplace "$preferred_nicknames" "\\" ""]
      set i [lSearch "$nicks" "$nick"]
      incr i
      if {[lLength "$nicks"] > $i} {
	set nickname "[lIndex "$nicks" $i]"
	send2irc "/nick $nickname"
	UpdateAllTitles
      }
    } else {
      set margin(text) "note"
      print2crap "××× tkirc automatically tries to give you a valid nickname. Please set the variable 'preferred_nicknames' in your tkircrc for the next time!"
      global filternext ; set filternext 1

      set comics {"micky" "goofy" "pluto" "donald" "daisy"}
      set secs [clock seconds] ; set len [llength "$comics"]
      send2irc "/nick [lindex "$comics" [expr $secs%$len]][expr $secs%100]"
      UpdateAllTitles
    }
  } else {
    set nickname "$lastnickname"
    UpdateAllTitles
  }
}

proc SelectedNicks {num} {
  global win
  set result ""

  if {"$win($num,popupnick)" == ""} {
    set path "[GetPathFromNum $num]"
    set nums "[$path.body.right.list.users curselection]"
    foreach x "$nums" {
      append result " [TrimNick "[$path.body.right.list.users get $x]"]"
    }
    if {[string length "$nums"] == 0 && "$win($num,query)" != ""} {
      append result " [string trimleft "$win($num,query)" "="]"
    }
    return "[string trimleft "[expand "$result"]" " "]"
  } else {
    return "[expand "$win($num,popupnick)"]"
  }
}

proc UnselectNicks {num} {
  set path "[GetPathFromNum $num]"
  $path.body.right.list.users selection clear 0 end
}

proc UserOp {num} {
  set snicks "[SelectedNicks $num]"
  set len [llength "$snicks"]
  if {$len} {
    set onicks ""
    set flags "+"
    UnselectNicks $num
    for {set i 0} {$i < $len} {incr i} {
      append onicks "[lindex "$snicks" $i] "
      append flags "o"
      if {[lLength "$onicks"] == 3} {
	send2irc "/mode [GetActual $num] $flags $onicks"
	set onicks ""
        set flags "+"
      }
    }
    if {[lLength "$onicks"]} {
      send2irc "/mode [GetActual $num] $flags $onicks"
    }
  }
}

proc UserDeop {num} {
  set snicks "[SelectedNicks $num]"
  set len [lLength "$snicks"]
  if {$len} {
    set onicks ""
    set flags "-"
    UnselectNicks $num
    for {set i 0} {$i < $len} {incr i} {
      append onicks "[lindex "$snicks" $i] "
      append flags "o"
      if {[lLength "$onicks"] == 3} {
        send2irc "/mode [GetActual $num] $flags $onicks"
	set onicks ""
        set flags "-"
      }
    }
    if {[lLength "$onicks"]} {
      send2irc "/mode [GetActual $num] $flags $onicks"
    }
  }
}

proc UserGiveVoice {num} {
  set snicks "[SelectedNicks $num]"
  set len [lLength "$snicks"]
  if {$len} {
    set vnicks ""
    set flags "+"
    UnselectNicks $num
    for {set i 0} {$i < $len} {incr i} {
      append vnicks "[lindex "$snicks" $i] "
      append flags "v"
      if {[lLength "$vnicks"] == 3} {
        send2irc "/mode [GetActual $num] $flags $vnicks"
	set vnicks ""
        set flags "+"
      }
    }
    if {[lLength "$vnicks"]} {
      send2irc "/mode [GetActual $num] $flags $vnicks"
    }
  }
}

proc UserRemoveVoice {num} {
  set snicks "[SelectedNicks $num]"
  set len [lLength "$snicks"]
  if {$len} {
    set vnicks ""
    set flags "-"
    UnselectNicks $num
    for {set i 0} {$i < $len} {incr i} {
      append vnicks "[lindex "$snicks" $i] "
      append flags "v"
      if {[lLength "$vnicks"] == 3} {
        send2irc "/mode [GetActual $num] $flags $vnicks"
	set vnicks ""
        set flags "-"
      }
    }
    if {[lLength "$vnicks"]} {
      send2irc "/mode [GetActual $num] $flags $vnicks"
    }
  }
}

proc UserWho {num} {
  set i 0
  foreach x "[SelectedNicks $num]" {
    set wait [expr 3000 * $i]
    after $wait [list send2irc "/who -nick $x"]
    incr i
  }
  UnselectNicks $num
}

proc UserWhois {num} {
  set i 0
  foreach x "[SelectedNicks $num]" {
    set wait [expr 3000 * $i]
    after $wait [list send2irc "/whois $x"]
    incr i
  }
}

proc UserPopup {num W x y} {
  if {"[SelectedNicks $num]" != ""} {
    tk_popup .win$num.menu.user.menu $x $y
  }
}

proc UserPopup2 {num W x y nick} {
  global win
  set win($num,popupnick) "$nick"
  tk_popup .win$num.menu.popup.menu $x $y
  bind .win$num.menu.popup.menu <Unmap> "after 1000 \"global win ; set win($num,popupnick) {}\""
}

proc KeepUser {nick address} {
  global wcnicklist wcaddresslist

  set i [lSearch "$wcnicklist" "$nick"]
  if {$i != -1} {
    set wcnicklist "[lreplace "$wcnicklist" $i $i "$nick"]"
    set wcaddresslist "[lreplace "$wcaddresslist" $i $i "$address"]"
  } else {
    set wcnicklist "[linsert "$wcnicklist" 0 "$nick"]"
    set wcaddresslist "[linsert "$wcaddresslist" 0 "$address"]"
  }
  if {[llength "$wcnicklist"] > 50} {
    set wcnicklist "[lrange "$wcnicklist" 0 49]"
    set wcaddresslist "[lrange "$wcaddresslist" 0 49]"
  }
}

proc AddAddressToNick {nick address} {
  global chan

  set count 0
  set address "[StripAddressPrefix "$address"]"
  foreach x "$chan(list)" {
    set i [UserNumOfChannel $x "$nick"]
    if {$i != -1} {
      set chan($x,addresses) "[lreplace "$chan($x,addresses)" $i $i "$address"]"
      incr count
    }
  }
  if {$count == 0} {
    KeepUser $nick $address
  }
}

proc TakeOverTest {cnum address} {
  global chan margin react_to_takeover nickname takeover
  global takeover_users takeover_period alreadykicked

  if {!$react_to_takeover} {
    return
  }

  set secs [clock seconds]
  set at [string first "@" "$address"]
  if {$at == -1} {
    return
  }

  set channel "$chan($cnum)"
  set host "[string range "$address" [expr $at+1] end]"
  set count 0 ; set i 0
  foreach x "$chan($cnum,addresses)" {
    set at [string first "@" "$x"]
    if {$at == -1} {
      continue
    }
    set y "[string range "$x" [expr $at+1] end]"
      if {[strcmp "$y" "$host"] == 0} {
        set period [expr $secs-[lindex "$chan($cnum,jointimes)" $i]]
	if {$period < $takeover_period} {
          incr count
        }
      }
      incr i
    }
    if {$count >= $takeover_users} {
      # wurde der TakeOver schon gemeldet?
      for {set i 0} {$i < [llength "$takeover(tries)"]} {incr i} {
	if {[strcmp "[lindex "$takeover(tries)" $i]" "$cnum $host"] == 0} {
	  if {[expr [clock seconds]-[lindex "$takeover(times)" $i]] < 300} {
	    # Nach 5 Minuten ist es bestimmt ein neuer Takeover!
	    return
	  }
	}
      }
      for {set i 0} {$i < 3} {incr i} {
	after [expr $i * 750] beep
      }
      lappend takeover(tries) "$cnum $host"
      lappend takeover(times) "[clock seconds]"
      if {[isOpOnChannel $cnum "$nickname"]} {
	set margin(text) "alert"
	if {$react_to_takeover == 666} {
	  global takeover_star_patterns
	  print2channels "$cnum" "××× Possible takeover detected on channel '$channel' from host '$host'. Sending ban and kicks..."
	  foreach x "$takeover_star_patterns" {
	    if [strmatch "$x" "$host"] {
	      set host "*[string range "$host" [string first "." "$host"] end]"
	      break
	    }
	  }
	  send2irc "/mode $channel +b *!*@$host"
	  set alreadykicked ""
	  KickWarScriptUser "$channel" "*@$host"
	} else {
	  print2channels "$cnum" "××× A user of '$host' possibly tries to take over channel '$channel'!"
	  BanWindow $channel {} $address 3
	}
      } else {
	set margin(text) "alert"
	print2channels "$cnum" "××× A user of '$host' possibly tries to take over channel '$channel', but you are not a channel operator!"
	BanWindow $channel {} $address 3
      }
    }
}

proc AddressOfNick {nick} {
  global chan win wcnicklist wcaddresslist

  foreach x "$chan(list)" {
    set i [UserNumOfChannel $x "$nick"]
    if {$i != -1} {
      set address "[lindex "$chan($x,addresses)" $i]"
      if {[string length "$address"]} {
	return "$address"
      }
    }
  }
  set i [lSearch "$wcnicklist" "$nick"]
  if {$i >= 0} {
    return "[lindex "$wcaddresslist" $i]"
  }
  return ""
}

proc UserQuery {num} {
  global win margin

  set nick "[SelectedNicks $num]"
  if {[llength "$nick"] > 1} {
    set margin(text) "note"
    print2text $num "××× Please select only one user for query!"
  } else {
    set nick "[reduce "$nick"]"
    set win($num,query) "$nick"
    UpdateTitle $num
  }
}

proc UserIgnore {num} {
  foreach x "[SelectedNicks $num]" {
    send2irc "/ignore $x MSGs NOTICEs INVITEs CTCPs"
  }
}

proc UserBan {num} {
  global margin

  set nick "[SelectedNicks $num]"
  if {[llength "$nick"] > 1} {
    set margin(text) "note"
    print2text $num "××× Please select only one user for ban!"
  } elseif {[llength "$nick"] == 1} {
    set address "[AddressOfNick [reduce $nick]]"
    if {[string length "$address"] == 0} {
      AddToWhoisQueue "[reduce $nick]" "$num" "BanWindow [expand "[GetActual $num]"] $nick {} 1" "Getting informations to ban..."
      return
    }
    BanWindow [GetActual $num] [reduce $nick] $address 1
  }
}

proc NickBan {nick channel num} {
  if {[string length "$channel"] == 0} {
    set channel "[GetActual $num]"
  }
  if {[string length "$nick"] > 0} {
    set address "[AddressOfNick $nick]"
    if {[string length "$address"] == 0} {
      set command "BanWindow [expand "$channel"] [expand "$nick"] {} 0"
      AddToWhoisQueue "$nick" "$num" "$command" "Getting informations to ban..."
      return
    }
    BanWindow "$channel" "$nick" $address 0
  }
}

proc UserKick {num} {
  global win margin
  incr win(reqcount)

  set selected "[SelectedNicks $num]"
  set len [lLength "$selected"]
  if {$len == 1} {
    set nick "$selected"
    set address "[AddressOfNick [reduce $nick]]"
  }
  if {$len > 3} {
    set margin(text) "note"
    print2text $num "××× Please don't select more than 3 users for kick!"
  } elseif {$len == 1 && "$address" != ""} {
    BanWindow [GetActual $num] [reduce $nick] $address 2
  } else {
    set path ".ban$win(reqcount)"
    if [catch {Toplevel $path -class tkirc-request}] {
      raise $path
    } else {
      global geometry_kick
      if {[info exists geometry_kick]} {
	wm geometry $path $geometry_kick
      }
      wm title $path  " tkirc: Kick user(s) from the channel"
      bind $path <Escape> "closewindow $path"

      Label $path.mid -text "Please select one of your preferred kickmessages or a new one:"
      pack $path.mid -side top -padx 2 -pady 2

      Frame $path.reasons
      InitKickReasonList $path
      pack $path.reasons -expand true -fill both

      set f $path.buttons
      Frame $f
      pack $f -fill x -pady 2
      DefaultButton $f.ban -text "Kick" -command "Kick $num \"\[$path.reasons.entry get\]\"; closewindow $path"
      bind $path <Return> "Kick $num \"\[$path.reasons.entry get\]\"; closewindow $path"
      Button $f.cancel -text "Cancel" -command "closewindow $path"
      pack $f.cancel $f.ban -side right
    }
  }
}

proc Ctcp {num command args} {
  foreach x "[SelectedNicks $num]" {
    send2irc "/ctcp $x $command $args"
  }
}

proc UserChat {num} {
  foreach x "[SelectedNicks $num]" {
    send2tkirc $num "/chat [expandescape $x]"
  }
}

proc UserDChat {num} {
  foreach x "[SelectedNicks $num]" {
    send2tkirc $num "/dchat [expandescape $x]"
  }
}

proc UserDList {num} {
  send2irc "/dcc list"
}

proc UserDSend {num args} {
  set snicks "[SelectedNicks $num]"
  if {[string length "$snicks"]} {
    if {[string length "$args"] == 0} {
      FileRequest " Please select the file(s) to send via DCC!" "Send" "UserDSend $num \:file" "" "" 1
    } else {
      set len [lLength "$snicks"]
      for {set i 0} {$i < $len} {incr i} {
	send2irc "/dcc send [reduce [lIndex "$snicks" $i]] $args"
      }
    }
  }
}

proc UserDSendPopup {num} {
  foreach x "[SelectedNicks $num]" {
    send2tkirc $num "/dcc send [expandescape $x] :file"
  }
}

proc LeaveChannel {num message} {
  set channel [GetActual $num]
  if {"$channel" != "*"} {
    send2irc "/quote part $channel :$message"
  }
}

proc RejoinChannel {num} {
  global nickname commandqueue

  set channel [GetActual $num]
  if {"$channel" != "*"} {
    lappend commandqueue "[expand "\*\*\*?$nickname (*) has left channel $channel*"]"
    lappend commandqueue "global chan win ; lappend chan(tojoin) \"[expand "$channel"]\" ; lappend win(tojoin) \"$num\" ; send2irc \"/join [expand "$channel"]\""
    send2irc "/leave $channel"
  }
}

proc InviteToChannel {num} {
  set channel [GetActual $num]
  if {"$channel" != "*"} {
    StringRequest "Which user (nickname) do you want to invite to channel $channel?" "" "Cancel|" "Invite|send2irc \"/invite \$string [expand $channel]\""
  }
}

proc DetectSign {nick type call} {
  global crapwindow notify

  set address ""
  if {$type == 0} {
    if {$call == 0} {
      # Die Adresse wird auch beim Signoff besorgt.
      set command "DetectSign [expand $nick] $type 1"
      AddToWhowasQueue "$nick" "0" "$command" ""
      return
    } else {
      # Die vorhandene Addresse ist aktuell.
      set address "[AddressOfNick $nick]"
    }
  }

  if {$type > 0} {
    if {$call == 0} {
      # Die Adresse muß besorgt werden.
      set command "DetectSign [expand $nick] $type 1"
      AddToWhoisQueue "$nick" "0" "$command" ""
      return
    } else {
      # Die vorhandene Addresse _sollte_ aktuell sein.
      set address "[AddressOfNick $nick]"
    }
  }

  set print_nick 1
  if {[string length "$address"] == 0} {
    set address "?"
  } else {
    for {set i 0} {$i < [llength "$notify(users)"]} {incr i} {
      set entry "[lindex "$notify(users)" $i]"
      if {[strcmp "$nick" "[lindex "$entry" 0]"] == 0} {
	if {[strmatch "[lindex "$entry" 1]" "$address"]} {
	  set print_nick 1
	  break
	} else {
	  set print_nick 0
	}
      }
    }
  }
  if {$print_nick == 1} {
    global margin
    switch -exact -- "$type" {
      "1" {
	ExecOnCommands notify_signon nick "$nick" address "$address"
	set margin(text) "notify"
	print2crap "*** $nick ($address) detected at [time]"
      }
      "0" {
	ExecOnCommands notify_signoff nick "$nick" address "$address"
	set margin(text) "notify"
	print2crap "*** $nick ($address) signs off at [time]"
      }
    }
    UpdateNotifyWindow $type "$nick" "$address"
  }
}

##########
#  KICK  #
##########

proc SelectedKickReason {path add} {
  set result ""

  $path.reasons.entry delete 0 end
  set num "[$path.reasons.list.view curselection]"
  if {"$num" != ""} {
    set num "[expr $num + $add]"
    append result "[$path.reasons.list.view get $num]"
    $path.reasons.list.view selection clear 0 end
    $path.reasons.list.view selection set $num
    set size [$path.reasons.list.view size]
    set top "[lindex "[$path.reasons.list.view yview]" 0]"
    set bottom "[lindex "[$path.reasons.list.view yview]" 1]"
    if {[expr $num.000/$size] > $bottom} {
      $path.reasons.list.view yview scroll +1 units
    } elseif {[expr $num.000/$size] < $top} {
      $path.reasons.list.view yview scroll -1 units
    }
  }
  $path.reasons.entry insert 0 "$result"
}

proc InitKickReasonList {path} {
  # f is the frame
  global preferred_kickreasons

  set f $path.reasons
  Entry $f.entry
  pack $f.entry -fill x -side bottom
  myfocus 3 $f.entry

  listbox_vs $f.list
  pack $f.list -expand true -fill both -pady 0 -ipady 0

  for {set i 0} {$i < [llength "$preferred_kickreasons"]} {incr i} {
    $f.list.view insert end "[lindex "$preferred_kickreasons" $i]"
  }

  bind $f.list.view <ButtonRelease> "SelectedKickReason $path +0"
  bind $f.list.view <B1-Motion> "SelectedKickReason $path +0"
  $f.list.view selection set 0
  SelectedKickReason $path +0
  bind $path <Up> "SelectedKickReason $path -1"
  bind $path <Down> "SelectedKickReason $path +1"
}

################
#  BAN / KICK  #
################

proc SetBanNickButton {wx} {
  global bannick

  set a .ban$wx.body.address
  if {"[$a.nick cget -text]" == "*"} {
    $a.nick configure -text "$bannick($wx)"
  } else {
    $a.nick configure -text "*"
  }
}

proc SetBanUserButton {wx} {
  global banuser

  set a .ban$wx.body.address
  if {"[$a.user cget -text]" == "*"} {
    $a.user configure -text "$banuser($wx)"
    if {"[$a.tilde cget -text]" == "!"} {
      $a.tilde configure -text "!*"
    }
  } else {
    $a.user configure -text "*"
    if {"[$a.tilde cget -text]" == "!*"} {
      $a.tilde configure -text "!"
    }
  }
}

proc SetBanAddressButton {wx num} {
  global banaddress

  set a .ban$wx.body.address
  if {"[$a.$num cget -text]" != "[lindex "$banaddress($wx)" $num]"} {
    $a.$num configure -text "[lindex "$banaddress($wx)" $num]"

    for {set i $num} {$i < [expr [llength "$banaddress($wx)"] - 1]} {incr i} {
      set post [expr $i + 1]
      if {"[$a.$post cget -text]" != "[lindex "$banaddress($wx)" $post]"} {
        $a.$post configure -text "[lindex "$banaddress($wx)" $post]" -state normal
	$a.point$i configure -text "."
      } else {
	break
      }
    }
  } else {
    $a.$num configure -text "*"

    for {set i 1} {$i < [llength "$banaddress($wx)"]} {incr i} {
      set pre [expr $i - 1]
      if {"[$a.$i cget -text]" != "[lindex "$banaddress($wx)" $i]"} {
        if {"[$a.$pre cget -text]" != "[lindex "$banaddress($wx)" $pre]"} {
	  $a.point$pre configure -text ""
	  $a.$i configure -text "" -state disabled
	}
      }
    }
  }
}

proc Kick {num reason} {
  foreach x "[SelectedNicks $num]" {
    send2tkirc $num "/kick [expandescape "[GetActual $num] $x"] $reason"
  }
}

proc KickWarScriptUser {channel address} {
  global chan nickname alreadykicked takeover_kick_reasons takeover_period
  global margin

  set cnum [GetChannelNumber "$channel"]

  set num $chan($cnum,window)
  set secs [clock seconds]
  set count 0 ; set i 0 ; set matchnicks ""
  foreach x "$chan($cnum,addresses)" {
    if {[strmatch "*$address" "$x"]} {
      set nick [lindex "$chan($cnum,nicks)" $i]
      set period [expr $secs - [lindex "$chan($cnum,jointimes)" $i]]
      if {$period < $takeover_period} {
	# User hat innerhalb der letzten 'takeover_period' Sekunden
        # gejoint.
        if {[strcmp "$nick" "$nickname"]} {
          if {[lSearch "$alreadykicked" "$nick"] == -1} {
            set alen [llength "$alreadykicked"]
            set rlen [llength "$takeover_kick_reasons"]
            if {$rlen} {
	      set j [expr $alen % $rlen]
	      set kmsg "[lindex "$takeover_kick_reasons" $j]"
	      send2tkirc $num "/kick [expandescape "$channel $nick"] $kmsg"
	    } else {
	      send2tkirc $num "/kick [expandescape "$channel $nick"] *zupf*"
	    }

	    lappend alreadykicked "$nick"
	  }
	  # Zähler auch für den Fall erhöhen, daß der User schon 
	  # gekickt, der Kick aber noch nicht bestätigt wurde.
	  incr count
        }
      } else {
	# User ist länger als 'takeover_period' Sekunden auf dem Kanal.
        lappend matchnicks "$nick"
      }
    }
    incr i
  }
  if {$count > 0} {
    after 5000 [list KickWarScriptUser "$channel" "$address"]
  } else {
    if {[string length "$matchnicks"]} {
      set margin(text) "alert"
      print2channels "$cnum" "××× Some users left with matching addresses (longer than $takeover_period seconds on this channel): [reduce "$matchnicks"]"
    }
    global takeover
    set i [lsearch -exact "$takeover(tries)" "$cnum $address"]
    if {$i != -1} {
      set takeover(tries) "[lreplace "$takeover(tries)" $i $i]"
    }
  }
}

proc BanKick {wx type} {
  global banaddress bannick banchannel alreadykicked
  # type: 0=ban, 1=kick, 2=ban+kick, 3=ban+kickall

  set num [GetChannelWindow "$banchannel($wx)"]
  set a .ban$wx.body.address
  if {$type == 3} {
    append user "*!"
  } else {
    append user "[$a.nick cget -text]!"
  }
  append address "[string trimleft "[$a.tilde cget -text]" "!"]"
  append address "[$a.user cget -text]@"

  set len [llength "$banaddress($wx)"]
  for {set i 0} {$i < $len} {incr i} {
    append address "[$a.$i cget -text]"

    if {$i < [expr $len - 1]} {
      append address "[$a.point$i cget -text]"
    }
  }
  append user "$address"
  switch -- "$type" {
    "0" {
      if {[string length "$bannick($wx)"]} {
	if {[isOpOnChannel [GetChannelNumber "$banchannel($wx)"] "$bannick($wx)"]} {
          send2irc "/mode $banchannel($wx) -o+b $bannick($wx) $user"
	} else {
          send2irc "/mode $banchannel($wx) +b $user"
	}
      } else {
        send2irc "/mode $banchannel($wx) +b $user"
      }
    }
    "1" {
      send2tkirc $num "/kick [expandescape "$banchannel($wx) $bannick($wx)"] [.ban$wx.reasons.entry get]"
    }
    "2" {
      if {[isOpOnChannel [GetChannelNumber "$banchannel($wx)"] "$bannick($wx)"]} {
        send2irc "/mode $banchannel($wx) -o+b $bannick($wx) $user"
      } else {
        send2irc "/mode $banchannel($wx) +b $user"
      }
      send2tkirc $num "/kick [expandescape "$banchannel($wx) $bannick($wx)"] [.ban$wx.reasons.entry get]"
    }
    "3" {
      global nickname margin
      set cnum [GetChannelNumber "$banchannel($wx)"]
      if {$cnum != -1} {
	if {[isOpOnChannel $cnum "$nickname"]} {
          send2irc "/mode $banchannel($wx) +b $user"
          set alreadykicked ""
          KickWarScriptUser "$banchannel($wx)" "$address"
        } else {
	  set margin(text) "error"
	  print2crap "××× You are not channel operator on $banchannel($wx)!"
	  beep
          BanWindow $banchannel($wx) {} $address 3
	}
      }
    }
  }
}

proc BanWindow {channel nick address type} {
  # type: 0=ban ohne kick, 1=ban mit kick, 2=kick mit ban,
  #       3=prevent channel-take-over

  global win
  incr win(reqcount)
  set wx $win(reqcount)
  global banaddress bannick banuser banchannel

  set bannick($wx) "$nick"
  set banchannel($wx) "$channel"
  if {[string length "$address"] == 0} {
    set address "[AddressOfNick $nick]"
  }
  set user "[string range "$address" 0 [expr [string first "@" "$address"] - 1]]"
  if [regexp -- {^[\^~+=-].*} "$user"] {
    set user "[string range "$user" 1 end]"
  }
  set i [string length "$user"]
  if {$i > 8} {
    set banuser($wx) "[string range "$user" [expr $i - 8] end]"
  } else {
    set banuser($wx) "$user"
  }
  set address "[string range "$address" [expr [string first "@" "$address"] + 1] end]"
  set banaddress($wx) "[split "$address" "."]"
  set path ".ban$wx"
  if [catch {Toplevel $path -class tkirc-request}] {
    raise $path
    return ""
  } else {
    if {$type == 3} {
      wm title $path " tkirc-alert"
      Label $path.top -text "It's possible that someone of host '$address'\ntries to take over channel '$channel'.\nPlease select the pattern to ban the users of that host."
    } elseif {$type} {
      global geometry_kick
      if {[info exists geometry_kick]} {
	wm geometry $path $geometry_kick
      }
      wm title $path " tkirc: Ban-Kick '$nick' from channel '$channel'"
      Label $path.top -text "Please select the pattern to ban user '$user@$address':"
    } else {
      wm title $path " tkirc: Ban '$nick' from channel '$channel'"
      Label $path.top -text "Please select the pattern to ban user '$user@$address':"
    }
    bind $path <Escape> "closewindow $path"
    pack $path.top -side top -padx 2 -pady 2
    set f $path.body
    Frame $f
    pack $f -fill x -padx 2 -pady 2

    set a $f.address
    Frame $a -relief sunken -bd 1
    pack $a -side top -expand true -fill x

    if {$type == 3} {
      Button $a.nick -text "*" -bd 0 -state disabled
    } else {
      Button $a.nick -text "*" -bd 0 -command "SetBanNickButton $wx"
    }
    pack $a.nick -side left -padx 0 -ipadx 0

    if {$type == 3} {
      Label $a.tilde -text "!" -bd 0
    } else {
      Label $a.tilde -text "!*" -bd 0
    }
    pack $a.tilde -side left -padx 0 -ipadx 0

    if {$type == 3} {
      Button $a.user -text "*" -bd 0 -command "SetBanUserButton $wx"
    } else {
      Button $a.user -text "$banuser($wx)" -bd 0 -command "SetBanUserButton $wx"
    }
    pack $a.user -side left -padx 0 -ipadx 0

    Label $a.at -text "@" -bd 0
    pack $a.at -side left -padx 0 -ipadx 0

    for {set i 0} {$i < [llength "$banaddress($wx)"]} {incr i} {
      set element "[lindex "$banaddress($wx)" $i]"
      if {$type == 3} {
	global takeover_star_patterns
        foreach x "$takeover_star_patterns" {
	  if [strmatch "$x" "$element"] {
	    set element "*"
	    break
	  }
	}
      }
      Button $a.$i -text "$element" -bd 0 \
        -command "SetBanAddressButton $wx $i"
      pack $a.$i -side left -padx 0 -ipadx 0

      if {$i < [expr [llength "$banaddress($wx)"] - 1]} {
        Label $a.point$i -text "." -bd 0
        pack $a.point$i -side left -padx 0 -ipadx 0
      }
    }

    set f $path.buttons
    Frame $f
    pack $f -fill x -pady 2 -side bottom
    Button $f.cancel -text "Cancel" -command "closewindow $path"
    if {$type == 3} {
      Button $f.ban -text "Ban" -command "BanKick $wx 0 ; closewindow $path"
      DefaultButton $f.bankick -text "Ban + Kick" -command "BanKick $wx 3 ; closewindow $path"
      bind $path <Return> "BanKick $wx 3 ; closewindow $path"
      pack $f.cancel $f.bankick $f.ban -side right
      myfocus 4 $f.bankick
    } elseif {$type == 2} {
      Button $f.wait -text "Kick + Wait" -command "BanKick $wx 1"
      Button $f.ban -text "Ban" -command "BanKick $wx 0 ; closewindow $path"
      DefaultButton $f.kick -text "Kick" -command "BanKick $wx 1 ; closewindow $path"
      Button $f.bankick -text "Ban + Kick" -command "BanKick $wx 2 ; closewindow $path"
      bind $path <Return> "BanKick $wx 1 ; closewindow $path"
      pack $f.wait -side left
      pack $f.cancel $f.bankick $f.kick $f.ban -side right
    } elseif {$type == 1} {
      Button $f.wait -text "Kick + Wait" -command "BanKick $wx 1"
      DefaultButton $f.ban -text "Ban" -command "BanKick $wx 0 ; closewindow $path"
      bind $path <Return> "BanKick $wx 0 ; closewindow $path"
      Button $f.kick -text "Kick" -command "BanKick $wx 1 ; closewindow $path"
      Button $f.bankick -text "Ban + Kick" -command "BanKick $wx 2 ; closewindow $path"
      pack $f.wait -side left
      pack $f.cancel $f.bankick $f.kick $f.ban -side right
    } else {
      DefaultButton $f.ban -text "Ban" -command "BanKick $wx 0 ; closewindow $path"
      bind $path <Return> "BanKick $wx 0 ; closewindow $path"
      pack $f.cancel $f.ban -side right
    }

    if {$type > 0 && $type < 3} {
      Label $path.mid -text "Please select the kickmessage, if you also want to kick:"
      pack $path.mid -side top -padx 2 -pady 2

      Frame $path.reasons
      InitKickReasonList $path
      pack $path.reasons -expand true -fill both
    }
    return "$path"
  }
}

proc BanList_EntrySelected { } {
  global banlist chan
  set pattern "" ; set comment "" ; set date "" ; set user ""
  .banlist.edit.entries.comment configure -state normal

  .banlist.edit.entries.pattern delete 0 end
  .banlist.edit.entries.comment delete 0 end

  set num "[MultiListbox_Selected .banlist.list]"
  if {"$num" != ""} {
    append pattern "[MultiListbox_Get .banlist.list 0 $num]"
    append comment "[MultiListbox_Get .banlist.list 1 $num]"
  }
  .banlist.edit.entries.pattern insert 0 "$pattern"
  .banlist.edit.entries.comment insert 0 "$comment"

  set cnum [GetChannelNumber "$banlist(channel)"]
  if {$cnum != -1} {
    set j [lsearch -exact "$chan($cnum,banpatterns)" "$pattern"]
    if {$j != -1} {
      # Der Ban gilt bereits. Datum und Nick sind bekannt.
      set date "[lindex "$chan($cnum,bantimes)" $j]"
      if {"$date" == "0"} {
	set date ""
      } else {
        set date "[longdate $date]"
      }
      set user "[lindex "$chan($cnum,banusers)" $j]"
      if {"$user" == "!"} {
        set user ""
      }
    }
  }
  .banlist.edit.entries.date configure -text "$date"
  .banlist.edit.entries.user configure -text "$user"

  set j [lsearch -exact "$banlist(old)" "$pattern"]
  if {$j == -1} {
    # Der Ban gilt noch nicht für den Kanal.
    .banlist.edit.entries.comment configure -state disabled
  }
}

proc BanList_Undo { } {
  global banlist bancomments

  MultiListbox_Delete .banlist.list 2 0 end
  for {set i 0} {$i < [llength "$banlist(old)"]} {incr i} {
    MultiListbox_Insert .banlist.list 2 end "[lindex "$banlist(old)" $i]" "[riddletext "[lindex "$bancomments(old)" $i]"]"
  }
  set banlist(new) "$banlist(old)"
  set bancomments(new) "$bancomments(old)"

  .banlist.edit.entries.pattern delete 0 end
  .banlist.edit.entries.comment delete 0 end
  .banlist.edit.entries.date configure -text ""
  .banlist.edit.entries.user configure -text ""
}

proc BanList_AddEntry { } {
  global banlist bancomments

  MultiListbox_Delete .banlist.list 2 0 end
  set j [lsearch -exact "$banlist(new)" "[.banlist.edit.entries.pattern get]"]
  if {$j == -1} {
    lappend banlist(new) "[.banlist.edit.entries.pattern get]"
    lappend bancomments(new) "[.banlist.edit.entries.comment get]"
  } else {
    set bancomments(new) "[lreplace "$bancomments(new)" $j $j "[.banlist.edit.entries.comment get]"]"
  }
  for {set i 0} {$i < [llength "$banlist(new)"]} {incr i} {
    MultiListbox_Insert .banlist.list 2 end "[lindex "$banlist(new)" $i]" "[riddletext "[lindex "$bancomments(new)" $i]"]"
  }
  .banlist.edit.entries.pattern delete 0 end
  .banlist.edit.entries.comment delete 0 end
  .banlist.edit.entries.date configure -text ""
  .banlist.edit.entries.user configure -text ""
}

proc BanList_RemoveEntry { } {
  global banlist bancomments

  set num "[MultiListbox_Selected .banlist.list]"
  if {"$num" != ""} {
    MultiListbox_Delete .banlist.list 2 $num
    set banlist(new) "[lreplace "$banlist(new)" $num $num]"
    set bancomments(new) "[lreplace "$bancomments(new)" $num $num]"
  }
  .banlist.edit.entries.pattern delete 0 end
  .banlist.edit.entries.comment delete 0 end
  .banlist.edit.entries.date configure -text ""
  .banlist.edit.entries.user configure -text ""
}

proc BanList_CommitChanges { } {
  global banlist bancomments chan

  set cnum [GetChannelNumber "$banlist(channel)"]
  for {set i 0} {$i < [llength "$banlist(new)"]} {incr i} {
    set j [lsearch -exact "$chan($cnum,banpatterns)" "[lindex "$banlist(new)" $i]"]
    if {$j != -1} {
      set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $j $j "[lindex "$bancomments(new)" $i]"]"
    }
  }

  set flags "" ; set params "" ; set count 0
  for {set i 0} {$i < [llength "$banlist(old)"]} {incr i} {
    set j [lsearch -exact "$banlist(new)" "[lindex "$banlist(old)" $i]"]
    if {$j == -1} {
      append flags "-b"
      append params " [lindex "$banlist(old)" $i]"
      incr count
      if {$count >= 3} {
	send2irc "/mode $banlist(channel) $flags$params"
        set flags "" ; set params "" ; set count 0
      }
    } else {
      set banlist(new) "[lreplace "$banlist(new)" $j $j]"
      set bancomments(new) "[lreplace "$bancomments(new)" $j $j]"
    }
  }
  for {set i 0} {$i < [llength "$banlist(new)"]} {incr i} {
    append flags "+b"
    append params " [lindex "$banlist(new)" $i]"
    incr count
    if {$count >= 3} {
      send2irc "/mode $banlist(channel) $flags$params"
      set flags "" ; set params "" ; set count 0
    }
  }

  if {$count > 0} {
    send2irc "/mode $banlist(channel) $flags$params"
  }
}

proc BanListWindow {num} {
  global chan banlist bancomments

  if {[winfo exists .banlist]} {
    # This window is already open.
    beep ; return
  }

  set channel [GetActual $num]
  set banlist(channel) "$channel"
  if {"$channel" != "*"} {
    set cnum [GetChannelNumber "$channel"]
    set banlist(old) "$chan($cnum,banpatterns)"
    set banlist(new) "$banlist(old)"
    set bancomments(old) "$chan($cnum,bancomments)"
    set bancomments(new) "$bancomments(old)"

    if [catch {Toplevel .banlist -class tkirc-request}] {
      raise .banlist
    } else {
#      grab set .banlist
      wm title .banlist  " tkirc: Edit banlist of channel $channel"
      bind .banlist <Escape> "closewindow .banlist"

      set f .banlist.buttons
      Frame $f
      pack $f -side bottom -fill x -pady 2
      Button $f.commit -text "Commit changes" -command "BanList_CommitChanges ; closewindow .banlist"
      Button $f.cancel -text "Cancel" -command "closewindow .banlist"
      pack $f.cancel $f.commit -side right

      Button $f.undo -text "Undo" -command "BanList_Undo"
      Button $f.remove -text "Remove" -command "BanList_RemoveEntry"
      pack $f.undo $f.remove -side left

      set frame .banlist.edit
      Frame $frame ; pack $frame -fill x -padx 2 -pady 2 -side bottom

      set frame .banlist.edit.labels
      Frame $frame ; pack $frame -side left
      Label $frame.pattern -text "Banpattern:"
      pack $frame.pattern -anchor e -pady 1
      Label $frame.comment -text "Comment:"
      pack $frame.comment -anchor e -pady 1
      Label $frame.date -text "Date,Time:"
      pack $frame.date -anchor e -pady 1
      Label $frame.user -text "Banned by:"
      pack $frame.user -anchor e -pady 1

      set frame .banlist.edit.entries
      Frame $frame ; pack $frame -fill x -expand true -side right
      Entry $frame.pattern ; pack $frame.pattern -fill x
      Entry $frame.comment ; pack $frame.comment -fill x
      Label $frame.date -bd 1 -relief sunken -anchor w
      pack $frame.date -fill x -padx 1 -pady 1
      Label $frame.user -bd 1 -relief sunken -anchor w
      pack $frame.user -fill x -padx 1 -pady 1

      MultiListbox .banlist.list 2 {Banpattern 30} {Comment 30}
      MultiListbox_Delete .banlist.list 2 0 end
      pack .banlist.list -side left -expand true -fill both -ipadx 0 -padx 0

      myfocus 27 $frame.pattern
      bind $frame.pattern <Return> "BanList_AddEntry"
      bind $frame.comment <Return> "BanList_AddEntry"

      BanList_Undo

      MultiListbox_Bind .banlist.list 2 <B1-Motion> "+BanList_EntrySelected"
      MultiListbox_Bind .banlist.list 2 <ButtonPress-1> "+BanList_EntrySelected"
      MultiListbox_Bind .banlist.list 2 <ButtonRelease-1> "+BanList_EntrySelected"
    }
  }
}

#############
#  WINDOWS  #
#############

proc setColor {w options color} {
  if {"$color" == ""} {
    return
  }
  foreach option $options {
    catch {
      $w config $option $color
    }
  }
  foreach child [winfo children $w] {
    setColor $child $options $color
  }
}

proc ChooseColor {name options} {
  global color crapwindow win
  set initialColor [.win$crapwindow.cmdline cget -$name]

  set farbe [tk_chooseColor -title "Choose color `$name'" -parent . \
    -initialcolor $initialColor]
  if [string compare $farbe ""] {
    setColor . $options $farbe
    foreach x "$options" {
      set x [string trimleft "$x" "-"]
      set color($x) $farbe
    }
    foreach x "$win(list)" {
      ConfigureStyles $x
    }
  }
}

proc InitMenu {num} {
  global messagewindow crapwindow

  set path "[GetPathFromNum $num]"
  Frame $path.menu -bd 1

  # Project
  set f $path.menu.project
  Menubutton $f -text "Project" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu
  $f.menu add command -label "About" -command "About $num"
  $f.menu add separator
  $f.menu add command -label "New window" -command "MainWindow -1"
  $f.menu add command -label "Set message window" \
      -command "set messagewindow $num ; UpdateAllTitles"
  $f.menu add command -label "Set crap window" \
      -command "set crapwindow $num ; UpdateAllTitles"
  $f.menu add separator

  $f.menu add command -label "Clear" \
    -command "ClearTraffic $num"
  $f.menu add command -label "Reload tkircrc" \
    -command "ReloadTKircRC {} ; RefreshMainWindows"
  $f.menu add command -label "Save buffer..." \
    -command "SaveBuffer $num {}"
  $f.menu add cascade -label "Extras" -menu $f.menu.extra
  Menu $f.menu.extra
  $f.menu.extra add command -label "Notify window" \
    -command "NotifyWindow"
  $f.menu.extra add command -label "URL window" \
    -command "URLWindow"
  $f.menu.extra add command -label "MessageID window" \
    -command "MsgIDWindow"
  $f.menu add separator

  $f.menu add command -label "Connect..." -command "ServersWindow"
  $f.menu add command -label "Disconnect" -command "send2tkirc $num {/disconnect}"
  $f.menu add separator

  $f.menu add command -label "Close" -command "CloseMainWindow $num"
  $f.menu add cascade -label "Signoff with message" -menu $f.menu.quit
  Menu $f.menu.quit
  $f.menu add command -label "Exit" -command "Exit {}"
  pack $f -side left -pady 0 -ipady 0

  # Prefs
  set f $path.menu.prefs
  Menubutton $f -text "Prefs" -menu $f.menu -bd 0 -underline 1
  Menu $f.menu
  $f.menu add cascade -label "Beep" -menu $f.menu.beep
  Menu $f.menu.beep

  $f.menu.beep add cascade -label "on public" -menu $f.menu.beep.pub
  Menu $f.menu.beep.pub
  $f.menu.beep.pub add checkbutton -label "when present" \
    -variable beep_on_public_when_present
  $f.menu.beep.pub add checkbutton -label "when away" \
    -variable beep_on_public_when_away

  $f.menu.beep add cascade -label "on message" -menu $f.menu.beep.mes
  Menu $f.menu.beep.mes
  $f.menu.beep.mes add checkbutton -label "when present" \
    -variable beep_on_message_when_present
  $f.menu.beep.mes add checkbutton -label "when away" \
    -variable beep_on_message_when_away

  $f.menu.beep add cascade -label "on notice" -menu $f.menu.beep.not
  Menu $f.menu.beep.not
  $f.menu.beep.not add checkbutton -label "when present" \
    -variable beep_on_notice_when_present
  $f.menu.beep.not add checkbutton -label "when away" \
    -variable beep_on_notice_when_away

  $f.menu.beep add cascade -label "on invite" -menu $f.menu.beep.inv
  Menu $f.menu.beep.inv
  $f.menu.beep.inv add checkbutton -label "when present" \
    -variable beep_on_invite_when_present
  $f.menu.beep.inv add checkbutton -label "when away" \
    -variable beep_on_invite_when_away

  $f.menu.beep add cascade -label "on ctrl-g" -menu $f.menu.beep.ctrlG
  Menu $f.menu.beep.ctrlG
  $f.menu.beep.ctrlG add checkbutton -label "when present" \
    -variable beep_on_ctrlG_when_present
  $f.menu.beep.ctrlG add checkbutton -label "when away" \
    -variable beep_on_ctrlG_when_away

  $f.menu add cascade -label "Show address" -menu $f.menu.show
  Menu $f.menu.show

  $f.menu.show add cascade -label "on message" -menu $f.menu.show.mes
  Menu $f.menu.show.mes
  $f.menu.show.mes add checkbutton -label "when present" \
    -variable show_address_on_message_when_present
  $f.menu.show.mes add checkbutton -label "when away" \
    -variable show_address_on_message_when_away
  $f.menu.show.mes add checkbutton -label "in logfile" \
    -variable show_address_on_message_in_logfile

  $f.menu.show add cascade -label "on notice" -menu $f.menu.show.not
  Menu $f.menu.show.not
  $f.menu.show.not add checkbutton -label "when present" \
    -variable show_address_on_notice_when_present
  $f.menu.show.not add checkbutton -label "when away" \
    -variable show_address_on_notice_when_away
  $f.menu.show.not add checkbutton -label "in logfile" \
    -variable show_address_on_notice_in_logfile

  $f.menu add cascade -label "Show time" -menu $f.menu.time
  Menu $f.menu.time

  $f.menu.time add cascade -label "on private" -menu $f.menu.time.priv
  Menu $f.menu.time.priv
  $f.menu.time.priv add checkbutton -label "when present" \
    -variable show_time_on_private_when_present
  $f.menu.time.priv add checkbutton -label "when away" \
    -variable show_time_on_private_when_away

  $f.menu.time add cascade -label "on public" -menu $f.menu.time.pub
  Menu $f.menu.time.pub
  $f.menu.time.pub add checkbutton -label "when present" \
    -variable show_time_on_public_when_present
  $f.menu.time.pub add checkbutton -label "when away" \
    -variable show_time_on_public_when_away

  $f.menu add cascade -label "Chat window" -menu $f.menu.chat
  Menu $f.menu.chat

  $f.menu.chat add cascade -label "on message" -menu $f.menu.chat.mes
  Menu $f.menu.chat.mes
  $f.menu.chat.mes add checkbutton -label "when present" \
    -variable chat_window_on_message_when_present
  $f.menu.chat.mes add checkbutton -label "when away" \
    -variable chat_window_on_message_when_away

  $f.menu.chat add cascade -label "on notice" -menu $f.menu.chat.not
  Menu $f.menu.chat.not
  $f.menu.chat.not add checkbutton -label "when present" \
    -variable chat_window_on_notice_when_present
  $f.menu.chat.not add checkbutton -label "when away" \
    -variable chat_window_on_notice_when_away

  $f.menu add cascade -label "Request window" -menu $f.menu.request
  Menu $f.menu.request
  $f.menu.request add checkbutton -label " on dcc chat" \
     -variable request_on_dcc_chat
  $f.menu.request add checkbutton -label " on dcc send" \
     -variable request_on_dcc_send
  $f.menu.request add checkbutton -label " on invite" \
     -variable request_on_invite
  $f.menu.request add checkbutton -label " on kick" \
     -variable request_on_kick

  $f.menu add checkbutton -label "Silence" -variable silence
  $f.menu add separator
  $f.menu add cascade -label "Hide" -menu $f.menu.hide
  Menu $f.menu.hide
  $f.menu.hide add checkbutton -label " joins" \
      -variable hide_joins$num
  $f.menu.hide add checkbutton -label " leaves" \
      -variable hide_leaves$num
  $f.menu.hide add checkbutton -label " signoffs" \
      -variable hide_signoffs$num

  $f.menu add cascade -label "Show" -menu $f.menu.show2
  Menu $f.menu.show2
  $f.menu.show2 add checkbutton -label " commandline" \
    -command "AddOrRemoveCmdLine $num" -variable show_commandline$num
  $f.menu.show2 add checkbutton -label " topic" \
    -command "AddOrRemoveTopic $num" -variable show_topic$num
  $f.menu.show2 add checkbutton -label " userlist" \
    -command "AddOrRemoveUserList $num" -variable show_userlist$num

  $f.menu add cascade -label "Margin" -menu $f.menu.margin
  Menu $f.menu.margin
  $f.menu.margin add checkbutton -label " use margin" \
    -command "UseMargin $num" -variable use_margin$num
  $f.menu.margin add checkbutton -label " display types" \
    -variable display_types$num

  $f.menu add cascade -label "Set colors" -menu $f.menu.color
  Menu $f.menu.color
  $f.menu.color add cascade -label " background " -menu $f.menu.color.back
  Menu $f.menu.color.back
  $f.menu.color.back add command -label " normal..." \
    -command "ChooseColor background {-background}"
  $f.menu.color.back add command -label " highlight..." \
    -command "ChooseColor highlightbackground {-highlightbackground}"
  $f.menu.color.back add command -label " select..." \
    -command "ChooseColor selectbackground {-selectbackground}"
  $f.menu.color add cascade -label " foreground " -menu $f.menu.color.fore
  Menu $f.menu.color.fore
  $f.menu.color.fore add command -label " normal..." \
    -command "ChooseColor foreground {-foreground}"
  $f.menu.color.fore add command -label " highlight..." \
    -command "ChooseColor highlightcolor {-highlightcolor}"
  $f.menu.color.fore add command -label " select..." \
    -command "ChooseColor selectforeground {-selectforeground}"

  $f.menu add cascade -label "Sort userlist" -menu $f.menu.sortul
  Menu $f.menu.sortul
  $f.menu.sortul add checkbutton -label " alphabeticly" \
    -variable sort_userlist_alphabeticly$num -command "FillUserList $num -1"
  $f.menu.sortul add checkbutton -label " by channelmodes" \
    -variable sort_userlist_by_channelmodes$num -command "FillUserList $num -1"

  $f.menu add checkbutton -label "Auto popup" -variable auto_popup$num

  pack $f -side left -pady 0 -ipady 0

  # User
  set f $path.menu.user
  Menubutton $f -text "User" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu
  $f.menu add cascade -label "ctcp" -menu $f.menu.ctcp
  Menu $f.menu.ctcp
  $f.menu.ctcp add command -label "clientinfo" -command "Ctcp $num clientinfo"
  $f.menu.ctcp add command -label "finger" -command "Ctcp $num finger"
  $f.menu.ctcp add command -label "ping" -command "Ctcp $num ping \[clock seconds\]"
  $f.menu.ctcp add command -label "time" -command "Ctcp $num time"
  $f.menu.ctcp add command -label "userinfo" -command "Ctcp $num userinfo"
  $f.menu.ctcp add command -label "version" -command "Ctcp $num version"

  $f.menu add cascade -label "dcc" -menu $f.menu.dcc
  Menu $f.menu.dcc
  $f.menu.dcc add command -label "chat" -command "UserDChat $num"
  $f.menu.dcc add command -label "list" -command "UserDList $num"
  $f.menu.dcc add command -label "send..." -command "UserDSend $num"

  $f.menu add command -label "chat" -command "UserChat $num" \
    -accelerator alt+c
  $f.menu add command -label "who" -command "UserWho $num" \
    -accelerator alt+w
  $f.menu add command -label "whois" -command "UserWhois $num ; UnselectNicks $num" -accelerator alt+i
  $f.menu add command -label "query" -command "UserQuery $num" \
    -accelerator alt+q
  $f.menu add command -label "unquery" -command "send2tkirc $num /query" -accelerator alt+y
  $f.menu add command -label "op" -command "UserOp $num" \
    -accelerator alt+o
  $f.menu add command -label "deop" -command "UserDeop $num" \
    -accelerator alt+d
  $f.menu add command -label "give voice" -command "UserGiveVoice $num" \
    -accelerator alt+v
  $f.menu add command -label "remove voice " -command "UserRemoveVoice $num" -accelerator alt+e
  $f.menu add command -label "ignore" -command "UserIgnore $num"
  $f.menu add command -label "ban..." -command "UserBan $num" \
    -accelerator alt+b
  $f.menu add command -label "kick..." -command "UserKick $num" \
    -accelerator alt+k
  pack $f -side left -pady 0 -ipady 0

  # Popup für den Nickname von privaten Messages/Notices
  set f $path.menu.popup
  Menubutton $f -text "User" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu -tearoff 0
  $f.menu add separator
  $f.menu add cascade -label "ctcp" -menu $f.menu.ctcp
  Menu $f.menu.ctcp -tearoff 0
  $f.menu.ctcp add separator
  $f.menu.ctcp add command -label "clientinfo" -command "Ctcp $num clientinfo"
  $f.menu.ctcp add command -label "finger" -command "Ctcp $num finger"
  $f.menu.ctcp add command -label "ping" -command "Ctcp $num ping \[clock seconds\]"
  $f.menu.ctcp add command -label "time" -command "Ctcp $num time"
  $f.menu.ctcp add command -label "userinfo" -command "Ctcp $num userinfo"
  $f.menu.ctcp add command -label "version" -command "Ctcp $num version"

  $f.menu add cascade -label "dcc" -menu $f.menu.dcc
  Menu $f.menu.dcc -tearoff 0
  $f.menu.dcc add separator
  $f.menu.dcc add command -label "chat" -command "UserDChat $num"
  $f.menu.dcc add command -label "list" -command "UserDList $num"
  $f.menu.dcc add command -label "send..." -command "UserDSendPopup $num"

  $f.menu add command -label "chat" -command "UserChat $num"
  $f.menu add command -label "who" -command "UserWho $num"
  $f.menu add command -label "whois" -command "UserWhois $num ; UnselectNicks $num"
  $f.menu add command -label "ignore" -command "UserIgnore $num"

  # Channel
  set f $path.menu.channel
  Menubutton $f -text "Channel" -menu $f.menu -bd 0 -underline 1
  Menu $f.menu
  $f.menu add cascade -label "join" -menu $f.menu.join
  Menu $f.menu.join
  $f.menu add cascade -label "wjoin" -menu $f.menu.wjoin
  Menu $f.menu.wjoin
  $f.menu add command -label "leave" -command "LeaveChannel $num \"\""
  $f.menu add cascade -label "part with msg" -menu $f.menu.part
  Menu $f.menu.part
  $f.menu add command -label "rejoin" -command "RejoinChannel $num"
  $f.menu add command -label "baninfos" -command "send2tkirc $num {/baninfos *}"
  $f.menu add command -label "invite..." -command "InviteToChannel $num"
  $f.menu add command -label "set modes..." -command "ChannelModesWindow $num"
  $f.menu add command -label "edit banlist..." -command "BanListWindow $num"
  $f.menu add cascade -label "set topic" -menu $f.menu.topic
  Menu $f.menu.topic
  pack $f -side left -pady 0 -ipady 0

  # Personal
  set f $path.menu.personal
  Menubutton $f -text "Personal" -menu $f.menu -bd 0 -underline 5
  Menu $f.menu
  $f.menu add cascade -label "set mode" -menu $f.menu.mode
  Menu $f.menu.mode
  foreach x "{i {invisible}} {w wallops} {s {server notices}}" {
    global modes
    $f.menu.mode add checkbutton -label [lindex "$x" 1] \
      -command "ChangeUserMode [lindex "$x" 0]" \
      -variable modes(~,[lindex "$x" 0])
  }
  $f.menu add cascade -label "set nickname" -menu $f.menu.nick
  Menu $f.menu.nick
  $f.menu add cascade -label "mark away" -menu $f.menu.away
  Menu $f.menu.away
  $f.menu add command -label "unmark away" -command "send2tkirc 0 \"/away\""
  pack $f -side left -pady 0 -ipady 0

  # Server
  set f $path.menu.server
  Menubutton $f -text "Server" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu
  $f.menu add command -label "connections" \
   -command "send2irc \"/trace\" ; AddToFilterQueue \{262?* End of TRACE\}"
  $f.menu add command -label "date" -command "send2irc \"/date\""
  $f.menu add command -label "info" -command "send2irc \"/quote info\""
  $f.menu add command -label "lusers" -command "send2irc \"/lusers\""
  $f.menu add command -label "motd" -command "send2irc \"/motd\""
  $f.menu add command -label "uptime" -command "send2irc \"/stats u\""
  $f.menu add command -label "version" -command "send2irc \"/quote version\""
  $f.menu add command -label "connect..." -command "ServersWindow"
  $f.menu add command -label "disconnect" -command "send2tkirc $num {/disconnect}"
  pack $f -side left -pady 0 -ipady 0

  set f $path.menu.private
  Menubutton $f -text "Private" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu
  pack $f -side left -pady 0 -ipady 0

  # Channel modes
  foreach x "t s p n m i l k b v o" {
    Label $path.menu.$x
    pack $path.menu.$x -side right
  }
  PrintModeChars $num ""

  pack $path.menu -fill x
}

proc RefreshMenu {num} {
  global preferred_channels preferred_servers preferred_awayreasons
  global preferred_signoffmessages preferred_partmessages
  global preferred_topics preferred_nicknames
  global messagewindow crapwindow on_args

  set path "[GetPathFromNum $num]"

  # Project
  set f $path.menu.project
  $f.menu.quit delete 0 end
  foreach x "$preferred_signoffmessages" {
    $f.menu.quit add command -label "$x" \
      -command "Exit \"$x\""
  }

  # Prefs

  # User

  # Channel
  set f $path.menu.channel
  $f.menu.join delete 0 end
  foreach x "$preferred_channels" {
    $f.menu.join add command -label "$x" \
     -command "send2tkirc $num \"[expandescape "[expand "/join $x"]"]\""
  }
  $f.menu.wjoin delete 0 end
  foreach x "$preferred_channels" {
    $f.menu.wjoin add command -label "$x" \
     -command "send2tkirc $num \"[expandescape "[expand "/wjoin $x"]"]\""
  }
  set f $path.menu.channel
  $f.menu.part delete 0 end
  foreach x "$preferred_partmessages" {
    $f.menu.part add command -label "$x" \
     -command "LeaveChannel $num \"$x\""
  }
  $f.menu.topic delete 0 end
  foreach x "$preferred_topics" {
    $f.menu.topic add command -label "$x" \
     -command "send2tkirc $num \"[expandescape "[expand "/topic * $x"]"]\""
  }

  # Personal
  set f $path.menu.personal
  $f.menu.nick delete 0 end
  for {set i 0} {$i < [llength "$preferred_nicknames"]} {incr i} {
    set x "[lindex "$preferred_nicknames" $i]"
    $f.menu.nick add command -label "$x"\
      -command "send2tkirc $num \"/nick [expandescape "[expand "$x"]"]\""
  }
  $f.menu.away delete 0 end
  foreach x "$preferred_awayreasons" {
    $f.menu.away add command -label "$x"\
      -command "send2tkirc $num \"/away [expandescape "[expand "$x"]"]\""
  }

  # Server

  # Private menus
  set f $path.menu.private
  if [winfo exists $f] {
    pack forget $f ; destroy $f
  }
  Menubutton $f -text "Private" -menu $f.menu -bd 0 -underline 0
  Menu $f.menu
  pack $f -side left -pady 0 -ipady 0

  foreach x "[lsort "[info commands "on_menucreate*"]"]" {
    set on_args(path) "$path.menu"
    set on_args(window) $num
    $x
  }
}

proc GetTopic {num} {
  if {[strcmp "*" "[GetActual $num]"]} {
    myfocus 6 [GetPathFromNum $num].cmdline
    send2tkirc $num "/topic [expandescape "[GetActual $num]"]"
  }
}

proc SendTopic {num} {
  global margin escape_sign

  if {[strcmp "*" "[GetActual $num]"]} {
    set path "[GetPathFromNum $num]"
    set topic "[$path.body.left.topic.entry get]"
    set len [string length "[strreplace "$topic" "$escape_sign" ""]"]
    if {$len > 80} {
      beep
      set margin(text) "error"
      print2text $num "××× Topic has $len chars, but maximum is 80."
      myfocus 7 $path.body.left.topic.entry
    } else {
      send2tkirc $num "/topic [expandescape "[GetActual $num]"] $topic"
      myfocus 8 $path.cmdline
    }
  }
}

proc InitTopic {num} {
  set f "[GetPathFromNum $num].body.left.topic"
  Frame $f
  Menubutton $f.label -text " Topic: " -bd 0 -underline 1
  Entry $f.entry -bd 0 -state disabled
  pack $f.label -side left
  bind $f.label <Button-1> "GetTopic $num"
  pack $f.entry -side left -expand true -fill x
  bind $f.entry <Return> "SendTopic $num"
  bind $f.entry <FocusOut> "UpdateTopic $num"
}

proc AddOrRemoveTopic {num} {
  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  global show_topic$num
  if {[set show_topic$num]} {
    pack forget $path.body.left.traffic
    pack $path.body.left.topic -fill x
    pack $path.body.left.traffic -expand true -fill both \
      -padx 0 -ipadx 0

    if {$end == 1} {
#      after 150 $widget yview end
      $widget yview end
    }
  } else {
    pack forget $path.body.left.topic
  }
}

proc InitCmdLine {num} {
  set f "[GetPathFromNum $num]"
  Entry $f.cmdline
  bind $f.cmdline <Return> "+entry2irc $num"
  bind $f.cmdline <Shift-Return> "entry2history $num"
  bind $f.cmdline <Up> "HistoryUp $num"
  bind $f.cmdline <Down> "HistoryDown $num"
  bind $f.cmdline <Tab> "CompleteOrReplace $num ; $f.cmdline xview insert"
  bind $f.cmdline <Alt-Tab> "MsgHistoryUp $num"
  bind $f.cmdline <Meta-Tab> "MsgHistoryUp $num"
}

proc AddOrRemoveCmdLine {num} {
  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  global show_commandline$num
  if {[set show_commandline$num]} {
    pack forget $path.body
    pack $path.cmdline -side bottom -fill x
    pack $path.body -expand true -fill both
    myfocus 9 $path.cmdline

    if {$end == 1} {
#      after 150 $widget yview end
      $widget yview end
    }
  } else {
    pack forget $path.cmdline
  }
}

proc UseMargin {num} {
  set widget [GetPathFromNum $num].body.left.traffic.text
  set end "[lindex "[$widget yview]" 1]"

  ConfigureStyles $num

  if {$end == 1} {
#    after 150 $widget yview end
    $widget yview end
  }
}

proc InitUserList {num} {
  set f "[GetPathFromNum $num].body.right"
  Frame $f -bd 1 -relief sunken
  Frame $f.top -bd 0
  pack $f.top -fill x -pady 0 -ipady 0
  Menubutton $f.top.channel -text "[GetActual $num]" -bd 0 \
     -menu ""
  Menu $f.top.channel.menu
  pack $f.top.channel -fill x -side left -pady 0 -ipady 0
  Label $f.top.count -text "-" -relief sunken -bd 0
  pack $f.top.count -side right -pady 0 -ipady 0

  Frame $f.list -bd 0
  pack $f.list -expand true -fill both -pady 0 -ipady 0
  Listbox $f.list.users -width 13 -yscrollcommand "$f.list.scroll set" -exportselection false -relief raised -selectmode extended
  Scrollbar $f.list.scroll -width 10 -orient vertical -command [list $f.list.users yview]
  pack $f.list.users -expand true -side left -fill both
  pack $f.list.scroll -side left -fill y

  bind $f.list.users <Double-Button-1> "UserWhois $num"
  bind $f.list.users <Double-Button-2> "Ctcp $num version"
#  bind $f.list.users <Double-Button-3> "UserChat $num"
  bind $f.list.users <Button-3> "UserPopup $num %W %X %Y ; break"
}

proc AddOrRemoveUserList {num} {
  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  global show_userlist$num
  if {[set show_userlist$num]} {
    pack forget $path.body.left
    pack $path.body.right -side right -fill y
    pack $path.body.left -side left -expand true -fill both

    if {$end == 1} {
#      after 150 $widget yview end
      $widget yview end
    }
  } else {
    pack forget $path.body.right
  }
}

proc FillUserList {num cnum} {
  global chan win

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }
  if {$cnum == -1} {
    set cnum [GetChannelNumber "$win($num,actual)"]
  }
  if {$cnum == -1} {
    return
  }
  set path "[GetPathFromNum $num]"
  set len [llength "$chan($cnum,nicks)"]
  $path.body.right.list.users delete 0 end

  global sort_userlist_alphabeticly$num sort_userlist_by_channelmodes$num
  switch -- "[set sort_userlist_alphabeticly$num][set sort_userlist_by_channelmodes$num]" {
    "00" {
      for {set i 0} {$i < $len} {incr i} {
	$path.body.right.list.users insert end "[lindex "$chan($cnum,names)" $i]"
      }
    }
    "01" {
      for {set i 0} {$i < $len} {incr i} {
	if {[lindex "$chan($cnum,olist)" $i]} {
	  $path.body.right.list.users insert end "[lindex "$chan($cnum,names)" $i]"
	}
      }
      for {set i 0} {$i < $len} {incr i} {
	if {[lindex "$chan($cnum,vlist)" $i] && ![lindex "$chan($cnum,olist)" $i]} {
	  $path.body.right.list.users insert end "[lindex "$chan($cnum,names)" $i]"
	}
      }
      for {set i 0} {$i < $len} {incr i} {
	if {![lindex "$chan($cnum,vlist)" $i] && ![lindex "$chan($cnum,olist)" $i]} {
	  $path.body.right.list.users insert end "[lindex "$chan($cnum,names)" $i]"
	}
      }
    }
    "10" {
      set tmp ""
      for {set i 0} {$i < $len} {incr i} {
	if {[lindex "$chan($cnum,olist)" $i]} {
	  lappend tmp "[lindex "$chan($cnum,nicks)" $i]@"
	} elseif {[lindex "$chan($cnum,vlist)" $i]} {
	  lappend tmp "[lindex "$chan($cnum,nicks)" $i]+"
	} else {
	  lappend tmp "[lindex "$chan($cnum,nicks)" $i]"
	}
      }
      set tmp "[lsort -command "strcmp" "$tmp"]"
      for {set i 0} {$i < $len} {incr i} {
	set user "[lindex "$tmp" $i]"
	if {[string first "@" "$user"] != -1} {
	  $path.body.right.list.users insert end "@[string trimright "$user" "@"]"
	} elseif {[string first "+" "$user"] != -1} {
	  $path.body.right.list.users insert end "+[string trimright "$user" "+"]"
	} else {
	  $path.body.right.list.users insert end "$user"
	}
      }
    }
    "11" {
      set tmp "[lsort -command "strcmp" "$chan($cnum,names)"]"
      set i $chan($cnum,mode_v)
      while {"[string index "[lindex "$tmp" $i]" 0]" == "@"} {
	$path.body.right.list.users insert end "[lindex "$tmp" $i]"
	incr i
      }
      for {set k 0} {$k < $chan($cnum,mode_v)} {incr k} {
	$path.body.right.list.users insert end "[lindex "$tmp" $k]"
      }
      for {set k $i} {$k < $len} {incr k} {
	$path.body.right.list.users insert end "[lindex "$tmp" $k]"
      }
    }
  }
  $path.body.right.top.count configure -text "$len"
}

proc DeleteFromUserList {num cnum nick} {
  set path "[GetPathFromNum $num]"
  set tmp "[$path.body.right.list.users get 0 end]"
  $path.body.right.top.count configure -text "[expr [llength "$tmp"]-1]"
  set i [lsearch -exact "$tmp" "$nick"]
  if {$i == -1} {
    set i [lsearch -exact "$tmp" "@$nick"]
    if {$i == -1} {
      set i [lsearch -exact "$tmp" "+$nick"]
    }
  }
  if {$i != -1} {
    $path.body.right.list.users delete $i
  }
}

proc InsertToUserList {num cnum nick} {
  global chan

  set rc -1
  set path "[GetPathFromNum $num]"
  set tmp "[$path.body.right.list.users get 0 end]"
  set len [llength "$chan($cnum,nicks)"]

  global sort_userlist_alphabeticly$num sort_userlist_by_channelmodes$num
  switch -- "[set sort_userlist_alphabeticly$num][set sort_userlist_by_channelmodes$num]" {
    "00" {
      set i [lsearch -exact "$chan($cnum,names)" "$nick"]
      if {$i != -1} {
	$path.body.right.list.users insert $i "$nick"
	set rc $i
      }
    }
    "01" {
      set i [lsearch -exact "$chan($cnum,names)" "$nick"]
      if {$i != -1} {
	if {"[string index "$nick" 0]" == "@"} {
	  set sub 0
	  for {set j 0} {$j < $i} {incr j} {
	    if {[lindex "$chan($cnum,olist)" $j] == 0} {
	      # -o überspringen!
	      incr sub
	    }
	  }
	  $path.body.right.list.users insert [expr $i-$sub] "$nick"
	  set rc [expr $i-$sub]
	} elseif {"[string index "$nick" 0]" == "+"} {
	  set sub 0 ; set j 0
	  while {$j < $i} {
	    if {[lindex "$chan($cnum,olist)" $j] == 0 && [lindex "$chan($cnum,vlist)" $j] == 0} {
	      # -o-v überspringen!
	      incr sub
	    }
	    incr j
	  }
	  set add 0 ; incr j
	  while {$j < [llength "$chan($cnum,olist)"]} {
	    if {[lindex "$chan($cnum,olist)" $j] == 1} {
	      # +o vorlassen!
	      incr add
	    }
	    incr j
	  }
	  $path.body.right.list.users insert [expr $i-$sub+$add] "$nick"
	  set rc [expr $i-$sub+$add]
	} else {
	  set add 0 ; set j [expr $i+1]
	  while {$j < [llength "$chan($cnum,olist)"]} {
	    if {[lindex "$chan($cnum,olist)" $j] == 1 || [lindex "$chan($cnum,vlist)" $j] == 1} {
	      # +o und +v vorlassen!
	      incr add
	    }
	    incr j
	  }
	  $path.body.right.list.users insert [expr $i+$add] "$nick"
	  set rc [expr $i+$add]
	}
      }
    }
    "10" {
      set tmp "[lsort -command "strcmp" "$chan($cnum,nicks)"]"
      set i [lsearch -exact "$tmp" "[TrimNick "$nick"]"]
      if {$i != -1} {
	$path.body.right.list.users insert $i "$nick"
	set rc $i
      }
    }
    "11" {
      set tmp "[lsort -command "strcmp" "$chan($cnum,names)"]"
      set i [lsearch -exact "$tmp" "$nick"]
      if {$i != -1} {
	if {"[string index "$nick" 0]" == "@"} {
	  $path.body.right.list.users insert [expr $i-$chan($cnum,mode_v)] "$nick"
	  set rc [expr $i-$chan($cnum,mode_v)]
	} elseif {"[string index "$nick" 0]" == "+"} {
	  $path.body.right.list.users insert [expr $i+$chan($cnum,mode_o)] "$nick"
	  set rc [expr $i+$chan($cnum,mode_o)]
	} else {
	  $path.body.right.list.users insert $i "$nick"
	  set rc $i
	}
      }
    }
  }
  $path.body.right.top.count configure -text "$len"
  return $rc
}

proc HandleKey {num key} {
    switch -- "$key" {
      "c" { UserChat $num }
      "o" { UserOp $num }
      "d" { UserDeop $num }
      "v" { UserGiveVoice $num }
      "e" { UserRemoveVoice $num }
      "w" { UserWho $num }
      "i" { UserWhois $num ; UnselectNicks $num }
      "b" { UserBan $num }
      "k" { UserKick $num }
      "q" { UserQuery $num }
      "y" { send2tkirc $num /query }
      "t" { GetTopic $num }
    }
}

proc RefreshMainWindows { } {
  global win

  foreach x "$win(list)" {
    MainWindow $x
  }
}

proc CloseMainWindow {num} {
  global chan win messagewindow crapwindow

  # Ggf. DCC CHAT beenden.
  if {[string length "$win($num,query)"]} {
    if {"[string index "$win($num,query)" 0]" == "="} {
      send2irc "/dcc close chat [string range "$win($num,query)" 1 end]"
    }
  }
  # Ggf. Kanäle verlassen.
  foreach x "$win($num,channels)" {
    # Wird der Kanal evtl. noch in anderen Fenstern verfolgt?
    foreach y "$win(list)" {
      if {$num != $y && [lsearch "$win($y,channels)" $x] != -1} {
	set chan($x,window) $y
        set x -1
	break
      }
    }
    if {$x != -1} {
      send2irc "/leave $chan($x)"
      DeleteChannel $x
    }
  }
  DeleteWindow $num

  set path "[GetPathFromNum $num]"
  destroy $path

  if {$messagewindow == $num} {
    set messagewindow [lindex "$win(list)" 0]
  }
  if {$crapwindow == $num} {
    set crapwindow [lindex "$win(list)" 0]
  }
  UpdateAllTitles

  foreach x "$win(list)" {
    if {$x < 500} {
      return
    }
  }

  # Es sind höchstens noch versteckte Fenster vorhanden.
  Exit ""
}

proc MainWindow {type} {
  global win margin margin_size geometry normal_style
  set num $type

  # type:  -1  opens a window (prefs will be noticed)
  # type:  -2  opens a window without topic and userlist, but commandline
  # type:  -3  opens a window without topic, userlist and commandline
  # type:  -4  opens a window with topic, userlist and commandline
  # type:  -5  opens a fake-window e.g. for &servers
  # type: >-1  redraws an existent window

  if {$num >= 500} {
    # Schwindel-Fenster
    return
  }

  if {$num < 0} {
    # Initialize variables
    if {$num == -5} {
      set num [ProduceWindow hidden]
    } else {
      set num [ProduceWindow]
    }
  } else {
    # Just refresh window
  }

  foreach x "geometry auto_popup hide_joins hide_leaves hide_signoffs show_commandline show_topic show_userlist use_margin display_types sort_userlist_alphabeticly sort_userlist_by_channelmodes" {
    global $x $x$num
    if {![info exists $x$num]} {
      set $x$num [set $x]
    }
  }

  if {$type == -5} {
    return $num
  }

  if {$type < 0} {
    set margin(size) $margin_size
  }

  switch -- "$type" {
    "-2" {
      set show_topic$num 0
      set show_userlist$num 0
      set show_commandline$num 1
    }
    "-3" {
      set show_topic$num 0
      set show_userlist$num 0
      set show_commandline$num 0
    }
    "-4" {
      set show_topic$num 1
      set show_userlist$num 1
      set show_commandline$num 1
    }
  }

  set path "[GetPathFromNum $num]"
  if {$type < 0} {
    Toplevel $path -class tkirc -takefocus 0
    wm geometry $path [set geometry$num]
    wm protocol $path WM_DELETE_WINDOW "CloseMainWindow $num"
    bind $path <Map> "set win($num,touched) 0 ; UpdateTitle $num"

    foreach x "c o d v e w i b k q y t" {
	bind $path <Alt-$x> "HandleKey $num $x ; break"
	bind $path <Meta-$x> "HandleKey $num $x ; break"
    }
    bind all <Tab> ""
    bind all <Alt-Tab> ""
    bind all <Meta-Tab> ""

    InitMenu $num
    InitCmdLine $num

    # body
    set f $path.body ; Frame $f
    pack $f -expand true -fill both
    Frame $f.left -bd 1 -relief sunken
    pack $f.left -side left -expand true -fill both

    if {[string compare "" "$normal_style"] == 0} {
      listview $f.left.traffic
    } else {
      eval listview $f.left.traffic "$normal_style"
    }
    pack $f.left.traffic -expand true -fill both -padx 0 -ipadx 0
    bind $f.left.traffic.text <Button-2> "[subst -nocommands {if {![catch {selection get}]} {$path.cmdline insert insert \"\[selection get\]\"}}]"

    bind $path <Shift-Prior> "$f.left.traffic.text yview 0"
    bind $path <Shift-Next> "$f.left.traffic.text yview end"
    bind $path <Prior> "TextPageUp $f.left.traffic.text"
    bind $path <Next> "TextPageDown $f.left.traffic.text"

    InitTopic $num
    InitUserList $num
    ConfigureStyles $num
  }
  RefreshMenu $num
  AddOrRemoveCmdLine $num
  AddOrRemoveUserList $num
  AddOrRemoveTopic $num

  UpdateInfos $num
  return $num
}

proc Disconnect { } {
  global chan win server nickname lastnickname messagewindow
  global startup notified crapwindow

  if {"$server" != ""} {
    ExecOnCommands disconnect window $crapwindow

    set tmp_names ""
    set tmp_windows ""
    foreach x "$chan(list)" {
      lappend tmp_names "$chan($x)"
      lappend tmp_windows "$chan($x,window)"
      DeleteChannel $x
    }
    set chan(tojoin) "$tmp_names"
    set win(tojoin) "$tmp_windows"

    set server ""
    set lastnickname ""
    set startup 1
    InitUserModes
    UpdateAllInfos

    ExecOnCommands signoff window "$messagewindow" nick "$nickname" address "[AddressOfNick "$nickname"]" message ""
  }
}

proc HandleFakeNetjoin {time} {
  global pjoin margin raw chan linetype

  if {$pjoin(state) && $time == $pjoin(time)} {
    # Die folgende Zeile wurde nur zur Vorsicht eingefügt.
    set pjoin(state) 0
    set linetype 0

    # Die Anzeige des möglichen Netjoins muß noch nachgeliefert werden.
    set original "$raw(line)"

    for {set i 0} {$i < [llength "$pjoin(channels)"]} {incr i} {
      set cnum [lindex "$pjoin(channels)" $i]
      if {[info exists chan($cnum,window)]} {
        set j $chan($cnum,window)
	if {$j != -1} {
  	  global hide_joins$j
	  if {[set hide_joins$j] == 0} {
	    set raw(line) "$pjoin(nick)!$pjoin(address) JOIN :$chan($cnum)"
	    set margin(text) "join"
	    print2channels "$cnum" "*** $pjoin(nick) ($pjoin(address)) has joined channel $chan($cnum)[lindex "$pjoin(modetexts)" $i]\x0f\x0f"
	  }
	}
      }
    }

    set raw(line) "$original"
  }
}

proc HandleNetjoins {cnum nick address modetext} {
  global join pjoin chan margin split psplit

#  if {$psplit(state) && [strcmp "$psplit(nick)" "$nick"] == 0} {
#    HandleFakeNetsplit $psplit(time)
#  }

  set thistime [clock seconds]

  # Wurde dieser User von einem Netsplit erfaßt?
  for {set i $split(count)} {$i > 0} {set i [expr $i-1]} {
    set splitnum [expr $i-1]
    if {[expr $thistime - $split($splitnum,time)] < 1800} {
      set j [lsearch -exact "$split($splitnum,nicks)" "$nick"]
      if {$j != -1} {
	if {[lsearch -exact "$split($splitnum,channels)" "$cnum"] != -1} {
	  # Nick und Kanal stimmen. ==> User gehört möglicherweise zum
	  # Netsplit $num.
	  set message "$split($splitnum,message)"
	  break
	}
      }
    }
  }
  if {$i == 0} {
    # Dieser Nick ist nicht von einem Netsplit erfaßt worden. Der User
    # kann aber mit einem Netjoin aufgetaucht sein. Aus diesem Grund wird
    # pjoin(state) nicht angerührt.
    TakeOverTest $cnum "$address"

    set j $chan($cnum,window)
    if {$j != -1} {
      global hide_joins$j
      if {[set hide_joins$j] == 0} {
        set margin(text) "join"
        print2channels $cnum "*** $nick ($address) has joined channel $chan($cnum)$modetext\x0f\x0f"
      }
    }
  } else {
    if {$join(count) > 0} {
      # Es gab bereits Netjoins, die erkannt wurden.
      set last [expr $join(count)-1]

      for {set i $last} {$i >= 0} {set i [expr $i-1]} {
	if {[expr $thistime-$join($i,time)] < 90} {
	  if {$join($i,splitnum) == $splitnum} {
	    break
	  }
	} else {
	  set i -1
	  break
	}
      }
      if {$i >= 0} {
        # Dieser User muß zum letzten Netjoin zugeordnet werden.
        lappend join($i,nicks) "$nick"
        lappend join($i,addresses) "$address"
        lappend join($i,channels) $cnum

        if {[lsearch -exact "$join($i,channels)" $cnum] == -1} {
          # Der Netjoin wurde in diesem Kanal noch nicht angezeigt.
          set time "[clock format $join($i,time) -format "%H:%M:%S"]"
          set margin(text) "netjoin"
          belated2channels "$cnum" "$join($i,channels)" "××× Netjoin at $time ($message)"
        }
        return
      }
    }

    if {$pjoin(state)} {
      if {$splitnum == $pjoin(splitnum)} {
        if {[string compare "$nick" "$pjoin(nick)"] != 0} {
          # Der Netjoin wurde bestätigt.
          set num $join(count)
          incr join(count)
          set join($num,time) $pjoin(time)
          set join($num,splitnum) "$pjoin(splitnum)"
          set join($num,channels) "[concat $pjoin(channels) $cnum]"
          set join($num,nicks) "[concat $pjoin(nick) $nick]"
          set join($num,addresses) "[concat $pjoin(address) $address]"
          set join($num,modetexts) "[concat $pjoin(modetexts) "$modetext"]"
          set pjoin(state) 0

	  # Der Netjoin wird angezeigt.
          set margin(text) "netjoin"
          print2channels "$join($num,channels)" "××× Netjoin at [clock format $join($num,time) -format "%H:%M:%S"] ($message)"
        } else {
          # Der gleiche Nick wie eben!
          lappend pjoin(channels) $cnum
	  lappend pjoin(modetexts) "$modetext"
        }
        return
      } else {
        HandleFakeNetjoin $pjoin(time)
      }
    }

    # Der aktuelle Join muß als möglicher Netjoin eingestuft
    # werden und wird noch später behandelt.
    set pjoin(time) $thistime
    set pjoin(splitnum) $splitnum
    set pjoin(nick) "$nick"
    set pjoin(channels) "[list $cnum]"
    set pjoin(address) "$address"
    set pjoin(modetexts) "[list "$modetext"]"
    set pjoin(state) 1

    after 4200 HandleFakeNetjoin $thistime
  }
}

proc HandleFakeNetsplit {time} {
  global psplit margin raw chan linetype

  if {$psplit(state) && $time == $psplit(time)} {
    # Die folgende Zeile wurde nur zur Vorsicht eingefügt.
    set psplit(state) 0
    set linetype 0

    # Die Anzeige des möglichen Netsplits muß noch nachgeliefert werden.
    set tmp ""
    foreach x "$psplit(channels)" {
      if {[info exists chan($x,window)]} {
	set i $chan($x,window)
	if {$i != -1} {
	  global hide_signoffs$i
	  if {[set hide_signoffs$i] == 0} {
	    lappend tmp $x
	  }
	}
      }
    }
    if {"$tmp" != ""} {
      set original "$raw(line)"
      set raw(line) "$psplit(nick)!$psplit(address) QUIT :$psplit(message)"

      set margin(text) "signoff"
      print2channels "$tmp" "*** $psplit(nick) has signed off ($psplit(message)\x0f)\x0f\x0f"

      set raw(line) "$original"
    }
  }
}

proc HandleNetsplits {nick address message} {
  global split psplit chan margin join pjoin

#  if {$pjoin(state) && [strcmp "$pjoin(nick)" "$nick"] == 0} {
#    HandleFakeNetjoin $pjoin(time)
#  }

  set thistime [clock seconds]
  set channels ""
  foreach cnum "$chan(list)" {
    set i [UserNumOfChannel $cnum "$nick"]
    if {$i != -1} {
      # Der Benutzer wird gelöscht, ohne eine Ausgabe zu machen.
      RemoveChannelUser $cnum $i "$nick"
      # Die Kanalnummern werden evtl. noch gebraucht.
      lappend channels $cnum
    }
  }

  if {$split(count) > 0} {
    # Es gab bereits Netsplits, die erkannt wurden.
    set last [expr $split(count)-1]
    set i $last
    # Evtl. müssen hier noch weitere Netsplits berücksichtigt werden.

    if {[expr $thistime-$split($i,time)] < 90 \
     && [string compare "$split($i,message)" "$message"] == 0 \
     && [lsearch -exact "$split($i,nicks)" "$nick"] == -1} {
      # Dieser User muß dem Netsplit $i zugeordnet werden.
      lappend split($i,nicks) "$nick"
      lappend split($i,addresses) "$address"

      # Es muß sichergestellt werden, daß der Netsplit in allen
      # relevanten Kanälen dargestellt wird.
      set tmp ""
      foreach cnum "$channels" {
        if {[lsearch -exact "$split($i,channels)" $cnum] == -1} {
	  lappend tmp $cnum
	}
      }
      if {"$tmp" != ""} {
        set time "[clock format $split($i,time) -format "%H:%M:%S"]"
        set margin(text) "netsplit"
        belated2channels "$tmp" "$split($i,channels)" "××× Netsplit at $time ($message)"
	set split($i,channels) "[concat $split($i,channels) $tmp]"
      }
      return
    }
  }

  if {$psplit(state)} {
    if {[string compare "$message" "$psplit(message)"] == 0} {
      # Der Netsplit wurde bestätigt.
      set num $split(count)
      incr split(count)
      set split($num,time) $psplit(time)
      set split($num,message) "$psplit(message)"
      set split($num,channels) "[concat $psplit(channels) $channels]"
      set split($num,nicks) "[concat $psplit(nick) $nick]"
      set split($num,addresses) "[concat $psplit(address) $address]"
      set psplit(state) 0

      # Der Netsplit wird angezeigt.
      set time "[clock format $split($num,time) -format "%H:%M:%S"]"
      set margin(text) "netsplit"
      print2channels "$split($num,channels)" "××× Netsplit at $time ($message)"
      return
    } else {
      HandleFakeNetsplit $psplit(time)
    }
  }

  # Der aktuelle Signoff muß als möglicher Netsplit eingestuft
  # werden und wird noch später behandelt.
  set psplit(time) $thistime
  set psplit(message) "$message"
  set psplit(nick) "$nick"
  set psplit(address) "$address"
  set psplit(channels) "$channels"
  set psplit(state) 1

  after 4200 HandleFakeNetsplit $thistime
}

proc StripAddressPrefix {address} {
  if [regexp -- {^(\~|\+|\^|=|-).*} "$address"] {
    return "[string range "$address" 1 end]"
  }
  return "$address"
}

proc TrimNick {user} {
  return "[string trimleft "$user" "@+"]"
}

proc AddChannelUser {cnum nick address v o} {
  global chan

  set prefix ""
  if {$o} {
    set prefix "@"
    incr chan($cnum,mode_o)
  } elseif {$v} {
    set prefix "+"
    incr chan($cnum,mode_v)
  }
  set chan($cnum,nicks) "[linsert "$chan($cnum,nicks)" 0 "$nick"]"
  set chan($cnum,names) "[linsert "$chan($cnum,names)" 0 "$prefix$nick"]"
  set chan($cnum,vlist) "[linsert "$chan($cnum,vlist)" 0 "$v"]"
  set chan($cnum,olist) "[linsert "$chan($cnum,olist)" 0 "$o"]"
  set chan($cnum,addresses) "[linsert "$chan($cnum,addresses)" 0 "[StripAddressPrefix "$address"]"]"
  set chan($cnum,jointimes) "[linsert "$chan($cnum,jointimes)" 0 "[clock seconds]"]"
  lappend chan($cnum,cnicks) "$nick"
  lappend chan($cnum,ctimes) 0

  set wnum "$chan($cnum,window)"
  if {$wnum != -1} {
    if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
      InsertToUserList $wnum $cnum "$prefix$nick"
    }
  }
}

proc RemoveChannelUser {cnum unum nick} {
  global chan wcnicklist wcaddresslist

  set i [lsearch -exact "$chan($cnum,cnicks)" "$nick"]
  if {$i != -1} {
    set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i]"
    set chan($cnum,ctimes) "[lreplace "$chan($cnum,ctimes)" $i $i]"
  }

  set i $unum
  if {[llength "$wcnicklist"] > 10} {
    set wcnicklist "[lrange "$wcnicklist" 0 9]"
    set wcaddresslist "[lrange "$wcaddresslist" 0 9]"
  }
  set wcnicklist "[linsert "$wcnicklist" 0 "$nick"]"
  set wcaddresslist "[linsert "$wcaddresslist" 0 "[lindex "$chan($cnum,addresses)" $i]"]"

  if {[lindex "$chan($cnum,olist)" $i]} {
    set chan($cnum,mode_o) [expr $chan($cnum,mode_o)-1]
  } elseif {[lindex "$chan($cnum,vlist)" $i]} {
    set chan($cnum,mode_v) [expr $chan($cnum,mode_v)-1]
  }
  set address "[lindex "$chan($cnum,addresses)" $i]"
  set chan($cnum,nicks) "[lreplace "$chan($cnum,nicks)" $i $i]"
  set chan($cnum,names) "[lreplace "$chan($cnum,names)" $i $i]"
  set chan($cnum,vlist) "[lreplace "$chan($cnum,vlist)" $i $i]"
  set chan($cnum,olist) "[lreplace "$chan($cnum,olist)" $i $i]"
  set chan($cnum,addresses) "[lreplace "$chan($cnum,addresses)" $i $i]"
  set chan($cnum,jointimes) "[lreplace "$chan($cnum,jointimes)" $i $i]"
  KeepUser $nick $address

  global takeover
  set j [lsearch -exact "$chan($cnum,addresses)" "$address"]
  if {$j == -1} {
    set j [lsearch -exact "$takeover(tries)" "$cnum $address"]
    if {$j != -1} {
      set takeover(tries) "[lreplace "$takeover(tries)" $j $j]"
    }
  }

  set wnum "$chan($cnum,window)"
  if {$wnum != -1} {
    if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
      DeleteFromUserList $wnum $cnum "$nick"
    }
  }
}

proc RenameChannelUser {old new} {
  global chan win margin msghistory

  # Ggf. werden die Query-Einträge geändert.
  foreach wnum "$win(list)" {
    if {[strcmp "$old" "$win($wnum,query)"] == 0} {
      set win($wnum,query) "$new"
      UpdateTitle $wnum
    }
  }

  # Befindet sich der Nick in der Message-History?
  set i [lsearch -exact "$msghistory" "$old"]
  if {$i != -1} {
    set msghistory "[lreplace "$msghistory" $i $i "$new"]"
  }

  set channels ""
  foreach cnum "$chan(list)" {
    set i [lsearch -exact "$chan($cnum,cnicks)" "$old"]
    if {$i != -1} {
      set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i "$new"]"
    }

    set i [UserNumOfChannel $cnum "$old"]
    if {$i != -1} {
      set chan($cnum,nicks) "[lreplace "$chan($cnum,nicks)" $i $i "$new"]"

      if {[lindex "$chan($cnum,olist)" $i]} {
	set prefix "@"
      } elseif {[lindex "$chan($cnum,vlist)" $i]} {
	set prefix "+"
      } else {
	set prefix ""
      }
      set chan($cnum,names) "[lreplace "$chan($cnum,names)" $i $i "$prefix$new"]"

      set num $chan($cnum,window)
      if {$num != -1} {
	if {[strcmp "[GetActual $num]" "$chan($cnum)"] == 0} {
	  set path "[GetPathFromNum $num]"
	  set selected [$path.body.right.list.users selection includes $i]
	  DeleteFromUserList $num $cnum "$old"
	  set j [InsertToUserList $num $cnum "$prefix$new"]
	  if {$selected} {
	    $path.body.right.list.users selection set $i
	  }
	}
	lappend channels $cnum
      }
    }
  }
  set margin(text) "nick"
  print2channels "$channels" "*** $old is now known as $new\x0f\x0f"
}

proc ChannelUserOp {cnum user plus} {
  global chan win

  set i [UserNumOfChannel $cnum "$user"]
  if {$i != -1 && [expr [lindex "$chan($cnum,olist)" $i] + $plus] == 1} {
    # Der User existiert, und das Vorzeichen hat sich geändert.
    if {$plus} {
      incr chan($cnum,mode_o)
      if {[lindex "$chan($cnum,vlist)" $i]} {
	set chan($cnum,mode_v) [expr $chan($cnum,mode_v)-1]
      }
    } else {
      if {[lindex "$chan($cnum,vlist)" $i]} {
	incr chan($cnum,mode_v)
      }
      set chan($cnum,mode_o) [expr $chan($cnum,mode_o)-1]
    }
    set chan($cnum,olist) "[lreplace "$chan($cnum,olist)" $i $i "$plus"]"
    
    if {$plus} {
      set prefix "@"
    } elseif {[hasVoiceOnChannel $cnum "$user"]} {
      set prefix "+"
    } else {
      set prefix ""
    }
    set chan($cnum,names) "[lreplace "$chan($cnum,names)" $i $i "$prefix$user"]"
    
    set wnum $chan($cnum,window)
    if {$wnum != -1} {
      if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
	set path "[GetPathFromNum $wnum]"
	set selected [$path.body.right.list.users selection includes $i]
	DeleteFromUserList $wnum $cnum "$user"
	set j [InsertToUserList $wnum $cnum "$prefix$user"]
	if {$selected} {
	  $path.body.right.list.users selection set $i
	}
      }
    }
  }
}

proc ChannelUserVoice {cnum user plus} {
  global chan win

  set i [UserNumOfChannel $cnum "$user"]
  if {$i != -1 && [expr [lindex "$chan($cnum,vlist)" $i] + $plus] == 1} {
    # Der User existiert, und das Vorzeichen hat sich geändert.
    if {[lindex "$chan($cnum,olist)" $i] == 0} {
      if {$plus} {
	incr chan($cnum,mode_v)
      } else {
	set chan($cnum,mode_v) [expr $chan($cnum,mode_v)-1]
      }
    }
    set chan($cnum,vlist) "[lreplace "$chan($cnum,vlist)" $i $i "$plus"]"

    if {[lindex "$chan($cnum,olist)" $i]} {
      set prefix "@"
    } elseif {$plus} {
      set prefix "+"
    } else {
      set prefix ""
    }
    set chan($cnum,names) "[lreplace "$chan($cnum,names)" $i $i "$prefix$user"]"

    set wnum $chan($cnum,window)
    if {$wnum != -1} {
      if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
	set path "[GetPathFromNum $wnum]"
	set selected [$path.body.right.list.users selection includes $i]
	DeleteFromUserList $wnum $cnum "$user"
	InsertToUserList $wnum $cnum "$prefix$user"
	if {$selected} {
	  $path.body.right.list.users selection set $i
	}
      }
    }
  }
}

proc UnbanChannelUser {cnum address} {
  global chan

  set i [lsearch -exact "$chan($cnum,banpatterns)" "$address"]
  if {$i != -1} {
    set chan($cnum,banpatterns) "[lreplace "$chan($cnum,banpatterns)" $i $i]"
    set chan($cnum,bantimes) "[lreplace "$chan($cnum,bantimes)" $i $i]"
    set chan($cnum,banusers) "[lreplace "$chan($cnum,banusers)" $i $i]"
    set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $i $i]"
    set chan($cnum,mode_b) [expr $chan($cnum,mode_b)-1]
  }
  if {[winfo exists .banlist]} {
    global banlist bancomments
    if {[strcmp "$banlist(channel)" "$chan($cnum)"] == 0} {
      set i [lsearch -exact "$banlist(new)" "$address"]
      if {$i != -1} {
        MultiListbox_Delete .banlist.list 2 $i
        set banlist(new) "[lreplace "$banlist(new)" $i $i]"
        set bancomments(new) "[lreplace "$bancomments(new)" $i $i]"
      }
      set i [lsearch -exact "$banlist(old)" "$address"]
      if {$i != -1} {
        set banlist(old) "[lreplace "$banlist(old)" $i $i]"
        set bancomments(old) "[lreplace "$bancomments(old)" $i $i]"
      }
    }
  }
}

proc BanChannelUser {cnum address user} {
  global chan

  if {$cnum != -1} {
    set i [lsearch -exact "$chan($cnum,banpatterns)" "$address"]
    if {$i == -1} {
      lappend chan($cnum,banpatterns) "$address"
      if {[string length "$user"]} {
        lappend chan($cnum,banusers) "$user"
	lappend chan($cnum,bantimes) "[clock seconds]"
      } else {
        lappend chan($cnum,banusers) "!"
        lappend chan($cnum,bantimes) "0"
      }
      lappend chan($cnum,bancomments) ""
      incr chan($cnum,mode_b)
    }
  }
  if {[winfo exists .banlist]} {
    global banlist bancomments
    if {[strcmp "$banlist(channel)" "$chan($cnum)"] == 0} {
      if {[lsearch -exact "$banlist(new)" "$address"] == -1} {
        MultiListbox_Insert .banlist.list 2 end "$address"
        lappend banlist(new) "$address"
        lappend bancomments(new) ""
      }
      if {[lsearch -exact "$banlist(old)" "$address"] == -1} {
        lappend banlist(old) "$address"
        lappend bancomments(old) ""
      }
    }
  }
}

#################
#  ONLINE HELP  #
#################

global help_choices help_header help_commands help_texts

set help_choices {
***  additional choices:
bannick      bancomment   baninfos     chat         close
closecraplog closelog     closelogall  closemsglog  craplog      
dchat        exchange     loadbaninfos log          logall       
logs         msgids       msglog       newwin       notifies     
savebaninfos savebuffer   search       splits       takeovers    
urls         wjoin        
}

set help_header {
! Additional tkirc command. Copyright (C) 1996-98  Andreas Gelhausen,
! <atte@gecko.North.DE>. See the README file of tkirc for more information.
!
}

set help_commands {
  "bannick"      "bancomment"   "baninfos"     "chat"         "clear"        
  "clearall"     "close"        "closecraplog" "closelog"     "closelogall"  
  "closemsglog"  "craplog"      "dchat"        "exchange"     "loadbaninfos" 
  "log"          "logall"       "logs"         "msgids"       "msglog"       
  "newwin"       "notifies"     "savebaninfos" "savebuffer"   "search"       
  "splits"       "takeovers"    "urls"         "wjoin"        
}

set help_texts {
"Usage: \x02\BANNICK\x02 <nick>
  This command opens a new window you can select a banpattern with to ban
  user <nick> from the actual channel."

"Usage: \x02\BANCOMMENT\x02 <channel> (<number>|<pattern>) <comment>
  This command allows you to set a comment to a ban of channel <channel>.
  <number> or <pattern> selects the certain ban of <channel>'s banlist.
See also:
  \x1f\BANINFOS\x1f"

"Usage: \x02\BANINFOS\x02 <channel>
  This displays more informations of <channel>'s bans than:
   \x1f\MODE\x1f <channel> b
See also:
  \x1f\BANCOMMENT\x1f
  \x1f\MODE\x1f"

"Usage: \x02\CHAT\x02 <nick1>\[,<nick2>\[...\]\]
  A new window will be opened for a private conversation with all users
  specified through <nick1>\[,<nick2>\[...\]\].
See also:
  \x1f\QUERY\x1f"

"Usage: \x02\CLEAR\x02
  The window you typed this command in will be cleared."

"Usage: \x02\CLEARALL\x02
  All windows will be cleared."

"Usage: \x02\CLOSE\x02
  The window you typed this command in will be closed."

"Usage: \x02\CLOSECRAPLOG\x02
  The craplog will be closed.
See also:
  \x1f\CRAPLOG\x1f"

"Usage: \x02\CLOSELOG\x02 (<channel>|<nick>)
  The logfile you opened for conversation with <channel> or <nick> will be
  closed.
See also:
  \x1f\LOG\x1f"

"Usage: \x02\CLOSELOGALL\x02
  The logfile for all kinds of irc-traffic will be closed.
See also:
  \x1f\LOGALL\x1f"

"Usage: \x02\CLOSEMSGLOG\x02
  The logfile for whole traffic of private messages and notices will be closed.
See also:
  \x1f\MSGLOG\x1f"

"Usage: \x02\CRAPLOG\x02 <filename> \[-d\]
   This command allows you to open file <filename> to log all kinds of crap
   in. Select option '-d' for additional time stamps.
See also:
  \x1f\CLOSECRAPLOG\x1f"

"Usage: \x02\DCHAT\x02 <nick>
  A new window will be opened for a direct client to client (DCC) conversation
  with user <nick>.
See also:
  \x1f\DCC CHAT\x1f"

"Usage: \x02\EXCHANGE\x02 <ircpath> \[<nick> \[<server>\]\]
  This command allows you to exchange the ircII command, tkirc is just
  running with. If you are able to use the remote shell command 'rsh',
  you can start your ircII on another host. For example:
   /exchange \"rsh <host> -l <your_login> <ircpath_on_that_host>\" <nickname>

  This command is also available as shell option '-x'.
  Try to start from shell:
   ~> tkirc -x \"rsh <host> -l <your_login> <ircpath_on_that_host>\"

See also:
  Manual of command 'rsh'"

"Usage: \x02\LOADBANINFOS\x02 <channel> <filename>
  This tries to update baninfos of channel <channel> with the includes of
  file <filename>.
See also:
  \x1f\BANINFOS\x1f
  \x1f\SAVEBANINFOS\x1f"

"Usage: \x02\LOG\x02 (<channel>|<nick>) <filename> \[-d\] \[-r\]
  The logfile <filename> will be opened for the traffic of channel <channel>
  or for a private chat with user <nick>. Select option '-d' for additional
  time stamps or '-r' to log the messages how you receive them from your
  IRC-server.
See also:
  \x1f\CLOSELOG\x1f"

"Usage: \x02\LOGALL\x02 <filename> \[-d\] \[-r\]
  The file <filename> will be opened to log the whole irc-traffic in. Select
  option '-d' for additional time stamps or '-r' for raw messages.
See also:
  \x1f\CLOSELOGALL\x1f"

"Usage: \x02\LOGS\x02
  A list of all opened logfiles will be displayed.
See also:
  \x1f\CRAPLOG\x1f
  \x1f\LOG\x1f
  \x1f\LOGALL\x1f
  \x1f\MSGLOG\x1f"

"Usage: \x02\MSGIDS\x02
  A window will be opened that shows you a list of all message IDs 
  detected in your tkirc session."

"Usage: \x02\MSGLOG\x02 <filename> \[-d\] \[-r\]
   This command allows you to open file <filename> to log all private
   messages and notices in. Select option '-d' for additional time stamps
   or '-r' for raw messages.
See also:
  \x1f\CLOSEMSGLOG\x1f"

"Usage: \x02\NEWWIN\x02
  This command opens a new traffic window.
See also:
  \x1f\CLOSE\x1f"

"Usage: \x02\NOTIFIES\x02
  This command opens a window to display all notified nicknames.
See also:
  Examples for variable 'notifies' in file 'tkircrc-example'
  \x1f\NOTIFY\x1f"

"Usage: \x02\SAVEBANINFOS\x02 <channel> <filename>
  Tries to save baninfos of channel <channel> into file <filename>. You
  can load these baninfos, when you rejoin channel <channel>.
See also:
  \x1f\LOADBANINFOS\x1f"

"Usage: \x02\SAVEBUFFER\x02 <tofile>
  The buffer of the current window will be saved into file <tofile>."

"Usage: \x02\SEARCH\x02 <text>
  This command highlights all occurrences of '<text>' in the text field
  and jumps to it/the next."

"Usage: \x02\SPLITS\x02
  Shows you a list of all detected netsplits."

"Usage: \x02\TAKEOVERS\x02
  Shows you a list of all detected (possible) channel takeovers."

"Usage: \x02\URLS\x02
  A window will be opened that shows you a list of all URLs detected in
  your tkirc session."

"Usage: \x02\WJOIN\x02 <channel1>\[,<channel2>\[,...\]\]
  This command opens a new window for each channel and joins all of
  them."
}

#####################
#  TRAFFIC PARSING  #
#####################

proc parsecl {num line} {
  global escape_sign
  # change shortcuts to control chars if necessary
  set esc [string first "$escape_sign" "$line"]
  if {$esc != -1} {
    set newline "[string range "$line" 0 [expr $esc-1]]"
    for {set i $esc} {$i < [string length "$line"]} {incr i} {
      set char "[string index "$line" $i]"
      if {"$char" == "$escape_sign"} {
	switch -- "[string index "$line" [expr $i+1]]" {
	  "b" { append newline "\x02" ; incr i }
	  "r" { append newline "\x16" ; incr i }
	  "s" { append newline "\x03" ; incr i }
	  "u" { append newline "\x1f" ; incr i }
	  "o" { append newline "\x0f" ; incr i }
	  "g" { append newline "\a" ; incr i }
	  "x" {
	    set z ""
	    set j $i
	    for {set k 0} {$k < 2} {incr k} {
	      set char2 "[string index "$line" [expr $k + $j + 2]]"
	      if {[regexp -- {[0-9a-fA-F]} "$char2"]} {
		append z "$char2"
		incr i
	      } else {
		break
	      }
	    }
	    if {[string length "$z"]} {
	      eval append newline \\x$z
	    }
	    incr i
	  }
	  default {
	    if {"$escape_sign" == "[string index "$line" [expr $i+1]]"} {
	      append newline "$escape_sign" ; incr i
	    }
	  }
	}
      } else {
	append newline "$char"
      }
    }
    set line "$newline"
  }
  parsein $num "$line"
}

proc parsein {num line} {
  global chan win crapwindow nickname margin
  set command ""
  set margin(text) ""

  if {[string first " \:file " "[string tolower "$line "]"] != -1} {
    FileRequest " Please select the file to execute command \n '$line'!" "Continue" "send2tkirc $num \"[expandescape "[expand "$line"]"]\"" "" "" 0
    return ""
  }

  # commands
  if {"[string index "$line" 0]" == "/"} {
    set list "[line2list "$line"]"
    switch -regexp -- "[string tolower "[lindex "$list" 0]"]" {
      "^/away$" {
	global away send_away_notice san automatic_away
	set automatic_away 0
	if {$send_away_notice == 1} {
	  if {[llength "$list"] > 1} {
	    set san(nicks) ""
	    set san(times) ""
	    set san(message) "[cutwords "$line" 1]"
	    set away " (away)"
	    set margin(text) "away"
	    print2crap "*** You have been marked as being away"
	  } else {
	    set away ""
	    set margin(text) "away"
	    print2crap "*** You are no longer marked as being away"
	  }
	  UpdateAllTitles
	  return ""
	}
      }
      "^/beep$" {
	beep
	return ""
      }
      "^/bannick$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP BANNICK"
	} else {
	  NickBan "[lindex "$list" 1]" "[lindex "$list" 2]" $num
	}
	return ""
      }
      "^/bancomment$" {
	set channel "[lindex "$list" 1]"
        if {[llength "$list"] < 4} {
	  print2crap "××× Wrong number of parameters. Try /HELP BANCOMMENT"
	} else {
  	  set channel "[lindex "$list" 1]"
	  if {[strcmp "$channel" "*"] == 0} {
	    if {[strcmp "[GetActual $num]" "*"]} {
	      set channel "[GetActual $num]"
	    } else {
	      set margin(text) "error"
	      print2text $num "××× You have no channel joined in this window"
	      return ""
	    }
	  }
	  set cnum [GetChannelNumber "$channel"]
	  if {[info exists chan($cnum,bancomments)]} {
	    set len [lLength "$chan($cnum,bancomments)"]
	    set comnum "[lindex "$list" 2]"
	    for {set i 1} {$i <= [lLength "$chan($cnum,bancomments)"]} {incr i} {
	      set j [expr $i-1]
	      if {[strcmp "$i" "$comnum"] == 0} {
		set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $j $j "[cutwords "$line" 3]"]"
		set margin(text) "note"
		print2crap "××× $channel: Comment of ban number $i changed"
	      } elseif {[strcmp "[lIndex "$chan($cnum,banpatterns)" $j]" "$comnum"] == 0} {
		set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $j $j "[cutwords "$line" 3]"]"
		set margin(text) "note"
		print2crap "××× $channel: Comment of ban number $i changed"
	      }
	    }
	  } else {
	    set margin(text) "failure"
	    print2crap "××× There are no baninfos for channel $channel"
	  }
	}
	return ""
      }
      "^/baninfos$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP BANINFOS"
	} else {
	  set channel "[lindex "$list" 1]"
	  if {[strcmp "$channel" "*"] == 0} {
	    if {[strcmp "[GetActual $num]" "*"]} {
	      set channel "[GetActual $num]"
	    } else {
	      set margin(text) "error"
	      print2text $num "××× You have no channel joined in this window"
	      return ""
	    }
	  }
	  set cnum [GetChannelNumber "$channel"]
	  if {[info exists chan($cnum,banpatterns)]} {
	    set len [lLength "$chan($cnum,banpatterns)"]
	    if {$len} {
	      print2crap "××× Baninfos of channel $channel:"
	      for {set i 0} {$i < $len} {incr i} {
		set address "[lindex "$chan($cnum,banusers)" $i]"
		set pattern "[lindex "$chan($cnum,banpatterns)" $i]"
		set comment "[lindex "$chan($cnum,bancomments)" $i]"
		set time "[lindex "$chan($cnum,bantimes)" $i]"
		if {"$time" == "0"} {
		  set time "00.00.00  00:00:00"
		} else {
		  set time "[longdate $time]"
		}
		set j [string first "!" "$address"]
		if {$j == 0 || [string length "$address"] < 2} {
		  set user "<unknown>"
		} else {
		  set user "[string range "$address" 0 [expr $j-1]]"
		}
		print2crap "[format "×××  %2d.  $time  %-9s  %s" "[expr $i+1]" "$user" "$pattern  ($comment\x0f)"]"
	      }
	    } else {
	      set margin(text) "failure"
	      print2crap "××× There are no baninfos for channel $channel"
	    }
	  } else {
	    set margin(text) "failure"
	    print2crap "××× There are no baninfos for channel $channel"
	  }
	}
	return ""
      }
      "^/chat$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP CHAT"
	} else {
	  set wnum [MainWindow -2]
	  set win($wnum,query) [lindex "$list" 1]
	  UpdateTitle $wnum
	}
	return ""
      }
      "^/clear$" {
	ClearTraffic $num
	return ""
      }
      "^/clearall$" {
	foreach x "$win(list)" {
	  ClearTraffic $x
	}
	return ""
      }
      "^/close$" {
	CloseMainWindow $num
	return ""
      }
      "^/closecraplog$" {
	send2tkirc $num "/closelog <crap>"
	return ""
      }
      "^/closelog$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP CLOSELOG"
	} else {
	  global log
	  foreach x "$log(list)" {
	    if {[strcmp "[lindex "$list" 1]" "$log($x)"] == 0} {
	      puts $log($x,handle) "Logfile closed at:  [longdate]\n"
	      set filename "$log($x,file)"
	      set source "$log($x)"
	      close $log($x,handle)
	      DeleteLog $x
	      set margin(text) "note"
	      print2crap "××× Logfile '$filename' for $source closed"
	      return ""
	    }
	  }
	  set margin(text) "error"
	  print2crap "××× There's no logfile for '[lindex "$list" 1]'"
	}
	return ""
      }
      "^/closelogall$" {
	send2tkirc $num "/closelog <all>"
	return ""
      }
      "^/closemsglog$" {
	send2tkirc $num "/closelog <messages>"
	return ""
      }
      "^/craplog$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP CRAPLOG"
	} else {
	  send2tkirc $num "/log <crap> [expandescape "[string range "$line" 9 end]"]"
	}
	return ""
      }
      "^/dchat$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP DCHAT"
	} else {
	  set wnum [MainWindow -2]
	  set win($wnum,query) =[lindex "$list" 1]
	  UpdateTitle $wnum
	  send2irc "/dcc chat [lindex "$list" 1]"
	}
	return ""
      }
      "^/echo$" {
        if {[llength "$list"] > 1} {
	  print2text $num "[cutwords "$line" 1]"
	}
	return ""
      }
      "^/exchange$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP EXCHANGE"
	} else {
	  global ircpath ip nickname server
	  Disconnect
	  catch {close $ip} result

	  set ircpath "[lindex "$list" 1]"
	  set arguments "[expand "[lRange "$line" 2 end]"]"
	  set len [lLength "$arguments"]
	  if {$len >= 1} {
	    set nickname "[lindex "$arguments" 0]"
	  }
	  if {$len >= 2} {
	    set server "[lindex "$arguments" 1]"
	  }
	  set margin(text) "note"
	  print2all "××× Exchanging ircII..."

	  if [catch {open "|$ircpath -d -q $arguments" r+} ip] {
	    set margin(text) "error"
	    print2crap "Error executing \"$ircpath -d -q $arguments\" - $ip"
	    set ip ""
	  } else {
	    global startup
	    InitClient
	    if {$startup < 2} {
	      on_ircIIstart
	      set startup 2
	    }

	    fconfigure $ip -blocking 0
	    fileevent $ip readable "irc2text"
	  }
	}
	return ""
      }
      "^/help$" {
	global help_choices help_header help_commands help_texts
	if {[llength "$list"] == 1} {
	  foreach x "[split "[string trim "$help_choices" "\n"]" "\n"]" {
	    print2crap "$x"
	  }
	} elseif {[llength "$list"] == 2} {
	  set len [llength "$help_commands"]
	  if {"[lindex "$list" 1]" == "?"} {
	    foreach x "[split "[string trim "$help_choices" "\n"]" "\n"]" {		      print2crap "$x"
	    }
	  } else {
	    for {set i 0} {$i < $len} {incr i} {
	      if {[string compare "[lindex "$help_commands" $i]" "[string tolower "[lindex "$list" 1]"]"] == 0} {
		print2crap "*** Help on [lindex "$help_commands" $i]"
		foreach x "[split "[string trim "$help_header" "\n"]" "\n"]" {
		  print2crap "$x"
		}
		set len [llength "[lindex "$help_texts" $i]"]
		foreach x "[split "[lindex "$help_texts" $i]" "\n"]" {
		  print2crap "$x"
		}
		return ""
	      }
	    }
	  }
	}
      }
      "^/(j|join)$" {
	foreach x "[split "[lindex "$list" 1]" ","]" {
	  lappend chan(tojoin) "$x"
          lappend win(tojoin) "$num"
	}
      }
      "^/(wj|wjoin)$" {
	if {[llength "$list"] == 1} {
	  print2crap "××× Wrong number of parameters. Try /HELP WJOIN"
	} else {
	  foreach x "[split "[lindex "$list" 1]" ","]" {
	    set wnum [MainWindow -4]
	    lappend chan(tojoin) "$x"
            lappend win(tojoin) "$wnum"
	    send2irc "/join $x"
	  }
	}
	return ""
      }
      "^/kick$" {
	set len [string length "[cutwords "$line" 3]"]
	if {$len > 80} {
          beep
          set margin(text) "error"
          print2text $num "××× Kick-reason has $len chars, but maximum is 80."
	  return ""
        }
      }
      "^/(leave|part)$" {
	# to support leave messages with ircII < 2.9
	if {[llength "$list"] > 2} {
	  set len [string length "[cutwords "$line" 2]"]
	  if {$len > 80} {
            beep
            set margin(text) "error"
            print2text $num "××× Leave-message has $len chars, but maximum is 80."
	    return ""
	  }
	  if {[strcmp "[lindex "$list" 1]" "*"]} {
	    set line "/quote part [lindex "$list" 1] :[cutwords "$line" 2]"
	  } else {
	    set line "/quote part [GetActual $num] :[cutwords "$line" 2]"
	  }
	} elseif {[llength "$list"] == 1} {
	  if {"[GetActual $num]" != "*"} {
	    set line "/leave [GetActual $num]"
	  } else {
	    set margin(text) "error"
	    print2text $num "××× You have no channel joined in this window"
	    return ""
	  }
	}
      }
      "^/list$" {
	if {[llength "$list"] == 1 \
	 || "[lindex "$list" 1]" == "*" && [llength "$chan(list)"] == 0} {
	  set margin(text) "warning"
	  print2text $num "××× If you really want to do that, use '/really list [string trim "[string range "$line" 5 end]" " "]', but this command makes a lot of traffic and it could take a VERY LONG while!"
	  return ""
	}
      }
      "^/loadbaninfos$" {
        if {[llength "$list"] != 3} {
	  print2crap "××× Wrong number of parameters. Try /HELP LOADBANINFOS"
	} else {
	  set channel "[lindex "$list" 1]"
	  set filename "[lindex "$list" 2]"
	  if {[strcmp "$channel" "*"] == 0} {
	    if {[strcmp "[GetActual $num]" "*"]} {
	      set channel "[GetActual $num]"
	    } else {
	      set margin(text) "error"
	      print2text $num "××× You have no channel joined in this window"
	      return ""
	    }
	  }
	  set cnum [GetChannelNumber "$channel"]
	  set file "[OpenFile "$filename" r]"
	  if {[string length "$file"]} {
	    if {[info exists chan($cnum,banpatterns)]} {
	      set len [lLength "$chan($cnum,banpatterns)"]
	      if {$len} {
		set getrc [gets $file infoline]
		set infoline "[expand "$infoline"]"
		if {$getrc < 0 || [strmatch "××× Baninfos of channel *" "$infoline"] == 0} {
		  set margin(text) "error"
		  print2crap "××× File '$filename' doesn't have the right format for baninfos"
		} elseif {[strcmp "$channel" "[lindex "$infoline" 4]"] != 0} {
		  set margin(text) "error"
		  print2crap "××× File '$filename' doesn't have baninfos for channel $channel"
		} else {
		  while {[gets $file infoline] >= 0} {
		    set infoline "[expand "$infoline"]"
		    set date "[lindex "$infoline" 0]"
		    set address "[lindex "$infoline" 1]"
		    set pattern "[lindex "$infoline" 2]"
		    set comment "[reduce "[lrange "$infoline" 3 end]"]"
		    set comment "[string range "$comment" 1 [expr [string length "$comment"]-2]]"
		    for {set i 0} {$i < $len} {incr i} {
		      if {[string compare "$pattern" "[lindex "$chan($cnum,banpatterns)" $i]"] == 0 && "$comment" != ""} {
			if {$date > [lindex "$chan($cnum,bantimes)" $i] || [lindex "$chan($cnum,bantimes)" $i] == 0} {
			  set chan($cnum,banusers) "[lreplace "$chan($cnum,banusers)" $i $i "$address"]"
			  set chan($cnum,banpatterns) "[lreplace "$chan($cnum,banpatterns)" $i $i "$pattern"]"
			  set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $i $i "$comment"]"
			  set chan($cnum,bantimes) "[lreplace "$chan($cnum,bantimes)" $i $i "$date"]"
			  break
			}
		      }
		    }
		  }
		  set margin(text) "note"
		  print2crap "××× Baninfos for channel $channel actualized"		    
		}
	      } else {
		set margin(text) "failure"
		print2crap "××× There are no baninfos for channel $channel"
	      }
	    } else {
	      set margin(text) "failure"
	      print2crap "××× There are no baninfos for channel $channel"
	    }
	    close $file
          }
	}
	return ""
      }
      "^/log$" {
	set len [llength "$list"]
        if {$len < 3 || $len > 5} {
	  print2crap "××× Wrong number of parameters. Try /HELP LOG"
	} else {
	  set dateflag 0 ; set rawflag 0
	  for {set i 3} {$i < $len} {incr i} {
	    if {[strcmp "[lindex "$list" $i]" "-d"] == 0} {
	      set dateflag 1
 	    } elseif {[strcmp "[lindex "$list" $i]" "-r"] == 0} {
	      set rawflag 1
 	    } else {
	      set margin(text) "error"
	      print2crap "××× Wrong option '[lindex "$list" $i]'"
	    }
	  }

	  global log
	  set handle "[OpenFile "[lindex "$list" 2]" a+]"
	  if {[string length "$handle"]} {
	    set num [ProduceLog "[lindex "$list" 1]"]
	    if {$num == -1} {
	      close $handle
	      set margin(text) "error"
	      print2crap "××× There is already a logfile opened for [lindex "$list" 1]"
	      return ""
	    }
	    set log($num,file) "[lindex "$list" 2]"
	    set log($num,handle) "$handle"
	    set log($num,type) [lindex "$list" 1]
	    set log($num,dateswitch) $dateflag
	    set log($num,rawswitch) $rawflag
	    set log($num,opendate) "[longdate]"
	    puts $handle "\nLogfile opened for [lindex "$list" 1] at:  [longdate]"
	    set margin(text) "note"
	    print2crap "××× Logfile '[lindex "$list" 2]' opened for [lindex "$list" 1]"
	    flush $log($num,handle)
	  }
	}
	return ""
      }
      "^/logall$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP LOGALL"
	} else {
	  send2tkirc $num "/log <all> [expandescape "[string range "$line" 8 end]"]"
	}
	return ""
      }
      "^/logs$" {
	global log
	if {[llength "$log(list)"]} {
	  set i 0
	  print2crap "××× List of actually opened logfiles"
	  foreach x "$log(list)" {
	    print2crap "[format "×××  %2d.  %s  %-16s  %s" "[expr $i+1]" "$log($x,opendate)" "$log($x)" "$log($x,file)"]"
	    incr i
	  }
	} else {
	  set margin(text) "note"
	  print2crap "××× No opened logfiles found"
	}
	return ""
      }
      "^/me$" {
 	if {"$win($num,query)" != ""} {
	  if {"[string index "$win($num,query)" 0]" == "="} {
	    return "/msg $win($num,query) $nickname [cutwords "$line" 1]"
	  } else {
	    return "/describe $win($num,query) [cutwords "$line" 1]"
	  }
	}
	if {"[GetActual $num]" != "*"} {
          return "/describe [GetActual $num] [cutwords "$line" 1]"
	}
	set margin(text) "error"
	print2text $num "××× No target, neither channel nor query in this window"
	return ""
      }
      "^/mode$" {
	return "/mode [string trimleft "[cutwords "$line" 1]" " "]"
      }
      "^/msgids$" {
	MsgIDWindow
	return ""
      }
      "^/msglog$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP MSGLOG"
	} else {
	  send2tkirc $num "/log <messages> [expandescape "[string range "$line" 8 end]"]"
	}
	return ""
      }
      "^/names$" {
	if {[llength "$list"] == 1 \
	 || "[lindex "$list" 1]" == "*" && [llength "$chan(list)"] == 0} {
	  set margin(text) "warning"
	  print2text $num "××× If you really want to do that, use '/really names [string trim "[string range "$line" 6 end]" " "]', but this command makes a lot of traffic and it could take a VERY LONG while!"
	  return ""
	}
      }
      "^/newwin$" {
	MainWindow -1
	return ""
      }
      "^/note$" {
        global notify note
        if {[llength "$list"] == 2} {
	  if {[strcmp "update" "[lindex "$list" 1]"] == 0} {
	    set margin(text) "note"
            print2crap {*** updating note's notifies...}

	    set note(status) 1 ; # updating...

	    send2irc {/note rm *!*}
	    MultiListbox_Delete .not.list 2 0 end

	    set unum [llength "$notify(users)"]
	    for {set j 0} {$j < $unum} {incr j} {
	      if {$j >= $note(limit)} {
	        set margin(text) "note"
	        after 2000 "global margin ; set margin(text) \"note\" ; print2crap \"××× Please reduce number of notifies! Maximum is $note(limit).\""
	        break
	      }
	      set uentry "[lindex "$notify(users)" $j]"
	      if {[strcmp "*" "[lindex "$uentry" 1]"]} {
  	        send2irc "/note spy +7 [lindex "$uentry" 0]![lindex "$uentry" 1]"
	      } else {
  	        send2irc "/note spy +7 [lindex "$uentry" 0]!*@*"
	      }
	    }
	    send2irc {/note ls}
	    return ""
	  }
	}
      }
      "^/noteserv$" {
        global notify noteserv
        if {[llength "$list"] == 2} {
	  if {[strcmp "update" "[lindex "$list" 1]"] == 0} {
	    set margin(text) "note"
            print2crap {*** updating noteserv's notifies...}

	    set noteserv(status) 1 ; # updating...

	    send2irc {/noteserv lang raw}
	    send2irc {/noteserv rm *!*}
	    MultiListbox_Delete .not.list 2 0 end

	    set unum [llength "$notify(users)"]
	    for {set j 0} {$j < $unum} {incr j} {
	      if {$j >= $noteserv(limit)} {
	        set margin(text) "note"
	        after 2000 "global margin ; set margin(text) \"noteserv\" ; print2crap \"××× Please reduce number of notifies! Maximum is $noteserv(limit).\""
	        break
	      }
	      set uentry "[lindex "$notify(users)" $j]"
	      if {[strcmp "*" "[lindex "$uentry" 1]"]} {
  	        send2irc "/noteserv notify +0 [lindex "$uentry" 0]![lindex "$uentry" 1]"
	      } else {
  	        send2irc "/noteserv notify +0 [lindex "$uentry" 0]!*@*"
	      }
	    }
	    send2irc {/noteserv ls}
	    return ""
	  } elseif {[strcmp "findall" "[lindex "$list" 1]"] == 0} {
	    set margin(text) "note"
            print2crap {*** executing 'find' to all notifies...}

	    set unum [llength "$notify(users)"]
	    for {set j 0} {$j < $unum} {incr j} {
	      if {$j >= $noteserv(limit)} {
	        set margin(text) "note"
	        after 2000 "global margin ; set margin(text) \"noteserv\" ; print2crap \"××× Please reduce number of notifies! Maximum is $noteserv(limit).\""
	        break
	      }
	      set uentry "[lindex "$notify(users)" $j]"
	      if {[strcmp "*" "[lindex "$uentry" 1]"]} {
  	        send2irc "/noteserv find [lindex "$uentry" 0]![lindex "$uentry" 1]"
	      } else {
  	        send2irc "/noteserv find [lindex "$uentry" 0]!*@*"
	      }
	    }
  	    return ""
	  }
	}
      }
      "^/notifies$" {
	NotifyWindow
	return ""
      }
      "^/query$" {
	set win($num,query) "[lindex "$list" 1]"
	UpdateTitle $num
	return ""
      }
      "^/(bye|exit|quit|sign|signoff)$" {
        if {[llength "$list"] < 2} {
	  Exit ""
	} else {
	  Exit "[cutwords "$line" 1]"
	}
      }
      "^/really$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Usage: /really <command>"
	} else {
	  return "/[cutwords "$line" 1]"
	}
	return ""
      }
      "^/savebaninfos$" {
        if {[llength "$list"] != 3} {
	  print2crap "××× Wrong number of parameters. Try /HELP SAVEBANINFOS"
	} else {
	  set channel "[lindex "$list" 1]"
	  set filename "[lindex "$list" 2]"
	  if {[strcmp "$channel" "*"] == 0} {
	    if {[strcmp "[GetActual $num]" "*"]} {
	      set channel "[GetActual $num]"
	    } else {
	      set margin(text) "error"
	      print2text $num "××× You have no channel joined in this window"
	      return ""
	    }
	  }
	  set cnum [GetChannelNumber "$channel"]
	  set file "[OpenFile "$filename" w]"
	  if {[string length "$file"]} {
	    if {[info exists chan($cnum,banpatterns)]} {
	      puts $file "××× Baninfos of channel $channel"
	      set len [lLength "$chan($cnum,banpatterns)"]
	      for {set i 0} {$i < $len} {incr i} {
		set address "[lindex "$chan($cnum,banusers)" $i]"
		set pattern "[lindex "$chan($cnum,banpatterns)" $i]"
		set comment "[lindex "$chan($cnum,bancomments)" $i]"
		set date "[lindex "$chan($cnum,bantimes)" $i]"
		puts $file "$date $address $pattern ($comment)"
	      }
	      set margin(text) "note"
	      print2crap "××× Baninfos for channel $channel saved into file '$filename'"
	    } else {
	      set margin(text) "failure"
	      print2crap "××× There are no baninfos for channel $channel"
	    }
	    close $file
          }
	}
	return ""
      }
      "^/savebuffer$" {
	if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP SAVEBUFFER"
	} else {
	  SaveBuffer $num "[lindex "$list" 1]"
	}
	return ""
      }
      "^/search$" {
        if {[llength "$list"] < 2} {
	  print2crap "××× Wrong number of parameters. Try /HELP SEARCH"
	} else {
 	  textSearch $num "[cutwords "$line" 1]" search
	}
	return ""
      }
      "^/servers$" {
	ServersWindow
	return ""
      }
      "^/set$" {
        if {[llength "$list"] > 1} {
	  set x [lindex "$list" 1]
	  set value "[cutwords "$line" 2]"
	  if {"[string index "$x" 0]" == "-"} {
	    set state -1
	    set x "[string range "$x" 1 end]"
	  } elseif {"[string index "$x" 0]" == "+"} {
	    set state 1
	    set x "[string range "$x" 1 end]"
	  } else {
	    set state 0
	  }
	  if [strmatch "*(*)" "$x"] {
	    # x is array
	    if [catch {global [string range "$x" 0 [expr [string first "(" "$x"]-1]]} y] {
	      set margin(text) "error"
	      print2crap "××× $y"
	      return ""
	    }
  	  } else {
	    # x is a normal variable
	    if [catch {global $x} y] {
	      set margin(text) "error"
	      print2crap "××× $y"
	      return ""
	    }
	  }
	  if [info exists "$x"] {
	    # Eine Tcl/Tk-Variable dieses Namens existiert.
	    if {$state < 0} {
	      # Der Wert dieser Variablen soll gelöscht werden.
	      if [catch {set $x ""} y] {
		set margin(text) "error"
		print2crap "××× $y "
	      } else {
		print2crap "*** Value of $x set to <EMPTY>"
	      }
	    } else {
	      if {[string length "$value"]} {
	        # Die Variable erhält einen neuen Wert.
		switch -- "$x" {
		  "escape_sign" {
		    if {[string length "$value"] > 1} {
		      set margin(text) "error"
		      print2crap "××× Value for $x is too long"
		      return ""
		    }
		  }
		  "crapwindow" {
		    global win
		    if {[lsearch -exact "$win(list)" "$value"] != -1} {
		      set crapwindow "$value"
		      UpdateAllInfos
		    } else {
		      set margin(text) "error"
		      print2crap "××× window $value does not exist"
		      return ""
		    }
		  }
		  "messagewindow" {
		    global win
		    if {[lsearch -exact "$win(list)" "$value"] != -1} {
		      set messagewindow "$value"
		      UpdateAllInfos
		    } else {
		      set margin(text) "error"
		      print2crap "××× window $value does not exist"
		      return ""
		    }
		  }
		}
		if [catch {set $x "$value"} y] {
		  set margin(text) "error"
		  print2crap "××× $y "
		} else {
		  print2crap "*** Value of $x set to $value"
		}
	      } else {
	        # Der Wert dieser Variablen wird nur abgefragt.
		print2crap "*** Current value of $x is [set $x]"
	      }
	    }
	    return ""
	  } elseif {$state > 0} {
	    # Eine Tcl/Tk-Variable dieses Namens existiert nicht, soll
	    # aber erzeugt werden.
	    if [catch {set $x "$value"} y] {
	      set margin(text) "error"
	      print2crap "××× $y "
	    } else {
	      print2crap "*** Value of $x set to $value"
	    }
	    return ""
	  }
        }
      }
      "^/splits$" {
	global split join starttime
        if {$split(count)} {
	  if {$split(count) < 11} {
 	    print2crap "××× Detected $split(count) netsplit(s) since $starttime:"
	    set from 0
	  } else {
  	    print2crap "××× Last 10 detected netsplits:"
	    set from [expr $split(count)-10]
	  }
	  for {set i $from} {$i < $split(count)} {incr i} {
	    print2crap "[format "×××  %13s  %s  (%s)"  "[expr $i+1].  splitted" "[longdate $split($i,time)]" "$split($i,message)"]"
	    set thisjoins 0
	    if {$join(count)} {
	      for {set j 0} {$j < $join(count)} {incr j} {
	        if {$join($j,splitnum) == $i} {
		  print2crap "[format "×××  %13s  %s" "joined " "[longdate $join($j,time)]"]"
		  incr thisjoins
		}
	      }
	    }
	    if {$thisjoins == 0} {
	      if {[expr [clock seconds]-$split($i,time)] > 1800} {
	        print2crap "×××        timed out after 30 minutes"
	      }
	    }
	  }
	} else {
	  set margin(text) "note"
	  print2crap "××× No netsplits detected since $starttime"
	}
	return ""
      }
      "^/takeovers$" {
	global react_to_takeover takeover starttime chan
	if {$react_to_takeover} {
	  set count [llength "$takeover(times)"]
	  if {$count} {
	    print2crap "××× Detected $count (possible) takeover(s) since $starttime:"
	    for {set i 0} {$i < $count} {incr i} {
	      set x "[lindex "$takeover(tries)" $i]"
	      print2crap "[format "×××  %2s.  %s  %-12s  %s"  "[expr $i+1]" "[longdate [lindex "$takeover(times)" $i]]" "$chan([lindex "$x" 0])" "[lindex "$x" 1]"]"
	    }
	  } else {
	    set margin(text) "note"
	    print2crap "××× No (possible) takeovers detected since $starttime"
	  }
	} else {
	  set margin(text) "note"
	  print2crap "××× tkirc doesn't try to detect takeovers, because value of 'react_to_takeover' is 0."
	}
	return ""
      }
      "^/topic$" {
	set len [string length "[cutwords "$line" 2]"]
	if {$len > 80} {
          beep
          set margin(text) "error"
          print2text $num "××× Topic has $len chars, but maximum is 80."
	  return ""
        }
      }
      "^/unset$" {
        if {[llength "$list"] != 2} {
	  print2crap "××× Usage: /unset <varname>"
	} else {
	  set x [lindex "$list" 1]
	  if [strmatch "*(*)" "$x"] {
	    # x is array
	    global [string range "$x" 0 [expr [string first "(" "$x"]-1]]
  	  } else {
	    # x is a normal variable
	    if [catch {global $x} y] {
	      set margin(text) "error"
	      print2crap "××× $y"
	      return ""
	    }
	  }
	  if [info exists "$x"] {
	    if [catch {unset $x} y] {
	      set margin(text) "error"
	      print2crap "××× $y "
	    } else {
	      print2crap "*** tkirc's variable $x has been unset"
	    }
	  }
        }
	return ""
      }
      "^/urls$" {
	URLWindow
	return ""
      }
      "^/who$" {
	if {[llength "$list"] == 1 \
	 || "[lindex "$list" 1]" == "*" && [llength "$chan(list)"] == 0} {
	  set margin(text) "warning"
	  print2text $num "××× If you really want to do that, use '/really who [string trim "[string range "$line" 4 end]" " "]', but this command makes a lot of traffic and it could take a VERY LONG while!"
	  return ""
	}
      }
      default {
	set command "[lindex "$list" 0]"
	# maybe a user defined command
	foreach x "[info commands "on_command_*"]" {
	  set y "[string range "$x" 11 end]"
	  if {[strcmp "/$y" "$command"] == 0} {
	    set on_args(window) $num
	    on_command_$y $num "[cutwords "$line" 1]"
	    return ""
	  }
	}
      }
    }
  }
  return "$line"
}

proc parse3stars {line} {
  global chan win nickname server away margin
  global on_args destlog crapwindow react_to_netsplits
  global lastOPjoin

  set loline "[string tolower "$line"]"
  switch -glob -- "$loline" {
    "you are now talking to channel *" {
      set channel "[lIndex "$line" end]"
      if {[GetDestWin "$channel"] == -1} { return "" }

      set cnum [GetChannelNumber "$channel"]
      set wnum $on_args(window)

      set i [lSearch "$chan(tojoin)" "$channel"]
      if {$i != -1} {
	set num [lindex "$win(tojoin)" $i]

	set chan(tojoin) "[lreplace "$chan(tojoin)" $i $i]"
	set win(tojoin) "[lreplace "$win(tojoin)" $i $i]"

	# Soll mit dem neuen Fenster nur mitgehorcht werden?
	if {$num < 500} {
	  # Der Kanal bekommt ein neues Fenster zugewiesen.
	  set chan($cnum,window) $num

	  # Wird mit dem alten Fenster nur mitgehorcht?
	  if {$wnum < 500} {
	    # Das alte Fenster muß aktualisiert werden.
	    set j [lsearch -exact "$win($wnum,channels)" "$cnum"]
	    if {$j != -1} {
	      set win($wnum,channels) "[lreplace "$win($wnum,channels)" $j $j]"
	    }
	    if {[llength "$win($wnum,channels)"] == 0} {
	      set win($wnum,actual) "*"
	    } else {
	      set win($wnum,actual) "$chan([lindex "$win($wnum,channels)" 0])"
	    }
	    UpdateInfos $wnum
	  }
	}
	# Der Kanal, das alte und das neue Fenster werden hier aktualisiert.
	lappend win($num,channels) "$cnum"
	set win($num,actual) "$channel"
	UpdateInfos $num
      } else {
	# Ist der Kanal aktuell?
	if {[strcmp "$win($wnum,actual)" "$channel"]} {
	  set win($wnum,actual) "$channel"
	  UpdateInfos $wnum
	}
      }
      return ""
    }
    "* flooding detected from *" {
      set margin(text) "flood"
    }
    "* been kicked off channel *" {
      # Diese Meldung wird herausgefiltert wegen eines Fehlers von ircII.
      return ""
    }
    "ctcp *" {
      set margin(text) "ctcp"
    }
    "unknown ctcp *" {
      set margin(text) "ctcp"
    }
    "* removed from notification list" {
      # Ein Nick wird aus dem Notify-Window entfernt, wenn
      # '/notify -<nick>' ausgeführt wurde.

      DetectSign [lIndex "$line" 0] -1 0
    }
    "users on *" {
      # Wo gehört diese Meldung hin?
      if {[GetDestWin "[string trimright "[lIndex "$line" 2]" ":"]"] == -1} {
	return ""
      }
    }
    "*use /server to *" {
      Disconnect
    }
    "disconnecting from server *" {
      Disconnect
    }
    "*dcc *" {
      set list "[line2list "$line"]"
      # DCC connections
      set margin(text) "dcc"
      switch -glob -- "$loline" {
	"dcc send (*) request received from *" {
	  set from "[lindex "$list" end]"
	  set file "[string trim "[lindex "$list" 2]" "()"]"
	  global request_on_dcc_send
	  if {$request_on_dcc_send} {
	    FileRequest "Please select the new filename for file\n'$file', if you want to get it\nfrom $from via DCC!" "DCC GET" "send2irc \"[expand "/dcc rename $from $file :file"]\";send2irc \"[expand "/dcc get $from :file"]\"" "send2irc \"[expand "/dcc close get $from $file"]\"" "$file" 0
	  }
	}
        "dcc chat (*) request received from *" {
	  set from "[lindex "$list" end]"
	  global request_on_dcc_chat
	  if {$request_on_dcc_chat} {
	    request "Do you want to accept DCC CHAT request from $from?" "Close|send2tkirc $crapwindow \"[expand "/dcc close chat $from"]\"" "Accept|send2tkirc $crapwindow \"[expand "/dchat $from"]\""
	    return ""
	  } else {
	    return "××× DCC CHAT request received from $from. Choose '/dchat $from' to establish the connection."
	  }
	}
        "sent dcc chat request to *" {
	  set to [lindex "$list" 5]
	  set on_args(window) [GetMsgWinFromNick =$to 0]
	}
        "dcc chat (chat) request received*" {
	  set to [lindex "$list" 6]
	  set on_args(window) [GetMsgWinFromNick =$to 0]
	}
        "dcc chat connection * established" {
	  # with <nick>[<host>,<port>] established
	  set four "[lindex "$list" 4]"
	  set i [string last "\[" "$four"]
	  if {$i == -1} {
	    set to $four
	  } else {
	    set to [string range "$four" 0 [expr $i-1]]
	  }
	  set on_args(window) [GetMsgWinFromNick =$to 0]
        }
        "dcc chat connection to *" {
	  # DCC CHAT connection to <nick> lost: *
	  set to "[lindex "$list" 4]"
	  set on_args(window) [GetMsgWinFromNick =$to 0]
	  set win($on_args(window),query) ""
	  UpdateTitle $on_args(window)
	}
        "dcc chat:* closed" {
	  # DCC chat:<any> to <nick> closed
	  set to "[lindex "$list" 3]"
	  set on_args(window) [GetMsgWinFromNick =$to 0]
	  set win($on_args(window),query) ""
	  UpdateTitle $on_args(window)
	}
        "no active dcc chat:chat connection for *" {
	  set to [lindex "$list" 6]
	  set on_args(window) [GetMsgWinFromNick =$to 0]
	}
      }
    }
  }
  return "*** $line"
}

proc parseraw {line} {
  global linetype raw cooked on_args psplit pjoin

  set linetype 0
  set raw(line) "[string range "$line" 5 end]"
  set raw(dp) [string first ":" "$raw(line)"]
  set raw(list) "[line2list "$raw(line)"]"
  set raw(type) "[lindex "$raw(list)" 1]"

  set one "[lindex "$raw(list)" 0]"
  set az [string first "!" "$one"]
  if {$az != -1} {
    set raw(nick) "[string range "$one" 0 [expr $az-1]]"
    set raw(address) "[string range "$one" [expr $az+1] end]"
  } else {
    set raw(nick) ""
    set raw(address) "$one"
  }

  set on_args(nick) "$raw(nick)"
  set on_args(address) "$raw(address)"

  if {[string length "$raw(type)"] == 3} {
    # NUMERICS
    global on_$raw(type)
    if {[info exists raw_$raw(type)] && [string length "[set raw_$raw(type)]"]} {
      eval [set raw_$raw(type)]
    }
    foreach x "[lsort -ascii "[info commands "on_$raw(type)*"]"]" {
      set on_args(line) "$raw(line)"
      $x
    }
    global raw_$raw(type)
    set cooked(line) ""
    if {[string length "[info commands "raw_$raw(type)"]"]} {
      set on_args(line) "$raw(line)"
      raw_$raw(type)
    } else {
      switch -regexp -- "$raw(type)" {
	{^(301|312|313|317|319)} {
	  global whoisfilter whowasfilter
	  # RPL_AWAY, RPL_WHOISUSER and other replies
	  if {$whoisfilter || $whowasfilter} {
	    global filternext ; set filternext 1
	  }
	}
	{^(305|306)} {
	  # RPL_UNAWAY, RPL_NOWAWAY
	  global san away automatic_away
	  set san(nicks) ""
	  set san(times) ""
	  if {$raw(type) == 305} {
	    set away ""
	  } else {
	    if {$automatic_away} {
	      set away " (autoaway)"
	    } else {
	      set away " (away)"
	    }
	  }
	  UpdateAllTitles
	  set automatic_away 0
	  global filternext ; set filternext 1
	  set cooked(line) "$raw(type) [string range "[cutwords "$raw(line)" 3]" 1 end]"
	}
	{^(001|002|003|004)} {
	  global startup nickname server
	  set startup 2
	  if {[string compare "$nickname" "[lindex "$raw(list)" 2]"] != 0 \
	   || [string compare "$server" "$raw(address)"] != 0} {
	    set nickname "[lindex "$raw(list)" 2]"
	    set server "$raw(address)"
	    UpdateAllTitles
	  }
	}
	{^(251|252|254|255)} {
	  global startup notified
	  if {$startup < 3} {
	    set_client_information

	    set notified(nicks) ""
	    set notified(addresses) ""
	    MultiListbox_Delete .not.list 2 0 end

	    on_connect
	    InitIdleTime
	    after 3000 notify_startup
	    set startup 3
	  }
	}
	{^408} {
	  # ERR_NOSUCHSERVICE
	  global notify margin
	  if {[strcmp "noteserv" "[lindex "$raw(list)" 3]"] == 0 \
	   && [strcmp "noteserv" "$notify(method)"] == 0} {
	    set notify(method) "notify"

	    set margin(text) "note"
	    print2crap {*** notification method automatically changed to: 'notify'}

	    for {set j 0} {$j < [llength "$notify(users)"]} {incr j} {
	      set uentry "[lindex "$notify(users)" $j]"
	      if {[string first "*" "[lindex "$uentry" 0]"] == -1} {
		# nick does not include '*'
		append nicks "[lindex "$uentry" 0] "
		AddToFilterQueue "*** [expand "[lindex "$uentry" 0]"] added to the notification list"
	      }
	    }
	    send2irc "/notify $nicks"
	    global filternext ; set filternext 1
	  }
	}
	{^421} {
	  # ERR_UNKNOWNCOMMAND
	  global notify margin
	  if {[strcmp "note" "[lindex "$raw(list)" 3]"] == 0 \
	   && [strcmp "note" "$notify(method)"] == 0} {
	    set notify(method) "notify"

	    set margin(text) "note"
	    print2crap {*** notification method automatically changed to: 'notify'}

	    for {set j 0} {$j < [llength "$notify(users)"]} {incr j} {
	      set uentry "[lindex "$notify(users)" $j]"
	      if {[string first "*" "[lindex "$uentry" 0]"] == -1} {
		# nick does not include '*'
		append nicks "[lindex "$uentry" 0] "
		AddToFilterQueue "*** [expand "[lindex "$uentry" 0]"] added to the notification list"
	      }
	    }
	    send2irc "/notify $nicks"
	    global filternext ; set filternext 1
	  }
	}
	{^(401|403|405|406|432|433|436|437|442|471|473|474|475)} {
	  global nickname chan win destlog whoisqueue whowasqueue
	  # ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, ERR_TOOMANYCHANNELS,
	  # ERR_WASNOSUCHNICK, ERR_ERRONEUSNICKNAME, ERR_NICKNAMEINUSE, 
	  # ERR_NICKCOLLISION, Nick/channel is temporarily unavailable,
	  # ERR_NOTONCHANNEL, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN,
	  # ERR_BANNEDFROMCHAN, ERR_BADCHANNELKEY
	  set tmp [lindex "$raw(list)" 3]
	  if {[strcmp "$nickname" "$tmp"] == 0} {
	    NickNotAvailable "$tmp"
	  } else {
	    # Ggf. müssen die Tojoin-Listen korrigiert werden.
	    set i [lSearch "$chan(tojoin)" "$tmp"]
	    if {$i != -1} {
	      set chan(tojoin) "[lreplace "$chan(tojoin)" $i $i]"
	      set win(tojoin) "[lreplace "$win(tojoin)" $i $i]"
	    }
	    # Dieser Kanal ist zur Zeit nicht gejoint.
	    set cnum [GetChannelNumber "$tmp"]
	    if {$cnum != -1} {
	      DeleteChannel $cnum
	    }
	    # Steht der Nick evtl. noch im Notify-Window?
	    UpdateNotifyWindow 0 "$tmp" ""
	    
	    # Sollte diese Fehlermeldung von tkirc ausgelöst worden sein,
	    # muß sie unterdrückt werden.
	    if {[llength "$whoisqueue"] > 0} {
	      set entry "[lindex "$whoisqueue" 0]"
	      if {[strcmp "[lindex "$entry" 0]" "$tmp"] == 0} {
		set whoisqueue "[lreplace "$whoisqueue" 0 0]"
		if {[llength "$whoisqueue"] > 0} {
		  # nächstes 'whois' losschicken
		  send2irc "/whois [lindex "[lindex "$whoisqueue" 0]" 0]"
		}
		global filternext ; set filternext 1
	      }
	    }
	    if {[llength "$whowasqueue"] > 0} {
	      set entry "[lindex "$whowasqueue" 0]"
	      if {[strcmp "[lindex "$entry" 0]" "$tmp"] == 0} {
	        set whowasqueue "[lreplace "$whowasqueue" 0 0]"
	        if {[llength "$whowasqueue"] > 0} {
		  # nächstes 'whowas' losschicken
		  send2irc "/whowas [lindex "[lindex "$whowasqueue" 0]" 0]"
	        }
	        global filternext ; set filternext 1
	      }
	    }
	  }
	}
      }
    }
  } else {
    # ALPHABETICS
    if {$psplit(state) && [string compare "$raw(nick)" "$psplit(nick)"] == 0} {
      HandleFakeNetsplit $psplit(time)
    }
    if {$pjoin(state) && [string compare "$raw(nick)" "$pjoin(nick)"] == 0} {
      HandleFakeNetjoin $pjoin(time)
    }
    set cooked(line) ""
    if {[string length "[info commands "raw_$raw(type)"]"]} {
      set on_args(line) "$raw(line)"
      raw_$raw(type)
    }
  }
  return "$cooked(line)"
}

proc raw_005 { } {
  global raw server nickname
  # fu-berlin.de 005 oswald :Try server irc.gmd.de, port 6667
  if {[strmatch "$server 005 $nickname :Try server *, port *" "$raw(line)"]} {
    send2irc "/server [string trimright "[lindex "$raw(list)" 5]" ","] [lindex "$raw(list)" 7]"
  }
}

proc raw_311 { } {
  global raw whoisfilter whoisqueue
  # RPL_WHOISUSER
  set nick [lindex "$raw(list)" 3]
  AddAddressToNick $nick [lindex "$raw(list)" 4]@[lindex "$raw(list)" 5]
  set whoisfilter 0

  if {[llength "$whoisqueue"] > 0} {
    set entry "[lindex "$whoisqueue" 0]"
    if {[strcmp "[lindex "$entry" 0]" "$nick"] == 0} {
      set whoisfilter 1
      set command "[lindex "$entry" 2]"
      if {"[AddressOfNick "$nick"]" != "@" && [string length "$command"]} {
        eval $command
      }
      global filternext ; set filternext 1
    }
  }
}

proc raw_314 { } {
  global raw whowasfilter whowasqueue
  # RPL_WHOWASUSER
  if {$whowasfilter == 0} {
    set nick [lindex "$raw(list)" 3]
    AddAddressToNick $nick [lindex "$raw(list)" 4]@[lindex "$raw(list)" 5]

    if {[llength "$whowasqueue"] > 0} {
      set entry "[lindex "$whowasqueue" 0]"
      if {[strcmp "[lindex "$entry" 0]" "$nick"] == 0} {
        set whowasfilter 1
        set command "[lindex "$entry" 2]"
        if {"[AddressOfNick "$nick"]" != "@" && [string length "$command"]} {
          eval $command
        }
        global filternext ; set filternext 1
      }
    }
  } else {
    global filternext ; set filternext 1
  }
}

proc raw_315 { } {
  global raw cooked whofilter whoqueue margin
  # RPL_ENDOFWHO
  if {$whofilter} {
    set entry "[lindex "$whoqueue" 0]"
    if {[string length "[lindex "$entry" 2]"]} {
      set margin(text) "note"
      print2text [lindex "$entry" 1] "[lindex "$entry" 2]"
    }
    set whoqueue "[lreplace "$whoqueue" 0 0]"

    if {[llength "$whoqueue"] > 0} {
      # nächstes 'who' losschicken
      send2irc "/who [lindex "[lindex "$whoqueue" 0]" 0]"
    }
    set whofilter 0
  } elseif {"$raw(lasttype)" != "352"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_318 { } {
  global raw whoisfilter whoisqueue
  # RPL_ENDOFWHOIS
  if {$whoisfilter} {
    set whoisqueue "[lreplace "$whoisqueue" 0 0]"

    if {[llength "$whoisqueue"] > 0} {
      # nächstes 'whois' losschicken
      send2irc "/whois [lindex "[lindex "$whoisqueue" 0]" 0]"
    }
    set whoisfilter 0
  }
}

proc raw_322 { } {
  # RPL_LIST
  global direct2crap ; set direct2crap 1
}

proc raw_324 { } {
  global raw
  # RPL_CHANNELMODEIS
  set channel [lindex "$raw(list)" 3]
  if {[GetDestWin "$channel"] == -1} { return }

  set cnum [GetChannelNumber "$channel"]
  SetChannelModes $cnum "[cutwords "$raw(line)" 4]" 0 ""
}

proc raw_329 { } {
  global raw cooked margin
  # RPL_CREATIONTIME
  set margin(text) "extra"
  set channel [lindex "$raw(list)" 3]
  if {[GetDestWin "$channel"] == -1} { return }

  global filternext ; set filternext 1
  set cooked(line) "*** Channel $channel was created [clock format [lindex "$raw(list)" 4] -format "on %d.%m.%y at %H:%M:%S"]"
}

proc raw_331 { } {
  global raw cooked margin chan on_args 
  # RPL_NOTOPIC
  set margin(text) "topic"
  set channel [lindex "$raw(list)" 3]
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]

  set chan($cnum,topic) ""
  UpdateTopic $on_args(window)
  global filternext ; set filternext 1
  set cooked(line) "*** $channel: No topic is set."
}

proc raw_332 { } {
  global raw cooked margin chan on_args
  # RPL_TOPIC
  set margin(text) "topic"
  set channel [lindex "$raw(list)" 3]
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]

  set chan($cnum,topic) "[string range "[cutwords "$raw(line)" 4]" 1 end]"
  UpdateTopic $on_args(window)
  global filternext ; set filternext 1
  set cooked(line) "*** Topic for $channel: [string range "[cutwords "$raw(line)" 4]" 1 end]"
}

proc raw_333 { } {
  global raw cooked margin
  # RPL_TOPICWHOTIME
  set margin(text) "extra"
  set channel [lindex "$raw(list)" 3]
  if {[GetDestWin "$channel"] == -1} { return }

  global filternext ; set filternext 1
  set cooked(line) "*** Topic was set by [lindex "$raw(list)" 4] [clock format [lindex "$raw(list)" 5] -format "on %d.%m.%y at %H:%M:%S"]"
}

proc raw_352 { } {
  global raw cooked whofilter whoqueue
  # RPL_WHOREPLY
  set channel [lindex "$raw(list)" 3]
  set whofilter 0

  if {[llength "$whoqueue"] > 0} {
    set entry "[lindex "$whoqueue" 0]"
    if {[strcmp "[lindex "$entry" 0]" "$channel"] == 0} {
      AddAddressToNick [lindex "$raw(list)" 7] [lindex "$raw(list)" 4]@[lindex "$raw(list)" 5]
      set whofilter 1
      global filternext ; set filternext 1
    }
  }
}

proc raw_353 { } {
  global raw chan
  # RPL_NAMREPLY
  set channel "[lindex "$raw(list)" 4]"
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]

  if {$chan($cnum,ucount) == 0} {
    set i [string last ":" "$raw(line)"] ; incr i
    foreach x "[expand "[string range "$raw(line)" $i end]"]" {
      lappend chan($cnum,nicks) "[TrimNick "$x"]"
      lappend chan($cnum,names) "$x"
      if {"[string index "$x" 0]" == "+"} {
        incr chan($cnum,mode_v)
        lappend chan($cnum,vlist) "1"
      } else {
        lappend chan($cnum,vlist) "0"
      }
      if {"[string index "$x" 0]" == "@"} {
        incr chan($cnum,mode_o)
        lappend chan($cnum,olist) "1"
      } else {
        lappend chan($cnum,olist) "0"
      }
      lappend chan($cnum,addresses) ""
      lappend chan($cnum,jointimes) "0"
      lappend chan($cnum,ctimes) 0
    }
    set chan($cnum,cnicks) "$chan($cnum,nicks)"
  }
}

proc raw_366 { } {
  global raw chan win on_args nickname ownaddress
  # RPL_ENDOFNAMES
  set channel "[lindex "$raw(list)" 3]"
  if {[GetDestWin "$channel"] != -1} {
    set cnum [GetChannelNumber "$channel"]
    if {$cnum != -1 && $chan($cnum,ucount) == 0} {
      if {[strcmp "$channel" "$win($on_args(window),actual)"] == 0} {
	FillUserList $on_args(window) $cnum
      }
      set chan($cnum,ucount) 1
      ExecOnCommands join channel "$channel" nick "$nickname" address "$ownaddress"
      UpdateInfos $on_args(window)

      foreach nick "$chan($cnum,names)" {
        append users " $nick"
      }
      global raw ; set raw(line) "*** Users on $chan($cnum):$users"
      print2log $chan($cnum) "$raw(line)"
    }
  }
  if {"$raw(lasttype)" != "353"} {
    print2crap "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_367 { } {
  global raw banlist
  # RPL_BANLIST
  set channel "[lindex "$raw(list)" 3]"
  BanChannelUser [GetChannelNumber "$channel"] "[lindex "$raw(list)" 4]" ""
  if {[lSearch "$banlist(filter)" "$channel"] != -1} {
    global filternext ; set filternext 1
  } else {
    if {[winfo exists .banlist]} {
      if {[strcmp "$banlist(channel)" "$channel"] == 0} {
        global filternext ; set filternext 1
        return
      }
    }
    global direct2crap ; set direct2crap 1
  }
}

proc raw_368 { } {
  global raw cooked banlist on_args
  # RPL_ENDOFBANLIST
  set channel "[lindex "$raw(list)" 3]"

  set i [lSearch "$banlist(filter)" "$channel"]
  if {$i != -1} {
    set banlist(filter) "[lreplace "$banlist(filter)" $i $i]"
    if {[GetDestWin "$channel"] != -1} {
      UpdateInfos $on_args(window)
    }
  } elseif {"$raw(lasttype)" != "367"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_369 { } {
  global raw whowasfilter whowasqueue
  # RPL_ENDOFWHOWAS
  if {$whowasfilter} {
    set whowasqueue "[lreplace "$whowasqueue" 0 0]"

    if {[llength "$whowasqueue"] > 0} {
      # nächstes 'whowas' losschicken
      send2irc "/whowas [lindex "[lindex "$whowasqueue" 0]" 0]"
    }
    set whowasfilter 0
  }
}

proc raw_219 { } {
  global raw cooked
  if {"$raw(lasttype)" != "244"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_323 { } {
  global raw cooked
  if {"$raw(lasttype)" != "322"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_365 { } {
  global raw cooked
  if {"$raw(lasttype)" != "364"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_374 { } {
  global raw cooked
  if {"$raw(lasttype)" != "371"} {
    set cooked(line) "*** [string range "$raw(line)" [expr $raw(dp)+1] end]"
  }
}

proc raw_ERROR { } {
  global raw margin
  set margin(text) "error"
  set last "[string range "[cutwords "$raw(line)" 2]" 1 end]"
  print2all "××× $last"
  Disconnect
  global filternext ; set filternext 1
}

proc raw_JOIN { } {
  global raw cooked nickname chan win on_args margin
  set channel "[string range "[lindex "$raw(list)" 2]" 1 end]"

  # Nun muß überprüft werden, ob gleichzeitig ein Channelop-Status
  # mitgeliefert wurde.
  set modetext "" ; set omode 0 ; set vmode 0
  set i [string last "\a" "$channel"]
  if {$i != -1} {
    set modetext "[string range "$channel" [expr $i+1] end]"
    if {[strmatch "*o*" "$modetext"]} {
      set omode 1
    }
    if {[strmatch "*v*" "$modetext"]} {
      set vmode 1
    }
    if {[string length "$modetext"]} {
      set modetext " (+$modetext)"
    }
    set channel "[string range "$channel" 0 [expr $i-1]]"
  }

  if {[strcmp "$raw(nick)" "$nickname"] == 0} {
    # /me besucht den Kanal.
    set cnum [GetChannelNumber "$channel"]
    if {$cnum != -1} {
      # Beim Reconnect etc. muß der Kanal neu angelegt werden.
      set wnum $chan($cnum,window)
      DeleteChannel $cnum
      lappend chan(tojoin) "$channel"
      lappend win(tojoin) $wnum
    }
    set cnum [ProduceChannel "$channel"]
    if {$cnum == -1} { return }

    global banlist 
    if {[winfo exists .banlist]} {
      # Enable commit-button of banlist-editor
      if {$cnum == [GetChannelNumber "$banlist(channel)"]} {
        .banlist.buttons.commit configure -state normal
      }
    }

  } else {
    # Jemand anderes besucht den Kanal.
    if {[GetDestWin "$channel"] == -1} { return }
    set cnum [GetChannelNumber "$channel"]
    AddChannelUser $cnum "$raw(nick)" "$raw(address)" $vmode $omode
    UpdateInfos $on_args(window)
  }

  # Das 'on_join' wird hier nur unterstützt, wenn jemand
  # anderes den Kanal betritt. Beim eigenen Betreten des 
  # Kanals wird auf die Userliste gewartet.
  if {[strcmp "$raw(nick)" "$nickname"]} {
    ExecOnCommands join channel "$channel"
  } else {
    global ownaddress
    set ownaddress "$raw(address)"
  }

  set line ""
  global react_to_netsplits
  if {$react_to_netsplits} {
    HandleNetjoins $cnum "$raw(nick)" "$raw(address)" "$modetext"
  } else {
    TakeOverTest $cnum "$raw(address)"

    # Soll die Join-Meldung ausgegeben werden?
    global hide_joins$on_args(window)
    if {[set hide_joins$on_args(window)] == 0} {
      set margin(text) "join"
      set cooked(line) "*** $raw(nick) ($raw(address)) has joined channel $channel$modetext\x0f\x0f"
    }
  }
}

proc raw_PART { } {
  global raw cooked margin nickname chan on_args
  set channel "[lindex "$raw(list)" 2]"
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]
  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"

  if {[strcmp "$raw(nick)" "$nickname"] == 0} {
    # /me verläßt den Kanal.

    global banlist 
    if {[winfo exists .banlist]} {
      # Disable commit-button of banlist-editor
      if {$cnum == [GetChannelNumber "$banlist(channel)"]} {
        .banlist.buttons.commit configure -state disabled
      }
    }

    ExecOnCommands leave channel "$channel" message "$last"
    DeleteChannel $cnum

  } else {
    # Jemand anderes verläßt den Kanal.
    ExecOnCommands leave channel "$channel" message "$last"

    # Ist der Typ ggf. ein Channelhopper?
    set i [UserNumOfChannel $cnum "$raw(nick)"]
    if {$i != -1} {
      global channelhop_period
      set period [expr [clock seconds]-[lindex "$chan($cnum,jointimes)" $i]]
      if {$period >= 0 && $period <= $channelhop_period} {
	# Ist in der letzten Zeit (channelhop_period) ein Netjoin
	# aufgetreten, dann wird 'on_channelhop' nicht aufgerufen!
        global join
        if {$join(count) > 0} {
          set num [expr $join(count)-1]
          if {[expr [clock seconds] - $join($num,time)] > $channelhop_period} {
            ExecOnCommands channelhop channel "$channel" period $period
          }
        } else {
          ExecOnCommands channelhop channel "$channel" period $period
	}
      }
      RemoveChannelUser $cnum $i $raw(nick)
    }
  }

  # Soll die Leave-Meldung ausgegeben werden?
  global hide_leaves$on_args(window)
  if {[set hide_leaves$on_args(window)] == 0} {
    set margin(text) "leave"
    set cooked(line) "*** $raw(nick) ($raw(address)) has left channel $channel"
    if {[string length "$last"] && [string compare "$raw(nick)" "$last"]} {
      append cooked(line) " ($last\x0f)\x0f\x0f"
    } else {
      append cooked(line) "\x0f\x0f"
    }
  }
}

proc raw_QUIT { } {
  global raw cooked chan win react_to_netsplits margin
  set last "[string range "[cutwords "$raw(line)" 2]" 1 end]"
  ExecOnCommands signoff message "$last"

  if {$react_to_netsplits && [regexp -- {^([^ .]+\.)+[^ .]+ ([^ .]+\.)+[^ .]+$} "$last"]} {
    HandleNetsplits "$raw(nick)" "$raw(address)" "$last"
  } else {
    set channels ""
    foreach cnum "$chan(list)" {
      set i [UserNumOfChannel $cnum "$raw(nick)"]
      if {$i != -1} {
	# Soll die Signoff-Meldung ausgegeben werden?
	set j $chan($cnum,window)
	if {$j != -1} {
	  global hide_signoffs$j
	  if {[set hide_signoffs$j] == 0} {
    	    lappend channels $cnum
	  }
	}
	RemoveChannelUser $cnum $i $raw(nick)
      }
    }
    set margin(text) "signoff"
    print2channels "$channels" "*** $raw(nick) has signed off ($last\x0f)\x0f\x0f"
  }
}

proc raw_PRIVMSG { } {
  global raw cooked nickname linetype on_args margin next away win
  set to "[lindex "$raw(list)" 2]"
  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"

  # Das Zielfenster wird ermittelt.
  if {[strcmp "$to" "$nickname"]} {
    set linetype 0
    set prefix "[string index "$to" 0]"
    if {[GetDestWin "$to"] == -1 && ("$prefix" == "&" || "$prefix" == "#")} {
      debug "PRIVMSG: '$raw(line)'"
      global filternext ; set filternext 1
      return
    }
  } else {
    set linetype 1
    set on_args(window) [GetMsgWinFromNick $raw(nick) 0]
  }
  # Kommt die Privmsg evtl. direkt vom Server?
  if {"$raw(nick)" == ""} {
    if {[strcmp "$nickname" "$to"]} {
      set margin(text) "[string tolower "$to"]"
    } else {
      set margin(text) "server"
    }
    ExecOnCommands servermessage to "$to" rest "$last"
    global filternext ; set filternext 1
    set cooked(line) "*** $last\x0f\x0f"
    return
  }
  # Hier werden mögliche CTCP-Kommandos herausgefiltert, die dem
  # Benutzer allerdings nicht alle einzeln angezeigt werden. Es
  # werden bis zu 4 CTCP-Kommandos in einer Zeile berücksichtigt.
  set together ""
  set ctcps_within_message 0
  for {set l 0} {$l < 4} {incr l} {
    set left [string first "\x01" "$last"]
    if {$left == -1} {
      break
    }
    incr left
    set right [string first "\x01" "[string range "$last" $left end]"]
    if {$right == -1} {
      break
    }
    incr ctcps_within_message

    if {$l == 1} {
      AddToFilterQueue "[expand "*CTCP $command from *"]"
      append together "$ctcpline "
    }

    set right [expr $right+$left-1]
    set ctcpline "[string range "$last" $left $right]"
    set command "[lIndex "$ctcpline" 0]"
    set parameters "[cutwords "$ctcpline" 1]"
    set last "[string range "$last" 0 [expr $left-2]][string range "$last" [expr $right+2] end]"

    if {$l > 0} {
      AddToFilterQueue "[expand "*CTCP $command from *"]"
      append together "; $ctcpline "
    }

    # Der Client soll evtl. nicht auf beliebig viele CTCP-Kommandos
    # antworten, um nicht vom Server wegen "Excess flood" rausgeschmissen
    # zu werden. CTCPs und INVITEs werden ggf. abgestellt.
    global react_to_ctcp_flood
    if {$l == 0 && [strcmp "$command" "ACTION"] && [strcmp "$command" "DCC"] && [strcmp "0" "$react_to_ctcp_flood"]} {
      global ctcp_count ctcp_list
      global host_flood_ignore_period global_flood_ignore_period

      set add2count 1
      if {$ctcp_count < 5} {
        # Flood-Protection für einzelne Hosts:

        set new_ctcp_list ""
        set at [string first "@" "$raw(address)"]
        set host "[string range "$raw(address)" [expr $at+1] end]"
        set newadd 0

        set len [llength "$ctcp_list"]
        for {set i 0} {$i < $len} {incr i} {
          set x "[lindex "$ctcp_list" $i]"

          if {[expr [clock seconds]-[lindex "$x" 1]] < [lindex "$x" 3]} {
            if {[string compare "$host" "[lindex "$x" 0]"] == 0} {
	      set newadd 1
	      set time "[lindex "$x" 1]"
	      set valid 30
	      set count [lindex "$x" 2]
	      if {$count == 2} {
	        ignore "$host"
	        set margin(text) "note"
	        print2crap "××× Flood protection activated for host '$host' ($host_flood_ignore_period seconds)"
	        after [expr $host_flood_ignore_period * 1000] "unignore \"$host\" ; global margin ; set margin(text) \"note\" ; print2crap \"××× Flood protection deactivated for host '$host'\""
	        set valid [expr $host_flood_ignore_period]
	        set time "[clock seconds]"
	      } elseif {$count > 2} {
	        global filternext ; set filternext 1
	        set add2count 0
	        set valid [expr $host_flood_ignore_period]
	      }
	      lappend new_ctcp_list "$host $time [expr $count+1] $valid"
	    } else {
	      lappend new_ctcp_list "$x"
	    }
	  }
        }
        set ctcp_list "$new_ctcp_list"
        if {$newadd == 0} {
          lappend ctcp_list "$host [clock seconds] 1 30"
        }
      } elseif {$ctcp_count == 5} {
        # Flood-Protection für alle eingehenden CTCPs:
        set ctcp_count 105
        ignore "*"
        set margin(text) "note"
        print2crap "××× Global flood protection activated ($global_flood_ignore_period seconds)"
        after [expr $global_flood_ignore_period * 1000] "global ctcp_count ; set ctcp_count 1 ; unignore \"*\" ; global margin ; set margin(text) \"note\" ; print2crap \"××× Global flood protection deactivated\""
      } elseif {$ctcp_count > 5} {
        # Eingehende CTCPs werden ab jetzt nur noch herausgefiltert.
        global filternext ; set filternext 1
        return
      }
      if {$add2count} {
        incr ctcp_count
        after 60000 {global ctcp_count ; set ctcp_count [expr $ctcp_count-1]}
      } else {
        # Die Flood-Protection wurde für einen Host aktiviert.
        return
      }
    }

    set upcommand "[string toupper "$command"]"
    switch -regexp -- "$upcommand" {
      {^ACTION} {
        set next(direct) 1
        if {$linetype} {
	  ExecOnCommands privaction rest "$parameters"

	  set next(from) "$raw(nick)"
          set next(towin) "** $raw(nick)\t$parameters"
          set next(right) [expr [set next(left) 3]+[string length "$raw(nick)"]]
          set next(pattern) "\*> [expand "$raw(nick)"]*"
          if {[string length "$away"]} {
	    global show_address_on_message_when_away
	    if {$show_address_on_message_when_away} {
	      set next(towin) "** $raw(nick)!$raw(address)\t$parameters"
	    }
	  } else {
	    global show_address_on_message_when_present
	    if {$show_address_on_message_when_present} {
	      set next(towin) "** $raw(nick)!$raw(address)\t$parameters"
	    }
	  }
	  global show_address_on_message_in_logfile
          if {$show_address_on_message_in_logfile} {
	    set next(tolog) "** $raw(nick)!$raw(address) $parameters"
	  } else {
	    set next(tolog) "$next(towin)"
	  }
	  if {[string length "$away"]} {
	    global beep_on_message_when_away
	    if {$beep_on_message_when_away} {
	      set next(beep) 1
	    }
	  } else {
	    global beep_on_message_when_present
	    if {$beep_on_message_when_present} {
	      set next(beep) 1
	    }
	  }
        } else {
          ExecOnCommands pubaction to "$to" rest "$parameters"

	  set next(to) "$to"
          set next(pattern) "\* [expand "$raw(nick)"]*"
	  set i [UserNumOfChannel [GetChannelNumber "$to"] "$raw(nick)"]
	  if {$i != -1} {
	    global show_only_background_channels
	    if {[llength "$win($on_args(window),channels)"] < 2 \
	      || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
	      set next(towin) "* $raw(nick)\t$parameters"
	    } else {
	      set next(towin) "* $raw(nick)+$to\t$parameters"
	    }
	  } else {
	    set next(towin) "* $raw(nick)»$to\t$parameters"
	  }

	  if {[string length "$away"]} {
	    global beep_on_public_when_away
	    if {$beep_on_public_when_away} {
	      set next(beep) 1
	    }
	  } else {
	    global beep_on_public_when_present
	    if {$beep_on_public_when_present} {
	      set next(beep) 1
	    }
	  }
        }
        break
      }
      {^DCC} {
        ExecOnCommands dcc type "[lIndex "$parameters" 0]" rest "[cutwords "$parameters" 1]"
      }
      default {
        ExecOnCommands ctcprequest command "$upcommand" to "$to" rest "$parameters"
      }
    }
  }
  if {"$together" != ""} {
    set margin(text) "ctcp"
    if {$linetype} {
      print2crap "××× MULTI-CTCP from $raw(nick): $together\x0f\x0f"
    } else {
      print2crap "××× MULTI-CTCP from $raw(nick) to $to: $together\x0f\x0f"
    }
  }

  if {$ctcps_within_message != 0 && "$last" == ""} {
    return
  }

  # Dann muß die Privmsg für mich oder einen Kanal bestimmt sein.
  set next(direct) 1
  if {$linetype} {
    set next(from) "$raw(nick)"
    AddToMsgHistory "[expandescape "$raw(nick)"]"
    if {[strcmp "$nickname" "$raw(nick)"]} {
      if {[string length "$away"]} {
        global chat_window_on_message_when_away
        if {$chat_window_on_message_when_away} {
          set next(chatwin) 1
        }
      } else {
        global chat_window_on_message_when_present
        if {$chat_window_on_message_when_present} {
          set next(chatwin) 1
        }
      }
    }
    set next(towin) "*$raw(nick)*\t$last"
    set next(right) [expr [set next(left) 1]+[string length "$raw(nick)"]]
    set next(pattern) "?[expand "$raw(nick)"]*"
    if {[string length "$away"]} {
      global show_address_on_message_when_away
      if {$show_address_on_message_when_away} {
        set next(towin) "*$raw(nick)!$raw(address)*\t$last"
      }
    } else {
      global show_address_on_message_when_present
      if {$show_address_on_message_when_present} {
        set next(towin) "*$raw(nick)!$raw(address)*\t$last"
      }
    }
    global show_address_on_message_in_logfile
    if {$show_address_on_message_in_logfile} {
      set next(tolog) "*$raw(nick)!$raw(address)* $last"
    } else {
      set next(tolog) "$next(towin)"
    }
    if {[string length "$away"]} {
      global beep_on_message_when_away
      if {$beep_on_message_when_away} {
        set next(beep) 1
      }
    } else {
      global beep_on_message_when_present
      if {$beep_on_message_when_present} {
        set next(beep) 1
      }
    }
    ExecOnCommands privmessage rest "$last"
  } else {
    set next(to) "$to"
    set next(pattern) "?[expand "$raw(nick)"]*"
    set i [UserNumOfChannel [GetChannelNumber "$to"] "$raw(nick)"]
    if {$i != -1} {
      global show_only_background_channels
      if {[llength "$win($on_args(window),channels)"] < 2 \
       || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
        set next(towin) "<$raw(nick)>\t$last"
      } else {
        set next(towin) "<$raw(nick)+$to>\t$last"
      }
    } else {
      set next(towin) "<$raw(nick)»$to>\t$last"
    }

    if {[string length "$away"]} {
      global beep_on_public_when_away
      if {$beep_on_public_when_away} {
	set next(beep) 1
      }
    } else {
      global beep_on_public_when_present
      if {$beep_on_public_when_present} {
	set next(beep) 1
      }
    }
    ExecOnCommands pubmessage to "$to" rest "$last"
  }
}

#
# english noteserv messages 
# kai 'oswald' seidler, 14oct97
# modified by atte for Tcl/Tk, 02feb98
#
set noteserv(messages,en) {
  {Please use a more specific recipient.}
  {(S)he's online: use /msg.}
  {Too many messages queued. Maximum is %d.}
  {%2d. Notify: %s %s%s %s}
  {%2d. Notify: %s%s %s}
  {}
  {    Date: %s}
  {    Body: %s}
  {Please use a more specific pattern.}
  {Too many spys queued. Maximum is %d.}
  {No help available on %s.}
  {This is NoteServ version %s}
  {No no no...}
  {Good bye cruel world!}
  {Unknown command.}
  {%s (%s) is on (%s)}
  {Stopped after %d matches...}
  {New login created.}
  {Login incorrect}
  {Welcome back.}
  {Goodbye. I'm now serving %s.}
  {Nothing, absolutly nothing.}
  {Nothing.}
  {There are %d users online, %d users active and %d offline users waiting to deliver notes.}
  {%s (%s) %s %s}
  {You are subscribed as %s.}
  {You are unsubscribed.}
  {Message(s) delivered by %s (http://oswald.pages.de/irc/noteserv/)}
  {From: %s (%s)}
  {Date: %s}
  {Body: %s}
  {%d more messages. CONTinue reading or TRASH remaining messages.}
  {Please specify an entry to remove.}
  {Please read the manual. (http://oswald.pages.de/irc/noteserv/)}
  {The secret word?}
  {%s}
  {}
  {Did you forget the : before the command?}
  {Who is the recipient?}
  {In case of SEND an eternal lifetime isn't possible!}
  {Please specify a message?}
  {<pattern> is too long.}
  {<comment> is too long.}
  {<message> is too long.}
  {Please specify any pattern.}
  {<password> is too long.}
  {Password changed.}
  {Couldn't change password.}
  {Please login first.}
  {%d message(s) deleted.}
  {No more messages.}
  {}
  { %c. To:   %s%s queued}
  {}
  { %c. To:   %s%s added}
  {%s (%s) gets visible %s}
  {%s (%s) gets invisible %s}
  {%s (%s) signs off %s}
  {%s (%s) signs on (%s) %s}
  {%s (%s) changed nick to <%s> %s}
  { %c. To:   %s%s delivered}
  { %c. To:   %s%s expired}
  { %c. To:   %s%s removed}
  { %c. To:   %s%s}
  {%2d. Notify: %s%s%s}
  {%2d. Notify: %s%s%s expired}
  {%2d. Notify: %s%s%s removed}
  {%2d. Notify: %s%s%s queued}
  {%2d. Notify: %s%s%s added}
  {day}
  {days}
  {hour}
  {hours}
  {minute}
  {minutes}
  {second}
  {seconds}
  {Valid languages are:%s}
  {New language (%s) selected.}
  { (eternal)}
  {%s (%s) signs on %s}
  {%s (%s) changed nick %s}
  {You're ghosted, try again. \[Tnx to Yegg!\]}
  {Warning: %s doesn't look like a valid pattern.}
}
#
# german noteserv messages 
# kai 'oswald' seidler, 14oct97
# modified by atte for Tcl/Tk, 02feb98
#
set noteserv(messages,de) {
  {Bitte gebe den Empfaenger genauer an.}
  {Empfaenger ist bereits online. }
  {Zu viele Nachrichten abgelegt. Das Maximum ist %d.}
  {%2d. Notify: %s %s%s %s}
  {%2d. Notify: %s%s %s}
  {}
  {    Datum: %s}
  {    Text:  %s}
  {Bitte benutze ein genaueres Pattern.}
  {Zu viele Notifys. Das Maximum ist %d.}
  {Keine Hilfe zu %s.}
  {Dies ist NoteServ Version %s}
  {No no no...}
  {Adieu, schreckliche Welt.}
  {Unbekanntes Kommando.}
  {%s (%s) ist da (%s)}
  {Nach %d Treffern aufgehoert...}
  {Neuen Benutzer angelegt.}
  {Zugriff verweigert.}
  {Willkommen zurueck.}
  {Ciao. Ich bediene nun %s.}
  {Nichts.}
  {Nichts. Gar nichts.}
  {There are %d users online, %d users active and %d offline users waiting to deliver notes.}
  {%s (%s) %s %s}
  {Eingetragen als %s.}
  {Nicht eingetragen.}
  {Nachricht(en) uebermittelt von %s (http://oswald.pages.de/irc/noteserv/)}
  {Von:   %s (%s)}
  {Datum: %s}
  {Text:  %s}
  {%d weitere Nachrichten. Mit CONT weiterlesen oder mit TRASH alle weiteren Nachrichten loeschen.}
  {Welchen Eintrag loeschen?}
  {Bitte schau nochmal in die Anleitung (http://oswald.pages.de/irc/noteserv/)}
  {Das Passwort?}
  {%s}
  {}
  {Vielleicht das : vor dem Kommando vergessen?}
  {Wer ist der Empfaenger?}
  {Bei SEND ist eine unbegrenzte Lebensdauer nicht moeglich.}
  {Die Nachricht?}
  {<pattern> ist zu lang.}
  {<comment> ist zu lang.}
  {<message> ist zu lang.}
  {Das Suchmuster?}
  {<password> ist zu lang.}
  {Passwort geaendert.}
  {Passwort konnte nicht geaendert werden.}
  {Bitte zuerst mit LOGIN anmelden.}
  {%d Nachricht(en) geloescht.}
  {Keine weiteren Nachrichten.}
  {}
  { %c. An:    %s%s veraendert}
  {}
  { %c. An:    %s%s abgelegt}
  {%s (%s) wird sichtbar %s}
  {%s (%s) wird unsichtbar %s}
  {%s (%s) geht wieder %s}
  {%s (%s) kommt rein (%s) %s}
  {%s (%s) nennt sich nun <%s> %s}
  { %c. An:    %s%s ausgeliefert}
  { %c. An:    %s%s abgelaufen}
  { %c. An:    %s%s geloescht}
  { %c. An:    %s%s}
  {%2d. Notify: %s%s%s}
  {%2d. Notify: %s%s%s abgelaufen}
  {%2d. Notify: %s%s%s geloescht}
  {%2d. Notify: %s%s%s veraendert}
  {%2d. Notify: %s%s%s abgelegt}
  {Tag}
  {Tage}
  {Stunde}
  {Stunden}
  {Minute}
  {Minuten}
  {Sekunde}
  {Sekunden}
  {Gueltige Sprachen sind:%s}
  {Neue Sprache (%s) gewaehlt. }
  { (endlos)}
  {%s (%s) kommt rein %s}
  {%s (%s) nennt sich anders %s}
  {You're ghosted, try again. \[Tnx to Yegg!\]}
  {Achtung: %s sieht nicht wie ein Pattern aus.}
}

proc noteserv_raw2msg { } {
  global raw cooked margin notified noteserv

  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"
  set list "[line2list "$last"]"

  if [regexp -- {^[0-9](|[0-9])$} "[lindex "$list" 0]"] {
    set last "[cutwords "$last" 1]"

    switch -regexp -- "[lindex "$list" 0]" {
      "^(15|55|58|80)$" {
        # en:15:%s (%s) is on (%s)
	# en:55:%s (%s) gets visible %s
	# en:58:%s (%s) signs on (%s) %s
	# en:80:%s (%s) signs on %s

        set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
        if {$i != -1} {
	  # an old entry must be deleted
      	  set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
  	  set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
	  MultiListbox_Delete .not.list 2 $i
        }
        lappend notified(nicks) "[lindex "$list" 1]"
        lappend notified(addresses) "[lindex "$list" 2]"
        MultiListbox_Insert .not.list 2 end "[lindex "$list" 1]" "[lindex "$list" 2]"
      }
      "^(56|57|81)$" {
	# en:56:%s (%s) gets invisible %s
	# en:57:%s (%s) signs off %s
	# en:81:%s (%s) changed nick %s

        set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
        if {$i != -1} {
      	  set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
  	  set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
	  MultiListbox_Delete .not.list 2 $i
        }
      }
      "^59$" {
	# en:59:%s (%s) changed nick to <%s> %s

        set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
        if {$i != -1} {
	  set notified(nicks) "[lreplace "$notified(nicks)" $i $i "[lindex "$list" 3]"]"
	  MultiListbox_Delete .not.list 2 $i
          MultiListbox_Insert .not.list 2 $i "[lindex "$list" 3]" "[lindex "$list" 2]"
        }
      }
      "^64$" {
        # en:64:%2d. Notify: %s%s%s
        # (Ergebnis von "/noteserv ls")

        if {$noteserv(status) == 1} {
	  set noteserv(status) 0
	}
      }
      "^65$" {
	# en:65:%2d. Notify: %s%s%s expired

        set len [expr [llength "$notified(nicks)"]-1]
        for {set i $len} {$i >= 0} {set i [expr $i-1]} {
	  if {[strmatch "[expand "[lindex "$list" 2]"]" "[lindex "$notified(nicks)" $i]![lindex "$notified(addresses)" $i]"]} {
      	    set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
  	    set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
	    MultiListbox_Delete .not.list 2 $i
	  }
	}
      }
      "^66$" {
	# en:66:%2d. Notify: %s%s%s removed

        set len [expr [llength "$notified(nicks)"]-1]
        for {set i $len} {$i >= 0} {set i [expr $i-1]} {
	  if {[strmatch "[expand "[lindex "$list" 2]"]" "[lindex "$notified(nicks)" $i]![lindex "$notified(addresses)" $i]"]} {
	    set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
  	    set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
	    MultiListbox_Delete .not.list 2 $i
	  }
	}
        if {$noteserv(status) == 1} {
	  set cooked(line) "" ; return
	}
      }
      "^68$" {
	# en:68:%2d. Notify: %s%s%s added

        if {$noteserv(status) == 1} {
	  set cooked(line) "" ; return
	}
      }
      "^78$" {
	# en:78:New language (%s) selected.

        if {$noteserv(status) == 1} {
	  set cooked(line) "" ; return
	}
      }
    }

    if {![info exists noteserv(messages,$noteserv(language))]} {
      set magin(text) "error"
      print2crap "××× NoteServ doesn't support language '$noteserv(language)'"
      set noteserv(language) "en"
    }

    set args "\"[expand "[lindex "$noteserv(messages,$noteserv(language))" [lindex "$list" 0]]"]\" [expand "$last"]"
    set cooked(line) "*** [eval format $args \{\} \{\}]"

    if {[regexp -- {^(15|55|56|57|58|59|80|81)$} "[lindex "$list" 0]"]} {
      append cooked(line) " ([time])"
    }
  } else {
    # Keine Nummer am Anfang.
    set cooked(line) "*** $last"
  }

  set margin(text) "noteserv"
}

proc note_raw2msg { } {
  global raw cooked margin notified note

  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"
  set list "[line2list "$last"]"

  if {[strmatch "### * (*) signs on" "$last"] \
   || [strmatch "### * (*) is here" "$last"] \
   || [strmatch "### * (*) is on IRC now" "$last"] } {

    set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
    if {$i != -1} {
      # an old entry must be deleted
      set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
      set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
      MultiListbox_Delete .not.list 2 $i
    }
    lappend notified(nicks) "[lindex "$list" 1]"
    lappend notified(addresses) "[string trim "[lindex "$list" 2]" "()"]"
    MultiListbox_Insert .not.list 2 end "[lindex "$list" 1]" "[string trim "[lindex "$list" 2]" "()"]"

  } elseif {[strmatch "### * (*) signs off" "$last"]} {

    set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
    if {$i != -1} {
      set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
      set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
      MultiListbox_Delete .not.list 2 $i
    }

  } elseif {[strmatch "### * (*) changed name to <*>" "$last"]} {
    set i [lsearch -exact "$notified(nicks)" "[lindex "$list" 1]"]
    if {$i != -1} {
      set notified(nicks) "[lreplace "$notified(nicks)" $i $i "[string trim "[lindex "$list" 6]" "<>"]"]"
      MultiListbox_Delete .not.list 2 $i
      MultiListbox_Insert .not.list 2 $i "[string trim "[lindex "$list" 6]" "<>"]" "[string trim "[lindex "$list" 2]" "()"]"
    }

  } elseif {[string compare "#?# No such request(s) found" "$last"] == 0 \
    || [strmatch "### Queued... <*> * for *" "$last"]} {

    if {$note(status) == 1} {
      set cooked(line) "" ; return
    }

  } elseif {[strmatch "### Removed -> <*> * (*)" "$last"]} {

    set len [expr [llength "$notified(nicks)"]-1]
    for {set i $len} {$i >= 0} {set i [expr $i-1]} {
      if {[strmatch "[expand "[lindex "$list" 4]![string trim "[lindex "$list" 5]" "()"]"]" "[lindex "$notified(nicks)" $i]![lindex "$notified(addresses)" $i]"]} {
	set notified(nicks) "[lreplace "$notified(nicks)" $i $i]"
        set notified(addresses) "[lreplace "$notified(addresses)" $i $i]"
	MultiListbox_Delete .not.list 2 $i
      }
    }
    if {$note(status) == 1} {
      set cooked(line) "" ; return
    }

  } elseif {[strmatch "#* <*> * (*) for *" "$last"]} {
    # (Ergebnis von "/note ls")
    
    if {$note(status) == 1} {
      set note(status) 0
    }
  }

  set cooked(line) "*** $last"
#  set margin(text) "server"
}

proc raw_NOTICE { } {
  global raw cooked nickname linetype on_args margin next away win
  set to "[lindex "$raw(list)" 2]"
  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"

  # Das Zielfenster wird ermittelt.
  if {[strcmp "$to" "$nickname"]} {
    set linetype 0
    set prefix "[string index "$to" 0]"
    if {[GetDestWin "$to"] == -1 && ("$prefix" == "&" || "$prefix" == "#")} {
      debug "NOTICE: '$raw(line)'"
      global filternext ; set filternext 1
      return
    }
  } else {
    set linetype 1
    set on_args(window) [GetMsgWinFromNick "$raw(nick)" 0]
  }
  # Kommt die Notice evtl. direkt vom Server?
  if {"$raw(nick)" == ""} {
    ExecOnCommands servernotice to "$to" rest "$last"
    global filternext ; set filternext 1

    if {[strcmp "$nickname" "$to"]} {
      # Die Notice scheint für einen Kanal bestimmt zu sein.
      set margin(text) "[string tolower "$to"]"
    } else {
      set margin(text) "server"

      if {[string compare "*" "$raw(address)"] == 0} {
        # Hier werden Note-Notices verarbeitet.
        note_raw2msg
	return
      } elseif {[string match "NoteServ@*" "$raw(address)"]} {
        # Hier werden NoteServ-Notices verarbeitet.
        noteserv_raw2msg
	return

      } elseif {[string match {\*\*\* *} "$last"]} {
	set cooked(line) "$last\x0f\x0f"
        return
      }
    }
    set cooked(line) "*** $last\x0f\x0f"
    return
  }
  # Hier werden mögliche CTCP-Replies herausgefiltert, die dem
  # Benutzer allerdings nicht alle einzeln angezeigt werden. Es
  # werden bis zu 4 CTCP-Replies in einer Zeile berücksichtigt.
  set together ""
  set ctcps_within_message 0
  for {set l 0} {$l < 4} {incr l} {
    set left [string first "\x01" "$last"]
    if {$left == -1} {
      break
    }
    incr left
    set right [string first "\x01" "[string range "$last" $left end]"]
    if {$right == -1} {
      break
    }
    incr ctcps_within_message

    if {$l == 1} {
      AddToFilterQueue "[expand "*CTCP $command reply from *"]"
      # Die folgende Zeile sorgt dafür, daß eine durch ircII fehlerhaft
      # erzeugte Message (beim multiplen CTCP-Reply) herausgefiltert wird.
      AddToFilterQueue "[expand "-$raw(nick)*\x01"]"
      append together "$ctcpline "
    }

    set right [expr $right+$left-1]
    set ctcpline "[string range "$last" $left $right]"
    set command "[lIndex "$ctcpline" 0]"
    set parameters "[cutwords "$ctcpline" 1]"
    set last "[string range "$last" 0 [expr $left-2]][string range "$last" [expr $right+2] end]"

    if {$l > 0} {
      AddToFilterQueue "[expand "*CTCP $command reply from *"]"
      append together "; $ctcpline "
    }

    set upcommand "[string toupper "$command"]"
    ExecOnCommands ctcpreply command "$upcommand" to "$to" rest "$parameters"
  }
  if {"$together" != ""} {
    set margin(text) "ctcp"
    if {$linetype} {
      print2crap "××× MULTI-CTCP reply from $raw(nick): $together\x0f\x0f"
    } else {
      print2crap "××× MULTI-CTCP reply from $raw(nick) to $to: $together\x0f\x0f"
    }
  }

  if {$ctcps_within_message != 0 && "$last" == ""} {
    return
  }

  # Dann muß die Notice für mich oder einen Kanal bestimmt sein.
  set next(direct) 1
  if {$linetype} {
    set next(from) "$raw(nick)"
    AddToMsgHistory "[expandescape "$raw(nick)"]"
    if {[strcmp "$nickname" "$raw(nick)"]} {
      if {[string length "$away"]} {
        global chat_window_on_notice_when_away
        if {$chat_window_on_notice_when_away} {
          set next(chatwin) 1
        }
      } else {
        global chat_window_on_notice_when_present
        if {$chat_window_on_notice_when_present} {
          set next(chatwin) 1
        }
      }
    }
    set next(towin) "+$raw(nick)+\t$last"
    set next(right) [expr [set next(left) 1]+[string length "$raw(nick)"]]
    set next(pattern) "-[expand "$raw(nick)"]*"
    if {[string length "$away"]} {
      global show_address_on_notice_when_away
      if {$show_address_on_notice_when_away} {
        set next(towin) "+$raw(nick)!$raw(address)+\t$last"
      }
    } else {
      global show_address_on_notice_when_present
      if {$show_address_on_notice_when_present} {
        set next(towin) "+$raw(nick)!$raw(address)+\t$last"
      }
    }
    global show_address_on_notice_in_logfile
    if {$show_address_on_notice_in_logfile} {
      set next(tolog) "+$raw(nick)!$raw(address)+ $last"
    } else {
      set next(tolog) "$next(towin)"
    }
    if {[string length "$away"]} {
      global beep_on_notice_when_away
      if {$beep_on_notice_when_away} {
        set next(beep) 1
      }
    } else {
      global beep_on_notice_when_present
      if {$beep_on_notice_when_present} {
        set next(beep) 1
      }
    }
    ExecOnCommands privnotice rest "$last"
  } else {
    set next(to) "$to"
    set next(pattern) "-[expand "$raw(nick)"]*"
    set i [UserNumOfChannel [GetChannelNumber "$to"] "$raw(nick)"]
    if {$i != -1} {
      global show_only_background_channels
      if {[llength "$win($on_args(window),channels)"] < 2 \
       || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
        set next(towin) "-$raw(nick)-\t$last"
      } else {
        set next(towin) "-$raw(nick)+$to-\t$last"
      }
    } else {
      set next(towin) "-$raw(nick)»$to-\t$last"
    }

    if {[string length "$away"]} {
      global beep_on_public_when_away
      if {$beep_on_public_when_away} {
        set next(beep) 1
      }
    } else {
      global beep_on_public_when_present
      if {$beep_on_public_when_present} {
        set next(beep) 1
      }
    }
    ExecOnCommands pubnotice to "$to" rest "$last"
  }
}

proc raw_WALLOPS { } {
  global raw cooked margin
  set to "[lindex "$raw(list)" 2]"
  set last "[coloncut "$raw(line)"]"
  set margin(text) "wallops"
  global filternext ; set filternext 1
  set cooked(line) "*** ![lindex "$raw(list)" 0]! $last"
}

proc raw_TOPIC { } {
  global raw cooked chan on_args margin
  set channel "[lindex "$raw(list)" 2]"
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]

  set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"
  set chan($cnum,topic) "$last"
  UpdateTopic $on_args(window)
  ExecOnCommands topic channel "$channel" topic "$last"
  set margin(text) "topic"
  set cooked(line) "*** $raw(nick) has changed the topic on channel $channel to $last\x0f\x0f"
}

proc raw_NICK { } {
  global raw nickname lastnickname
  set last "[string range "[cutwords "$raw(line)" 2]" 1 end]"
  RenameChannelUser "$raw(nick)" "$last"
  if {[strcmp "$raw(nick)" "$nickname"] == 0} {
    set lastnickname "$nickname"
    set nickname "$last"
    UpdateAllInfos
  }
  ExecOnCommands nick newnick "$last"
}

proc raw_KICK { } {
  global raw margin nickname chan on_args
  set channel "[lindex "$raw(list)" 2]"
  if {[GetDestWin "$channel"] == -1} { return }
  set cnum [GetChannelNumber "$channel"]

  set victim "[lindex "$raw(list)" 3]"
  set last "[string range "[cutwords "$raw(line)" 4]" 1 end]"

  if {[strcmp "$nickname" "$victim"]} {
    set margin(text) "kick"
    print2channels $cnum "*** $victim has been kicked off channel $channel by $raw(nick) ($last\x0f)\x0f\x0f"

    # Hier wird einem Kick möglicherweise vorhergehenden Ban die
    # Kickmeldung als Kommentar zugefügt.
    set i [UserNumOfChannel $cnum "$victim"]
    if {$i != -1} {
      set address "[lindex "$chan($cnum,addresses)" $i]"
      if {[string length "$address"]} {
        set len [llength "$chan($cnum,banpatterns)"]
        for {set j 0} {$j < $len} {incr j} {
          if {[strmatch "[lindex "$chan($cnum,banpatterns)" $j]" "$victim!$address"] && [expr [clock seconds]-[lindex "$chan($cnum,bantimes)" $j]] < 10} {
	    set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $j $j "$last"]"

	    if {[winfo exists .banlist]} {
	      global banlist bancomments
	      if {[strcmp "$banlist(channel)" "$chan($cnum)"] == 0} {

		set k [lsearch -exact "$banlist(new)" "[lindex "$chan($cnum,banpatterns)" $j]"]
		if {$k != -1} {
		  MultiListbox_Delete .banlist.list 2 $k
		  MultiListbox_Insert .banlist.list 2 $k "[lindex "$chan($cnum,banpatterns)" $j]" "[riddletext "$last"]"
		  set bancomments(new) "[lreplace "$bancomments(new)" $k $k "$last"]"
		}
		set k [lsearch -exact "$banlist(old)" "[lindex "$chan($cnum,banpatterns)" $j]"]
		if {$k != -1} {
		  set bancomments(old) "[lreplace "$bancomments(old)" $k $k "$last"]"
		}
	      }
	    }
	    break
	  }
        }
      }
      RemoveChannelUser $cnum $i $victim
    }
  } else {
    set margin(text) "kick"
    print2channels $cnum "*** You have been kicked off channel $channel by $raw(nick) ($last\x0f)\x0f\x0f"
    DeleteChannel $cnum

    global request_on_kick
    if {$request_on_kick} {
      request "Do you want to rejoin channel $channel?" "Cancel|" "Join|send2tkirc $on_args(window) \"[expandescape "[expand "/join $channel"]"]\""
    }
  }
  ExecOnCommands kick channel "$channel" victim "$victim" message "$last"
}

proc raw_MODE { } {
  global raw cooked nickname on_args margin
  if {[strcmp "$nickname" "[lindex "$raw(list)" 2]"]} {
    set channel "[lindex "$raw(list)" 2]"
    if {[GetDestWin "$channel"] == -1} { return }
    set cnum [GetChannelNumber "$channel"]

    set modes "[cutwords "$raw(line)" 3]"
    SetChannelModes $cnum "$modes" 1 "$raw(nick)!$raw(address)"
    UpdateInfos $on_args(window)

    set margin(text) "mode"
    set tmp "*** Mode change \"$modes\" on channel $channel by"
    if {[string length "$raw(nick)"]} {
      set cooked(line) "$tmp $raw(nick)\x0f\x0f"
    } else {
      set cooked(line) "$tmp $raw(address)\x0f\x0f"
    }
  } else {
    set modes "[string range "[cutwords "$raw(line)" 3]" 1 end]"
    SetUserModes "$modes"

    set margin(text) "mode"
    set tmp "*** Mode change \"$modes\" for user $nickname by"
    set cooked(line) "$tmp $raw(address)\x0f\x0f"
  }
}

proc raw_INVITE { } {
  global raw cooked nickname away crapwindow margin
  if {[strcmp "$nickname" "[lindex "$raw(list)" 2]"] == 0} {
    if {[string length "$away"]} {
      global beep_on_invite_when_away
      if {$beep_on_invite_when_away} {
        beep
      }
    } else {
      global beep_on_invite_when_present
      if {$beep_on_invite_when_present} {
        beep
      }
    }
    set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"
    ExecOnCommands invite channel "$last"

    global request_on_invite
    if {$request_on_invite} {
      request "$raw(nick) ($raw(address)) invites you to channel $last. Do you want to join that channel?" "Cancel|" "Join|send2tkirc $crapwindow \"[expandescape "[expand "/wjoin $last"]"]\""
    }
    set margin(text) "invite"
    global filternext ; set filternext 1
    set cooked(line) "*** $raw(nick) ($raw(address)) invites you to channel $last\x0f\x0f"
  }
}

proc parseons {line} {
  global nickname ownaddress channel away destlog margin
  global messagewindow on_args win linetype raw

  set linetype 0

  # Eigene Hilfsinformationen durch '/on'
  set list "[line2list "$line"]"
  set type "[string range "[lindex "$list" 0]" 1 end]"
  switch -exact -- "$type" {
    {send_public} {
      set to [lindex "$list" 1]
      set destlog "$to"
      set last "[cutwords "$line" 2]"

      set raw(line) "$nickname!$ownaddress PRIVMSG $to :$last"

      if {[GetDestWin "$to"] == -1} {
	set on_args(window) $messagewindow
	return "<$nickname»$to>\t$last"
      } else {
	global show_only_background_channels
	if {[llength "$win($on_args(window),channels)"] < 2 \
	 || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
	  return "<$nickname>\t$last"
	} else {
	  return "<$nickname+$to>\t$last"
	}
      }
    }
    {send_msg} {
      set linetype 1
      set to [lindex "$list" 1]
      set destlog "$to"
      set last "[cutwords "$line" 2]"

      set raw(line) "$nickname!$ownaddress PRIVMSG $to :$last"

      print2log "<messages>" "*»$to* $last"
      set on_args(window) [GetMsgWinFromNick $to 0]
      return "*»$to*\t$last"
    }
    {send_dcc_chat} {
      set linetype 1
      set to [lindex "$list" 1]
      set destlog "$to"
      set last "[cutwords "$line" 2]"

      print2log "<messages>" "=»$to= $last"
      set on_args(window) [GetMsgWinFromNick =$to 0]
      return "=»$to=\t$last"
    }
    {dcc_chat} {
      set linetype 1
      set from [lindex "$list" 1]
      set destlog "$from"
      set last "[cutwords "$line" 2]"

      print2log "<messages>" "=$from= $last"
      set on_args(window) [GetMsgWinFromNick =$from 0]
      if {[string length "$away"]} {
        global beep_on_message_when_away
	if {$beep_on_message_when_away} {
	  set next(beep) 1
	}
      } else {
        global beep_on_message_when_present
	if {$beep_on_message_when_present} {
	  set next(beep) 1
	}
      }
      return "=$from=\t$last"
    }
    {send_action} {
      set to [lindex "$list" 1]
      set destlog "$to"
      set last "[cutwords "$line" 2]"

      set raw(line) "$nickname!$ownaddress PRIVMSG $to :\x01\ACTION $last\x01"

      if [regexp -- {^(\#|&|\+).*} "$to"] {
	if {[GetDestWin "$to"] == -1} {
	  set on_args(window) $messagewindow
	  return "* $nickname»$to\t$last"
	} else {
	  global show_only_background_channels
	  if {[llength "$win($on_args(window),channels)"] < 2 \
	   || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
	    return "* $nickname\t$last"
	  } else {
	    return "* $nickname+$to\t$last"
	  }
	}
      } else {
        set linetype 1
	set destlog "$to"
	print2log "<messages>" "**»$to $nickname $last"
	set on_args(window) [GetMsgWinFromNick $to 0]
	return "**»$to\t$nickname $last"
      }
    }
    {send_notice} {
      set to [lindex "$list" 1]
      set destlog "$to"
      set last "[cutwords "$line" 2]"

      set raw(line) "$nickname!$ownaddress NOTICE $to :$last"

      if [regexp -- {^(\#|&|\+).*} "$to"] {
	if {[GetDestWin "$to"] == -1} {
	  set on_args(window) $messagewindow
	  return "-$nickname»$to-\t$last"
	} else {
	  global show_only_background_channels
	  if {[llength "$win($on_args(window),channels)"] < 2 \
	   || $show_only_background_channels && [strcmp "$win($on_args(window),actual)" "$to"] == 0} {
	    return "-$nickname-\t$last"
	  } else {
	    return "-$nickname+$to-\t$last"
	  }
	}
      } else {
	print2log "<messages>" "+»$to+ $last"
	set on_args(window) [GetMsgWinFromNick $to 0]
	return "+»$to+\t$last"
      }
    }
    {disconnect} {
      Disconnect
    }
    {notify_signon} {
      DetectSign [lindex "$list" 1] 1 0
      return ""
    }
    {notify_signoff} {
      DetectSign [lindex "$list" 1] 0 0
      return ""
    }
  }
  return "$line"
}

proc AddToFilterQueue {pattern} {
  global filterqueue
  lappend filterqueue "[string tolower "$pattern"]"
  lappend filterqueue "[clock seconds]"
}

proc AddToWhoQueue {channel num message1 message2} {
  global whoqueue margin

  lappend tmp "$channel" "$num" "$message2"
  lappend whoqueue "$tmp"
  if {[string length "$message1"]} {
    set margin(text) "note"
    print2text $num "$message1"
  }
  if {[llength "$whoqueue"] <= 1} {
    send2irc "/who $channel"
  }
}

proc AddToWhoisQueue {nick num command message} {
  # command2 and message2 are mutual exclusiv for the error case
  global whoisqueue margin

  lappend tmp "$nick" "$num" "$command"
  lappend whoisqueue "$tmp"
  if {[string length "$message"]} {
    set margin(text) "note"
    print2text $num "××× $message"
  }
  if {[llength "$whoisqueue"] <= 1} {
    send2irc "/whois $nick"
  }
}

proc AddToWhowasQueue {nick num command message} {
  # command2 and message2 are mutual exclusiv for the error case
  global whowasqueue margin

  lappend tmp "$nick" "$num" "$command"
  lappend whowasqueue "$tmp"
  if {[string length "$message"]} {
    set margin(text) "note"
    print2text $num "××× $message"
  }
  if {[llength "$whowasqueue"] <= 1} {
    send2irc "/whowas $nick"
  }
}

#######################################################################
#  COMPLETION OF NICKNAMES OR CERTAIN WORDS AND REPLACING OF ALIASES  #
#######################################################################

proc CompleteOrReplace {num} {
  global chan nickname margin
  global nick_completion_mode nick_completion_suffix
  global nick_completion_prefer_number nick_completion_prefer_period

  set path "[GetPathFromNum $num]"
  set oldline "[$path.cmdline get]"
  set insert "[$path.cmdline index insert]"
  set left "[string range "$oldline" 0 [expr $insert-1]]"
  set right "[string range "$oldline" $insert end]"
  set lastspace [string last " " "$left"]
  if {$lastspace != -1} {
    # space found
    set pattern "[string range "$left" [expr $lastspace+1] end]"
  } else {
    # no space, add colon and space
    set pattern "$left"
  }
  set last [expr [string length "$pattern"] - 1]

  set channel "[GetActual $num]"
  if {"$channel" != "*"} {
    set cnum [GetChannelNumber "$channel"]

    if {$nick_completion_mode > 1} {
      for {set i 0} {$i < [llength "$chan($cnum,ctimes)"]} {incr i} {
        if {[expr [clock seconds]-[lindex "$chan($cnum,ctimes)" $i]] > $nick_completion_prefer_period} {
	  break
        }
      }
      set nicks2prefer $i
    } else {
      set nicks2prefer $nick_completion_prefer_number
    }

    set matches ""
    for {set i 0} {$i < $nicks2prefer} {incr i} {
      if {[string length "$pattern"] == 0} {
	# Wenn kein Buchstabe angegeben wird, dann wird der letzte
        # Gesprächspartner genommen.
        lappend matches 0
	break
      }
      set x "[lindex "$chan($cnum,cnicks)" $i]"
      if {[strcmp "$pattern" "[string range "$x" 0 $last]"] == 0} {
        if {[strcmp "$x" "$nickname"]} {
	  lappend matches $i
	}
      }
    }

    if {[llength "$matches"] != 1} {
      # Kein Nickname konnte bis jetzt eindeutig bestimmt werden.
      if {[string length "$pattern"] == 0} {
	beep
	return
      }
      set len [llength "$chan($cnum,cnicks)"]
      for {set i $nicks2prefer} {$i < $len} {incr i} {
        set x "[lindex "$chan($cnum,cnicks)" $i]"
        if {[strcmp "$pattern" "[string range "$x" 0 $last]"] == 0} {
          if {[strcmp "$x" "$nickname"]} {
	    lappend matches $i
	  }
	}
      }
    }

    set len [llength "$matches"]
    if {$len == 1} {
      set x "[lindex "$chan($cnum,cnicks)" $matches]"
      if {$lastspace != -1} {
        $path.cmdline delete $lastspace $insert
        $path.cmdline insert $lastspace " [expandescape "$x"]"
      } else {
        $path.cmdline delete 0 $insert
        $path.cmdline insert $lastspace "[expandescape "$x"]$nick_completion_suffix"
      }
      set i [lsearch -exact "$chan($cnum,cnicks)" "$x"]
      if {$i != -1} {
	set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i]"
	set chan($cnum,ctimes) "[lreplace "$chan($cnum,ctimes)" $i $i]"
	set chan($cnum,cnicks) "[linsert "$chan($cnum,cnicks)" 0 "$x"]"
	set chan($cnum,ctimes) "[linsert "$chan($cnum,ctimes)" 0 [clock seconds]]"
      }
      return
    } elseif {$len > 1} {
      # Mehrere Nicks passen.
      beep
      set cmpnick "[lindex "$chan($cnum,cnicks)" [lindex "$matches" 0]]"

      # Der getippte Nick soll so weit wie möglich (bis i-1) 
      # erweitert werden.
      set equal 42
      foreach x "$matches" {
        set nick "[lindex "$chan($cnum,cnicks)" $x]"

        for {set i 0} {$i < [string length "$nick"]} {incr i} {
          set char1 "[string tolower "[string index "$nick" $i]"]"
          set char2 "[string tolower "[string index "$cmpnick" $i]"]"
          if {"$char1" != "$char2"} {
            break
          }
        }
        if {$i < $equal} {
          set equal $i
        }
      }
      if {$lastspace != -1} {
        $path.cmdline delete $lastspace $insert
        $path.cmdline insert $lastspace " [expandescape "[string range "$cmpnick" 0 [expr $equal-1]]"]"
      } else {
        $path.cmdline delete 0 $insert
        $path.cmdline insert $lastspace "[expandescape "[string range "$cmpnick" 0 [expr $equal-1]]"]"
      }

      set text ""
      foreach x "$matches" {
        append text "[lindex "$chan($cnum,cnicks)" $x] "
      }
      set margin(text) "note"
      print2text $num "××× Matching nicknames: $text"
      return
    }
  }
  if {[string length "$pattern"]} {
    global words_to_complete
    foreach x "$words_to_complete" {
      if {[strcmp "$pattern" "[string range "$x" 0 $last]"] == 0} {
        if {$lastspace != -1} {
          $path.cmdline delete $lastspace $insert
	  $path.cmdline insert $lastspace " $x"
        } else {
          $path.cmdline delete 0 $insert
          $path.cmdline insert $lastspace "$x"
        }
        return
      }
    }

    global tab_aliases
    set len [llength "$tab_aliases"]
    for {set i 0} {$i < $len} {incr i} {
      set entry "[lindex "$tab_aliases" $i]"
      if {[strcmp "$pattern" "[lindex "$entry" 0]"] == 0} {
        if {$lastspace != -1} {
          $path.cmdline delete $lastspace $insert
	  $path.cmdline insert $lastspace " [lindex "$entry" 1]"
        } else {
          $path.cmdline delete 0 $insert
          $path.cmdline insert $lastspace "[lindex "$entry" 1]"
        }
        return
      }
    }
  }
  beep
}

#############
#  HISTORY  #
#############

proc AddToMsgHistory {nick} {
  global msghistory msghistorynum msghistory_max

  set i [lsearch -exact "$msghistory" "$nick"]
  if {$i != -1} {
    set msghistory "[lreplace "$msghistory" $i $i]"
  }
  lappend msghistory "$nick"
  if {[llength "$msghistory"] > $msghistory_max} {
    set msghistory "[lreplace "$msghistory" 0 0]"
  }
  set msghistorynum "[llength "$msghistory"]"
}

proc MsgHistoryUp {num} {
  global msghistory msghistorynum win

  set win($num,hsize) 0
  set path "[GetPathFromNum $num]"
  $path.cmdline delete 0 end
  if {$msghistorynum > 0} {
    set msghistorynum [expr $msghistorynum - 1]
    $path.cmdline insert 0 "/msg [lindex "$msghistory" $msghistorynum] "
  } else {
    set msghistorynum [llength "$msghistory"]
  }
}

proc AddToHistory {num line} {
  global win history_max

  set len [llength "$win($num,history)"]
  if {[string compare "$line" "[lindex "$win($num,history)" [expr $len-1]]"]} {
    lappend win($num,history) "$line"
    if {[expr $len+1] > $history_max} {
      set win($num,history) "[lreplace "$win($num,history)" 0 0]"
    }
  }
  set win($num,hsize) "[llength "$win($num,history)"]"
}

proc HistoryUp {num} {
  global win msghistorynum

  set msghistorynum 0
  set path "[GetPathFromNum $num]"
  if {$win($num,hsize) == [llength "$win($num,history)"]} {
    set win($num,history2) "[$path.cmdline get]"
  }
  $path.cmdline delete 0 end
  if {$win($num,hsize) > 0} {
    set win($num,hsize) [expr $win($num,hsize) - 1]
    $path.cmdline insert 0 "[lindex "$win($num,history)" $win($num,hsize)]"
  } else {
    set win($num,hsize) [llength "$win($num,history)"]
    $path.cmdline insert 0 "$win($num,history2)"
  }
#  $path.cmdline icursor 0
}

proc HistoryDown {num} {
  global win msghistorynum

  set msghistorynum 0
  set path "[GetPathFromNum $num]"
  if {$win($num,hsize) == [llength "$win($num,history)"]} {
    set win($num,history2) "[$path.cmdline get]"
  }
  $path.cmdline delete 0 end
  if {$win($num,hsize) < [llength "$win($num,history)"]} {
    incr win($num,hsize)
  } else {
    set win($num,hsize) 0
  }
  if {$win($num,hsize) == [llength "$win($num,history)"]} {
    $path.cmdline insert 0 "$win($num,history2)"
  } else {
    $path.cmdline insert 0 "[lindex "$win($num,history)" $win($num,hsize)]"
  }
#  $path.cmdline icursor 0
}

######################################################################
#                       IDLE TIME MANAGEMENT                         #
######################################################################

proc InitIdleTime { } {
  global secs margin
  set secs(idle) [clock seconds]

  global auto_unmark_away away send_away_notice automatic_away
  if {"$away" != ""} {
    if {$auto_unmark_away > 1 \
     || $auto_unmark_away == 1 && "$away" == " (autoaway)"} {
      if {$send_away_notice} {
	set away ""
	set margin(text) "away"
	print2crap "*** You are no longer marked as being away"
	UpdateAllTitles
      } elseif {$automatic_away != 1} {
	set automatic_away 1
	send2irc "/away"
      }
    }
  }
}

proc Secondly { } {
  global secs margin sendqueue

  global auto_mark_away auto_away_period auto_away_text away san
  global send_away_notice automatic_away
  if {$auto_mark_away && "$away" == ""} {
    set i [expr $auto_away_period + $secs(idle)]
    if {[clock seconds] >= $i && $i > $secs(lastview)} {
      if {$send_away_notice} {
	set san(nicks) ""
	set san(times) ""
	set san(message) "$auto_away_text"
	set away " (autoaway)"
	set margin(text) "away"
	print2crap "*** You automatically have been marked as being away"
	UpdateAllTitles
      } else {
	set automatic_away 1
	send2irc "/away $auto_away_text"
      }
    }
  }

  if {[llength "$sendqueue"]} {
    send2irc "[lindex "$sendqueue" 0]"
    set sendqueue "[lreplace "$sendqueue" 0 0]"
  }
  set secs(lastview) [clock seconds]
  after 1000 Secondly
}

######################################################################
#                     INITIALIZATION OF ircII                        #
######################################################################

proc InitClient { } {
  AddToFilterQueue {\*\*\*?Value of DISPLAY set to OFF}
  send2irc "/set DISPLAY off"
  send2irc "/set SHOW_NUMERICS on"
  send2irc "/set NOVICE off"
  send2irc "/set NO_CTCP_FLOOD on"
  send2irc "/set EIGHT_BIT_CHARACTERS ON"
  send2irc "/set SHOW_CHANNEL_NAMES OFF"
  send2irc "/set VERBOSE_CTCP ON"

  send2irc {/on #^send_public 0 * echo %send_public $0-}
  send2irc {/on #^send_action 0 * echo %send_action $0-}
  send2irc {/on #^send_msg 0 * echo %send_msg $0-}
  send2irc {/on #^send_notice 0 * echo %send_notice $0-}

  send2irc {/on #-raw_irc 0 "*" if ([$1]!=[PING]) {if ([$1]!=[303]) {echo ~raw $0-}}}
#  send2irc {/on #-raw_irc 0 "*" echo ~raw $0-}

  send2irc {/on #^dcc_chat 0 * echo %dcc_chat $0-}
  send2irc {/on #^send_dcc_chat 0 * echo %send_dcc_chat $0-}
  send2irc {/on #^notify_signon 0 * echo %notify_signon $0-}
  send2irc {/on #^notify_signoff 0 * echo %notify_signoff $0-}

  foreach x "join leave signoff topic nickname mode kick" {
    send2irc "/on #^$x 0 * -"
  }

  send2irc {/alias squery ${K}${K}quote squery $0 :$1-}
  send2irc {/alias servlist ${K}${K}quote servlist $*}
  send2irc {/alias noteserv ${K}${K}quote squery noteserv :$*}

  send2irc "/set DISPLAY on"
}

proc ServerIsAvailable { } {
  global newircpath preferred_nicknames nickname ircserver
  global ircrc ircpath arguments ip startup crapwindow
  global tcl_version tk_version ask4server

  set ask4server 0
  MainWindow -1

  if {$ircrc == 0} {
    set result [catch {open "|$ircpath -d -q $arguments" r+} ip]
  } else {
    set result [catch {open "|$ircpath -d $arguments" r+} ip]
  }
  if {$result} {
    # ircII konnte nicht erfolgreich aufgerufen werden.
    puts stdout "Error executing \"$ircpath -d -q $arguments\" - $ip"
    exit
  }
  fconfigure $ip -blocking 0

  global startup
  if {$startup < 1} {
    on_tkircstart
    set startup 1
  }
  InitClient
  if {$startup < 2} {
    on_ircIIstart
    set startup 2
  }

  if {[gets $ip line] >= 0} {
    print2text $crapwindow "$line"
  }
  if [eof $ip] {
    catch {close $ip} result
    exit
  }
  fileevent $ip readable "irc2text"

  # Lieber noch einmal auf schnellere Versionen von Tcl/Tk hinweisen!
  if {$tcl_version < 8.0 || $tk_version < 8.0} {
    send2irc "/echo *** Hint: If possible, use Tcl/Tk versions greater or equal 8.0. You will notice the higher speed!" 
  }


  global path
  if {[info exists path(default)]} {
    cd "$path(default)"
  }
}

######################################################################
#                               MAIN                                 #
######################################################################

global tcl_version tk_version starttime
if ![info exists starttime] {
  # Das Programm wurde gerade gestartet. Nachdem die Versionen von Tcl/Tk
  # überprüft werden, werden ein paar Standardeinstellungen vorgenommen.
  if {$tcl_version < 7.5} {
    puts stdout "Error: Version of Tcl is lower than 7.5"
    exit
  }
  if {$tk_version < 4.1} {
    puts stdout "Error: Version of Tk is lower than 4.1"
    exit
  }
  foreach x "env(IRCNICK) env(USER) env(LOGNAME)" {
    if [info exists $x] {
      set nickname "[set $x]"
      break
    }
  }

  # Der folgende Befehl macht auf manchen Systemen Probleme.  ?:^|
  catch {fconfigure stdout -blocking 0}

  set secs(idle) [set secs(lastview) [set secs(start) [clock seconds]]]
  set starttime "[clock format $secs(start) -format "%d.%m.%y %H:%M:%S"]"
  Secondly
  after [expr 60000*42] "huibu 1"

  # Das versteckte Hauptfenster und die Widgets werden vorbereitet.
  wm geometry . 1x1+0+0
  wm overrideredirect . 1

  bind Listbox <Button-2> "[bind Listbox <Button-1>];break"
  bind Listbox <B2-Motion> "[bind Listbox <B1-Motion>];break"
  bind Listbox <Shift-Button-2> "[bind Listbox <Shift-Button-1>];break"
  bind Listbox <Control-Button-2> "[bind Listbox <Control-Button-1>];break"
  bind Listbox <Button-3> "[bind Listbox <Button-1>];break"

  # Für tkirc bestimmte Argumente werden herausgefiltert, und der Rest
  # wird ircII übergeben.
  set arguments ""
  set tkircrc ""
  set newircpath ""
  set quiet 0
  set ircrc 0
  set ask4server 0
  set len [llength "$argv"]
  for {set i 0} {$i < $len} {incr i} {
    set x "[lindex "$argv" $i]"
    switch -- "$x" {
      "-x" {
	# Option '-x' dient zur Auswahl des ircII-Pfades.
	incr i
	set newircpath "[lindex "$argv" $i]"
      }
      "-t" {
	# Option '-t' dient zur Auswahl eines tkircrc-Pfades.
	incr i
	set tkircrc "[lindex "$argv" $i]"
      }
      "-q" {
	# Das tkircrc soll nicht beim Start eingeladen werden.
	set quiet 1
      }
      "-r" {
	# Das File .ircrc soll berücksichtigt werden.
	set ircrc 1
      }
      "-a" {
        # Der Server soll via GUI gewählt werden.
        set ask4server 1
      }
      default {
	append arguments "$x "
      }
    }
  }

  if {$quiet == 0} {
    foreach x "/usr/pkg/lib/tkirc/tkircrc /usr/local/lib/tkirc/tkircrc" {
      if {[file exists "$x"] && [file readable "$x"]} {
        ReloadTKircRC "$x"
	break
      }
    }
    ReloadTKircRC "$tkircrc"
  }
  InitStyles

  if {[string length "$newircpath"]} {
    set ircpath "$newircpath"
  }
  set arguments "[expand "$arguments"]"
  set len [llength "$arguments"]
  if {$len == 0} {
    if {[llength "$preferred_nicknames"]} {
      set nickname "[lindex "$preferred_nicknames" 0]"
    }
    set arguments "$nickname"
  }
  if {$len >= 1} {
    set nickname "[lindex "$arguments" 0]"
  }
  if {$len >= 2} {
    set server "[lindex "$arguments" 1]"
    ServerIsAvailable
  } else {

    if {$ask4server} {
      ServersWindow
    } else {
      if {[string length "$ircserver"]} {
        set server "$ircserver"
        append arguments " $ircserver"
      }
      ServerIsAvailable
    }
  }

} else {
  # Eine neue Version des Programmes wurde gerade nachgeladen.
  set_client_information
}
