#!/usr/bin/perl -w
#
# gnview 2ch BBS Viewer
# http://gnview.sourceforge.jp/

#--------------------------------------
# ライブラリ読み込み
#--------------------------------------

use strict;
use Config;
BEGIN {
   $Config{useithreads} or die "Recompile perl with ithreads to use this program.";
}
use threads;
use threads::shared;
use Thread::Queue;

use utf8;
use POSIX qw/ locale_h strftime /;
use Switch;
use Encode qw/ encode from_to decode /;
use Encode::Guess qw/ euc-jp shiftjis /;
use Time::HiRes qw/ sleep gettimeofday tv_interval /;
use Time::Local;

use Gtk2 '-init';	# perl-Gtk2 パッケージが必要
use Gtk2::GladeXML;	# perl-Gtk2-GladeXML パッケージが必要
use Gtk2::Pango;
use Glib::Object::Subclass
    Glib::Object::,
    interfaces => [ Gtk2::TreeModel::, Gtk2::TreeSortable:: ],
    ;


use HTML::TokeParser;
use HTML::Entities;
use HTTP::Date;
use Compress::Zlib;	# perl-Compress-Zlibパッケージが必要

use LWP::UserAgent;	# perl-libwww-perl パッケージが必要
use LWP::MediaTypes qw/ guess_media_type /;
use HTTP::Cookies;

use Image::Magick;	# ImageMagick-perl パッケージが必要

use Data::Dumper;



#--------------------------------------
# 全体設定
#--------------------------------------

use constant TRUE  => 1;
use constant FALSE => 0;

my $gnIdxFileVersion = "1.01";	# よくわかんないけどFolder.idxのなかに出てくるので入れるようにした
					# ギコナビ互換用
# gnviewのバージョン
my $gnVersion = "0.5";
   
# ユーザーエージェント指定
my $gnUsrAgent : shared = "Monazilla/1.00 \(gnview\/$gnVersion\)";

#--------------------------------------
# スレッド間共有変数
#--------------------------------------
our $gnDebugFlag : shared = 0;	# デバッグレベル指定(0 = まったく出力しない, 1-->大きくなるほど詳細な
                                        #                    デバッグ出力が行われる)
our $hold : shared;
our $gnImgGetSt : shared;
our $queue : shared = Thread::Queue->new;
our $fnsize : shared;

#--------------------------------------
# スレッドの開始
#--------------------------------------
#our $locked : shared = 0;
my $thr = threads->new (\&command_runner);


#--------------------------------------
# グローバル変数
#--------------------------------------

my $gnEnvFile;
my $gnURLIniFile;
my $gnSentIniFile;

my %gnEnvArg;		# gikoNavi.ini保存配列
my %gnEnvArgN;

my %gnURLIni;		# url.iniの内容をハッシュ配列に格納
my %gnInitCfg;	# 初期設定保存配列

my $gnCacheTblFile;


my $boardcfg;				# 板一覧設定ファイル
my %gnBoardTbl;

my $logdir;
my $gladefn = &gnGetLibPath . "\/gnview.glade";   # GladeでデザインしたGUIのXMLファイルの場所

my $gnHoverOverLink = FALSE;            # カーソルがリンクの上にあるかのフラグ
my $gnPopupText     = "";               # レス表示ウィンドウがポップアップしているかのフラグ
                                        # ポップアップがなければNULL,
                                        # ポップアップしていればGtk2::Windowが入っている
my $gnPopupImg      = undef;            # 画像表示ウィンドウがポップアップしているかのフラグ
                                        # ポップアップがなければundef,
my $gnPop2;
                                        # ポップアップしていればGtk2::Windowが入っている
my $gnPopupImgBuf;			# ポップアップ用画像バッファ
my $gnTmpfn;
my %gnCacheTbl;	# 画像キャッシュテーブル保存配列

#--------------------------------------
# グローバル定数
#--------------------------------------
   
# カーソル作成
# スレのポップアップメニュー用
my $gnCursor_Beam = Gtk2::Gdk::Cursor->new('xterm');
my $gnCursor_Link = Gtk2::Gdk::Cursor->new('hand2');

# 2ch用定数
my $gnKeyTime = "1104688508";

#--------------------------------------
# グローバル変数
#--------------------------------------
my $gnItaLogDir = '';                   # 板毎のログ格納ディレクトリ
my @gnSureViewInfo;                     # スレ表示管理用配列
                                        # n(偶数,0スタート): スレファイル名
my $gnSureViewPageNum = 0;		# スレ表示用ページの最終番号
                                        # n+1(奇数,1スタート): Notebookタブの番号
my $tmpdir = &gnGetTmpDir;		# テンポラリディレクトリを取得
if($gnDebugFlag) { print "$tmpdir\n"; }

my $gnTimeforPopup = 0;		# ポップアップウィンドウ遅延表示用
my $gnPopupCallback;
my $gnImgUpdateCallback;
my $gnPopupCnt = 0;

my $gnSuppressEventFlg = FALSE;	# コールバック関数を実行するかどうかのフラグ。
					# TRUEならコールバック時に何もしない
my $gnSignalHandler;			# コールバック関数

my $gnGUIxml;				# gladeで生成したXMLファイル格納用
my $gnFRxml;				# gladeで生成したXMLファイル格納用
my $gnWxml = "";				# gladeで生成したXMLファイル格納用


#--------------------------------------
# 初期処理
#--------------------------------------
print STDERR "Starting gnview $gnVersion\n";
  #____________________________________________________
  # 初回起動かどうかをチェック
  my $gnCfgPath = &gnGetPersonalCfgPath;
  my $gnCfgFileN = $gnCfgPath . ".gnviewconfig";
  if($gnDebugFlag) { print Dumper($gnCfgFileN); }
  if (!(-e $gnCfgFileN)) {
     &gnFirstRun;
  }else{
     &gnInit;
  }
  Gtk2->main;

sub gnFirstRun {
   # 初回起動時にウィザードを起動して、gikoNavi.iniとログディレクトリを
   # 指定する
   #
   # 引数: なし
   # 返り値: なし
   $gnFRxml = Gtk2::GladeXML->new($gladefn, 'gnInitialConfig');
   $gnFRxml->signal_autoconnect_from_package('main');
   my $gnFirstRunGUI = $gnFRxml->get_widget('gnInitialConfig');
   if($gnDebugFlag) { print Dumper($gnFirstRunGUI); }
   
   my $gnFRIniChoose = $gnFRxml->get_widget('gnFRIniChoose');
   my $gnFRIniFilter = Gtk2::FileFilter->new;
   $gnFRIniFilter->add_pattern('gikoNavi.ini');
   $gnFRIniChoose->set_filter($gnFRIniFilter);
   
   $gnFirstRunGUI->queue_resize;
   $gnFirstRunGUI->show_all;
   
   my $gnFRpage = 0;
   
}

sub gnFR_next {
   my $gnFRnextButton = $_[0];
   my $gnFRnote = $gnFRxml->get_widget('gnFRnote');
   my $gnFRnoteP = $gnFRnote->get_current_page;
   
   if ($gnFRnoteP == ($gnFRnote->get_n_pages-1)) {
      # 最終ページなので終了処理
      my $gnFRIni = $gnFRxml->get_widget('gnFRIniChoose');
      $gnInitCfg{'Main'}{'gikoNavi.ini'} = $gnFRIni->get_filename;
      if($gnDebugFlag) { print "$gnInitCfg{'Main'}{'gikoNavi.ini'}\n"; }
      my $gnFRUrl = $gnFRxml->get_widget('gnFRUrlChoose');
      $gnInitCfg{'Main'}{'url.ini'} = $gnFRUrl->get_filename;
      if($gnDebugFlag) { print "$gnInitCfg{'Main'}{'url.ini'}\n"; }
      my $gnFRSent = $gnFRxml->get_widget('gnFRSentChoose');
      $gnInitCfg{'Main'}{'sent.ini'} = $gnFRSent->get_filename;
      if($gnDebugFlag) { print "$gnInitCfg{'Main'}{'sent.ini'}\n"; }
      my $gnFRLog = $gnFRxml->get_widget('gnFRLogChoose');
      $gnInitCfg{'Main'}{'LogFolder'} = $gnFRLog->get_filename;
      if($gnDebugFlag) { print "$gnInitCfg{'Main'}{'LogFolder'}\n"; }
      
      $gnEnvFile = $gnInitCfg{'Main'}{'gikoNavi.ini'};
      $gnURLIniFile = $gnInitCfg{'Main'}{'url.ini'};
      $gnSentIniFile = $gnInitCfg{'Main'}{'sent.ini'};
      $logdir = $gnInitCfg{'Main'}{'LogFolder'} . "\/2ch\/";
      
      
      if (!(-d $logdir)) {
             my $gnFirstRunGUI = $gnFRxml->get_widget('gnInitialConfig');
             my $gnMsgDialog = Gtk2::MessageDialog->new($gnFirstRunGUI,
                                                           'GTK_DIALOG_MODAL',
                                                           'GTK_MESSAGE_INFO',
                                                           'GTK_BUTTONS_YES_NO',
                                                           "2chディレクトリがありません。\n作成しますか？",
                                                           ,
                                                           'GTK_BUTTONS_YES' => 'gtk-yes',
                                                           'GTK_BUTTONS_NO' => 'gtk-no');
             if($gnMsgDialog->run eq 'yes') {
                $gnMsgDialog->destroy;
                undef($gnMsgDialog);
                mkdir($logdir);
             }else{
                $gnMsgDialog->destroy;
                undef($gnMsgDialog);
                exit(1);
             }
         
      }

      # <設定ディレクトリ>/.gnviewconfigへ書き込み
      if (!(-d $gnCfgPath)) {
             my $gnFirstRunGUI = $gnFRxml->get_widget('gnInitialConfig');
             my $gnMsgDialog = Gtk2::MessageDialog->new($gnFirstRunGUI,
                                                           'GTK_DIALOG_MODAL',
                                                           'GTK_MESSAGE_INFO',
                                                           'GTK_BUTTONS_YES_NO',
                                                           "設定ファイルを格納するディレクトリがありません。\n作成しますか？",
                                                           ,
                                                           'GTK_BUTTONS_YES' => 'gtk-yes',
                                                           'GTK_BUTTONS_NO' => 'gtk-no');
             if($gnMsgDialog->run eq 'yes') {
                $gnMsgDialog->destroy;
                undef($gnMsgDialog);
                mkdir($gnCfgPath);
             }else{
                $gnMsgDialog->destroy;
                undef($gnMsgDialog);
                exit(1);
             }
         
      }
      # 画像キャッシュテーブルの初期化
      $gnInitCfg{'Main'}{'cachetbl.ini'} = $gnCfgPath . "cachetbl.ini";
      if (!(-e $gnInitCfg{'Main'}{'cachetbl.ini'})) {
         open(gnCTBL, ">$gnInitCfg{'Main'}{'cachetbl.ini'}");
         close(gnCTBL);
      }
      
      my $gnInitCfgFN = $gnCfgPath . ".gnviewconfig";
      $gnInitCfg{'Main'}{'gnCfgPath'} = $gnCfgPath;
      if($gnDebugFlag) { print Dumper(%gnInitCfg); }
      &gnSaveEnv($gnInitCfgFN, \%gnInitCfg);
      
      my $gnFirstRunGUI = $gnFRxml->get_widget('gnInitialConfig');
      $gnFirstRunGUI->destroy;
      
      &gnInit;
      
   }else{
      
      if ($gnFRnoteP == 0) {
         # 次のページが2ページ目(0スタートの1)の場合は
         # ボタンのアイコンとラベルを変える
         my $gnFRprevButton = $gnFRxml->get_widget('gnFRprev');
         $gnFRprevButton->set_image(Gtk2::Image->new_from_stock('gtk-go-back','button'));
         $gnFRprevButton->set_label('戻る');
      }
      
      if (($gnFRnoteP+1) == $gnFRnote->get_n_pages-1) {
         # 次のページが最終ページの場合は
         # ボタンのアイコンとラベルを変える
         $gnFRnextButton->set_image(Gtk2::Image->new_from_stock('gtk-ok','button'));
         $gnFRnextButton->set_label('OK');
      }
      
      # 次のページに進む
      $gnFRnote->next_page;
   }
   
   
   
}

sub gnFR_prev {
   my $gnFRprevButton = $_[0];
   my $gnFRnote = $gnFRxml->get_widget('gnFRnote');
   my $gnFRnoteP = $gnFRnote->get_current_page;
   if (($gnFRnoteP-1) < 0) {
      # 最初のページなのでキャンセル処理
      my $gnFirstRunGUI = $gnFRxml->get_widget('gnInitialConfig');
      my $gnMsgDialog = Gtk2::MessageDialog->new($gnFirstRunGUI,
                                                    'GTK_DIALOG_MODAL',
                                                    'GTK_MESSAGE_INFO',
                                                    'GTK_BUTTONS_YES_NO',
                                                    "初期設定を中断しますか？\n（設定は保存されません）",
                                                     ,
                                                    'GTK_BUTTONS_YES' => 'gtk-yes',
                                                    'GTK_BUTTONS_NO' => 'gtk-no');
      if($gnMsgDialog->run eq 'yes') {
          $gnMsgDialog->destroy;
          undef($gnMsgDialog);
          exit(0);
      }else{
          $gnMsgDialog->destroy;
          undef($gnMsgDialog);
      }
   }else{
      
      if ($gnFRnoteP == $gnFRnote->get_n_pages-1) {
         # 前のページが最終ページの1ページ前の場合は
         # ボタンのアイコンとラベルを変える
         my $gnFRnextButton = $gnFRxml->get_widget('gnFRnext');
         $gnFRnextButton->set_image(Gtk2::Image->new_from_stock('gtk-go-forward','button'));
         $gnFRnextButton->set_label('進む');
      }
      
      if ($gnFRnoteP == 1) {
         # 戻った後のページが1ページ目(0スタートの0)の場合は
         # ボタンのアイコンとラベルを変える
         $gnFRprevButton->set_image(Gtk2::Image->new_from_stock('gtk-cancel','button'));
         $gnFRprevButton->set_label('キャンセル');
      }
      
      # 前のページに戻る
      $gnFRnote->prev_page;
   }
}

sub gnInit {
  
  # 初期設定ファイル(.gnconfig)読み込み
  print STDERR "Loading Configration File\n";
  my $gnCfgPath = &gnGetPersonalCfgPath;
  my $gnCfgFileN = $gnCfgPath . ".gnviewconfig";
  if (!(-e $gnCfgFileN)) { &gnRestartInit; }
  my $gnInitCfgRef = &gnLoadEnv($gnCfgFileN);
  %gnInitCfg = %$gnInitCfgRef;
  if($gnDebugFlag) { print "gnInit_LoadCfg=\n" . Dumper(%gnInitCfg); }
  $gnCfgPath = $gnInitCfg{'Main'}{'gnCfgPath'};
  
  $gnEnvFile = $gnInitCfg{'Main'}{'gikoNavi.ini'};
  if (!(-e $gnEnvFile)) { &gnRestartInit; }
  $gnURLIniFile = $gnInitCfg{'Main'}{'url.ini'};
  if (!(-e $gnURLIniFile)) { &gnRestartInit; }
  $gnSentIniFile = $gnInitCfg{'Main'}{'sent.ini'};
  if (!(-e $gnSentIniFile)) { &gnRestartInit; }
  $gnCacheTblFile = $gnInitCfg{'Main'}{'cachetbl.ini'};
  if($gnDebugFlag) { print "cachetbl.ini\n".Dumper($gnCacheTblFile); }
  if ($gnCacheTblFile eq "") { &gnRestartInit; }
  $logdir = $gnInitCfg{'Main'}{'LogFolder'} . "\/2ch\/";
  if (!(-e $logdir)) { &gnRestartInit; }

  
  $boardcfg = $gnCfgPath . "2chboard.lst";
  $gladefn = &gnGetLibPath . "gnview.glade";
  
  # 環境設定ファイル読み込み(gikoNavi.ini)
  if($gnDebugFlag) { print "gikonaviini=\n" . Dumper($gnEnvFile); }
  my $gnEnvArgRef = &gnLoadEnv($gnEnvFile);
  %gnEnvArg = %$gnEnvArgRef;
  $gnEnvArg{'Folder'}{'LogFolderUnix'} = $gnInitCfg{'Main'}{'LogFolder'};
  %gnEnvArgN = %gnEnvArg;
  
  # 環境設定ファイル読み込み(url.ini)
  if($gnDebugFlag) { print "urliini=\n" . Dumper($gnURLIniFile); }
  my $gnUrlArgRef = &gnLoadEnv($gnURLIniFile);
  %gnURLIni = %$gnUrlArgRef;
  if($gnDebugFlag) { print "urlini=\n" . Dumper(%gnURLIni); }

  # 環境設定ファイル読み込み(cachetbl.ini)
  if($gnDebugFlag) { print "cachetbl.ini=\n" . Dumper($gnCacheTblFile); }
  my $gnCacheTblRef = &gnLoadEnv($gnCacheTblFile);
  %gnCacheTbl = %$gnCacheTblRef;

  # 起動時に環境を読み込みメインウィンドウを開く
  $gnGUIxml = Gtk2::GladeXML->new($gladefn, 'mW');
  $gnGUIxml->signal_autoconnect_from_package('main');


  #____________________________________________________
  # 板一覧読み込み
  print STDERR "Building Board List...";
  # 板一覧のファイル名を与えると、TreeStore型のウィジェットが帰ってくる
  my $gnItaStore = &gnMakeItaTreeStore($boardcfg);
  
  # TreeView作成
  my $gnItaView = $gnGUIxml->get_widget('gnII');
  $gnItaView->set_model($gnItaStore);
  
  my $gnIICol1 = Gtk2::TreeViewColumn->new();
  $gnIICol1->set_title('２ちゃんねる');
  
  my $gnIICol1Rend = Gtk2::CellRendererText->new;
  $gnIICol1->pack_start($gnIICol1Rend, FALSE);
  $gnIICol1->add_attribute($gnIICol1Rend, text => 0);

  $gnItaView->append_column($gnIICol1);
  
  my $gnIS_pos = Gtk2::TreePath->new_from_indices(0);
  $gnItaView->expand_row($gnIS_pos, FALSE);
  
  
  # 板名が選択されたらスレ一覧読み込み
  #my @gnForceFLG = ("none");
  $gnItaView->signal_connect('cursor-changed' => sub{ &gnSleIchiranSelect($gnItaView,"none"); });
  
  #____________________________________________________
  # スレ一覧初期設定
  #
  my $gnSureLstStore = &gnMakeInitialSureLst;
  &gnSureIchiranInit($gnSureLstStore, undef);
  
  #____________________________________________________
  # スレ表示部初期設定
  my $gnNoteInit = $gnGUIxml->get_widget('gnNote');
  $gnNoteInit->set_property(homogeneous => FALSE);
  $gnSignalHandler->{gnNote}{switch_page} = $gnNoteInit->signal_connect_after('switch-page' => sub{ &on_gnNote_switch_page; });
  if($gnDebugFlag) { print Dumper($gnSignalHandler)."\n"; }

  #--------------------------------------
  # メインウィンドウ表示
  #--------------------------------------

   # iniでサイズが指定されている部分はそれに合わせてリサイズする
   # メインウィンドウのリサイズ
   my $mW = $gnGUIxml->get_widget('mW');
   $mW->resize($gnEnvArg{'WindowSize'}{'Width'}, $gnEnvArg{'WindowSize'}{'Height'});
   $mW->move($gnEnvArg{'WindowSize'}{'Left'}, $gnEnvArg{'WindowSize'}{'Top'});
   $mW->queue_resize;
   
   # カテゴリ一覧の幅を環境変数から読み取ってセット
   my $gnCatHPan = $gnGUIxml->get_widget('gnCatHPan');
   $gnCatHPan->set_position($gnEnvArg{'CategoryColumnWidth'}{'ID0'});

   # 板一覧の高さを環境変数から読み取ってセット
   my $gnItaVPan = $gnGUIxml->get_widget('gnItaVPan');
   $gnItaVPan->set_position($gnEnvArg{'BoardColumnWidth'}{'ID7'});
   


   # メインウィンドウを表示
   $mW->show_all;
   
   print "done. Ready.\n";

}

sub gnRestartInit {
   # 初期設定内容に不備があった場合に、再度初期設定を行うよう
   # 促す関数
   #
   # 引数: なし
   # 返り値: なし
   $gnGUIxml = Gtk2::GladeXML->new($gladefn, 'mW');
   my $mW = $gnGUIxml->get_widget('mW');
   &gnWarn("初期設定内容に不備があるか、\nバージョンの変更に伴い内容に不足が生じました。\n$gnInitCfg{'Main'}{'gnCfgPath'}\.gnviewconfigを削除するかリネームしてからgnviewをもう一度起動して\n再度初期設定を行ってください");
   exit(1);
}

#--------------------------------------
# GUIメソッド(アクション)対応関数
#--------------------------------------

sub on_gnSI_fil_changed {
   # スレフィルタに入力、もしくはプルダウンから選択された
   my $gnSIFil = @_[0];
   my $gnItaView = $gnGUIxml->get_widget('gnII');
   &gnSleIchiranSelect($gnItaView,"none");

}

sub on_gnSI_fil_realize {
   # スレフィルタ初期化

   my $gnSIFil = @_[0];
   if($gnDebugFlag) { print "realize\:\n" . Dumper($gnSIFil)."\n"; }

   my @gnSIFilHashKeys = keys(%{$gnEnvArg{'SelectText'}});
   foreach my $val(@gnSIFilHashKeys) {
     if ($gnEnvArg{'SelectText'}{$val}) {
        $gnSIFil->append_text($gnEnvArg{'SelectText'}{$val});
     }
  }

}

sub on_gnTBS_write_clicked {
   # 書き込むボタンが押された
   &gnWrite;
}

sub on_gnMS_write_activate {
   # 書き込むボタンが押された
   &gnWrite;
}

sub on_mW_unrealize {
   # メインウィンドウが閉じたら終了
   &gnExit;
}


sub gnSureViewMouseMove {
   # スレ表示のTextViewの中でマウスが動いたら
   # この関数が呼び出される
   # ここから各ポップアップの呼び出しに分岐する
   my ($gnSureV, $gnEvent) = @_;
   my $gnHover = FALSE;
   my $gnLink = '';
   my ($x, $y) = $gnSureV->window_to_buffer_coords('widget', #GTK_TEXT_WINDOW_WIDGET,
                                                         $gnEvent->x, $gnEvent->y);
   
   my $gnSV_iter = $gnSureV->get_iter_at_location ($x, $y);
   
   foreach my $gnTag ($gnSV_iter->get_tags) {
      if($gnTag->{page}) {
         # タグの中に"page"タグを含んでいれば$gnHoverフラグをTRUEにして
         # ループを抜ける
         $gnLink = $gnTag->{page};
         $gnHover = TRUE;
         last;
      }
   }
   
   if($gnHover == TRUE) {
      if($gnHoverOverLink != $gnHover) {
         $gnHoverOverLink = $gnHover;
         $gnSureV->get_window('text')->set_cursor($gnCursor_Link);
         my $gnLinkFlg = substr($gnLink, 0, 4);
         switch($gnLinkFlg) {
            case "resn" {
                         if($gnDebugFlag) { print "resn\n"; }
                        }
            case "repl" {
                         if($gnDebugFlag) { print "repl\n"; }
                         # レス先をポップアップ表示
                         if($gnPopupText eq "") {
                            $gnLink = substr($gnLink, 10);
                            $gnPopupText = &gnResPopup($gnLink);
                         }

            }
            case "datl" {
                         # read.cgiを含んだリンク
                         if($gnDebugFlag) { print "datl\n"; }
            }
            case "http" {
                         # httpの場合は別のスレをさしている場合と
                         # 画像などの場合がある
                         # 画像の場合は読み込んでポップアップ表示する
                         if($gnDebugFlag) { print "http\n"; }
                            
                         # 指定の時間waitするための
                         # Gtk2のtimeout関数をセット
                         if (!($gnPopupCallback)) {
                            $gnPopupCallback = Glib::Timeout->add($gnEnvArg{'Browser'}{'PreviewWait'},
                                                                 sub{ 
                                                                    my $ref = &gnImgPopup2($gnLink);
                                                                    ($gnPopupImg, $gnPop2) = @$ref;
                                                                    return(FALSE);
                                                                 },
                                                                 undef,
                                                                 0);
                           # gnImgBufferUpdateを定期的に呼び出すtimeout関数をセット
                           if (!($gnImgUpdateCallback)) {
                           $gnImgUpdateCallback = Glib::Timeout->add($gnEnvArg{'Browser'}{'PreviewWait'}+1000,
                                                                        sub {
                                                                        my $retval;
                                                                        my $ref = &gnImgBufferUpdate($gnPopupImg, $gnPop2);
                                                                        ($gnPopupImg, $gnPop2, $retval) = @$ref;
                                                                        return($retval);
                                                                        },
                                                                        undef,
                                                                        0);
                        
                           }
                         }
                        
           }
           else {
              if($gnDebugFlag) { print "Unknown Code\:$gnLinkFlg\n"; }
           }
            
         }
      }else{
         $gnSureV->get_window('text')->set_cursor($gnCursor_Link);
      }
   }else{
      if($gnHoverOverLink != $gnHover) {
         $gnHoverOverLink = $gnHover;
         $gnSureV->get_window('text')->set_cursor($gnCursor_Beam);

         # ポップアップウィンドウが出てたらdestroyする
         if($gnPopupText ne "") {
            $gnPopupText->destroy;
            $gnPopupText = "";
         }
         if ($gnPopupImg) {
            if($gnPopupImg ne "") {
               if($gnDebugFlag) { print "linkout-img2\:" . Dumper($gnPopupImg); }
               Gtk2::Object::destroy($gnPopupImg);
               $gnPop2->clear;
               $gnPopupImg = "";
            }
         }
            
         # timeout関数をremoveする。
         # removeしないと次のポップアップが出ない
         if ($gnPopupCallback) {
            Glib::Source->remove($gnPopupCallback);
            undef($gnPopupCallback);
         }
         if ($gnImgUpdateCallback) {
            if ($gnImgUpdateCallback != NULL) {
               Glib::Source->remove($gnImgUpdateCallback);
               undef($gnImgUpdateCallback);
            }
         }
      }else{
         $gnSureV->get_window('text')->set_cursor($gnCursor_Beam);
      }
   }
   
   # 必ずget_pointerしてFALSEを返すこと
   # get_pointerしないと、Linuxではクリックしたとき(正確に言うとPRESS_AFTER)しかイベントを
   # 検出しない。
   # またFALSEを入れないとTextView内で
   # 文字の選択ができないようになってしまう
   $gnSureV->window->get_pointer;
   return FALSE;
}

