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

#############################################################################
#                                                                           #
#  Copyright (C) 1996-97  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.151 c"
set date         "12.11.97"

######################################################################
#            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 user_styles {
  {{^(\*|\+|\*\*|\=).*} {-foreground #00aa00} {}}
  {{^((<|-)$me(>|-\	||\+)|\* $me(	||\+)).*} {-foreground #007700} {}}
  {{^(\*|\+|\*\* |\=)[^ \*\+].*} {-foreground #dd0000} {}}

  {{^(\*\*\*|\[ notify \]).Sign(on|off) .*} {-foreground #cc9900} {}}
  {{^(.*[^a-zA-Z0-9]|)$me(|[^a-zA-Z0-9].*)$} {-foreground #aa0000} {}}
  {{^(\( |)([0-9][0-9][0-9])(| \)).*} {-foreground #440044} {}}

  {{^(\*\*\*|\[ signoff \]).+ has signed off \([^ ]+\.[^ .]+ [^ .]+\.[^ ]+\)$} {-foreground #ff5500} {}}
  {{^(\\\.|\[ )Net(split|join).*} {-foreground #ff5500} {}}
  {{^(\\\|\+\+\+|\[ (alert|error|failure|note|warning) \]).*} {-foreground #aa0000} {}}
  {{^(\*\*\*|\[ ).*} {-foreground #000066} {}}
}

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

set margin(text)    ""

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_awayreasons {"Be back later"}
set preferred_kickreasons {"No flooding"}
set preferred_servers {{"irc.colorado.edu" 6667} {"irc.texas.net" 6667}}
set preferred_nicknames {}

set nick_completion_suffix ": "

set notifies           {{"atte" "*atte@gecko.North.DE"}}
set noteserv(filterls) 0
set noteserv(after)    ""
set noteserv(spies)    ""

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 file '~/.tkircrc'."}
set on_msgclick {global margin ; set margin(text) "note" ; print2crap " Please set the variable 'on_msgclick' in file '~/.tkircrc'."}

set words_to_complete {}
set private_commands {}
set entry_bindings {}
set escape_sign "^"

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 ReloadTKircRC {file} {
  global win margin

  set tmp ""
  lappend tmp \
    crapwindow messagewindow \
    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_notifies send_away_notice \
    auto_mark_away auto_away_period auto_away_text auto_unmark_away \
    history_max lines_max ircpath \
    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_awayreasons preferred_kickreasons preferred_servers \
    notifies on_urlclick on_msgclick \
    words_to_complete nick_completion_suffix user_styles \
    private_commands tab_aliases escape_sign ircserver channelhop_period \
    noteserv

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

  for {set i 0} {$i < 12} {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 "~/.tkircrc"]} {
    source "~/.tkircrc"
  } elseif {[file exists "~/.tkirc/tkircrc"]} {
    source "~/.tkirc/tkircrc"
  }

  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 "$win(list)" {
    ConfigureStyles $x
  }
}

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

set nickname        ""

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

set commandqueue    ""
set whoqueue        ""
set whoisqueue      ""
set whowasqueue     ""
set filterqueue     ""
set filternext      0
set direct2crap     0
set messagewindow   0
set crapwindow      0
set debugwindow     -1
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 signed_userlist ""

set msghistory      ""
set msghistorynum   0
set msghistory_max  5

set underline_style "-underline on"
# reverse_styles werden nachtrglich 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-Besttigung
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
#  puts stdout "$lines_all: $text"
#  flush stdout
  return
}

#####################
#  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 noteserv_startup { } {
  global noteserv server

  # Sind fr den aktuellen Server alle NoteServ-Eintrge vorhanden?
  if {[info exists noteserv($server,login)] && [info exists noteserv($server,password)] && [info exists noteserv($server,spies)]} {
    if {[string length "$noteserv($server,login)"]} {
      if {[string length "$noteserv($server,password)"] == 0} {
        # Zu dem Login wurde kein Passwort angegeben.
        set margin(text) "failure" ; print2crap " NoteServ: No password given for server $server"
      } else {
        # Login und ein LS zum Vergleich der SPY-Liste werden durchgefhrt.
        send2irc "/quote squery noteserv :login $noteserv($server,login) $noteserv($server,password)"
#        set noteserv(filterls) 1
#        send2irc "/quote squery noteserv :ls"
      }
    } else {
      # Der NoteServ kann auch mit einem leeren Login-Eintrag benutzt 
      # werden.
      for {set i 0} {$i < [llength "$noteserv($server,spies)"]} {incr i} {
        send2irc "/quote squery noteserv :spy +0 [lindex "$noteserv($server,spies)" $i]"
      }
    }
  }
}

proc noteserv_update { } {
  global noteserv server

  set noteserv(after) ""
  set noteserv(filterls) 0

  # Sind SPY-Eintrge entfernt worden?
  for {set i 0} {$i < [llength "$noteserv(spies)"]} {incr i} {
    if {[lsearch -exact "$noteserv($server,spies)" "[lindex "$noteserv(spies)" $i]"] == -1} {
      send2irc "/quote squery noteserv :rm [lindex "$noteserv(spies)" $i]"
    }
  }
  # Sind SPY-Eintrge hinzugefgt worden?
  for {set i 0} {$i < [llength "$noteserv($server,spies)"]} {incr i} {
    if {[lsearch -exact "$noteserv(spies)" "[lindex "$noteserv($server,spies)" $i]"] == -1} {
      send2irc "/quote squery noteserv :spy +0 [lindex "$noteserv($server,spies)" $i]"
    }
  }
}

proc beep { } {
  global prefs

  if !$prefs(s) {
    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 prefs

  if {$linetype} {
    if {"$away" == "" && $prefs(t1)} {
      return " ([time])"
    } elseif {"$away" != "" && $prefs(t2)} {
      return " ([time])"
    }
  } else {
    if {"$away" == "" && $prefs(t3)} {
      return " ([time])"
    } elseif {"$away" != "" && $prefs(t4)} {
      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 fr 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 (unabhngig 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 pat.
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 {"\\\\" "\\\"" "\\\{" "\\\}" "\\\[" "\\\]" "\\\|" "\\\^"}

  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+1] end]"
	set j [string first "[lindex "$slist" $i]" "$line"]
      }
      set line "$newline$line"
    }
  }
  return "$line"
}

# expand2: Bestimmten Zeichen wie z.B. runde und eckige Klammern wird
#          ein "\" vorangestellt, damit Tcl keine Probleme damit bekommt.
#          Zudem wird auch das Zeichen "$" nicht als Zeichen fr einen
#          Variablenwert interpretiert (fr MsgIDs und URLs).
proc expand2 {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+1] end]"
	set j [string first "[lindex "$slist" $i]" "$line"]
      }
      set line "$newline$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 rckgngig
#         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: Mglicherweise 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 lt tkirc 'sauber' zum Ende kommen. ircII wird
#       beendet, der Prozedur "on_signoff" wird der eigene Signoff 
#       mitgeteilt, und geffnete 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 "using ircII/tkirc"
  }
  ExecOnCommand 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 { } {
  global win
  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
    }
  }
  foreach x "channels query history history2" {
    set win($wnum,$x) ""
  }
  foreach x "visible lines hsize taghi" {
    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 actual taghi 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 -exact "$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 -exact "$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 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 fr 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

  if {$cnum == -1} {
    return
  }

  set channel "$chan($cnum)"
  set wnum $chan($cnum,window)

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

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

  foreach x "nicks names cnicks 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)
  }

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

  # Nachdem der Kanal ausgelscht wurde, mu evtl. das Fenster
  # aktualisiert werden.
  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)" "$channel"] == 0} {
      set win($wnum,actual) "*"
      if [llength "$win($wnum,channels)"] {
	set win($wnum,actual) "$chan([lindex "$win($wnum,channels)" 0])"
      }
    }
    UpdateInfos $wnum
  }
}

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"])]} {
    # Fr 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 gendert
  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" {
	  ExecOnCommand modechange to "$chan($cnum)" mode "$prefix$flag" \
	   argument "[lIndex "$parameter" $pcnt]"
	}
	"i|m|n|p|s|t" {
	  ExecOnCommand 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
	ExecOnCommand 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 Button {path args} {
  global button_font
  if {[string length "$button_font"]} {
    eval {button $path} -font $button_font $args
  } else {
    eval {button $path} $args
  }
}

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

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]
  }
  return $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
  }
}

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

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

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

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

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}
  return $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 prefs win

  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 {$prefs($num,m1)} {
      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 {$prefs($num,m1)} {
      $name.text tag configure tag_$num\_$i -lmargin2 $margin(size)
    } else {
      $name.text tag configure tag_$num\_$i -lmargin2 0
    }
  }

  # reverse styles werden hier nachtrglich 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 {$prefs($num,m1)} {
    $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 prefs

  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" "!?.,"]"
	  if {[string length "$suffix"] == 0} {
	    break
	  }
	  # 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 eingefgt werden.
	  $widget insert end "[string range "$line" 0 [expr $i-$xlen-1]]" $prestylenum
	  if {$prefs($num,m1)} {
	    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
	  }

	  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)
	  set line "[string range "$line" [expr $i-$xlen+[string length "$url"]] 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) $url_style"
	  $widget tag bind tag_$num\_$win($num,taghi) <ButtonPress> "global selected_url ; set selected_url \{[strreplace "[expand2 "$url"]" "%" "%%"]\} ; ExecUrlAction"
	  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)

	  if {$prefs($num,m1)} {
	    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 \{[expand2 "$possible"]\} ; ExecMsgIDAction"
	} 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 fr diese Quelle geffnet?
    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/geffnet.
      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 fr DeleteLog befindet sich in der Variablen
	  # "destlog". Das File konnte nicht geffnet 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 print2text {num line args} {
  global win lines_max user_styles nickname style
  global prefs 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 }
  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 {$prefs($num,p) && [string compare "[wm state $path]" "iconic"] == 0} {
    wm deiconify $path
  }

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

  if {$prefs($num,m2)} {
    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"]"
  set enickname "[expand "$nickname"]"
  for {set i 0} {$i < [llength "$user_styles"]} {incr i} {
    set regexpr "[lindex "[lindex "$user_styles" $i]" 0]"
    set regexpr "[strreplace "$regexpr" "\$me" "$enickname"]"
    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 knnen?
    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"

    $widget tag bind tag_$num\_$win($num,taghi) <Button-1> "send2irc \"/whois [string range "$line" $next(left) [expr $next(right)-1]]\" ; break"
    $widget tag bind tag_$num\_$win($num,taghi) <Button-2> "send2irc \"/ctcp [string range "$line" $next(left) [expr $next(right)-1]] version\" ; break"
    $widget tag bind tag_$num\_$win($num,taghi) <Button-3> "send2tkirc $num \"/chat [expandescape [string range "$line" $next(left) [expr $next(right)-1]]]\" ; 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
	  if {[regexp -- {[0-9]} "[string index "$line" [expr $i+1]]"]} {
	    if {!$special_state} {
	      set style [expr $style + $special]
	      set special_state 1
	    }
	    incr i
	    if {[regexp -- {[0-9]} "[string index "$line" [expr $i+1]]"]} {
	      incr i
	    }
	    if {[regexp -- {[0-9]} "[string index "$line" [expr $i+1]]"]} {
	      incr i
	    }
	    if {[regexp -- {,[0-9]} "[string range "$line" [expr $i+1] [expr $i+2]]"]} {
	      incr i ; incr i
	      if {[regexp -- {[0-9]} "[string index "$line" [expr $i+1]]"]} {
		incr i
	      }
	      if {[regexp -- {[0-9]} "[string index "$line" [expr $i+1]]"]} {
		incr i
	      }
	    }
	  } 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"]} {
	      if {$prefs(b8)} {
		beep
	      }
	    } else {
	      if {$prefs(b7)} {
		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 fr 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 {"$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 gelscht.
  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} {
  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 230
  set header ""
  foreach x "[split "$line" "\n"]" {
    if {"$header" == "" && "[string index "$line" 0]" == "/"} {
      # Zeile enthlt 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]"
	}
	default {
	  # Kommando (auer msg und notice)
	  send2irc "[parsecl $num "[string range "$line" 0 $maxchars]"]"
	  return
	}
      }
    } else {
      # Zeile enthlt 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,cnicks) "[linsert "$chan($cnum,cnicks)" 0 "$nick"]"
	      }
	    }
	  }
	}
      } 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]..."]"
      set x "[string range "$x" [expr $cutnum+1] end]"
    }
    send2irc "[parsecl $num "$header$x"]"
  }
}

