English Francais Deutsch

KDE 元件技術

by Philippe Fremy

繁體中文翻譯:Jim Huang (黃敬群)



介紹

KDE 2.0 在 2000 年九月份正式釋出,相較於其前身 KDE 1.X 是很大的突破。桌面系統幾乎重新撰寫過,並受益於許多威力強大的新技術:元件 (components)、RPC/IPC、網路通透性 (network transparency)、背景音效伺服器 (sound daemon)、...

或許有些人會認為以上這些新技術會讓開發應用程式變得困難,但,實際上恰好相反。這些新技術相當新潮並易於使用,這也是我在本文所要說明的。

本文所有的程式範例都在 KDE 2.1 下撰寫並測試過,主要是從以下不同的地方取得的:cvs 上的範例程式、kde book、mails (譯注:應該是指 mailing-list)、文件檔、教學說明檔 (tutorials),以及應用程式本身。你可以在 KDE 開發者站台 developer.kde.org 上取得以上這些資料。

值得注意的是,這些新技術都當穩定,在歷經三份穩定的釋出版 (stable releases,指的是 KDE 2.0、2.1,以及 2.2) 時,都幾乎沒有更動過。這些也是 KDE 底層最重要的技術,所以,你可以視他們如堅石一般穩固。

KDE 使用者介面(GUI) 元件技術

使用者介面元件 (GUI components) 是 KDE 中相當具有威力的技術。基本上,每個元件都允許應用程式提供自己專屬的特徵與介面,並且適用的範圍不僅包含應用程式,連同嵌入 (embedded frame) 到其他桌面環境中的應用程式也可。每個元件都可單獨發展,也可以指定若干元件賦予某些任務,一些經典的例子是:一個郵件閱讀程式需要一個文字編輯器 (Text editor) 元件以便能夠發信;一個整合開發環境 (Integreted Development Environment) 也需要一個文字編輯器 (Text editor) 來編輯程式碼 (當然不見得要同一份程式碼);試算表程式 (spreadsheet) 則需要一個報表產生器 (Chart builder) 來分析資料;而一個普通的應用程式或許也需要個 html browser 等等。

元件增加了程式碼重用性 (code reuse) 與模組性 (modularity),也因此更應該儘量被使用。為了讓元件技術更能切合實際,KDE 小組讓這些技術能夠相當簡單的撰寫與使用。

在 Gnome 陣營中,其元件技術是由 Bonobo 這個建構於 CORBA 分散式運算標準的項目所提供的。過去,KDE 也曾使用過 CORBA,但最後卻放棄而改用另一項技術:KPart。這個選擇曾經廣受批評,雖然幾乎沒有多少人知道這個背景與其因果。事實上,這是個正確的抉擇,而且這也是為什麼我要撰寫本文來說明的原因了。

簡而言之,一個好的使用者介面元件技術應該具有以下特徵:

如果採用 CORBA 的話,則相當難符合以上需求,而非變得容易。CORBA 是個相當優秀的技術,但是並不適合使用者介面元件採用。在以下的部分,我將說明 KPart 是如何在這領域拔得頭籌 (譯注:即取代 CORBA 成為 KDE 底層元件技術)。

KPart

當 KDE 核心開發人員發現使用 CORBA 逐漸成為他們的夢魘時,他們開始撰寫一個輕量級 (lightweight) 而有效的元件技術來取代:KPart

KPart 以共享程式庫 (shared libraries) 為基礎,這讓元件直接以 C++ 物件的形式存在,也因此沒有必要用 IDL 語法去包裝 (wrap) 他們的特徵,所有項目都可以不假外力的取得。所以,撰寫一個元件就如同撰寫一個 C++ 物件一般,只需要負責在應用程式裡頭所需要注意的項目。共享程式庫也相當容易去觸發 (activate) 或卸載 (unload)。你甚至不必考量 fork 的產生,因為程式碼就在應用程式裡頭運行 (譯注:這句話有點抽象,主要是說明一般程式設計師只要去注意撰寫元件時的 Parts,而不需留意整體的運作)。 95% 與元件相關的工作都由 KPart API 所處理,也因此,撰寫與使用元件就變成相當容易的工作了,待會我就會援以範例說明。