sub gnSureViewLinkPress {
   # リンクをクリック(マウスダウン)したときに実行する関数
   # ポップアップウィンドウを消去する
   
   if($gnDebugFlag) { print "LinkClick\(button_press_event\)\n"; }
   my ($gnSureV, $gnEvent) = @_;
   
   if ($gnPopupCallback) {
      Glib::Source->remove($gnPopupCallback);
      undef($gnPopupCallback);
   }
   if ($gnImgUpdateCallback) {
      Glib::Source->remove($gnImgUpdateCallback);
      undef($gnPopupCallback);
   }
   # ポップアップウィンドウが出てたらdestroyする
   if($gnPopupText ne "") {
      $gnPopupText->destroy;
      $gnPopupText = "";
   }
   if ($gnPopupImg) {
      if($gnPopupImg ne "") {
         if($gnDebugFlag) { print "linkout-img2\:" . Dumper($gnPopupImg); }
         Gtk2::Object::destroy($gnPopupImg);
         $gnPop2->clear;
         $gnPopupImg = "";
      }
   }
   $gnSureV->window->get_pointer;
   return(FALSE);
}

sub gnSureViewLinkRelease {
   # リンクをクリック(マウスリリース)したときに実行する関数
   # ポップアップウィンドウを消去する
   if($gnDebugFlag) { print STDERR "LinkClick\(button_release_event\)\n"; }
   if($gnDebugFlag>1) { print Dumper(@_);}
   my ($gnSureV, $gnEvent) = @_;

   my $gnHover = FALSE;
   my $gnLink = '';
   my ($x, $y) = $gnSureV->window_to_buffer_coords('widget', #GTK_TEXT_WINDOW_WIDGET,
                                                         $gnEvent->x, $gnEvent->y);
   
   my $gnSV_iter = $gnSureV->get_iter_at_location ($x, $y);
   
   foreach my $gnTag ($gnSV_iter->get_tags) {
      if($gnTag->{page}) {
         # タグの中に"page"タグを含んでいれば$gnHoverフラグをTRUEにして
         # ループを抜ける
         $gnLink = $gnTag->{page};
         $gnHover = TRUE;
         last;
      }
   }
   
   if ($gnHover) {
      # リンクの中にあったら以下の処理をする
      $gnSureV->get_window('text')->set_cursor($gnCursor_Link);
      my $gnLinkFlg = substr($gnLink, 0, 4);
            
      switch($gnLinkFlg) {
         case "resn" {
                       if($gnDebugFlag) { print STDERR "\(PressAfter-resn\)\n"; }
                     }
         case "repl" {
                       if($gnDebugFlag) { print STDERR "\(PressAfter-repl\)\n"; }
                     }
         case "datl" {
                       if($gnDebugFlag) { print STDERR "\(PressAfter-datl\)\n"; }
                       $gnLink = substr($gnLink, 7);
                       if($gnDebugFlag) { print "$gnLink\n"; }
                       my($buf1, $linktgt) =  split(/read\.cgi/, $gnLink);
                       $linktgt = &gnGetlinktgt($linktgt); #read.cgiの仕様違いを吸収
                       my ($ita, $datno, $sute) = split(/\//, $linktgt);
                       my ($tgtname, $tgturl) = split(chr(001), $gnBoardTbl{$ita});
                       
                       # 取得したdat番号がすでにFolder.idx内にあるかどうかチェック
                       my $idxfn = $logdir . $ita . "\/Folder.idx";
                       my $datfn = $datno . "\.dat";
                       if($gnDebugFlag) { print "ita=$ita, datno=$datno, sute=$sute\n"; }
                       if($gnDebugFlag) { print "tgtname=$tgtname, tgturl=$tgturl\n"; }
                       if($gnDebugFlag) { print "Folder\.idx\(fullpath\)\: $idxfn\n"; }
                       if($gnDebugFlag) { print "datfilename\: $datfn\n"; }
                       
                       if (!(-r ($logdir . $ita))) {
                       my $dirres = &gnYesNo("Folder.idxを保管するディレクトリがないのでスレが取れません\n板名\: $tgtname\nディレクトリ\: " . $logdir . $ita . "\nURL\: $tgturl\nディレクトリを作成しますか？");
                          if ($dirres eq "yes") {
                             mkdir($logdir . $ita);
                          }else{
                             &gnLogger("スレの取得をキャンセルしました");
                             last;
                          }
                       }
                       
                       if($gnDebugFlag) { print "chekking Folder.idx.\(datfn=$datfn, idxfn=$idxfn\)\n"; }
                       my $gnIsDatNameAlrdyLocalFLG = &gnIsDatNameAlrdyLocal($datfn, $idxfn);
                       
                       if ($gnIsDatNameAlrdyLocalFLG) {
                          # 既にFolder.idx内にある
                          if($gnDebugFlag) { print "already in Folder.idx\n"; }
                          
                          my $gnSS_Genre = $gnGUIxml->get_widget('gnII');
                          my $gnSS_GenreM = $gnSS_Genre->get_model;
                          my ($gnSS_GenreP, $gnSS_GenreC) = $gnSS_Genre->get_cursor;
                          my $gnSS_GenreI = $gnSS_GenreM->get_iter($gnSS_GenreP);
                          my $gnSS_Genre_now = $gnSS_GenreM->get($gnSS_GenreI, 1);

                          $gnSS_Genre_now = &gnGetGenreFromURL($gnSS_Genre_now);
                          if($gnDebugFlag) { print "$gnSS_Genre_now\n"; }

                          if ($gnSS_Genre_now ne $ita) {
                             $gnSS_GenreM->foreach(\&gnGetItaPathFromStr, $ita);
         
                             my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
                             $gnSS_SuresNow->scroll_to_point(0, 0);
                             $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat(($ita . "\/" . $datno . "\.dat")));
                          }else{
                             my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
                             $gnSS_SuresNow->scroll_to_point(0, 0);
                             $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat(($ita . "\/" . $datno . "\.dat")));
                          }
                          
                       }else{
                          # Folder.idxの中にはない
                          if($gnDebugFlag) { print "no entry in Folder.idx\n"; }
                          
                          # subject.txtを取得してFolder.idxを更新し再チェック
                          &gnLogger("対象の板の現在のスレ一覧を取得します");
                          my $idxdir = $logdir . $ita . "\/";
                          my $suburl = $tgturl . "subject.txt";
                          my $gnSIUFlg = &gnSureItiranUpdate($suburl, $idxdir);
                          if($gnSIUFlg == TRUE) {
                               &gnLogger("スレ一覧を更新しました");
                          }else{
                               &gnLogger("スレ一覧更新に失敗しました");
                          }
                          
                          if($gnDebugFlag) { print "chekking Folder.idx\(AGAIN\).\(datfn=$datfn, idxfn=$idxfn\)\n"; }
                          my $gnIsDatNameAlrdyLocalFLG2 = &gnIsDatNameAlrdyLocal($datfn, $idxfn);
                       
                          if ($gnIsDatNameAlrdyLocalFLG2) {
                             # 再取得の結果、既にFolder.idx内にある
                             if($gnDebugFlag) { print "\(retry\)already in Folder.idx\n"; }
                          
                             my $gnSS_Genre = $gnGUIxml->get_widget('gnII');
                             my $gnSS_GenreM = $gnSS_Genre->get_model;
                             my ($gnSS_GenreP, $gnSS_GenreC) = $gnSS_Genre->get_cursor;
                             my $gnSS_GenreI = $gnSS_GenreM->get_iter($gnSS_GenreP);
                             my $gnSS_Genre_now = $gnSS_GenreM->get($gnSS_GenreI, 1);
 
                             $gnSS_Genre_now = &gnGetGenreFromURL($gnSS_Genre_now);
                             if($gnDebugFlag) { print "$gnSS_Genre_now\n"; }

                             if ($gnSS_Genre_now ne $ita) {
                                $gnSS_GenreM->foreach(\&gnGetItaPathFromStr, $ita);
         
                                my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
                                $gnSS_SuresNow->scroll_to_point(0, 0);
                                $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat(($ita . "\/" . $datno . "\.dat")));
                                #my $gncursor = &gnSureSelectFromDat($ita . "\/" . $datno . "\.dat");
                                #$gnSS_SuresNow->set_cursor($gncursor);
                             }else{
                                my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
                                $gnSS_SuresNow->scroll_to_point(0, 0);
                                $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat(($ita . "\/" . $datno . "\.dat")));
                                #my $gncursor = &gnSureSelectFromDat($ita . "\/" . $datno . "\.dat");
                                #if($gnDebugFlag) { print Dumper($gncursor)."\n"; }
                                #$gnSS_SuresNow->set_cursor($gncursor);
                             }
                          
                          }else{
                          
                             # 再取得してもFolder.idxの中にはない
                             # datを取得し
                             # Folder.idxにスレの情報を追加
                             &gnLogger("スレタイの取得が出来ませんでした。datの取得を試みます");
                             my ($gnGetDatRST, $gnGetDatRes) = &gnGetDat($tgturl, $datfn);
                             if ($gnGetDatRST eq "gn200OK") {
                                my $gnNow = time();
                                # 取得数を更新
                                my $gnDatNumRow = 0;
                                my $gnItaDatFN = $logdir . $ita . "\/$datfn";
                                open(gnDatF2, "$gnItaDatFN");
                                while(<gnDatF2>) {
                                   $gnDatNumRow++;
                                }
                                close(gnDatF2);
                                my $gnFolderIdxUpdateFLG = "";
                                my $gnFolderIdxGetValFLG = "";
                             
                                my $gngmt = str2time($gnGetDatRes->header('Last-Modified'));
                                $gngmt += 32400;
                                my @pushvar = ($datfn,
                                                "",
                                                sprintf("%X", $gnDatNumRow),
                                                0,
                                                sprintf("%X", $gnDatNumRow),
                                                sprintf("%X", $gngmt),
                                                sprintf("%X", $gnDatNumRow),
                                                0,0,0,0,
                                                sprintf("%X", $gnDatNumRow),
                                                0,
                                                3
                                                );
                                &gnFolderIdxPush($idxfn, \@pushvar);
                                
                                # 取得したスレを表示
                                &gnItaSelect($ita);

                                my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
                                $gnSS_SuresNow->scroll_to_point(0, 0);
                                my $aaa = &gnSureSelectFromDat($ita . "\/" . $datno . "\.dat");
                                if($gnDebugFlag) { print "sureselectfromdat\: " . Dumper($aaa); }
                                $gnSS_SuresNow->set_cursor($aaa);
                             }else{
                                # datの取得に失敗
                             }
                          }
                       }
                       
                     }
         case "http" {
                       if($gnDebugFlag) { print STDERR "\(PressAfter-http\)\n"; }
                       #my $gnLinkURL = substr($gnLink, 7);
                       &gnJumpToURL($gnLink);
                     }
         else       {
                       if($gnDebugFlag) { print STDERR "\(PressAfter-else\)\n"; }
                     }
      }
   }else{
      if($gnDebugFlag) { print STDERR "\(PressAfter-outoflink\)\n"; }
      
   }

   $gnSureV->window->get_pointer;
   return(FALSE);
}

sub on_gnII_size_allocate {
   # カテゴリ一覧のハンドルバーが動いたら
   # 下にあるGtkHPanのハンドルバー位置を環境変数の配列に記憶
   my $gnCatHPan = $_[2];
   my $gnCatHPan_pos = $gnCatHPan->get_position;
   $gnEnvArg{'CategoryColumnWidth'}{'ID0'} = $gnCatHPan_pos;
}

sub on_gnSI_size_allocate {
   # 板一覧のハンドルバーが動いたら
   # 下にあるGtkVPanのハンドルバー位置を環境変数の配列に記憶
   my $gnItaVPan = $_[2];
   my $gnItaVPan_pos = $gnItaVPan->get_position;
   $gnEnvArg{'BoardColumnWidth'}{'ID7'} = $gnItaVPan_pos;
}


sub on_gnMH_about_activate {
   # aboutボックスの表示
   my $gnAboutxml = Gtk2::GladeXML->new($gladefn, 'gnAbout');
   $gnAboutxml->signal_autoconnect_from_package;
   my $gnAbout = $gnAboutxml->get_widget('gnAbout');
   $gnAbout->show;
}

sub on_gnTB_bupdate_clicked {
   # 板更新
   &gnGetItaIchiran;
}

sub on_gnMF_bupdate_activate {
   # 板更新
   &gnGetItaIchiran;
}

sub on_gnSureUpdate_clicked {
   # スレ一覧更新
   #my @gnForceFLG = ("force");
   
   my $gnItaView = $gnGUIxml->get_widget('gnII');
   &gnLogger("スレ一覧を更新します");
   &gnSleIchiranSelect($gnItaView, "force");
}

sub on_gnMS_sureitiranupdate_activate {
   # スレ一覧更新
   my $gnItaView = $gnGUIxml->get_widget('gnII');
   &gnLogger("スレ一覧を更新します");
   &gnSleIchiranSelect($gnItaView, "force");

}

sub on_gnMF_quit_activate {
   # 終了処理
   &gnExit;
}


sub on_gnAB_ok_clicked {
   # Aboutダイアログの「閉じる」ボタンがクリックされたら
   # ダイアログボックスを閉じる
   my $gnAbout = $_[1];
   $gnAbout->destroy;
   $gnAbout = undef;
}

sub on_gnAbout_close {
   # ダイアログボックスを閉じる
   my $gnAbout = $_[0];
   $gnAbout->destroy;
   $gnAbout = undef;
}


sub on_gnMS_Env_activate {
   # メニューから「設定」が選択されたので
   # 環境設定ウィンドウを開く
   my $gnEnvXML = Gtk2::GladeXML->new($gladefn, 'gnEnv');
   $gnEnvXML->signal_autoconnect_from_package('main');

   my $gnEnv = $gnEnvXML->get_widget('gnEnv');

   $gnEnv->show_all;
}

sub on_gnEnvCancel_clicked {
   # 環境設定ウィンドウで"キャンセル"ボタンが押されたら
   # 単純にウィンドウを廃棄する
   my $gnEnv = $_[1];
   $gnEnv->destroy;
   $gnEnv = undef;
}

sub on_gnEnvOk_clicked {
   # 環境設定ウィンドウで"OK"ボタンが押されたら
   # 配列gnEnvArgNの内容を内部配列gnEnvArgに反映して、
   # ウィンドウを廃棄する
   my $gnEnv = $_[1];

   # 仮に格納していたハッシュ配列を本番の環境設定配列にコピー
   %gnEnvArg = %gnEnvArgN;

   $gnEnv->destroy;
   $gnEnv = undef;

}

sub on_gnEnvLog_Winent_realize {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Windows]
   # 表示時にgnEnvArg{'Folder'}{'LogFolder'}の値を読み込み
   if ($gnEnvArg{'Folder'}{'LogFolder'}) {
      $_[0]->set_text($gnEnvArg{'Folder'}{'LogFolder'});
   }
}

sub on_gnEnvLog_Winent_changed {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Windows]
   # 値が変更された時にgnEnvArgN{'Folder'}{'LogFolder'}に書き込み
   $gnEnvArgN{'Folder'}{'LogFolder'} = $_[0]->get_text;
   #print $gnEnvArgN{'Folder'}{'LogFolder'}; print "\n";
}

sub on_gnEnvLog_WinentDB_clicked {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Windows] 横の
   # "選択"ボタンが押されたら、フォルダ選択ダイアログボックスを
   # 表示してフォルダを選択する。
   # 選択された値はEntry(gnEnvLog_Winent)に書き込まれる
   my $gnEnvLog_Winent = $_[1];
   
   my $gnFolderXML = Gtk2::GladeXML->new($gladefn, 'gnFolderChoose');
   $gnFolderXML->signal_autoconnect_from_package('main');
   my $gnFolderChoose = $gnFolderXML->get_widget('gnFolderChoose');
   
   my $retval = $gnFolderChoose->run;
   if($retval eq "ok") {
      $retval = &env_gtkstr_to_gtkstr($gnFolderChoose->get_filename);
      $gnEnvLog_Winent->set_text($retval);
      $gnFolderChoose->destroy;
      $gnFolderChoose = undef;
   }else{
      $gnFolderChoose->destroy;
      $gnFolderChoose = undef;
   }

}

sub on_gnEnvLog_Linent_realize {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Unix]
   # 表示時にgnEnvArg{'Folder'}{'LogFolderUnix'}の値を読み込み
   $_[0]->set_text($gnEnvArg{'Folder'}{'LogFolderUnix'});
}

sub on_gnEnvLog_Linent_changed {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Unix]
   # 値が変更された時にgnEnvArgN{'Folder'}{'LogFolderUnix'}に書き込み
   $gnEnvArgN{'Folder'}{'LogFolderUnix'} = $_[0]->get_text;
   #print $gnEnvArgN{'Folder'}{'LogFolderUnix'}; print "\n";
}

sub on_gnEnvLog_LinentDB_clicked {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[Unix] 横の
   # "選択"ボタンが押されたら、フォルダ選択ダイアログボックスを
   # 表示してフォルダを選択する。
   # 選択された値はEntry(gnEnvLog_Linent)に書き込まれる
   my $gnEnvLog_Linent = $_[1];
   
   my $gnFolderXML = Gtk2::GladeXML->new($gladefn, 'gnFolderChoose');
   $gnFolderXML->signal_autoconnect_from_package('main');
   my $gnFolderChoose = $gnFolderXML->get_widget('gnFolderChoose');
   
   my $retval = $gnFolderChoose->run;
   if($retval eq "ok") {
      $retval = &env_gtkstr_to_gtkstr($gnFolderChoose->get_filename);
      $gnEnvLog_Linent->set_text($retval);
      #print "$retval\n";
      $gnFolderChoose->destroy;
      $gnFolderChoose = undef;
   }else{
      $gnFolderChoose->destroy;
      $gnFolderChoose = undef;
   }

}

sub on_gnEnvLog_Ftp_path_realize {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[パス]
   # 表示時にgnEnvArg{'Folder'}{'LogFolderFTPPath'}の値を読み込み
   if ($gnEnvArg{'Folder'}{'LogFolderFTPPath'}) {
      $_[0]->set_text($gnEnvArg{'Folder'}{'LogFolderFTPPath'});
   }
}
sub on_gnEnvLog_Ftp_path_changed {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[パス]
   # 値が変更された時にgnEnvArgN{'Folder'}{'LogFolderFTPPath'}に書き込み
   $gnEnvArgN{'Folder'}{'LogFolderFTPPath'} = $_[0]->get_text;
   #print $gnEnvArgN{'Folder'}{'LogFolderUnix'}; print "\n";
}
sub on_gnEnvLog_Ftp_user_realize {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[ユーザ名]
   # 表示時にgnEnvArg{'Folder'}{'LogFolderFTPUser'}の値を読み込み
   if ($gnEnvArg{'Folder'}{'LogFolderFTPUser'}) {
      $_[0]->set_text($gnEnvArg{'Folder'}{'LogFolderFTPUser'});
   }
}
sub on_gnEnvLog_Ftp_user_changed {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[ユーザ名]
   # 値が変更された時にgnEnvArgN{'Folder'}{'LogFolderFTPUser'}に書き込み
   $gnEnvArgN{'Folder'}{'LogFolderFTPUser'} = $_[0]->get_text;
   #print $gnEnvArgN{'Folder'}{'LogFolderUnix'}; print "\n";
}
sub on_gnEnvLog_Ftp_password_realize {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[パスワード]
   # 表示時にgnEnvArg{'Folder'}{'LogFolderFTPPasswd'}の値を読み込み
   if ($gnEnvArg{'Folder'}{'LogFolderFTPPasswd'}) {
      $_[0]->set_text($gnEnvArg{'Folder'}{'LogFolderFTPPasswd'});
   }
}
sub on_gnEnvLog_Ftp_password_changed {
   # 環境設定ウィンドウ-[ログ保存]-[保存先]-[FTP]-[パスワード]
   # 値が変更された時にgnEnvArgN{'Folder'}{'LogFolderFTPPasswd'}に書き込み
   $gnEnvArgN{'Folder'}{'LogFolderFTPPasswd'} = $_[0]->get_text;
   #print $gnEnvArgN{'Folder'}{'LogFolderUnix'}; print "\n";
}

sub on_gnImgPrevSize_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]
   # 表示時にgnEnvArg{'Browser'}{'PreviewSize'}の値を読み込み
   $_[0]->set_value($gnEnvArg{'Browser'}{'PreviewSize'});
}

sub on_gnImgPrevSize_changevalue {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]
   # で値が変更されたらgnEnvArgN{'Browser'}{'PreviewSize'}に書き込み
   $gnEnvArgN{'Browser'}{'PreviewSize'} = $_[0]->get_value;
}

sub on_gnEnableImgPopup_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-チェック
   # 表示時にgnEnvArg{'Browser'}{'PreviewVisible'}の値を読み込み
   $_[0]->set_active($gnEnvArg{'Browser'}{'PreviewVisible'});
}

sub on_gnEnableImgPopup_toggled {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-チェック
   # で値が変更されたらgnEnvArgN{'Browser'}{'PreviewVisible'}に書き込み
   $gnEnvArgN{'Browser'}{'PreviewVisible'} = $_[0]->get_active;
}

sub on_gnImgPrevInterval_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[表示までの時間]
   # 表示時にgnEnvArg{'Browser'}{'PreviewWait'}の値を読み込み
   my $gnPreviewWait = $gnEnvArg{'Browser'}{'PreviewWait'} / 100 ;
   $_[0]->set_value($gnPreviewWait);
}

sub on_gnImgPrevInterval_value_changed {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[表示までの時間]
   # で値が変更されたらgnEnvArgN{'Browser'}{'PreviewWait'}に書き込み
   my $gnPreviewWait = $_[0]->get_value * 100;
   $gnEnvArgN{'Browser'}{'PreviewWait'} = $gnPreviewWait;
}

sub on_gnEnv_EnableCache_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[キャッシュを有効にする]
   # 表示時にgnEnvArg{'Cache'}{'Enable'}の値を読み込み
   my $gnCT =$_[0];
   if($gnDebugFlag) { print "cache:\n" . Dumper($gnEnvArg{'Cache'}{'Enable'}); }
   if ($gnEnvArg{'Cache'}{'Enable'}) {
      if ($gnEnvArg{'Cache'}{'Enable'} == FALSE) {
         $gnCT->set_active(FALSE);
      }else{
         $gnCT->set_active(TRUE);
      }
   }else{
      $gnCT->set_active(FALSE);
   }
}

sub on_gnEnv_EnableCache_toggled {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[キャッシュを有効にする]
   # トグルされたらgnEnvArgN{'Cache'}{'Enable'}へ値を書き込み
   my $gnCT =$_[0];
   if ($gnCT->get_active) {
      $gnEnvArgN{'Cache'}{'Enable'} = TRUE;
   }else{
      $gnEnvArgN{'Cache'}{'Enable'} = FALSE;
   }

}

