#! /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.128"
set date         "08.07.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 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 ircpath        "irc"

set margin(text)    ""

set beep_on_msg                              1
set beep_on_msg_only_when_away               1
set beep_on_notice                           1
set beep_on_notice_only_when_away            1
set beep_on_invite                           0
set beep_on_invite_only_when_away            0
set beep_on_ctrlG                            1
set beep_on_ctrlG_only_when_away             0
set show_address_on_msg                      1
set show_address_on_msg_only_when_away       1
set show_address_on_msg_always_in_logfile    0
set show_address_on_notice                   1
set show_address_on_notice_only_when_away    1
set show_address_on_notice_always_in_logfile 0
set chat_window_on_msg                       0
set chat_window_on_msg_only_when_away        0
set chat_window_on_notice                    0
set chat_window_on_notice_only_when_away     0
set silence                                  0
set request_on_dcc_chat                      0
set request_on_dcc_send                      0
set request_on_invite                        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 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 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 startup1 { } {
}
proc startup2 { } {
}

proc ReloadTKircRC {tkircrc} {
  global win margin

  if {[string length "$tkircrc"] == 0} {
    set tkircrc "~/.tkircrc"
  }

  set tmp ""
  lappend tmp \
    crapwindow messagewindow \
    normal_style bold_style special_style url_style msgid_style \
    search_style 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_msg beep_on_msg_only_when_away \
    beep_on_notice beep_on_notice_only_when_away \
    beep_on_invite beep_on_invite_only_when_away \
    beep_on_ctrlG beep_on_ctrlG_only_when_away \
    show_address_on_msg show_address_on_msg_only_when_away \
    show_address_on_msg_always_in_logfile \
    show_address_on_notice show_address_on_notice_only_when_away \
    show_address_on_notice_always_in_logfile \
    chat_window_on_msg chat_window_on_msg_only_when_away \
    chat_window_on_notice chat_window_on_notice_only_when_away \
    silence request_on_dcc_chat request_on_dcc_send request_on_invite \
    auto_popup hide_joins hide_leaves hide_signoffs \
    show_commandline show_topic show_userlist use_margin \
    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

  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
  }

  if {[file exists "$tkircrc"]} {
    source "$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 server          ""

set commandqueue    ""
set whoisqueue      ""
set filterqueue     ""
set filternext      0
set direct2crap     0
set messagewindow   0
set crapwindow      0
set debugwindow     -1
set whoisfilter     0

set beeptext        ""
set beepstate       1

set next(direct)    0

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

set signed_userlist        ""
set signed_addresslist     ""
set visual_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     ""

# to detect and to handle netsplits
set split(count)       0
set join(count)        0

# for (maybe just possible) netsplits and netjoins
set psplits            ""
set pjoins             ""
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}}
  {<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 win crapwindow debugwindow
  if {$debugwindow >= 0} {
    if {[winfo exists .win$debugwindow]} {
      set num "$debugwindow"
      global lines_max

      set path "[GetPathFromNum $debugwindow]"
      set widget "$path.body.left.traffic.text"
      set end "[lindex "[$widget yview]" 1]"

      $widget configure -state normal
      if {$win($num,lines) >= [expr $lines_max - 1]} {
	$widget delete 1.0 2.0
      } else {
        incr win($num,lines)
      }
      if {$win($num,lines) > 1} {
	$widget insert end "\n"
      }
      $widget insert end "| $text"
      $widget configure -state disabled

      if {$end == 1} {
	$widget yview end
      }
    }
  }
}

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

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