proc send2irc {line} {
  global ip crapwindow

  if {"$ip" != "" && "$line" != ""} {
debug "out: $line"
    puts $ip "$line\n"
    flush $ip
  }
}

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
#    if {[expr $lines_all%5] == 0} {
      update idletasks
#    }
    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 geffnet 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
  return [lsearch -exact "$chan($cnum,nicks)" "$nick"]
}

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" "" ""
    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.path.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.selected delete 0 end
	  $win.selected 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.path.entry delete 0 end
    $win.path.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.path.entry get]"
  set file "[$win.selected get]"

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

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

  set selected_file ""
  set command1 "[expand2 "$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.path
    pack $path.path -fill x
    Label $path.path.label -text "Path:"
    pack $path.path.label -side left
    Entry $path.path.entry
    pack $path.path.entry -side left -expand true -fill x
    bind $path.path.entry <Return> "FileSelect $path 1"

    set f $path.buttons
    frame $f
    pack $f -fill x -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

    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
    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"
    myfocus 1 $path.selected
  }
  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 "[expand2 "[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" "" ""
    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 message IDs"
    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 {} [expand2 $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 [expand2 "$msgid"]" "" ""
    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 ExecOnCommand {type args} {
  global on_$type on_args

  for {set i 0} {$i < [llength "$args"]} {incr i ; incr i} {
    set on_args([lindex "$args" $i]) "[lindex "$args" [expr $i+1]]"
  }

  if {[info exists on_$type] && [string length "[set on_$type]"]} {
    eval [set on_$type]
  } elseif {[string length "[info commands "on_$type"]"]} {
    on_$type
  }
}

###################
#  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 "[expand2 "[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" "" ""
    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} {
  global signed_userlist

  # Beim Signoff oder evtl. auch bei fehlerhaftem Signon mu
  # der schon vorhandene Nick aus der Liste entfernt werden.
  set i [lSearch "$signed_userlist" "$nick"]
  if {$i >= 0} {
    set signed_userlist "[lreplace "$signed_userlist" $i $i]"
    if {[winfo exists .not]} {
      .not.list.entries delete $i
    }
  }

  if {$type > 0} {
    if {[winfo exists .not]} {
      set end "[lindex "[.not.list.entries yview]" 1]"
      .not.list.entries insert end "$nick"
      if {$end == 1} {
	.not.list.entries yview end
      }
    }
    lappend signed_userlist "$nick"
  }
}

proc NotifyNickSelected {button} {
  global crapwindow

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

proc NotifyWindow { } {
  global signed_userlist

  if [catch {toplevel .not -class tkirc-request}] {
    raise .not
  } else {
    global geometry_notifies
    if {[info exists geometry_notifies]} {
      wm geometry .not $geometry_notifies
    }
    wm title .not  " tkirc: Notifies"
    bind .not <Escape> "closewindow .not"

    Button .not.close -text "Close" -command "closewindow .not"
    pack .not.close -side bottom -anchor se
    Label .not.info -text " Notified nicks " -relief flat
    pack .not.info

    set f .not
    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

    .not.list.entries delete 0 end
    set ulen [llength "$signed_userlist"]

    for {set i 0} {$i < $ulen} {incr i} {
      .not.list.entries insert end "[lindex "$signed_userlist" $i]"
    }
    bind $f.list.entries <Double-Button-1> "NotifyNickSelected 1"
    bind $f.list.entries <Double-Button-2> "NotifyNickSelected 2"
    bind $f.list.entries <Double-Button-3> "NotifyNickSelected 3"
  }
}

####################
#  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) - Freely distributable!\n  Andreas 'atte' Gelhausen, <atte@gecko.North.DE>"
    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

  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} {
  # zuletzt gendert am 21.10.97
  global chan nickname

  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 debugwindow

  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"
  if {$messagewindow == $num} {
    append title "m"
  }
  if {$crapwindow == $num} {
    append title "c"
  }
  if {$debugwindow == $num} {
    append title "d"
  }
  set at ""
  if {$actual != -1} {
    if {[isOpOnChannel $actual "$nickname"]} {
      set at "@"
    }
  }
  append title " \] : "
  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"
}

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

proc PrintModeChars {num text} {
  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

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

    # Bei einem Disconnect wird der Kanalname auf "*" gesetzt.
    if {"$server" == "" && "[GetActual $num]" != "*"} {
      set win($num,actual) "*"
    }
    set channel [GetActual $num]
    set cnum [GetChannelNumber "$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 " Please select a nickname with \"/nick <nickname>\" now and set the variable 'preferred_nicknames' in your .tkircrc for the next time!"
      global filternext ; set filternext 1
    }
  } else {
    set nickname "$lastnickname"
    UpdateAllTitles
  }
}

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

  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 " $win($num,query)"
  }
  return "[string trimleft "[expand "$result"]" " "]"
}

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
#  } else {
#    tkListboxBeginSelect $W [$W index @$x,$y]
  }
}

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 to send via DCC!" "Send" "UserDSend $num \:file" "" ""
    } else {
      set len [lLength "$snicks"]
      for {set i 0} {$i < $len} {incr i} {
	send2irc "/dcc send [reduce [lIndex "$snicks" $i]] $args"
      }
    }
  }
}

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 notifies

  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 "$notifies"]} {incr i} {
      set entry "[lindex "$notifies" $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" {
	ExecOnCommand notify_signon nick "$nick" address "$address"
	set margin(text) "notify"
	print2crap "*** Signon by $nick ($address) detected at [time]"
      }
      "0" {
	ExecOnCommand notify_signoff nick "$nick" address "$address"
	set margin(text) "notify"
	print2crap "*** Signoff by $nick ($address) detected at [time]"
      }
    }
    UpdateNotifyWindow $type "$nick"
  }
}

