]>

# KOM を使う / OpenParts の使い方

Simon
Hausmann

    hausmann@kde.org

1999/06/18
1.00.00

このドキュメントは標準的な Qt/KDE アプリケーションフレームワークに通じており、 自分のアプリケーションにKDEのコンポーネント技術、KDE オブジェクトモデル(KOM)及び OpenParts を利用することに興味を持っているプログラマのためのものです。このドキュメントは、なぜ KOM/OP を利用すべきなのか、そしてどのようにそれを利用すれば良いのかの両方をカバーしています。

KDE
KOM
OpenParts
CORBA
MICO

## はじめに

このドキュメントは標準的なQt/KDEアプリケーションフレームワークに通じており、自分のアプリケー ションにKDEのコンポーネント技術であるKDE Object Model(KOM)と OpenParts を利用することに興味を 持っているプログラマのためのものです。標準的なKDEアプリケーションでコンポーネントを利用する 場合の目標は次のようなものです。

- アプリケーションモジュールを、他のアプリケーションでも利用でき、 書かれている言語や稼働しているプラットフォーム/マシンに関する問題なく、 再利用できるコンポーネントにすることです。

- OpenParts の埋め込み機能を利用し、 アプリケーションにシームレス グラフィカル インテグレーションを備える 共有GUI要素を管理する方法をつくり出す。

- 簡単なプロセス間通信方法として CORBA や KOM を利用します。

Torben Weis による KOM/Openparts についての記事 をお読みになることをお勧めします。 Torben Weisは KOM/Openparts の 著作権者であり、創作者であり、マスターであり、神です。

## KDE オブジェクトモデル (KOM)

### 通信の第一段階 - KOM::Base インタフェース

`
libkom` 内のベースインタフェース及びインプリメンテーションは、KOM を使用して CORBAオブジェクト間の標準化された通信に基本的な機能を提供します。

- シグナルとスロット、これは Qtツールキットで使われているメカニズムに似ています。

- イベント操作、フィルタリングも含まれます。

- 関係するものの管理

- リファレンスのカウント

### シグナルとスロット

使用方法とインプリメンテーションに若干に違いはありますが、 KOM は Qtツールキットのようなシグナルとスロットをサポートしています。 まず、最大の違いはシグナルとスロットがもはやどのようなタイプセーフでもないということです。 つまり、接続しているとき、実行時に型をチェックするようにする KOM シグナル/スロットの メタデータを作成している mocコンパイラがないということです。

もう一つの相違点は、シグナルとスロット機能を宣言する方法です。 シグナルは、シグナル引数を指定せずに `
SIGNAL&lowbar;IMPL`マクロを用いて、 `
komBase.h`から宣言されなければなりません。スロットメソッドは CORBAインターフェースディスクリプション に定義されていなければなりません。

KOM シグナルにとって、Qt から出される "emit"キーワードと同等なのは、 `
SIGNAL&lowbar;CALLx`マクロで、 "x"は引数の数に従い、0、1、2の内の一つです。
例えば：

CORBA インターフェースディスクリプション:

      #include <kom.idl>

      interface FooSender : KOM::Base
      {
        signal void mySignal( in long foobaz );
      };

      interface FooReceiver : KOM::Base
      {
        slot void mySlot( in long gosh );
      };

FooSender の実装:

      FooSender_Impl::FooSender_Impl( ... )
      {
        ...
        SIGNAL_IMPL( "mySignal" );
        ...
      }

      FooSender_Impl::mySignal( CORBA::Long foobaz )
      {
        SIGNAL_CALL1( "mySignal", foobaz );
      }

FooReceiver の実装:

      FooReceiver_Impl::mySlot( CORBA::Long gosh )
      {
        ...
      }

最初に センダについていくつかあります。 インターフェースディスクリプションでシグナルを定義する必要はありません。 また単にシグナルを発信する実装を提供する必要もありません。 しかしながら多くの場合、これは推奨されていることでもあります。 というのはこれは、開発者が実装のソースの中にこのオブジェクトが発信する シグナルを見つけ出そうと努力する必要が無くなるため、開発者がインターフェースを使用する のをより簡単にしてくれるからです。 もう一つの方法は、方法を定義することなくインターフェースディスクリプションに シグナルの存在を単純に明記することです。シグナルを発信する際、技術的なポイントから `
SIGNAL&lowbar;CALLx` マクロのみカウントします。

次のものは接続と切断です。上記の例では次のようになります:

      ...
      SenderObject->connect( "mySignal", ReceiverObject, "mySlot" );
      ...
      SenderObject->disconnect( "mySignal", ReceiverObject, "mySlot");

これは全く自明のことだと思われますが、切断に際しては常にオブジェクトから切り離すことを忘れないでください。

### イベント

イベントは2つの要素、イベントネームつまり文字列とイベント引数 CORBA::Any value で 成り、それゆえ開発者は自由に選択できます。

指定されたオブジェクトに送られたイベントは、イベントが目的地となるオブジェクトまでの パイプを通されていると考えることができます。 このパイプはインストールされているイベントフィルタで満たされています。 フィルタには三種類があります。

- 読み込みフィルタ ( `
  FM&lowbar;READ`)

- 書き込みフィルタ ( `
  FM&lowbar;WRITE`)

- 実行フィルタ ( `
  FM&lowbar;IMPLEMENT`)

KOM の現在の実装では、これらのフィルタモードはイベント処理の命令を指示するだけです。 イベントが発信される場合、まずフィルタモード`
FM&lowbar;WRITE`で全てのイベントフィルタがフィルタリングされます。 そして`
FM&lowbar;IMPLEMENT`に続き、 最終的に`
FM&lowbar;READ`と続きます。 イベントフィルタには実際のイベントで行うことができる二つの可能性があります。 これらのフィルタはイベントを単に読み込むだけ可、 あるいはそのイベントを廃棄することができます。 つまりそのイベントは破棄され、その行き先のオブジェクトを決して受け取らないということです。