proc beep { } {
  global prefs

  if !$prefs(s) {
    bell
  }
}

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

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 awaydate { } {
  global away

  if {[string length "$away"]} {
    return " ([date])"
  } else {
    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"

  if [catch {toplevel .req -class tkirc-request}] {
    raise .req
  } else {
    grab set .req
    set width 40 ; set height 1
    wm title .req  " tkirc: Request"
    bind .req <Escape> "grab release .req ; destroy .req"

    frame .req.f1 -bd 1 -relief sunken
    pack .req.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 .req.f1.label -width $width -height $height -bd 0 -text "$newbody"
    pack .req.f1.label -side top -pady 3 -expand true

    frame .req.f2
    pack .req.f2 -ipadx 2 -ipady 2 -padx 2 -pady 2 -side top -fill x
    set i 0
    foreach buttondef $args {
      set trenn [string last "|" $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 .req ; destroy .req"

      button .req.f2.$i -text "$text" -command "$action"
      if {$i == 0} {
        pack .req.f2.$i -side right -pady 2
      } else {
        pack .req.f2.$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 "[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"
}

# 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 "[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 "$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 "lines hsize urlcount idcount" {
    set win($wnum,$x) 0
  }
  set win($wnum,actual) "*"

  return $wnum
}

proc DeleteWindow {wnum} {
  global win

  foreach x "channels lines query history hsize history2 actual urlcount idcount" {
    unset win($wnum,$x)
  }
  set i [lsearch "$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_filter

  # Dem Kanal wurde noch kein Fenster zugewiesen.
  set i [lsearch "$chan(tojoin)" "[expand "$channel"]"]
  if {$i != -1} {
    # Es sollte ein bestimmtes Fenster genommen werden.
    while {$i != -1} {
      set num [lindex "$win(tojoin)" $i]
      set chan(tojoin) "[lreplace "$chan(tojoin)" $i $i]"
      set win(tojoin) "[lreplace "$win(tojoin)" $i $i]"
      set i [lsearch "$chan(tojoin)" "[expand "$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 cnicks olist vlist addresses jointimes topic bancomments banpatterns bantimes banusers" {
    set chan($cnum,$x) ""
  }
  foreach x "o b i m n p s t" {
    set chan($cnum,mode_$x) 0
  }
  foreach x "k l" {
    set chan($cnum,mode_$x) ""
  }
  set chan($cnum,ucount) 0
  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 "$chan(tojoin)" "[expand "$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 cnicks olist vlist addresses jointimes topic bancomments banpatterns bantimes banusers ucount window" {
    unset chan($cnum,$x)
  }
  foreach x "o b i m n p s t k l" {
    unset chan($cnum,mode_$x)
  }

  set i [lsearch "$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 "$win(list)" "$wnum"] != -1} {
    set i [lsearch "$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 "$win(list)" $chan($x,window)] != -1} {
	return $chan($x,window)
      }
    }
  }
#  return [ProduceChannel "$channel"]
  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 destlog
  set destlog "$channel"
  set on_args(window) -1
  foreach x "$chan(list)" {
    if {[strcmp "$channel" "$chan($x)"] == 0} {
      if {[lsearch "$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

  if {[info exists log($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 opendate" {
    set log($num,$x) ""
  }
  return $num
}

proc DeleteLog {num} {
  global log

  set source "$log($num)"
  foreach x "file handle type dateswitch opendate" {
    unset log($num,$x)
  }
  unset log([string tolower "$source"])
  unset log($num)

  set i [lsearch "$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" {
	if {$vorzeichen} {
	  incr chan($cnum,mode_o)
	} else {
	  set chan($cnum,mode_o) [expr $chan($cnum,mode_o)-1]
	}
	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
      }
    }
  }
  UpdateInfos $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
      deluxeentry $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
      deluxeentry $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 defaultbutton {path args} {
    frame $path -relief sunken -borderwidth 1
    eval button $path.default $args
    pack $path.default
}

proc deluxetext { name args } {
    eval text $name -relief sunken -wrap word -setgrid 0 $args
#    bind $name <ButtonRelease-2> {emacsTInsertSelect %W ; break}
#    bind $name <Control-u> {%W delete 1.0 insert }
    bind $name <Delete> "[bind Text <BackSpace>] ; break"
#    bind $name <Meta-b> {tkEntryInsert %W \002 ; %W icursor insert}
    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 deluxeentry { name args } {
    global default_entry_bindings entry_bindings
    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 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 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 1} {$i <= $win($num,urlcount)} {incr i} {
    if {$prefs($num,m1)} {
      $name.text tag configure url_$num\_$i -lmargin2 $margin(size)
    } else {
      $name.text tag configure url_$num\_$i -lmargin2 0
    }
  }
  for {set i 1} {$i <= $win($num,idcount)} {incr i} {
    if {$prefs($num,m1)} {
      $name.text tag configure id_$num\_$i -lmargin2 $margin(size)
    } else {
      $name.text tag configure id_$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 {deluxetext $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

  set w [GetPathFromNum $num].body.left.traffic.text
  $w tag remove search 0.0 end
  if {$string == ""} {
    return
  }
  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,lines) + 1)]
  set endnum [expr int($endview * $win($num,lines))]

  set skipthisview 1
  if {[strcmp "$search_laststring" "$string"]} {
    set skipthisview 0
  } elseif {$search_lastview < $startview} {
    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 [expr double($i-1) / $win($num,lines)]
	return
      }
    }
  }

  # Rest des Textes durchsuchen
  if {$endview != 1} {
    for {set i [expr $endnum+1]} {$i <= $win($num,lines)} {incr i} {
      set loline "[string tolower "[$w get $i.0 [expr $i+1].0]"]"
      if {[string first "$lostring" "$loline"] != -1} {
	set search_lastview [expr double($i-1) / $win($num,lines)]
        $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 [expr double($i-1) / $win($num,lines)]
      $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 destlog

  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" {
        set xlen [string length "$x"]

	if {[strcmp "$prefix" "$x"] == 0} {
	  set suffix "[string trimright "[lindex "[split "[string range "$line" [expr $i+3] end]" "\"\!\,\[\]\<\>\'\(\) "]" 0]" "\?\."]"
	  set url "$prefix://$suffix"
	  if {[string length "$suffix"] == 0} {
	    break
	  }
	  set rightURLfound 1
	  incr win($num,urlcount)

	  # 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 url_$num\_$win($num,urlcount) -lmargin2 $margin(size) $style $url_style
	  } else {
	    eval $widget tag configure url_$num\_$win($num,urlcount) -lmargin2 0 $style $url_style	      
	  }

	  global url_list
	  set k [lsearch "$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" url_$num\_$win($num,urlcount)
	  set line "[string range "$line" [expr $i+3+[string length "$suffix"]] end]"
	  $widget tag bind url_$num\_$win($num,urlcount) <Any-Enter> "$widget tag configure url_$num\_$win($num,urlcount) -background #e4e4e4"
	  $widget tag bind url_$num\_$win($num,urlcount) <Any-Leave> "$widget tag configure url_$num\_$win($num,urlcount) $url_style"
	  $widget tag bind url_$num\_$win($num,urlcount) <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 match "*<*@*>*" "$line"]} {}
  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,idcount)

	  if {$prefs($num,m1)} {
	    eval $widget tag configure id_$num\_$win($num,idcount) -lmargin2 $margin(size) $style $msgid_style
	  } else {
	    eval $widget tag configure id_$num\_$win($num,idcount) -lmargin2 0 $style $msgid_style
	  }
	  global msgid_list
	  set k [lsearch "$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" id_$num\_$win($num,idcount)
	  set cutline "[string range "$cutline" [expr $i+$j+1] end]"
          $widget tag bind id_$num\_$win($num,idcount) <Any-Enter> "$widget tag configure id_$num\_$win($num,idcount) -background #e4e4e4"
          $widget tag bind id_$num\_$win($num,idcount) <Any-Leave> "$widget tag configure id_$num\_$win($num,idcount) $msgid_style"
          $widget tag bind id_$num\_$win($num,idcount) <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
  set newline ""

  set destlog "[string tolower "$destlog"]"
  set source "$destlog"
  for {set i 0} {$i < 2} {incr i} {
    # Wurde ein Logfile fr diese Quelle geffnet?
    set lodestlog "[string tolower "$destlog"]"
    if {[info exists log($lodestlog)]} {
      set num "$log($lodestlog)"

      # 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,opendate) "[longdate]"
	  puts $handle "\nLogfile opened for $destlog at:  [longdate]"
	} else {
	  # Der Parameter fr DeleteLog befindet sich in der Variablen
	  # "destlog". Das File konnte nicht geffnet werden.
	  DeleteLog $num
	  continue
	}
      }

      if {[string compare "$destlog" "<all>"]} {
	# Die aktuelle Zeile wird ins Logfile geschrieben.
	if {$log($num,dateswitch)} {
	  puts $handle "[longdate]  $newline"
	} else {
	  puts $handle "$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,dateswitch)} {
	    puts $handle "[format "[longdate]  %-10s : %s" "$source" "$newline"]"
	  } else {
	    puts $handle "[format "%-10s : %s" "$source" "$newline"]"
	  }
	}
      }
      flush $handle
    }
    # Auf zur zweiten Runde mit "<all>"!
    set destlog "<all>"
  }
}

proc print2text {num line} {
  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 path "[GetPathFromNum $num]"

  if {[string compare "[wm state $path]" "iconic"] == 0} {
    if {$prefs($num,p)} {
      wm deiconify $path
    }
  }

  set widget "$path.body.left.traffic.text"
  set end "[lindex "[$widget yview]" 1]"
  $widget configure -state normal
  if {$win($num,lines) >= [expr $lines_max - 1]} {
    $widget delete 1.0 2.0
  } else {
    incr win($num,lines)
  }

  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]"} {
	$command
      }
      break
    }
  }

  set style [expr $userstyle * 16]
  set cr "" ; set part ""
  if {$win($num,lines) > 1} {
    set cr "\n"
  }
  for {set i 0} {$i <= [string length "$line"]} {incr i} {
    set char "[string index "$line" $i]"
    if {"$char" > "\x1f"} {
      append part "$char"
    } else {
#      append part "$char"
      formatted2text $num $widget "$cr$part" $style
      set cr "" ; 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 {$prefs(b7)} {
	      if {$prefs(b8)} {
		if {[string length "$away"]} {
		  beep
		}
	      } else {
		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 "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != ""} {
	if {[lsearch "$win(list)" "$chan($x,window)"] != -1} {
	  # Wurde diese Meldung evtl. schon dort ausgegeben?
	  if {[lsearch "$windows" "$chan($x,window)"] == -1} {
	    set margin(text) "$mtext"
	    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 "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != ""} {
	if {[lsearch "$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 "$chan(list)" "$x"] != -1} {
      # Existiert das Fenster?
      if {"$chan($x,window)" != ""} {
	if {[lsearch "$win(list)" "$chan($x,window)"] != -1} {
	  # Wurde diese Meldung evtl. schon dort ausgegeben?
	  if {[lsearch "$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 "$args" "logsonly"]
  set withoutcrap [lsearch "$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 margin

  set entry "[GetPathFromNum $num].cmdline"
  set line "[$entry get]"
  set maxchars 230

  # Leere Eingaben werden nicht weiter bearbeitet.
  if {"$line" == 0} {
    return
  }

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

  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)
	  send2tkirc $num "[string range "$line" 0 $maxchars]"

	  # Kommandozeile wird gelscht.
	  if {[lsearch "$win(list)" $num] != -1} {
	    $entry delete 0 end 
	  }
	  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"
	  $entry delete 0 end 
	  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 "$chan($cnum,cnicks)" "[expand "$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 und ein Teil der Message mit "/" anfngt,
    # 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
      }
      send2tkirc $num "$header[string range "$x" 0 $cutnum]..."
      set x "[string range "$x" [expr $cutnum+1] end]"
    }
    send2tkirc $num "$header$x"

    # Kommandozeile wird gelscht.
    if {[lsearch "$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 "$win(list)" $num] == -1} {
    set num $crapwindow
  }
  if {"$ip" != ""} {
    set actual "[GetActual $num]"
    if {"$actual" != "*" && "$actual" != ""} {
      puts $ip "/join [GetActual $num]"
    }
    send2irc "[parsein $num "$line"]"
  }
}

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

  if {[gets $ip line] >= 0} {
    global filternext direct2crap margin next
debug "in : $line"
    if {"[string index "$line" 0]" == "~"} {
      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 line "[parseraw "$line"]"
      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
      }

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

	  } 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 "$san(nicks)" "[expand "$next(from)"]"]
	      if {$i == -1} {
		lappend san(nicks) "$next(from)"
		lappend san(times) "[clock seconds]"
		send2tkirc $crapwindow "/notice $next(from) $nickname is away: [expandescape "$san(message)"]"
	      } elseif {[expr [clock seconds]-[lindex "$san(times)" $i]] > 900} {
		set san(times) "[lreplace "$san(times)" $i $i [clock seconds]]"
		send2tkirc $crapwindow "/notice $next(from) $nickname is away: [expandescape "$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)"
	  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)
      set len [llength "$filterqueue"]
      for {set i 0} {$i < $len} {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 "$filterqueue"
	} 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 "$win(list)" $on_args(window)] != -1} {
        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 "$chan($cnum,nicks)" "[expand "$nick"]"]
}

proc UserNumOfChannel2 {cnum nick address} {
  global chan
  set i [lsearch "$chan($cnum,nicks)" "[expand "$nick"]"]
  set chan($cnum,addresses) "[lreplace "$chan($cnum,addresses)" $i $i "$address"]"
  return i
}

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

proc hasVoiceOnChannel {cnum nick} {
  global chan
  set i [lsearch "$chan($cnum,nicks)" "[expand "$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} {
    FileRequester " 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,lines)} {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]"
        $win.selected delete 0 end
        if [file isfile "$file"] {
	  $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 FileRequester {title ok command1 command2 filename} {
  global selected_file win
  incr win(reqcount)

  set selected_file ""
  set command "[expand "$command1"]"
  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
    deluxeentry $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 \"$command\""
    button $f.cancel -text "Cancel" -command "closewindow $path; $command2"
    pack $f.ok -side left
    pack $f.cancel -side right

    deluxeentry $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} {
    FileRequester " 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"
  }
}

##################
#  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 {[string length "[info commands "on_$type"]"]} {
    on_$type
  } elseif {[info exists on_$type]} {
    if {[string length "[set on_$type]"]} {
      eval [set 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} {
    FileRequester " 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 visual_signed_userlist

  # Beim Signoff oder evtl. auch bei fehlerhaftem Signon mu
  # der schon vorhandene Nick aus der Liste entfernt werden.
  set i [lSearch "$visual_signed_userlist" "$nick"]
  if {$i >= 0} {
    set visual_signed_userlist "[lreplace "$visual_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 visual_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 visual_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 "$visual_signed_userlist"]

    for {set i 0} {$i < $ulen} {incr i} {
      .not.list.entries insert end "[lindex "$visual_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,lines) 0
  $widget configure -state normal
  $widget delete 0.0 end
  $widget configure -state disabled

  for {set i 1} {$i <= $win($num,urlcount)} {incr i} {
    $widget tag delete url_$num\_$i
  }
  set win($num,urlcount) 0
  for {set i 1} {$i <= $win($num,idcount)} {incr i} {
    $widget tag delete id_$num\_$i
  }
  set win($num,idcount) 0
}

proc UpdateTopic {num type} {
  # type: 0=disabled, 1=normal, 2= unchanged
  global chan

  set path "[GetPathFromNum $num]"
  set oldstate [$path.body.left.topic.entry cget -state]
  $path.body.left.topic.entry configure -state normal
  if {"[GetActual $num]" == "*"} {
    set topic ""
    $path.body.left.topic.entry delete 0 end
    $path.body.left.topic.entry configure -state disabled
  } elseif {"[myfocus 2]" != "$path.body.left.topic.entry"} {
    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 delete 0 end
      $path.body.left.topic.entry insert 0 "$topic"
    }
    switch -- "$type" {
      "0" {$path.body.left.topic.entry configure -state disabled}
      "1" {$path.body.left.topic.entry configure -state normal}
      "2" {$path.body.left.topic.entry configure -state $oldstate}
    }
  }
}

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 b k l i m n p s t"
  set path "[GetPathFromNum $num]"

  if {[string length "$text"]} {
    for {set i 0} {$i < 9} {incr i} {
      $path.menu.[lindex "$flags" $i] configure -fg #111111 -text ""
    }
    $path.menu.[lindex "$flags" 9] 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?"}

  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
  global chan win
  global CHANNEL_NAME_WIDTH

  if {[lsearch "$win(list)" "$num"] != -1} {
    set path "[GetPathFromNum $num]"

    if {"$server" == "" && "[GetActual $num]" != "*"} {
      set win($num,actual) "*"
    }
    set channel [GetActual $num]
    set cnum [GetChannelNumber "$channel"]

    set len [expr $CHANNEL_NAME_WIDTH-1]
    if {[strcmp "[string range "$channel" 0 $len]" "[$path.body.right.top.channel cget -text]"]} {
      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
      }
    }

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

      set os 0
      foreach x "$chan($cnum,olist)" {
	if {$x} {incr os}
      }
      $path.menu.o configure -text "o=$os" -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"
	}
      }

      if {[isOpOnChannel $cnum "$nickname"]} {
        set at "@"
	UpdateTopic $num 1
      } elseif {$chan($cnum,mode_t) == 0} {
	UpdateTopic $num 1
      } else {
	UpdateTopic $num 0
      }
    } else {
      PrintModeChars $num ""
      UpdateTopic $num 0
    }
    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} {
  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]"]"
  }
  return "[string trimleft "[expand "$result"]" " "]"
}

proc UserOp {num} {
  set snicks "[SelectedNicks $num]"
  set len [llength "$snicks"]
  if {$len} {
    set onicks ""
    set flags "+"
    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 "-"
    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 "+"
    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 "-"
    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
  }
}

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

proc KeepUser {nick address} {
  global wcuserlist wcaddresslist

  set wcuserlist "[linsert "$wcuserlist" 0 "$nick"]"
  set wcaddresslist "[linsert "$wcaddresslist" 0 "$address"]"

  if {[llength "$wcuserlist"] > 50} {
    set wcuserlist "[lrange "$wcuserlist" 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 type} {
  global chan margin react_to_takeover nickname takeover_tries
  global takeover_users takeover_period

  set channel "$chan($cnum)"
  set secs [clock seconds]
  if {$react_to_takeover != 0} {
    set at [string first "@" "$address"]
    if {$at == -1} {
      return
    }
    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?
      if {[lsearch "$takeover_tries" "$cnum $host"] != -1} {
        if {$type == 0} {
	  return
	}
      } else {
	for {set i 0} {$i < 3} {incr i} {
	  after [expr $i * 750] beep
	}
        lappend takeover_tries "$cnum $host"
      }
      if {[isOpOnChannel $cnum "$nickname"]} {
	set margin(text) "alert"
	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!"
      }
    }
  }
}

proc AddressOfNick {nick} {
  global chan win wcuserlist wcaddresslist

  foreach x "$chan(list)" {
    set i [UserNumOfChannel $x "$nick"]
    if {$i >= 0} {
      set address "[lindex "$chan($x,addresses)" $i]"
      if {[string length "$address"]} {
	return "$address"
      }
    }
  }
  set i [lSearch "$wcuserlist" "$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 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} {
  foreach x "[SelectedNicks $num]" {
    send2irc "/ctcp $x $command"
  }
}

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} {
      FileRequester " 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 DetectSign {nick type call} {
  global crapwindow notifies
  global signed_userlist signed_addresslist

  set address ""
  if {$type == 0 || $call > 0} {
    # 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 address "[lindex "$signed_addresslist" $i]"
      set signed_userlist "[lreplace "$signed_userlist" $i $i]"
      set signed_addresslist "[lreplace "$signed_addresslist" $i $i]"
    }
  }

  if {$type > 0} {
    if {$call == 0} {
      # auf jeden Fall neue Addresse besorgen
      set command "DetectSign [expand $nick] $type 1"
      AddToWhoisQueue "$nick" "0" "$command" ""
      return
    } else {
      # vorhandene Addresse ist aktuell
      set address "[AddressOfNick $nick]"
      lappend signed_userlist "$nick"
      lappend signed_addresslist "$address"
    }
  }

  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 [date]"
      }
      "0" {
	ExecOnCommand notify_signoff nick "$nick" address "$address"
	set margin(text) "notify"
	print2crap "*** Signoff by $nick ($address) detected at [date]"
      }
    }
    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
  deluxeentry $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 [GetActual $num] [expandescape $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_tries
    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" {
      send2irc "/mode $banchannel($wx) +b $user"
      set alreadykicked ""
      KickWarScriptUser "$banchannel($wx)" "$address"
    }
  }
}

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 oldbanlist

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

proc EditBanListEntry { } {
  global banlist

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

proc AddBanListEntry { } {
  global banlist

  .banlist.entries.list.view delete 0 end
  lappend banlist "[.banlist.entries.entry get]"
  for {set i 0} {$i < [llength "$banlist"]} {incr i} {
    .banlist.entries.list.view insert end "[lindex "$banlist" $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 [lreplace "$banlist" $num $num]
  }
  .banlist.entries.entry delete 0 end
}

proc CommitBanListChanges { } {
  global banlist oldbanlist banlistchannel

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

proc InitBanList {f} {
  # f is the frame

  deluxeentry $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 oldbanlist banlistchannel

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

    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 SetMenuStates {num} {
  global prefs
  switch -- "$num" {
    "1" {if {!$prefs(b1)} {set prefs(b2) 0}}
    "2" {if {$prefs(b2)} {set prefs(b1) 1}}
    "3" {if {!$prefs(b3)} {set prefs(b4) 0}}
    "4" {if {$prefs(b4)} {set prefs(b3) 1}}
    "5" {if {!$prefs(b5)} {set prefs(b6) 0}}
    "6" {if {$prefs(b6)} {set prefs(b5) 1}}
    "7" {if {!$prefs(b7)} {set prefs(b8) 0}}
    "8" {if {$prefs(b8)} {set prefs(b7) 1}}
    "9" {if {!$prefs(a1)} {set prefs(a2) 0}}
   "10" {if {$prefs(a2)} {set prefs(a1) 1}}
   "12" {if {!$prefs(a4)} {set prefs(a5) 0}}
   "13" {if {$prefs(a5)} {set prefs(a4) 1}}
   "15" {if {!$prefs(c1)} {set prefs(c2) 0}}
   "16" {if {$prefs(c2)} {set prefs(c1) 1}}
  }
}

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 checkbutton -label " on msg" \
      -variable prefs(b1) -command "SetMenuStates 1"
  $f.menu.beep add checkbutton -label " ... only when away" \
      -variable prefs(b2) -command "SetMenuStates 2"
  $f.menu.beep add separator
  $f.menu.beep add checkbutton -label " on notice" \
      -variable prefs(b3) -command "SetMenuStates 3"
  $f.menu.beep add checkbutton -label " ... only when away" \
      -variable prefs(b4) -command "SetMenuStates 4"
  $f.menu.beep add separator
  $f.menu.beep add checkbutton -label " on invite" \
      -variable prefs(b5) -command "SetMenuStates 5"
  $f.menu.beep add checkbutton -label " ... only when away" \
      -variable prefs(b6) -command "SetMenuStates 6"
  $f.menu.beep add separator
  $f.menu.beep add checkbutton -label " on ctrl-g" \
      -variable prefs(b7) -command "SetMenuStates 7"
  $f.menu.beep add checkbutton -label " ... only when away" \
      -variable prefs(b8) -command "SetMenuStates 8"
  $f.menu add cascade -label "Show address" -menu $f.menu.show
  menu $f.menu.show
  $f.menu.show add checkbutton -label " on msg" \
      -variable prefs(a1) -command "SetMenuStates 9"
  $f.menu.show add checkbutton -label " ... only when away" \
      -variable prefs(a2) -command "SetMenuStates 10"
  $f.menu.show add checkbutton -label " ... always in logfile" \
      -variable prefs(a3) -command "SetMenuStates 11"
  $f.menu.show add separator
  $f.menu.show add checkbutton -label " on notice" \
      -variable prefs(a4) -command "SetMenuStates 12"
  $f.menu.show add checkbutton -label " ... only when away" \
      -variable prefs(a5) -command "SetMenuStates 13"
  $f.menu.show add checkbutton -label " ... always in logfile" \
      -variable prefs(a6) -command "SetMenuStates 14"
  $f.menu add cascade -label "Chat window" -menu $f.menu.chat
  menu $f.menu.chat
  $f.menu.chat add checkbutton -label " on msg" \
      -variable prefs(c1) -command "SetMenuStates 15"
  $f.menu.chat add checkbutton -label " ... only when away" \
      -variable prefs(c2) -command "SetMenuStates 16"
  $f.menu.chat add separator
  $f.menu.chat add checkbutton -label " on notice" \
      -variable prefs(c3) -command "SetMenuStates 17"
  $f.menu.chat add checkbutton -label " ... only when away" \
      -variable prefs(c4) -command "SetMenuStates 18"

  $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 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 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"
  $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 "who" -command "UserWho $num" \
    -accelerator alt+w
  $f.menu add command -label "whois" -command "UserWhois $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 "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 0
  menu $f.menu
  $f.menu add cascade -label "join" -menu $f.menu.join
  menu $f.menu.join
  $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 "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 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"]"]\""
  }
  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
  deluxeentry $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"
}

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
    }
  } else {
    pack forget $path.body.left.topic
  }
}

