6.コモンコントロール(2) 〜 リストビュウ(2) 〜

 

※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。

 


1.ファイル情報取得

リストビュウ が表示されましたが、まだファイル情報は表示されていません。
これまたある程度決まった書き方がありますので、それに倣って記述していきます。

ますは、ファイル情報を取得しようとしているディレクトリ(この場合カレントディレクトリです)にファイルがあるかどうかを確認する必要があります。
ファイル情報があるかどうかを確認する関数として、 FindFirstFile() があります。
※ なお、このMSDNのページには、ちょっと間違いがあります。
"C:\*" のようなファイル名を指定し、引き続き GetFileAttributes 関数を使ってルートディレクトリ内を検索してください。
とありますが、正しくは、引き続き FindNextFile 関数を使ってですね。

HANDLE FindFirstFile(
  LPCTSTR           lpFileName,     // ファイル名
  LPWIN32_FIND_DATA lpFindFileData  // データバッファのポインタ
);

第1引数には対象となるファイル名を指定します。
ですが、ファイル名がわかっていないので(だから調べてる)、ここには指定することができません。
こういった場合に、ワイルドカードが役に立ちます。

ワイルドカードというのは何でしょうか。
ワイルドカードは「*」という文字を「0文字以上の何か」という意味に置き換えるものです。
この特殊な意味を持つ文字をワイルドカード文字と呼びます。
例えば、"*.txt" と指定すると、拡張子が ".txt" の全てのファイルが対象となります。
今回は、カレントディレクトリに何かあればそれを取得したいと考えていますので、ワイルドカードを、"*.*" のように指定します。

第2引数には、WIN32_FIND_DATA 構造体 のポインタを指定します。
WIN32_FIND_DATA 構造体 はどのようになっているかというと、

typedef struct _WIN32_FIND_DATA {
    DWORD    dwFileAttributes;       // 属性
    FILETIME ftCreationTime;         // 作成時間
    FILETIME ftLastAccessTime;       // 最終アクセス時間
    FILETIME ftLastWriteTime;        // 最終書き込み時間
    DWORD    nFileSizeHigh;          // サイズ (バイト単位) の上位ワード
    DWORD    nFileSizeLow;           // サイズ (バイト単位) の下位ワード
    DWORD    dwReserved0;            // リパースタグ
    DWORD    dwReserved1;            // 未使用
    CHAR     cFileName[MAX_PATH];    // ファイル名 (NULLで終わる文字列)
    CHAR     cAlternateFileName[14]; // 8.3形式のファイル名 (NULLで終わる文字列)
} WIN32_FIND_DATA, *LPWIN32_FIND_DATA;

特殊なのは、サイズです。上位ワードと下位ワードって何なんでしょう?

ファイルサイズは (nFileSizeHigh * MAXDWORD) + nFileSizeLowで求めることができます。
これは、DWORD という型に秘密があります。
DWORD は、unsigned long 型ですから、4,294,967,295(FFFFFFFF) まで表すことができます。
逆に言うと 4 Gbyteまでしか表示できないことになります。
そこで、もうひとつ使うことで最大 FFFFFFFFFFFFFFFF(18,446,744,073,709,551,615)という当分追いつきそうにない 16,777,216 Tbyte(テラバイト)までを表示できるようになっているのです。

何かファイル(ディレクトリ情報を含む)が見つかれば、この構造体に、その情報が格納されます。
ただし、この関数が返すのはそのディレクトリに含まれる(ワイルドカードの条件に合致するものだけですが)すべてのファイルを返してくれるわけではありません。
見つかったうちのひとつです。
※配列のような形で返してくれるとありがたかったのですが、残念です。

だとすると、そのディレクトリに複数のファイルがあった場合はどのように残りを取得すればいいのでしょうか?
FindFirstFile() をそのフォルダにあるファイル(ディレクトリ)の数だけ呼び出せばいいような気がしますが、何度やっても同じファイルしか返してくれません。
実は、この関数の戻り値に秘密があります。この関数が成功すると、検索ハンドルが返ります。
この検索ハンドルは、現在どこまでファイル取得を行ったかを覚えています。

そして、最初に取得した次以降のファイル取得には、 FindNextFile() を使います。

BOOL FindNextFile(
  HANDLE            hFindFile,      // 検索ハンドル
  LPWIN32_FIND_DATA lpFindFileData  // データバッファ
);