実際のイベントネームは、イベントフィルタに関して特別な意味を持っています。 あるオブジェクトに対してイベントフィルタをインストールした場合、 フィルタオブジェクトのリファレンスやフィルタマッピング機能の名称に加えて、 一連のいわゆるイベントタイプパターンについても指示しなければなりません。 あるイベントタイプパターンは、特別なパターンだけではなく単一のイベント名称です。 (イベントパターンについて、詳しくは`
kom.idl`を ご覧ください)。

イベントがフィルタによって処理される際、引数としてのイベントネームとイベント値で 指定されたフィルタ関数が呼ばれます。 このフィルタ関数は、イベントが廃棄されるかどうかを(ブール値を通じて) 返さなければなりません。

全てのフィルタリングが実行され、インストールされたどのフィルタがイベントを破棄しない場合、最終的に行き先のオブジェクトによって受け取られます。 つまり、オブジェクトの`
event()`メソッドを呼ぶことによって KOM::Base
インターフェースに定義されます。 デフォルトの実装は実際には何も行いません。 ですからもしかするとこの仮想関数の再実装が必要になるかもしれません。

イベントの低レベルな使用方法は、イベント送信用オブジェクトの receive/receiveASyncメソッドを呼んだり、イベントをマッピングするために
KOMBase::event を 再インプリメントすることです。 しかし KOM には、イベント処理を単純化する素晴らしいマクロがいくつか用意されています。

#### イベントの送信

送信用に komBase.h は、役立つ`
EMIT&lowbar;EVENT`マクロを定義してあり、全て同じ文法`EMIT_EVENT_x(destination_object, event_name, event_argument)`を使用しています。

"destination&lowbar;object" は、イベントの受け取り/処理を予定しているオブジェクトのリファレンスです。 イベントは、この目的オブジェクトにインストールされている全てのイベントフィルタで処理されます。 "event&lowbar;name" は、自明でしょう;-)。 イベント引数は、特定のマクロに従います。それらは特に以下のようなものです：

- `EMIT&lowbar;EVENT`, 送信用一般的なマクロです。 イベント引数には、CORBA::Any で定義された `<<=`演算子が必要です。 イベント引数が例えば一つの構造である時は必ずこのマクロを使う必要性があるでしょう。 ("--any"を使って idlファイルをコンパイルすることを忘れないでください。idlコンパイラは必要な演算子メソッドを作成してくれます。)

- `
  EMIT&lowbar;EVENT&lowbar;BOOLEAN`, ブールイベント引数に便利です。 `EMIT&lowbar;EVENT`マクロとの違いは、ブール値を変換するのに CORBA::Any::from&lowbar;boolean を使うことです。 ですから、以下のものをどう使うかは実際は問題ではありません。

  `EMIT_EVENT( receiver, name, CORBA::Any::from_boolean( value ) );` または `EMIT_EVENT_BOOLEAN( receiver, name, value );`

- `
  EMIT&lowbar;EVENT&lowbar;OCTET`,EMIT&lowbar;EVENT&lowbar;BOOLEAN と類似し
  CORBA::Octet値に使用できます。

- `
  EMIT&lowbar;EVENT&lowbar;CHAR`,EMIT&lowbar;EVENT&lowbar;BOOLEANと類似し、char (
  CORBA::Char)値に使用できます。

- `
  EMIT&lowbar;EVENT&lowbar;WCHAR`,EMIT&lowbar;EVENT&lowbar;BOOLEANと類似し、
  CORBA::WChar値に使用できます。

- `
  EMIT&lowbar;EVENT&lowbar;STRING`, EMIT&lowbar;EVENT&lowbar;BOOLEANと類似し、char *(
  CORBA::Char*)値に使用できます。

- `
  EMIT&lowbar;EVENT&lowbar;WSTRING`, EMIT&lowbar;EVENT&lowbar;BOOLEANと類似し、
  CORBA::WChar*値に使用できます。

- `
  EMIT&lowbar;EVENT&lowbar;OBJECT`, EMIT&lowbar;EVENT&lowbar;BOOLEANと類似し、CORBAオブジェクト (
  CORBA::Object)に使用できます。

#### イベントの受信

イベントの受信は送信に比べて若干困難です。 というのは、あらゆる種類のイベントを処理しなければならないので、 オブジェクトは1つのハンドラメソッド KOM::Base
::event (IDL
) / KOMBase::event (C++)で受け取ることができるます。 イベントの送信のように、低CORBAレベルで全てのことを行うことができます。 しかし、なぜ堅実な方法を採ろうとするのでしょう？ KOMは、これに関してもとても素晴らしく、使いやすいマクロがあります(komBase.hで定義されています):-)。 たいていが次のサンプルのような感じになっています:

    #include <kom.idl>

    module MyModule
    {
      // イベント引数は文字列
      const string eventFirstFoo = "MyFooEventNameOrWhateverYouNameIt";

      struct MyStruct
      {
        boolean kde_rules;
      };

      const string eventSecondFoo = "Blaafooo";
      typedef MyStruct EventSecondFoo;

      interface SomethingElse
      {
        //..
      };

      const string eventThirdFoo = "KOMIsCool";
      typedef SomethingElse EventThirdFoo;

      interface Foo : KOM::Base
      {
        //...
      };

    };

    bool FooImpl::event( const char *event, const CORBA::Any &value )
    {
      EVENT_MAPPER( event, value );

      MAPPING_STRING( MyModule::eventFirstFoo, mappingFirstFoo );

      MAPPING( MyModule::eventSecondFoo, MyModule::EventSecondFoo, mappingSecondFoo );

      MAPPING( MyModule::eventThirdFoo, MyModule::EventThirdFoo_ptr, mappingThirdFoo );

      END_EVENT_MAPPER; //マクロは "return false;" を実行します。
                        //このポイントに達したときイベントを取り扱わなかったことを示します。
    }

    bool FooImpl::mappingFirstFoo( const char *myArgument )
    {
      ...
      //忘れずにブール値を用いて返して下さい。
      //イベント処理が成功したかどうかを暗示します。
    }

    bool FooImpl::mappingSecondFoo( MyModule::MyString anotherArg )
    {
      //...
    }

    bool FooImpl::mappingThirdFoo( MyModule::SomethingElse_ptr whaaboo )
    {
      //...
    }