sub on_gnEnv_CacheExpire_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[キャッシュ有効期間]
   # 表示時にgnEnvArg{'Cache'}{'Expire'}の値を読み込み
   my $gnCE = $_[0];
   if ($gnEnvArg{'Cache'}{'Expire'}) {
      $gnCE->set_value($gnEnvArg{'Cache'}{'Expire'});
   }else{
      $gnCE->set_value(0);
   }
}

sub on_gnEnv_CacheExpire_value_changed {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[キャッシュ有効期間]
   # 値の変更時にgnEnvArgN{'Cache'}{'Expire'}へ値を書き込み
   my $gnCE = $_[0];
   $gnEnvArgN{'Cache'}{'Expire'} = $gnCE->get_value_as_int;
}

sub on_gnEnv_ClearCacheOnExit_realize {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[終了時にキャッシュをクリア]
   # 表示時にgnEnvArg{'Cache'}{'ClearOnExit'}の値を読み込み
   my $gnCCOE = $_[0];
   if ($gnEnvArg{'Cache'}{'ClearOnExit'}) {
      if ($gnEnvArg{'Cache'}{'ClearOnExit'} == TRUE) {
         $gnCCOE->set_active(TRUE);
      }else{
         $gnCCOE->set_active(FALSE);
      }
   }else{
      $gnCCOE->set_active(FALSE);
   }

}

sub on_gnEnv_ClearCacheOnExit_toggled {
   # 環境設定ウィンドウ-[環境]-[画像プレビュー]-[終了時にキャッシュをクリア]
   # トグルされたらgnEnvArgN{'Cache'}{'ClearOnExit'}へ値を書き込み
   my $gnCCOE =$_[0];
   if ($gnCCOE->get_active) {
      $gnEnvArgN{'Cache'}{'ClearOnExit'} = TRUE;
   }else{
      $gnEnvArgN{'Cache'}{'ClearOnExit'} = FALSE;
   }

}

sub on_gnEnv_BrowseR1_realize {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windowsのみ)既定のブラウザを使用]
   # 表示時にgnEnvArg{'URLApp'}{'Select'}の値を読み込み
   my $gnBR = $_[0];
   
   if ($^O ne 'MSWin32') {
      $gnBR->set_sensitive(FALSE);
   }
   
   if ($gnEnvArg{'URLApp'}{'gnSelect'}) {
      if ($gnEnvArg{'URLApp'}{'gnSelect'} == 0) {
         $gnBR->set_active(TRUE);
      }
   }elsif ($gnEnvArg{'URLApp'}{'Select'}) {
      if ($gnEnvArg{'URLApp'}{'Select'} == 0) {
         $gnBR->set_active(TRUE);
      }
   }
   
}

sub on_gnEnv_BrowseR1_toggled {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windowsのみ)既定のブラウザを使用]
   # トグルされたらgnEnvArgN{'URLApp'}{'gnSelect'}へ値を書き込み
   my $gnBR = $_[0];
   
   if ($gnBR->get_active) {
      $gnEnvArgN{'URLApp'}{'gnSelect'} = 0;
   }
}

sub on_gnEnv_BrowseR2_realize {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]
   # 表示時にgnEnvArg{'URLApp'}{'gnSelect'}の値を読み込み
   my $gnBR = $_[0];
   
   if ($gnEnvArg{'URLApp'}{'gnSelect'}) {
      if ($gnEnvArg{'URLApp'}{'gnSelect'} == 2) {
         $gnBR->set_active(TRUE);
      }
   }
   
}

sub on_gnEnv_BrowseR2_toggled {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]
   # トグルされたらgnEnvArgN{'URLApp'}{'gnSelect'}へ値を書き込み
   my $gnBR = $_[0];
   
   if ($gnBR->get_active) {
      $gnEnvArgN{'URLApp'}{'gnSelect'} = 2;
   }
   
}

sub on_gnEnv_BrowseR3_realize {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のコマンドを使用]
   # 表示時にgnEnvArg{'URLApp'}{'gnSelect'}の値を読み込み
   my $gnBR = $_[0];
   
   if ($gnEnvArg{'URLApp'}{'gnSelect'}) {
      if ($gnEnvArg{'URLApp'}{'gnSelect'} == 1) {
         $gnBR->set_active(TRUE);
      }
   }elsif ($gnEnvArg{'URLApp'}{'Select'}) {
      if ($gnEnvArg{'URLApp'}{'Select'} == 1) {
         $gnBR->set_active(TRUE);
      }
   }
   
}

sub on_gnEnv_BrowseR3_toggled {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]
   # トグルされたらgnEnvArgN{'URLApp'}{'gnSelect'}へ値を書き込み
   my $gnBR = $_[0];
   
   if ($gnBR->get_active) {
      $gnEnvArgN{'URLApp'}{'gnSelect'} = 1;
   }
   
}

sub on_gnEnv_BrowserSel_realize {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]-[使用ブラウザ]
   # 表示時にgnEnvArg{'URLApp'}{'gnSelBrowser'}の値を読み込み
   my $gnBR = $_[0];
   
   if ($gnEnvArg{'URLApp'}{'gnSelBrowser'}) {
      $gnBR->set_active($gnEnvArg{'URLApp'}{'gnSelBrowser'});
   }
}

sub on_gnEnv_BrowserSel_changed {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]-[使用ブラウザ]
   # トグルされたらgnEnvArgN{'URLApp'}{'gnSelect'}へ値を書き込み
   my $gnBR = $_[0];
   
   $gnEnvArgN{'URLApp'}{'gnSelBrowser'} = $gnBR->get_active;
   
}

sub on_gnEnv_BrowserCmd_realize {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]-[コマンド名]
   # 表示時にgnEnvArg{'URLApp'}{'gnSelBrowser'}の値を読み込み
   my $gnBR = $_[0];
   
   if ($gnEnvArg{'URLApp'}{'gnFile'}) {
      $gnBR->set_text($gnEnvArg{'URLApp'}{'gnFile'});
   }elsif ($gnEnvArg{'URLApp'}{'File'}) {
      $gnBR->set_text($gnEnvArg{'URLApp'}{'File'});
   }
}

sub on_gnEnv_BrowserCmd_changed {
   # 環境設定ウィンドウ-[環境]-[ブラウザ]-[(Windows以外)以下のブラウザを使用]-[コマンド名]
   # トグルされたらgnEnvArgN{'URLApp'}{'gnSelect'}へ値を書き込み
   my $gnBR = $_[0];
   
   $gnEnvArgN{'URLApp'}{'gnFile'} = $gnBR->get_text;
   
}

sub on_gnEnvNote_switch_page { } # ページが変わっても別に何もしない

sub on_gnPopupImg_button_release_event {
   # ポップアップウィンドウが出てたらdestroyする
   if($gnDebugFlag) { print "button_press_event\n"; }
   my $aaa = $_[0];
   if($gnDebugFlag) { print Dumper($aaa) . "\n"; }
   if($aaa ne "") {
      $aaa->destroy;
      $gnPop2->clear;
      $aaa = "";
   }
            
   # timeout関数をremoveする。
   # removeしないと次のポップアップが出ない
   if ($gnPopupCallback) {
      Glib::Source->remove($gnPopupCallback);
      undef($gnPopupCallback);
   }
   if ($gnImgUpdateCallback) {
      Glib::Source->remove($gnImgUpdateCallback);
      undef($gnPopupCallback);
   }
}

sub on_gnTBS_supdate_clicked {
   # メニューボタンからスレを更新
   my $gnSureView = $gnGUIxml->get_widget('gnSI');
   &gnSureUpdate($gnSureView);
}

sub on_gnMS_sureupdate_activate {
   # メニューからスレを更新
   my $gnSureView = $gnGUIxml->get_widget('gnSI');
   &gnSureUpdate($gnSureView);
}

sub on_gnNote_switch_page {
   # スレ本文を表示するNotebookのタブを切り替えた
   if($gnDebugFlag) { print "Switch Page\n"; }
   &gnSureSwitch;
}

sub on_gnW_sage_toggled {
   # 書き込みウィンドウで、sageチェックボックスをトグルした
   my $gnSage = $_[0];
   my $gnW_mail = $gnWxml->get_widget('gnW_mail');
   if ($gnSage->get_active) {
      $gnW_mail->child->set_text("sage");
      $gnW_mail->set_sensitive(FALSE);
   }else{
      $gnW_mail->child->set_text("");
      $gnW_mail->set_sensitive(TRUE);
   }
}

sub on_gnW_SubmitBtn_clicked {
   # 書き込みウィンドウの送信ボタンが押された
   &gnSubmit;
}
sub on_gnWM_submit_activate {
   # 書き込みウィンドウのメニュー[ファイル]-[送信]が選択された
   &gnSubmit;
}

sub on_gnWrite_unrealize {
   # 書き込みウィンドウが閉じた
   $gnWxml = "";
}

sub on_gnPopupText_check_resize {
   # ポップアップ画面がウィンドウからはみ出している場合は
   # 出現位置を調整
   my $gnPopupTxt = $_[0];
   my ($gnPopupWinX, $gnPopupWinY) = $gnPopupTxt->get_position;
   my ($gnResizedWidth, $gnResizedHeight) = $gnPopupTxt->get_size;
   my $gnScreenHeight = Gtk2::Gdk->screen_height;
   my $gnScreenWidth = Gtk2::Gdk->screen_width;
   if($gnDebugFlag) { print "gnResPopup-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)\n"; }
   if (($gnPopupWinX + $gnResizedWidth) > $gnScreenWidth) { $gnPopupWinX = $gnScreenWidth - $gnResizedWidth; }
   if (($gnPopupWinY + $gnResizedHeight) > $gnScreenHeight) { $gnPopupWinY = $gnScreenHeight - $gnResizedHeight; }
   
   $gnPopupTxt->move($gnPopupWinX, $gnPopupWinY);
   if($gnDebugFlag) { print "gnResPopup-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)"; }
}

#--------------------------------------
# カスタム関数(gnview内部用)
#--------------------------------------

