/*
 * HTMLとJavaScriptによる、編集可能なテーブルコンポーネント
 * テーブルモデルとフィルタモデルのデフォルト実装
 *
 * このファイルの利用について
 * このファイルの内容は、個人利用・商用利用に関わらず、一部または全部を、
 * 自由に利用していただいてかまいません。
 * 但し、引用元の表示か、著作者が（有）ワイシステムクリエイトであることを
 * 示す、著作権表示を必ず行ってください。
 * また、このファイルの内容を利用した事により、何らかの不利益を被った場合
 * でも、（有）ワイシステムクリエイトは、一切保証できませんので、利用者ご
 * 自身の責任において、ご利用くださるようお願いします。
 *
 * Copyright (C) 2005 Y System Create Corporation. All Rights Reserved.
 *
 * $Date: 2005/05/28 09:12:26 $
 * $Revision: 1.5 $
 */
/**
 * デフォルトテーブルモデル
 */
function DefaultTableModel() {}

DefaultTableModel.prototype.initTableModel = function () {

    this.listenerList = new Array();
    this.rows = new Array();
    this.columnCount = -1;
}

/**
 * 行数を返す。
 */
DefaultTableModel.prototype.getRowCount = function () {

    return this.rows.length;
}

/**
 * 列数を返す。
 * このメソッドが-1を返すと、テーブルビューは、
 * 行毎に異なる列数で、テーブルを作成する。
 */
DefaultTableModel.prototype.getColumnCount = function () {

    return this.columnCount;
}

/**
 * 指定行の列数を返す。
 * @param rowIndex : 数値 : 行インデックス。
 */
DefaultTableModel.prototype.getColumnCountAt = function (rowIndex) {

    if (-1 != this.columnCount) {

        return this.columnCount;
    }

    var values = this.rows[rowIndex];

    if (values) {

        return values.length;
    }

    return 0;
}

/**
 * 列のタイトルを返す。
 * @param columnIndex : 数値 : 列インデックス。
 * @return 文字列 : 指定カラムのタイトル。
 */
DefaultTableModel.prototype.getColumnTitleAt = function (columnIndex) {

    return "" + columnIndex;
}

/**
 * リスナの登録
 * モデルの内容が変更されたことを通知する、リスナを登録する。
 * リスナは、以下のメソッドを実装している必要がある。
 *  tableChanged(TableModelEvent)
 * @param view : Object : リスナ
 */
DefaultTableModel.prototype.addTableModelListener = function (view) {

    if (! view) {

        throw WebApplyUtil.createException(
                new Error(),
                "DefaultTableModel#addTableModelListener:"+
                "IllegalArgument:"+
                "view is null.");
    }

    if (! view.tableChanged) {

        throw WebApplyUtil.createException(
                new Error(),
                "DefaultTableModel#addTableModelListener:"+
                "NoSuchMethod:"+
                "view.tableChanged");
    }

    this.listenerList.push(view);

    this.fireTableStructureChanged();
}

/**
 * モデルの内容が変更されたことを、全てのリスナーに通知する。
 * @param rowIndex : 数値 : 値が変更された行インデックス。
 * @param columnIndex : 数値 : 値が変更された列インデックス。
 */
DefaultTableModel.prototype.fireTableChanged = function (modelEvent) {

    for (var i = 0; i < this.listenerList.length; i ++) {

        var view = this.listenerList[i];

        if (view) {

            var newEvent = null;

            if (null != modelEvent) {

                /*
                 * viewがフィルターであった場合など、
                 * その中でイベントを書き換えたしまうので、
                 * それが他のリスナに影響しないように、
                 * 新たなイベントインスタンスを作成して渡す。
                 * 本来ここでやるべきではないかもしれないが、
                 * ここでやれば間違いない。
                 */
                newEvent = new TableModelEvent(modelEvent.model,
                                               modelEvent.startRow,
                                               modelEvent.endRow,
                                               modelEvent.startColumn,
                                               modelEvent.endColumn,
                                               modelEvent.type);
            }

            view.tableChanged(newEvent);
        }
    }
}

/**
 * 指定のセルの内容が変更されたことを、全てのリスナーに通知する。
 * @param rowIndex : 数値 : 値が変更された行インデックス。
 * @param columnIndex : 数値 : 値が変更された列インデックス。
 */