ご覧のように、イベントハンドラはたいてい`EVENT&lowbar;MAPPER`マクロで始まっており、`END&lowbar;EVENT&lowbar;MAPPER`で終わっています。 `EMIT&lowbar;EVENT&lowbar;x`マクロと類似し、 `MAPPING`マクロは通常の`MAPPING`マクロと以下のものから構成されています：

- `MAPPING&lowbar;BOOLEAN`

- `MAPPING&lowbar;OCTET`

- `MAPPING&lowbar;CHAR`

- `MAPPING&lowbar;WCHAR`

- `MAPPING&lowbar;STRING`

- `MAPPING&lowbar;WSTRING`

- `MAPPING&lowbar;OBJECT`

多少イベント処理を構造化するには、どのイベントも関数を取り扱っている独自のイベントを得ます。 これらの関数はマッピングマクロ(最後の引数)で呼ばれます。 上記で用いられたネーミングスキームは、必要ではありませんが、KOMを使用しているアプリケーションではだいたい用いられています。

### Adopting

オブジェクトにリファレンスを保たせ、そしてオブジェクトがその全てのリファレンスから開放されたがために機能を失った場合の情報を得たいときは必ず adopting を利用すべきでしょう。 しかしこれは、親オブジェクトでない場合のみ使われるべきです。つまり KOMリファレンスカウンタで直接オブジェクトをリファレンスしないということです。 adoptingを使う場合は、ご自分のオブジェクトの`leaveNotify`(もしかすると`adoptNotify`)メソッドを再実装すべきでしょう。(オリジナルの KOMBaseメソッドを呼び忘れないでください。)

### KOMリファレンス

KOMリファレンスカウンティングは、所有するオブジェクトを "高速"にするのに使用するべきでしょう。 これは、リファレンスにサーバオブジェクト上の振る舞いを直接カウントさせることによって、 オブジェクトのライフサイクルを直接操作できるようになります。 つまり CORBA リファレンスカウンタとは対照的です。 CORBA リファレンスカウンタは、遠隔オブジェクトの場合には、リファレンスカウンタはスタブオブジェクトに従って動作します。(これはローカルオブジェクトの違いです。この場合、スタブ = サーバオブジェクトです。) KOMリファレンスカウンタがゼロに落ちると、オブジェクトは破壊されます。 この崩壊は、他のオブジェクトへの全てのコネクションを閉じ、関連性は残す`cleanUp()`を呼ぶことによってなされます。 このコールが終了した後、オブジェクトは本当にリリースされます。 `cleanUp()`メソッドの再実装が必要かもしれません。 この場合は、以下の2つのことを忘れないでください:

- ブール変数`m&lowbar;bIsClean`が falseの時のみ実行、 そうでなかれば単にリターンします。

- 以前の実装を確実に呼んでください !

このKOMリファレンス関係の概要として心に留めておいてほしいことは、リファレンスをオブジェクトに保たせておく方法が以下の3つあるということです:

- シグナル/スロット、もしくはイベントフィルタを通してオブジェクトに接続する。

- 親オブジェクトである。つまりKOMリファレンスカウンタでリファレンスを保持します。

- さもなければ、adopting を使用すべきでしょう。そうすれば、オブジェクトがその機能を失った、つまり上記にあげたどの種別に当てはまらないオブジェクトへの全リファレンスを解放した際の情報を得られます。 この考えの背景は、オブジェクト間の全コネクション/リファレンスが対称的ということです。 つまり、両者がコネクションについていっており、 一方がその機能を失ったときは常に両者が、全コネクションを切断します。 シグナル/スロットコネクションの場合、これは KOMで自動的に行われます。 KOMリファレンスカウンタの場合、オブジェクトのライフサイクルを直接操作することができます。3つ目の方法としてAdoptingは、シグナル/スロットコネクションでもKOMリファレンスカウンタ経由の直接リファレンスでもない、その他の対称的なコネクションを維持するために使われます。

ヒント：`KOMVar`テンプレートを使用するとKOMリファレンスを操作するのがより簡単になります。このリファレンスはCORBA &lowbar;var形式と同じように使用することができます。

### コンポーネント - KOM::Component
インタフェース

コンポーネントインタフェースは、ベースインタフェースから派生し、更にこのコンポーネント用の小さなインタフェースを提供します。ダイナミック集約演算による新しいインタフェース提供の可能性とプラグインコンポーネントを追加する標準的な方法を結合させています。 これによってCORBAオブジェクトは、起動時に機能を強化性を持ちます。

5種類のインタフェースがあります:

- 組み込みインタフェース(派生型インタフェース)

- 組み込み集合体インタフェース

- ダイナミック集合体インタフェース

- 組み込みプラグインインタフェース

- ダイナミックプラグインインタフェース

組み込みインタフェースは全て、オブジェクトが直接実装するインタフェースです。これはそのイ ンタフェースが実際のオブジェクト実装の一部であり、`
ADD&lowbar;INTERFACE`マクロ(
komComponent.hの)経由で、指定できるということです。 ですから例としてインタフェースディスクリプションは次のようなものになります:

    module Foo
    {
      interface MyInterface : AnotherInterface
      {
        ...
      };
    };

このインタフェースの実装構造に次の行を追加すべきでしょう:

    ConstructorNameOfMyInterface::ConstructorNameOfMyInterface( ... )
    {
      ...
      ADD_INTERFACE( "IDL:Foo/MyInterface:1.0" );
      ...
    }