##########
#  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"
	  }
	  # Zhler auch fr den Fall erhhen, da der User schon 
	  # gekickt, der Kick aber noch nicht besttigt wurde.
	  incr count
        }
      } else {
	# User ist lnger 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 "$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 BanListEntryWasSelected { } {
  set result ""

  .banlist.entries.entry delete 0 end
  set num "[.banlist.entries.list.view curselection]"
  if {"$num" != ""} {
    append result "[.banlist.entries.list.view get $num]"
  }
  .banlist.entries.entry insert 0 "$result"
}

proc UndoBanList { } {
  global banlist

  .banlist.entries.list.view delete 0 end
  for {set i 0} {$i < [llength "$banlist(old)"]} {incr i} {
    .banlist.entries.list.view insert end "[lindex "$banlist(old)" $i]"
  }
  set banlist(new) "$banlist(old)"
}

proc EditBanListEntry { } {
  global banlist

  set num "[.banlist.entries.list.view curselection]"
  if {"$num" != ""} {
    .banlist.entries.list.view delete $num
    set banlist(new) "[lreplace "$banlist(new)" $num $num]"
  }
}

proc AddBanListEntry { } {
  global banlist

  .banlist.entries.list.view delete 0 end
  lappend banlist(new) "[.banlist.entries.entry get]"
  for {set i 0} {$i < [llength "$banlist(new)"]} {incr i} {
    .banlist.entries.list.view insert end "[lindex "$banlist(new)" $i]"
  }
  .banlist.entries.entry delete 0 end
  .banlist.entries.list.view yview end
}

