#!/usr/local/bin/ruby -Ke
# goRua -- Gtk+ on Ruby User Agent for 2ch version 0.11d
# 春山 征吾 haruyama@unixuser.org
# $Id: goRua.rb,v 1.45 2002/01/28 03:27:32 haruyama Exp $

# このファイルはGPLで配付されています.
# このプログラムは無保証です.
require 'getopts'
require 'gtk'
require 'kconv'
require 'net/http'
begin
require 'zlib'
  $gzip=true
rescue LoadError
  STDERR.print "ruby-zlibがありませんでした。\ngzipで圧縮されたストリームを扱うことができません。\n現在のread.cgiの仕様ではgzipで圧縮されたストリームのみを提供するようです。\nruby-zlibのインストールを推奨します。\n"
  $gzip=false
#  exit
end

require 'connect2ch'
#設定

# proxyを使うなら設定
$proxy_addr = nil
$proxy_port = nil
# $proxy_addr = "proxy.addr..."  #ホスト名かIPアドレスを文字列で
# $proxy_port = 8080

#httpスキームを処理するコマンド
$http_command = "galeon -n %u"

#X印のPixmap用
$batu_xpm = [
"12 12 2 1",
"  c black",
".       c None s None",

"  ........  ",
".  ......  .",
"..  ....  ..",
"...  ..  ...",
"....    ....",
".....  .....",
"....    ....",
"...  ..  ...",
"..  ....  ..",
".  ......  .",
"  ........  ",
"............"

]

$menu_popup = false

#フォントの設定
Gtk::RC.parse_string <<EOS
style "default"
{
  fontset = "-mona-gothic-medium-r-normal--14-*"
}
widget_class "*" style "default"
EOS


style = Gtk::Style.new
$font = style.font

$font_height = 14

#notebookのtitleを切る文字数
$title_string_trunc = 7
# $title_string_trunc = 6

#Threadの内容を見るBoxのだいたいの大きさ
$view_width = 600
$view_height = 600
#Textの大きさ
#$text_width = $view_width-35
$text_width = $view_width-32
#Threadを選択するBoxのだいたいの大きさ
# $select_width = 300
$select_width = 250
$select_height = 600


#設定終わり

getopts("s")


#特殊変数

#プログラム用ディレクトリ
$base = ENV["HOME"]+ "/.goRua_2ch/"
$kensaku_kekka = '検索結果'

# w3m-0.2.2-inu-1.1 entity.cより
$latin1_eucjp_map =
[
    " ", "!",  "¢", "£", "CUR","￥", "|",  "§",   
    "¨", "(C)","-a", "≪", "¬", "-",  "(R)","￣",  
    "°", "±", "^2", "^3", "'",  "μ", "¶", "・",  
    ",",  "^1", "-o", "≫", "1/4","1/2","3/4","?",   
    "A`", "A'", "A^", "A~", "A:", "Å",  "AE","C,",  
    "E`", "E'", "E^", "E", "I`",  "I'", "I^", "I:",  
    "D-", "N~", "O`", "O'", "O^", "O~", "Oe", "×",  
    "φ", "U`", "U'", "U^", "U:", "Y'", "th", "ss",  
    "a`", "a'", "a^", "a~", "a:", "a",  "ae", "c",   
    "e`", "e'", "e^", "e:", "i`", "i'", "i^", "i:",  
    "d-", "n~", "o`", "o'", "o^", "o~", "oe", "÷",  
    "φ", "u`", "u'", "u^", "u:", "y'", "th", "y:"   
]


# w3m-0.2.2-inu-1.1 entity.c conv_entityより
def unquote_char(num)
  ret = " "
  if num < 0x80
    ret[0] = num 
  elsif num < 0xa0 
    # ここは手で似た文字にするか...
    ret = " "
  elsif num < 0x100 
    ret = $latin1_eucjp_map[num-0xa0]
  else
    ret = "  "
    # ここは手で似た文字にするか...
  end
  return ret
end

#雑多な関数
def unquote(str)
  if str 
  #手抜き
    str.gsub! "&gt;", ">"
    str.gsub! "&lt;", "<"
    str.gsub! "&nbsp;", " "
    str.gsub! "&quot;", "\""

#こういうのを利用したAAなどある?

#    str.gsub! "&reg;","\$"
#    str.gsub! "&copy;","@"
#    str.gsub! "&shy;","-"

#結局これはされていない?
#    str.gsub! "&amp;", "&"
#時々こうなってるのが.....
#    str.gsub! "&amp", "&"


#やるとuuencodeされているものなどがおかしくなる可能性がある
#toggleできるようにする
#    str = str.gsub(/&#(\d+);/){ |s|
#      unquote_char($1.to_i)
#    }

#    str = str.gsub (/&#x([\da-fA-F]+);/){
#      unquote_char($1.hex)
#    }



#    str.gsub! "&#\d+?;", " "  
#    str.gsub! "&amp#\d+?;", " "  
  end
  return str
end




#URIクラスが普及するまで? のいいかげんなパーサ


class UrlMenuItem < Gtk::MenuItem
  def initialize(text)
    @url = text