このようにしてコンポーネントがインタフェース"Foo/インタフェース"をサポートしており、 したがって、3つの関数`getInterface()`、`interfaces()`、`supportsInterface()`によってそれを利用できるということを示しています。

### 集約演算でコンポーネントの拡張 - KOM::Aggregate

Aggregateは分散オブジェクトの問題、及びその派生問題を解決します。 インタフェースの実装は、完全にカプセル化されているため、既に存在するオブジェクトの機能拡張には別の方法がなければなりません。 aggregateコンポーネントを使用することで、オブジェクトに対して新しいインターフェースを起動時に追加することができます。 つまり機能の拡張はしますが、オブジェクト自体の動作変更はしません。

#### 組み込み集約体

組み込み集約体インタフェースは、コンポーネントとして同じ過程で起動する集約体実装のインタフェースです。 komComponent.hには、組み込み集約体を追加する`
KOMComponent`クラスの4つの関数があります。簡単ですよ。

#### ダイナミック集約体

ダイナミック集約体は2点の違いはありますが、組み込み集約体と類似しています:

- AggregateFactories (kom.idl
  をご覧下さい。)を使ってコンポーネント外からインストールされます。 こうする事で、同過程または同じコンピュータで動かす必要がなくなります。

- その他の利用可能なインタフェースに従います。 KOMがこれらの依存問題を解決してくれます。

### プラグイン - KOM::Plugin
インタフェース

プラグインは、集合体への断片です。 これは新しいインタフェースを提供することでオブジェクトの機能拡張はしませんが、その代わり通常その動作を下記の手段で変更します。

1. オブジェクトにイベントフィルタをインストールするようなこと

2. 特別なプラグインインタフェースの提供

### コンポーネントの収集 - KOM::Container
インタフェース

コンテナは単純なことしかしませんが、大変に役に立ち。ます。 これはコンテナメンバ用のレポジトリとしての役割をします。 コンテナメンバの構造は、次の要素からなっています:

- メンバ名、自由に選択できかつ単一の文字識別子。

- メンバオブジェクト、 KOM::Base
  タイプ。コンテナメンバの追加、削除、置換、リスト、検索を行い、コンテナ全体を単にクリアすることもできます。 しかもメンバが追加されたり削除されたりすると必ずコンテナは、シグナルを発します。

### ファクトリ

KOMは、2つの抽象的ファクトリインタフェースを含んでいます。

1. KOM::AggregateFactory

2. KOM::PluginFactory

共にオブジェクト作成ジョブに従い、ダイナミックプラグインと集合体コンポーネント作成に必要とされます。 そのような動的に作成されたオブジェクトをコンポーネントにインストールしたい場合は必ず、ファクトリインタフェースを実装しなければなりません。

### KOMアプリケーション

KOMアプリケーション
は、Kアプリケーション
用のドロップイン置換で、KDEアプリケーションでCORBAを使用する際必要です。 これは内部で、CORBAイベント処理をQtイベント処理と組み合わせ、スタートアップ時にORB
とBOA
を初期化します。 komApplication.hには、ORB/BOAにリファレンスを与える次の2つのスマートなマクロ:`komapp&lowbar;orb`と`komapp&lowbar;boa`が定義してあります。 通常、KOMアプリケーションから派生する独自のアプリケーションクラスの使用、そして`start()` かつ/または `restore()`の再実装を必要とするでしょう。それらは、オブジェクトをリストアしているBOAの状態に従って、>`
KOMApplication::exec()`から呼ばれます。 KOMアプリケーションのAPIについての更に詳しい情報は
komApplication.hをご覧ください。

### KOMシャットダウンマネージャー

異なる処理で稼働するKOMオブジェクトを動作する際、KDEはリソースの使用を最小限にするため、要求があり次第、自動的にこれらの処理を開始するツールを提供します。 KDEデーモンがこの機能を提供しますが、同様の方法で、他の処理を随時開始させたいなら、使わなくなったときにはそれらの処理をシャットダウンが必要かもしれません。 CORBAは、これに関していかなる規格も定義しないので、その独自のメカニズムについても説明しなければなりませんでした。

ですから基本的な質問は: 処理の中で全オブジェクトが停止されたと確信できるのはいつか ? ということです。 この質問に答えられれば、ORBを、それゆえ処理をいつシャットダウンするのかがわかります。

これに対する第一歩は、特定のオブジェクトがいつ停止されるのかを知ることです。 この答えはとても単純で: あるオブジェクトがもうリファレンスされなくなったとき、そのオブジェクトのリファレンスカウンタはゼロに低下し、オブジェクトはリリースされす。

ですからサーバプロセスがいつ"不使用"になったかを発見するためKOMのアプローチは、全KOMベースオブジェクトが停止されているか見るためです。というのはこのオブジェクトは、オブジェクト のライフサイクルに対して有効なサーバ側のリファレンスカウンタを実装しているからです。

KOMシャットダウンマネージャークラスは、このアプローチを実装するためのヘルパークラスです。これは KOMShutdownManager::self()経由でにアクセスする1-インスタンスクラスです。 ですからこれを作成したり削除したりすることについて気を配る必要はありません。

サーバアプリケーションにそれがもう必要でなくなったとき、自動的にシャットダウンさせるなら、 KOMシャットダウンマネージャーの使用は次のように行われます:

このクラスに、それが監視すべき全オブジェクトについて伝えなければなりません。 そして全ての監視されているオブジェクトが停止すると、タイマー(タイムアウト値はデフォルトでは1分ですが、これは設定できます)仕掛けてくれます。 タイムアウトになるとORBはシャットダウンし、アプリケーションも終了します。