proc InitCmdLine {num} {
  set f "[GetPathFromNum $num]"
  deluxeentry $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
    }
  } 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
  }
}

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

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
    }
  } else {
    pack forget $path.body.right
  }
}

proc FillUserList {num cnum} {
  global chan

  set path "[GetPathFromNum $num]"
  set len [llength "$chan($cnum,nicks)"]
  for {set i 0} {$i < $len} {incr i} {
    if {[lindex "$chan($cnum,olist)" $i]} {
      set prefix "@"
    } elseif {[lindex "$chan($cnum,vlist)" $i]} {
      set prefix "+"
    } else {
      set prefix ""
    }
    $path.body.right.list.users insert end "$prefix[lindex "$chan($cnum,nicks)" $i]"
    $path.body.right.top.count configure -text "[expr $i+1]"
  }
}

proc HandleKey {num key} {
    switch -- "$key" {
      "o" { UserOp $num }
      "d" { UserDeop $num }
      "v" { UserGiveVoice $num }
      "e" { UserRemoveVoice $num }
      "w" { UserWho $num }
      "i" { UserWhois $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_msg beep_on_msg_only_when_away
  global beep_on_notice beep_on_notice_only_when_away
  global beep_on_invite beep_on_invite_only_when_away
  global beep_on_ctrlG beep_on_ctrlG_only_when_away
  global show_address_on_msg show_address_on_msg_only_when_away
  global show_address_on_msg_always_in_logfile
  global show_address_on_notice show_address_on_notice_only_when_away
  global show_address_on_notice_always_in_logfile
  global chat_window_on_msg chat_window_on_msg_only_when_away
  global chat_window_on_notice chat_window_on_notice_only_when_away 
  global silence request_on_dcc_chat request_on_dcc_send request_on_invite
  global auto_popup hide_joins hide_leaves hide_signoffs
  global show_commandline show_topic show_userlist use_margin display_types
  global margin_size

  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" {
    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 margin(size) $margin_size
    set geo $geometry
  }

  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"
    foreach x "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_msg
      set prefs(b2) $beep_on_msg_only_when_away
      set prefs(b3) $beep_on_notice
      set prefs(b4) $beep_on_notice_only_when_away
      set prefs(b5) $beep_on_invite
      set prefs(b6) $beep_on_invite_only_when_away
      set prefs(b7) $beep_on_ctrlG
      set prefs(b8) $beep_on_ctrlG_only_when_away
      set prefs(a1) $show_address_on_msg
      set prefs(a2) $show_address_on_msg_only_when_away
      set prefs(a3) $show_address_on_msg_always_in_logfile
      set prefs(a4) $show_address_on_notice
      set prefs(a5) $show_address_on_notice_only_when_away
      set prefs(a6) $show_address_on_notice_always_in_logfile
      set prefs(c1) $chat_window_on_msg
      set prefs(c2) $chat_window_on_msg_only_when_away
      set prefs(c3) $chat_window_on_notice
      set prefs(c4) $chat_window_on_notice_only_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
    }
    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-Home> "$f.left.traffic.text yview 0"
    bind $path <Shift-End> "$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 HandleFakeNetjoins { } {
  global chan pjoins margin

  set thistime [clock seconds]
  for {set i 0} {$i < [llength "$pjoins"]} {incr i} {
    set x "[lindex "$pjoins" $i]"
    if {[expr $thistime - [lindex "$x" 0]] >= 4} {
      set pjoins "[lreplace "$pjoins" $i $i]"

      set channels ""
      foreach y "[lindex "$x" 1]" {
	# Ist das Fenster berhaupt noch vorhanden?
	set wnum [GetDestWin "$chan($y)"]
	if {$wnum != -1} {
	  global prefs
	  if {$prefs($wnum,h1) == 0} {
	    lappend channels "$y"
	  }
	}
      }
      set margin(text) "join"
      print2channels "$channels" "*** [lindex "$x" 2] ([lindex "$x" 4]) has joined channel $chan($y)"

    }
  }
}