KPart 有兩個部分:components (譯注:此處 component 不譯作「元件」,以區隔一般通稱的「元件技術」與 KPart 中的元件) 與 plugins。Components 提供可以讓你顯示的widget (譯注:Widget 特指 Window System 中用來顯示的單位元件,如對話框、捲軸等),且可以用來延伸應用程式的選單項目與增加元件專屬的動作 (action)。而 plugin 則沒有 widget,通常以一個選單的項目 (menu entry) 的方式提供一項新特徵,並且,在應用程式裡的選單,會定義在一個 XML 檔中 (譯注:即 XML-GUI)。

範例一:使用 html browser

使用一個 Part 相當的簡單,這邊有個範例就是讓你能夠嵌入一個 html browser 到你的應用程式中。這段程式碼片段是使用由 kapptemplate 所產生的程式範本 (application template) (application template 產生器是由 Kurt Granroth 所撰寫)。

KTrader::OfferList offers = KTrader::self()->query("text/html", 
                                        "'KParts/ReadOnlyPart' in ServiceTypes");
 
KLibFactory *factory = 0;
// 理論上,我們只需留意到一開始,但我們可以嘗試如果因為某種理由
// 而沒辦法載入的狀況
KTrader::OfferList::Iterator it(offers.begin());
for( ; it != offers.end(); ++it) {
	KService::Ptr ptr = (*it);

	// 現在我們知道這段可以處理 HTML 顯示,且為一個 Part。
	// 既然是個 Part,必定有個程式庫,咱們現在嘗試來載入
	factory = KLibLoader::self()->factory( ptr->library() );
	if (factory) {
    m_html = static_cast<KParts::ReadOnlyPart *>(factory->create(this, 
                          ptr->name(), "KParts::ReadOnlyPart"));
	    break;
	}
}

所有可以使用的元件都被索引化 (indexed) 並儲存於資料庫中。你可以透過 KTrader 來作查詢。KTrade 允許你去指定你所要求元件的特徵:元件名稱、可以處理的 mimetype ,或是其他參數。而在這邊,我們要求一個可以處理 text/html 唯讀顯示 (譯注:就是一般我們稱「瀏覽網頁」的動作) 的元件,則 KTrader 會傳回一份有效元件的列表 (KService::Ptr),並依據 preference 作排序。KService::Ptr 具有一份關連性的程式庫,可讓你透過 KLibLoader 來載入系統,而這個程式庫則設計有可以建立 widget 的 factory (譯注:factory 是 Design Patterns 專有名詞,在此不作翻譯。關於 factory 的定義,可以見物件導向設計的專書《Design Patterns》,簡言之,factory 是指如同我們所見的工廠可以源源不絕的產生新的產品一般的行為,我們可以透過特定的 factory 來產生對應的物件),當然,widget 正是你所要求的元件。

乍看下似乎頗複雜,不過請放心,不會變得更困難了:尋找特定的服務、載入程式庫、取得程式庫的 factory,並且藉此產生我們所要的 widget。就是這樣!在這個範例來說,預設的 html 瀏覽器將會因此被觸發 (可能是 khtml,也可能透過 kmozilla 來觸發 gecko 也說不定)。如果要求不同的元件,只要更改原本的 text/html 成其他 mimetype 即可。

範例二:使用特定的文字編輯器 (text editor)

透過相同的途徑,你可以使用一個文字編輯器。以下的程式片段變得更緊密,但是基本上不需要更動。這個程式片段是節取自 KTextEditor interface documentation 中。

 KTrader::OfferList offers = KTrader::self()->query( "KTextEditor/Document" );
 ASSERT( offers.count() >= 1 );
 KService::Ptr service = *offers.begin();
 KLibFactory *factory = KLibLoader::self()->factory( service->library() );
 ASSERT( factory );
 m_part = static_cast<KTextEditor::Document *>( factory->create( this, 0, "KTextEditor::Document" ) );
 ASSERT( m_part );
 QWidget * view = m_part->createView( my_parent_widget, 0 );