ですから、リモートクライアント用オブジェクトが作成されるアプリケーションでの全ての場所 (大抵はファクトリ内)に、次ぎのような単純な一行を追加する必要があるかも知れません:

    ... (コード内で)
    //基本オブジェクトの作成
    MyFooObject *obj = new MyFooObject;

    //KOMShutdownManager がこのオブジェクトを監視していることの伝達
    KOMShutdownManager::self()->watchObject( obj );
    ...

いかがですか、自明でしょ? :-) 参考までに、kdelibs/corba/tutorials/kdedのkdedサンプルをご覧になるといいかもしれません。 KOMシャットダウンマネージャーかそこでも使われています。

説明不足だったり理解しにくかったりした場合は、どうぞ質問をなげてください。 私宛<hausmann@kde.org>に簡単なメールをお送りください。

## OpenParts

### はじめに

KOMをベースにした OpenParts テクノロジの目標は次のものです：

- グラフィカルに他のアプリケーションの"ウィジェット"を組込むことを容易にする方法の供給

- 共有GUI要素を管理するより良い方法を導き、その CORBA/KOM インタフェース/実装の提供

- ドキュメントビュモデルに対する基本サポートの実装

OpenParts の理解を早めるために、ちょっとした例を載せておくことにします:

ワードプロセッサと数式エディタがあり、2つは別々のアプリケーションと仮定します。 数式エディタを用いて、数式をワードプロセッサーのドキュメントに入力するとしたら、 いくつか問題が生じます: もちろん、XReparentWindow やそれに類するもの、もしくは更に簡単な QXEmbed を使って数式エディタのメインウィジェットウィンドウをはめ込むことはできます。 しかしその際、エディタのメニューバーやツールバーにアクセスせずにどうやって数式を編集するのでしょうか? これが数式ウインドウの一つなら、どう見てもこれはみっともないでしょう。 ワードプロセッサアプリケーションのメニュやツールバーが、一般的なメニュやツールバーアイテムを除いて数式エディタアプリケーションで置き換えられたなら、なかなか恰好良いと思いませんか?更に、テキストドキュメントに戻ったときには、古いメニュやツールバーが再現します。

これが OpenParts の素晴らしいところです :-) 。

OpenParts は、ビジュアルコンポーネントといった新しいシステム、メニュやツールバーのような共有 GUI 要素の新規作成法を導入することで、上述の問題を解決します。それらは随時動的です。

OpenParts の実装に際しては、全ての要素はたいてい2つのクラスからなっています。 そのインタフェース実装で、クラスネームが"If"で終わっているものと、Qt/KDEオブジェクトです。 例えば OpenParts ステータスバー要素は、次の2つのクラスで表されます: `
OPStatusBar`は、
KStatusBarから派生して、Qt/KDE特定の拡張を処理を行います。`
OPStatusBarIf`は、実際の
OpenPartsUI::StatusBar インタフェースを Qt/KDE 関数コールにインタフェース機能を"渡す"ことで、その実装を供給する役割を果たします。

OpenParts のどの Qt/KDE オブジェクトも、上記のようによくインタフェースに結びつけられるので、たいてい要素の OpenParts インタフェースにリファレンスを返す interface() 関数が存在します。 上記の例では、`
OPStatusBar::interface()`は、直接この`
OPStatusBar`オブジェクトに結びつけられる`
OPStatusBarIf`オブジェクトにリファレンスを返します。

### OPアプリケーション

KOMApplicationと類似し、
OPApplicationクラス(KOMApplicationから派生)は OpenParts を使用する際に必要となります。

###
コンポーネントのようなQWidget? - OpenParts::Part インタフェース

OpenParts では、独自のGUIを持ち、メインウインドウに表示されることになっているあらゆるウインドウが Part(ちょうど上記例での数式エディタビューまたはワードプロセッサのドキュメントビューのようなものです。)として呼ばれ、`OPPartIf`クラスから派生する
OpenParts::Partインタフェースを実装します。

ウィジェットクラスをフル機能の Part コンポーネントにしたいなら、 標準的なQt/KDEウィジェットフレームワークに固執し、以前の処理を行うよりも別のことを処理すべきです。 実際ウィジェットは、もはやデータを表示する単なるウインドウではありません、いいえ、Part はこれ以上です。 特に Part は、それがウインドウ(もちろんサブウインドウ、またはその他のPart(`
OPFrame`ドキュメントをご覧ください)を含むウィジェット)でありながら、フル機能GUIを備えています。それは、メニュ用メニュバー、ツールバー、ステータスバーで構成されています。

GUIを使用する素晴らしい点は、作成、処理/使用、そして無効にする際の方法にあります。 ユーザーが今アクティブにしたい Part を決定するので、これは高度に動的でなければなりません。 OpenParts は次の基本的なフレームワークを提供します:

- いつ GUI を作成すべきかを知らせ、OpenParts のオブジェクトに関係するGUIに必要なリファレンスを提供します。

- 同様にGUIが無効にされるときも知らせます。

- 更に Part がフォーカスを得て、そのフォーカスを受け入れるか否かの選択が通知されます。

- また、Part がメインウィンドウに登録されるとそれも通知されます。 それは、いくつかのGUIサーバントオブジェクトにコンポーネントを登録するためで、例えばメニューバーマネージャのような感じです。

まず、しなければならない最初の段階は、OpenParts に Part インタフェース(`
OPPartIf`)、そこから継承すべきクラス、実際のウィジェットが何かを通知することです。というのは`
OPartIf`はQWidgetから継承しないからです。 これは、Part コンポーネントが実際のウィジェットから切り離されているので柔軟です。 しかしこれを行う必要はありません。 単純に、`
OPartIf`や`
QWidget`、もしくは適当なウィジェットクラスから多様な継承がおこなえます。 どの場合でも、`setWidget( your&lowbar;widget&lowbar;here )`を呼ぶことで、Part ウィジェットを指定します。 多くの場合、Part コンポーネントが同時にウィジェットである場合、単に`setWidget( this )`を呼びます :-)。 このコールはクラスの構造内で行われなければなりません。

