Lesson 12:複数ファイルを扱えるようにしてみる


 はぁ・・・、また大遅刻してしまいました・・・。今回は書く時間は十分に有ったにもかかわらずの大遅刻、何も言い訳できません。

 今回は複数ファイルを同時に扱う事ができるようにしてみたいと思います。
 今まではBTinyEditor.hにアプリケーション全体を管理するコードを記述していましたが、今回ではこの部分に大きく手を加えることになります。まずは、作業しやすいようにBTinyEditor.hをBTinyEditor.hとBTinyEditor.cppに分けて記述するようにします。

/**** ファイル名 : BTinyEditor.h ****/

#ifndef BTINYEDITORAPP 
#define BTINYEDITORAPP 
//--------------------------------------------------------------------- 
#include <Be.h> 
#include "MainWindow.h" 
//--------------------------------------------------------------------- 
#define APPLICATION_SIGNATURE   "application/x-vnd.vow-betinyeditor" 
//--------------------------------------------------------------------- 
class BTinyEditorApp : public BApplication 
{ 
private: 
    BFilePanel *open_filepanel; 
public: 
    BTinyEditorApp(); 
    ~BTinyEditorApp(); 
    void MessageReceived(BMessage *msg); 
    void RefsReceived(BMessage *message); 
}; 
//--------------------------------------------------------------------- 
#endif

/**** ファイル名 : BTinyEditor.cpp ****/

//--------------------------------------------------------------------- 
#include "BTinyEditor.h" 
#include <Be.h> 
//--------------------------------------------------------------------- 
BTinyEditorApp::BTinyEditorApp() 
    :BApplication(APPLICATION_SIGNATURE) 
{ 
    BEditorWindow *mainwnd=new BEditorWindow( 
            BRect(MAINWINDOW_POSITION_LEFT,MAINWINDOW_POSITION_TOP, 
                  MAINWINDOW_POSITION_WIDTH+MAINWINDOW_POSITION_LEFT-1, 
                  MAINWINDOW_POSITION_HEIGHT+MAINWINDOW_POSITION_TOP-1), 
            MAINWINDOW_TITLE);
    open_filepanel= new BFilePanel(); 
    mainwnd->Show();
} 
//--------------------------------------------------------------------- 
BTinyEditorApp::~BTinyEditorApp() 
{ 
    delete open_filepanel; 
} 
//--------------------------------------------------------------------- 
void BTinyEditorApp::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what)
    {
        case MSG_OPEN:
             open_filepanel->Show();
             break; 
        default:
             BApplication::MessageReceived(msg); 
    }
} 
//--------------------------------------------------------------------- 
void BTinyEditorApp::RefsReceived(BMessage *message) 
{ 
    type_code ref_type;
    int32 ref_count;
    entry_ref ref;
    BEntry entry;        

    message->GetInfo("refs", &ref_type,&ref_count);

    for (int i=0;i<ref_count;i++)
    {
        message->FindRef("refs",i,&ref);
        if(entry.SetTo(&ref)==B_OK)
        {
            WindowAt(0)->Lock();
            ((BEditorWindow *)WindowAt(0))->OpenFile(&entry);
            WindowAt(0)->Unlock();
            return;
        }
    }
}
//---------------------------------------------------------------------

 複数のファイルを扱うにはどうしたらよいか。ファイルの読み書きや編集を行っているのはBEditorWindowクラスですから、単純に考えれば、このクラスの実体を複数作成すれば良さそうです。今はBTinyEditorAppクラスのコンストラクタ内で一つだけBEditorWindowクラスの実体を作成していますが、これを必要に応じて複数作成するようにします。
 ではBEditorWindowクラスの実体を作成するタイミングはいつがあるでしょう? まずプログラムが起動した時。それとファイルをオープンする時。新しくテキストの編集を始めたい時にもウィンドウを作成すべきでしょう。
 作成するタイミングはこれでよいとして、次に作成した実体を管理する方法です。今まではアプリケーション内にウィンドウは一つしかありませんでしたから、特に管理する必要もありませんでした。しかし、複数のウィンドウを扱うとなると、アプリケーションを終了するタイミングを決める際など、どうしても管理する必要があります。
 管理する方法としては、BEditorWindowの配列を作成する方法が考えられますが、これでは今回のように、いくつ作成するかわからない時には、あまり良い方法ではありません。そうなると、やはりリストで管理するのが良い方法でしょう。
 最近はほとんどの開発環境にリストを扱うクラスが用意されています。BeOSにも当然あるものと調べてみると、やはりありました、BList。追加や削除も簡単に使えそうです。

 まずは、コンストラクタで作成しているウィンドウをリストに追加するようにしてみましょう。リストはEditorListとし、ウィンドウを追加するAddEditor関数も作成します。