proc HandleNetjoins {cnum nick address optext} {
  global chan split join psplits pjoins margin

  set thistime [clock seconds]

  # Wurde dieser User von einem "mglichen" Netsplit erfat?
  set len [llength "$psplits"]
  for {set i 0} {$i < $len} {incr i} {
    set x "[lindex "$psplits" $i]"
    # Ist's der gleiche Nick?
    if {[string compare "$nick" "[lindex "$x" 2]"] == 0} {
      # Ist's einer der richtigen Kanle?
      if {[lsearch "[lindex "$x" 1]" $cnum] != -1} {
	# Ja. Der Netsplit wurde noch nicht als solcher erkannt. ==>
	# Mglichen Split-Eintrag entfernen und Signoff-Meldung nachliefern!
	set psplits "[lreplace "$psplits" $i $i]"
	set i [expr $i-1]

	foreach y "[lindex "$x" 1]" {
	  # Ist das Fenster berhaupt noch vorhanden?
	  set wnum [GetDestWin "$chan($y)"]
	  if {$wnum != -1} {
	    global prefs
	    if {$prefs($wnum,h3) == 0} {
	      set margin(text) "signoff"
	      print2channels "$y" "*** [lindex "$x" 2] has signed off ([lindex "$x" 3]\x0f)"
	    }
	  }
	}
	break
      }
    }
  }

  # Wurde dieser User von einem Netsplit erfat?
  for {set i $split(count)} {$i > 0} {set i [expr $i-1]} {
    set num [expr $i-1]
    if {[expr $thistime - $split($num,time)] < 1800} {
      if {[lsearch "$split($num,nicks)" "[expand $nick]"] != -1} {
	if {[lsearch "$split($num,channels)" "$cnum"] != -1} {
	  # Nick und Kanal stimmen. ==> User gehrt mglicherweise zum
	  # Netsplit $i.
	  set message "$split($num,message)"
	  break
	}
      }
    }
  }
  if {$i < 1} {
    # Dieser User ist nicht von einem Netsplit erfat worden.
    set wnum [GetDestWin "$chan($cnum)"]
    if {$wnum != -1} {
      TakeOverTest $cnum "$address" 0
      global prefs
      if {$prefs($wnum,h1) == 0} {
	set margin(text) "join"
	print2channels $cnum "*** $nick ($address) has joined channel $chan($cnum)$optext"
      }
    }
    return
  }
  set splitnum $num

  # Wurde der Netjoin bereits erkannt und angezeigt?
  for {set i $join(count)} {$i > 0} {set i [expr $i-1]} {
    set num [expr $i-1]
    if {[expr $thistime - $join($num,time)] < 90} {
      # Nur die letzten 90 Sekunden sind relevant.
      if {$splitnum == $join($num,split)} {
	# Der zugehrige Split stimmt auch.
	if {[lsearch "$join($num,channels)" $cnum] == -1} {
	  # Der Netjoin wurde in diesem Kanal noch nicht angezeigt.
	  set date "[clock format $join($num,time) -format "%H:%M:%S"]"
	  set margin(text) "netjoin"
   	  belated2channels "$cnum" "$join($num,channels)" " Netjoin at $date ($message)"
	  lappend join($num,channels) $cnum
	}
	# Die Nickliste wird noch aktualisiert.
	if {[lsearch "$join($num,nicks)" "[expand "$nick"]"] == -1} {
	  lappend join($num,nicks) "$nick"
	}
	return
      }
    } else {
      # Die restlichen Netjoins sind schon zu lange her.
      break
    }
  }

  # Ist evtl. schon ein zweiter User dieses Splits wieder aufgetaucht?
  set len [llength "$pjoins"]
  for {set i 0} {$i < $len} {incr i} {
    set x "[lindex "$pjoins" $i]"
    if {$splitnum == [lindex "$x" 3]} {
      # Der zugehrige Split stimmt.
      if {[string compare "[lindex "$x" 2]" "$nick"]} {
	# Es handelt sich nicht um denselben User.
	# Beide Nicks mssen jetzt abgearbeitet werden, aber vorher
	# mu der pjoin-Eintrag entfernt werden, um eine doppelte
	# Ausgabe zu verhindern.
	set pjoins "[lreplace "$pjoins" $i $i]"

	set num $join(count)
	incr join(count)
	set join($num,time) [lindex "$x" 0]
	lappend join($num,channels) $cnum [lindex "$x" 1]
	lappend join($num,nicks) $nick [lindex "$x" 2]
	set join($num,split) [lindex "$x" 3]

	set date "[clock format $join($num,time) -format "%H:%M:%S"]"
	set margin(text) "netjoin"
	print2channels "$cnum [lindex "$x" 1]" " Netjoin at $date ($split($splitnum,message))"
      } else {
	# Es handelt sich um denselben User, aber einen anderen Kanal.
	set pjoins "[lreplace "$pjoins" $i $i]"
	lappend pjoins "[lindex "$x" 0] \"$cnum [lindex "$x" 1]\" $nick [lindex "$x" 3] [lindex "$x" 4]"
      }
      return
    }
  }

  # Wurde ein Optext mit angegeben, so kann davon ausgegangen werden,
  # da dieser User durch einen Netjoin wieder aufgetaucht ist.
  if {[string length "$optext"]} {
  }

  # Der mgliche Netjoin wird eingetragen.
  lappend pjoins "$thistime $cnum $nick $splitnum $address"
  after 4600 HandleFakeNetjoins
}