次のファイルが見つからなくなると、この関数は0を返します。
そこで、ウインドウプロシージャを以下のように修正します。
※ 必要部分のみ抜粋です。


LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HANDLE            hFind;
  HWND              hCtlListView;
  WIN32_FIND_DATA   WinFindData;
  SYSTEMTIME        SystemTime;
  FILETIME          FileTime;
  LVITEM            LVItem;
  LV_COLUMN         LVColumn[] =
                    {
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  150, "名前",     0, 0},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT,  75, "サイズ",   0, 1},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  110, "更新日時", 0, 2}
                    };
  int               nIndex = 0;
  INITCOMMONCONTROLSEX ICC;


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);

      // コモンコントロール初期化
      ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
      ICC.dwICC  = ICC_LISTVIEW_CLASSES;
      InitCommonControlsEx(&ICC);

      // リストビュウ作成
      hCtlListView = CreateWindowEx(WS_EX_CLIENTEDGE,
                                    WC_LISTVIEW,
                                    NULL,
                                    WS_CHILD | WS_VISIBLE | LVS_REPORT,
                                    10,
                                    30,
                                    380,
                                    320,
                                    hWnd,
                                    0,
                                    hInstance,
                                    NULL);

      for (int i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++)
      {
        ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
      }

      if ((hFind = FindFirstFile("*.*", &WinFindData)) == INVALID_HANDLE_VALUE)
      {
        if (GetLastError() != ERROR_NO_MORE_FILES)
        {
          MessageBox(NULL, "ファイルの検索に失敗しました", "ERR", MB_OK);
          break;
        }
      }

      do
      {
        // 1項目(名前)
        LVItem.mask     = LVIF_TEXT;
        LVItem.iItem    = nIndex;
        LVItem.iSubItem = 0;
        LVItem.pszText  = WinFindData.cFileName;
        ListView_InsertItem(hCtlListView, &LVItem);

        // 2項目(サイズ)
        LVItem.iSubItem = 1;
        if (WinFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
          wsprintf(LVItem.pszText, "%s", "<DIR>");
        }
        else
        {
          wsprintf(LVItem.pszText, "%d", WinFindData.nFileSizeHigh * MAXDWORD + WinFindData.nFileSizeLow);
        }
        ListView_SetItem(hCtlListView, &LVItem);

        // 3項目(更新日時)
        LVItem.iSubItem = 2;
        // 協定世界時(UTC)を地域標準時に変更
        FileTimeToLocalFileTime(&WinFindData.ftLastWriteTime, &FileTime);
        FileTimeToSystemTime(&FileTime, &SystemTime);
        wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth,
                                                         SystemTime.wDay,  SystemTime.wHour,  SystemTime.wMinute);
        ListView_SetItem(hCtlListView, &LVItem);

        nIndex++;
      }
      while (FindNextFile(hFind, &WinFindData));

      FindClose(hFind);

      break;
  }

  return 0L;
}

そうそう、この検索ハンドルは、検索が終わったら閉じないといけません。
閉じるには、 FindClose() を使用します。
ハンドルは、使ったらクローズする。プログラムの世界では、覚えて起きましょう。

BOOL FindClose(
  HANDLE hFindFile   // ファイル検索ハンドル
);

引数には、使用した ファイル検索ハンドル を指定します。

どうにか表示することができました。


2.行のカラムを作る

まだ、解説していないところがありました。
行の追加を行うところですね。これはちょっとややこしいことがあります。
名前と、サイズ・更新日時を一緒に追加できればいいのですが、それぞれ別に追加する必要があります。

まず、1番左の項(名前の項)をリストビュウに追加するには、LVM_INSERTITEM メッセージを送ってやる必要があります。
送出するには、 やっぱり SendMessage() を使います。

LVM_INSERTCOLUMN
  wParam = 0                                         // 未使用
  lParam = (LPARAM) (const LV_ITEM FAR *) pItem  // 行(アイテム)の情報を格納した LVITEM 構造体のポインタ

列ヘッダーを追加するときと似ていますが、今度は第2引数にLVITEM 構造体 を指定しないといけないようです。
LVITEM 構造体 はどのようになっているかというと、