/**** ファイル名 : BTinyEditor.h ****/

#ifndef BTINYEDITORAPP 
#define BTINYEDITORAPP 
//--------------------------------------------------------------------- 
#include <Be.h> 
#include "MainWindow.h" 
//--------------------------------------------------------------------- 
#define APPLICATION_SIGNATURE   "application/x-vnd.vow-betinyeditor" 
//--------------------------------------------------------------------- 
class BTinyEditorApp : public BApplication 
{ 
private: 
    BList *EditorList; 
    BFilePanel *open_filepanel; 
public: 
    BTinyEditorApp(); 
    ~BTinyEditorApp(); 
    BEditorWindow *AddEditor(void); 
    void MessageReceived(BMessage *msg); 
    void RefsReceived(BMessage *message); 
}; 
//--------------------------------------------------------------------- 
#endif

/**** ファイル名 : BTinyEditor.cpp ****/

//--------------------------------------------------------------------- 
#include "BTinyEditor.h" 
#include <Be.h> 
//--------------------------------------------------------------------- 
BTinyEditorApp::BTinyEditorApp() 
    :BApplication(APPLICATION_SIGNATURE) 
{ 
    EditorList=new BList(); 
    open_filepanel= new BFilePanel(); 
    AddEditor();
} 
//--------------------------------------------------------------------- 
BTinyEditorApp::~BTinyEditorApp() 
{ 
    delete open_filepanel; 
        
    for(int i=EditorList->CountItems()-1;i>=0;i--) 
    { 
        BEditorWindow *targetwnd= 
                        ((BEditorWindow *)EditorList->ItemAt(i)); 
        targetwnd->Quit(); 
        EditorList->RemoveItem(targetwnd); 
    }
    delete EditorList; 
} 
//--------------------------------------------------------------------- 
BEditorWindow *BTinyEditorApp::AddEditor(void) 
{ 
    BEditorWindow *mainwnd=new BEditorWindow( 
        BRect(MAINWINDOW_POSITION_LEFT,MAINWINDOW_POSITION_TOP, 
              MAINWINDOW_POSITION_WIDTH+MAINWINDOW_POSITION_LEFT-1, 
              MAINWINDOW_POSITION_HEIGHT+MAINWINDOW_POSITION_TOP-1), 
        MAINWINDOW_TITLE); 
        
    EditorList->AddItem((void *)mainwnd); 
    mainwnd->Show(); 

    return mainwnd; 
} 
//--------------------------------------------------------------------- 
void BTinyEditorApp::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what)
    {
        case MSG_OPEN:
             open_filepanel->Show();
             break; 
        default:
             BApplication::MessageReceived(msg); 
    }
} 
//--------------------------------------------------------------------- 
void BTinyEditorApp::RefsReceived(BMessage *message) 
{ 
    type_code ref_type;
    int32 ref_count;
    entry_ref ref;
    BEntry entry;        

    message->GetInfo("refs", &ref_type,&ref_count);

    for (int i=0;i<ref_count;i++)
    {
        message->FindRef("refs",i,&ref);
        if(entry.SetTo(&ref)==B_OK)
        {
            ((BEditorWindow *)EditorList->ItemAt(0))->Lock();
            ((BEditorWindow *)EditorList->ItemAt(0))->OpenFile(&entry);
            ((BEditorWindow *)EditorList->ItemAt(0))->Unlock();
            return;
        }
    }
}
//---------------------------------------------------------------------

 次に考えなければならないのは、ウィンドウの作成とアプリケーションの終了でしょう。
 ウィンドウの作成は、プログラム起動時の処理は既に入っていますので、残りはファイルをオープンする時と新しくテキストの編集を始めたい時です。ファイルオープン時の処理はRefsReceived関数内に記述すれば良いはずです。新しくテキストの編集を行うには、トリガとしてメニューに「New Text」を追加して、そこで処理するようにします。
 問題はアプリケーションの終了です。アプリケーションの終了は、すべてのウィンドウが閉じた際に行うようにしたいので、BEditorWindowクラスは、ウィンドウを閉じられる際にBTinyEditorAppクラスに通知し、BTinyEditorAppは通知を受けるとEditorListから、そのウィンドウを削除するようにします。そして、すべてのウィンドウが削除されたら、アプリケーションを終了します。また、それとは別に、メニューから直接アプリケーションの終了を行うこともできるようにしておきます。

 BTinyEditorAppクラス、BEditorWindowクラス間の通知はすべてメッセージで行います。以下にメッセージの一覧と、その際の処理をまとめます。