#2重に処理しすることになるので、いまいち
#将来整理
    @text=nil
    host,port,path=parse_http_url(text)
    
    if host
      if /\.(2ch|bbspink)\./=~ host  && %r!/test/(read|offlaw)\.cgi! =~ path

	# Thanks: >>66
	@text = "goRuaで処理: " +text
      else
	@text = "外部ブラウザで処理: " +text
      end
    else
      @text = text
    end
    
    super @text
    



    self.signal_connect('button_press_event'){|w,e|
#      self.activate
      $vvbox.exec_entry_text(@url)
    }



  end

end

class MessageFrame < Gtk::Frame
  def initialize(num,name,mailto,date_id,body)
    super "#{num}"
    self.set_label_align(0, 0)
    self.border_width(1)

    url = []

    table = Gtk::Table.new(2,2,false)
    self.add table
    name.gsub!("<.+?>","")
    name=unquote(name)
    if mailto && mailto != "" 
      mailto.gsub!("<.+?>","")
      mailto=unquote(mailto)
      name_label = Gtk::Button.new(name)
      tooltips = Gtk::Tooltips::new()
      tooltips.set_tip(name_label,mailto,"")
    else
      name_label = Gtk::Label.new(name)
    end
    table.attach name_label,0,1,0,1,0,0
    date_id_label =  Gtk::Label.new(date_id)
    table.attach date_id_label,1,2,0,1,0,0

    if body
      lines  = body.split("<br>")
      text = Gtk::Text.new
      text.set_word_wrap false
      table.attach text,0,2,1,2,nil,0
      
      tmp = lines.size+1
      
      lines.each { |tmp_line|
	
	tmp_line.sub!("\s+$","")
	tmp_line.sub!("^ ","") 
	
	tmp_line.gsub!("<.*?>","")
	tmp_line=unquote(tmp_line)	    
	text.insert nil,nil,nil, tmp_line + "\n"
	tmp+=$font.string_width(tmp_line)/ ($text_width)

#適当
	while true
	  # RFC 2396 より
 	  if /^(.*?)(h?ttp:[a-zA-Z0-9%&\?\/\;\:\@\&\=\+\$\,\-\_\.\!\~\*\'\\(\)]+)\s?(.*)$/ =~ tmp_line
	    host,port,path=parse_http_url($2)
	    if host
	      url << $2
	    end
	    tmp_line = $1 +" " + $3
	  elsif />>(\d+-\d+)(.*)$/ =~ tmp_line
 	    url << $1	      
	    tmp_line = $2
	  elsif />>(\d+-)(.*)$/ =~ tmp_line
 	    url << $1	      
	    tmp_line = $2
	  elsif />>(\d+)(.*)$/ =~ tmp_line
 	    url << $1	      
	    tmp_line = $2
	  else
	    break
	  end
	end
      }
      
      text.set_usize $text_width,tmp*$font_height
      text.signal_connect('button_press_event'){|w,e|
	if e.instance_of?(Gdk::EventButton) &&  e.button==3
	  $menu_popup = true
	  menu = Gtk::Menu.new
	  menu.signal_connect('button_press_event'){
	    menu.popdown
	  }

	  item = Gtk::MenuItem.new('Menu')
#	  item.set_sensitive false	  
	  menu.append item

	  separator = Gtk::MenuItem.new()
	  menu.append separator


	  url.each{ |text|
	    item = UrlMenuItem.new(text)
	    menu.append item
	  }
	  menu.popup(nil, nil, nil, e.button, e.time)
	  menu.show_all
	end
      }
      

    end
    @name = name
    @mailto= mailto
    @date_id=date_id
    @body=body

  end

end


class PostWindow < Gtk::Window
  include PostMessage2ch

  def initialize(thread)
    super Gtk::WINDOW_TOPLEVEL
    @thread = thread
    title = @thread.get_title
    self.set_title('書きこむ: ' + title)
    self.set_position(Gtk::WIN_POS_CENTER)
    self.set_transient_for($main_window)

    vbox = Gtk::VBox.new false,0
    
    title_label = Gtk::Label.new(title + ' への書きこみ')
    vbox.pack_start title_label,false,false

    table = Gtk::Table.new(2,2,false)
    name_label = Gtk::Label.new('名前:')
    @name_combo = Gtk::Combo.new
    @name_combo.entry.set_editable(true)
    table.attach name_label,0,1,0,1,0,0
    table.attach @name_combo,1,2,0,1

    mailto_label = Gtk::Label.new('mailto:')
    @mailto_combo = Gtk::Combo.new
    @mailto_combo.entry.set_editable(true)

    table.attach mailto_label,0,1,1,2,0,0
    table.attach @mailto_combo,1,2,1,2
    vbox.pack_start table,false,false

    @message = Gtk::Text.new
    @message.set_editable(true)
    vbox.pack_start @message

    self.add vbox

    hbox = Gtk::HBox.new false,10
    #書きこむボタン
    kakikomu_button = Gtk::Button.new('書きこむ')
    #キャンセルボタン
    cancel_button = Gtk::Button.new('キャンセル')
    
    hbox.pack_start kakikomu_button
    hbox.pack_start cancel_button
    vbox.pack_start hbox,false,false
    self.set_usize 600,400
    self.show_all

    cancel_button.signal_connect('clicked'){
      self.hide
    }


    kakikomu_button.signal_connect('clicked'){
      name = @name_combo.entry.get_text
      mailto = @mailto_combo.entry.get_text
      message=@message.get_chars(0,@message.get_length)
      if post_message(thread,name,mailto,message)
	$vvbox.push_entry_text('書きこみ成功')
	self.hide
      else
	$vvbox.push_entry_text('書きこみ失敗')
      #失敗した際どうするか
      end
    }


  end
end

#Threadの内容を表示するVBox
class ViewThreadVBox < Gtk::VBox


  def initialize(thread)
    @sw=nil
    super false,0

    @start = 1
    @view_num = 100
    @size = 0
    @thread = thread
    @file = @thread.get_file
    self.show

    self.signal_connect_after('button_press_event'){|w,e|
      if e.instance_of?(Gdk::EventButton) &&  e.button==3
	if $menu_popup
	  $menu_popup=false
	  break
	end

	menu = Gtk::Menu.new
	menu.signal_connect('button_press_event'){
	  menu.popdown
	}
	item = Gtk::MenuItem.new('Menu')
	menu.append item

	separator = Gtk::MenuItem.new()
	menu.append separator

	prev_item = Gtk::MenuItem.new('Prev')
	menu.append prev_item
	if @start > 1
	  prev_item.set_sensitive true
	else
	  prev_item.set_sensitive false
	end
	next_item = Gtk::MenuItem.new('Next')
	menu.append next_item
	if @start+@view_num-1 < @size
	  next_item.set_sensitive true
	else
	  next_item.set_sensitive false
	end
	menu.popup(nil, nil, nil, e.button, e.time)
	menu.show_all
	
	prev_item.signal_connect('button_press_event'){
	  self.set_start(@start-@view_num)
	  $vvbox.set_notebook_page
	}
	  
	next_item.signal_connect('button_press_event'){
	  self.set_start(@start+@view_num)
	  $vvbox.set_notebook_page
	}
	

	end
    }
  end

  def get_start
    return @start 
  end

  def get_view_num
    return @view_num 
  end

  def get_size
    return @size 
  end


  def set_start(num)
    if num>0
      @start = num
    else
      @start =1
    end
  end

  def set_view_num(num)
    @view_num   = num
  end

  def set_size(num)
    @size  = num
  end

  def set_sw(sw)
    if @sw
      self.remove @sw
    end
    @sw  = sw
    self.pack_start @sw
  end


  def get_sw
    return @sw
  end

  def get_thread
    return @thread
  end

  #Threadの内容をparseし表示
  def make_view  
    $vvbox.push_entry_text('描画中')
    
    @file.rewind
    vbox_view = Gtk::VBox.new(false,0)
    if @view_num <= 0
      return
    end
    
    i=1
    j=0

    tmp = self.get_thread.get_title 
    if  tmp ==nil || tmp== ""
      while line=@file.gets
	line.strip!	
	begin
	  line=Kconv::kconv(line,Kconv::EUC,Kconv::SJIS)
	  if /^(?:\+OK|-INCR)\s+(\d+)/ =~ line
	    next
	  end
	  (name,mailto,date_id,body,title)=line.split(/<>/)
	  if title && title !=""
	    self.get_thread.set_title(title)
	    tmp = title+ " (#{self.get_size})"
	    self.get_thread.set_title_and_size(tmp)
	    $vvbox.set_title(tmp)
	    hbox = $vvbox.create_tab_label(title,self)
	    notebook = $vvbox.get_notebook
	    notebook.set_tab_label(self,hbox)
	    notebook.set_menu_label(self,Gtk::Label.new(title))
	    $vvbox.show_all
	  end
	  break
	rescue
	end
      end
    end
    @file.rewind
    while line=@file.gets

      begin            

	line=Kconv::kconv(line,Kconv::EUC,Kconv::SJIS)
	line.strip!	
	if /^(?:\+OK|-INCR)\s+(\d+)/ =~ line
	  next
	end

	
	if i< @start
	  i+=1
	  next
	end

	(name,mailto,date_id,body,title)=line.split(/<>/)
	
	
	frame = MessageFrame::new(i,name,mailto,date_id,body)
	vbox_view.pack_start frame,false,false

      rescue

      end
      j+=1
      i+=1
      if j>=@view_num
	break
      end
    end

    @sw.add_with_viewport vbox_view
    @sw.show_all
    $vvbox.pop_entry_text    
  end



  def set_file(start=nil,file=nil)

    if file
      @file =file
    end

    @file.rewind
    @size=0

    while line=@file.gets
      
      #いらないとはおもうが一応
      line=Kconv::kconv(line,Kconv::EUC,Kconv::SJIS)

      if /^(?:\+OK|-INCR)\s+(\d+)/ =~ line
	next
      end
      @size+=1
    end

    if start
      self.set_start(start)
    end

    @file.rewind
  end
end



#Threadの内容を見るBox
class ViewVBox < Gtk::VBox

  #長くなったなぁ...
  def initialize()
    super(false,0)
    @v_thread=nil
    
    @sessions=[]
   
    #メニュー   
    @mbar = Gtk::MenuBar.new
    @file_item = Gtk::MenuItem.new('File')
    @file_menu = Gtk::Menu.new

#Thanks: >> 63
    @toziru_item = Gtk::MenuItem.new('全部閉じる')
    @file_menu.add @toziru_item

    @quit_item = Gtk::MenuItem.new('Quit')
    @file_menu.add @quit_item
    @file_item.set_submenu @file_menu
    @mbar.append @file_item

    @one_item = Gtk::MenuItem.new('1-')
    @mbar.append @one_item

    
    @prev_item = Gtk::MenuItem.new('Prev')
    @mbar.append @prev_item
    @next_item = Gtk::MenuItem.new('Next')
    @mbar.append @next_item

    @new_item = Gtk::MenuItem.new('最新')
    @mbar.append @new_item

    @reload_item = Gtk::MenuItem.new('再読込')
    @mbar.append @reload_item

    @url_item = Gtk::MenuItem.new('URL表示')
    @mbar.append @url_item

    @check_new_item = Gtk::CheckMenuItem.new('   新着の表示')
    @check_new_item.activate
    @mbar.append @check_new_item

    @bookmark_item = Gtk::MenuItem.new('ブックマークに追加')
    @mbar.append @bookmark_item

    @kakikomi_item = Gtk::MenuItem.new('書きこむ')
    @mbar.append @kakikomi_item

 

    self.pack_start @mbar, false, false, 0

    @title_label = Gtk::Label.new('')
    
    self.pack_start @title_label,false,false,0

    @entry = Gtk::Entry.new
    self.pack_start @entry,false,false,0
    @entry.set_editable(true)
    
    @entry_text_stack = []

    @notebook = Gtk::Notebook::new()
    @notebook.set_usize $view_width, $view_height
    @notebook.signal_connect("switch_page") do |widget, page, num_page|
      self.page_switch(widget, page, num_page)
    end
    @notebook.set_scrollable(true)
    @notebook.popup_enable
    self.pack_start @notebook


    self.set_item


    @url_item.signal_connect('activate'){
      if @v_thread
	@entry.set_text @v_thread.get_thread.get_url
      end
    }


    @quit_item.signal_connect('activate'){
      exit
    }

    @next_item.signal_connect('activate'){
      unless @v_thread
	return
      end
      
      @v_thread.set_start(@v_thread.get_start+@v_thread.get_view_num)
      self.set_notebook_page
    }

    @one_item.signal_connect('activate'){
      unless @v_thread 
	return
      end
      @v_thread.set_start(1)
      self.set_notebook_page
    }



    @prev_item.signal_connect('activate'){
      if @v_thread == nil
	return
      end
      @v_thread.set_start(@v_thread.get_start-@v_thread.get_view_num)
      self.set_notebook_page
    }

    @new_item.signal_connect('activate'){
      if @v_thread == nil
	return
      end

      @v_thread.set_start(@v_thread.get_size-@v_thread.get_view_num+1)
      self.set_notebook_page
    }

    @reload_item.signal_connect('activate'){
      if @v_thread == nil
	return
      end

      
      tmp = @v_thread.get_thread.load_file
      @v_thread.set_file(tmp,@v_thread.get_thread.get_file)
      
      tmp = @v_thread.get_thread.get_title  + " (#{@v_thread.get_size})"
      self.set_title(tmp)

      page = @notebook.get_nth_page(@notebook.get_current_page)
      hbox = create_tab_label(tmp,@v_thread)


      @notebook.set_tab_label(page,hbox)
      @notebook.set_menu_label(page,Gtk::Label.new(tmp))
      
      self.set_notebook_page
    }

    @bookmark_item.signal_connect('activate'){
      if @v_thread
	title = Kconv::kconv(@v_thread.get_thread.get_title,Kconv::SJIS,Kconv::EUC)
	url = @v_thread.get_thread.get_url
	str = "T #{url} #{title}"
	self.append_str_to_file(str,$bookmarks)
	if $bookmarks_board 
	  $bookmarks_board.append_thread @v_thread.get_thread
	  $bookmarks_board.get_item.set_bookmarks_item
	end
      end 
    }

    @kakikomi_item.signal_connect('activate'){
      if @v_thread
	window = PostWindow.new(@v_thread.get_thread)
      end
    }

    @toziru_item.signal_connect('activate'){
      while @v_thread
	@notebook.remove_page(@notebook.page_num(@v_thread))
	if @notebook.get_current_page == -1
	  @v_thread= nil
	else
	  @v_thread=@notebook.cur_page.child
	end
      end
      @sessions = []
      self.write_sessions
      self.set_item
    }



    @entry.signal_connect('activate') {|widget|
      text = @entry.get_text
      text.strip!
      exec_entry_text(text)
    }
  end

  def exec_entry_text(text)
    if text == "" || !text 
      return
    end


    host,port,path=parse_http_url(text)

    if host
      if /^ttp/ =~ text
	text = "h" +text
      end

      internal=false
      catch(:tag) do
	if /\.(2ch|bbspink)\./=~ host 
	  if %r!^(.*/test/(read|offlaw)\.cgi/.+?/[0-9]+?/).*! =~ text
	    text = $1
	    
	    # とりあえず read.cgi使うほうに固定
	  elsif %r!^(.*/test/(read|offlaw)\.cgi)\?(.*)! =~ text
	    base = $1
	    tmp = $3
	    if /bbs=([^&]+)&.*key=([^&]+)/i =~ tmp
	      bbs = $1
	      key = $2
	    elsif  /key=([^&]+)&.*bbs=([^&]+)/i =~ tmp
	      bbs = $2
	      key = $1
	    else
	      throw :tag
	    end
	    text = base+"/"+bbs+"/"+key+"/"
	  else
	    throw :tag
	  end
	  internal=true
	  thread = Thread2ch.new("",text,nil,false)
	  pre_size=thread.load_file
	  if thread.get_file
	    $vvbox.set_thread(thread,pre_size)
	  end
	end
      end
      if !internal
	fork do
	  $SAFE = 2
	  tmp =$http_command.dup
	  tmp.sub!('%u','"'+text+'"')
#	  tmp.gsub!('&','\\\&')
#	  tmp.gsub!(';','\\\;')
#	  tmp.gsub!('\(','\\\(')
#	  tmp.gsub!('\)','\\\)')
#	  tmp.gsub!('<','\\\<')
#	  tmp.gsub!('>','\\\>')
#	  tmp.gsub!('|','\\\|')
	  tmp.untaint
	  exec(tmp)
	end
      end
    end

    unless @v_thread
      return
    end
    
    text.sub!(">>","")
    text.strip!
    _size=@v_thread.get_size
      
    if text =~ /^(\d+)$/
      start = Integer($1)
      if start <= _size && start > 0
	@v_thread.set_start(start)
	@v_thread.set_view_num(1)
	self.set_notebook_page
      end
    elsif text =~ /^(\d+)\s*-$/
      start = Integer($1)
      if start < _size && start > 0
	@v_thread.set_start(start)	  
      end
      self.set_notebook_page
    elsif text =~ /^(\d+)\s*-\s*(\d+)$/
      start = Integer($1)
      stop = Integer($2)
      view_num = stop - start + 1
      if start <= _size && start > 0 && view_num > 0
	@v_thread.set_start(start)
	@v_thread.set_view_num(view_num)  
	self.set_notebook_page
      end
    end

  end

  def get_check_new_item
    return @check_new_item 
  end

  def push_entry_text(text)
    @entry_text_stack.push(@entry.get_text)
    @entry.set_text(text)
    if $no_start
      while Gtk.main_iteration()
      end
    end 

  end 

  def pop_entry_text
    text=@entry_text_stack.pop
    @entry.set_text(text)
  end 

  
  def set_thread(thread,start)
    @sw = Gtk::ScrolledWindow.new(nil,nil)
    @v_thread = ViewThreadVBox.new(thread)
    @v_thread.set_sw(@sw)
    @v_thread.set_file(start)
    self.set_notebook_page
  end

  def get_notebook
    return @notebook
  end

  def set_notebook_page

    self.set_item

    tmp =@notebook.page_num(@v_thread)
    if tmp == -1
      

      #push_styleがうまくされていない模様
#      style = Gtk::Style.new
#      style.set_font Gdk::Font.fontset_load '-mona-gothic-bold-r-normal--12-*'
#      Gtk::Widget.push_style style
      
      


      tmp = @v_thread.get_thread.get_title + " (#{@v_thread.get_size})"
      hbox =create_tab_label(tmp,@v_thread)

      @notebook.append_page_menu(@v_thread,hbox,Gtk::Label.new(tmp))
      @sessions <<  @v_thread.get_thread
      # あとでappendにするかと思ったが、
      # たいしたこともないししなくてもいいか。
      self.write_sessions

      hbox.show_all
#      Gtk::Widget.pop_style
      @notebook.set_page(-1)
    else

      
      @sw = Gtk::ScrolledWindow.new(nil,nil)
      @v_thread.set_sw(@sw)
      @v_thread.set_file
    end

    @v_thread.make_view

  end

  def write_sessions
    open($sessions,'w') { |file|
      @sessions.each{ |thread|
	if thread.get_dat 
	  str =  "S #{thread.get_url} #{thread.get_title}\n"
	else
	  str = "T #{thread.get_url} #{thread.get_title}\n"	
	end
	file.print Kconv::kconv(str,Kconv::SJIS,Kconv::EUC)
      }
    }
  end 

  def append_str_to_file(str,filename)
    open(filename,'a') { |file|
      file.print "#{str}\n"
    }
  end 

    


  def create_tab_label(str,v_thread)
    hbox = Gtk::HBox.new false,5
    
    trunc = trunc_string(str,$title_string_trunc)
    hbox.pack_start Gtk::Label.new(trunc)
    button = Gtk::Button.new
    button.add Gtk::Pixmap.new($batu_pix, $batu_mask)
    button.signal_connect('clicked'){
      @notebook.remove_page(@notebook.page_num(v_thread))
      @sessions.delete(v_thread.get_thread)
      self.write_sessions
      if @notebook.get_current_page == -1
	@v_thread= nil
      else
	@v_thread=@notebook.cur_page.child
      end
      self.set_item
    }
      

    hbox.pack_start button,false,false
    hbox.show_all
    return hbox
  end


#外に出す?
  def trunc_string(str,trunc)

     if  /^(.{1,#{trunc}}).*$/ =~ str
       return $1
     else
       return str
     end
  end


 def page_switch(widget, page, page_num)
   oldpage = @notebook.cur_page
   if (page == oldpage)
     return
   end

   @v_thread=page.child
   self.set_title(@v_thread.get_thread.get_title+ " (#{@v_thread.get_size})")
   self.set_item
 end


  def set_title(title)
    @title_label.set title
  end

  def set_item
    unless @v_thread
      @one_item.set_sensitive false
      @prev_item.set_sensitive false
      @next_item.set_sensitive false
      @new_item.set_sensitive false
      @reload_item.set_sensitive false
      @url_item.set_sensitive false
      @bookmark_item.set_sensitive false
      @kakikomi_item.set_sensitive false
      @toziru_item.set_sensitive false
      return
    end

    @reload_item.set_sensitive true
    @url_item.set_sensitive true
    @bookmark_item.set_sensitive true
    @kakikomi_item.set_sensitive true
    @toziru_item.set_sensitive true

    start= @v_thread.get_start
    view_num=@v_thread.get_view_num
    size=@v_thread.get_size

    if start > 1
      @one_item.set_sensitive true
    else
      @one_item.set_sensitive false
    end

    if view_num < size
      @new_item.set_sensitive true
    else
      @new_item.set_sensitive false
    end

    if start > 1
      @prev_item.set_sensitive true
    else
      @prev_item.set_sensitive false
    end

    if start+view_num-1 < size
      @next_item.set_sensitive true
    else
      @next_item.set_sensitive false
    end
  end

  def set_start (start)

    view_num=@v_thread.get_view_num
    size=@v_thread.get_size

    if  start < @size
      if start+@view_num < 1
	@v_thread.set_start(1)
      else
	@v_thread.set_start(start)
      end
    end
  end

  def set_view_num(num)
    if num > 0
      @v_thread.set_view_num( num)
    end
  end

end 

#スレッドを管理する
class Thread2ch
  include GetThread2ch

  def initialize(title,url_base,dat_file,dat)

    @title_and_size = title
    @title = title.gsub(/\s+\(\d+\)\s*$/,"")
    @dat = dat
    @bbs = nil
    @key = nil
    @base_path = nil
    if dat_file
      if @dat
	@url = url_base + dat_file
      else
	@url = url_base + dat_file.sub("\.dat$","")+"/"
      end
    else
      url_base.strip!
      @url = url_base
    end



    @file= nil
    @size= 0
    @num_of_statement= 0

    @host,@port,@path = parse_http_url(@url)
    if @host
      @filename = $base + "thread/"+ @host + "_" + "#{@port}" + "_" + @path.gsub("/","_")
    # initializeの引数から見直さんと
    # 将来整理
      if %r!^(/.*)/(.+?)/(.+?)/?$! =~ @path
	tmp = $1
	@bbs = $2
	@key = $3.sub("\.dat$","")
	@base_path = tmp.sub("test/read\.cgi$","")
      else
	STDERR.print "おかしなURL: #{@url}\n"
      end
    else
      @filename =nil
      STDERR.print "対応しているURLではありません。: #{@url}\n"
    end

  end

  def get_bbs
    return @bbs
  end

  def get_key
    return @key
  end

  def get_base_path
    return @base_path
  end

  def set_title(title)
    @title = title
  end


  def get_title
    return @title
  end

  def set_title_and_size(title)
    @title_and_size = title
  end


  def get_title_and_size
    return @title_and_size
  end

  def get_dat
    return @dat
  end


  def get_url
    return @url
  end

  def get_host
    return @host
  end
  def get_port
    return @port
  end
  def get_path
    return @path
  end
  def get_filename
    return @filename
  end


  def get_file
    return @file
  end

  def load_file

    $vvbox.push_entry_text('Thread読込中')
    @file,pre_size = get_thread_file(self)
    $vvbox.pop_entry_text
    
    if !pre_size || pre_size==0 
      size=1
    end
    if $vvbox.get_check_new_item.active?
      return pre_size
    end 
    return 1
  end

  def set_size(size)
    @size = size
  end

  def get_size
    return @size 
  end


  def set_num_of_statement(num)
    @num_of_statement = num
  end

  def get_num_of_statement
    return @num_of_statement
  end

end

#板を管理する
class Board2ch
  include GetBoardSubject2ch

  def initialize(title,url,dat,special=false)
    @title = title
    @url = url
    @host,@port,@path = parse_http_url(@url)
    @special=special
    if @host
      @filename = $base + "subject/"+ @host + "_" + "#{@port}" + "_" + @path.gsub("/","_")
    else
      @filename =nil
      if @special ==false
	STDERR.print "対応しているURLではありません。: #{@url}\n"
      end 
    end
      
    @dat = dat

    @thread_array=[]
    @item = nil
  end

  def get_item
    return @item
  end

  def set_item(item)
    @item = item
  end

  def get_special
    return @special
  end

  def append_thread(thread)
    @thread_array << thread
  end

  def get_thread_array
    return @thread_array
  end

  def set_filename(filename)
    @filename = filename
  end

  def get_title
    return @title
  end

  def get_url
    return @url
  end

  def get_host
    return @host
  end
  def get_port
    return @port
  end
  def get_path
    return @path
  end
  def get_filename
    return @filename
  end

  def get_dat
    return @dat
  end

end

#板用のTreeItem
class BoardTreeItem < Gtk::TreeItem

  def initialize(board)
    @board=board    
    super(@board.get_title)

    @num_of_thread=0
    @subtree=nil
    @special = @board.get_special

    @board.set_item self
    self.signal_connect('select') { 
      if @special
      else
	self.get_board_subject
      end
      self.expand
    }
    @destroyed=false
    self.signal_connect('destroy'){
      @destroyed=true
    }
  end



  def set_bookmarks_item
    if @destroyed
      return
    end

    array = @board.get_thread_array
    if array  != [] 
      subtree = Gtk::Tree.new
      self.set_subtree subtree
      i=0
      array.each{ |thread|
	thread_item = Gtk::ThreadTreeItem.new(thread,self)
	subtree.append thread_item
	thread_item.show_all
	i+=1
      }
      self.set_num_of_thread(i)
    end 
    self.show_all
    self.expand
  end

  def get_board_subject

    $vvbox.push_entry_text('Subject読込中')
    file = @board.get_board_subject_file(@board)
    $vvbox.pop_entry_text
    unless file
      return
    end
    
    file.rewind

    subtree = Gtk::Tree.new
    self.set_subtree subtree


    if @board.get_port == 80
      url = "http://" + @board.get_host 
    else
      url = "http://" + @board.get_host + ":#{@board.get_port}" 
    end

    path = @board.get_path 
    

    if @board.get_dat
      url += path + "dat/"
    else
      if /^(\/.*)(\/.+)$/i =~ path
	url += $1
	path = $2
      end
      url += "/test/read.cgi" + path
    end

    i=0
    file.each{ |line|
      line.chop!
      line=Kconv::kconv(line,Kconv::EUC,Kconv::SJIS)

      if /(.*)<>(.*)/ =~ line
	title = unquote($2)
	thread = Thread2ch.new(title,url,$1,@board.get_dat)
	@board.append_thread thread
	thread_item = Gtk::ThreadTreeItem.new(thread,self)
	subtree.append thread_item
	thread_item.show
	i+=1      
      end
    }
    self.set_num_of_thread(i)
    file.close
    self.show_all
  end


  def get_board
    return @board
  end

  def set_num_of_thread(num)
    @num_of_thread=num
  end
  def get_num_of_thread
    return @num_of_thread
  end

  def set_subtree(subtree)
    if @subtree
      begin
	@subtree.clear_items 0,@num_of_thread
      rescue ArgumentError
      end
    end
    @subtree=subtree
    super
  end
end

#Thread用のTreeItem
class ThreadTreeItem < Gtk::TreeItem
  def initialize(thread,board_item)
    @thread=thread
    @board_item = board_item
    super(@thread.get_title_and_size)
    tooltip = Gtk::Tooltips.new()
    tooltip.set_tip(self,@thread.get_title_and_size,"")
    tooltip.set_delay(1000)
    self.signal_connect('select') { 
      pre_size=@thread.load_file
      if @thread.get_file
	$vvbox.set_thread(@thread,pre_size)
      end
    }
    self.signal_connect('button_press_event'){|w,e|
      if e.instance_of?(Gdk::EventButton) &&  e.button==3
	@board_item.collapse
      end
    }

  end
  def get_thread
    return @thread
  end

end

class SelectionVBox < Gtk::VBox

  def initialize(board_file_array)

    @board_hash = Hash.new
    @category_array = []
    @sw=nil
    $bookmarks_board =nil
    board_file_array.each { |board_file|
      while line = board_file.gets
	line.chop!
	line=Kconv::kconv(line,Kconv::EUC,Kconv::SJIS)
	
	if /^C\s(.*)/ =~ line
	  category = $1.strip
	  @category_array.push category
	  @board_hash[category] = []
	elsif  /^D\s(\S*)/ =~ line
	  board = Board2ch.new($1,"",false,true)
	  @board_hash[category] << board
	  $bookmarks_board =board
	elsif  /^T\s(\S*)\s(.*)/ =~ line
	  thread = Thread2ch.new($2.strip,$1,nil,false)
	  unless $bookmarks_board
	    board = Board2ch.new('Threadのブックマーク',"",false,true)
	    unless category
	      category = 'お気に入り'
	      @category_array.push category
	      @board_hash[category] = []
	    end
	    @board_hash[category] << board
	    $bookmarks_board =board
	  end
	  $bookmarks_board.append_thread thread
	elsif  /^S\s(\S*)\s(.*)/ =~ line
	  thread = Thread2ch.new($2.strip,$1,nil,true)
	  unless $bookmarks_board
	    board = Board2ch.new('Threadのブックマーク',"",false,true)
	    unless category
	      category = 'お気に入り'
	      @category_array.push category
	      @board_hash[category] = []
	    end
	    @board_hash[category] << board
	    $bookmarks_board =board
	  end

	  $bookmarks_board.append_thread thread
	elsif  /^B\s(\S*)\s(.*)/ =~ line
	  if $gzip
	    board = Board2ch.new($2.strip,$1,false)
	  else
	    board = Board2ch.new($2.strip,$1,true)
	  end
	  @board_hash[category] << board
	elsif  /^A\s(\S*)\s(.*)/ =~ line
	  board = Board2ch.new($2.strip,$1,true)
	  @board_hash[category] << board
	end
      end
      board_file.close
    }
    category = $kensaku_kekka
    @category_array.push category
    @board_hash[category] = []
    
    @cur_category = @category_array[0]
    super(false,0)

    @combo = Gtk::Combo.new
    @combo.entry.set_editable(false)
    @combo.set_popdown_strings(@category_array)

#    @combo.entry.add_events( Gdk::ALL_EVENTS_MASK)
    @combo.entry.add_events( Gdk::KEY_RELEASE_MASK)


    @combo.entry.signal_connect('key_release_event'){
      @cur_category = @combo.entry.get_text
      self.set_sw
    }

    @combo.list.signal_connect('button_release_event'){
      @cur_category = @combo.entry.get_text
      self.set_sw
    }

#    @combo.entry.signal_connect('changed'){
#      unless @combo.list.visible?
#	@cur_category = @combo.entry.get_text
#	self.set_sw
#      end
#    }


    self.pack_start @combo,nil


    entry = Gtk::Entry.new
    self.pack_start entry,false,false,0
    entry.set_editable(true)
    self.show_all
    entry.signal_connect('activate'){ |widget|
      text=entry.get_text.strip
      if text == ""
	break
      end 
      re = Regexp.new(text,true)



      kensaku_board = Board2ch.new($kensaku_kekka + ': ' + text,"","",true)
      @board_hash[$kensaku_kekka] << kensaku_board
      item = Gtk::BoardTreeItem.new(kensaku_board)
      @board_hash[@cur_category].each{ |board|
	board.get_thread_array.each{|thread|
	  if re =~ thread.get_title_and_size
	    kensaku_board.append_thread(thread)
	  end
	}
      }
      @combo.entry.set_text($kensaku_kekka)
      # 3つでてきたから後で整理するか
      @cur_category = @combo.entry.get_text
      self.set_sw
    }



    self.set_sw

  end
  
  def set_sw
    if @sw
      self.remove @sw
    end
    @sw = set_tree(@board_hash[@combo.entry.get_text])
    self.pack_start @sw      
    @sw.show_all
  end

  def set_tree(hash)
    @tree = Gtk::Tree.new
    @tree_size=0

    sw =  Gtk::ScrolledWindow.new()
    sw.set_usize $select_width,$select_height
    sw.add_with_viewport @tree   

    @tree.clear_items 0,@tree_size
    @tree_size=0
    hash.each{ |board|
      @tree_size+=1

      item = Gtk::BoardTreeItem.new(board)
      @tree.append item
      array = board.get_thread_array
      if array  != []
	subtree = Gtk::Tree.new
	item.set_subtree subtree
	i=0
	array.each{ |thread|
	  thread_item = Gtk::ThreadTreeItem.new(thread,item)
	  subtree.append thread_item
	  thread_item.show_all
	  i+=1
	}
	item.set_num_of_thread(i)
	item.show_all
      end 
    }
    @tree.show_all

    return sw
  end

end

board_info = $base + "board_info"

if File.exist?(board_info)
  file = open(board_info,'r')
else
  $base = ENV["HOME"]+ "/.2ch_ruby_gtk/"
  board_info = $base + "board_info"
  if File.exist?(board_info)
    file = open(board_info,'r')
    STDERR.print "~/.2ch_ruby_gtk ディレクトリを ~/.goRua_2ch に移動してください。\n将来 ~/.2ch_ruby_gtk はサポートされなくなります\n"
  else
    STDERR.print "not found: ~/.goRua_2ch and ~/.2ch_ruby_gtk. see README\n"
    exit
  end
end

$bookmarks = $base + "bookmarks"
$sessions = $base + "sessions"
sessions_backup = $base + "sessions_backup"


$main_window = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL)
$main_window.set_title("goRua -- Gtk+ on Ruby User Agent for 2ch version 0.11d")

# Thanks : honeto@mail.goo.ne.jp さん
#hbox = Gtk::HBox.new(false,0)
hpaned = Gtk::HPaned.new

$vvbox = ViewVBox.new()

file_array=[]
if File.exist?($bookmarks)
  bm_file = open($bookmarks)
  file_array << bm_file
end 
file_array << file
$svbox = SelectionVBox.new(file_array)

# Thanks : honeto@mail.goo.ne.jp さん
#hbox.pack_start $svbox,nil
#hbox.pack_start $vvbox
#window.add hbox
hpaned.pack1 $svbox, nil, true
hpaned.pack2 $vvbox, nil, true
$main_window.add hpaned


$main_window.show_all
$batu_pix, $batu_mask =
  Gdk::Pixmap::create_from_xpm_d($main_window.window, nil, $batu_xpm)

# Thanks: >>73
$main_window.signal_connect("destroy") do exit end

$no_start = false


if File.exist?($sessions) 
  open($sessions,'r'){ |file|
    open(sessions_backup,'w'){|backup|
      backup.write file.read
    }
  }
  if !$OPT_s
    open($sessions,'r'){ |file|
      while line=file.gets
	line = Kconv::toeuc(line)
	thread=nil
	line.strip!
	if  /^S\s(\S*)\s(.*)/ =~ line
	  thread = Thread2ch.new($2.strip,$1,nil,true)
	elsif  /^T\s(\S*)\s(.*)/ =~ line
	  thread = Thread2ch.new($2.strip,$1,nil,false)
	end
	if thread
	  pre_size=thread.load_file
	  if thread.get_file
	    $vvbox.set_thread(thread,pre_size)
	  end
	end
      end
    }
  end
end
$no_start = true

Gtk.main