DefaultTableModel.prototype.fireTableCellUpdated = function (rowIndex, columnIndex) {

    var modelEvent = new TableModelEvent(this,
                                         rowIndex,
                                         rowIndex,
                                         columnIndex,
                                         columnIndex,
                                         TableModelEvent.CELL_UPDATE);

    this.fireTableChanged(modelEvent);
}

/**
 * 指定の行が追加されたことを、全てのリスナーに通知する。
 * @param rowIndex : 数値 : 追加された行インデックス。
 */
DefaultTableModel.prototype.fireTableRowInserted = function (rowIndex) {

    var modelEvent = new TableModelEvent(this,
                                         rowIndex,
                                         -1,
                                         -1,
                                         -1,
                                         TableModelEvent.ROW_INSERT);

    this.fireTableChanged(modelEvent);
}

/**
 * 指定の行が削除されたことを、全てのリスナーに通知する。
 * @param rowIndex : 数値 : 削除された行インデックス。
 */
DefaultTableModel.prototype.fireTableRowDeleted = function (rowIndex) {

    var modelEvent = new TableModelEvent(this,
                                         rowIndex,
                                         -1,
                                         -1,
                                         -1,
                                         TableModelEvent.ROW_DELETE);

    this.fireTableChanged(modelEvent);
}

/**
 * モデルの構造が変更されたことを、全てのリスナーに通知する。
 */
DefaultTableModel.prototype.fireTableStructureChanged = function () {

    this.fireTableChanged(null);
}

/**
 * 指定セルの表示用の値を取得する。
 * @param rowIndex : 数値 : 値を取得するセルの行インデックス。
 * @param columnIndex : 数値 : 値を取得するセルの列インデックス。
 * @return モデルが格納している値
 */
DefaultTableModel.prototype.getValueAt = function (rowIndex, columnIndex) {

    return this.getEditableValueAt(rowIndex, columnIndex);
}

/**
 * 指定セルの編集用の値を取得する。
 * @param rowIndex : 数値 : 値を取得するセルの行インデックス。
 * @param columnIndex : 数値 : 値を取得するセルの列インデックス。
 * @return モデルが格納している値。
 */
DefaultTableModel.prototype.getEditableValueAt = function (rowIndex, columnIndex) {

    var values = this.rows[rowIndex];

    if (values) {

        var value = values[columnIndex];
        if (value) {

            return value;
        }
    }

    return "";
}

/**
 * 指定の値を、モデルにセットする。
 * @param value : Object : セットする値。
 * @param rowIndex : 数値 : 値をセットする行数。
 * @param columnIndex : 数値 : 値をセットする列数。
 */
DefaultTableModel.prototype.setValueAt = function (value, rowIndex, columnIndex) {

    var values = this.rows[rowIndex];
    var date = values[columnIndex] = value;

    this.fireTableCellUpdated(rowIndex, columnIndex);
}

/**
 * 編集可否判定
 * 指定セルが編集可能かどうかを返す。
 * @param rowIndex : 数値 : 行インデックス。
 * @param columnIndex : 数値 : 列インデックス。
 * @return 必ずtrue
 */
DefaultTableModel.prototype.isCellEditable = function (rowIndex, columnIndex) {

    return true;
}

/**
 * 指定の行に、空の行を挿入する。
 * @param rowIndex : 数値 : 挿入する行インデックス。
 *                   -1を指定すると、最期に行を追加する。
 * @param values : 配列 : 新規行の内容の配列。
 */
DefaultTableModel.prototype.insertRow = function (rowIndex) {

    var values = new Array();
    this.rows.splice(rowIndex, 0, values);
    this.fireTableRowInserted(rowIndex);
}

/**
 * 指定のインッデクスの行を削除する。
 * @param rowIndex : 数値 : 削除する行インデックス。
 */
DefaultTableModel.prototype.deleteRow = function (rowIndex) {

    var values = new Array();
    this.rows.splice(rowIndex, 1);
    this.fireTableRowDeleted(rowIndex);
}


/*****************************************************************************
 * フィルターモデル
 * ビューとモデルの間で、列インデックスの変換などを行う。
 * ビューに対してはモデルとして、モデルに対してはビューとして
 * 見せかける。
 ****************************************************************************/
FilterModel.prototype = new DefaultTableModel();

/*
 * コンストラクタ
 * @param model : テーブルモデル : 実際にデータを格納するモデル
 */
function FilterModel() {
    this.orgModel = null;
}

/*
 * テーブルモデルとして、実装が必須のメソッド
 */
/**
 * 行数を返す。
 */
FilterModel.prototype.getRowCount = function () {

    return this.orgModel.getRowCount();
}