次に重要なポイントは、`
OPPartIf`の仮想`init()`関数を再実装が必要でしょう。 これは是非推奨したいところです。 というのはこの関数は、Part がメインウインドウに登録された後に呼ばれるからです。 この関数に背後にある意図は、Part の構成が実行される時、Part 自体はまだ明確にメインウインドウに登録されないということろにあります。 しかし実際、自分の Part が GUI サーバントオブジェクトにいつ登録されたか知る必要があります。というのは、GUIサーバントオブジェクトに Part が登録されるため、メインウィンドウインタフェース経由のみ有効だからです。 メインウインドウへのリファレンスは、`
m&lowbar;vMainWindow`変数で可能です。このは、メインウインド上の登録が行われるとに自動的に初期化されます。ですから`init()`関数が呼ばれる前にこの変数を使用しないでください。(`m&lowbar;vMainWindow`はとにかく、nil になります。)

`
init()`関数の詳細についてです。 ここでは、メインウィンドウへの登録に従う全初期設定を行うべきでしょう。 更に、GUI管理オブジェクトに上述の登録を行うことができます。 通常、これは以下のサンプルのようです:

    void MyPart::init()
    {
      //メニュウバーを使用/表示したいならメニュウバーマネージャに登録
      OpenParts::MenuBarManager_var menuBarManager = m_vMainWindow->menuBarManager();
      if ( !CORBA::is_nil( menuBarManager ) ) //ウィンドウシェルがメニュバーを持てるかのチェック
        menuBarManager->registerClient( id(), this ); //更に詳しい説明は
                  //*バーマネージャオブジェクトの章
                  //をご覧下さい。

      //...ツールバーと同様。
      OpenParts::ToolBarManager_var toolBarManager = m_vMainWindow->toolBarManager();
      if ( !CORBA::is_nil( toolBarManager ) )
        toolBarManager->registerClient( id(), this );

      //もちろん、広義クラス変数を定義したほうがよい。
      OpenPartsUI::StatusBar_var m_vMyStatusBar;

      OpenParts::StatusBarManager_var statusBarManager = m_vMainWindow->statusBarManager();
      if ( !CORBA::is_nil( statusBarManager ) )
        m_vMyStatusBar = statusBarManager->registerClient( id() );
    }

ただし、これら3つのGUI要素登録コールは、実際に使用したいときにのみ必要です。 たとえば、Part でツールバーを表示させたくないなら、それに対応するコールを無視させなければなしません。 更に、たとえとして`
toolBarManager()`コールが nil のリファレンスを返すようなことがあるかもしれません。つまり、メインウィンドウはその Part にツールバーを持たせないように指示するということです。 当然同じようなことがメニューバーやステータスバーにも当てはまります。

OpenParts は Part に GUI の構成/破棄についての伝達をKOMイベントに任せます。これらは特に：

- `OpenPartsUI::eventCreateMenuBar` ("OpenPartsUI/CreateMenuBar")

設定された引数は `OpenPartsUI::MenuBar`です。

- `OpenPartsUI::eventCreateToolBar` ("OpenPartsUI/CreateToolBar")

設定された引数は`OpenPartsUI::ToolBarFactory`です。

Part がメニューバーやツールバーを表示するかしないかに従って、オブジェクト管理はそれに2つのイベントを発動します。 実装に関しては、これは`
event()`関数を再実装しなければならないということです。(認識事項：Part は完全なKOMコンポーネント機能です。)

用いられたイベント引数は、ツールバーやメニューバーが作成されるか、削除されるかを指示します。 `CORBA::is&lowbar;nil()`に対するこれらの引数について調べれば理解できるでしょう:-)。

OpenParts ステータスバーは、メニューバー/ツールバーと比べると異なった処理を行います。 事実これは操作が簡単です。 OpenParts ステータスバーマネージャに登録すると、返り値として`
OpenParts::StatusBar`を受け取ります。 そうすると、Part のどこででもステータスバー使うことができ、それが可視的(アクティブ)であるかどうかに関係しません。

`
init()`関数で、多数の登録がなされます。 そしてこれに関連して`
cleanUp()`関数(KOM::Base
の章をご覧ください。) では、GUIサーバントオブジェクトから登録を解除し、全ての適切なリファレンスを解放しなければ張りません。それはシンメトリックリファレンスや接続といったKOMのモデルにしたがいます。 コードは次のような感じです：

    void MyPart::cleanUp()
    {
      if ( m_bIsClean )
        return;

      //メニューバーの登録解除
      OpenParts::MenuBarManager_var menuBarManager = m_vMainWindow->menuBarManager();
      if ( !CORBA::is_nil( menuBarManager ) )
        menuBarManager->unregisterClient( id() );

      //...ツールバーと同様
      OpenParts::ToolBarManager_var toolBarManager = m_vMainWindow->toolBarManager();
      if ( !CORBA::is_nil( toolBarManager ) )
        toolBarManager->unregisterClient( id() );

      OpenParts::StatusBarManager_var statusBarManager = m_vMainWindow->statusBarManager();
      if ( !CORBA::is_nil( statusBarManager ) )
        statusBarManager->unregisterClient( id() );

      //ここでその他のリファレンスを解放
      //...

      //これは重要!!!
      //常に、 cleanUp() を基本クラスの手段として呼んで下さい。
      OPPartIf::cleanUp();
    }

### 子 Part

子 Part は3つの特別な機能を持った普通の Part です。

1. 子 Part には親 Part が割り当てられます。

2. GUI は存在しません。したがってGUI作成イベントを受け取りません。