sub gnSubmit {
   # レスを送信する関数
   #
   # 引数: なし
   
   my $gnW = $gnWxml->get_widget('gnWrite');
   # 名前を取得
   my $gnWname = $gnWxml->get_widget('gnW_name');
   my $gnFROM = $gnWname->child->get_text;
   if($gnDebugFlag) { print "submit-FROM\: $gnFROM\n"; }
   # メールアドレスを取得
   my $gnWmail = $gnWxml->get_widget('gnW_mail');
   my $gnMAIL = $gnWmail->child->get_text;
   if($gnDebugFlag) { print "submit-mail\: $gnMAIL\n"; }
   # 本文を取得
   my $gnWcont = $gnWxml->get_widget('gnW_cont');
   my $gnWcont_buf = $gnWcont->get_buffer;
   my $gnMESSAGE = $gnWcont_buf->get_text($gnWcont_buf->get_start_iter, $gnWcont_buf->get_end_iter, FALSE);
   if($gnDebugFlag) { print "submit-cont\: $gnMESSAGE\n"; }
   
   # 本文がない場合はエラー
   if (!($gnMESSAGE)) {
      &gnWarn("本文がないと投稿できません");
      $gnW->present;
      return;
   }
   
   # 投稿時に必要なキー
   substr($gnW->get_title, rindex($gnW->get_title, "\(")+1) =~ m/\d{9,}/;
   my $gnkey = $&;
   my $gnbbs = $`;									# ` gedit誤認識対策用コメント
   chop($gnbbs);
   if($gnDebugFlag) { print "submit-bbs\: $gnbbs\n"; }
   if($gnDebugFlag) { print "submit-key\: $gnkey\n"; }
   
   # 投稿時間
   my $gntime = $gnKeyTime;
   if ($gnEnvArg{'2ch'}{'time'}) {
      $gntime = $gnEnvArg{'2ch'}{'time'};
   }
   if($gnDebugFlag) { print "submit-time\: $gntime\n"; }
   
   # 板名とドメイン名取得
   my $gndomain = $gnBoardTbl{$gnbbs};
   my $sep = chr(001);
   $gndomain =~ m/$sep/;
   $gndomain = $';									# ' gedit誤認識対策用コメント
   $gndomain = substr($gndomain, 7);
   my $sep = q{(?:(?:[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.)} .
              q{*[a-zA-Z](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.?|[0-9]+\.[0-9]+\.[0-9]+\.} .
              q{[0-9]+)(?::[0-9]*)?};
   $gndomain =~ m/$sep/;
   $gndomain = $&;
   if($gnDebugFlag) { print "domainname\: $gndomain\n"; }

   # クッキーがあれば取り込み
   my $gnFolIniFN = $logdir . $gnbbs . "\/Folder.ini";
   if($gnDebugFlag) { print "folderini\: $gnFolIniFN\n"; }
   my $gnPON;
   my $gnPONTime;
   my $gnHAP;
   my %gnFI;
   if (-e $gnFolIniFN) {
      my $gnFIRef = &gnLoadEnv($gnFolIniFN);
      %gnFI = %$gnFIRef;
      if ($gnFI{'Cookie'}{'PON'}) {
        if($gnFI{'Cookie'}{'PON'} ne "") {
         $gnPON = $gnFI{'Cookie'}{'PON'};
         my($a, $b) = split(/ /, $gnFI{'Cookie'}{'Expires'});
         my($y, $m, $d) = split(/\//, $a);
         my($hh, $mm, $ss) = split(/:/, $b);
         $m -= 1;
         $gnPONTime = timegm($ss, $mm, $hh, $d, $m, $y);
         $gnPONTime -= time;
        }
      }
      if ($gnFI{'Cookie'}{'Cookie=HAP'}) {
         $gnHAP = $gnFI{'Cookie'}{'Cookie=HAP'};
         chop($gnHAP);
      }
   }
   if($gnDebugFlag) { print "PON\: $gnPON, HAP\: $gnHAP\n"; }
   
   # リクエスト組み立て
   my $gnSUA = LWP::UserAgent->new;
   my $gnSCo = HTTP::Cookies->new;
   $gnSUA->agent($gnUsrAgent);
   $gnSUA->requests_redirectable([]);
   $gnSCo->set_cookie(1, "NAME", Encode::encode('shiftjis', $gnFROM), "\/", $gndomain, 80, 0, 0, 2592000, 0);
   $gnSCo->set_cookie(1, "MAIL", Encode::encode('shiftjis', $gnMAIL), "\/", $gndomain, 80, 0, 0, 2592000, 0);
   $gnSCo->set_cookie(1, "hana", Encode::encode('shiftjis', "mogera"), "\/", $gndomain, 80, 0, 0, undef, 0);
   if ($gnPON) {
      $gnSCo->set_cookie(0, "PON", Encode::encode('shiftjis', $gnPON), "\/", $gndomain, 80, 0, 0, $gnPONTime, 0);
   }else{
      $gnSCo->set_cookie(0, "PON", Encode::encode('shiftjis', ""), "\/", $gndomain, 80, 0, 0, $gnPONTime, 0);
   }
   if ($gnHAP) {
      $gnSCo->set_cookie(0, "HAP", Encode::encode('shiftjis', $gnHAP), "\/", $gndomain, 80, 0, 0, $gnPONTime, 0);
   }else{
      $gnSCo->set_cookie(0, "HAP", Encode::encode('shiftjis', ""), "\/", $gndomain, 80, 0, 0, $gnPONTime, 0);
   }
   $gnSUA->cookie_jar($gnSCo);
   if ($gnDebugFlag) { print "request-cookies\: " . Dumper($gnSCo->as_string) . "\n"; }
   
   my $gnSRqURL = "http\:\/\/" . $gndomain . "\/test\/bbs.cgi";
   my $gnSRq = HTTP::Request->new(POST => $gnSRqURL);
   my $gnSRq_ref = "http\:\/\/" . $gndomain . "\/test\/read.cgi\/$gnbbs\/$gnkey\/l50";
   $gnSRq->header(Referer => $gnSRq_ref);
   my $gncont = "submit=" . "書き込む" . "\&FROM=" . $gnFROM . "\&mail=" . $gnMAIL . "\&MESSAGE=" . $gnMESSAGE . "\&bbs=" . $gnbbs . "\&key=" . $gnkey . "\&time=1";
   $gncont = URI->new(Encode::encode('shiftjis', $gncont));
   $gnSRq->content($gncont->as_string);
   if ($gnDebugFlag) { print "POST Content\: \n"; }
   if ($gnDebugFlag) { print Dumper($gnSUA) . "\n" . Dumper($gnSRq); }
   
   my $response = $gnSUA->request($gnSRq);
   if ($gnDebugFlag) { print "response\:\n" . Dumper($response) . "\n" . Encode::decode('shiftjis', $response->content) . "\n"; }
   
   # レスポンスをゲット
   my $gnRes_cont = Encode::decode('shiftjis', $response->content);
   my $gnRes = HTML::TokeParser->new(\$gnRes_cont);
   my $gnRes_txt;
   my $gn2chXflg;
   my %gn2chForm;
   while (my $gnRes_tkn = $gnRes->get_token) {
      my $flg = $gnRes_tkn->[0];
      my $tag = $gnRes_tkn->[1];
      
      if ($flg eq "C") {
         
         if ($gnDebugFlag) { print "response-comment\: " . $tag . "\n"; }
         if (substr($tag, 5, 5) eq "2ch_X") {
            # 2ch_Xステータスを取得
            $gn2chXflg = substr($tag, 11, 4);
         }
      }
      if ($flg eq "T") {
         $gnRes_txt .= $tag;
      }
      if (($flg eq "S") && ($tag eq "br")) {
         $gnRes_txt .= "\n";
      }
      if (($flg eq "S") && ($tag eq "input")) {
         if ($gnDebugFlag) { print "response-input\: " . Dumper($gnRes_tkn->[2]) . "\n"; }
      }
      
   }
   
   
   # 2ch_Xステータスを元に処理を分岐
   my $gn2chXflg_res = FALSE;
   if ($gn2chXflg) {
      if ($gnDebugFlag) { print "2ch_X-flag \: $gn2chXflg\n"; }
      if ($gn2chXflg eq "cook") {
         # クッキー確認
         my $gnCook = &gnYesNo($gnRes_txt . "\n\nクッキーを設定しますか？");
         if($gnCook eq "yes") {
            $gnSCo->extract_cookies($response);
            if ($gnDebugFlag) { print scalar localtime(time) . "\n"; }
            if ($gnDebugFlag) { print "response-cookies\: " . Dumper($gnSCo->as_string) . "\n"; }
            &gnSetCookie($gnFolIniFN, $gnSCo, \%gnFI);
         
            &gnWarn("クッキーを設定しました。再度送信ボタンを押して投稿してください");
            $gnW->present;
            
         }
      }elsif($gn2chXflg eq "erro") {
        # エラー
        &gnWarn($gnRes_txt);
        
      }elsif($gn2chXflg eq "fals") {
        # 注意付き書き込み成功
        &gnWarn($gnRes_txt);
        $gn2chXflg_res = TRUE;
      }elsif($gn2chXflg eq "fals") {
        # 正常終了
        $gn2chXflg_res = TRUE;
      }else{
        #未対応(今のところ2ch_X:checkは未対応
        &gnWarn($gnRes_txt . "\n現在のところ\(バージョン$gnVersion\)未対応です\n");
      }
   }else{
      # 2ch_Xステータスがなかったら成功と見なす
      $gn2chXflg_res = TRUE;
   }
   
   # 書き込み成功時の処理
   if ($gn2chXflg_res == TRUE) {
      substr($gnW->get_title, 5) =~ m/\s+\-\s+/;
      my $title = $`;									# `gnview誤認識対策コメント
      $title = substr($title, index($title, ":")+1);
      &gnLogger("書き込みが完了しました\($title\)");
      
      # sent.ini に履歴を書き込み
      if (-e $gnInitCfg{'Main'}{'sent.ini'}) {
         open(gnSIN, ">>$gnInitCfg{'Main'}{'sent.ini'}");
            print gnSIN "[" . time ."]\n";
            print gnSIN "Name=" . Encode::encode('shiftjis', $gnFROM) . "\n";
            print gnSIN "EMail=" . Encode::encode('shiftjis', $gnMAIL) . "\n";
            my $gnSIN_mes = URI->new(Encode::encode('shiftjis', $gnMESSAGE));
            $gnSIN_mes = $gnSIN_mes->as_string;
            print gnSIN "Body=" . $gnSIN_mes . "\n";
            print gnSIN "Status=200\n";
            my @now = localtime;
            my $gnSIN_time = ($now[5]+1900) . "\/" . sprintf("%.2d", ($now[4]+1)) . "\/" . sprintf("%.2d", $now[3]) . " " .
                               sprintf("%.2d", $now[2]) . ":" . sprintf("%.2d", $now[1]) . ":" . sprintf("%.2d", $now[0]);
            print gnSIN "Date=$gnSIN_time\n";
            print gnSIN "Title=" . Encode::encode('shiftjis', $title) . "\n";
            print gnSIN "URL=$gnSRq_ref\n";
            print gnSIN "Key=$gnkey\n";
            print gnSIN "\n";
         close(gnSIN);
         
      }
      $gnW->destroy;
      return(0);
   }
   
   

}

sub gnSetCookie {
   # 書き込み用のクッキーを保存する関数
   #
   # 引数: 1: 文字列(書き込み先のFolder.iniのフルパス)
   #       2: HTTP::Cookiesオブジェクト
   #       3: Folder.iniの内容をgnLoadEnvで読み取ったハッシュ配列へのリファレント
   # 返り値: なし
   my $gnFIFN = $_[0];
   my $gnHCo = $_[1];
   my $gnFIRef = $_[2];
   my %gnFI = %$gnFIRef;
   if ($gnDebugFlag) { print "gnSetCookie-Folder.ini\: $gnFIFN\n"; }
   if ($gnDebugFlag) { print "gnSetCookie-cookie\: " . Dumper($gnHCo->as_string) . "\n"; }
   my $lnsl = $gnHCo->as_string;
   my @lns = split(/\n/, $lnsl);
   foreach (@lns) {
      my $ln = $_; chomp($ln);
      my ($name, $path, $domain, $port, $expires, $version) = split(/; /, substr($ln, 13));
      if ($gnDebugFlag) { print "gnSetCookie-each\: $name\n"; }
      my ($key, $val) = split(/=/, $name);
      if(($key ne "MAIL") && ($key ne "NAME")) {
         if ($key eq "HAP") {
            $gnFI{'Cookie'}{'Cookie=HAP'} = $val . ";";				# HAPの格納だけ書式が違うので対処
         }else{
            $gnFI{'Cookie'}{$key} = $val;
         }
         if ($key eq "PON") {
            my($x,$y) = split(/=/, $expires);
            $y = substr($y, 1, 19);
            $y =~ s/-/\//g;
            if ($gnDebugFlag) { print "gnSetCookie-PONExpire\: $y\n"; }
            $gnFI{'Cookie'}{'Expires'} = $y;
         }
      }
   }
   
   if ($gnDebugFlag) { print "gnSetCookie-Folder.ini\(update\)\:\n" . Dumper(%gnFI); }
   &gnSaveEnv($gnFIFN, \%gnFI);
}

sub gnWrite {
   # 書き込みウィンドウを表示する関数
   #
   # 引数: なし
   # 返り値: なし
   
   # 既に書き込みウィンドウが出ていたら何もしない
   if ($gnWxml ne "") {
      &gnLogger("今のところ書き込みウィンドウは１つしか開けません");
      return;
   }
   # 現在選択しているノートのdat(xxxx/xxx.dat)を得る
   my $gnSS_Note = $gnGUIxml->get_widget('gnNote');
   if($gnDebugFlag) { print "\n".Dumper($gnSS_Note)."\n"; }
   my $gnSS_Sures = $gnGUIxml->get_widget('gnSI');
   my $gnSS_SuresM = $gnSS_Sures->get_model;
   my ($gnSortCol, $gnSortOrder) = $gnSS_SuresM->get_sort_column_id;
   
   my $gnSS_TgtDat = "";
   
   my $gnSS_NoteP = $gnSS_Note->get_current_page;
   for my $i ( 0 .. $#gnSureViewInfo ) {
      if ($gnSS_NoteP == $gnSureViewInfo[$i][1]) {
         $gnSS_TgtDat = $gnSureViewInfo[$i][0];
         last;
      }
   }
   
   
   if ($gnSS_TgtDat) {
      # xxxx/xxx.datをキーにしてスレの名前を取得
      my $gnSureName = &gnGetSureVarFromDat($gnSS_SuresM, $gnSS_TgtDat, 0);
      
      # Sent.iniからハンドル名とメールアドレスを取得
      my ($gnSS_GenN, $gnSS_DatN) = split(/\//, $gnSS_TgtDat);
      $gnSS_DatN = substr($gnSS_DatN, 0, rindex($gnSS_DatN, "."));
      if ($gnDebugFlag) { print "datname=$gnSS_DatN\n"; }
      my $gnSentRef = &gnLoadEnv($gnSentIniFile);
      my %gnSent = %$gnSentRef;
      my $gnSent_hdlRef = &gnHashSel(\%gnSent, "", "Key", $gnSS_DatN);
      my %gnSent_hdl = %$gnSent_hdlRef;
      if ($gnDebugFlag) { print "handledata=" . Dumper(%gnSent_hdl); }
      
      # 書き込み用ウィンドウ生成
      if ($gnWxml eq "") {
         $gnWxml = Gtk2::GladeXML->new($gladefn, 'gnWrite');
         $gnWxml->signal_autoconnect_from_package('main');
      }
      my $gnWritew = $gnWxml->get_widget('gnWrite');
      if ($gnDebugFlag) { print Dumper($gnWritew); }
      
      # タイトルをセット
      my $gnWritew_title = $gnWritew->get_title;
      $gnWritew_title .= " - " . $gnSureName . " - " . "\($gnSS_TgtDat\)";
      $gnWritew->set_title($gnWritew_title);
      
      # ハンドル名、メールアドレスをセット
      my $gnW_name = $gnWxml->get_widget('gnW_name');
      my $gnW_mail = $gnWxml->get_widget('gnW_mail');
      
      my @gnHashKeys = keys(%gnSent_hdl);
      my $namebuf = "";
      my $emailbuf ="";
      my $namecnt = 1;
      foreach my $val(@gnHashKeys) {
         if ($gnSent_hdl{$val}{'Name'}) {
            if(!($namebuf =~ $gnSent_hdl{$val}{'Name'})) {
               $gnW_name->append_text($gnSent_hdl{$val}{'Name'});
               $namebuf .= $gnSent_hdl{$val}{'Name'};
               $namecnt++;
            }
         }
         if ($gnSent_hdl{$val}{'EMail'}) {
            if (!($emailbuf =~ $gnSent_hdl{$val}{'EMail'})) {
               $gnW_mail->append_text($gnSent_hdl{$val}{'EMail'});
               $emailbuf .= $gnSent_hdl{$val}{'EMail'};
            }
         }
      }
      

      $gnWritew->show_all;

   }else{
     &gnLogger("書き込み対象のスレが選択されていません");
   }
   
   
}

sub gnHashSel {
   # 2次元ハッシュ配列の中からある特定のkey=valueの組み合わせが含まれるもののみを
   # 抽出して返す関数
   #
   # 例えば、$aaa{'bbb'}{'ccc'}="ddd"
   #         $aaa{'bbb'}{'eee'}="fff" というハッシュ配列がある場合、
   # &gnHashSel(\%aaa, "", "ccc", "ddd"); と関数を呼び出すと、返り値は
   # $aaa{'bbb'}{'ccc'}="ddd" のみが含まれるハッシュ配列へのリファレンスが返る
   #
   # 引数: 1: ハッシュ配列へのリファレンス(元のハッシュ配列)
   #       2: 文字列: 1番目のkey(NULLの場合は*とみなす)
   #       3: 文字列: 2番目のkey(NULLの場合は*とみなす)
   #       4: 文字列: 抽出条件のvalue(NULLの場合は*とみなす)
   #          2~4のそれぞれの要素は部分一致で大文字小文字を区別する。
   #          各要素はANDされるものとする
   my $gnRefHash_Ref = $_[0];
   my %gnRefHash = %$gnRefHash_Ref;
   my $gnKey1 = $_[1];
   my $gnKey2 = $_[2];
   my $gnVal = $_[3];
   my %gnTgtHash;
   
   if ($gnKey1 eq "") { $gnKey1 = "\(.*\)"; }
   if ($gnKey2 eq "") { $gnKey2 = "\(.*\)"; }
   if ($gnVal eq "") { $gnVal = "\(.*\)"; }
   
   my @gnRefHash_cat = keys(%gnRefHash);
   foreach my $val1(@gnRefHash_cat) {
      if ($val1 =~ $gnKey1) {
         my @gnRefHash_keys = keys(%{$gnRefHash{$val1}});
         foreach my $val2(@gnRefHash_keys) {
            if ($val2 =~ $gnKey2) {
               my $val3 = $gnRefHash{$val1}{$val2};
               if ($val3 =~ $gnVal) {
                  $gnTgtHash{$val1} = $gnRefHash{$val1};
               }
            }
         }
      }
   }
   
   return(\%gnTgtHash);
   
}

sub gnGetSureVarFromDat {
   # dat名(xxxx/xxxx.dat)をキーにして、スレ一覧の中の
   # 任意の列の要素を取得する関数
   #
   # 引数: 1: 文字列(xxxxx/xxxx.dat)
   #       2: 整数(取り出す列。0スタート)
   # 返り値: 文字列
   my $gnTrModel = $_[0];
   my $gnTgtGenre = $_[1];
   my $gnTrTgtCol = $_[2];
   
   my $gnTrIter = $gnTrModel->get_iter_first;
   
   my $cnt = 0;
   my $gnRetVal;
   while (1) {
      my $gnRefDat = $gnTrModel->get($gnTrIter, 10);
      if ($gnDebugFlag>1) { print "referense\: " . Dumper($gnRefDat); }
      if ($gnRefDat eq $gnTgtGenre) {
         $gnRetVal = $gnTrModel->get($gnTrIter, $gnTrTgtCol);
         last;
      }else{
         $gnTrIter = $gnTrModel->iter_next($gnTrIter);
      }
   }

   if ($gnRetVal) {
      return($gnRetVal);
   }else{
      return("");
   }
}

sub gnJumpToURL {
   # URLを引数にして外部アプリを呼び出す関数
   #
   # 引数: 文字列: (URL)
   # 返り値: なし
   my $gnToURL = $_[0];
   if($gnDebugFlag) { print "$gnToURL\n"; }
   
   if ($gnEnvArg{'URLApp'}{'gnSelect'} == 0) {
      
      # Windows専用：既定のブラウザを起動する
      system("cmd \/c start $gnToURL");
      
   }elsif ($gnEnvArg{'URLApp'}{'gnSelect'} == 1) {
      
      # 汎用：指定したコマンドを使用する
      my $URLcmdln = $gnEnvArg{'URLApp'}{'gnFile'} . " $gnToURL";
      if($gnDebugFlag) { print "$URLcmdln\n"; }
      system("$URLcmdln \&");
      
   }elsif ($gnEnvArg{'URLApp'}{'gnSelect'} == 2) {
      
      # Windows以外：指定したブラウザを起動する
      if ($gnEnvArg{'URLApp'}{'gnSelBrowser'} == 0) {
         
         # Mozilla(Seamonkey)
         system("mozilla $gnToURL \&");
         
      }elsif ($gnEnvArg{'URLApp'}{'gnSelBrowser'} == 1) {
         
         # Mozilla Firefox
         system("firefox -UILocale ja $gnToURL \&");
         
      }elsif ($gnEnvArg{'URLApp'}{'gnSelBrowser'} == 2) {
         
         # Opera
         system("opera -newpage $gnToURL \&");
         
      }elsif ($gnEnvArg{'URLApp'}{'gnSelBrowser'} == 3) {
         
         # Konqueror
         system("konqueror --silent $gnToURL \&");
         
      }elsif ($gnEnvArg{'URLApp'}{'gnSelBrowser'} == 4) {
         
         # Epiphany
         system("epiphany $gnToURL \&");
         
      }
   }
   
   return(FALSE);
}

sub gnItaSelect {
   # 板のジャンル名を引数にして、ツリーから該当の板を選択する
   #
   # 引数: 文字列(板のURLからホスト名を除いたもの。例えばLinux板なら"linux"。)
   # 返り値; なし
   my $tgtgenre = $_[0];
   
   my $gnTrV = $gnGUIxml->get_widget('gnII');
   my $gnTrV_Model = $gnTrV->get_model;
   $gnTrV_Model->foreach(\&gnGetItaPathFromStr, $tgtgenre);
}

sub gnExit {
   # 終了処理関数
   #
   # 引数: なし
   # 返り値: なし

      # 終了時のウィンドウ位置、大きさを記憶
      my $gnmW = $gnGUIxml->get_widget('mW');
      ($gnEnvArg{'WindowSize'}{'Left'}, $gnEnvArg{'WindowSize'}{'Top'}) = Gtk2::Gdk::Window::get_position($gnmW->window);
      my $sute;
      ($sute, $sute,
       $gnEnvArg{'WindowSize'}{'Width'},
       $gnEnvArg{'WindowSize'}{'Height'},) = Gtk2::Gdk::Window::get_geometry($gnmW->window);

      # キャッシュファイルをセーブ
      if ($gnEnvArg{'Cache'}{'Enable'}) {
         if ($gnEnvArg{'Cache'}{'ClearOnExit'}) {
            &gnClearAllCache;
         }else{
            &gnClearExpiredCache;
         }
      }
      &gnSaveEnv($gnCacheTblFile, \%gnCacheTbl);
      # gikoNavi.iniに設定値を書き出し
      &gnSaveEnv($gnEnvFile, \%gnEnvArg);
      
      # URL読み込みスレッドを終了
      $queue->enqueue("exit");
      
      exit(0);
}

sub gnSureSwitch {
   # スレを表示するNotebookのタブを切り替えたらそれに対応する
   # カテゴリ、スレ一覧の該当部分を選択するようにする
   #
   # 引数: なし
   # 返り値: なし
   my $gnSS_Note = $gnGUIxml->get_widget('gnNote');
   if($gnDebugFlag) { print "\n".Dumper($gnSS_Note)."\n"; }
   my $gnSS_Genre = $gnGUIxml->get_widget('gnII');
   my $gnSS_Sures = $gnGUIxml->get_widget('gnSI');
   my $gnSS_SuresM = $gnSS_Sures->get_model;
   my ($gnSortCol, $gnSortOrder) = $gnSS_SuresM->get_sort_column_id;
   
   # スレフィルタをクリア
   my $gnSIFil = $gnGUIxml->get_widget('gnSI_fil');
   $gnSIFil->child->set_text("");
   
   #
   my $gnSS_TgtDat = "";
   
   my $gnSS_NoteP = $gnSS_Note->get_current_page;
   for my $i ( 0 .. $#gnSureViewInfo ) {
      if ($gnSS_NoteP == $gnSureViewInfo[$i][1]) {
         $gnSS_TgtDat = $gnSureViewInfo[$i][0];
         last;
      }
   }
   
   if ($gnSS_TgtDat ne "") {
      my ($gnSS_TgtDat_Ita, $gnSS_TgtDat_dat) = split(/\//, $gnSS_TgtDat);
      # ジャンルを選択

      my $gnSS_GenreM = $gnSS_Genre->get_model;
      my ($gnSS_GenreP, $gnSS_GenreC) = $gnSS_Genre->get_cursor;
      my $gnSS_GenreI = $gnSS_GenreM->get_iter($gnSS_GenreP);
      my $gnSS_Genre_now = $gnSS_GenreM->get($gnSS_GenreI, 1);

      $gnSS_Genre_now = &gnGetGenreFromURL($gnSS_Genre_now);
      if($gnDebugFlag) { print "$gnSS_Genre_now\n"; }

      if ($gnSS_Genre_now ne $gnSS_TgtDat_Ita) {
         $gnSS_GenreM->foreach(\&gnGetItaPathFromStr, $gnSS_TgtDat_Ita);
         
         my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
         $gnSS_SuresNow->scroll_to_point(0, 0);
         $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat($gnSS_TgtDat));
         
      }else{
         my $gnSS_SuresNow = $gnGUIxml->get_widget('gnSI');
         $gnSS_SuresNow->scroll_to_point(0, 0);
         $gnSS_SuresNow->set_cursor(&gnSureSelectFromDat($gnSS_TgtDat));
      
      }
      
      
   }
   
}

sub gnGetGenreFromURL {
   # 板のURL(http://xxx.xxx.xxx/xxxxx/)からジャンル名を返す関数
   #
   # 引数: 1: 文字列(板のURL。http://xxxxx.xxxx.xxxx/yyyyyy/)<-最後にスラッシュが必ずあり、その前がジャンル名であること
   # 返り値:  文字列(引数のyyyyyの部分)
   my $gnGGFU_url = $_[0];
   
   my @gnGGFU_val = split(/\//, $gnGGFU_url);
   my $gnGGFU_valcnt = @gnGGFU_val;
   return($gnGGFU_val[$gnGGFU_valcnt-1]);
   
}

sub gnGetItaPathFromStr {
   # 板名"xxxxx"をキーにして板一覧の該当行を選択する関数
   #
   # 引数: 1: 文字列(板名。例えばLinux板なら"linux")
   # 返り値: なし
   my $gnTrModel = $_[0];
   my $gnTrPath = $_[1];
   my $gnTrIter = $_[2];
   my $gnTgtGenre = $_[3];
   #my @val = @_;
   
   my $gnRefGenreURL = $gnTrModel->get($gnTrIter, 1);
   my @Str = split(/\//, $gnRefGenreURL);
   my $strCnt = @Str;
   my $gnRefGenre = $Str[$strCnt-1];
   
   if($gnRefGenre) {
      if($gnDebugFlag) { print "Target=$gnTgtGenre, Reference=$gnRefGenre\n"; }
      if ($gnRefGenre eq $gnTgtGenre) {
         my $gnTrV = $gnGUIxml->get_widget('gnII');
         $gnTrV->expand_to_path($gnTrPath);
         $gnTrV->set_cursor($gnTrPath);
         return(1);
      }else{
         return(0);
      }
   }else{
      return(0);
   }
}

sub gnSureIchiranInit {
   # スレ一覧を作成する関数
   #
   # 引数: 1: Gtk2::TreeStore(スレ一覧に入れるTreeStore(スレの一覧データ))
   #       2: array(参照するGtk2::TreeViewColumnの配列(スレ一覧の並び替え状態を再現する)。省略可)
   # 返り値: なし(この関数内でスレ一覧を作成するため戻り値はなし)
   my $gnSureStore = shift;
   my @gnTrVwColumn = @_;
   
   my $checkval = Gtk2::TreeViewColumn->new();
   my $checkref = Data::Dumper->new([$checkval]);
   my $checkref_flg = $checkref->Dump;
   
   my $checktgt = Data::Dumper->new([$gnTrVwColumn[0]]);
   my $checktgt_flg = $checktgt->Dump;
   
   my $check_flg;
   if($checkref_flg eq $checktgt_flg) {
       $check_flg = "matched";
       if($gnDebugFlag) { print "matched\n"; }
   }else{
       $check_flg = "unmatched";
       if($gnDebugFlag) { print "unmatched\n"; }
   }
  
   # TreeView作成
   my $gnSureView = $gnGUIxml->get_widget('gnSI');
   
   # 既にcolumnが設定してあったら削除
   if ($check_flg eq "matched") {
      my @gnSureViewCols = $gnSureView->get_columns;
      my $gnSureViewColN = @gnSureViewCols;
      for (my $i = 0 ; $i<$gnSureViewColN ; $i++) {
         $gnSureView->remove_column($i);
      }
   }
   
   $gnSureView->set_model($gnSureStore);
   $gnSureView->set_reorderable(TRUE);
   $gnSureView->signal_connect('row-activated' => sub{ &gnSureUpdate; });
   $gnSureView->signal_connect('cursor-changed' => sub{ &gnSureSelect($gnSureView, FALSE); });

   my @gnSICol ='';
   # スレ一覧の表タイトル
   my @gnSICol_title = ('スレッド名', 'カウント', '取得', '未取得', '新着', '未読',
                          '巡回予約', '取得日時', 'スレ作成日時', '最終更新日時',
                          'スレデータファイル名(隠し)' );
   my $gnSIColRend = Gtk2::CellRendererText->new;
   for(my $cnt=0;$cnt<12;$cnt++) {
      $gnSICol[$cnt] = Gtk2::TreeViewColumn->new();
      $gnSICol[$cnt]->set_title($gnSICol_title[$cnt]);
      $gnSICol[$cnt]->set_resizable(TRUE);
      $gnSICol[$cnt]->set_sort_column_id($cnt);
      $gnSICol[$cnt]->pack_start($gnSIColRend, FALSE);
      $gnSICol[$cnt]->add_attribute($gnSIColRend, text => $cnt);
      if($cnt < 10) {
         if ($check_flg eq "matched") {
            if($gnDebugFlag) { print "aaa\n" . Dumper($gnTrVwColumn[$cnt]); }
               my $gnTrVwColumn_sortType = $gnTrVwColumn[$cnt]->get_sort_order;
               $gnSICol[$cnt]->set_sort_order($gnTrVwColumn_sortType);
            }
         $gnSureView->append_column($gnSICol[$cnt]);
      }

   }


}

sub gnGetLogDir {
   # ログの保管ディレクトリ取得用関数
   # 環境設定に記述があればそれを使用
   # ない場合は"./Log/2ch/"を返す
   # 引数: なし
   # 返り値: 文字列(ログ保管ディレクトリへのフルパス)
   my $logdir = '';
   if($^O eq 'MSWin32') {
      if($gnEnvArg{'Folder'}{'LogFolder'} eq '') {
         $logdir   = "Log/2ch/";
      }else{
         $logdir   = $gnEnvArg{'Folder'}{'LogFolder'} . "/2ch/";
      }
   }else{
      if($gnEnvArg{'Folder'}{'LogFolderUnix'} eq '') {
         $logdir   = "Log/2ch/";
      }else{
         $logdir   = $gnEnvArg{'Folder'}{'LogFolderUnix'} . "/2ch/";
      }
   }
   
   return($logdir);

}

sub gnLoadEnv {
   # 環境設定ファイル(gikoNavi.ini)を読み込んで
   # 内部管理用のハッシュ配列に格納する
   # 引数: 文字列("gikoNavi.ini"へのパス)
   # 返り値: 2次元ハッシュ配列(カテゴリ名, Key)
   # ----------------------
   # 備考:   各変数へのアクセスのしかたは
   #         <$変数>{<カテゴリの名前>}{<keyの名前>} とすると値が出てくる
   #         <$変数>{<カテゴリの名前>}{<keyの名前>} = <新しい値> とすれば値を変更できる
   #         (例1)
   #           my $testvar = $gnEnvArg{'WindowSize'}{'Top'};
   #                       $testvar に入るのは、[WindowSize]カテゴリの Top=xxxxxx の値(xxxxxxx)
   #         (例2)
   #           $gnEnvArg{'WindowSize'}{'Top'} = "500";
   #                       [WindowSize]カテゴリの Top=xxxxxx の値(xxxxxxx) が 500 になる
   #        ---------------
   #         読み込むファイルはSJISだけど、配列の中にはutf8フラグ付きのshiftjisで
   #         記録されているので、Perl内部ではUTF-8として扱われている・・・はず
   
   # 引数を取得
   my $fn = $_[0];
   if($gnDebugFlag) { print "gnLoadEnv Filename=$fn\n"; }
   
   my $inibuf = '';
   # ファイルをオープンして読み込み
   open(gnLE, $fn);
      $inibuf = join '', <gnLE>;
   close(gnLE);
   
   $inibuf =~ s/\r//g;
   $inibuf = Encode::decode('shiftjis', $inibuf);
   my @inibufarray = split(/\n/, $inibuf);
   
   my $iniGenre= '';
   my $iniKey = '';
   my $iniVal = '';
   my %retarray;
   foreach(@inibufarray) {
      my $iniln = $_;
      if($iniln =~ /^\[/) {
         chop($iniln);
         $iniGenre = substr($iniln, 1);
      }else{
         $iniKey = substr($iniln, 0, rindex($iniln, "="));
         $iniVal = substr($iniln, rindex($iniln, "=")+1);
         $retarray{$iniGenre}{$iniKey} = $iniVal;
      }
   }
   return(\%retarray);
}

sub gnSaveEnv {
   # 環境設定ファイル(*.ini)へ、内部で保管したハッシュ配列を書き込む
   # 引数: 1:文字列("*.ini"へのパス)
   #       2:ハッシュ配列
   # 返り値: なし
   # ----------------------
   # 備考:   各変数は
   #         <$変数>{<カテゴリの名前>}{<keyの名前>} という形式で保管されている
   #         iniファイルに書き込むときは
   #         [カテゴリの名前]
   #         <keyの名前>=<値>crlf
   #         という形で書き込まれる
   #        ---------------
   #         ハッシュ配列はutf8フラグ付きのshiftjisで記録されているが
   #         iniファイルに書き出す際にはutf8フラグを落とし、ShiftJISで書き込まれる
   
   # 引数を取得
   my $fn = $_[0];
   my $gnEnvArg1 = $_[1];
   my %gnEnvArg2 = %$gnEnvArg1;
   if($gnDebugFlag) { print "saveenv\: $fn\n"; }

   my @gnEA_cat = keys(%gnEnvArg2);
   
   # ファイルをオープン
   open(gnLE, ">$fn");
   foreach my $val(@gnEA_cat) {
      # カテゴリの一覧を取得
      my @gnEA_keys = keys(%{$gnEnvArg2{$val}});
      
      my $flg1 = TRUE;
      foreach my $val2(@gnEA_keys) {
         # カテゴリの中のそれぞれのkeyを取り出し
         
         if($flg1) {
            my $str = "[" . $val . "]\n";
            print gnLE $str;
            $flg1 = FALSE;
         
         }
         
         my $val3 = $gnEnvArg2{$val}{$val2};
         
         my $str = $val2 . "=" . $val3 . "\n";
         $str = Encode::encode('shiftjis', $str);
         
         print gnLE $str;
      
      }
   }
   close(gnLE);
   
}


sub gnMakeItaTreeStore {
   # 板一覧のファイル名を引数として受け取り、
   # TreeStore型のウィジェットを返す
   # またグローバル変数%gnBoardTblも更新する
   my $cfgfile = shift;
   
   my $gnGenreid;
   my $gnGenreStr;
   
   my @gnItaGenre;
   my @gnItaInfo;
   
   # TreeStoreを作成
   my $gnItaStore = Gtk2::TreeStore->new( qw/ Glib::String Glib::String / );

   # 一番上「２ちゃんねる」を作成
   my $gnItaStore_iter1 = $gnItaStore->append(undef);
   $gnItaStore->set($gnItaStore_iter1, 0 => '２ちゃんねる', 1 => '');
   
   # TreeStoreのトップレベルになるジャンル抽出
   open(bnMITS, $cfgfile);
      while(<bnMITS>) {
         my $gnStr = $_; chomp($gnStr);
         $gnStr = Encode::decode('utf8', $gnStr);
         if($gnStr =~ /-0::/) {
            (my $gnGenreid, my $gnGenreStr) = split(/-0::/, $gnStr);
            push(@gnItaGenre, [$gnGenreid, $gnGenreStr])
         }
      }
   close(bnMITS);
   
   # ジャンルごとに板一覧を抽出
   my $loadcnt = 0;
   my $gnItaGenreCnt = @gnItaGenre;
   foreach my $gnGenreTmp (@gnItaGenre) {
      $gnGenreid  = $gnGenreTmp->[0];  # ジャンル番号
      $gnGenreStr = $gnGenreTmp->[1];  # ジャンルの名称
      
      # 第2レベルとなる各ジャンルをTreeStoreに追加
      my $gnItaStore_iter2 = $gnItaStore->append($gnItaStore_iter1);
      $gnItaStore->set($gnItaStore_iter2, 0 => $gnGenreStr, 1 => '');
      
      
      open(bnMITS, $cfgfile);
         while(<bnMITS>) {
            my $gnStr = $_; chomp($gnStr);
            $gnStr = Encode::decode('utf8', $gnStr);
            
            my $gnValStr = "/${gnGenreid}-/";
            if($gnStr =~ /^$gnGenreid-/) {
              if($gnStr !~ /$gnGenreid-0::/) {
              
                 # 第3レベルとなる各板を作成
                 my $gnItaStore_iter3 = $gnItaStore->append($gnItaStore_iter2);
                 (my $gnGenreid, my $gnItaName, my $gnItaUrl) = split(/::/, $gnStr);
                 $gnItaStore->set($gnItaStore_iter3, 0 => $gnItaName, 1 => $gnItaUrl);
                 
                 # 環境変数%gnBoardTblを更新
                 $gnBoardTbl{&gnGetGenreFromURL($gnItaUrl)} = $gnItaName . chr(001) . $gnItaUrl;
                 
                 print STDERR ".";
                 
              }
            }
         }
      close(bnMITS);
      
      $loadcnt++;
      print STDERR int($loadcnt / $gnItaGenreCnt * 100) . "\%";
   }
   
   return($gnItaStore);
}

sub gnMakeInitialSureLst {
   # 引数なし
   # スレ一覧の表に起動時の空データを渡す
   # 返り値はTreeStore型のウィジェット(実データは空)
   
   # TreeStoreを作成                       0: スレッド名(Str)
   #                                       1: カウント(Int)
   #                                       2: 取得(Int)
   #                                       3: 未取得(Int)
   #                                       4: 新着(Int)
   #                                       5: 未読(Int)
   #                                       6: 巡回予約(Str)
   #                                       7: 取得日時(Str)
   #                                       8: スレ作成日時(Str)
   #                                       9: 最終更新日時(Str)
   #                                      10: スレデータファイル名(Str, 隠し要素)
   my $gnSureStore = Gtk2::TreeStore->new( qw/ Glib::String Glib::String Glib::String
                                               Glib::String Glib::String Glib::String
                                               Glib::String Glib::String Glib::String
                                               Glib::String Glib::String / );
   my $gnSureStore_iter1 = $gnSureStore->append(undef);
   $gnSureStore->set($gnSureStore_iter1, 0 => '', 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0,
                     6 => '', 7 => '', 8 => '', 9 => '', 10 => '', 11 => '');
   
   return($gnSureStore);
}

sub gnSleIchiranSelect {
   # 板がクリックされたらスレの情報を読み込む
   # <ログディレクトリ>/Folder.idxがなかった場合はそのまま終了。
   # 一度もスレの一覧をとったことがない場合は確認の上、スレの一覧を取得する。(別関数へ)
   # あったら"<ログディレクトリ>/Folder.idx"を引数に
   # gnSleIchiranReadを呼ぶ
   #-------------------------------------------------------------------

   # 引数: 1: Gtk2::TreeView
   #       2: 文字列(強制的にFolder.idxの再読み込みをするか指定。"force"なら再読み込み。"none"ならしない。)
   
  if ($gnSuppressEventFlg == FALSE) { 
   my $gnSISHikisu =$_[0];
   if($gnDebugFlag) { print Dumper($gnSISHikisu); }
   
   my $gnSISForceFLG = $_[1];
   
   my ($gnMyTree, $gnMyTreePath, $gnMyTreeCol) = $gnSISHikisu;

   # 選択している部分を取得(返り値はGtk2::TreeSelecton)
   my $gnTreeSel = $gnMyTree->get_selection;

   # 得られたTreeSelectionからTreeModelとTreeIterを得る
   my ($gnMyTreeModel, $gnTM_iter) = $gnTreeSel->get_selected;

   # TreeIterを引数にしてget_valueすると、選択した行の配列が得られる
   my ($gnItaName, $gnItaUrl) = $gnMyTreeModel->get_value($gnTM_iter);

   
   # URLの最後はログのディレクトリ名でもあるので取り出す
   my @gnItaUrl_val = split(/\//, $gnItaUrl);
   my $gnIUv_cnt = @gnItaUrl_val;
   $gnIUv_cnt--;
   
   if($gnIUv_cnt > 0) {

      $gnItaLogDir = $gnItaUrl_val[$gnIUv_cnt];

      # ここでログのあるディレクトリが確定
      my $gnItaSfx = $logdir . $gnItaLogDir . "/";
      my $gnItaIdxFN = $gnItaSfx . "Folder.idx";
      
      # Folder.idxがなかったら新規作成用関数に飛ぶ
      if(!(-e "$gnItaIdxFN")) {
          if (!(-d $gnItaSfx)) {
             &gnLogger("板を格納するディレクトリがありません\($gnItaSfx\)");
             my $mW = $gnGUIxml->get_widget('mW');
             my $gnMsgDialog = Gtk2::MessageDialog->new($mW,
                                                           'GTK_DIALOG_MODAL',
                                                           'GTK_MESSAGE_INFO',
                                                           'GTK_BUTTONS_YES_NO',
                                                           "スレ一覧を格納するディレクトリがありません。\n作成してスレ一覧を取得しますか？",
                                                           ,
                                                           'GTK_BUTTONS_YES' => 'gtk-yes',
                                                           'GTK_BUTTONS_NO' => 'gtk-no');
             if($gnMsgDialog->run eq 'yes') {
                $gnMsgDialog->destroy;
                mkdir($gnItaSfx);
                &gnLogger("板を格納するディレクトリを作成しました\($gnItaSfx\)");

                my $gnSINFlg = &gnSureItiranNew($gnItaUrl, $gnItaSfx);
                if ($gnSINFlg == FALSE) {
                   &gnLogger("スレ一覧更新に失敗しました");
                   return(FALSE);
                }
             }else{
                &gnLogger("この板はまだ一覧を取得していません\($gnItaName\)");
                $gnMsgDialog->destroy;
                return(FALSE);
             }
          }
      }
      
      if ($gnSISForceFLG eq "force") {
         my $gnSINFlg = &gnSureItiranNew($gnItaUrl, $gnItaSfx);
         if ($gnSINFlg == FALSE) {
            &gnLogger("スレ一覧更新に失敗しました");
            return(FALSE);
         }
      }
      
      # ファイルパスを引数にして
      # gnSleIchiranReadを呼ぶ
      &gnSleIchiranRead($gnItaIdxFN);
      return(TRUE);
      
   }else{
      return(FALSE);
   }
 }  
}

sub gnSureItiranNew {
   # スレ一覧を更新(もしくは新規作成)する関数
   #
   # 引数: 1: 文字列(板のベースURL)
   #       2: 文字列(板のディレクトリ)
   
   my $gnItaUrl = $_[0];
   my $gnItaSfx = $_[1];
       
       &gnLogger("subject.txtを取得します");
       my $gnItaIdxFURL = $gnItaUrl . "subject.txt";
       
       my $gnSIUFlg = &gnSureItiranUpdate($gnItaIdxFURL, $gnItaSfx);
       if($gnSIUFlg == TRUE) {
           &gnLogger("スレ一覧を更新しました");
           return(TRUE);
       }else{
           &gnLogger("スレ一覧更新に失敗しました");
           return(FALSE);
       }
}

sub gnSureItiranUpdate {
   # 指定されたURLからsubject.txtを取得して
   # gnSub2FldIniを呼び出す。
   #
   # 引数: 1: 文字列(取得するsubject.txtのURL)
   #       2: 文字列(subject.txt, Folder.idxを保存するディレクトリ)
   my $gnItaIdxFURL = $_[0];
   my $gnItaSfx = $_[1];

   &gnLogger("取得するURL：$gnItaIdxFURL");
                
   my $gnUA = LWP::UserAgent->new;
   $gnUA->agent($gnUsrAgent);
             
   my $gnUA_res = $gnUA->get($gnItaIdxFURL);
   
   if($gnUA_res->is_success) {
       # subject.txtを取得したのでローカルに保存
       my $gnItaSubFN = $gnItaSfx . "subject.txt";
               
       open(gnIdxF, ">$gnItaSubFN");
          print gnIdxF $gnUA_res->content;
       close(gnIdxF);
               
       &gnLogger("subject.txtの取得に成功しました");
       
       # subject.txtからFolder.idxを生成(既にある場合はアップデート)
       my $gnSub2FldIniRST = &gnSub2FldIni($gnItaSubFN, $gnItaSfx);
               
       if($gnSub2FldIniRST eq FALSE) {
           &gnLogger("インデックスファイルの更新に失敗しました");
           return(FALSE);
       }else{
         return(TRUE);
       }
               
    }else{
       &gnLogger("subject.txtの取得に失敗しました");
    }
          
}

sub gnSub2FldIni {
   # 板のインデックスファイル(<logdirname>/subject.txt)とワーキングディレクトリを引数として受け取り
   # ワーキングディレクトリのFolder.idxを更新する。
   # 既にFolder.idxがあれば差分のみ追加
   #
   # 引数: 1: 文字列(<logdirname>/subject.txt)
   #       2: 文字列(<logdirname>)
   # 返り値: int(TRUEが成功、FALSEが失敗)
   
   my $gnSubFN = $_[0];
   my $gnFldIdxDir = $_[1];
   my $gnSureNo = 0;
   
   my $gnFldIdxFN = $gnFldIdxDir . "Folder.idx";
   # もしFolder.idxがなかったら作っておいて、行数を計っておく
   # (いちおう行数と行内の1カラム目が同期していない場合を考えて・・・
   if (!(-e $gnFldIdxFN)) {
      open(gnFIdx, ">>$gnFldIdxFN");
         print gnFIdx "$gnIdxFileVersion\n";
      close(gnFIdx);
   }
   open(gnFIdx2, "$gnFldIdxFN");
   while(<gnFIdx2>) {
      $gnSureNo++;
   }
   close(gnFIdx2);

   open(gnFSub, "$gnSubFN");
   my $aaa = 0;
   while(<gnFSub>) {
      # subject.txtの行をUTF8で処理できるように変換
      my $gnBuf = $_; chomp($gnBuf);
      $gnBuf = Encode::decode('shiftjis', $gnBuf);

      # datファイル名とスレタイを分離
      my ($gnDatName, $gnSureTitle) = split(/\<\>/, $gnBuf);
      # コメント数を取得
      my $gnSureCnt = substr($gnSureTitle,
                                rindex($gnSureTitle, "\(")+1,
                                (length($gnSureTitle) - rindex($gnSureTitle, "\)"))
                                );

      # コメント数が取得できなかったらその行は飛ばす
      # subject.txtの最後の行にchr(000)が続く行があるため
      if (!($gnSureCnt)) {
         next;
      }
      # タイトル名を取得
      my $bbb = rindex($gnSureTitle, "\(");
      if ($bbb != -1) {
        $gnSureTitle = substr($gnSureTitle, 0, $bbb-1);
      }
      
      my $gnDatFileFN = $gnFldIdxDir . $gnDatName;
      my $gnSureNoHex = sprintf("%X", $gnSureNo);
      my $gnIdxBuf = $gnSureNoHex . chr(001) .
                       $gnDatName . chr(001) .
                       $gnSureTitle . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       "0" . chr(001) .
                       $gnSureCnt . chr(001) .
                       $gnSureCnt . chr(001) .
                       "0";
      $gnIdxBuf = Encode::encode("shiftjis", $gnIdxBuf);
      
      # 現在のFolder.idxの中に$gnDatNameのエントリがなかったら追加する
      my $gnIsDatNameAlrdyLocalFLG = &gnIsDatNameAlrdyLocal($gnDatName, $gnFldIdxFN);
      if ($gnIsDatNameAlrdyLocalFLG == FALSE) {
         open(gnFIdx, ">>$gnFldIdxFN");
            print gnFIdx "$gnIdxBuf\n";
         close(gnFIdx);
         $gnSureNo++;
      }
      
   }
   close(gnFSub);
   
   #仮置き
   return(TRUE);
}

sub gnIsDatNameAlrdyLocal {
   # 指定したスレ(xxxxxx.dat)が既にFolder.idxの中に
   # いるかどうかを調べる関数
   #
   # 引数: 1: 文字列(スレファイル名(xxxxxx.dat)。パスなし)
   #       2: 文字列(Folder.idxのフルパス)
   # 返り値 : TRUE あった
   #        : FALSE なかった
   my $gnDatName = $_[0];
   my $gnFldIdxFN = $_[1];
   
   my $ret = FALSE;
   my $pos = -1;
   if (!(-e $gnFldIdxFN)) {
      &gnWarn("指定されたFolder.idx\($gnFldIdxFN\)が存在しません。\nFolder.idxを入れるディレクトリがない可能性があります");
      return(FALSE);
   }
   open(gnFIDNAL, "$gnFldIdxFN");
   while(<gnFIDNAL>) {
      my $buf = $_; chomp($buf);
      $buf = Encode::decode('shiftjis', $buf);
      $pos = index($buf, $gnDatName);
      if($pos>0) {
         $ret = TRUE;
         last;
      }
   }
   close(gnFIDNAL);
   
   if($ret == TRUE) {
      return(TRUE);
   }else{
      return(FALSE);
   }
}

sub gnSleIchiranRead {
   # 板のインデックスファイル(<logdirname>/Folder.idx)を引数として受け取り
   # オープンし、TreeStoreを構築し、gnSureViewに当てはめる
   my $gnSureIdxFN = shift;
   if($gnDebugFlag) { print "$gnSureIdxFN\n"; }
   
   # TreeStoreを作成                       0: スレッド名(Str)
   #                                       1: カウント(Int)
   #                                       2: 取得(Int)
   #                                       3: 未取得(Int)
   #                                       4: 新着(Int)
   #                                       5: 未読(Int)
   #                                       6: 巡回予約(Str)
   #                                       7: 取得日時(Str)
   #                                       8: スレ作成日時(Str)
   #                                       9: 最終更新日時(Str)
   #                                      10: スレデータファイル名(Str, 隠し要素)
   my $gnSureStore = Gtk2::TreeStore->new( qw/ Glib::String Glib::Int    Glib::Int
                                               Glib::Int    Glib::Int    Glib::Int
                                               Glib::String Glib::String Glib::String
                                               Glib::String Glib::String  / );

   
   my $gnSureDir = substr($gnSureIdxFN,
                            length($logdir),
                            (length($gnSureIdxFN) - (length($logdir)+length("Folder.idx")))-1
                            );
   if($gnDebugFlag) { print "$logdir\n"; }
   if($gnDebugFlag) { print "$gnSureDir\n"; }
   
   # スレフィルタの情報を取得
   my $gnSIFil = $gnGUIxml->get_widget('gnSI_fil');
   my $gnSIFilTxt = $gnSIFil->child->get_text;
   if ($gnDebugFlag) { print "sure_filter\: $gnSIFilTxt\n";}

   # gnSureStoreにデータを入れる
   my $gnSureStrcnt = 0;
   open(gnSIDX, "$gnSureIdxFN");
      while(<gnSIDX>) {
         my $gnSureInfoStr = $_;
         $gnSureInfoStr = Encode::decode('shiftjis', $gnSureInfoStr);
         chomp($gnSureInfoStr);
         
         if($gnSureStrcnt > 0) {
            # スレデータ1行を各要素に分割
            my($gnSureId, $gnSureFN, $gnSureName, $gnSureShutoku, $gnSureByte, $gnSureShutokuDate,
               $gnSureLastUpdate, $gnSureKidoku, $gnSurenazo1, $gnSurenazo2, $gnSurenazo3,
               $gnSurenazo4, $gnSurecnt, $gnSureShinchaku, $gnSurenazo7) = split(/\001/, $gnSureInfoStr);
            
            
            
            # 未取得はカウント-取得で算出
            my $gnSureMishutoku = hex($gnSurecnt) - hex($gnSureShutoku);

            # 取得日時・最終更新は文字列変換
            if(hex($gnSureShutokuDate) > 0) {
               my ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = gmtime(hex($gnSureShutokuDate));
               $MM++; $YY += 1900;
               $gnSureShutokuDate = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);
               ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = gmtime(hex($gnSureLastUpdate));
               $MM++; $YY += 1900;
               $gnSureLastUpdate = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);
            } else {
               $gnSureShutokuDate = '';
               $gnSureLastUpdate = '';
            }

            # スレタイトルは先頭にidをつける
            $gnSureName = sprintf("%05d",hex($gnSureId)) . ": " . decode_entities($gnSureName);
            # スレ作成日時はファイル名から抽出する
            my ($gnSureMkDate, $gnSureExt) = split(/\./, $gnSureFN);
            my ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = localtime($gnSureMkDate);
            $MM++; $YY += 1900;
            $gnSureMkDate = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);

            # スレフィルタにヒットしなければ次へ
            if (!($gnSIFilTxt =~ m/^ *$/)) {
               # スレフィルタが空白しか含まない場合は評価しない
               my $val = index($gnSureName, $gnSIFilTxt);
               if ($val < 0) {
                  if ($gnDebugFlag>1) { print "s";}
                  next;
               }else{
                  if ($gnDebugFlag>1) { print "h";}
               }
            }else{
                  if ($gnDebugFlag>1) { print "b";}
            }
            
            
            # $gnSureStore(TreeStore)に行を追加
            my $gnSureStore_iter = $gnSureStore->append(undef);
            $gnSureStore->set($gnSureStore_iter,
                              0 => $gnSureName,
                              1 => hex($gnSurecnt), 2 => hex($gnSureShutoku), 3 => $gnSureMishutoku,
                              4 => hex($gnSureShinchaku), 5 => '', 6 => '',
                              7 => $gnSureShutokuDate, 8 => $gnSureMkDate,
                              9 => $gnSureLastUpdate, 10 => ($gnSureDir . "\/" . $gnSureFN)
                              );
         
         }
         $gnSureStrcnt++;
      }
   close(gnSIDX);
   if ($gnDebugFlag>1) { print "\n";}
   
   # スレ一覧(TreeView)を呼び出し、新しく作ったTreeStoreを当てはめる
   my $gnSureLst = $gnGUIxml->get_widget('gnSI');
   $gnSureLst->set_model($gnSureStore);
   

}

sub gnSureSelect {
   # スレ一覧から選択されたスレのファイルを読み込む
   # 既にNotebookに読み込んでいたら読み込んだページを表示
   # 読み込んでいなかったらデータファイルを読み込みNotebookウィジェットに追加。
   
   # 引数: 1: Gtk2::TreeView
   
   my $gnTrView = $_[0];
   my $gnSureUpdateForce = $_[1];

   # 処理の間はswitch_pageシグナルをブロック
   my $gnNotebook = $gnGUIxml->get_widget('gnNote');
   $gnNotebook->signal_handler_block($gnSignalHandler->{gnNote}{switch_page});

   # 選択している部分を取得(返り値はGtk2::TreeSelecton)
   my $gnTreeSel = $gnTrView->get_selection;

   # 得られたTreeSelectionからTreeModelとTreeIterを得る
   my ($gnMyTreeModel, $gnTM_iter) = $gnTreeSel->get_selected;
   if ($gnDebugFlag) { print "hikisu: \n" . Dumper($gnTrView, $gnSureUpdateForce,
                                                  $gnMyTreeModel, $gnTM_iter), "\n"; }
   

   # TreeIterを引数にしてget_valueすると、選択した行の配列が得られる
   my @gnSureInfo = $gnMyTreeModel->get_value($gnTM_iter);
   
   # 管理用の配列を参照して、Notebook内に既に展開されているかを
   # 確認する
   my $gnFlg_in = FALSE;
   my $gnSVI_fn = '';
   my $gnSVI_idx = -1;
   my $gntmpval1 = @gnSureViewInfo;
   my @gnSureViewInfoTmp = @gnSureViewInfo;
   my @gnSureViewInfoNew;
   if ($gnDebugFlag) { print "whole" . Dumper(@gnSureViewInfo); }
   
   for my $i ( 0 .. $#gnSureViewInfo ){
      my $gnSVI_fnBuf = $gnSureViewInfo[$i][0];
      my $gnSVI_idxBuf = $gnSureViewInfo[$i][1];
      
      if ($gnDebugFlag) { print "confirm: $gnSVI_fnBuf, $gnSVI_idxBuf, $gnSureInfo[10]\n"; }
      if($gnSVI_fnBuf eq $gnSureInfo[10]) {
         $gnFlg_in = TRUE;
         $gnSVI_fn = $gnSVI_fnBuf;
         $gnSVI_idx = $gnSVI_idxBuf;
         if($gnSureUpdateForce == FALSE) {
            if ($gnDebugFlag) { print "found and pushed.\n"; }
            push(@gnSureViewInfoNew, ([$gnSVI_fnBuf, $gnSVI_idxBuf]));
         }else{
            if ($gnDebugFlag) { print "found and NOT pushed.\n"; }
         }
      }else{
         if ($gnDebugFlag) { print "not found but pushed.\n"; }
         push(@gnSureViewInfoNew, ([$gnSVI_fnBuf, $gnSVI_idxBuf]));
      }
      
      if ($gnDebugFlag) { print "loop examined num=$i.\n"; }
   }
   if ($gnDebugFlag) { print "result: \n$gnSVI_fn, $gnSureInfo[10], $gnSVI_idx, $gnFlg_in\n"; }
   if ($gnDebugFlag) {
      if ($gnFlg_in == TRUE) {
         print "result: found\n";
      }
   }
   @gnSureViewInfo = @gnSureViewInfoNew;
   if ($gnDebugFlag) { print "whole2 :\n" . Dumper(@gnSureViewInfo); }
   

   if($gnFlg_in == TRUE) {
       
       if($gnSureUpdateForce == TRUE) {
          if ($gnDebugFlag) { print "sure already in notebook BUT force update\n"; }
          
          
          # ノートブックのページを削除する
          $gnNotebook->remove_page($gnSVI_idx);
          if ($gnDebugFlag) { print "page removed: $gnSVI_idx\n"; }
          
          # 新規にページを追加する
          # スレ表示用にNotebookに新規ページを作成
          my $gnNB_tv = Gtk2::TextView->new;
          $gnNB_tv->set_property('left-margin' => 5);
          $gnNB_tv->set_property('pixels-above-lines' => 1);
          $gnNB_tv->set_property('pixels-below-lines' => 2);
          $gnNB_tv->set_property('editable' => FALSE);
          $gnNB_tv->signal_connect (motion_notify_event => \&gnSureViewMouseMove);     # マウスが動いたらイベント発生
          $gnNB_tv->signal_connect (button_press_event => \&gnSureViewLinkPress);
          $gnNB_tv->signal_connect (button_release_event => \&gnSureViewLinkRelease);
          
          # TextBufferを取得して初期化(必要ないかも)
          my $gnNB_tvbuf = $gnNB_tv->get_buffer;
          $gnNB_tvbuf->set_text('');
          
          # ScrolledWindowに入れる
          my $gnNB_tvsw = Gtk2::ScrolledWindow->new(undef, undef);
          $gnNB_tvsw->set_shadow_type('etched-out');
          $gnNB_tvsw->set_policy('automatic', 'automatic');
          $gnNB_tvsw->add($gnNB_tv);

          # スレの名前からタブ用のラベルを作成
          my ($gnSureNum, $gnSureName) = split(/: /, $gnSureInfo[0]);
          my $gnNB_lb = Gtk2::Label->new($gnSureName);

          # フォントサイズを小さくする
          # デフォルトの85%に【今後】将来的には設定できるように？
          my $gnNB_lb_context = $gnNB_lb->get_pango_context();
          my $gnNB_lb_fontDesc = $gnNB_lb_context->get_font_description();
          my $gnNB_lb_fontsize = $gnNB_lb_fontDesc->get_size();
          my $gnNB_lb_newsize = int($gnNB_lb_fontsize * 0.85);
          $gnNB_lb_fontDesc->set_size($gnNB_lb_newsize);
          $gnNB_lb->modify_font($gnNB_lb_fontDesc);
       
          # ファイルパスを関数gnParseSureにあたえると
          # 整形済みのTextBufferが返って来るので$gnNB_tvbuf(TextBuffer)に格納
          my $gnSureFN = $logdir . $gnSureInfo[10];
          if($gnDebugFlag) { print "$gnSureFN\n"; }
          $gnSureFN = &env_FNconv($gnSureFN);

          my $gnSureFN1 = $gnSureInfo[10];
          if($gnDebugFlag) { print "$gnSureFN1\n"; }
          $gnSureFN1 = &env_FNconv($gnSureFN1);
          $gnNB_tvbuf = &gnParseSure($gnSureFN, $gnSureFN1);
          $gnNB_tv->set_buffer($gnNB_tvbuf);

          # カーソルを先頭に(後々変える必要あり)
          $gnNB_tvbuf->place_cursor($gnNB_tvbuf->get_start_iter);
       
          # 用意できたページをNotebookに追加
          my $gnSureView_newpagenum = $gnNotebook->append_page($gnNB_tvsw, $gnNB_lb);
          
          if($gnDebugFlag) { print "page appended.num=$gnSureView_newpagenum\n"; }
       
          # Notebookを再表示(なぜかこれをしないと追加したページが表示されない)
          $gnNotebook->show_all;
          $gnNotebook->set_current_page($gnSureView_newpagenum);
          if($gnDebugFlag) { print "show again.\n"; }

          # 管理配列にスレの情報を書き込み
          push(@gnSureViewInfo, ([$gnSureInfo[10], $gnSureView_newpagenum]));
          

       }else{
          # スレは既にNotebook内に表示されているので
          # 該当のページを表示
          if ($gnDebugFlag) { print "page already in notebook\n"; }
          $gnNotebook->set_current_page($gnSVI_idx);
       }
       
   }else{
       # スレはNotebook内にはない。
       if ($gnDebugFlag) { print "page NOT already in notebook\n"; }
       if ($gnDebugFlag) { print "need to create new page\n"; }
       # スレが取得されていないかを確認
       if($gnSureInfo[2] eq "0") {
          # スレはまだ取得されていない
          # ログウィンドウにエラーログを出力
          
          my ($gnSureNum, $gnSureName) = split(/: /, $gnSureInfo[0]);
          my $gnLogStr = "このスレはまだ取得していません(" . $gnSureName . ")";
          if ($gnDebugFlag) { print "sure is never gotton: $gnSureInfo[2]\n"; }
          
          &gnLogger($gnLogStr);
          
       }else{
          # スレは取得されているので、ファイルから読み込み
          
          # スレ表示用にNotebookに新規ページを作成
          my $gnNB_tv = Gtk2::TextView->new;
          $gnNB_tv->set_property('left-margin' => 5);
          $gnNB_tv->set_property('pixels-above-lines' => 1);
          $gnNB_tv->set_property('pixels-below-lines' => 2);
          $gnNB_tv->set_property('editable' => FALSE);
          $gnNB_tv->signal_connect (motion_notify_event => \&gnSureViewMouseMove);     # マウスが動いたらイベント発生
          $gnNB_tv->signal_connect (button_press_event => \&gnSureViewLinkPress);
          $gnNB_tv->signal_connect (button_release_event => \&gnSureViewLinkRelease);
          
          # TextBufferを取得して初期化(必要ないかも)
          my $gnNB_tvbuf = $gnNB_tv->get_buffer;
          $gnNB_tvbuf->set_text('');
          
          # ScrolledWindowに入れる
          my $gnNB_tvsw = Gtk2::ScrolledWindow->new(undef, undef);
          $gnNB_tvsw->set_shadow_type('etched-out');
          $gnNB_tvsw->set_policy('automatic', 'automatic');
          $gnNB_tvsw->add($gnNB_tv);

          # スレの名前からタブ用のラベルを作成
          my ($gnSureNum, $gnSureName) = split(/: /, $gnSureInfo[0]);
          my $gnNB_lb = Gtk2::Label->new($gnSureName);

          # フォントサイズを小さくする
          # デフォルトの85%に【今後】将来的には設定できるように？
          my $gnNB_lb_context = $gnNB_lb->get_pango_context();
          my $gnNB_lb_fontDesc = $gnNB_lb_context->get_font_description();
          my $gnNB_lb_fontsize = $gnNB_lb_fontDesc->get_size();
          my $gnNB_lb_newsize = int($gnNB_lb_fontsize * 0.85);
          $gnNB_lb_fontDesc->set_size($gnNB_lb_newsize);
          $gnNB_lb->modify_font($gnNB_lb_fontDesc);
       
          # ファイルパスを関数gnParseSureにあたえると
          # 整形済みのTextBufferが返って来るので$gnNB_tvbuf(TextBuffer)に格納
          my $gnSureFN = $logdir . $gnSureInfo[10];
          if($gnDebugFlag) { print "$gnSureFN\n"; }
          $gnSureFN = &env_FNconv($gnSureFN);

          my $gnSureFN1 = $gnSureInfo[10];
          if($gnDebugFlag) { print "$gnSureFN1\n"; }
          $gnSureFN1 = &env_FNconv($gnSureFN1);
          $gnNB_tvbuf = &gnParseSure($gnSureFN, $gnSureFN1);
          $gnNB_tv->set_buffer($gnNB_tvbuf);

          # カーソルを先頭に(後々変える必要あり)
          $gnNB_tvbuf->place_cursor($gnNB_tvbuf->get_start_iter);
       
          # 用意できたページをNotebookに追加
          my $gnSureView_newpagenum = $gnNotebook->append_page($gnNB_tvsw, $gnNB_lb);
          if($gnDebugFlag) { print "page appended\(normal, num=$gnSureView_newpagenum\).\n"; }
       
          # Notebookを再表示(なぜかこれをしないと追加したページが表示されない)
          $gnNotebook->show_all;
          $gnNotebook->set_current_page($gnSureView_newpagenum);
          if($gnDebugFlag) { print "show again\(normal\).\n"; }

          # 管理配列にスレの情報を書き込み
          my $gnSureInfoStr = sprintf("%s", $gnSureInfo[10]);
          my @gnNewSureInfo = ($gnSureInfoStr, $gnSureView_newpagenum);
          if($gnDebugFlag) { print "added\n" . Dumper("@gnNewSureInfo"); }
          
          push(@gnSureViewInfo, ([$gnSureInfoStr, $gnSureView_newpagenum]));
          undef(@gnNewSureInfo);
          if($gnDebugFlag) { print "whole\n" . Dumper(@gnSureViewInfo) . "\n"; }
       
       }

   }
   
   &gnSIFilUpdate;

   # switch_pageシグナルのブロックを解除
   $gnNotebook->signal_handler_unblock($gnSignalHandler->{gnNote}{switch_page});

}

sub gnSIFilUpdate {
   # スレフィルタを更新する
   # つまり現在のテキストボックスの入力内容をプルダウンメニューに追加する
   my $gnSIFil = $gnGUIxml->get_widget('gnSI_fil');
   my $gnSIFil_txt = $gnSIFil->child->get_text;
   
   if ($gnSIFil_txt =~ m/^ *$/) {
     # スレフィルタが空白しか含まない場合はそのままリターンする
     return;
   }   

   # フィルタリストのなかになかったらリストに追加
   my $gnSIFilHit = FALSE;
   my $cnt = 0;
   my @gnSIFilHashKeys = keys(%{$gnEnvArg{'SelectText'}});
   foreach my $val(@gnSIFilHashKeys) {
     if ($gnEnvArg{'SelectText'}{$val} eq $gnSIFil_txt) {
        $gnSIFilHit = TRUE;
     }
     $cnt++;
   }
  
  if ($gnSIFilHit == FALSE) {
     $cnt++;
     my $cntstr = sprintf("%02d", $cnt);
     $gnEnvArg{'SelectText'}{$cntstr} = $gnSIFil_txt;
     $gnSIFil->append_text($gnSIFil_txt);
  }

}

sub gnSureUpdate {
   # 選択したスレを更新する
   #
   # 引数: 1: Gtk2::TreeView

   # 引数(Gtk2::TreeView)を代入
   my $gnTrView = $_[0];
   
   # Notebook内のページを再読み込みするか
   my $gnIsNoteReload = FALSE;

   # 選択している部分を取得(返り値はGtk2::TreeSelecton)
   my $gnTreeSel = $gnTrView->get_selection;
   my ($gnTreePath,) = $gnTrView->get_cursor;
   # 得られたTreeSelectionからTreeModelとTreeIterを得る
   my ($gnMyTreeModel, $gnTM_iter) = $gnTreeSel->get_selected;
   my @gnTreeSelRows = $gnTreeSel->get_selected_rows;
   # TreeIterを引数にしてget_valueすると、選択した行の配列が得られる
   # [10]の中身が板のディレクトリとdatファイル名
   my @gnSureInfo = $gnMyTreeModel->get_value($gnTM_iter);
   my ($linktgt_ita, $linktgt_sure) = split(/\//, $gnSureInfo[10]);
   if($gnDebugFlag) { print "$gnSureInfo[10]"; }
   
   # 板のURL取得
   # 【俺メモ】要改善：板一覧をいじった後にスレ取得しようとすると、現在の板が取得できないので
   #                  URLが取得できずエラーになる
   my $gnIIBuf = $gnGUIxml->get_widget('gnII');
   my ($gnMyTree, $gnMyTreePath, $gnMyTreeCol) = $gnIIBuf;
   # 選択している部分を取得(返り値はGtk2::TreeSelecton)
   my $gnTreeSel2 = $gnMyTree->get_selection;
   # 得られたTreeSelectionからTreeModelとTreeIterを得る
   my ($gnMyTreeModel2, $gnTM_iter2) = $gnTreeSel2->get_selected;
   # TreeIterを引数にしてget_valueすると、選択した行の配列が得られる
   my ($gnItaName, $gnItaUrl) = $gnMyTreeModel2->get_value($gnTM_iter2);
   
   # リクエストヘッダを生成
   my $gnItaDatURL = $gnItaUrl . "dat\/" . $linktgt_sure;
   my $gnItaDatFN = $logdir . "\/" . $gnSureInfo[10];
   if($gnDebugFlag) { print "$gnItaDatURL"; }

   my $gnUpdateFLG = FALSE; # 既にあるファイルの更新なのか新規取得なのか
   my $gnDatSize = 0;
   
   my $gnDatLastModified = time2str(0);
   if (-e $gnItaDatFN) {
      $gnUpdateFLG = TRUE;
      my @gnDatStat = stat($gnItaDatFN);
      $gnDatLastModified = time2str($gnDatStat[9]);
      $gnDatSize = $gnDatStat[7];
      $gnDatSize++;
   }
   if($gnDebugFlag) { print "$gnDatLastModified"; }
   
   # datファイルを取得
   my ($gnGetDatRST, $gnGetDatRes) = &gnGetDat($gnItaUrl, $linktgt_sure);
   
   if ($gnGetDatRST eq "gn200OK") {
       my $gnNow = time();
       # 取得数を更新
       my $gnDatNumRow = 0;
       open(gnDatF2, "$gnItaDatFN");
       while(<gnDatF2>) {
          $gnDatNumRow++;
       }
       close(gnDatF2);
       my $gnFolderIdxUpdateFLG = "";
       my $gnFolderIdxGetValFLG = "";
       $gnFolderIdxUpdateFLG = &gnFolderIdxUpdate($logdir . "\/" . $linktgt_ita . "/Folder.idx",
                                                        $linktgt_sure,
                                                        "3",
                                                        sprintf("%X", $gnDatNumRow)
                                                        );
       $gnFolderIdxUpdateFLG = &gnFolderIdxUpdate($logdir . "\/" . $linktgt_ita . "/Folder.idx",
                                                        $linktgt_sure,
                                                        "12",
                                                        sprintf("%X", $gnDatNumRow)
                                                        );
       # 取得日時が0なら更新
       $gnFolderIdxUpdateFLG = &gnFolderIdxUpdate($logdir . "\/" . $linktgt_ita . "/Folder.idx",
                                                           $linktgt_sure,
                                                           "5",
                                                           sprintf("%X",$gnNow+32400));  # quick-hack
       # 最終更新日を取得
       my $gnSureUpdateTime = str2time($gnGetDatRes->header('Last-Modified'));
       
       $gnFolderIdxUpdateFLG = &gnFolderIdxUpdate($logdir . "\/" . $linktgt_ita . "/Folder.idx",
                                                           $linktgt_sure,
                                                           "6",
                                                           sprintf("%X", $gnSureUpdateTime+32400)); # quick-hack

       if($gnDebugFlag) { print "Last-Modified\: $gnSureUpdateTime"; }
       &gnLogger("Folder.idxを更新しました：" . $logdir . "\/" . $linktgt_ita . "/Folder.idx");
       if($gnDebugFlag) { print "update aquired.\n"; }
       
       # スレ一覧をアップデート
       &gnSureIchiranSetVal($gnSureInfo[10], 1, $gnDatNumRow);
       &gnSureIchiranSetVal($gnSureInfo[10], 2, $gnDatNumRow);
       my ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = localtime($gnNow);
       $MM++; $YY += 1900;
       my $gnGetTime = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);
       &gnSureIchiranSetVal($gnSureInfo[10], 7, $gnGetTime);

       ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = localtime($gnSureUpdateTime);
       $MM++; $YY += 1900;
       my $gnLastUpdate = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);
       &gnSureIchiranSetVal($gnSureInfo[10], 9, $gnLastUpdate);
       &gnLogger("スレ一覧の情報を更新しました");
       if($gnDebugFlag) { print "update sure-list.\n"; }
       
       # ソート順序を再現
       my ($gnSortCol, $gnSortOrder) = $gnMyTreeModel->get_sort_column_id;
       if ($gnSortOrder) {
          my $gnTrViewAfter = $gnGUIxml->get_widget('gnSI');
          my $gnTrViewA_Model = $gnTrViewAfter->get_model;
          $gnTrViewA_Model->set_sort_column_id($gnSortCol, $gnSortOrder);
       }
       if($gnDebugFlag) { print "id=$gnSortCol, order=$gnSortOrder\n"; }
       
       # アップデート前のコピーを元にスレ一覧の並び替え状況を復元
       #
       $gnTrView->scroll_to_point(0, 0);
       my $gnNewPath = &gnSureSelectFromDat($gnSureInfo[10]);
       $gnTrView->set_cursor($gnNewPath);
       $gnTrView->scroll_to_cell($gnNewPath);

       # スレを再読み込み
       &gnSureSelect($gnTrView, TRUE);
       if($gnDebugFlag) { print "\(force\)about to re-show.\n"; }
       
   }else{
       &gnLogger("datの取得に失敗しました\($gnItaDatURL\)");
       if($gnDebugFlag) { print "sure update canceled.\n"; }
   }
}

sub gnSureSelectFromDat {
   # "xxxxx/xxxxx.dat"をキーにしてスレ一覧の該当行(Gtk2::TreePath)を返す
   #
   # 引数: 1: 文字列(xxxxxx/xxxxx.dat)
   # 返り値: Gtk2::TreePath
   my $gnDatStr = $_[0];
   my $gnRetPath;
   
   my $gnTrV = $gnGUIxml->get_widget('gnSI');
   my $gnTrVModel = $gnTrV->get_model;
   my $gnTrvModel_iter = $gnTrVModel->get_iter_first;
   my $gnTrVModelR;
   while ($gnTrvModel_iter) {
      $gnTrVModelR = $gnTrVModel->get($gnTrvModel_iter, 10);
      if ($gnTrVModelR eq $gnDatStr) {
         $gnRetPath = $gnTrVModel->get_path($gnTrvModel_iter);
         last;
      }
   $gnTrvModel_iter = $gnTrVModel->iter_next($gnTrvModel_iter);
   }
   
   return($gnRetPath);
}

sub gnSureIchiranSetVal {
   # スレ一覧の要素を書き換える関数
   #
   # 引数: 1: 文字列(xxxxxx/xxxxx.datの書式で記述。更新する行を指定)
   #       2: 整数(0スタート。更新する列を指定)
   #       3: 文字列(更新内容を指定(この関数内で型変換などは特にはしない))
   my $gnDatStr = $_[0];
   my $gnSelCol = $_[1];
   my $gnUpdtVar = $_[2];
   
   my $gnTrV = $gnGUIxml->get_widget('gnSI');
   my $gnTrVModel = $gnTrV->get_model;
   my $gnTrvModel_iter = $gnTrVModel->get_iter_first;
   my $gnTrVModelR;
   while ($gnTrvModel_iter) {
      $gnTrVModelR = $gnTrVModel->get($gnTrvModel_iter, 10);
      if ($gnTrVModelR eq $gnDatStr) {
         $gnTrVModel->set($gnTrvModel_iter,
                            $gnSelCol, $gnUpdtVar);
         last;
      }
   $gnTrvModel_iter = $gnTrVModel->iter_next($gnTrvModel_iter);
   }
   

}

sub gnSetTreeViewOrder {

}

sub gnGetDat {
   # datファイル名を指定してレスを取得するラッパー関数
   # 2ch, bbspinkに対応して、notfoundに対してURLを変えながらdatの取得を試みる
   #
   # 引数: 1: 板のURL(http://xxx.xxx.xxx/yyyy/)
   #       2: datファイル名(xxxxx.dat)
   # 返り値 1: 文字列(ステータスコード: "gn200OK"=正常完了, "gn404NOTFOUND"=dat存在せず, "gn999UNKNOWN"=不明のエラー)
   #        2: HTTP::Response構造体(返り値1が"gn200OK"の場合のみ含まれる。返り値1がそれ以外の場合はNULLとなる)
   my $gnDatURL = $_[0];
   my $gnDatFN = $_[1];
   my $gnGetDatRetVal1;
   my $gnGetDatRetVal2;
   if($gnDebugFlag) { print ("gnGetDat\:\ngnGetDat-Filename/:$gnDatFN"); }

   my @gnDatURLVal = split(/\//, $gnDatURL);
   my $gnDatURLValCnt = @gnDatURLVal;
   if($gnDebugFlag) { print ("$gnDatURL\n" . "$gnDatURLVal[$gnDatURLValCnt-1]\n"); }
   
   my $gnItaDatFN = $logdir .
                      $gnDatURLVal[$gnDatURLValCnt-1] . "\/" .
                      $gnDatFN;
   if($gnDebugFlag) { print "$logdir\n" . "$gnDatURL\n" . "$gnDatURLVal[$gnDatURLValCnt-1]\n" . "$gnItaDatFN\n"; }

   my $gnDatURLFLG = "else";
   my $gnDatURLFLG1;
   $gnDatURLFLG1 = index($gnDatURL, "2ch\.net");
   if ($gnDatURLFLG1>-1) { $gnDatURLFLG = "2ch"; }
   $gnDatURLFLG1 = index($gnDatURL, "bbspink\.com");
   if ($gnDatURLFLG1>-1) { $gnDatURLFLG = "bbspink"; }
   if($gnDebugFlag) { print "$gnDatURLFLG\n"; }
   
   my $gnReq = HTTP::Request->new;
   my $gnUA  = LWP::UserAgent->new;
   my $gnHed = HTTP::Headers->new;
   $gnUA->agent($gnUsrAgent);
   $gnUA->requests_redirectable([]); # 自動リダイレクト禁止
   
   if (-e $gnItaDatFN) {
         my @gnDatStat = stat($gnItaDatFN);
         my $gnDatLastModified = time2str($gnDatStat[9]);
         my $gnIfModSince = $gnDatLastModified;
         chop($gnIfModSince); chop($gnIfModSince); chop($gnIfModSince); chop($gnIfModSince);
         my $gnDatSize = $gnDatStat[7];
         
         # 更新用ヘッダの生成
         $gnHed->header ( 'Last-Modified' => $gnDatLastModified,
                            'Accept' => "text\/html, \*\/\*",
                            'If-Modified-Since' => $gnIfModSince . " \+0900",
                            'Cache-control' => "no-cache",
                            'Pragma' => "no-cache",
                            'Range' => "$gnDatSize-");
         $gnHed->remove_header('TE', 'Conection', 'Date');
   }else{
         my $gnDatLastModified = time2str(0);
         
         # 新規取得用ヘッダの生成
         $gnHed->header ( 'Last-Modified' => $gnDatLastModified,
                            'Accept' => "text\/html, \*\/\*",
                            'Accept-Encoding' => 'gzip',
                            'If-Modified-Since' => $gnDatLastModified);
   }
             
   my $gnUA_res;
   my $gnUA_resflg;
   my $gnDatURL1;
   
   if (($gnDatURLFLG eq "2ch") || ($gnDatURLFLG eq "bbspink")) {
      		my $gnDatFN1;
      		my $gnDatFN2;
      			
      		$gnDatURL1 = $gnDatURL . "dat\/" . $gnDatFN;
      		$gnReq->url($gnDatURL1);
      		$gnUA->default_headers($gnHed);
      		$gnReq->method('GET');
      		#$gnHed->remove_header('TE', 'Conection', 'Date');
      		$gnUA_res = $gnUA->request($gnReq);
      		if($gnDebugFlag) { print "First Try\!\!\($gnDatURL1\)\n"; }
      		&gnLogger("アクセス結果\($gnDatURL1\)：" . $gnUA_res->code);
      			
      		if (!($gnUA_res->is_success)) {
      			if (!($gnUA_res->code == "304")) {
      				my $gnDatFNLength = length($gnDatFN);
      				if ($gnDatFNLength == 13) {
      					$gnDatFN1 = substr($gnDatFN, 0, 3);
      					$gnDatURL1 = $gnDatURL . "kako\/" . "$gnDatFN1\/" . $gnDatFN . "\.gz";
      					$gnReq->url($gnDatURL1);
      					$gnReq->method('GET');
      					$gnUA_res = $gnUA->request($gnReq);
      					if ($gnDebugFlag) { print "Second Try\(1\)\!\!\($gnDatURL1\)\n"; }
      					&gnLogger("アクセス結果\($gnDatURL1\)：" . $gnUA_res->code);

      			
      					if (!($gnUA_res->is_success)) {
      						$gnDatFN1 = substr($gnDatFN, 0, 3);
      						$gnDatURL1 = $gnDatURL . "kako\/" . "$gnDatFN1\/" . $gnDatFN;
      						$gnReq->url($gnDatURL1);
      						$gnReq->method('GET');
      						$gnUA_res = $gnUA->request($gnReq);
      						if($gnDebugFlag) { print "Second Try\(2\)\!\!\($gnDatURL1\)\n"; }
      						&gnLogger("アクセス結果\($gnDatURL1\)：" . $gnUA_res->code);
      					}
      				}else{

      				
      					$gnDatFN1 = substr($gnDatFN, 0, 4);
      					$gnDatFN2 = substr($gnDatFN, 0, 5);
      					$gnDatURL1 = $gnDatURL . "kako\/" . "$gnDatFN1\/" . "$gnDatFN2\/" . $gnDatFN . "\.gz";
      					$gnReq->url($gnDatURL1);
      					$gnReq->method('GET');
      					$gnUA_res = $gnUA->request($gnReq);
      					if($gnDebugFlag) { print "Third Try\(1\)\!\!\($gnDatURL1\)\n"; }
      					&gnLogger("アクセス結果\($gnDatURL1\)：" . $gnUA_res->code);


      					if (!($gnUA_res->is_success)) {
      						$gnDatFN1 = substr($gnDatFN, 0, 4);
      						$gnDatFN2 = substr($gnDatFN, 0, 5);
      						$gnDatURL1 = $gnDatURL . "kako\/" . "$gnDatFN1\/" . "$gnDatFN2\/" . $gnDatFN;
      						$gnReq->url($gnDatURL1);
      						$gnReq->method('GET');
      						$gnUA_res = $gnUA->request($gnReq);
      						if($gnDebugFlag) { print "Third Try\(2\)\!\!\($gnDatURL1\)\n"; }
      						&gnLogger("アクセス結果\($gnDatURL1\)：" . $gnUA_res->code);
      					}
      				}
      			}
      		}
   }else{
      		# 今のところ2ch.netとbbspink.com以外のログ取得は
      		# 対応していませんorz
      		&gnLogger("2ch.netとbbspink.com以外のログ取得は対応していません");
   }
   
   if ($gnUA_res) {
      if($gnUA_res->is_success) {
      	$gnGetDatRetVal1 = "gn200OK";
      }else{
      	if ($gnUA_res->code == "304") {
      	   $gnGetDatRetVal1 = "gn302NOCHANGE";
      	}else{
      	   $gnGetDatRetVal1 = "gn404NOTFOUND";
      	}
      }
   }else{
      $gnGetDatRetVal1 = "gn999NOFUNCTION";
   }
   #
   # ここから下の処理は将来的には別関数として処理
   #
   if($gnUA_res->is_success) {
       # datを取得したのでローカルに保存
       my $gnContEnc = $gnUA_res->header('Content-Encoding');
       if($gnDebugFlag) { print ("Content-Encoding\: $gnContEnc\n"); }
       
       
       
       my $gnContBuf = "";
       if ($gnContEnc) {
          if($gnContEnc eq "gzip") {
             $gnContBuf = Compress::Zlib::memGunzip($gnUA_res->content);
          }else{
          $gnContBuf = $gnUA_res->content;
          }
       }else{
          $gnContBuf = $gnUA_res->content;
       }
       my $gnResCode = $gnUA_res->code;
       switch ($gnResCode) {
          case "200" { # 全体を正常取得
                     my $gnModTimeStr = $gnUA_res->header('Last-Modified');
                     my $gnModTime = str2time($gnModTimeStr);
                     
                     open(gnDatF, ">$gnItaDatFN");
                     print gnDatF $gnContBuf;
                     close(gnDatF);
                     
                     # 更新日時はサーバ側の最終更新と同じにする
                     utime($gnModTime, $gnModTime, $gnItaDatFN);
                     &gnLogger("スレ全体を取得しました");
                     }
          case "206" { # 一部(更新された部分)を正常取得
                     my $gnModTimeStr = $gnUA_res->header('Last-Modified');
                     my $gnModTime = str2time($gnModTimeStr);
                     
                     open(gnDatF, ">>$gnItaDatFN");
                     print gnDatF $gnContBuf;
                     close(gnDatF);
                     
                     # 更新日時はサーバ側の最終更新と同じにする
                     my $gnModTime = str2time($gnModTimeStr);
                     &gnLogger("差分を取得しました");
                     }
               else {
                     &gnLogger("対応していないリターンコードです\($gnResCode\)");
                     }
       }

       my $gnNow = time();

   }else{
       &gnLogger("datの取得に失敗しました");
       my $gnResCode = $gnUA_res->code;
       switch ($gnResCode) {
          case "302" { # bbspinkだけ？notfond
                     &gnLogger("datファイルが存在しません\(code\:302\)");
                     }
          case "304" { # 更新なし
                     &gnLogger("スレは更新されていません\(code\:304\)");
                     }
          case "404" { # datがなかった
                     &gnLogger("datファイルが存在しません\(code\:404\)");
                     }
               else {
                     &gnLogger("対応していないリターンコードです\(code\:$gnResCode\)");
                     }
       }
   }

   return($gnGetDatRetVal1, $gnUA_res);
}

sub gnFolderIdxGetVal {
   # Folder.idx内の項目を返す関数
   #
   # 引数: 1: 文字列(Folder.idxへのフルパス)
   #       2: 文字列(xxxxxx.dat で取得する行を指定)
   #       3: 整数(0スタート。取得する列)
   # 返り値: 文字列(エラーの場合はNULLを返す)
   my $gnFIdxFN = $_[0];
   my $gnFIdxDatName = $_[1];
   my $gnFIdxCol = $_[2];
   my $retval = "";

   if (-e $gnFIdxFN) {
      open(gnFIdxFrom, "$gnFIdxFN");
      while(<gnFIdxFrom>) {
         my $buf = $_; chomp($buf);
         $buf = Encode::decode('shiftjis', $buf);
         
         my $isHit = index($buf, $gnFIdxDatName);
         if($isHit>0) {
            # 該当行
            my $separator = chr(001);
            my @colsbuf = split($separator, $buf);
            $retval = $colsbuf[$gnFIdxCol];
         }
      }
      close(gnFIdxFrom);
            
      &gnLogger("Folder.idx\($gnFIdxFN\)の値：$retval");
      return("$retval");
      
   }else{
      &gnLogger("Folder.idxが存在しません：$gnFIdxFN");
      return("");
   }
}

sub gnFolderIdxUpdate {
   # Folder.idx内の項目を更新する関数
   #
   # 引数: 1: 文字列(Folder.idxへのフルパス)
   #       2: 文字列(xxxxxx.dat で更新する行を指定)
   #       3: 整数(0スタート。更新する列)
   #       4: 文字列(更新する内容)
   # 返り値: TRUE/FALSE(TRUE=成功。FALSE=失敗)
   my $gnFIdxFN = $_[0];
   my $gnFIdxDatName = $_[1];
   my $gnFIdxCol = $_[2];
   my $gnFIdxUpdateBuf = $_[3];
   
   if (-e $gnFIdxFN) {
      my $gnFIdxTmpFN = $gnFIdxFN . &genRandStr("8");
      open(gnFIdxFrom, "$gnFIdxFN");
      open(gnFIdxTo, ">$gnFIdxTmpFN");
      while(<gnFIdxFrom>) {
         my $buf = $_; chomp($buf);
         $buf = Encode::decode('shiftjis', $buf);
         
         my $isHit = index($buf, $gnFIdxDatName);
         if($isHit<0) {
            # 該当行じゃない場合はそのままoutfileに書き出し
            my $outbuf = Encode::encode('shiftjis', $buf);
            print gnFIdxTo "$outbuf\n";
         }else{
            # 該当行
            my $separator = chr(001);
            my @colsbuf = split($separator, $buf);
            $colsbuf[$gnFIdxCol] = $gnFIdxUpdateBuf;
            
            my $outbuf = "";
            my $cnt = 0;
            foreach(@colsbuf) {
               $outbuf = $outbuf . $colsbuf[$cnt] . chr(001);
               $cnt++;
            }
            
            $outbuf = Encode::encode('shiftjis', $outbuf);
            print gnFIdxTo "$outbuf\n";

         }
      }
      close(gnFIdxTo);
      close(gnFIdxFrom);
      
      rename($gnFIdxTmpFN, $gnFIdxFN);
      
      if($gnDebugFlag) { print "Update Complete Folder.idx\: $gnFIdxFN\n"; }
      return(TRUE);
      
   }else{
      if($gnDebugFlag) { print "File miss\: $gnFIdxFN\n"; }
      return(FALSE);
   }
}

sub gnFolderIdxPush {
   # 指定したFolder.idxに指定したデータを追加する関数
   #
   # 引数: 1: 文字列(Folder.idxへのフルパス)
   #       2: arrayへのリファレンス(arrayにはFolder.idxの書式で入れるべきデータが1エントリ分入っている)
   # 返り値 : TRUE/FALSE(TRUE=成功、FALSE=失敗)
   my $idxfn = $_[0];
   my $varref = $_[1];
   my @var = @$varref;
   
   my $cnt = 0;
   if (-e $idxfn) {
      open(gnFIP, "$idxfn");
         while(<gnFIP>) {
            $cnt++;
         }
      close(gnFIP);
   }else{
      open(gnFIP, ">$idxfn");
         print gnFIP "$gnIdxFileVersion\n";
      close(gnFIP);
   }
   
   my $buf = sprintf("%X", $cnt);
   
   my $cnt2 = 0;
   foreach (@var) {
      $buf = $buf . chr(001) . $var[$cnt2];
      $cnt2++;
   }
   open(gnFIP, ">>$idxfn");
      print gnFIP "$buf\n";
   close(gnFIP);
   
   return(TRUE);
}

sub gnGetNowTime {
   # 現在の時刻を取得して文字列で返す関数(ログ記録用)
   # 引数: なし
   # 返り値: 文字列(YYYY/MM/DD HH:MM:SS)
   my ($ss, $mm, $hh, $DD, $MM, $YY, $WKD, $YD, $ID) = localtime(time);
   $MM++; $YY += 1900;
   my $retval = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $YY, $MM, $DD, $hh, $mm, $ss);
   return($retval);
}

sub gnLogger {
  # ログウィンドウに時刻をつけてログ表示する関数
  # 引数: 文字列(ログに表示する文字列)
  # 返り値: なし
  
  # 引数を取得
  my $gnLogStr = $_[0];
  
  # 現在の時刻を取得
  my $gnLogTime = &gnGetNowTime;
  
  # ログウィジェットを取得
  my $gnLogView = $gnGUIxml->get_widget('gnLogView');
  my $gnLV_buf = $gnLogView->get_buffer;
  my $gnLVB_iter = $gnLV_buf->get_end_iter;

  # ログ用文字列を生成
  $gnLogStr = $gnLogTime . " " . $gnLogStr . "\n";

  # ログを書き込み
  $gnLV_buf->insert($gnLVB_iter, $gnLogStr);

  # ログの末尾にスクロール
  my $gnLV_Sc = $gnGUIxml->get_widget('gnLogViewSW');
  my $gnLV_Sc_va = $gnLV_Sc->get_vadjustment;
  $gnLV_Sc_va->set_value($gnLV_Sc_va->upper);

}

sub gnParseSure {
   # ファイルから読み込まれたスレの内容を
   # 表示用に整形する。
   # 引数: 1: 文字列(スレのファイルパス)
   #       2: 文字列(スレのファイルパスから環境変数のパスを抜いたもの(ascii2d/xxxxxxxx.dat))
   # 返り値: 整形済みTextBuffer
   my $gnSureFN = $_[0];
   my $linktgt_base = $_[1];
   my ($linktgt_ita, $linktgt_sure) = split(/\//, $linktgt_base);
   my ($linktgt_sure2, $sute) = split(/\./, $linktgt_sure);

   my $gnTmpBuf;
   if ($gnSureFN) {
      open(gnSR, $gnSureFN);
        $gnTmpBuf = join '', <gnSR>;
     close(gnSR);
   }else{
      &gnLogger("【異常動作】スレの読み込みに失敗しました");
   }
       
   my @gnSureBuf = split(/\n/, $gnTmpBuf);
   
   # return用のTextBufferを作成。iterも作っておく
   my $rescnt = 1;
   my $gnSureTBuf = Gtk2::TextBuffer->new;
   &gnCreateTag($gnSureTBuf);
   my $gnSureTBuf_iter = $gnSureTBuf->get_start_iter;
   
   
   # 1レスづつ変換
   foreach(@gnSureBuf){
     # レス(1行)を読んで各要素に分割
     my $LineBuf = $_; chomp($LineBuf);
     # 内容をレンダリング
     &gnAppendRes($gnSureTBuf, $LineBuf, $linktgt_ita, $linktgt_sure2, $rescnt);
   
     # レス番カウンタをインクリメント
     $rescnt++;
   }
   
   return($gnSureTBuf);

}



sub gnResPopup {
   # レスポップアップ用関数
   # (レス内の">>1"にマウスを乗せた場合に出るポップアップウィンドウを表示する)
   # 引数: 文字列("<板フォルダ名>/<スレ番号>/<レス番号>")
   # 返り値: Gtk2::Window(実際に表示されているポップアップウィンドウ。
   #                      マウスが外れた際にはこれを使ってdestroyする)
   my $gnReplyTo = $_[0];
   
   # ポップアップウィンドウを取得する
   my $gnPopxml = Gtk2::GladeXML->new($gladefn, 'gnPopupText');
   $gnPopxml->signal_autoconnect_from_package('main');
   my $gnPopupWin = $gnPopxml->get_widget('gnPopupText');
   
   # TextViewウィジェットを取得する
   my $gnPT_Text = $gnPopxml->get_widget('gnPT_Text');
   my $gnPTT_buf = $gnPT_Text->get_buffer;
   my $gnPTTbuf_iter = $gnPTT_buf->get_end_iter;

   # 該当レスを書き込み
   $gnPTT_buf = &gnGetResNo($gnReplyTo, $gnPTT_buf);
   

   # ポップアップウィンドウを表示
   $gnPopupWin->show_all;
   $gnPT_Text->set_buffer($gnPTT_buf);


   return($gnPopupWin);

}

sub gnImgPopup2 {
   # 画像プレビューポップアップ用関数(プログレッシブダウンロード対応版)
   # (レス内の画像と判断できるURLにマウスを乗せたときにポップアップウィンドウを表示する)
   #
   # 引数: 文字列(画像へのURL)
   # 返り値: 1:Gtk2::Window(実際には表示されているポップアップウィンドウ。
   #                        マウスが外れたときにはこれを使ってdestoryする)
   #         2: Gtk2::Image(画像の更新用)
   my $gnURLTo = $_[0];
   my $gnResizedBuf;
   my $gnResizedWidth;
   my $gnResizedHeight;

   my $gnPopxml = Gtk2::GladeXML->new($gladefn, 'gnPopupImg');
   $gnPopxml->signal_autoconnect_from_package('main');
   my $gnPopupWin = $gnPopxml->get_widget('gnPopupImg');
   my $gnPI_Image = $gnPopxml->get_widget('gnPI_Img');

   if($gnDebugFlag) { print "\(start\)gnImgPopup2\n"; }
   # キャッシュテーブルをチェック
   (my $gnImgCacheHitFLG, $gnTmpfn) = &gnImgCacheQuery($gnURLTo);
   my $gnPopupSize = $gnEnvArg{'Browser'}{'PreviewSize'} . "00";
   
   if ($gnImgCacheHitFLG == TRUE) {
      # キャッシュヒットしていたら
      if($gnDebugFlag) { print "\(start\)gnImgPopup2-cachehit\n"; }
      if (-e $gnTmpfn) {
         eval { $gnPopupImgBuf = Gtk2::Gdk::Pixbuf->new_from_file_at_scale($gnTmpfn,$gnPopupSize,
                                                                        $gnPopupSize,TRUE); };
         if ($@) {
            $gnPI_Image->set_from_stock('gtk-missing-image', 'GTK_ICON_SIZE_DIALOG');
            ($gnResizedWidth, $gnResizedHeight) = ($gnPopupSize, $gnPopupSize);
         }else{
            $gnPI_Image->set_from_pixbuf($gnPopupImgBuf);
            $gnResizedWidth = $gnPopupImgBuf->get_width;
            $gnResizedHeight = $gnPopupImgBuf->get_height;
         }
      }else{
         $gnPI_Image->set_from_stock('gtk-missing-image', 'GTK_ICON_SIZE_DIALOG');
         ($gnResizedWidth, $gnResizedHeight) = ($gnPopupSize, $gnPopupSize);
      }
   }else{
      # ヒットしていなかったら仮のウィンドウを生成
      if($gnDebugFlag) { print "\(start\)gnImgPopup2-cachemiss\n"; }
      $gnPI_Image->set_from_stock('gtk-refresh', 'GTK_ICON_SIZE_DIALOG');
      ($gnResizedWidth, $gnResizedHeight) = ($gnPopupSize, $gnPopupSize);
   }
         $gnPopupWin->set_size_request($gnResizedWidth, $gnResizedHeight);
   
   $gnPopupWin->show_all;
   
   
   # ポップアップ画面がウィンドウからはみ出している場合は
   # 出現位置を調整
   my $gnScreenHeight = Gtk2::Gdk->screen_height;
   my $gnScreenWidth = Gtk2::Gdk->screen_width;
   my ($gnPopupWinX, $gnPopupWinY) = $gnPopupWin->get_position;
   if($gnDebugFlag) { print "gnImgPopup2-regeo\:\($gnPopupWinX, $gnPopupWinY\)\n"; }
   if (($gnPopupWinX + $gnResizedWidth) > $gnScreenWidth) { $gnPopupWinX = $gnScreenWidth - $gnResizedWidth; }
   if (($gnPopupWinY + $gnResizedHeight) > $gnScreenHeight) { $gnPopupWinY = $gnScreenHeight - $gnResizedHeight; }
   $gnPopupWin->move($gnPopupWinX, $gnPopupWinY);
   if($gnDebugFlag) { print "gnImgPopup2-regeo\:\($gnPopupWinX, $gnPopupWinY\)\n"; }

   # キャッシュにヒットしていない場合は
   #   1. HTTPリクエストを別スレッドにして呼び出す
   #      別スレッドではURLからデータを取り込みキャッシュファイルに書き込んでいく
   if ($gnImgCacheHitFLG == FALSE) {
      $hold = 1;
      $gnTmpfn = $tmpdir . "/gnTmp" . &genRandStr(12);
      my @val = ("geturl", $gnURLTo, $gnTmpfn);
      if($gnDebugFlag) { print "\(start\)gnImgPopup2-enqueue\n"; }
      $queue->enqueue("geturl", $gnURLTo, $gnTmpfn);
      $gnCacheTbl{'Cache'}{$gnURLTo} = time . chr(001) . $gnTmpfn;

   }


   if($gnDebugFlag) { print "\(end\)gnImgPopup2\n"; }
   my @retval = ($gnPopupWin, $gnPI_Image);
   return(\@retval);
}

sub gnImgBufferUpdate {
   # ポップアップ用画像バッファを定期的にアップデートする
   #
   # 引数:   1: Gtk2::Window(更新対象のポップアップウィンドウ)
   #         2: Gtk2::Image(更新対象のImageオブジェクト)
   # 返り値: 1:Gtk2::Window(実際には表示されているポップアップウィンドウ。
   #                        マウスが外れたときにはこれを使ってdestoryする)
   #         2: Gtk2::Image(画像の更新用)
   #         3: TRUE/FALSE(TRUE=画像はダウンロード中, FALSE=画像のダウンロードは終了している)

   my $gnPopupWin =$_[0];
   my $gnPI_Image = $_[1];
   my $gnResizedWidth;
   my $gnResizedHeight;
   my $pointx = -1;
   my $pointy = -1;
   
   if($gnDebugFlag) { print "gnImgBufferUpdate-gnpopupwin\:" . Dumper($gnPopupWin) . "\n"; }
   if($gnDebugFlag) { print "gnImgBufferUpdate-gnPI_Image\:" . Dumper($gnPI_Image) . "\n"; }
   my $gnTmpImgfn = $gnTmpfn;
   my $gnTmpImgEstSz = $fnsize;
   if($gnDebugFlag) { print "gnImgBufferUpdate-estsize\:" . Dumper($gnTmpImgEstSz) . "\n"; }
   my ($gnPopupWinX, $gnPopupWinY) = $gnPopupWin->get_position;
   if($gnDebugFlag) { print "gnImgBufferUpdate-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)\n"; }
   
   my $gnPopupSize = $gnEnvArg{'Browser'}{'PreviewSize'} . "00";
   my $gnPopupImgBuf2;
   if ($gnTmpImgfn) {
      eval { $gnPopupImgBuf2 = Gtk2::Gdk::Pixbuf->new_from_file_at_scale($gnTmpImgfn,$gnPopupSize,
                                                                  $gnPopupSize,TRUE); };
      if ($@) {
         if ($gnImgGetSt) {
            $gnPI_Image->set_from_stock('gtk-refresh', 'GTK_ICON_SIZE_DIALOG');
         }else{
            $gnPI_Image->set_from_stock('gtk-missing-image', 'GTK_ICON_SIZE_DIALOG');
         }
         ($gnResizedWidth, $gnResizedHeight) = ($gnPopupSize, $gnPopupSize);
      }else{
         if($gnDebugFlag) { print "gnImgBufferUpdate-pixbuf\:" . Dumper($gnPopupImgBuf2) .
                                        "\n" . Dumper($gnPI_Image) . "\n"; }
         $gnPI_Image->set_from_pixbuf($gnPopupImgBuf2);
         $gnResizedWidth = $gnPopupImgBuf2->get_width;
         $gnResizedHeight = $gnPopupImgBuf2->get_height;
      }
   }else{
      if ($gnImgGetSt) {
         $gnPI_Image->set_from_stock('gtk-refresh', 'GTK_ICON_SIZE_DIALOG');
      }else{
         $gnPI_Image->set_from_stock('gtk-missing-image', 'GTK_ICON_SIZE_DIALOG');
      }
      ($gnResizedWidth, $gnResizedHeight) = ($gnPopupSize, $gnPopupSize);
   }
      $gnPopupWin->set_size_request($gnResizedWidth, $gnResizedHeight);
      $gnPopupWin->resize($gnResizedWidth, $gnResizedHeight);
      
      my $dis = Gtk2::Gdk::Display->get_default;
      my ($scr, $x, $y, $mask) = $dis->get_pointer;
      my ($wid, $hei) = $gnPopupWin->get_size;
      if (($x + int($wid/2)) >= Gtk2::Gdk->screen_width) { $gnPopupWinX = $x + int($wid/2); }
      if (($y + int($hei/2)) >= Gtk2::Gdk->screen_width) { $gnPopupWinY = $y + int($hei/2); }
      
      # ポップアップ画面がウィンドウからはみ出している場合は
      # 出現位置を調整
      
      if ($gnResizedWidth > 0) {
         my $gnScreenHeight = Gtk2::Gdk->screen_height;
         my $gnScreenWidth = Gtk2::Gdk->screen_width;
         if($gnDebugFlag) { print "gnImgBufferUpdate-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)\n"; }
         if($gnDebugFlag) { print "gnImgBufferUpdate-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)\n"; }
         if (($gnPopupWinX + $gnResizedWidth) > $gnScreenWidth) { $gnPopupWinX = $gnScreenWidth - $gnResizedWidth; }
         if (($gnPopupWinY + $gnResizedHeight) > $gnScreenHeight) { $gnPopupWinY = $gnScreenHeight - $gnResizedHeight; }
         $gnPopupWin->move($gnPopupWinX, $gnPopupWinY);
         if($gnDebugFlag) { print "gnImgBufferUpdate-regeo\:\($gnPopupWinX, $gnPopupWinY, $gnResizedWidth, $gnResizedHeight\)"; }

      }
   
   undef($gnPopupImgBuf2);
   
   my @retval = ($gnPopupWin, $gnPI_Image, $gnImgGetSt);
   return(\@retval);
}

sub gnGetResNo {
  # ポップアップウィンドウ用レス取得関数
  # ポップアップ内に表示するレスの内容を取得する
  # 引数１：文字列("<板フォルダ名>/<スレ番号>/<レス番号>")
  # 　　２：GtkTextBuffer(ポップアップウィンドウ内のTextViewにはめこむもの)
  # 返り値：GtkTextBuffer(引数２で受け取ったものにレスが入ったもの)
  
  my $gnResNoInfo = $_[0];
  if ($gnDebugFlag) { print "$gnResNoInfo\n"; }
  my $gnRNI_TextBuf = $_[1];

  my $gnRNI_return = "";
  my $gnRNI_from = 0;
  my $gnRNI_to = 0;
  
  my @gnRNI_info = split(/\//, $gnResNoInfo);
      # index: 0 = 板フォルダ名, 1 = スレ番号, 2 = レス番号
  if($gnRNI_info[2] =~ /-/) {
    ($gnRNI_from, $gnRNI_to) = split(/-/, $gnRNI_info[2]);
    if ($gnRNI_from > $gnRNI_to) {
       my $tmp = $gnRNI_from;
       $gnRNI_from = $gnRNI_to;
       $gnRNI_to = $tmp;
    }
  }else{
    $gnRNI_from = $gnRNI_info[2];
    $gnRNI_to = $gnRNI_info[2];
  }
  my $gnRNI_fn = $logdir.$gnRNI_info[0]."\/".$gnRNI_info[1].".dat";
  
  if (-e $gnRNI_fn) {
  open(gnRNI, $gnRNI_fn) or die "cannot read $gnRNI_fn";
     my $gnRNI_cnt = 1;
     while(<gnRNI>) {
        my $gnRNI_RepBuf = $_;
        if(($gnRNI_cnt >= $gnRNI_from) & ($gnRNI_cnt <= $gnRNI_to)) {
           chomp($gnRNI_RepBuf);
           $gnRNI_TextBuf = &gnAppendRes($gnRNI_TextBuf, $gnRNI_RepBuf,
                                             $gnRNI_info[0], $gnRNI_info[1], $gnRNI_cnt);

           $gnRNI_return = "found";
        }
        $gnRNI_cnt++;
     }
  close(gnRNI);
  }else{
     $gnRNI_return = "";
  }

  if($gnRNI_return eq "") {
     &gnLogger("該当の番号がありませんでした");
  };
  return($gnRNI_TextBuf);

}

sub gnCreateTag {
   # レス表示用にGtk2::TextTagを定義する
   # 引数: Gtk2::TextBuffer(レス表示用のTextBufer)
   # 返り値: なし
   
   # 引数を取得
   my $gnBuffer = shift;
   my $gnBuffer_tagtable = $gnBuffer->get_tag_table;
   
   if(!($gnBuffer_tagtable->lookup("gn-link"))) {
      $gnBuffer->create_tag("gn-link", foreground => "blue",
                                       underline => 'single');
   }
   if(!($gnBuffer_tagtable->lookup("gn-linkname"))) {
   $gnBuffer->create_tag("gn-linkname", foreground => "blue",
                                        underline => 'single',
                                        weight => PANGO_WEIGHT_BOLD);
   }
   if(!($gnBuffer_tagtable->lookup("gn-name"))) {
   $gnBuffer->create_tag("gn-name", foreground => "#05B705",
                                    weight => PANGO_WEIGHT_BOLD);
   }
   if(!($gnBuffer_tagtable->lookup("gn-mail"))) {
   $gnBuffer->create_tag("gn-mail", foreground => "blue",
                                    weight => PANGO_WEIGHT_BOLD);
   }
   if(!($gnBuffer_tagtable->lookup("gn-cont-normal"))) {
   $gnBuffer->create_tag("gn-cont-normal", left_margin => 25,
                                           pixels_above_lines => 2);
   }
   if(!($gnBuffer_tagtable->lookup("gn-cont-link"))) {
   $gnBuffer->create_tag("gn-cont-link", foreground => "blue",
                                         underline => 'single',
                                         left_margin => 25,
                                         pixels_above_lines => 2);
   }
}

sub gnCreateResLinkTag {
  # レス内のコメントリンク(例えばレスのコメントが「>>1乙」であった場合の">>1"へのリンク)を
  # 動的に生成する
  # 引数: 1: Gtk2::TextBuffer(タグ対象のTextBuffer)
  #       2: 文字列(リンク先(板名/スレID/コメント番号)
  # 返り値: Gtk2::TextTag
  
  # 引数を取得
  my $txtbuf = $_[0];
  my $restgt = $_[1];
  
  # TextTagを作成
  my $gnResLinkTag = $txtbuf->create_tag(undef,
                                         foreground => "blue",
                                         underline => 'single',
                                         left_margin => 25,
                                         pixels_above_lines => 2);
  $gnResLinkTag->{page} = $restgt;
  
  return($gnResLinkTag);
}

sub gnCreateNoLinkTag {
  # レス番号へポップアップメニュー用リンクを
  # 動的に生成する
  # 引数: 1: Gtk2::TextBuffer(タグ対象のTextBuffer)
  #       2: 文字列(リンク先(板名/スレID/コメント番号)
  # 返り値: Gtk2::TextTag
  
  # 引数を取得
  my $txtbuf = $_[0];
  my $restgt = $_[1];
  
  # TextTagを作成
  my $gnResLinkTag = $txtbuf->create_tag(undef,
                                         foreground => "blue",
                                         underline => 'single',
                                         pixels_above_lines => 2,
                                         left_margin => 0);
  $gnResLinkTag->{page} = $restgt;
  
  return($gnResLinkTag);
}

sub gnIsURL {
   # 文字列がURLかどうかを返す関数
   # http://www.din.or.jp/~ohzaki/perl.htm#httpURL を使用
   # ありがとうございます
   # 引数: 文字列
   # 返り値: 1: Boolean (URLを含む場合はTrue(1), 含まなければFalse(0))
   #         2: 文字列 (含まれるURL(http://が補完されている))
   #         3: 文字列 (hitした前の文字列)
   #         4: 文字列 (hitした文字列(ttp://...... で抽出している))
   #         5: 文字列 (hitした後の文字列)
   
   my $url = $_[0];
   my $isurl = FALSE;
   my $urltgt = '';
   my $strBefore = '';
   my $strHit = '';
   my $strAfter = '';
   
   my $http_URL_regex = q{ttp(?:[s])*://(?:(?:[-_.!~*'()a-zA-Z0-9;:&=+$,]|%[0-9A-Fa-f]} .		# 'gedit誤認識対策用コメント
                        q{[0-9A-Fa-f])*@)?(?:(?:[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.)} .
                        q{*[a-zA-Z](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.?|[0-9]+\.[0-9]+\.[0-9]+\.} .
                        q{[0-9]+)(?::[0-9]*)?(?:/(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f]} .
                        q{[0-9A-Fa-f])*(?:;(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-} .
                        q{Fa-f])*)*(?:/(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f} .
                        q{])*(?:;(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*)*)} .
                        q{*)?(?:\?(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])} .
                        q{*)?(?:#(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*} .
                        q{)?};
   my $pattern = $http_URL_regex;
   
   if($url =~ /$pattern/) {
      $url =~ /$pattern/;
      ($strBefore, $strHit, $strAfter) = ($`, $&, $'); # ` <--gedit誤認識対策用コメント
      $urltgt = "h" . $strHit;
      $isurl = TRUE;
   }
   
   return(($isurl, $urltgt, $strBefore, $strHit, $strAfter));

}

sub gnGetlinktgt {
  # 2ch仕様変更吸収用関数
  # 参照先コメント番号を指すURLの違いによって抽出方法をわける
  # 古い方：../test/read.cgi?bbs=unix&key=985444963&st=3&to=3&nofirst=true
  # 新しい方：../test/read.cgi/unix/985444963/303
  # 引数：文字列(URLの中のread.cgiより後の部分。古い仕様の場合は"?bbs=unix&key=xxxxx"、
  #             新しい仕様の場合は"/unix/xxxxxx"という文字列が与えられている)
  # 返り値：文字列(板名/スレID/コメント番号)
  my $gnBufURL = $_[0];
  
  if (!($gnBufURL)) { return(NULL); }
  
  my $flg = substr($gnBufURL,0,1);
  if($flg eq "\?") {
     my ($bbs, $key, $st, $to, $nofirst) = split(/\&/, substr($gnBufURL,1));
     my ($bbs_k, $bbs_v) = split(/=/, $bbs);
     my ($key_k, $key_v) = split(/=/, $key);
     my ($st_k, $st_v) = split(/=/, $st);
     
     my $retval = $bbs_v . "\/" . $key_v . "\/" . $st_v;
     return($retval);
  }else{
     if ($gnBufURL) {
        my $retval = substr($gnBufURL, 1);
        return($retval);
     }else{
        return(NULL);
     }
  }
} 

sub gnGetItaIchiran {
   # 指定した場所から2ch板一覧を取得してローカルテーブルをアップデートする関数
   # 引数：なし
   # 返り値：Integer(-1=成功
   #                  0=失敗：ネットワークがタイムアウト
   #                  1=失敗：ローカルファイルに書き込み失敗)
   my $gnURLNo = $gnURLIni{'URL'}{'selected'};
   my $gnURLTo = $gnURLIni{'URL'}{$gnURLNo};
   if($gnDebugFlag) { print "$gnURLNo\n"; }
   if($gnDebugFlag) { print "$gnURLTo\n"; }
   
   my $gnUA = LWP::UserAgent->new;
   $gnUA->agent($gnUsrAgent);
   my $gnUA_res = $gnUA->get($gnURLTo);
   if($gnDebugFlag) { print Dumper($gnUA_res->code) . "\n"; }
   
   if($gnUA_res->is_success) {
      # 板一覧を取得したので展開
      my $gnItaTmpBuf = $gnUA_res->content;
      
      my $gnTmpCatIdx = 0; # カテゴリ順をカウント
      my $gnTmpItaIdx = 0; # 板をカウント
      my $gnTmpPos = "";   # 現在位置("category" もしくは "ita")
      my $gnItaURL = "";   # 板のURL
      my $p = HTML::TokeParser->new(\$gnItaTmpBuf);
      
      open(gnFItaIdx, ">$boardcfg");
      
      while (my $token = $p->get_token) {
         my $flg = $token->[0];
         my $tag = $token->[1];
            
            # bタグがあったらカテゴリカウントをインクリメント
            # 参照先：http://info.2ch.net/wiki/pukiwiki.php?monazilla%2Fdevelop%2Fboardlist
            if(($flg eq "S") && ($tag eq "b")) { $gnTmpCatIdx++;}

            # 1回目のbタグでカテゴリに入ったこととして、データを作り始める
            if ($gnTmpCatIdx > 0) {
            
               if(($flg eq "S") && ($tag eq "b")) { $gnTmpPos = "category"; $gnTmpItaIdx = 0; }
               if(($flg eq "E") && ($tag eq "b")) { $gnTmpPos = "other"; }
               if(($flg eq "S") && ($tag eq "a")) { $gnTmpPos = "ita";
                                                      $gnTmpItaIdx++;
                                                      $gnItaURL = $token->[2]->{'href'};}
               if(($flg eq "E") && ($tag eq "a")) { $gnTmpPos = "other"; }
               
               if($flg eq "T") {
                  my $gnPrintBuf = "";
                  if($gnTmpPos eq "category") {
                     $gnPrintBuf = $gnTmpCatIdx . "-0::" . $tag . "\n";
                  }
                  if($gnTmpPos eq "ita") {
                     $gnPrintBuf = $gnTmpCatIdx . "-" . $gnTmpItaIdx . "::" . $tag . "::" . $gnItaURL . "\n";
                  }
                  
                  $gnPrintBuf = Encode::decode('shiftjis', $gnPrintBuf);
                  print gnFItaIdx $gnPrintBuf;
               }

               
            }
      }
      
      close(gnFItaIdx);
      
      #板一覧を更新したらTreeViewウィジェットの内容をアップデートする
      my $gnItaStore = &gnMakeItaTreeStore($boardcfg);
      my $gnItaView = $gnGUIxml->get_widget('gnII');
      $gnItaView->set_model($gnItaStore);
      my $gnIS_pos = Gtk2::TreePath->new_from_indices(0);
      $gnItaView->expand_row($gnIS_pos, FALSE);

      &gnLogger("板一覧を更新しました");
      return(TRUE);
   }else{
      &gnLogger("板一覧取得失敗");
      return(FALSE);
   }
}

sub gnAppendRes {
   # dat1行分のコメント文字列を整形したTextBufferに変換し、与えられた
   # TextBufferの末尾に追加する
   # スレ表示、ポップアップ表示に使用
   # 引数１：Gtk2::TextBuffer (変更するTextBuffer)
   # 引数２：文字列 (追加するコメントのデータ。１行(１コメント)分。
   #                 ファイルから取得されたままの状態で、ShiftJIS+UTFフラグ無しであることを
   #                 想定する。つまりEncode::decodeはこの関数の内部で行う)
   # 引数３：文字列 (板名。板のディレクトリ名)
   # 引数４：文字列 (スレID.datの.datをとったもの)
   # 引数５：文字列 (レス番号)
 
   my $gnAR_TextBuf = $_[0];
      # iter を最後に
      my $gnAR_TextBuf_iter = $gnAR_TextBuf->get_end_iter;
      # TextTagが定義されていなかったら作成
      my $gnAR_TextBuf_tagtable = $gnAR_TextBuf->get_tag_table;
      if(!($gnAR_TextBuf_tagtable->lookup("gn_name"))) {
         &gnCreateTag($gnAR_TextBuf);
      }

   my $gnAR_RepBuf = Encode::decode('shiftjis', $_[1], Encode::FB_DEFAULT);
      $gnAR_RepBuf =~ s/\r//;

   my @gnAR_RepBuf = split(/<>/, $gnAR_RepBuf);
        # index: 0 = 投稿者名, 1 = メールアドレス, 2 = 投稿日時, 3 = 投稿内容, 4 = null
   my @gnAR_info = ($_[2], $_[3], $_[4]);
   
   # ヘッダ(レス番、名前、日付、ID)を書く
   # レス番
   my $linktgt = "resno://" . $gnAR_info[0] . "/" . $gnAR_info[1] . "/" . $gnAR_info[2];
   if(!($gnAR_TextBuf_tagtable->lookup($linktgt))) {
   $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $gnAR_info[2], &gnCreateNoLinkTag($gnAR_TextBuf, $linktgt));
   }else{
   $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $gnAR_info[2], $gnAR_TextBuf_tagtable->lookup($linktgt));
   }
   # 名前
   $gnAR_TextBuf->insert($gnAR_TextBuf_iter, " 名前：");
   if($gnAR_RepBuf[1] eq '') {
      $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $gnAR_RepBuf[0], "gn-name");
   }else{
      $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $gnAR_RepBuf[0], "gn-linkname");
      $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, " [", "gn-mail");
      $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $gnAR_RepBuf[1], "gn-mail");
      $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, "]", "gn-mail");
   }
   # 投稿日
   $gnAR_TextBuf->insert($gnAR_TextBuf_iter, "  投稿日：");
   $gnAR_TextBuf->insert($gnAR_TextBuf_iter, $gnAR_RepBuf[2]);
   $gnAR_TextBuf->insert($gnAR_TextBuf_iter, "\n");

   # 本文
   $gnAR_RepBuf[3] =~ s/&gt;/>/g;
   $gnAR_RepBuf[3] =~ s/&lt;/</g;

     # TokeParserの初期化
     my $p = HTML::TokeParser->new(\$gnAR_RepBuf[3]);

     # Token毎に処理を行う
     while(my $token = $p->get_token) {
       my $flg = $token->[0];
       my $tag = $token->[1];
       if(($flg eq "S") && ($tag eq "a")) {
            # <a>のタグ内に入ったら$tinflgをTRUEにして、targetのURLを取り込む
            # タグ自体は出力しない
#            my($buf1, $linktgt) =  split(/read\.cgi\//, $token->[2]->{'href'});
            my($buf1, $linktgt) =  split(/read\.cgi/, $token->[2]->{'href'});
            $linktgt = &gnGetlinktgt($linktgt); #read.cgiの仕様違いを吸収
            $linktgt = "replyto://" . $linktgt;
            if(!($gnAR_TextBuf_tagtable->lookup($linktgt))) {
               $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $p->get_text, &gnCreateResLinkTag($gnAR_TextBuf, $linktgt));
            }else{
               $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $p->get_text, $gnAR_TextBuf_tagtable->lookup($linktgt));
            }
       }
       if(($flg eq "S") && ($tag eq "br")) {
            # 改行
            $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, "\n", "gn-cont-normal");
       }
       if(($flg eq "E") && ($tag ne "br") && ($tag ne "a")) {
          # </a>, </br>(実際には<br>)以外のタグはそのまま記述
          # (レスの中には<チラシの裏>....</チラシの裏>とかあるし)
          $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $token->[2], "gn-cont-normal");
       }
       if(($flg eq "S") && ($tag ne "a") && ($tag ne "br")) {
          # <a>以外のタグはそのまま記述
          # (レスの中には<チラシの裏>....</チラシの裏>とかあるし)
          $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $token->[4], "gn-cont-normal");
       }
       if($flg eq "T") {
          my $str = $token->[1];
          $str = substr($str, 1); # 先頭の空白を削除

          # URLを含むかどうかを判断する
          my ($urlflg, $linktgt, $strBefore, $strHit, $strAfter) = &gnIsURL($str);

          if($urlflg) {
             # URLが含まれていたらリンクタグを生成し、
             # タグ付書式で描画する
             
             if (index($linktgt, "read\.cgi") > -1) {
                $linktgt = "datl\:\/\/" . $linktgt;
             }
             $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $strBefore, "gn-cont-normal");
             if(!($gnAR_TextBuf_tagtable->lookup($linktgt))) {
                $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $strHit,
                                                     &gnCreateResLinkTag($gnAR_TextBuf, $linktgt));
             }else{
                $gnAR_TextBuf->insert_with_tags($gnAR_TextBuf_iter, $strHit,
                                                    $gnAR_TextBuf_tagtable->lookup($linktgt));
             }
             $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, $strAfter, "gn-cont-normal");
          }else{
             # URLが含まれていなければ普通に描画
             $gnAR_TextBuf->insert_with_tags_by_name($gnAR_TextBuf_iter, decode_entities($str), "gn-cont-normal");
          }
       }
     }
     
     # 最後にレス間に改行を入れる(いちおうギコナビのやりかたに合わせ)
     $gnAR_TextBuf->insert($gnAR_TextBuf_iter, "\n\n");
     
     return($gnAR_TextBuf);

}