発行元クラス処理クラスメッセージ処理
BEditorWindowBTinyEditorAppMSG_OPEN  オープン用のファイルパネルを開く。
-BTinyEditorAppB_REFS_RECEIVED  ウィンドウを追加。メッセージからオープンするファイル情報を取得し、追加したウィンドウのOpenFile関数を呼び出す。
BEditorWindowBTinyEditorAppMSG_ADDEDITOR  ウィンドウを追加する。
BEditorWindowBEditorWindowMSG_SAVE  ファイル名が設定されているかを調べ、設定されていればファイルを上書き保存する。設定されていなければ、MSG_SAVEASの処理を行う。
BEditorWindowBEditorWindowMSG_SAVEAS  保存用のファイルパネルを開く。
-BEditorWindowB_SAVE_REQUESTED  メッセージから保存先のファイル情報を取得し、SaveFile関数を呼び出す。
-BEditorWindowB_QUIT_REQUESTED  ファイルが保存済みかを確認。保存済みでなければ、保存確認メッセージを表示し、必要に応じてMSG_SAVEメッセージを発行。
 ファイルが保存済みであるか、保存確認メッセージで「Don't Save」を選択した場合は、ウィンドウの終了を許可し、BTinyEditorAppクラスにMSG_CLOSEDメッセージを発行する。MSG_CLOSEDメッセージには自分自身のポインタを付加しておく。
BEditorWindowBTinyEditorAppMSG_CLOSED  メッセージから終了したウィンドウを取得し、リストからそのウィンドウを削除する。
 リストの登録数が0になったら、自分自身にB_QUIT_REQUESTEDメッセージを発行する。