如你所見,八行的程式碼中,包含三個 assert 敘述,你可以發現在應用程式中使用 Part 是多麼簡單的一回事,我沒說錯吧。

範例三:如何提供一個元件

現在,想像我們已經撰寫一個相當棒的應用程式,並且想要在其他應用程式中,以 KParts 元件的形式被使用。這邊有份 Kurt Granroth 所撰寫的 KPart tutorial,有提及這部分所必要的程式碼片段,所舉 KPart 的例子,就是 aKtion。

基本上,你必須提供一個能夠在程式庫載入後能夠作用的 factory。這個 factory 角色只有提供一個 KPart 物件,而 KPart 會安裝新的選單項目並且回傳一份應該納入你的應用程式的 widget。

aktion_part.h aktion_part.cpp
#ifndef __aktion_part_h__
#define __aktion_part_h__

#include "kparts/browserextension.h"
#include "klibloader.h"

class KAboutData;
class KInstance;
class AktionBrowserExtension;
class QLabel;

class AktionFactory : public KLibFactory
{
    Q_OBJECT
public:
    AktionFactory() {}
    virtual ~AktionFactory();

    virtual QObject* create(QObject* parent = 0, 
                const char* name = 0, 
                const char* classname = "QObject", 
                const QStringList &args = QStringList());

    static KInstance *instance();
    static KAboutData *aboutData();

private:
    static KInstance *s_instance;
};

class AktionPart: public KParts::ReadOnlyPart
{
    Q_OBJECT
public:
    AktionPart(QWidget *parent, const char *name);
    virtual ~AktionPart() { closeURL(); }

    bool closeURL() { return true; }

protected:
    virtual bool openFile() 
        { widget->setText(m_file); return true; }
    QLabel *widget;
};

#endif
#include "aktion_part.h"
#include "kinstance.h"
#include "klocale.h"
#include "kaboutdata.h"
#include "qlabel.h"

extern "C"
{
    void *init_libaktion()
    {
        return new AktionFactory;
    }
};

/**
 * 我們需要一份 factory function 的 static instance
 */
KInstance *AktionFactory::s_instance = 0L;

AktionFactory::~AktionFactory()
{
    if (s_instance) {
        delete s_instance->aboutData();
        delete s_instance;
    }
    s_instance = 0;
}

QObject *AktionFactory::create(QObject *parent, 
                        const char *name, const char*, 
                        const QStringList& )
{
    QObject *obj = new AktionPart((QWidget*)parent, name);
    emit objectCreated(obj);
    return obj;
}

KInstance *AktionFactory::instance()
{
    if ( !s_instance ) s_instance = new KInstance( aboutData() );
    return s_instance;
}

KAboutdata *AktionFactory::aboutData()
{
    KAboutData *about = new KAboutData("aktion",
            I18N_NOOP("aKtion"), "1.99");
    return about;
}

AktionPart::AktionPart(QWidget *parent, const char *name)
    : KParts::ReadOnlyPart(parent, name)
{
    setInstance(AktionFactory::instance());

    // 建立一個可以插入我們 widget 的 canvas
    QWidget *canvas = new QWidget(parent);
    canvas->setFocusPolicy(QWidget::ClickFocus);
    setWidget(canvas);

    // 顯示一個黑白的簡單 widget 作為範例
    widget = new QLabel(canvas);
    widget->setText("aKtion!");
    widget->setAutoResize(true);
    widget->show();
}

以上八十行的程式碼,有 90% 是一般性的並且對其他元件來說,都是可重用的 (reusable),而只有最後 15 是 aktion part 所專屬的。當然,如果你使用 kapptemplate 或 kdevelop 這類的開發工具,甚至就不需要花時間撰寫以上這八十行了,因為都會自動建構 :-)

範例四:Konqueror