typedef struct _LVITEM {
    UINT   mask;              // どのメンバが有効かを指定
    int    iItem;             // 行に関連付けられるインデックス
    int    iSubItem;          // カラムに関連付けられるインデックス
    UINT   state;             // カラムの状態
    UINT   stateMask;         // どのカラムの状態のメンバが有効かを指定
    LPTSTR pszText;           // カラムの文字列
    int    cchTextMax;        // pszTextのサイズ
    int    iImage;            // イメージリストのインデックス
    LPARAM lParam;            // アイテムに関連付ける32ビット値(任意のデータ)

#if (_WIN32_IE >= 0x0300)     // IE3.0 (comctl32.dll Version4.70) 以降の機能
    int    iIndent;           // インデント
#endif

#if ((_WIN32_IE >= 0x560)     // IE5.6 (comctl32.dll Version 6.0) 以降の機能
    int    iGroupId;
    UINT   cColumns;          // tile view columns
    PUINT  puColumns;
#endif

#if (_WIN32_WINNT >= 0x0600)  // Windows Vista 以降の機能
    int *  piColFmt
    int    iGroup
    int    cxMin;
    int    cxDefault;
    int    cxIdeal;
#endif
} LVITEM, *LPLVITEM;

なんか、前回の LVCOLUMN 構造体 と似ていますね。
今回使うのは、mask、iItem、iSubItem、pszText の4つです。
mask には今から使用するメンバのフラグを指定しますので、LVIF_TEXT を指定します。
iItem、iSubItem、を指定する必要はありません。

pszText は、行に表示する文字列を指定します。
リストビュウの1番左の項("名前"欄)では、WIN32_FIND_DATA 構造体 のファイル名を指定します。

iItem は、 に関連付けられるインデックスです。
挿入する順番とは関係なく採番することができますが、余計わかりにくくなるので、0 から順につけていくのが無難だといえます。

iSubItem は、カラム に関連付けられるインデックスです。
リストビュウの1番左の項("名前"欄)には、LVM_INSERTITEM メッセージを使用するため、この値には、必ず 0 を使用しなければなりません。

Item と SubItem の関係がちょっとわかりにくいですね。
こういった場合は、図を使用すると理解が進むときがあります。

  SubItem
列1 列2 列3 列4
Item 行1 0 , 0 0 , 1 0 , 2 0 , 3
行2 1 , 0 1 , 1 1 , 2 1 , 3
行3 2 , 0 2 , 1 2 , 2 2 , 3
行4 3 , 0 3 , 1 3 , 2 3 , 3
行5 4 , 0 4 , 1 4 , 2 4 , 3

まず、行1の列1の項目を、Item 0、SubItem 0 として LVM_INSERTITEM メッセージで追加しました。
続いて、行1の列2以降の項目を追加するわけですが、LVM_INSERTITEM メッセージでは、行2が追加されてしまいます。

このため、列2以降の項目の追加には、別に LVM_SETITEM メッセージを送ってやる必要があります。
送出するには、 やっぱり SendMessage() を使います。

LVM_SETITEM
  wParam = 0                                         // 未使用
  lParam = (LPARAM) (const LV_ITEM FAR *) pItem  // 行(アイテム)の情報を格納した LVITEM 構造体のポインタ

LVM_INSERTITEM メッセージのときとまったく同じように、サブ項目を追加します。

リストビュウの2番目の項("サイズ"欄)は、pszText には、WIN32_FIND_DATA 構造体 のファイルサイズを指定します。
ただし、属性がフォルダのときは、ひと工夫してこの項目に <DIR> と表示してやります。
リストビュウの3番目の項("更新日時"欄)は、pszText には、WIN32_FIND_DATA 構造体 の最終書き込み時間を指定します。
ですが、ここにはちょっと複雑な式が書いてあります。

ちょっと説明しますと、ftLastAccessTime は、FILETIME 構造体からできており、これには、西暦1601年1月1日から経過した時刻を100ナノ秒を単位とした64ビット値の下位32ビットと上位32ビットが格納されています。
また、この時間は、世界標準時刻なので、日本時間の変換を行っています。
これが、 FileTimeToLocalFileTime() です。
この時点でも100ナノ秒を単位とした64ビット値ですから、我々が理解できる形にする必要があります。
これが、 FileTimeToSystemTime() です。

BOOL FileTimeToSystemTime(
  CONST FILETIME * lpFileTime,  // 変換するべきファイル時刻
  LPSYSTEMTIME     lpSystemTime // システム日時が格納される構造体
);