-BTinyEditorAppB_QUIT_REQUESTED  作成したクラスの実体を削除し、アプリケーションを終了する。
BEditorWindowBTinyEditorAppMSG_QUIT  開いているすべてのウィンドウにB_QUIT_REQUESTEDメッセージを発行する。
 少しややこしいので、ウィンドウのクローズからアプリケーションの終了までの、メッセージの流れをみてみましょう。
 まず、ウィンドウを終了しようとすると、BEditorWindowクラスにB_QUIT_REQUESTEDメッセージが通知されます。ここで、ウィンドウの終了を許可すると、BTinyEditorAppクラスにMSG_CLOSEDメッセージを発行します。BTinyEditorAppクラスはMSG_CLOSEDメッセージの通知を受けたら、そのウィンドウをリストから削除します。リストの登録件数が0になったら、自分自身にB_QUIT_REQUESTEDメッセージを発行し、アプリケーションを終了します。

 今回は、大きくプログラムを修正していますし、プログラム自体もかなり大きなものとなってしまいましたので、ソースファイルは圧縮ファイルをダウンロードするか、このページの末尾にそれぞれのソースのリンクを作成しておきましたので、そちらをご覧ください。これ以降の説明は、そのソースを前提に行います。
 前回、編集中のファイル情報をeditingfileに渡して保存していましたが、この状態で複数ファイルを扱うようにしますと、一見うまく動作しているように見えますが、ファイルを読み込んだ後にプログラムを終了すると、エラーが発生してしまいます。この原因を追及していくと、editingfileが原因となっていることがわかりました。(実は、これを調べるのに、随分と見当違いな方面の調査を続けてしまい、それが更新が遅れてしまった結果だったりします。)
 保存しておきたいのはファイルのパスだけですので、BEntryで保存するのではなく、BPathで保存するように全体を改造しました。BEntry *editingfileをBPath *editingpathに変更しています。


 ところで、勘の良い人はお気づきかもしれませんが、BTinyEditorAppクラスのコンストラクタでAddEditor関数を使用していると、一つ、困ったことが起きてしまいます。それは、アイコンにファイルをドロップしてアプリケーションを起動した場合です。
 この時は、コンストラクタでAddEditor関数を実行していると、まず一つウィンドウが作成され、更にB_REFS_RECEIVEDメッセージにより、もう一つウィンドウが作成されてしまいます。つまり、起動した直後であるのに、二つのウィンドウが作成されてしまうのです。これは何となく気持ち悪いですね。
 そこで、コンストラクタではAddEditor関数を使用しないようにします。こうすれば、B_REFS_RECEIVEDメッセージを処理する場合のみウィンドウが作成されるようになります。ただ、このままでは、普通にアプリケーションを起動した際にウィンドウが表示されなくなってしまいますので、BTinyEditorAppクラスのReadyToRun関数をオーバーライドして、リストの登録件数が0の場合、AddEditor関数を実行するようにします。(ReadyToRun関数は、アプリケーションの実行準備が整った時点で実行されます。)

 さて、それも踏まえて、プログラムをみていきましょう。

/**** ファイル名 : BTinyEditor.cpp ****/

//--------------------------------------------------------------------- 
void BTinyEditorApp::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what) 
    {        
    case MSG_OPEN: 
        open_filepanel->Show(); 
        break; 
    case MSG_QUIT: 
        for(int i=EditorList->CountItems()-1;i>=0;i--) 
            ((BEditorWindow *)EditorList->ItemAt(i))-> 
                                    PostMessage(B_QUIT_REQUESTED);                            
        break; 
    case MSG_CLOSED: 
        { 
            BEditorWindow *targetwnd; 
                        
            msg->FindPointer("wnd",(void **)&targetwnd); 
                        
            if(targetwnd!=NULL) 
            { 
                targetwnd->Quit(); 
                EditorList->RemoveItem(targetwnd);

                if(EditorList->CountItems()==0)
                    PostMessage(B_QUIT_REQUESTED); 
            } 
        } 
        break; 
    case MSG_ADDEDITOR: 
        AddEditor(); 
        break; 
    default: 
        BApplication::MessageReceived(msg); 
    } 
} 
//--------------------------------------------------------------------- 
void BTinyEditorApp::RefsReceived(BMessage *message) 
{ 
    type_code ref_type; 
    int32 ref_count; 
    entry_ref ref; 
    BEntry entry; 
        
    message->GetInfo("refs", &ref_type,&ref_count); 
    for(int i=0;i<ref_count;i++) 
    { 
        message->FindRef("refs",i,&ref); 
        if(entry.SetTo(&ref)==B_OK) 
        { 
            if(entry.IsFile()) 
            { 
                BEditorWindow *targetwnd; 

                targetwnd=AddEditor(); 

                targetwnd->Lock(); 
                targetwnd->OpenFile(&entry); 
                targetwnd->Unlock(); 
            } 
        } 
    } 
} 
//---------------------------------------------------------------------
void BTinyEditorApp::ReadyToRun()
{
    if(EditorList->CountItems()==0)
        AddEditor(); 
}
//---------------------------------------------------------------------

 MessageReceived関数をみてみると、MSG_QUIT、MSG_CLOSED、MSG_ADDEDITORのメッセージに対する処理が追加されています。
 MSG_QUITメッセージを受けた際の処理では、EditorListの件数分ループし、すべてのウィンドウにB_QUIT_REQUESTEDメッセージを発行しています。
 MSG_CLOSEDメッセージを受けた際の処理では、メッセージから"wnd"の名前で対象ウィンドウのポインタを取得します。対象ウィンドウはQuit関数で終了し、EditorListリストから削除します。また、EditorListリストの登録件数が0になったら、自分自身にB_QUIT_REQUESTEDメッセージを発行します。
 MSG_ADDEDITORメッセージを受けた際の処理では、AddEditor関数を実行してウィンドウを追加します。

 RefsReceived関数では、まず、GetInfo関数で、メッセージの"refs"に何件の情報が格納されているかを、ref_countに取得します。件数分ループし、"refs"から情報を取得、取得した情報がファイルであれば、AddEditor関数でウィンドウを追加し、追加したウィンドウのOpenFile関数でファイルを読み込みます。

 ReadyToRun関数は、EditorListリストの件数が0の場合に、AddEditor関数でウィンドウを追加します。


 MainWindow.hには、メッセージ定義、