3. フォーカスの変更に関係している子がある場合はいつでも親子がイベントを受け取ります。 子 Part は、機能的に`
   OpenParts::Part`インタフェースに統括されており、その実装は`
   OPPartIf`なので、子 Part を用いる際、追加クラスを取り扱う必要はありません。 単純に子 Part の OpenParts GUIマッピングを無視するか、代わりに以下で説明されている子 Part イベントを指図し、`
   setParent()`経由で親 Part を割り当てます。 一般的なイベントやAPIについては、
   openparts.idl内の `
   OpenParts::Part` の説明をご覧ください。

### Part の埋め込み方 -
OPFrame クラス

完全機能の Part コンポーネント作成方法をもう説明しましたが、Part が実際どのように表示され/示されるかについてはまだ説明されていません。 Part は単純なQWidgetsではなく、ここで解説が必要となるCORBAオブジェクトです。これは、`OPFrame`クラスです。`
OPFrame`は、事実上
QWidgetですが、Qtの
QXEmbedとの論理上は、Part ウィジェットウインドウを埋め込んでいます。 `OPFrame`使い方はとても簡単で、たいていはこのコードは次のようになります:

    ... アプリケーションウィジェットの一部 ...
    myFrame = new OPFrame( the_parent_widget );
    myFrame->attach( a_reference_to_the_part_we_want_to_embed );
    myFrame->show();

上のサンプルに加えて、Part を`
detach()`することができます。 これは最後に行うべきでしょう。 ちょっと
opFrame.hを見てください。これはドキュメントになっています。

最後に`
OPFrame`について知っておかなければならない重要なことは、このクラスが Part 保持に関係するKOMリファレンス(`KOMVar`変数を使用しています。)を内部的に使用するということです。 これは、`OPFrame`を使用する際に、2つの可能性があるということです。

- ウィジェット/オブジェクトの埋め込みがKOMリファレンスを使用し、更にパートにリファレンスを保持するならば、その際 Part のライフサイクルに影響を及ぼす2つのオブジェクトが存在します。 Part の KOM リファレンスカウンタを明記/消去するとそれを増加/減少させる`OPFrame`がある一方で、埋め込みウィジェット/オブジェクトがあるのです。 ですから Part の消去に関して、そのパートへの KOM リファレンスがいつ開放するかを確実に知っておいてください。

- 組み込みウィジェット/オブジェクトがKOMを使用しないなら、Part を消去する際自動的に破棄されるということを知っておかなければなりません。というのは通常`OPFrame`は、Part への KOM リファレンスを持っている単なるオブジェクトにすぎないからです。 したがって KOM カウンタはゼロになると、オブジェクトの完全な破棄を示します。

### OpenParts の要 - OpenParts::MainWindow インタフェース

もう1つの重要なコンポーネントは呼び出された
OpenParts::MainWindowです。 これは実装上の
KTMainWindowに由来するもので(それゆえ、アプリケーションのトップレベルウィンドウです。)、目に見えるサブウィンドウや共有 GUI 要素のまわりのシェルです。

メインウィンドウの機能性は、いくつかのビルトイン集合体で拡張されます。それは、メニュー-/ツール-/ステイタスバーの管理オブジェクトです。 これらのオブジェクトは、メインウィンドウインタフェースの`*barManager()`メソッドを通して直接利用するか、集合体にすることで間接的にし、したがってコンポーネントインタフェースレポジトリ(`
getInterface()`,`
supportsInterface()`,...)を利用するかです。

Part は、メインウィンドウにのみ表示可能で、メインウィンドウは、このことについての理解が必要です。 ですから Part を表示する前に、Part をメインウィンドウに登録しなければなりません。 これは、Part インタフェースの`
setMainWindow()`メソッドを呼ぶことで行えます(詳しくは登録の過程についての前章をご覧ください。)。 これで、Part に単一のIDが与えられます(たとえば、*バーマネージャオブジェクトによって Part のGUIを当てるのに使用されます。)。

メインウィンドウは、シェルとして全ての共有GUI要素を完全に制御することができます。 これは次のことを管理しているということです。

- 適切な*バーマネージャオブジェクトを作成。

- スケルトン/標準 GUI の提供。これは、たとえどの Part が有効でも、いつも提供されることを意味します。

- GUI 管理オブジェクトに Part の GUI を実行/無効にすることを指示。

*バーマネージャの作成は、通常適切なマネージャへのポインタを返す`*barManager()`へ見せかけの呼び出しを実行することで簡単に行えます。 もしマネージャがまだ存在しないならば、新規作成もおこないます。 メインウィンドウの構造上この呼び出しを実行することをお勧めします。

スケルトン GUI の作成/操作は、後に
OPメニュー(バー)/
OPツールバーの章で説明します。

メインウィンドウは、Qt シグナル(`
activePartChanged`)を発します。 これは、アクティブ Part のフォーカス変更について情報提供します。つまりユーザが非アクティブ Part をクイックすると、そのフォーカスを受け入れます。 このシグナルの純粋に有益な点に加え、このシグナルに接続し、スロット実装時の以下にあげる2つのステップを実行することをお勧めします:

1. *bar managers`
   clear()`関数を呼んで、以前アクティブになっている Part の GUI を非アクティブにします。そうすると、ジョブが行われ、内部的なものが、GUI イベント(前章をご覧ください)を Part に発します。

2. *bar managers`
   create()`関数を呼んで、新しいアクティブ Part GUIをアクティブにします。 これはアクティブ化に類似し、Part にイベントを発するはずです。 実際に、要求はされませんが、強く推奨されています。 次のコードはこのことを扱っています:

             void NameOfYourMainWindow::slotActivePartChanged( unsigned long old_id,
                                                               unsigned long new_id )
             {
               // メニュー/ツール/ステイタスバー を削除
               menuBarManager()->clear();
               toolBarManager()->clear();
               statusBarManager()->clear();
               // create the new Part's GUI
               menuBarManager()->create( new_id );
               toolBarManager()->create( new_id );
               statusBarManager()->create( new_id );
             }