proc RemoveBanListEntry { } {
  global banlist

  set num "[.banlist.entries.list.view curselection]"
  if {"$num" != ""} {
    .banlist.entries.list.view delete $num
    set banlist(new) "[lreplace "$banlist(new)" $num $num]"
  }
  .banlist.entries.entry delete 0 end
}

proc CommitBanListChanges { } {
  global banlist

  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]"
    }
  }
  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 InitBanList {f} {
  # f is the frame

  Entry $f.entry
  pack $f.entry -fill x -side bottom
  myfocus 5 $f.entry
  bind $f.entry <Return> "AddBanListEntry"

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

  UndoBanList

  bind $f.list.view <ButtonRelease> "BanListEntryWasSelected"
  bind $f.list.view <B1-Motion> "BanListEntryWasSelected"
}

proc BanListWindow {num} {
  global chan banlist

  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)"

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

      Label .banlist.mid -text "   Edit banlist of channel $channel:   "
      pack .banlist.mid -side top -padx 2 -pady 2

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

      set f .banlist.buttons2
      frame $f -borderwidth 1 -relief sunken
      pack $f -side bottom -fill x -pady 2 -padx 2 -ipady 2 -ipadx 2
      Button $f.undo -text "Undo" -command "UndoBanList"
      Button $f.remove -text "Remove" -command "RemoveBanListEntry"
      pack $f.undo -side left
      pack $f.remove -side right

      frame .banlist.entries
      InitBanList .banlist.entries
      pack .banlist.entries -expand true -fill both
    }
  }
}

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