#define MSG_CLOSED    'mcld' 
#define MSG_QUIT      'mqit' 
#define MSG_ADDEDITOR 'madd' 
の追加と、BEditorWindowクラスの定義に
class BEditorWindow : public BWindow 
{ 
private: 
    BFilePanel *save_filepanel; 
    bool save_to_quit; 
    void SetFileName(BEntry *entry); 
public: 
    BPath *editingpath; 
    BEditorView *mainview; 
    //----------------------------------------------------------------- 
editingpathが追加されています。editingpathは、編集中のファイルのパスを保存するために使用します。save_to_quitについては、後で説明します。


 MainWindow.cppでは、多くの点が変更されています。
 まずは、コンストラクタでFileメニューを定義している部分をみてみます。
    BMenu *filemenu=new BMenu("File"); 
    filemenu->AddItem(new BMenuItem("Open...",new BMessage(MSG_OPEN))); 
    filemenu->ItemAt(0)->SetTarget(be_app); 
    filemenu->AddItem(new BMenuItem("New Text",new BMessage(MSG_ADDEDITOR))); 
    filemenu->ItemAt(1)->SetTarget(be_app); 
    filemenu->AddItem(new BMenuItem("Save",new BMessage(MSG_SAVE))); 
    filemenu->ItemAt(2)->Message()->AddBool("to_quit",false); 
    filemenu->AddItem(new BMenuItem("Save As...",new BMessage(MSG_SAVEAS))); 
    filemenu->ItemAt(3)->Message()->AddBool("to_quit",false); 
    filemenu->AddSeparatorItem(); 
    filemenu->AddItem(new BMenuItem("Close",new BMessage(B_QUIT_REQUESTED))); 
    filemenu->AddSeparatorItem(); 
    filemenu->AddItem(new BMenuItem("Quit",new BMessage(MSG_QUIT))); 
    filemenu->ItemAt(7)->SetTarget(be_app); 
    mainmenu->AddItem(filemenu); 
 MSG_ADDEDITORメッセージを発行する「New Text」が追加されています。メッセージの送信先はbe_app、つまりBTinyEditorAppクラスになっています。
 「Save」、「Save As」は、メッセージにBool値「to_quit」を付加しています。どちらも値はfalseを設定しています。
 「Quit」は、メッセージがMSG_QUITになり、かわりに「Close」がB_QUIT_REQUESTEDメッセージを発行するように変更しています。