sub gnImgCacheQuery {
   # 指定されたURLがキャッシュテーブル上にあるかどうか確認する関数
   #
   # 引数: 文字列(対象のURL)
   # 返り値: 1: TRUE/FALSE(TRUE=キャッシュヒット,FALSE=キャッシュにはない)
   #         2: 返り値1がTRUEだった場合、キャッシュファイルへのフルパス
   #                    FALSEだった場合、NULL
   
   if ($gnEnvArg{'Cache'}{'Enable'} == FALSE) {
      return(FALSE, "");
   }
   
   my $gnQURL = $_[0];
   my $gnQResult;
   my $gnQFN;
   my $gnQTime;
   my $gnSeparator; $gnSeparator = chr(001);
   
   if ($gnCacheTbl{'Cache'}{$gnQURL}) {
      ($gnQTime, $gnQFN) = split(/$gnSeparator/, $gnCacheTbl{'Cache'}{$gnQURL});
   }
   if($gnDebugFlag) { print "cachehit\?:\n" . Dumper(($gnQTime, $gnQFN)); }
   if ($gnQTime) {
      $gnQTime += ($gnEnvArg{'Cache'}{'Expire'} * 86400);
      if ($gnQTime < time) {
         if($gnDebugFlag) { print "cache-expirerd:" . Dumper(($gnQTime, $gnQFN)); }
         $gnCacheTbl{'Cache'}{$gnQURL} = "-1" . chr(001) . "";
         unlink($gnQFN);
         &gnLogger("キャッシュが期限切れになったので再取得します");
         return(FALSE, "");
      }else{
         if($gnDebugFlag) { print "cache-hit:" . Dumper(($gnQTime, $gnQFN)); }
         &gnLogger("キャッシュされたファイルを表示します");
         return(TRUE, $gnQFN);
      }
   }else{
      if($gnDebugFlag) { print "cache-no-entry:" . Dumper(($gnQTime, $gnQFN)); }
      &gnLogger("キャッシュにないので取得します");
      return(FALSE, "");
   }
   
}