/**
 * 列数を返す。
 * このメソッドが-1を返すと、テーブルビューは、
 * 行毎に異なる列数で、テーブルを作成する。
 */
FilterModel.prototype.getColumnCount = function () {

    return this.orgModel.getColumnCount();
}

/**
 * 指定行の列数を返す。
 * @param rowIndex : 数値 : 行インデックス。
 */
FilterModel.prototype.getColumnCountAt = function (rowIndex) {

    return this.orgModel.getColumnCountAt(rowIndex);
}

/**
 * 列のタイトルを返す。
 * @param columnIndex : 数値 : 列インデックス。
 * @return 文字列 : 指定カラムのタイトル。
 */
FilterModel.prototype.getColumnTitleAt = function (columnIndex) {

    return this.orgModel.getColumnTitleAt(columnIndex);
}

/**
 * リスナの登録
 * モデルの内容が変更されたことを通知する、リスナを登録する。
 * リスナは、以下のメソッドを実装している必要がある。
 *  tableChanged(TableModelEvent)
 * @param view : Object : リスナ
 */
FilterModel.prototype.addTableModelListener = function (view) {

    this.view = view;
    this.orgModel.addTableModelListener(this);
}

/**
 * 指定セルの表示用の値を取得する。
 * @param rowIndex : 数値 : 値を取得するセルの行インデックス。
 * @param columnIndex : 数値 : 値を取得するセルの列インデックス。
 * @return モデルが格納している値
 */
FilterModel.prototype.getValueAt = function (rowIndex, columnIndex) {

    var newIndices = this.changeIndex(rowIndex, columnIndex);
    rowIndex = newIndices[0];
    columnIndex = newIndices[1];
    return this.orgModel.getValueAt(rowIndex, columnIndex);
}

/**
 * 指定セルの編集用の値を取得する。
 * @param rowIndex : 数値 : 値を取得するセルの行インデックス。
 * @param columnIndex : 数値 : 値を取得するセルの列インデックス。
 * @return モデルが格納している値。
 */
FilterModel.prototype.getEditableValueAt = function (rowIndex, columnIndex) {

    var newIndices = this.changeIndex(rowIndex, columnIndex);
    rowIndex = newIndices[0];
    columnIndex = newIndices[1];
    return this.orgModel.getEditableValueAt(rowIndex, columnIndex);
}

/**
 * 指定の値を、モデルにセットする。
 * @param value : Object : セットする値。
 * @param rowIndex : 数値 : 値をセットする行数。
 * @param columnIndex : 数値 : 値をセットする列数。
 */
FilterModel.prototype.setValueAt = function (value, rowIndex, columnIndex) {

    var newIndices = this.changeIndex(rowIndex, columnIndex);
    rowIndex = newIndices[0];
    columnIndex = newIndices[1];
    this.orgModel.setValueAt(value, rowIndex, columnIndex);
}

/**
 * 編集可否判定
 * 指定セルが編集可能かどうかを返す。
 * @param rowIndex : 数値 : 行インデックス。
 * @param columnIndex : 数値 : 列インデックス。
 * @return this.orgModel.isCellEditableの戻り値
 */
FilterModel.prototype.isCellEditable = function (rowIndex, columnIndex) {

    var newIndices = this.changeIndex(rowIndex, columnIndex);
    rowIndex = newIndices[0];
    columnIndex = newIndices[1];
    return this.orgModel.isCellEditable(rowIndex, columnIndex);
}

/**
 * インデックス変換
 * ビューのインデックスを、データモデルのインデックスに変換する。
 * @param rowIndex : 数値 : ビューの行インデックス。
 * @param columnIndex : 数値 : ビューの列インデックス。
 * @return : 配列 : １番目の要素を行インデックス、
 *                  ２番目の要素を列インデックスとする配列。
 */
FilterModel.prototype.changeIndex = function (rowIndex, columnIndex) {

    return [rowIndex, columnIndex];
}

/*
 * ビューとして、実装が必須のメソッド
 */
FilterModel.prototype.tableChanged = function (modelEvent) {

    if (null != modelEvent && 0 <= modelEvent.startRow) {

        modelEvent = this.changeEvent(modelEvent);
    }
    this.view.tableChanged(modelEvent);
}

/**
 * イベントの変換
 * イベントが指すインデックスを変換し返す。
 */
FilterModel.prototype.changeEvent = function (modelEvent) {

    return modelEvent;
}
