# sendpr.tcl - GUI to send-pr.
# Copyright (C) 1997,2008 Red Hat, Inc.
# Written by Tom Tromey <tromey@cygnus.com>.

# FIXME:
# * consider adding ability to set various options from outside,
#   eg via the configure method.
# * Have explanatory text at the top
# * if synopsis not set, don't allow PR to be sent
# * at least one text field must have text in it before PR can be sent
# * see other fixme comments in text.

# FIXME: shouldn't have global variable.
defarray SENDPR_state

itcl::class Sendpr {
  inherit Ide_window

  # This array holds information about this site.  It is a private
  # common array.  Once initialized it is never changed.
  common _site

  # Initialize the _site array.
  global Paths tcl_platform

  # On Windows, there is no `send-pr' program.  For now, we just
  # hard-code things there to work in the most important case.
  if {$tcl_platform(platform) == "windows"} then {
    set _site(header) ""
    set _site(to) bugs@cygnus.com
    set _site(field,Submitter-Id) cygnus
    set _site(field,Originator) Nobody
    set _site(field,Release) "Internal"
    set _site(field,Organization) "Red Hat, Inc."
    set _site(field,Environment) ""
    foreach item {byteOrder machine os osVersion platform} {
      append _site(field,Environment) "$item = $tcl_platform($item)\n"
    }
    set _site(categories) foundry
  } else {
    set _site(sendpr) [file join $Paths(bindir) send-pr]
    # If it doesn't exist, try the user's path.  This is a hack for
    # developers.
    if {! [file exists $_site(sendpr)]} then {
      set _site(sendpr) send-pr
    }

    set _site(header) {}
    set outList [split [exec $_site(sendpr) -P] \n]
    set lastField {}
    foreach line $outList {
      if {[string match SEND-PR* $line]} then {
	# Nothing.
      } elseif {[regexp {^$} $line] || [regexp "^\[ \t\]" $line]} then {
	# Empty lines and lines starting with a blank are skipped.
      } elseif {$lastField == "" &&
		[regexp [format {^[^>]([^:]+):[ %s]+(.+)$} \t] \
		   $line dummy field value]} then {
	# A non-empty mail header line.  This can only occur when there
	# is no last field.
	if {[string tolower $field] == "to"} then {
	  set _site(to) $value
	}
      } elseif {[regexp {^>([^:]*):(.*)$} $line dummy field value]} then {
	# Found a field.  Set it.
	set lastField $field
	if {$value != "" && ![string match <*> [string trim $value]]} then {
	  set _site(field,$lastField) $value
	}
      } elseif {$lastField == ""} then {
	# No last field.
      } else {
	# Stuff into last field.
	if {[info exists _site(field,$lastField)]} then {
	  append _site(field,$lastField) \n
	}
	append _site(field,$lastField) $line
      }
    }
    # Now find the categories.
    regsub -all -- {[()\"]} [exec $_site(sendpr) -CL] \
      "" _site(categories)
    set _site(categories) [lrmdups [concat foundry $_site(categories)]]
  }

  # Internationalize some text.  We have to do this because of how
  # Tk's optionmenu works.  Indices here are the names that GNATS
  # wants; this is important.
  set _site(sw-bug) [gettext "Software bug"]
  set _site(doc-bug) [gettext "Documentation bug"]
  set _site(change-request) [gettext "Change request"]
  set _site(support) [gettext "Support"]
  set _site(non-critical) [gettext "Non-critical"]
  set _site(serious) [gettext "Serious"]
  set _site(critical) [gettext "Critical"]
  set _site(low) [gettext "Low"]
  set _site(medium) [gettext "Medium"]
  set _site(high) [gettext "High"]

  # Any text passed to constructor is saved and put into Description
  # section of output.
  constructor {{text ""}} {
    Ide_window::constructor [gettext "Report Bug"]
  } {
    global SENDPR_state

    # The standard widget-making trick.
    set class [$this info class]
    set hull [namespace tail $this]
    set old_name $this
    ::rename $this $this-tmp-
    # For now always make a toplevel.  Number 7 comes from Windows
    ::rename $hull $old_name-win-
    ::rename $this $old_name
    ::rename $this $this-win-
    ::rename $this-tmp- $this

    wm withdraw  [namespace tail $this]
###FIXME - this constructor callout will cause the parent constructor to be called twice

    ::set SENDPR_state($this,desc) $text

    #
    # The Classification frame.
    #

    Labelledframe [namespace tail $this].cframe -text [gettext "Classification"]
    set parent [[namespace tail $this].cframe get_frame]

    tixComboBox $parent.category -dropdown 1 -editable 0 \
      -label [gettext "Category"] -variable SENDPR_state($this,category)
    foreach item $_site(categories) {
      $parent.category insert end $item
    }
    # FIXME: allow user of this class to set default category.
    ::set SENDPR_state($this,category) foundry

    ::set SENDPR_state($this,secret) no
    checkbutton $parent.secret -text [gettext "Confidential"] \
      -variable SENDPR_state($this,secret) -onvalue yes -offvalue no \
      -anchor w

    # FIXME: put labels on these?
    set m1 [_make_omenu $parent.class class 0 \
	      sw-bug doc-bug change-request support]
    set m2 [_make_omenu $parent.severity severity 1 \
	      non-critical serious critical]
    set m3 [_make_omenu $parent.priority priority 1 \
	      low medium high]
    if {$m1 > $m2} then {
      set m2 $m1
    }
    if {$m2 > $m3} then {
      set m3 $m2
    }
    $parent.class configure -width $m3
    $parent.severity configure -width $m3
    $parent.priority configure -width $m3

    grid $parent.category $parent.severity -sticky nw -padx 2
    grid $parent.secret $parent.class -sticky nw -padx 2
    grid x $parent.priority -sticky nw -padx 2

    #
    # The text and entry frames.
    #

    Labelledframe [namespace tail $this].synopsis -text [gettext "Synopsis"]
    set parent [[namespace tail $this].synopsis get_frame]
    entry $parent.synopsis -textvariable SENDPR_state($this,synopsis)
    pack $parent.synopsis -expand 1 -fill both

    # Text fields.  Each is wrapped in its own label frame.
    # We decided to eliminate all the frames but one; the others are
    # just confusing.
    ::set SENDPR_state($this,repeat) [_make_text [namespace tail $this].desc \
					[gettext "Description"]]

    # Some buttons.
    frame [namespace tail $this].buttons -borderwidth 0 -relief flat
    button [namespace tail $this].buttons.send -text [gettext "Send"] \
      -command [list $this _send]
    button [namespace tail $this].buttons.cancel -text [gettext "Cancel"] \
      -command [list destroy $this]
    button [namespace tail $this].buttons.help -text [gettext "Help"] -state disabled
    standard_button_box [namespace tail $this].buttons

    # FIXME: we'd really like to have sashes between the text widgets.
    # iwidgets or tix will provide that for us.
    grid [namespace tail $this].cframe -sticky ew -padx 4 -pady 4
    grid [namespace tail $this].synopsis -sticky ew -padx 4 -pady 4
    grid [namespace tail $this].desc -sticky news -padx 4 -pady 4
    grid [namespace tail $this].buttons -sticky ew -padx 4

    grid rowconfigure  [namespace tail $this] 0 -weight 0
    grid rowconfigure  [namespace tail $this] 1 -weight 0
    grid rowconfigure  [namespace tail $this] 2 -weight 1
    grid rowconfigure  [namespace tail $this] 3 -weight 1
    grid columnconfigure  [namespace tail $this] 0 -weight 1

    bind [namespace tail $this].buttons <Destroy> \
	[itcl::code itcl::delete object $this]

    wm deiconify  [namespace tail $this]
  }

  destructor {
    global SENDPR_state
    foreach item [array names SENDPR_state $this,*] {
      ::unset SENDPR_state($item)
    }
    catch {destroy $this}
  }

  method configure {config} {}

  # Create an optionmenu and fill it.  Also, go through all the items
  # and find the one that makes the menubutton the widest.  Return the
  # max width.  Private method.
  method _make_omenu {name index def_index args} {
    global SENDPR_state

    set max 0
    set values {}
    # FIXME: we can't actually examine which one makes the menubutton
    # widest.  Why not?  Because the menubutton's -width option is in
    # characters, but we can only look at the width in pixels.
    foreach item $args {
      lappend values $_site($item)
      if {[string length $_site($item)] > $max} then {
	set max [string length $_site($item)]
      }
    }

    eval tk_optionMenu $name SENDPR_state($this,$index) $values

    ::set SENDPR_state($this,$index) $_site([lindex $args $def_index])

    return $max
  }

  # Create a labelled frame and put a text widget in it.  Private
  # method.
  method _make_text {name text} {
    Labelledframe $name -text $text
    set parent [$name get_frame]
    text $parent.text -width 80 -height 15 -wrap word \
      -yscrollcommand [list $parent.vb set]
    scrollbar $parent.vb -orient vertical -command [list $parent.text yview]
    grid $parent.text -sticky news
    grid $parent.vb -row 0 -column 1 -sticky ns
    grid rowconfigure $parent 0 -weight 1
    grid columnconfigure $parent 0 -weight 1
    grid columnconfigure $parent 1 -weight 0
    return $parent.text
  }

  # This takes a text string and finds the element of site which has
  # the same value.  It returns the corresponding key.  Private
  # method.
  method _invert {text values} {
    foreach item $values {
      if {$_site($item) == $text} then {
	return $item
      }
    }
    error "couldn't find \"$text\""
  }

  # Send the PR.  Private method.
  method _send {} {
    global SENDPR_state

    set email {}

    if {[info exists _site(field,Submitter-Id)]} then {
      set _site(field,Customer-Id) $_site(field,Submitter-Id)
      unset _site(field,Submitter-Id)
    }

    foreach field {Customer-Id Originator Release} {
      append email ">$field: $_site(field,$field)\n"
    }
    foreach field {Organization Environment} {
      append email ">$field:\n$_site(field,$field)\n"
    }

    append email ">Confidential: "
    if {$SENDPR_state($this,secret)} then {
      append email yes\n
    } else {
      append email no\n
    }

    append email ">Synopsis: $SENDPR_state($this,synopsis)\n"

    foreach field {Severity Priority Class} \
            values {{non-critical serious critical} {low medium high}
	      {sw-bug doc-bug change-request support}} {
      set name [string tolower $field]
      set value [_invert $SENDPR_state($this,$name) $values]
      append email ">$field: $value\n"
    }

    append email ">Category: $SENDPR_state($this,category)\n"

    # Now big things.
    append email ">How-To-Repeat:\n"
    append email "[$SENDPR_state($this,repeat) get 1.0 end]\n"

    # This isn't displayed to the user, but can be set by the caller.
    append email ">Description:\n$SENDPR_state($this,desc)\n"

    send_mail $_site(to) $SENDPR_state($this,synopsis) $email

    destroy $this
  }

  # Override from Ide_window.
  method idew_save {} {
    global SENDPR_state

    foreach name {category secret severity priority class synopsis} {
      set result($name) $SENDPR_state($this,$name)
    }
    # Stop just before `end'; otherwise we add a newline each time.
    set result(repeat) [$SENDPR_state($this,repeat) get 1.0 {end - 1c}]
    set result(desc) $SENDPR_state($this,desc)

    return [list Sendpr :: _restore [array get result]]
  }

  # This is used to restore a bug report window.  Private proc.
  proc _restore {alist x y width height visibility} {
    global SENDPR_state

    array set values $alist

    set name .[gensym]
    Sendpr $name $values(desc)
    foreach name {category secret severity priority class synopsis} {
      ::set $SENDPR_state($this,$name) $values($name)
    }
    $SENDPR_state($name,repeat) insert end $desc

    $name idew_set_geometry $x $y $width $height
    $name idew_set_visibility $visibility
  }
}