SYSTEMTIME 構造体 は、以下のようになっています。これで無事時間を表示できます。

typedef struct _SYSTEMTIME {
    WORD  wYear;          // 年(西暦)
    WORD  wMonth;         // 月(1月=1, 2月=2 ...)
    WORD  wDayOfWeek;     // 曜日(日=0, 月=1 ...)
    WORD  wDay;           // 日
    WORD  wHour;          // 時
    WORD  wMinute;        // 分
    WORD  wSecond;        // 秒
    WORD  wMilliseconds;  // ミリ秒
} SYSTEMTIME, *PSYSTEMTIME;

いつものごとく、実際の値を見てみることにしましょう。

こういったことは、面倒くさがらずやったほうが視覚的に理解が進むと思います。

今回はちょっと説明が長くなりました。
最後に今回の「WinMain.cpp」の全文を載せておきます。


/* ========================================================================== */
/*          FILE        WinMain.cpp                                           */
/*          CREATE      2006/10/01                                            */
/*          MODIFY      2007/01/11                                            */
/*          REMARK                                                            */
/*          COPYRIGHT   2000-2007, Latin. All Rights Reserved                 */
/* ========================================================================== */
#include <Windows.h>
#include <WindowsX.h>
#include <CommCtrl.h>
#include "Resource.h"


/* -------------------------------------------------------------------------- */
/*          CALLBACK    WndProc                                               */
/*          PARAM       HWND        hWnd        : ウィンドウハンドル          */
/*                      UINT        uMsg        : メッセージ                  */
/*                      WPARAM      wParam      : メッセージの追加情報        */
/*                      LPARAM      lParam      : メッセージの追加情報        */
/*          RETURN      LRESULT                                               */
/*                      メッセージ処理の結果                                  */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HWND              hCtlCombo;
  HWND              hCtlListView;
  HANDLE            hFind;
  WIN32_FIND_DATA   WinFindData;
  SYSTEMTIME        SystemTime;
  FILETIME          FileTime;
  LVITEM            LVItem;
  LV_COLUMN         LVColumn[] =
                    {
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  150, "名前",     0, 0},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT,  75, "サイズ",   0, 1},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  110, "更新日時", 0, 2}
                    };
  char              szCurDir[MAX_PATH + 1];
  char              szDrives[128];
  int               nIndex = 0;
  int               i;
  INITCOMMONCONTROLSEX ICC;


  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
      hCtlCombo = CreateWindow("COMBOBOX",
                               NULL,
                               WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                               10,
                               5,
                               450,
                               100,
                               hWnd,
                               0,
                               hInstance,
                               NULL);
      SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0));

      GetCurrentDirectory(MAX_PATH + 1, szCurDir);
      ComboBox_SetText(hCtlCombo, szCurDir);
      ComboBox_AddString(hCtlCombo, szCurDir);

      GetLogicalDriveStrings(sizeof(szDrives), szDrives);

      i = 0;
      while (szDrives[i] != '\0')
      {
        ComboBox_AddString(hCtlCombo, &szDrives[i]);

        // 次のドライブ名へ
        while (szDrives[i] != '\0')
        {
          i++;
        }

        i++;
      }

      // コモンコントロール初期化
      ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
      ICC.dwICC  = ICC_LISTVIEW_CLASSES;
      InitCommonControlsEx(&ICC);

      // リストビュウ作成
      hCtlListView = CreateWindowEx(WS_EX_CLIENTEDGE,
                                    WC_LISTVIEW,
                                    NULL,
                                    WS_CHILD | WS_VISIBLE | LVS_REPORT,
                                    10,
                                    30,
                                    380,
                                    320,
                                    hWnd,
                                    0,
                                    hInstance,
                                    NULL);
      for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++)
      {
        ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
      }

      if ((hFind = FindFirstFile( "*.*", &WinFindData)) == INVALID_HANDLE_VALUE)
      {
        if (GetLastError() != ERROR_NO_MORE_FILES)
        {
          MessageBox(NULL, "ファイルの検索に失敗しました", "ERR", MB_OK);
          break;
        }
      }

      do
      {
        // 1項目(名前)
        LVItem.mask     = LVIF_TEXT;
        LVItem.iItem    = nIndex;
        LVItem.iSubItem = 0;
        LVItem.pszText  = WinFindData.cFileName;
        ListView_InsertItem(hCtlListView, &LVItem);

        // 2項目(サイズ)
        LVItem.iSubItem = 1;
        if (WinFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
          wsprintf(LVItem.pszText, "%s", "<DIR>");
        }
        else
        {
          wsprintf(LVItem.pszText, "%d", WinFindData.nFileSizeHigh * MAXDWORD + WinFindData.nFileSizeLow);
        }
        ListView_SetItem(hCtlListView, &LVItem);

        // 3項目(更新日時)
        LVItem.iSubItem = 2;
        // 協定世界時(UTC)を地域標準時に変更
        FileTimeToLocalFileTime(&WinFindData.ftLastWriteTime, &FileTime);
        FileTimeToSystemTime(&FileTime, &SystemTime);
        wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth,
                                                         SystemTime.wDay,  SystemTime.wHour,  SystemTime.wMinute);
        ListView_SetItem(hCtlListView, &LVItem);

        nIndex++;
      }
      while (FindNextFile(hFind, &WinFindData));

      FindClose(hFind);

      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