proc InitMenu {num} {
  global preferred_channels preferred_servers preferred_awayreasons
  global prefs
  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 "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 message" -menu $f.menu.beep.mes
  Menu $f.menu.beep.mes
  $f.menu.beep.mes add checkbutton -label "when present" -variable prefs(b1)
  $f.menu.beep.mes add checkbutton -label "when away" -variable prefs(b2)

  $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 prefs(b3)
  $f.menu.beep.not add checkbutton -label "when away" -variable prefs(b4)

  $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 prefs(b5)
  $f.menu.beep.inv add checkbutton -label "when away" -variable prefs(b6)

  $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 prefs(b7)
  $f.menu.beep.ctrlG add checkbutton -label "when away" -variable prefs(b8)

  $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 prefs(a1)
  $f.menu.show.mes add checkbutton -label "when away" -variable prefs(a2)
  $f.menu.show.mes add checkbutton -label "in logfile" -variable prefs(a3)

  $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 prefs(a4)
  $f.menu.show.not add checkbutton -label "when away" -variable prefs(a5)
  $f.menu.show.not add checkbutton -label "in logfile" -variable prefs(a6)

  $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 prefs(t1)
  $f.menu.time.priv add checkbutton -label "when away" -variable prefs(t2)

  $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 prefs(t3)
  $f.menu.time.pub add checkbutton -label "when away" -variable prefs(t4)

  $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 prefs(c1)
  $f.menu.chat.mes add checkbutton -label "when away" -variable prefs(c2)

  $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 prefs(c3)
  $f.menu.chat.not add checkbutton -label "when away" -variable prefs(c4)

  $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 prefs(r1)
  $f.menu.request add checkbutton -label " on dcc send" \
     -variable prefs(r2)
  $f.menu.request add checkbutton -label " on invite" \
     -variable prefs(r3)
  $f.menu.request add checkbutton -label " on kick" \
     -variable prefs(r4)

  $f.menu add checkbutton -label "Silence" -variable prefs(s)
  $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 prefs($num,h1)
  $f.menu.hide add checkbutton -label " leaves" \
      -variable prefs($num,h2)
  $f.menu.hide add checkbutton -label " signoffs" \
      -variable prefs($num,h3)

  $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 prefs($num,c)
  $f.menu.show2 add checkbutton -label " topic" \
    -command "AddOrRemoveTopic $num" -variable prefs($num,t)
  $f.menu.show2 add checkbutton -label " userlist" \
    -command "AddOrRemoveUserList $num" -variable prefs($num,u)

  $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 prefs($num,m1)
  $f.menu.margin add checkbutton -label " display types" \
    -variable prefs($num,m2)

  $f.menu add cascade -label "Sort userlist" -menu $f.menu.sortul
  Menu $f.menu.sortul
  $f.menu.sortul add checkbutton -label " alphabeticly" \
    -variable prefs($num,s1) -command "FillUserList $num -1"
  $f.menu.sortul add checkbutton -label " by channelmodes" \
    -variable prefs($num,s2) -command "FillUserList $num -1"

  $f.menu add checkbutton -label "Auto popup" -variable prefs($num,p)

  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

  # 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"
  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 cascade -label "connect to" -menu $f.menu.cnct
  Menu $f.menu.cnct
  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_nicknames
  global messagewindow crapwindow

  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\""
  }

  # 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
  set f $path.menu.server
  $f.menu.cnct delete 0 end
  foreach x "$preferred_servers" {
    $f.menu.cnct add command -label "[lindex "$x" 0] ([lindex "$x" 1])"\
      -command "send2irc \"/server [lindex "$x" 0] [lindex "$x" 1]\""
  }

  # Private menus
  if {"[info commands "add_to_menu_line"]" != ""} {
    add_to_menu_line $path.menu $num
  }
}

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} {
  global prefs

  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  if {$prefs($num,t)} {
    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} {
  global prefs

  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  if {$prefs($num,c)} {
    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 12 -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} {
  global prefs

  set path "[GetPathFromNum $num]"
  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  if {$prefs($num,u)} {
    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 prefs

  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

  switch -- "$prefs($num,s1)$prefs($num,s2)" {
    "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 prefs

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

  switch -- "$prefs($num,s1)$prefs($num,s2)" {
    "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. Kanle verlassen.
  foreach x "$win($num,channels)" {
    send2irc "/leave $chan($x)"
    DeleteChannel $x
  }
  DeleteWindow $num

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

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

  UpdateAllTitles
}

proc MainWindow {type} {
  # type: -1 opens new window (prefs will be noticed)
  # type: -2 opens new window without topic and userlist
  # type: -3 opens new window without topic, userlist and commandline
  # type: -4 opens new window with topic, userlist and commandline
  #     other values to redraw

  global win prefs margin geometry normal_style
  global beep_on_message_when_present beep_on_message_when_away
  global beep_on_notice_when_present beep_on_notice_when_away
  global beep_on_invite_when_present beep_on_invite_when_away
  global beep_on_ctrlG_when_present beep_on_ctrlG_when_away
  global show_address_on_message_when_present show_address_on_message_when_away
  global show_address_on_message_in_logfile show_address_on_notice_when_present
  global show_address_on_notice_when_away show_address_on_notice_in_logfile
  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
  global chat_window_on_message_when_present chat_window_on_message_when_away
  global chat_window_on_notice_when_present chat_window_on_notice_when_away
  global silence request_on_dcc_chat request_on_dcc_send request_on_invite
  global request_on_kick auto_popup hide_joins hide_leaves hide_signoffs
  global show_commandline show_topic show_userlist use_margin display_types
  global margin_size sort_userlist_alphabeticly sort_userlist_by_channelmodes

  if {$type < 0} {
    # Initialize variables
    set num [ProduceWindow]
  } else {
    # Just refresh window
    set num $type
  }

  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$num
  }

  if {$type < 0} {
    set prefs($num,h1) $hide_joins
    set prefs($num,h2) $hide_leaves
    set prefs($num,h3) $hide_signoffs
    set prefs($num,c) $show_commandline
    set prefs($num,t) $show_topic
    set prefs($num,u) $show_userlist
    set prefs($num,p) $auto_popup
    set prefs($num,m2) $display_types
    set prefs($num,m1) $use_margin
    set prefs($num,s1) $sort_userlist_alphabeticly
    set prefs($num,s2) $sort_userlist_by_channelmodes
    set margin(size) $margin_size
    set geo $geometry
  }

  if {[info exists sort_userlist_by_channelmodes$num]} {
    set prefs($num,s2) [set sort_userlist_by_channelmodes$num]
  }
  if {[info exists sort_userlist_alphabeticly$num]} {
    set prefs($num,s1) [set sort_userlist_alphabeticly$num]
  }
  if {[info exists use_margin$num]} {
    set prefs($num,m1) [set use_margin$num]
  }
  if {[info exists display_types$num]} {
    set prefs($num,m2) [set display_types$num]
  }
  if {[info exists auto_popup$num]} {
    set prefs($num,p) [set auto_popup$num]
  }
  if {[info exists hide_joins$num]} {
    set prefs($num,h1) [set hide_joins$num]
  }
  if {[info exists hide_leaves$num]} {
    set prefs($num,h2) [set hide_leaves$num]
  }
  if {[info exists hide_signoffs$num]} {
    set prefs($num,h3) [set hide_signoffs$num]
  }
  if {[info exists show_commandline$num]} {
    set prefs($num,c) [set show_commandline$num]
  }
  if {[info exists show_topic$num]} {
    set prefs($num,t) [set show_topic$num]
  }
  if {[info exists show_userlist$num]} {
    set prefs($num,u) [set show_userlist$num]
  }
  if {[info exists geometry$num]} {
    set geo [set geometry$num]
  }

  switch -- "$type" {
    "-2" {
      set prefs($num,t) 0
      set prefs($num,u) 0
    }
    "-3" {
      set prefs($num,t) 0
      set prefs($num,u) 0
      set prefs($num,c) 0
    }
    "-4" {
      set prefs($num,t) 1
      set prefs($num,u) 1
      set prefs($num,c) 1
    }
  }

  set path "[GetPathFromNum $num]"
  if {$type < 0} {
    toplevel $path -class tkirc -takefocus 0
    wm geometry $path $geo
    wm protocol $path WM_DELETE_WINDOW "CloseMainWindow $num"
    wm iconname $path tkirc\[$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"
    }
    if {$num == 0} {
      set prefs(b1) $beep_on_message_when_present
      set prefs(b2) $beep_on_message_when_away
      set prefs(b3) $beep_on_notice_when_present
      set prefs(b4) $beep_on_notice_when_away
      set prefs(b5) $beep_on_invite_when_present
      set prefs(b6) $beep_on_invite_when_away
      set prefs(b7) $beep_on_ctrlG_when_present
      set prefs(b8) $beep_on_ctrlG_when_away
      set prefs(a1) $show_address_on_message_when_present
      set prefs(a2) $show_address_on_message_when_away
      set prefs(a3) $show_address_on_message_in_logfile
      set prefs(a4) $show_address_on_notice_when_present
      set prefs(a5) $show_address_on_notice_when_away
      set prefs(a6) $show_address_on_notice_in_logfile
      set prefs(t1) $show_time_on_private_when_present
      set prefs(t2) $show_time_on_private_when_away
      set prefs(t3) $show_time_on_public_when_present
      set prefs(t4) $show_time_on_public_when_away
      set prefs(c1) $chat_window_on_message_when_present
      set prefs(c2) $chat_window_on_message_when_away
      set prefs(c3) $chat_window_on_notice_when_present
      set prefs(c4) $chat_window_on_notice_when_away
      set prefs(s) $silence
      set prefs(r1) $request_on_dcc_chat
      set prefs(r2) $request_on_dcc_send
      set prefs(r3) $request_on_invite
      set prefs(r4) $request_on_kick
    }
    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

  if {"$server" != ""} {
    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 ""
    InitUserModes
    UpdateAllInfos
    ExecOnCommand signoff window "$messagewindow" nick "$nickname" address "[AddressOfNick "$nickname"]" message ""
  }
}

proc HandleFakeNetjoin {time} {
  global pjoin margin raw chan

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

    # Die Anzeige des mglichen Netjoins mu noch nachgeliefert werden.
    set original "$raw(line)"

    for {set i 0} {$i < [llength "$pjoin(channels)"]} {incr i} {
      global prefs
      set cnum [lindex "$pjoin(channels)" $i]
      if {[info exists chan($cnum,window)]} {
        set j $chan($cnum,window)
	if {$j != -1 && $prefs($j,h1) == 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 erfat?
  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 gehrt mglicherweise zum
	  # Netsplit $num.
	  set message "$split($splitnum,message)"
	  break
	}
      }
    }
  }
  if {$i == 0} {
    # Dieser Nick ist nicht von einem Netsplit erfat worden. Der User
    # kann aber mit einem Netjoin aufgetaucht sein. Aus diesem Grund wird
    # pjoin(state) nicht angerhrt.
    TakeOverTest $cnum "$address"

    global prefs
    set j $chan($cnum,window)
    if {$j != -1 && $prefs($j,h1) == 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 besttigt.
          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 mglicher Netjoin eingestuft
    # werden und wird noch spter 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

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

    # Die Anzeige des mglichen Netsplits mu noch nachgeliefert werden.
    set tmp ""
    foreach x "$psplit(channels)" {
      global prefs
      if {[info exists chan($x,window)]} {
	set i $chan($x,window)
	if {$i != -1 && $prefs($i,h3) == 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 gelscht, 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. mssen hier noch weitere Netsplits bercksichtigt 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 Kanlen 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 besttigt.
      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 mglicher Netsplit eingestuft
  # werden und wird noch spter 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"

  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 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

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

  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 gendert.
    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 gendert.
    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 "$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
    if {[strcmp "$banlist(channel)" "$chan($cnum)"] == 0} {
      set i [lSearch "$banlist" "$address"]
      if {$i != -1} {
        .banlist.entries.list.view delete $i
        set banlist(new) "[lreplace "$banlist(new)" $i $i]"
      }
      set i [lSearch "$banlist(old)" "$address"]
      if {$i != -1} {
        set banlist(old) "[lreplace "$banlist(old)" $i $i]"
      }
    }
  }
}

proc BanChannelUser {cnum address user} {
  global chan

  if {$cnum != -1} {
    set i [lSearch "$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
    if {[strcmp "$banlist(channel)" "$chan($cnum)"] == 0} {
      set i [lSearch "$banlist(new)" "$address"]
      if {$i == -1} {
        .banlist.entries.list.view insert end "$address"
        .banlist.entries.list.view yview end
        lappend banlist(new) "$address"
      }
      if {[lSearch "$banlist(old)" "$address"] == -1} {
        lappend banlist(old) "$address"
      }
    }
  }
}

#################
#  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-97  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 "$line"]\"" "" ""
    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 ""
      }
      "^/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 ""
      }
      "^/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 ""
      }
      "^/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 gelscht 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 erhlt einen neuen Wert.
		switch -- "$x" {
		  "escape_sign" {
		    if {[string length "$value"] > 1} {
		      set margin(text) "error"
		      print2crap " Value for $x is too long"
		      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
	global private_commands
	foreach x "$private_commands" {
	  if {[string length "[lindex "$x" 0]"]} {
	    if {[strcmp "/[lindex "$x" 0]" "$command"] == 0} {
	      [lindex "$x" 1] $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]"

	# Evtl. mu 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])"
	}

	# Der Kanal, das alte und das neue Fenster werden hier aktualisiert.
	set chan($cnum,window) $num
	UpdateInfos $wnum
	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>' ausgefhrt wurde.

      DetectSign [lIndex "$line" 0] -1 0
    }
    "users on *" {
      # Wo gehrt 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"]"
      global prefs
      # 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]" "()"]"
	  if {$prefs(r2)} {
	    FileRequest "Please select the new filename for file\n'$file', if you want to get it\nfrom $from via DCC!" "DCC GET" "send2irc \"/dcc rename $from $file :file\";send2irc \"/dcc get $from \:file\"" "send2irc \"/dcc close get $from $file\"" "$file"
	  }
	}
        "dcc chat (*) request received from *" {
	  set from "[lindex "$list" end]"
	  if {$prefs(r1)} {
	    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)]
    } elseif {[string length "[info commands "on_$raw(type)"]"]} {
      set on_args(line) "$raw(line)"
      on_$raw(type)
    }
    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
	  if {$startup < 3} {
	    set_client_information
	    on_connect
	    noteserv_startup
	    set startup 3
	  }
	}
	{^(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. mssen 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
	    }
	    # Bei gefundenem DCC CHAT wird dieses Gesprch unterbrochen.
	    foreach x "$win(list)" {
	      if {[strcmp "$win($x,query)" "=$tmp"] == 0} {
		send2irc "/dcc close chat $tmp"
		set destlog "=$tmp"
		set on_args(window) $x
		break
	      }
	    }
	    # Steht der Nick evtl. noch im Notify-Window?
	    UpdateNotifyWindow 0 "$tmp"
	    
	    # Sollte diese Fehlermeldung von tkirc ausgelst worden sein,
	    # mu sie unterdrckt 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} {
		  # nchstes '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} {
		  # nchstes '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} {
      # nchstes '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} {
      # nchstes '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"
    }
    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
      ExecOnCommand join channel "$channel" nick "$nickname" address "$ownaddress"
      UpdateInfos $on_args(window)
    }
  }
  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} {
      # nchstes '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 berprft 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 }
  } 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 untersttzt, wenn jemand
  # anderes den Kanal betritt. Beim eigenen Betreten des 
  # Kanals wird auf die Userliste gewartet.
  if {[strcmp "$raw(nick)" "$nickname"]} {
    ExecOnCommand 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 prefs
    if {$prefs($on_args(window),h1) == 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 prefs
  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 verlt den Kanal.
    ExecOnCommand leave channel "$channel" message "$last"
    DeleteChannel $cnum
  } else {
    # Jemand anderes verlt den Kanal.
    ExecOnCommand 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} {
            ExecOnCommand channelhop channel "$channel" period $period
          }
        } else {
          ExecOnCommand channelhop channel "$channel" period $period
	}
      }
      RemoveChannelUser $cnum $i $raw(nick)
    }
  }

  # Soll die Leave-Meldung ausgegeben werden?
  if {$prefs($on_args(window),h2) == 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 prefs react_to_netsplits margin
  set last "[string range "[cutwords "$raw(line)" 2]" 1 end]"
  ExecOnCommand 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 && $prefs($j,h3) == 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 prefs 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
    if {[GetDestWin "$to"] == -1} {
      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"
    }
    ExecOnCommand servermessage to "$to" rest "$last"
    global filternext ; set filternext 1
    set cooked(line) "*** $last\x0f\x0f"
    return
  }
  # Hier werden mgliche CTCP-Kommandos herausgefiltert, die dem
  # Benutzer allerdings nicht alle einzeln angezeigt werden. Es
  # werden bis zu 4 CTCP-Kommandos in einer Zeile bercksichtigt.
  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 "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 fr 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 fr 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 fr einen Host aktiviert.
        return
      }
    }

    set upcommand "[string toupper "$command"]"
    switch -regexp -- "$upcommand" {
      {^ACTION} {
        set next(direct) 1
        if {$linetype} {
	  ExecOnCommand 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"]} {
	    if {$prefs(a2)} {
	      set next(towin) "** $raw(nick)!$raw(address)\t$parameters"
	    }
	  } else {
	    if {$prefs(a1)} {
	      set next(towin) "** $raw(nick)!$raw(address)\t$parameters"
	    }
	  }
          if {$prefs(a3)} {
	    set next(tolog) "** $raw(nick)!$raw(address) $parameters"
	  } else {
	    set next(tolog) "$next(towin)"
	  }
	  if {[string length "$away"]} {
	    if {$prefs(b2)} {
	      set next(beep) 1
	    }
	  } else {
	    if {$prefs(b1)} {
	      set next(beep) 1
	    }
	  }
        } else {
          ExecOnCommand 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} {
	    if {[llength "$win($on_args(window),channels)"] < 2} {
	      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"
	  }
        }
        break
      }
      {^DCC} {
        ExecOnCommand dcc type "[lIndex "$parameters" 0]" rest "[cutwords "$parameters" 1]"
      }
      default {
        ExecOnCommand ctcp 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 fr 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"]} {
        if {$prefs(c2)} {
          set next(chatwin) 1
        }
      } else {
        if {$prefs(c1)} {
          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"]} {
      if {$prefs(a2)} {
        set next(towin) "*$raw(nick)!$raw(address)*\t$last"
      }
    } else {
      if {$prefs(a1)} {
        set next(towin) "*$raw(nick)!$raw(address)*\t$last"
      }
    }
    if {$prefs(a3)} {
      set next(tolog) "*$raw(nick)!$raw(address)* $last"
    } else {
      set next(tolog) "$next(towin)"
    }
    if {[string length "$away"]} {
      if {$prefs(b2)} {
        set next(beep) 1
      }
    } else {
      if {$prefs(b1)} {
        set next(beep) 1
      }
    }
    ExecOnCommand privmessage rest "$last"
  } else {
    set next(to) "$to"
    set next(pattern) "?[expand "$raw(nick)"]*"
    set i [UserNumOfChannel [GetChannelNumber "$to"] "$raw(nick)"]
    if {$i != -1} {
      if {[llength "$win($on_args(window),channels)"] < 2} {
        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"
    }
    ExecOnCommand pubmessage to "$to" rest "$last"
  }
}