#** Current value of psplits is {865719409 1 Logain ""..billiger ist's...""}
proc HandleFakeNetsplits { } {
  global chan psplits margin

  set thistime [clock seconds]
  for {set i 0} {$i < [llength "$psplits"]} {incr i} {
    set x "[lindex "$psplits" $i]"
    if {[expr $thistime - [lindex "$x" 0]] > 4} {
      set psplits "[lreplace "$psplits" $i $i]"
      set i [expr $i-1]

      set channels ""
      foreach y "[lindex "$x" 1]" {
	# Ist das Fenster berhaupt noch vorhanden?
	set wnum [GetDestWin "$chan($y)"]
	if {$wnum != -1} {
	  global prefs
	  if {$prefs($wnum,h3) == 0} {
	    lappend channels "$y"
	  }
	}
      }
      set margin(text) "signoff"
      print2channels "$channels" "*** [lindex "$x" 2] has signed off ([lindex "$x" 3]\x0f)"
    }
  }
}

proc HandleNetsplits {cnum nick message} {
  global chan split join psplits pjoins margin

  set thistime [clock seconds]

  # Wurde dieser User evtl. gerade erst von einem "mglichen" 
  # Netjoin erfat?
  set len [llength "$pjoins"]
  for {set i 0} {$i < $len} {incr i} {
    set x "[lindex "$pjoins" $i]"
    # Ist's der gleiche Nick?
    if {[string compare "$nick" "[lindex "$x" 2]"] == 0} {
      # Ist's einer der richtigen Kanle?
      if {[lsearch "[lindex "$x" 1]" $cnum] != -1} {
	# Ja. Der Netjoin wurde noch nicht als solcher erkannt. ==>
	# Mglichen Join-Eintrag entfernen und Join-Meldung nachliefern!
	set pjoins "[lreplace "$pjoins" $i $i]"
	set i [expr $i-1]

	foreach y "[lindex "$x" 1]" {
	  # Ist das Fenster berhaupt noch vorhanden?
	  set wnum [GetDestWin "$chan($y)"]
	  if {$wnum != -1} {
	    global prefs
	    if {$prefs($wnum,h1) == 0} {
	      set margin(text) "join"
	      print2channels "$y" "*** [lindex "$x" 2] has joined channel $chan($y)"
	    }
	  }
	}
	break
      }
    }
  }

  # Wurde der Split bereits erkannt und angezeigt?
  for {set i $split(count)} {$i > 0} {set i [expr $i-1]} {
    set num [expr $i-1]
    if {[expr $thistime - $split($num,time)] < 90} {
      # Nur die letzten 90 Sekunden sind relevant.
      if {[string compare "$message" "$split($num,message)"] == 0} {
	# Die Splitmessage stimmt.
	if {[lsearch "$split($num,channels)" $cnum] == -1} {
	  # Der Split wurde in diesem Kanal noch nicht angezeigt.
	  set date "[clock format $split($num,time) -format "%H:%M:%S"]"
	  set margin(text) "netsplit"
   	  belated2channels "$cnum" "$split($num,channels)" " Netsplit at $date ($message)"
	  lappend split($num,channels) $cnum
	}
	# Die Nickliste wird noch aktualisiert.
	if {[lsearch "$split($num,nicks)" "[expand "$nick"]"] == -1} {
	  lappend split($num,nicks) "$nick"
	}
	return
      }      
    } else {
      # Die restlichen Splits sind schon zu lange her.
      break
    }
  }

  # Ist evtl. schon ein anderer User mit dieser Meldung rausgegangen?
  set len [llength "$psplits"]
  for {set i 0} {$i < $len} {incr i} {
    set x "[lindex "$psplits" $i]"
    if {[string compare "[lindex "$x" 3]" "$message"] == 0} {
      # Die Splitmessage stimmt.
      if {[string compare "[lindex "$x" 2]" "$nick"]} {
	# Es handelt sich nicht um denselben User.
	# Beide Nicks mssen jetzt abgearbeitet werden, aber vorher
	# mu der psplit-Eintrag entfernt werden, um eine doppelte
	# Ausgabe zu verhindern.
	set psplits "[lreplace "$psplits" $i $i]"

	set num $split(count)
	incr split(count)
	set split($num,time) [lindex "$x" 0]
	lappend split($num,channels) $cnum [lindex "$x" 1]
	lappend split($num,nicks) $nick [lindex "$x" 2]
	set split($num,message) "$message"

	set date "[clock format $split($num,time) -format "%H:%M:%S"]"
	set margin(text) "netsplit"
	print2channels "$cnum [lindex "$x" 1]" " Netsplit at $date ($message)"
      } else {
	# Es handelt sich um denselben User, aber einen anderen Kanal.
	set psplits "[lreplace "$psplits" $i $i]"
	lappend psplits "[lindex "$x" 0] \"$cnum [lindex "$x" 1]\" $nick [lindex "$x" 3]"
      }
      return
    }
  }

  # Der mgliche Netsplit wird eingetragen.
  lappend psplits "$thistime $cnum $nick \"[expand "$message"]\""
  after 4600 HandleFakeNetsplits
}

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

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

proc AddChannelUser {cnum user} {
  global chan

  set chan($cnum,nicks) "[linsert "$chan($cnum,nicks)" 0 "$user"]"
  set chan($cnum,vlist) "[linsert "$chan($cnum,vlist)" 0 "0"]"
  set chan($cnum,olist) "[linsert "$chan($cnum,olist)" 0 "0"]"
  lappend chan($cnum,cnicks) "$user"
  set chan($cnum,addresses) "[linsert "$chan($cnum,addresses)" 0 ""]"
  set chan($cnum,jointimes) "[linsert "$chan($cnum,jointimes)" 0 "[clock seconds]"]"

  set wnum $chan($cnum,window)
  if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
    set path "[GetPathFromNum $wnum]"
    $path.body.right.list.users insert 0 "$user"
    $path.body.right.top.count configure -text "[llength "$chan($cnum,nicks)"]"
  }
}

proc RemoveChannelUser {cnum user message} {
  global chan win margin wcuserlist wcaddresslist react_to_netsplits

  if {"$cnum" == ""} {
    # Fr SIGNOFFs notwendig!
    set channels ""
    foreach cnum "$chan(list)" {
      set i [lsearch "$chan($cnum,cnicks)" "[expand "$user"]"]
      if {$i != -1} {
        set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i]"
      }

      set i [UserNumOfChannel $cnum "$user"]
      if {$i != -1} {
	set address [lindex "$chan($cnum,addresses)" $i]
        set chan($cnum,nicks) "[lreplace "$chan($cnum,nicks)" $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 $user $address

	set num [GetChannelWindow $chan($cnum)]
	if {$num != -1} {
	  if {[strcmp "[GetActual $num]" "$chan($cnum)"] == 0} {
	    set path "[GetPathFromNum $num]"
	    $path.body.right.list.users delete $i
	    $path.body.right.top.count configure -text "[llength "$chan($cnum,nicks)"]"
	    UpdateInfos $num
	  }
	}
	if {$react_to_netsplits && [regexp -- {^[^ .][^ ]*\.[^ .]+ [^ .][^ ]*\.[^ .]+$} "$message"]} {
	  HandleNetsplits $cnum "$user" "$message"
	} elseif {$num != -1} {
	  global prefs
	  if {$prefs($num,h3) == 0} {
	    lappend channels $cnum
	  }
	}
      }
    }
    set margin(text) "signoff"
    print2channels "$channels" "*** $user has signed off ($message\x0f)"
  } else {
    set i [lsearch "$chan($cnum,cnicks)" "[expand "$user"]"]
    if {$i != -1} {
      set chan($cnum,cnicks) "[lreplace "$chan($cnum,cnicks)" $i $i]"
    }

    set i [UserNumOfChannel $cnum "$user"]
    if {$i != -1} {
      if {[llength "$wcuserlist"] > 10} {
        set wcuserlist "[lrange "$wcuserlist" 0 9]"
        set wcaddresslist "[lrange "$wcaddresslist" 0 9]"
      }
      set wcuserlist "[linsert "$wcuserlist" 0 "$user"]"
      set wcaddresslist "[linsert "$wcaddresslist" 0 "[lindex "$chan($cnum,addresses)" $i]"]"

      set address "[lindex "$chan($cnum,addresses)" $i]"
      set chan($cnum,nicks) "[lreplace "$chan($cnum,nicks)" $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]"

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

      foreach num "$win(list)" {
	if {[strcmp "[GetActual $num]" "$chan($cnum)"] == 0} {
	  set path "[GetPathFromNum $num]"
          $path.body.right.list.users delete $i
	  $path.body.right.top.count configure -text "[llength "$chan($cnum,nicks)"]"
        }
      }
    }
  }
}

proc RenameChannelUser {old new} {
  global chan margin

  set channels ""
  foreach cnum "$chan(list)" {
    set i [lsearch "$chan($cnum,cnicks)" "[expand "$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 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]
	  $path.body.right.list.users delete $i
	  $path.body.right.list.users insert $i "$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"
}

proc ChannelUserOp {cnum user plus} {
  global chan win

  set i [UserNumOfChannel $cnum "$user"]
  if {$i != -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 wnum $chan($cnum,window)
    if {$wnum != -1} {
      if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
	set path "[GetPathFromNum $wnum]"
	$path.body.right.list.users delete $i
	$path.body.right.list.users insert $i "$prefix$user"
      }
    }
  }
}