 一般的なテキストエディタは、編集中のテキストが美保損の場合、終了時に保存するか否かをきいてきます。今回は、この機能も追加してみました。
 その実現方法ですが、単純にBMemoViewクラスにModified変数を追加して、編集が行われると、Modified変数の値をtrueにするようにしています。Modified変数はパブリックにしておき、保存時や読み込み時等にfalseを設定します。

/**** ファイル名 : BMemoView.h ****/

#ifndef BMEMOVIEW 
#define BMEMOVIEW 
//--------------------------------------------------------------------- 
#include <Be.h> 
//--------------------------------------------------------------------- 
class BMemoView : public BTextView 
{ 
public: 
    bool Modified; 
    BMemoView(BRect frame,const char *name,uint32 resizingMode); 
    void MessageReceived(BMessage *msg); 
    void KeyDown(const char *bytes,int32 numBytes); 
}; 
//--------------------------------------------------------------------- 
#endif

/**** ファイル名 : BMemoView.cpp ****/

//--------------------------------------------------------------------- 
#include "BMemoView.h" 
//--------------------------------------------------------------------- 
BMemoView::BMemoView(BRect frame,const char *name,uint32 resizingMode) 
    :BTextView(frame,name,BRect(0,0,frame.Width(),frame.Height()), 
               resizingMode,B_WILL_DRAW) 
{ 
    Modified=false; 
} 
//--------------------------------------------------------------------- 
void BMemoView::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what) 
    { 
        case B_UNDO: 
        case B_COPY: 
        case B_CUT: 
        case B_PASTE: 
            Modified=true; 
        default: 
            BTextView::MessageReceived(msg); 
    } 
} 
//--------------------------------------------------------------------- 
void BMemoView::KeyDown(const char *bytes,int32 numBytes) 
{ 
    switch(*bytes) 
    { 
        case B_LEFT_ARROW: 
        case B_RIGHT_ARROW: 
        case B_UP_ARROW: 
        case B_DOWN_ARROW: 
        case B_INSERT: 
        case B_HOME: 
        case B_END: 
        case B_PAGE_UP: 
        case B_PAGE_DOWN: 
        case B_FUNCTION_KEY: 
            break; 
        default: 
            Modified=true; 
    } 
    
    BTextView::KeyDown(bytes,numBytes); 
} 
//---------------------------------------------------------------------

 ソースをみればわかるとは思いますが、B_UNDO、B_COPY、B_CUT、B_PASTEといった編集処理、カーソルやInsert等の特殊なキーを除いたキーの落下時にModified変数をtrueにすることで、編集作業が行われたらModified変数がtrueになるようにしています。


 これを踏まえた上で、MainWindow.cppを更にみてみます。
 ウィンドウを閉じる際の処理は、QuitRequested関数に記述します。今まではbe_appにB_QUIT_REQUESTEDメッセージを発行していただけですが、未保存の確認やBTinyEditorAppへの通知等、処理が大幅に増えています。
//--------------------------------------------------------------------- 
bool BEditorWindow::QuitRequested() 
{ 
    BMemoView *memo; 
    bool result; 
    
    memo=((BMemoView *)FindView("memo")); 
     
    result=false; 
    if(memo!=NULL) 
    { 
        if(memo->Modified) 
        { 
            char m[1024]; 
            int32 idx; 
            BAlert *wrnmsg; 
            
            sprintf(m,"Save changes to the document \"%s\"?",
                    (editingpath!=NULL)?editingpath->Leaf():"Untitled"); 

            wrnmsg=new BAlert("Warning!!", m, 
                      "Cancel","Don't save","Save", 
                              B_WIDTH_AS_USUAL,B_OFFSET_SPACING, 
                              B_WARNING_ALERT); 
            wrnmsg->SetShortcut(0,B_ESCAPE); 
            idx=wrnmsg->Go(); 
            if(idx==0) 
                return false; 
            if(idx==2) 
            { 
                BMessage *msg=new BMessage(MSG_SAVE); 
                msg->AddBool("to_quit",true); 

                PostMessage(msg);                   
                return false; 
            } 
        } 
    } 

    BMessage *msg_closed=new BMessage(MSG_CLOSED); 
    msg_closed->AddPointer("wnd",this); 
    be_app->PostMessage(msg_closed); 

    return true; 
}; 
//---------------------------------------------------------------------
 BMemoViewクラスのModified変数を調べ、trueならば、BAlertクラスで保存するか否かを確認します。「Cancel」が選択された場合は、falseを返して関数を終了します。QuitRequested関数の戻り値としてfalseを設定すると、ウィンドウの終了処理が中断されます。
 「Save」ならば、MSG_SAVEメッセージに「to_quit」をtrueで付加してメッセージを発行し、falseを返して関数を終了します。
 「Don't Save」を選択するか、Modified変数の値がfalseであれば、MSG_CLOSEDメッセージに「wnd」の名前で、自分自身のポインタを付加して、be_appに発行し、trueを返して関数を終了します。

 MSG_SAVEメッセージの「to_quit」は終了処理の場合はtrue、メニューから「Save」や「Save As」を選択した場合はfalseを設定します。SaveFile関数で、この値を調べ、trueならば終了処理中なので、もう一度B_QUIT_REQUESTEDメッセージを発行します。save_to_quit変数は、MessageReceived関数で取得した「to_quit」の値をSaveFile関数に渡すために使用します。