sub gnClearAllCache {
   # 全てのキャッシュファイルを削除しテーブルをクリアする
   #
   # 引数: なし
   # 返り値: なし
   my @gnCT_entry = keys(%{$gnCacheTbl{'Cache'}});
   
   foreach my $val(@gnCT_entry) {
      my $gnSplitStr = chr(001);
      my ($gnCTTime, $gnCTFN) = split(/$gnSplitStr/, $gnCacheTbl{'Cache'}{$val});
      if ($gnCTTime > -1) {
         unlink($gnCTFN);
      }
   }
   
   %gnCacheTbl = "";
   $gnCacheTbl{'Cache'}{'init'} = "-1" . chr(001) . "";
}

sub gnClearExpiredCache {
   # 期限切れのキャッシュエントリをクリアする
   #
   # 引数: なし
   # 返り値: なし
   my %gnCacheTblN;
   my @gnCT_entry = keys(%{$gnCacheTbl{'Cache'}});
   
   foreach my $val(@gnCT_entry) {
      my $gnSplitStr = chr(001);
      my ($gnCTTime, $gnCTFN) = split(/$gnSplitStr/, $gnCacheTbl{'Cache'}{$val});
      if ($gnCTTime > -1) {
         $gnCacheTblN{'Cache'}{$val} = $gnCacheTbl{'Cache'}{$val};
      }
   }
   
   %gnCacheTbl = %gnCacheTblN;
}

