1Qt 正夯, GTK+ 使用者該如何跳槽 ?PCMan 的跳槽經驗分享●講者:洪任諭 ●Lead developer of LXDE: http://lxde.org/●PCMan BBS 軟體 : http://pcman.openfoundry.org/●IE Tab Firefox Plugin: http://code.google.com/p/ietab/●現職:台北榮總過敏免疫風溼科總醫師只要有心,人人都可以玩自由軟體 !快逃啊 ~
View Slide
2Gtk+ (GIMP Toolkit)●Gnome 使用的 GUI toolkit●以 C 語言開發●用 C 手工實作物件導向功能●LGPL 授權●可跨 Windows, MacOS X ,但支援度差強人意●主要只含有 GUI 功能,其他功能仰賴其他 libraries
3Qt●KDE 使用的GUI toolkit●以 C++ 語言撰寫,搭配自訂關鍵字(signals, slots,...)●程式碼需經 moc (meta object compiler)預處理●歷史上曾是QPL/GPL雙授權,商業授權貴●被Nokia 收購後,有LGPL 授權了 (後被Digia收購)●跨 Windows, OS X, Android, iOS,...等●含有database, network, xml 等各種功能模組
4歷史上 ...●Gtk+ 授權較開放 (LGPL)●C 語言支援度比較好,較好移植●Gtk+ 寫的程式相對輕巧 ? Qt 程式比較肥 ?●C++ 語法比較複雜 ?●Gtk+ 支持者似乎比較多? Gnome, Adobe,Firefox, Google Chrome…●Gtk+ language bindings 多 (python, perl, ...)
5事實上 ...●Qt 一樣開放,現在也有 LGPL 授權●到處都有 C++ , Qt 支援平台還比 gtk+ 多●Gtk+ 一點都不輕巧, Qt 也不肥 ( 是 KDE 肥,不是 Qt)●C++ 比較簡單,用 C 寫 GObject 才複雜 ... 更容易出錯●有很多商業軟體是 Qt 寫的,例如 Virtualbox●Qt language bindings 其實一樣多
6為什麼你應該快逃●Gtk+2 => Gtk+ 3 變化太大,移植絕對不是改Makefile 換版本重編譯就會動●Gtk+ 3 經常改變 API/ABI ,開發者不管使用者,經常更新你的程式就壞掉●Gtk+ 3 就算 API 沒改,同一 API 行為可能不同,雖編得過,但是一執行就ㄎㄎ ...●Gnome 開發者打算讓 Gtk+ 變成 Gnome 專用,不顧別人的需求 (google "Gnome rotting")
7Gtk+ 怎麼改成 Qt?tG k+Q
8假設你本來就會 Gtk+●你只需要學會 C++ ( 這才是最難的部份 )●但不需要很會 (Qt 只用少部份 C++)●兩者概念非常相似,剩下的幾乎只是翻譯而已●當然,翻譯還是很花時間的 ...●不過,辛苦是值得的!
9GObject vs QObjectGObject QObject方便性 麻煩,需要手寫大量 code ,手動模擬 C++ 編譯器行為手動填寫 virtual function table簡單,是一般 C++ 物件,只需要加入 Q_OBJECT macro記憶體管理 強制 Reference counting ( 手動計算 )無 reference counting但可指定物件 owner ↔ childOwner 解構時會一起解構 child可用 smart pointerType safety 差,手動 type casting ,透過 gtk+實作的 RTTI 手動型別檢查C++ 內建語法,也可用 Qt 提供的RTTIProperty 手動處理,需寫 class init function在其中手動註冊新屬性g_object_class_install_property()用 Q_PROPERTY() macro儲存額外資料 g_object_set_data() QObject::setProperty()Gtk+ 和 Qt 當中大多數物件的基礎類別
10GObject vs QObjectGObject QObject定義新 signal 麻煩,需要手寫大量 code ,手動註冊 signals ,用 automake 規則生成 marshaller 函數 ...簡單,只需要加入 signals 關鍵字,或 Q_SIGNALS macro連接 signal g_signal_connect(object,"signal_name",G_CALLBACK(handler), user_data);connect(object,SIGNAL(signalName()),SLOT(handler()));斷開 signal g_signal_handler_disconnect(object, handler_id);disconnect(object,SIGNAL(signalName()),SLOT(handler()));方便性 需手動,物件解構前若忘記手動disconnect ,會 crash!!物件解構可自動 disconnect(sender 或 receiver 都會 )Type safety 差,連錯不相容的 signa/slot 會crash略好,會 runtime 檢查,但compile time 不檢查 (Qt5 改善 )兩者都有 signal/slot 機制 ( 其實是 Gtk+ 學 Qt 的 ...)
11GObject vs QObjectGObject QObject種類 只能是一般函數有 default handler (virtual)只能是 QObject 衍生 class 的 methods無 default handler參數 第一參數為 sender ( 除非 swapped)參數無 receiver ,只有任意 user_data ( 通常放 receiver data)參數無 sender ,用 sender() 取得this 是 receiver範例 static void handler(SenderObject* sender,ParamType param1,...,gpointer user_data){ReceiverObject* receiver =RECEIVER_OBJECT(user_data);do_something(receiver);}void ReceiverObject::handler(ParamTypeparam1, ...) {QObject* sender_ =sender();doSomething();}Signal handlers 大不同
12用 GObject 定義一個 class (*.h)#include // type-casting macros (for RTTI)#define FM_TYPE_FOLDER (fm_folder_get_type())#define FM_FOLDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),FM_TYPE_FOLDER, FmFolder))#define FM_FOLDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\FM_TYPE_FOLDER, FmFolderClass))#define FM_IS_FOLDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\FM_TYPE_FOLDER))#define FM_IS_FOLDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\FM_TYPE_FOLDER))typedef struct _FmFolder FmFolder;typedef struct _FmFolderClass FmFolderClass;struct _FmFolderClass // virtual function table 及其他 class info{GObjectClass parent_class;void (*files_added)(FmFolder* dir, GSList* files); // virtual function};struct _FmFolder // class 的記憶體內容{GObject parent; // parent class/**/FmPath* dir_path;...};GType fm_folder_get_type (void); // 向 GObject 物件系統註冊這個 object typeFmFolder* fm_folder_new(); // 呼叫 constructor// 其他 non-virtual methodsFmFileInfo* fm_folder_get_info(FmFolder* folder);FmPath* fm_folder_get_path(FmFolder* folder);
13用 GObject 定義一個 class (*.c)G_DEFINE_TYPE(FmFolder, fm_folder, G_TYPE_OBJECT); // 定義物件類別static void fm_folder_class_init(FmFolderClass *klass) // 初始化 class info ,填寫 virtual function table{GObjectClass *g_object_class;FmFolderClass* folder_class;g_object_class = G_OBJECT_CLASS(klass); // 手動 type castingg_object_class->dispose = fm_folder_dispose; // 手動填寫 virtual destructorfm_folder_parent_class = (GObjectClass*)g_type_class_peek(G_TYPE_OBJECT);folder_class = FM_FOLDER_CLASS(klass);folder_class->content_changed = fm_folder_content_changed; // 填寫 virtual function tablesignals[ FILES_ADDED ] = // 註冊新的 signalg_signal_new ( "files-added",G_TYPE_FROM_CLASS ( klass ),G_SIGNAL_RUN_FIRST,G_STRUCT_OFFSET ( FmFolderClass, files_added ),NULL, NULL,g_cclosure_marshal_VOID__POINTER, // marshaller function, 告訴 Gobject 如何傳遞參數,常需從 Makefile 生成G_TYPE_NONE, 1, G_TYPE_POINTER);}static void fm_folder_init(FmFolder *folder) // constructor{...}static void fm_folder_dispose(GObject *object) // destructor{(* G_OBJECT_CLASS(fm_folder_parent_class)->dispose)(object); // 手動呼叫父類別 destructor}
14如果你用 Qt/C++#include class FmFolder: public QObject { // 唯一缺點,不能多重繼承Q_OBJECT // 記得加上 Q_OBJECT macropublic:FmFolder() {// constructor}virtual ~FmFolder() {}signals: // 註冊新的 signals 就這樣,不用寫 code (MOC 會自動生成所需 code)void filesAdded(FileInfoList* files);protected:virtual void contentChanged() { // 產生 vtable 是 C++ compiler 的工作...}private: // 有存取權限控管FmPath* path;};
15多國語言翻譯Gtk+ Qt工具 使用 GNU gettext Qt 內建 QTranslator標記待翻譯字串_("English string") tr("English String)檔案格式 *.po *.ts (xml 格式 )初始化 bindtextdomain(GETTEXT_PACKAGE,PACKAGE_LOCALE_DIR);bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8" );textdomain ( GETTEXT_PACKAGE );QTranslator translator;translator.load(" 程式名稱 _"+Qlocale::system().name()," 檔案位置 ");qApp->installTranslator(&translator);雖然不同但非常類似
16Qt 跟現有 gnome/C libraries 合用●幾乎完全沒問題,可完美混用 ( 例如 glib/gio)●Qt 支援 glib mainloop integration!●小心 Qt 自訂的關鍵字 signals, slots,... 等等,用 -DQT_NO_KEYWORDS compiler flag 關掉 Qt自訂保留字,改用 Q_SIGNALS, Q_SLOTS 等代替(Glib headers 有用這些字命名變數 )●不要在 C code 尤其 header ,用到 C++ 關鍵字(new, class, …)●小心 extern "C" 問題 (C++ name mangling)
17廣告時間 -Migrate from GTK+ to Qt●http://wiki.lxde.org/en/Migrate_from_GTK%2B_to_QtTry it yourself!