proc ChannelUserVoice {cnum user plus} {
  global chan win

  set i [UserNumOfChannel $cnum "$user"]
  if {$i != -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 wnum $chan($cnum,window)
    if {$wnum != -1} {
      if {[strcmp "[GetActual $wnum]" "$chan($cnum)"] == 0} {
	set path "[GetPathFromNum $wnum]"
	$path.body.right.list.users delete $i
	$path.body.right.list.users insert $i "$prefix$user"
      }
    }
  }
}

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

set help_header {
! Additional tkirc command. Copyright (c) 1996-1997  Andreas Gelhausen.
! All rights reserved.  See the README file of tkirc for more information.
!
}

set help_commands {
  "bannick"      "bancomment"   "baninfos"     "chat"         "close"
  "closecraplog" "closelog"     "closelogall"  "closemsglog"  "craplog"
  "dchat"        "exchange"     "loadbaninfos" "log"          "logall"
  "logs"         "msgids"       "msglog"       "newwin"       "notifies"
  "savebaninfos" "savebuffer"   "search"       "splits"       "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\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> <irpath_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> <irpath_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\]
  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.
See also:
  \x1f\CLOSELOG\x1f"

"Usage: \x02\LOGALL\x02 <filename> \[-d\]
  The file <filename> will be opened to log the whole irc-traffic in. Select
  option '-d' for additional time stamps.
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\]
   This command allows you to open file <filename> to log all private
   messages and notices in. Select option '-d' for additional time stamps.
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\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 parsein {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"
  }

  global chan win crapwindow nickname margin
  set command ""
  set margin(text) ""

  if {[string first " \:file " "[string tolower "$line "]"] != -1} {
    FileRequester " 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) "error"
  	    print2crap " You are not on 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 date "[lindex "$chan($cnum,bantimes)" $i]"
		if {"$date" == "0"} {
		  set date "00.00.00  00:00:00"
		} else {
		  set date "[longdate $date]"
		}
		set user "[string range "$address" 0 [expr [string first "!" "$address"]-1]]"
		if {"$user" == ""} {
		  set user "<unknown>"
		}
		print2crap "[format "  %2d.  $date  %-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) "error"
  	    print2crap " You are not on 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 ""
      }
      "^/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
	  catch {close $ip} result

	  set ircpath "[lindex "$list" 1]"
	  set arguments "[expand "[cutwords "$line" 2]"]"
	  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] {
	    puts stdout "Error executing \"$ircpath -d -q $arguments\" - $ip"
	    set ip ""
	  } else {
	    fconfigure $ip -blocking 0
	    fileevent $ip readable "irc2text"
	  }
	}
      }
      "^/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 ""
      }
      "^/(leave|part)$" {
	# to support leave messages with ircII < 2.9
	if {[llength "$list"] > 2} {
	  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) "error"
	      print2crap " You are not on channel $channel"
	    }
	    close $file
          }
	}
	return ""
      }
      "^/log$" {
	set len [llength "$list"]
        if {$len < 3 || $len > 4} {
	  print2crap " Wrong number of parameters. Try /HELP LOG"
	} elseif {$len == 4 && [strcmp "[lindex "$list" 3]" "-d"]} {
	  set margin(text) "error"
	  print2crap " Wrong option '[lindex "$list" 3]'"
	} else {
	  if {$len == 4} {
	    set dateflag 1
	  } else {
	    set dateflag 0
	  }

	  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,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) "error"
	      print2crap " You are not on 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]
	  if {[strmatch "-*" "$x"]} {
	    set empty 1
	    set x "[string range "$x" 1 end]"
	  } else {
	    set empty 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"] {
	    if {$empty} {
	      if [catch {set $x ""} y] {
		set margin(text) "error"
		print2crap " $y "
	      } else {
		print2crap "*** Value of $x set to <EMPTY>"
	      }
	    } else {
	      set value "[cutwords "$line" 2]"
	      if {[string length "$value"]} {
		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 {
		print2crap "*** Current value of $x is [set $x]"
	      }
	    }
	    return ""
	  }
        }
      }
      "^/splits$" {
	global split join starttime
        if {$split(count)} {
	  print2crap " Detected $split(count) netsplit(s) since $starttime:"
	  for {set i 0} {$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,split) == $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 ""
      }
      "^/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 "*** 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)" "[expand $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]"

	# altes Fenster.
	set i [lsearch "$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])"
	  }
	}
	# Kanal
	set chan($cnum,window) $num
	# neues Fenster
	lappend win($num,channels) "$cnum"
	set win($num,actual) "$channel"

	UpdateInfos $wnum
	UpdateInfos $num
      } else {
	# Ist der Kanal aktuell?
	if {[strcmp "$win($wnum,actual)" "$channel"] != 0} {
	  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)} {
	    FileRequester "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 nickname lastnickname server away prefs destlog
  global whoisqueue whoisfilter
  global messagewindow crapwindow
  global chan win margin version date on_args next

  set dp [string first ":" "$line"]
  set prefix "[string range "$line" 0 $dp]"

  set list "[line2list "$line"]"
  set one "[lindex "$list" 1]"
  set type "[lindex "$list" 2]"

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

  set on_args(nick) "$nick"
  set on_args(address) "$address"
  if [regexp -- {^[0-9][0-9][0-9]} "$type"] {
   switch -regexp -- "$type" {
    {^303} {
      # RPL_ISON
      return ""
    }
    {^311} {
      # RPL_WHOISUSER
      set nick [lindex "$list" 4]
      AddAddressToNick $nick [lindex "$list" 5]@[lindex "$list" 6]
      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
	}
      }
      return ""
    }
    {^314} {
      # RPL_WHOWASUSER
      set nick [lindex "$list" 4]
      AddAddressToNick $nick [lindex "$list" 5]@[lindex "$list" 6]
      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
	}
      }
      return ""
    }
    {^(301|312|313|317|319)} {
      # RPL_AWAY, RPL_WHOISUSER - Replies 311 - 313, 317 - 319 are all replies
      if {$whoisfilter} {
	global filternext ; set filternext 1
      }
      return ""
    }
    {^318} {
      # 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
      }
      return ""
    }
    {^322} {
      # RPL_LIST
      global direct2crap ; set direct2crap 1
      return ""
    }
    {^324} {
      # RPL_CHANNELMODEIS
      set channel [lindex "$list" 4]
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]
      SetChannelModes $cnum "[cutwords "$line" 5]" 0 ""
      return ""
    }
    {^329} {
      # RPL_CREATIONTIME
      set margin(text) "extra"
      set channel [lindex "$list" 4]
      if {[GetDestWin "$channel"] == -1} { return "" }
      global filternext ; set filternext 1
      return "*** Channel $channel was created [clock format [lindex "$list" 5] -format "on %d.%m.%y at %H:%M:%S"]"
    }
    {^331} {
      # RPL_NOTOPIC
      set margin(text) "topic"
      set channel [lindex "$list" 4]
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]

      set chan($cnum,topic) ""
      UpdateTopic $on_args(window) 2
      global filternext ; set filternext 1
      return "*** $channel: No topic is set."
    }
    {^332} {
      # RPL_TOPIC
      set margin(text) "topic"
      set channel [lindex "$list" 4]
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]

      set chan($cnum,topic) "[string range "[cutwords "$line" 5]" 1 end]"
      UpdateTopic $on_args(window) 2
      global filternext ; set filternext 1
      return "*** Topic for $channel: [string range "[cutwords "$line" 5]" 1 end]"
    }
    {^333} {
      # RPL_TOPICWHOTIME
      set margin(text) "extra"
      set channel [lindex "$list" 4]
      if {[GetDestWin "$channel"] == -1} { return "" }
      global filternext ; set filternext 1
      return "*** Topic was set by [lindex "$list" 5] [clock format [lindex "$list" 6] -format "on %d.%m.%y at %H:%M:%S"]"
    }
    {^353} {
      # RPL_NAMREPLY
      set channel "[lindex "$list" 5]"
      if {[GetDestWin "$channel"] == -1} { return "$line" }

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

      if {$chan($cnum,ucount) == 0} {
	set i [string last ":" "$line"] ; incr i
	foreach x "[expand "[string range "$line" $i end]"]" {
	  lappend chan($cnum,nicks) "[TrimNick "$x"]"
	  set prefix ""
	  if {"[string index "$x" 0]" == "+"} {
	    set prefix "+"
	    lappend chan($cnum,vlist) "1"
	  } else {
	    lappend chan($cnum,vlist) "0"
	  }
	  if {"[string index "$x" 0]" == "@"} {
	    set prefix "@"
	    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)"
      }
      return ""
    }
    {^366} {
      # RPL_ENDOFNAMES
      set channel "[lindex "$list" 4]"
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]
      if {$chan($cnum,ucount) == 0} {
	if {[strcmp "$channel" "$win($on_args(window),actual)"] == 0} {
	  FillUserList $on_args(window) $cnum
	}
	set chan($cnum,ucount) 1
      }
      global ownaddress
      if [info exists ownaddress] {
	ExecOnCommand join channel "$channel" nick "$nickname" address "$ownaddress"
	unset ownaddress
      }
      UpdateInfos $on_args(window)
      return ""
    }
    {^367} {
      # RPL_BANLIST
      global banlist_filter
      set channel "[lindex "$list" 4]"
      BanChannelUser [GetChannelNumber "$channel"] "[lindex "$list" 5]" ""
      if {[lSearch "$banlist_filter" "$channel"] != -1} {
	global filternext ; set filternext 1
	return ""
      } else {
	if {[winfo exists .banlist]} {
	  global banlistchannel
	  if {[strcmp "$banlistchannel" "$channel"] == 0} {
	    global filternext ; set filternext 1
	    return ""
	  }
        }
      }
      global direct2crap ; set direct2crap 1
      return ""
    }
    {^368} {
      # RPL_ENDOFBANLIST
      global banlist_filter
      set channel "[lindex "$list" 4]"
      if {[GetDestWin "$channel"] == -1} { return "" }

      set i [lSearch "$banlist_filter" "$channel"]
      if {$i != -1} {
	set banlist_filter "[lreplace "$banlist_filter" $i $i]"
      }

      UpdateInfos $on_args(window)
      return ""
    }
    {^369} {
      # RPL_ENDOFWHOWAS
      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
      }
      return ""
    }
    {^(372|375)} {
      # RPL_MOTD, RPL_MOTDSTART
      return ""
    }
    {^(401|403|405|432|433|436|437|442|471|473|474|475)} {
      # ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL, ERR_TOOMANYCHANNELS,
      # ERR_ERRONEUSNICKNAME, ERR_NICKNAMEINUSE, 
      # ERR_NICKCOLLISION, Nick/channel is temporarily unavailable,
      # ERR_NOTONCHANNEL, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN,
      # ERR_BANNEDFROMCHAN, ERR_BADCHANNELKEY
      set tmp [lindex "$list" 4]
      if {[strcmp "$nickname" "$tmp"] == 0} {
        NickNotAvailable "$tmp"
      } else {
	# Ggf. mssen die Tojoin-Listen korrigiert werden.
	set i [lsearch "$chan(tojoin)" "[expand $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)" "=$nick"] == 0} {
	    send2irc "/dcc close chat $nick"
	    set destlog "=$nick"
	    set on_args(window) $x
	    break
	  }
	}
	# 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
	  }
	}
      }
      return ""
    }
    {^001} {
      set server "$address"
      set nickname "[lindex "$list" 3]"
      UpdateAllTitles
      return ""
    }
    {^255} {
      set_client_information
      global startup
      if {$startup < 2} {
	startup2
	incr startup
      }
      return ""
    }
    {^(305|306)} {
      # RPL_UNAWAY, RPL_NOWAWAY
      global san automatic_away
      set san(nicks) ""
      set san(times) ""
      if {$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
      return "$type [string range "[cutwords "$line" 4]" 1 end]"
    }
    default {
      return ""
    }
   }
  } else {
   switch -regexp -- "$type" {
    {^ERROR} {
      set margin(text) "error"
      set last "[string range "[cutwords "$line" 3]" 1 end]"
      print2all " $last"
      Disconnect
      global filternext ; set filternext 1
      return ""
    }
    {^JOIN} {
      set channel "[string range "[lindex "$list" 3]" 1 end]"

      # Nun mu berprft werden, ob gleichzeitig ein Channelop-Status
      # mitgeliefert wurde.
      set i [string last "\a" "$channel"]
      set optext ""
      if {$i != -1} {
	if {[strmatch "\a*o*" "[string range "$channel" $i end]"]} {
	  set optext " (+o)"
        }
	set channel "[string range "$channel" 0 [expr $i-1]]"
      }

      if {[strcmp "$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 "$nick"
	if {[string length "$optext"]} {
	  ChannelUserOp $cnum $nick 1
	}
	UpdateInfos $on_args(window)
      }

      AddAddressToNick "$nick" "$address"

      # 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 "$nick" "$nickname"]} {
        ExecOnCommand join channel "$channel"
      } else {
	global ownaddress
	set ownaddress "$address"
      }
      global react_to_netsplits
      if {$react_to_netsplits} {
	HandleNetjoins $cnum "$nick" "$address" "$optext"
	return ""
      } else {
	TakeOverTest $cnum "$address" 0
      }

      # Soll die Join-Meldung ausgegeben werden?
      global prefs
      if {$prefs($on_args(window),h1)} {
	return ""
      } else {
	set margin(text) "join"
	return "*** $nick ($address) has joined channel $channel$optext"
      }
    }
    {^PART} {
      set channel "[lindex "$list" 3]"
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]
      set last "[string range "[cutwords "$line" 4]" 1 end]"

      if {[strcmp "$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"
	RemoveChannelUser $cnum $nick ""
	UpdateInfos $on_args(window)
      }

      # Soll die Leave-Meldung ausgegeben werden?
      global prefs
      if {$prefs($on_args(window),h2)} {
	set line ""
      } else {
	set margin(text) "leave"
	set line "*** $nick ($address) has left channel $channel"
	if {[string length "$last"] && [string compare "$nick" "$last"]} {
	  append line " ($last\x0f)"
        }
      }
    }
    {^QUIT} {
      set last "[string range "[cutwords "$line" 3]" 1 end]"
      ExecOnCommand signoff message "$last"
      RemoveChannelUser "" $nick "$last"
#      UpdateAllInfos
      return ""
    }
    {^PRIVMSG} {
      set to "[lindex "$list" 3]"
      set last "[string range "[cutwords "$line" 4]" 1 end]"

      # Das Zielfenster wird ermittelt.
      if {[strcmp "$to" "$nickname"]} {
	set tome 0
	if {[GetDestWin "$to"] == -1} {
	  debug "PRIVMSG: '$line'"
	  global filternext ; set filternext 1
	  return ""
	}
      } else {
	set tome 1
	set on_args(window) [GetMsgWinFromNick $nick 0]
      }
      # Kommt die Privmsg evtl. direkt vom Server?
      if {"$nick" == ""} {
	ExecOnCommand pubmessage to "$to" rest "$last"
	global filternext ; set filternext 1
	return "*** $last"
      }
      # 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 "@" "$address"]
	    set host "[string range "$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 {$tome} {
	      ExecOnCommand privaction rest "$parameters"

	      set next(from) "$nick"
	      set next(towin) "** $nick\t$parameters\x0f[awaydate]"
	      set next(pattern) "\*> [expand "$nick"]*"
	      if {$prefs(a1)} {
		if {$prefs(a2)} {
		  if {[string length "$away"]} {
		    set next(towin) "** $nick!$address\t$parameters\x0f[awaydate]"
		  }
		} else {
	          set next(towin) "** $nick!$address\t$parameters\x0f[awaydate]"
		}
	      }
	      if {$prefs(a3)} {
	        set next(tolog) "** $nick!$address $parameters\x0f[awaydate]"
	      } else {
		set next(tolog) "$next(towin)"
	      }
	      if {$prefs(b1)} {
		if {$prefs(b2)} {
		  if {[string length "$away"]} {
		    set next(beep) 1
		  }
		} else {
		  set next(beep) 1
		}
	      }
	    } else {
	      ExecOnCommand pubaction to "$to" rest "$parameters"

	      set next(to) "$to"
	      set next(pattern) "\* [expand "$nick"]*"
	      set i [UserNumOfChannel [GetChannelNumber "$to"] "$nick"]
	      if {$i != -1} {
		if {[llength "$win($on_args(window),channels)"] < 2} {
		  set next(towin) "* $nick\t$parameters"
		} else {
		  set next(towin) "* $nick+$to\t$parameters"
		}
	      } else {
		set next(towin) "* $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 {$tome} {
	  print2crap " MULTI-CTCP from $nick: $together"
	} else {
	  print2crap " MULTI-CTCP from $nick to $to: $together"
	}
      }

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

      # Dann mu die Privmsg fr mich oder einen Kanal bestimmt sein.
      set next(direct) 1
      if {$tome} {
	set next(from) "$nick"
	AddToMsgHistory "[expandescape "$nick"]"
	if {[strcmp "$nickname" "$nick"]} {
	  if {$prefs(c1)} {
	    if {$prefs(c2)} {
	      if {[string length "$away"]} {
		set next(chatwin) 1
	      }
	    } else {
	      set next(chatwin) 1
	    }
	  }
	}
	set next(towin) "*$nick*\t$last\x0f[awaydate]"
	set next(pattern) "?[expand "$nick"]*"
	if {$prefs(a1)} {
	  if {$prefs(a2)} {
	    if {[string length "$away"]} {
	      set next(towin) "*$nick!$address*\t$last\x0f[awaydate]"
	    }
	  } else {
	    set next(towin) "*$nick!$address*\t$last\x0f[awaydate]"
	  }
	}
	if {$prefs(a3)} {
	  set next(tolog) "*$nick!$address* $last\x0f[awaydate]"
	} else {
	  set next(tolog) "$next(towin)"
	}
	if {$prefs(b1)} {
	  if {$prefs(b2)} {
	    if {[string length "$away"]} {
	      set next(beep) 1
	    }
	  } else {
	    set next(beep) 1
	  }
	}
	ExecOnCommand privmessage rest "$last"
      } else {
	set next(to) "$to"
	set next(pattern) "?[expand "$nick"]*"
	set i [UserNumOfChannel [GetChannelNumber "$to"] "$nick"]
	if {$i != -1} {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    set next(towin) "<$nick>\t$last"
	  } else {
	    set next(towin) "<$nick+$to>\t$last"
	  }
	} else {
	  set next(towin) "<$nick$to>\t$last"
	}
	ExecOnCommand pubmessage to "$to" rest "$last"
      }
      return ""
    }
    {^NOTICE} {
      set to "[lindex "$list" 3]"
      set last "[string range "[cutwords "$line" 4]" 1 end]"

      # Das Zielfenster wird ermittelt.
      if {[strcmp "$to" "$nickname"]} {
	set tome 0
	if {[GetDestWin "$to"] == -1} {
	  debug "NOTICE: '$line'"
	  global filternext ; set filternext 1
	  return ""
	}
      } else {
	set tome 1
	set on_args(window) [GetMsgWinFromNick $nick 0]
      }
      # Kommt die Notice evtl. direkt vom Server?
      if {"$nick" == ""} {
	set margin(text) "server"
	ExecOnCommand servernotice to "$to" rest "$last"
	global filternext ; set filternext 1
	return "*** $last"
      }
      # 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 "-$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 {$tome} {
	  print2crap " MULTI-CTCP reply from $nick: $together"
	} else {
	  print2crap " MULTI-CTCP reply from $nick to $to: $together"
	}
      }

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

      # Dann mu die Notice fr mich oder einen Kanal bestimmt sein.
      set next(direct) 1
      if {$tome} {
	set next(from) "$nick"
	AddToMsgHistory "[expandescape "$nick"]"
	if {[strcmp "$nickname" "$nick"]} {
	  if {$prefs(c3)} {
	    if {$prefs(c4)} {
	      if {[string length "$away"]} {
		set next(chatwin) 1
	      }
	    } else {
	      set next(chatwin) 1
	    }
	  }
	}
	set next(towin) "+$nick+\t$last\x0f[awaydate]"
	set next(pattern) "-[expand "$nick"]*"
	if {$prefs(a4)} {
	  if {$prefs(a5)} {
	    if {[string length "$away"]} {
	      set next(towin) "+$nick!$address+\t$last\x0f[awaydate]"
	    }
	  } else {
	    set next(towin) "+$nick!$address+\t$last\x0f[awaydate]"
	  }
	}
	if {$prefs(a6)} {
	  set next(tolog) "+$nick!$address+ $last\x0f[awaydate]"
	} else {
	  set next(tolog) "$next(towin)"
	}
	if {$prefs(b3)} {
	  if {$prefs(b4)} {
	    if {[string length "$away"]} {
	      set next(beep) 1
	    }
	  } else {
	    set next(beep) 1
	  }
	}
	ExecOnCommand privnotice rest "$last"
      } else {
	set next(to) "$to"
	set next(pattern) "-[expand "$nick"]*"
	set i [UserNumOfChannel [GetChannelNumber "$to"] "$nick"]
	if {$i != -1} {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    set next(towin) "-$nick-\t$last"
	  } else {
	    set next(towin) "-$nick+$to-\t$last"
	  }
	} else {
	  set next(towin) "-$nick$to-\t$last"
	}
	ExecOnCommand pubnotice to "$to" rest "$last"
      }
      return ""
    }
    {^WALLOPS} {
      set to "[lindex "$list" 3]"
      set last "[string range "[cutwords "$line" 4]" 1 end]"
      set margin(text) "wallops"
      global filternext ; set filternext 1
      return "*** ![lindex "$list" 1]! $last"
    }
    {^TOPIC} {
      set channel "[lindex "$list" 3]"
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]

      set last "[string range "[cutwords "$line" 4]" 1 end]"
      set chan($cnum,topic) "$last"
      UpdateTopic $on_args(window) 2
      ExecOnCommand topic channel "$channel" topic "$last"
      set margin(text) "topic"
      return "*** $nick has changed the topic on channel $channel to $last"
    }
    {^NICK} {
      set last "[string range "[cutwords "$line" 3]" 1 end]"
      RenameChannelUser "$nick" "$last"
      if {[strcmp "$nick" "$nickname"] == 0} {
        set lastnickname "$nickname"
        set nickname "$last"
        UpdateAllInfos
      }
      ExecOnCommand nick newnick "$last"
      return ""
    }
    {^KICK} {
      set channel "[lindex "$list" 3]"
      if {[GetDestWin "$channel"] == -1} { return "" }
      set cnum [GetChannelNumber "$channel"]

      set victim "[lindex "$list" 4]"
      set last "[string range "[cutwords "$line" 5]" 1 end]"

      if {[strcmp "$nickname" "$victim"]} {
	set margin(text) "kick"
	print2channels $cnum "*** $victim has been kicked off channel $channel by $nick ($last\x0f)"

	# Hier wird einem dem Kick mglicherweise vorhergehenden Ban die
	# Kickmeldung als Kommentar zugefgt.
	set unum [UserNumOfChannel $cnum "$victim"]
	if {$unum != -1} {
	  set address "[lindex "$chan($cnum,addresses)" $unum]"
	  if {[string length "$address"]} {
	    set len [llength "$chan($cnum,banpatterns)"]
	    for {set i 0} {$i < $len} {incr i} {
	      if {[strmatch "[lindex "$chan($cnum,banpatterns)" $i]" "$victim!$address"] && [expr [clock seconds]-[lindex "$chan($cnum,bantimes)" $i]] < 10} {
		set chan($cnum,bancomments) "[lreplace "$chan($cnum,bancomments)" $i $i "$last"]"
		break
	      }
	    }
	  }
	}

        RemoveChannelUser $cnum $victim ""
	UpdateInfos $on_args(window)
      } else {
	set margin(text) "kick"
	print2channels $cnum "*** You have been kicked off channel $channel by $nick ($last\x0f)"
        DeleteChannel $cnum
      }
      ExecOnCommand kick channel "$channel" victim "$victim" message "$last"
      return ""
    }
    {^MODE} {
      if {[strcmp "$nickname" "[lindex "$list" 3]"]} {
	set channel "[lindex "$list" 3]"
	if {[GetDestWin "$channel"] == -1} { return "" }
	set cnum [GetChannelNumber "$channel"]

	set modes "[cutwords "$line" 4]"
	SetChannelModes $cnum "$modes" 1 "$nick!$address"
	UpdateInfos $on_args(window)

	set margin(text) "mode"
	set tmp "*** Mode change \"$modes\" on channel $channel by"
	if {[string length "$nick"]} {
	  return "$tmp $nick"
	} else {
	  return "$tmp $address"
	}
      } else {
	set modes "[string range "[cutwords "$line" 4]" 1 end]"
	SetUserModes "$modes"

	set margin(text) "mode"
	set tmp "*** Mode change \"$modes\" for user $nickname by"
	return "$tmp $address"
      }
    }
    {^INVITE} {
      if {[strcmp "$nickname" "[lindex "$list" 3]"] == 0} {
	if {$prefs(b5)} {
	  if {$prefs(b6)} {
	    if {[string length "$away"]} {
	      beep
	    }
	  } else {
	    beep
	  }
	}
	set last "[string range "[cutwords "$line" 4]" 1 end]"
	ExecOnCommand invite channel "$last"

	if {$prefs(r3)} {
	  request "$nick ($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
	return "*** $nick ($address) invites you to channel $last"
      }
    }
    default {
      debug "PARSERAW-DEFAULT: $line"
      return ""
    }
   }
  }
  return "$line"
}