メインウィンドウが全ての共有物を処理しますが、このドキュメントでまだ説明していなかったことがあります: メインウィンドウのキャプションは？ OpenParts メインウィンドウインタフェースは、Part が独自のウィンドウキャプションをを持つことを許可していますが、OpenPartsはどのようにしてこれを処理しているのでしょうか？

方法は2つあります:

1. `
   OPMainWindow`は、アクティブパートが変わる場合はいつでもメインウィンドウのキャプションを自動的に変更する、いわゆる`
   AutoCaption`モードを提供します。 これはデフォルトで使用できるようになっています。

2. しかし時々シェルがウインドウのキャプションを完全に制御したがります。 これは、`AutoCaption`モードを無効にすることで実行できます。 そうするとウインドウのキャプションが、どんな方法でも OpenParts では変更されないようになり、代わりに自分でそれを制御できるようにします。

### CORBA 経由の共有GUIアクセス - OpenPartsUI と そのインタフェース

OpenPartsUIイベントを使って、いつ Part のGUIを構築/破棄しなければならないのかを分かっていますが、それをどのように作り上げるのかを学ぶ必要があります。 というのは、共通の
KMenuBar,
KToolBarなどのクラスがないからです。 この再設置が、openparts&lowbar;ui.idlで説明されているCORBAオブジェクトです。 それは、実装に対応するpartsuiモジュールの一部です。 このインタフェースは98&percnt;;がKDE/Qtクラスに似ています。ですから実に使いやすいでしょう。 このドキュメントにサンプルコードをのせる代わりに、kdelibs/corba/tutorialsのチュートリアルを読むことを推奨します。

### OpenParts のピクスマップ
と 配列
-
OPUIUtils

ツールバーとメニューは、たいていQPixmapクラスを使ってピクスマップにより、装飾されます。 GUI要素にアクセスするCORBAインタフェースを用いると、
QPixmapは、OpenParts のアプリケーション用
OpenPartsUI::Pixmapになります。 OpenPartsUI::Pixmapは、ちょうど"stringified"(文字列化された)QPixmapで、opUIUtils.(h,cc)には使いやすい変換ルーチンが含まれています。

更に、OPUIUtilsには、CORBA::WChar*と
QString間のストリング変換ルーチンを含んでいます。 これはQt バージョン>=2.0がQString経由で、
Unicodeをサポートしているため必要となります。 全ての場所、メニュー及びツールバーのような明らかなGUI要素はこのサポートで変換されています。 OpenParts もインタフェースで"wstring" (
CORBA::WChar *)を用い、変換ルーチンを使用し、提供したりすることによって変換されています。 これらのルーチンは、OPUIUtilクラスのスタティックメンバ関数で、まさにピクスマップ変換のようです。 用途を単純化するために、次の2つのマクロが定義されています:`
Q2C`と`
C2Q`。 最初のマクロはQStringをCORBA::WChar *文字列に変換し、2つ目のマクロはその逆です。

QStringからCORBA::WChar *への変換では、変換ルーチンがメモリを割り当てます。 メモリリーク
を回避するには、CORBA::WString&lowbar;var変数の使用を強くお勧めします。 正確には同じことが QPixmap ->OpenPartsUI::Pixmap変換にも適用されます:
OpenPartsUI::Pixmap&lowbar;varを使えばリークの心配はありません:-)。

これは、サンプルコードで、どのように行うかを表しています:

        ...
        OpenPartsUI::Pixmap_var pm = OPUIUtils::convertPixmap( QPixmap_variable_here );
        someToolBar->insertButton( pm, ... );
        ...
        // 同 pm 変数を再度使用
        pm = OPUIUtils::convertPixmap( another_qpixmap );
        ...

QStringの方を用いて行われなければならないことに類似:

        ...
        CORBA::WString_var text = Q2C( QString_here );
        someMenuBar->insertItem7( text, ... );
        ...
        // または次のようにもできます:
        someMenuBar->insertItem7( ( text = Q2C( QString_here ) ) , ...);
        ...

最後に残った注意点: `Q2C`を使うことで関数の結果として"広義文字列"(CORBA::WChar *)を返す場合、絶対に`
CORBA::wstring&lowbar;dup()` を使わないでください。

例:

      return CORBA::string_dup( Q2C( QString_here ) ); //!!!! 警告!!!!!
      ...
      return Q2C( QString_here ); // 正! Q2C は既配列を割り当てています。

### OpenParts フォーカス処理からのウィジェットの排除

時には OpenParts フォーカス処理からあるウィジェットを明示的に排除する必要があります。 その多くが子 Part を扱う場合です。 例えば以下のような状況です：

1つのメインウィジェットと親 Part があるとします。 このウィジェット(Part)は、スプリッターウィジェット(QSplitter)で分けられたいくつかの他の子 Part を埋め込んでいます。 レイアウトを再調整するためにユーザがスプリッターの1つをクリックすると、OpenParts は、子 Part を非アクティブにし、メインウィジェット(親 Part )を用いてアクティブにします。まるでスプリッターウィジェットは、(明らかに)メインウィジェットの子ウィジェットです。 これは、正しい動作ですが、おそらくユーザにはさほど親しみはないでしょう;-)。

これを固定する正しい方法は、このフォーカス処理からスプリッターウィジェットを排除することです。 そうすると実際、OpenParts にこのことを伝えられます。 (例では)スプリッターはメインウィジェット、OPPartIfを継承するメインウィジェットクラスに属しているため API を含んでいます。

利用できる3つの方法は、OPPartIfクラスの Part で、これについては改めて説明する必要はないでしょう:`addSeparatedWidget()`,`removeSeparatedWidget()`,`isSeparatedWidget()`

これで全てです。KOM/OpenParts を有効に利用しましょう:-)。

TODO:

- オートローダテンプレートクラス

- OPアプリケーション インタフェース + ファクトリー

日本語翻訳：レーザーファイブ info@laser5.co.jp