proc raw_NOTICE { } {
  global raw cooked nickname linetype on_args margin next away prefs 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
    if {[GetDestWin "$to"] == -1} {
      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)" == ""} {
    ExecOnCommand servernotice to "$to" rest "$last"
    global filternext ; set filternext 1

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

      # Hier werden NoteServ-Notices verarbeitet.
      if {[string match "NoteServ@*" "$raw(address)"]} {
	global noteserv
	if {$noteserv(filterls)} {
	  if {[string length "$noteserv(after)"] == 0} {
	    set noteserv(spies) ""
	  } else {
	    after cancel "$noteserv(after)"
	  }
          if {[string match "*. Spy: *" "$last"]} {
  	    lappend noteserv(spies) "[lindex "$raw(list)" 6]"
	    return
	  }
	  set noteserv(after) "[after 20000 noteserv_update]"
	}
	set cooked(line) "*** NoteServ ([time]): $last"
	return
      }
      if {[string match {\*\*\* *} "$last"]} {
	set cooked(line) "$last\x0f\x0f"
	return
      }
    }
    set cooked(line) "*** $last\x0f\x0f"
    return
  }
  # Hier werden mgliche CTCP-Replies herausgefiltert, die dem
  # Benutzer allerdings nicht alle einzeln angezeigt werden. Es
  # werden bis zu 4 CTCP-Replies in einer Zeile bercksichtigt.
  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 dafr, 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"]"
    ExecOnCommand 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 fr 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"]} {
        if {$prefs(c4)} {
          set next(chatwin) 1
        }
      } else {
        if {$prefs(c3)} {
          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"]} {
      if {$prefs(a5)} {
        set next(towin) "+$raw(nick)!$raw(address)+\t$last"
      }
    } else {
      if {$prefs(a4)} {
        set next(towin) "+$raw(nick)!$raw(address)+\t$last"
      }
    }
    if {$prefs(a6)} {
      set next(tolog) "+$raw(nick)!$raw(address)+ $last"
    } else {
      set next(tolog) "$next(towin)"
    }
    if {[string length "$away"]} {
      if {$prefs(b4)} {
        set next(beep) 1
      }
    } else {
      if {$prefs(b3)} {
        set next(beep) 1
      }
    }
    ExecOnCommand privnotice rest "$last"
  } else {
    set next(to) "$to"
    set next(pattern) "-[expand "$raw(nick)"]*"
    set i [UserNumOfChannel [GetChannelNumber "$to"] "$raw(nick)"]
    if {$i != -1} {
      if {[llength "$win($on_args(window),channels)"] < 2} {
        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"
    }
    ExecOnCommand 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)
  ExecOnCommand 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
  }
  ExecOnCommand nick newnick "$last"
}