#--------------------------------------
# カスタム関数(OS依存、文字エンコードなど)
#--------------------------------------

sub env_gtkstr_to_gtkstr {
    # gtk2-perlだけ(かつLinuxだけ?)の問題?
    # Gtk2ウィジェットから取った文字列をそのままGtk2ウィジェットにあたえると
    # 2バイト部分がなくなってしまう問題への対処
    # 引数=文字列(utf8エンコード、utf8フラグ付きを期待)
    my $mySrcStr = $_[0];
    my $myDstStr = '';
       $mySrcStr = Encode::encode(guess_encoding($mySrcStr, qw/ euc-jp shiftjis 7bit-jis /), $mySrcStr);
       $myDstStr = Encode::decode(guess_encoding($mySrcStr, qw/ euc-jp shiftjis 7bit-jis /), $mySrcStr);
       
    return($myDstStr);

}

sub env_locale_to_encstr {
    # ロケール名からEncodeモジュールが解釈できる
    # 文字エンコード名を返す関数
    # 引数=文字列(ロケール名)
    
    # ロケール名を取得
    my $myLocVal = $_[0];

    # Windowsでは"Japanese_Japan"という文字列が使われているので
    # 一括変換しやすいように"ja_JPと置換"
    $myLocVal =~ s/Japanese_Japan/ja_JP/g;
    
    # "ja_JP.eucJP"の"ja_JP"部はいらないので切り取る
    my @myLocStr  = split(/\./, $myLocVal);

    # 後ろの"eucJP"の部分は小文字に置き換える
    # (実際にも大文字・小文字を区別しないよう推奨されている)
    $myLocStr[1] = lc($myLocStr[1]);
    
    # あとはパターンマッチ
    switch($myLocStr[1]) {
      case "932"   { return('shiftjis'); }
      case "pck"   { return('shiftjis'); }
      case "sjis"  { return('shiftjis'); }
      case "20932" { return('euc-jp'); }
      case "eucjp" { return('euc-jp'); }
      case "euc"   { return('euc-jp'); }
      case "ujis"  { return('euc-jp'); }
      case "iso-2022-jp" { return('7bit-jis'); }
      case "utf-8" { return('utf8'); }
      else { return('ascii'); }  # エンコードが判別できなかったらシングルバイトエンコード(単なるバイト列)として解釈する
    }
    
}