proc parseons {line} {
  global nickname channel away destlog margin
  global messagewindow crapwindow on_args win

  # Eigene Hilfsinformationen durch '/on'
  set list "[line2list "$line"]"
  set type "[string range "$line" 1 [expr [string first " " "$line"]-1]]"
  switch -exact -- "$type" {
    {send_public} {
      set to [lindex "$list" 1]
      set destlog "$to"
      if {[GetDestWin "$to"] == -1} {
	set on_args(window) $messagewindow
	return "<$nickname$to>\t[cutwords "$line" 2]"
      } else {
	if {[llength "$win($on_args(window),channels)"] < 2} {
	  return "<$nickname>\t[cutwords "$line" 2]"
	} else {
	  return "<$nickname+$to>\t[cutwords "$line" 2]"
	}
      }
    }
    {send_msg} {
      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"
    }
    {send_dcc_chat} {
      set to [lindex "$list" 1]
      set destlog "$to"
      print2log "<messages>" "=$to= [cutwords "$line" 2]"
      set on_args(window) [GetMsgWinFromNick =$to 0]
      return "=$to=\t[cutwords "$line" 2]"
    }
    {dcc_chat} {
      set from [lindex "$list" 1]
      set destlog "$from"
      set last "[cutwords "$line" 2]"
      print2log "<messages>" "=$from= $last"
      set on_args(window) [GetMsgWinFromNick =$from 0]
      return "=$from=\t$last"
    }
    {send_action} {
      set to [lindex "$list" 1]
      set destlog "$to"
      if [regexp -- {^(\#|&|\+).*} "$to"] {
	if {[GetDestWin "$to"] == -1} {
	  set on_args(window) $messagewindow
	  return "* $nickname$to\t[cutwords "$line" 2]"
	} else {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    return "* $nickname\t[cutwords "$line" 2]"
	  } else {
	    return "* $nickname+$to\t[cutwords "$line" 2]"
	  }
	}
      } else {
	set last "[cutwords "$line" 2]"
	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"
      if [regexp -- {^(\#|&|\+).*} "$to"] {
	if {[GetDestWin "$to"] == -1} {
	  set on_args(window) $messagewindow
	  return "-$nickname$to-\t[cutwords "$line" 2]"
	} else {
	  if {[llength "$win($on_args(window),channels)"] < 2} {
	    return "-$nickname-\t[cutwords "$line" 2]"
	  } else {
	    return "-$nickname+$to-\t[cutwords "$line" 2]"
	  }
	}
      } else {
	set last "[cutwords "$line" 2]"
	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 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"
  }
}

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

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

######################################################################
#                       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 {/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} {
    ReloadTKircRC "$tkircrc"
  }
  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]"
  }
  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
  }
  InitClient

  global startup
  if {$startup < 1} {
    startup1
    incr startup
  }

  fconfigure $ip -blocking 0
  InitStyles
  MainWindow -1

  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
}