/* -------------------------------------------------------------------------- */
/*          WINAPI      WinMain                                               */
/*          PARAM       HINSTANCE   hInstance   : 現在のインスタンス          */
/*                      HINSTANCE   hPrevInst   : 直前のインスタンス          */
/*                      LPSTR       lpCmdLine   : コマンドライン引数          */
/*                      int         nCmdShow    : ウインドウの表示形式        */
/*          RETURN      int                     :                             */
/*                      メッセージの wParam パラメータ                        */
/*          REMARK      Win32 アプリケーションの初期エントリポイント          */
/*                      Windows システムが呼び出す。                          */
/* -------------------------------------------------------------------------- */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
  /* 変数宣言部 */
  HWND              hWnd;
  WNDCLASSEX        WndClass;
  MSG               Msg;


  /* 関数本体部 */
  // ウインドウクラス作成
  WndClass.cbSize        = sizeof(WndClass);
  WndClass.style         = CS_HREDRAW | CS_VREDRAW;
  WndClass.lpfnWndProc   = WndProc;
  WndClass.cbClsExtra    = 0;
  WndClass.cbWndExtra    = 0;
  WndClass.hInstance     = hInstance;
  WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
  WndClass.lpszMenuName  = MAKEINTRESOURCE(IDM_MENUMAIN);
  WndClass.lpszClassName = "FTPManager";

  // ウィンドウクラス登録
  if (! RegisterClassEx(&WndClass))
  {
    return FALSE;
  }

  // ウィンドウ作成
  hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名
                      "FTPManager",              // ウィンドウ名
                      WS_OVERLAPPEDWINDOW,    // ウィンドウスタイル
                      CW_USEDEFAULT,          // ウィンドウの横方向の位置
                      CW_USEDEFAULT,          // ウィンドウの縦方向の位置
                      CW_USEDEFAULT,          // ウィンドウの幅
                      CW_USEDEFAULT,          // ウィンドウの高さ
                      NULL,                   // 親ウィンドウまたはオーナーウィンドウのハンドル
                      NULL,                   // メニューハンドルまたは子ウィンドウ ID
                      hInstance,              // アプリケーションインスタンスのハンドル
                      NULL);                  // ウィンドウ作成データ

  if (! hWnd)
  {
    return FALSE;
  }

  // ウィンドウの表示
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  // メッセージループ
  while (GetMessage(&Msg, NULL, 0, 0))
  {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
  }

  return (int) Msg.wParam;
}

本日のソース: FTPManager106(VC8).zip   FTPManager106(BCC).zip

※小言

皆さんは、バックアップを取っていますか?
ここでいうバックアップというのは、世代管理のことです。

今日のプログラミングを行うときに、前回までの作業を退避することをお勧めします。
新しい機能を追加した場合に、バグが出ることはよくあります。
この場合、新しいバージョンで分からないバグをつぶすことに時間を割かれるより、
破棄して前のバージョンからやり直した方が効率がいいことが多々あります。

いまどきのハードディスクにはかなりの容量があります。
1から作り直す手間を考えると、バックアップのくせを付けることは非常に有効な開発手段だと思います。


Last Update 2014/01/31 21:28
[Index] [Prev] [Next]