sub env_FNconv {
   # OS名からopenに渡すファイル名の文字コードを簡易的に変換するルーチン
   # (読み出し専用？実装依存対策???)
   # 引数はフルパスのファイル名を想定(文字列であればなんでもよい)
   
   my $myenc_os = 'utf8';
   my $myFNorg = $_[0];
   
   if($^O eq 'MSWin32') {
      # "perl -V"の"osname"がMSWin32の場合は
      # 実行環境がWindowsであると判断し、ロケールから文字エンコードを判別
      # エンコード名に従って文字列を変換する
      # Windows以外(Linuxを想定)の場合はUTF-8の文字列を
      # open関数が受け付けるので、変換しない
      $myenc_os = &env_locale_to_encstr(setlocale(LC_CTYPE));
      $myFNorg = Encode::encode($myenc_os, $myFNorg);
   }
   
   return $myFNorg;

}
sub gnGetLibPath {
   # ライブラリパスをチェック
   #
   # 引数:      なし
   # 返り値: 1: ライブラリへのフルパス
   
   my $gnLibPath;
   if ($^O eq "MSWin32") {
      # "perl -V"の"osname"がMSWin32の場合は
      # 実行環境がWindowsであると判断
      $gnLibPath = "$ENV{'APPDATA'}" . "\\gnview\\";
   }else{
      $gnLibPath = "\/usr\/share\/gnview\/";
   }
      return($gnLibPath);
   
}

sub gnGetPersonalCfgPath {
   # 設定ファイルパスをチェック
   #
   # 引数:      なし
   # 返り値: 1: 設定ファイルのフルパス
   
   my $gnCfgPath;
   if ($^O eq "MSWin32") {
      # "perl -V"の"osname"がMSWin32の場合は
      # 実行環境がWindowsであると判断
      $gnCfgPath = "$ENV{'APPDATA'}" . "\\gnview\\";
   }else{
      $gnCfgPath = "$ENV{'HOME'}" . "\/.gnview\/";
   }
      return($gnCfgPath);
   
}


sub gnGetTmpDir {
   # テンポラリディレクトリ取得用関数
   # Windowsでは環境変数から取得。それ以外(Linux/*NIXを想定)では決め打ちで/tmpを使用
   # 引数：なし
   # 返り値：文字列(ディレクトリのフルパス)
   
   if($^O eq 'MSWin32') {
      # "perl -V"の"osname"がMSWin32の場合は
      # 実行環境がWindowsであると判断し、環境変数TEMPからテンポラリディレクトリを取得
      # Windows以外(Linuxを想定)の場合は/tmpを決め打ち
      return($ENV{'TEMP'});
   }else{
      return("/tmp");
   }
   
}

sub gnWarn {
   # ワーニングのポップアップを出す関数
   #
   # 引数: 文字列(ポップアップに表示する文字)
   # 返り値: なし
   my $mes = $_[0];
   my $mW = $gnGUIxml->get_widget('mW');
   my $pop = Gtk2::MessageDialog->new($mW,
                                        'GTK_DIALOG_MODAL',
                                        'GTK_MESSAGE_ERROR',
                                        'GTK_BUTTONS_OK',
                                        $mes,
                                         ,
                                         'GTK_BUTTONS_OK' => 'gtk-ok');
   if ($pop->run eq 'ok') {
      $pop->destroy;
   }
}

sub gnYesNo {
   # 質問のポップアップを出す関数
   #
   # 引数: 文字列(ポップアップに表示する文字)
   # 返り値: 文字列(yes/no)
   my $mes = $_[0];
   my $mW = $gnGUIxml->get_widget('mW');
   my $pop = Gtk2::MessageDialog->new($mW,
                                        'GTK_DIALOG_MODAL',
                                        'GTK_MESSAGE_INFO',
                                        'GTK_BUTTONS_YES_NO',
                                        $mes,
                                        'GTK_BUTTONS_YES' => 'gtk-yes',
                                        'GTK_BUTTONS_NO' => 'gtk-no');
   if ($pop->run eq 'yes') {
      $pop->destroy;
      return("yes");
   }else{
      $pop->destroy;
      return("no");
   }
}


sub genRandStr {
    # http://orfeus.knmi.nl/pub/outgoing/chad/perl/randstr.perl より取得
    # ありがとうございます
    # 指定された文字数のランダム文字列を生成する
    # 引数: integer(文字数)
    # 返り値：string(ランダム文字列)
    my ($size) = @_;
    my $ret = '';

    my @chars=("0".."9","a".."z","A".."Z");

    foreach (1..$size) {
         $ret .= $chars[rand($#chars)];
    }
    return $ret;
}

sub command_runner {
    ## 別スレッドで動く関数
    # goggify.pl(http://spr.mahonri5.net/oggify/)より使用
    if ($gnDebugFlag) { print STDERR "thread-start\n"; }
    my $run = 1;
    my $run2 = 1; 
    my $gnUA_res;
    my $gnUA;
    my $job;
    my $url;
    my $fn;
    # run until return
    while ($run) {

        $job = $queue->dequeue;
        #$url = $queue->dequeue_nb;
        #$fn = $queue->dequeue_nb;
        if ($job eq "geturl") {
           $url = $queue->dequeue_nb;
           $fn = $queue->dequeue_nb;
           {
              lock $gnImgGetSt;
              $gnImgGetSt = TRUE;
           }
           if ($run2 == 1) {
              { lock $fnsize; $fnsize = 0; }
              $gnUA = LWP::UserAgent->new;
              $gnUA->agent($gnUsrAgent);
              $gnUA->timeout(15); # 15秒間無反応だったら中断する
              $run2 = 0;
           }
           
           my $gnTotSz = 0;
           $gnUA_res = $gnUA->request(HTTP::Request->new(GET => $url),
                                            sub {
                                               my($chunk, $res) = @_;
                                               open(gnFN, ">>$fn");
                                                  binmode gnFN;
                                                  syswrite gnFN, $chunk, length($chunk);
                                               close(gnFN);
                                               $gnTotSz += length($chunk);
                                               if ($gnDebugFlag>1) { print STDERR "thread-write\: " . $gnTotSz . "\/$fnsize bytes\n"; }
                                            });
           {
              lock $gnImgGetSt;
              $gnImgGetSt = FALSE;
           }
        }elsif($job eq "exit") {
           $run = 0;
        }
        { lock $hold; $hold = 0; } 
    #$run = 0;
    }    
    
}