proc raw_KICK { } {
  global raw margin nickname chan prefs 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 dem Kick mglicherweise vorhergehenden Ban die
    # Kickmeldung als Kommentar zugefgt.
    set i [UserNumOfChannel $cnum "$victim"]
    if {$i != -1} {
      set address "[lindex "$chan($cnum,addresses)" $i]"
      if {[string length "$raw(address)"]} {
        set len [llength "$chan($cnum,banpatterns)"]
        for {set j 0} {$j < $len} {incr j} {
          if {[strmatch "[lindex "$chan($cnum,banpatterns)" $j]" "$victim!$raw(address)"] && [expr [clock seconds]-[lindex "$chan($cnum,bantimes)" $j]] < 10} {
	    set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $j $j "$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

    if {$prefs(r4)} {
      request "Do you want to rejoin channel $channel?" "Cancel|" "Join|send2tkirc $on_args(window) \"[expandescape "[expand "/join $channel"]"]\""
    }
  }
  ExecOnCommand 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 prefs away crapwindow margin
  if {[strcmp "$nickname" "[lindex "$raw(list)" 2]"] == 0} {
    if {[string length "$away"]} {
      if {$prefs(b6)} {
        beep
      }
    } else {
      if {$prefs(b5)} {
        beep
      }
    }
    set last "[string range "[cutwords "$raw(line)" 3]" 1 end]"
    ExecOnCommand invite channel "$last"

    if {$prefs(r3)} {
      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 prefs 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 {
	if {[llength "$win($on_args(window),channels)"] < 2} {
	  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"]} {
	if {$prefs(b2)} {
	  set next(beep) 1
	}
      } else {
	if {$prefs(b1)} {
	  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 {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    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 {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    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 nick_completion_suffix

  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"]

    foreach x "$chan($cnum,cnicks)" {
      if {[strcmp "$x" "$nickname"]} {
        if {[strcmp "$pattern" "[string range "$x" 0 $last]"] == 0} {
          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"
	  }
	  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 " [expandescape "$x"]"
        } else {
          $path.cmdline delete 0 $insert
          $path.cmdline insert $lastspace "[expandescape "$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 " [expandescape "[lindex "$entry" 1]"]"
        } else {
          $path.cmdline delete 0 $insert
          $path.cmdline insert $lastspace "[expandescape "[lindex "$entry" 1]"]"
        }
        return
      }
    }
  }
}

#############
#  HISTORY  #
#############

proc AddToMsgHistory {nick} {
  global msghistory msghistorynum msghistory_max

  set i [lSearch "$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

  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"
      }
    }
  }

  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 "*" 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 "/set DISPLAY on"
}

######################################################################
#                               MAIN                                 #
######################################################################

global tcl_version tk_version starttime
if ![info exists starttime] {
  # Das Programm wurde gerade gestartet. Nachdem die Versionen von Tcl/Tk
  # berprft 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"

  # Fr tkirc bestimmte Argumente werden herausgefiltert, und der Rest
  # wird ircII bergeben.
  set arguments ""
  set tkircrc ""
  set newircpath ""
  set quiet 0
  set ircrc 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 bercksichtigt werden.
	set ircrc 1
      }
      default {
	append arguments "$x "
      }
    }
  }

  if {$quiet == 0} {
    foreach x "/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]"
  } elseif {[string length "$ircserver"]} {
    set server "$ircserver"
    append arguments " $ircserver"
  }
  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!" 
  }

} else {
  # Eine neue Version des Programmes wurde gerade nachgeladen.
  set_client_information
}