Konqueror 就是我們所知的 KDE 2 Web Browser。但 Konqueror 並不只是一個網頁瀏覽器,事實上,Konqueror 正是一個處理 Kio slaves 資料的前端 shell (譯注:原文是 "it is just a Shell that requests data using Kio slaves (another KDE technology)",我為了口語化,就稍微修改這個敘述),並且能嵌入使用 KPart 展現的資料。這是使用 KDE 元件的最有趣的例子了。

這邊有份不完整的列表,是列出提供 KPart 元件的應用程式,當然,都可以被嵌入到 Konqueror 之中:

記得,這份列表意味著你可以在你的應用程式中,用八行的程式碼 (譯注:請回頭見 [範例二]) 嵌入以上任何元件,而隨著 KDE 2.2 的來臨,這份列表會更豐富!

範例五:KOffice

KDE 也因其 Office 套件而聞名:提供了文字處理器 (kword)、試算表 (kspread), 簡報展示軟體 (kpresenter)、向量繪圖工具 (killustrator),以及報表繪製工具 (kchart) 等等。

而,較少人注意的是,KOffice 的性質是「每個應用程式就是以一個元件的形式存在」代表一份 KOffice 文件的類別 (譯注:"class" 的翻譯,以下不多作說明) 是KoDocument,而 KoDocument 衍生於 (譯注:"inherits from" 的翻譯,以下亦不多作說明)KParts::ReadWritePart,因此,每份 KOffice 文件可以被嵌入並以一個 KPart 元件的形式顯示。這裡就是 KPart 應用到 KOffice 的優勢:可以很簡單的撰寫與使用元件、快速與輕量級的載入 (譯注:就是 KDE KPart 元件技術 vs. CORBA 的比較) 等等。KOffice 元件技術增加更多能夠被觸發的子工具 (activation subtilities),因為他們可以被重複應用,論功能性來說,他們就是 KParts。

KOffice embedding 在 Konqueror 中被使用作預覽在瀏覽器中的 KOffice 文件。但,主要的使用是讓 KWord 能夠即時嵌入一份試算表、公式項目,以及一份圖片到一個 KWord 文件中。當你在文字欄位編輯時,KWord 元件就會被觸發;而當你在試算表項目編輯時,KSpread 跟著被觸發 (請見下方快照)。

當你啟動 KWord 或 KSpread 時,你會看到一個建立新文件或開啟舊檔的對話框,如果你注意對話框裡頭的視窗 (請見快照),你將會發現工具列 (toolbar) 幾乎是空的,而且就跟 KWord 和 KSpread 是一致的。這個視窗是一個通用性的 KOffice 文件介面 (譯注,原文是 "shell",為了口語方便,我稍微修改敘述),並且包含與 KWord 或 KSpread 無關的項目 (譯注:換言之,就是這兩者通用的)。這會視 KWord 或 KSpread 元件被觸發與否而變成對應的應用程式。KSpread 與 Kword 唯一的區隔是,當他們能夠處理的 mimetypes (藉由 Open Dialog 的方式觸發)。

KWord 開啟一份文件 KSpread 開啟一份文件

現在,建立一份 KWord 文件,並在裡頭嵌入一份 KSpread 文件。當你在 KWord 文件上編輯時,所有的選單與工具列都是 KWord 的,而 KSpread 欄位還是靜態的區域 (請見快照)。但是,這時如果你按一下 KSpread 欄位,你將發現 KWord 選單與工具列就不見了,而是 KSpread 選單與工具列取而代之。以上所發生的,就是元件的解除觸發(deactivation) 與觸發的過程:KWord 元件被解除觸發狀態,其選單與工具列就自 Sehll 視窗被移除。在這過程的同時,會產生一個空的 Koffice 視窗 (跟在 opening dialog 後方的是同一個),下一刻,KSpread 元件就被觸發了,KSpread 選單與工具列就被新增上去了,原本的試算表就轉身一變成為可編輯的狀態。既然 KSpread 的特徵都以元件的形式被提供,也就是說,你可以直接透過 KSpread 來編輯試算表,如同你在快照所見一般。