//--------------------------------------------------------------------- 
void BEditorWindow::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what) 
    { 
        case B_UNDO: 
        case B_COPY: 
        case B_CUT: 
        case B_PASTE: 
        case B_SELECT_ALL: 
            ((BMemoView *)FindView("memo"))->MessageReceived(msg); 
            break; 
        case MSG_SAVE: 
            msg->FindBool("to_quit",&save_to_quit); 
                        
            if(editingpath!=NULL) 
            { 
                BEntry entry;
                entry.SetTo(editingpath->Path());
                SaveFile(&entry);
                break;
            }
        case MSG_SAVEAS: 
            msg->FindBool("to_quit",&save_to_quit); 

            save_filepanel->SetTarget(this); 
            save_filepanel->Show(); 
            break; 
        case B_SAVE_REQUESTED: 
            { 
                entry_ref ref; 
                BString nam; 
                BDirectory dir; 
                BEntry entry; 

                msg->FindRef("directory",&ref); 
                msg->FindString("name",&nam); 
                dir.SetTo(&ref); 

                if(entry.SetTo(&dir,nam.String())==B_OK) 
                { 
                    SaveFile(&entry); 
                    return; 
                } 
            } 
            break;        
        default: 
            BWindow::MessageReceived(msg); 
    } 
} 
//--------------------------------------------------------------------- 
void BEditorWindow::SaveFile(BEntry *entry) 
{ 
    BFile fil; 
    off_t wsize; 
    off_t tsize; 

    try 
    { 
        fil.SetTo(entry,B_READ_WRITE | B_CREATE_FILE); 
        tsize=strlen(((BTextView *)FindView("memo"))->Text()); 
        wsize=fil.WriteAt(0,((BTextView *)FindView("memo"))->Text(), 
                  tsize); 
        
        if(tsize==wsize) 
        { 
            SetFileName(entry); 
            
            if(save_to_quit) 
                PostMessage(new BMessage(B_QUIT_REQUESTED)); 
        } 
    } 
    catch(...) 
    { 
        BAlert *altmsg = new BAlert("Error", "File write failed!!","OK", 
                        NULL,NULL,B_WIDTH_AS_USUAL, 
                        B_WARNING_ALERT); 
        altmsg->Go(); 
    } 
} 
//--------------------------------------------------------------------- 
 SaveFile関数では、保存が正常に終了した場合、save_to_quitの値を調べて、trueならばB_QUIT_REQUESTEDメッセージを発行します。

 BMemoViewのModifiedをfalseに設定しなおすタイミングですが、新規テキスト、ファイルオープン、ファイル保存と全てウィンドウのタイトルを設定しているタイミングですので、SetFileName関数が呼ばれたら、その中で設定します。
//--------------------------------------------------------------------- 
void BEditorWindow::SetFileName(BEntry *entry) 
{ 
    if(editingpath==NULL) 
        editingpath=new BPath(); 
    entry->GetPath(editingpath); 
    
    if(editingpath!=NULL) 
    { 
        char nam[B_FILE_NAME_LENGTH]; 
        
        entry->GetName(nam); 
        SetTitle(nam); 
    } 

    ((BMemoView *)FindView("memo"))->Modified=false; 
} 
//--------------------------------------------------------------------- 

 今回はここまでで終了です。次回は検索画面でも作ってみようかと思っています。
 今回は、editingfileが原因のエラーに悩まされて、今までで一番苦労しました。まぁ、おかげでデバッガの使い方も少しはわかりましたし・・・。悪い事だけじゃないんで、まだマシですかね。

ソースリスト
圧縮ファイル
R5 Intel環境で確認
BTinyEditor20000530.zip
ソースファイル BTinyEditor.cpp
BTinyEditor.h
BMemoView.cpp
BMemoView.h
main.cpp
MainWindow.cpp
MainWindow.h

次の項目へ

トップページへ戻る