KWord 文件包含一份 KSpread 欄位
KWord 被觸發 KSpread 被觸發
同一份 KSpread 文件在 KSpread 中

KOffice 工作區 (Workspace)

There is a program that highlights the true component nature of KOffice. (譯注:以上這句我抓不到感覺,所以保留原文) 這稱為 KOffice 工作區 (Koffice workspace),你可以藉由輸入 "koshell" 來啟動。並且,你可注意到主視窗個我討論過的通用性視窗。你可以按擊左側 KOffice 元件的圖示,來觸發這些元件,之後,這個通用性的 KOffice Shell 就會轉變成 KWord、KSpread,或是其他你要求的應用程式。

空白 KOffice 工作區

KOffice 程式碼樣貌

不同於 Gnome 的辦公室整合套件,如 OpenOffice、GNU Spread,或是 Abiword,這些在 Gnome 之前就已經發展存在的專案 (譯注:意思就是說,Gnome 雖然以 CORBA 作為元件技術標準,但是其上殺手級應用程式卻非採用 Gnome 標準的元件技術),KOffice 是從無到有、跟隨著 KDE 的發展。對 KDE Office 套件來說,快速發展是可能的,因為KDE 底層的架構與發展的 framework 相當具有威力,並且相當易於使用。

舉例來說,我們所見的 KOffice 工作區只用不到 600 行程式碼就完成了,這相當於一個普通程式設計師三天份的工作量。

philippe@werewindle /usr/src/kde-cvs/koffice/koshell $ ls
AUTHORS   Makefile.am  dummy.cc         koshell_shell.cc
CVS/      Makefile.in  koshell.desktop  koshell_shell.h
Makefile  TODO         koshell_main.cc

philippe@werewindle /usr/src/kde-cvs/koffice/koshell $ wc -l *.h *.cc
    121 koshell_shell.h
      1 dummy.cc
     49 koshell_main.cc
    434 koshell_shell.cc

    605 total

如果你想開撰寫新的 KOffice 應用程式,最重要的任務就是去建立一個衍生於KoDocument 的新類別。KOffice 所提供的範例大約是 430 行的程式碼。

philippe@werewindle /usr/src/kde-cvs/koffice/example $ ls
CVS/         README           example_factory.cc  example_view.cc
Makefile     configure.in.in  example_factory.h   example_view.h
Makefile.am  example.desktop  example_part.cc     main.cc
Makefile.in  example.rc       example_part.h      x-example.desktop

philippe@werewindle /usr/src/kde-cvs/koffice/example $ wc -l *.h *.cc
     47 example_factory.h
     42 example_part.h
     48 example_view.h
    106 example_factory.cc
     70 example_part.cc
     67 example_view.cc
     49 main.cc

    429 total

如果要把以上的範例變成一個真實、全功能的 KOffice document 處理程式,為達成這個目標,你必須重新轉寫三個函式:ExamplePart::loadXML()、ExamplePart::saveXML(),以及ExamplePart::paintContent(),以上三者分別具有從一個 XML 檔中載入你的文件資料、把文件資料儲存於一份 XML 檔中,最後是依據文件內容繪製與呈現應有的效果。

結論

如你所體會的,KDE 擁有一個相當具有威力的架構,可盡可能廣泛發揮元件技術。KPart 實際上在 KDE 各處都高度使用著,而 Gnome 的元件技術 Bonobo 則還有點距離,而且據我所知,Gnome 2.0 也還不會全盤使用 Bonobo (譯注:原文是 "won't be bonoboized"),Gnome 仍然還是緊跟著 KDE 的發展步調。

在我下一篇文章中,我將會討論 KDE 中的 IPC/RPC 機制 —— DCOP。

參考文獻


Copyright (c) 2001 Philippe Fremy (pfremy at freehackers dot org)

Translation to Traditional Chinese Copyright (c) 2001 Jim Huang (jserv at kaffe dot org)

Last modification : $Date: 2005/01/04 09:12:39 $