顯示具有 Qt 標籤的文章。 顯示所有文章
顯示具有 Qt 標籤的文章。 顯示所有文章

2019年12月14日 星期六

用 Qt Graphics 做一個顯示座標的工具 - 細節調整

其實這篇才是我寫文的主因,前面兩篇其實都是前言(欸),總之就是我埋頭自幹 AZ 大大吃一頓飯就寫得出來的工具,結果過程中寫得亂七八糟,有時候該出現的東西就是不出現測到頭很痛,或是測一測就進無窮迴圈後來發現是自己蠢,發了 v0.1 之後被靠北顯示怎麼這麼醜,想改又很難搜不到解法QQ。
當然最後還是搜到了,但就希望可以多寫一篇文,讓這些解法更容易被搜到,如果運氣很好真的幫到人也算功德一件,如果你真的被幫到的話,麻煩幫我這篇文章留下一個 like 然後順手按下旁邊的訂閱和小鈴鐺(醒醒這裡是 blogger 不是 youtube。

畫上座標線:

這算是一個附加功能,一般的顯示工具只要設定 Scene 的 background brush,設定一個黑色的 brush,就能畫出黑色背景了。
但如果我們要更精細的話,就要去實作 scene 或是 view 的 drawBackground 函式:
void drawBackground(QPainter *painter, const QRectF &rect)

下面是我實作的程式碼節錄,有幾個可以注意的地方:
  1. Qt 的座標系統左上角是 0,0,往右往下是 x 遞增跟 y 遞增,所以用 top 的值會比 bottom 小。
  2. 這個函式參數是一個 QPainter 跟一個 QRectF,painter 好理解就是目前作畫的對象,在這個 painter 上面作畫就會畫在背景上;QRectF 一般會認為就是現在顯示背景的矩形,不過不對,它是「現在要更新背景的範圍的矩形」。
    例如我現在顯示的 Scene 大小是 -50,-50 ~ 50, 50,但我實作滑鼠可以在 scene 上面拉一個選取的小框框,在我拉框框的時候,Qt 會判定只有這個小框框裡面的背景是需要重畫的,QRectF 就會設定到這個小框框上;其實某種程度來看這樣也對,而且更節省重畫的資源。
  3. 承上點,所以有抓到現在 scene 的大小要怎麼辦?這也是我為什麼選擇是實作 View 的 drawBackground 而不是 Scene 的,因為我可以透過 mapToScene(viewport()->rect()).boundingRect() 取得這個 View 現在顯示 Scene 的大小。
後來就是一些數學計算,在對應的座標點上呼叫 drawPoint 了,下面的 code 我有略為刪節過了,要用的話不要全抄。
void
MyViewer::drawBackground(QPainter *painter, const QRectF &rect) {
  qreal left         = rect.left();
  qreal right      = rect.right();
  qreal top         = rect.top();
  qreal bottom = rect.bottom();

  QRectF sceneRect = mapToScene(viewport()->rect()).boundingRect();
  qreal size = qMax(sceneRect.width(), sceneRect.height());
  qreal step = qPow(10, qFloor(log10(size/4)));

  qreal snap_l = qFloor(left / step) * step;
  qreal snap_r = qFloor(right / step) * step;
  qreal snap_b = qFloor(bottom / step) * step;
  qreal snap_t = qFloor(top / step) * step; 

  // print coordinate point
  for (qreal x = snap_l; x <= snap_r; x += step) {
    for (qreal y = snap_t; y <= snap_b; y += step) {
      painter->drawPoint(x, y);
    }
  }

  // print coordinate line
  painter->drawLine(qFloor(left), 0, qCeil(right), 0);
  painter->drawLine(0, qCeil(bottom), 0, qFloor(top));
  QGraphicsView::drawBackground(painter, rect);
}

不動物件:

第一個是所謂的不動物件,也就是 QGraphicsItem 透過設定了 ItemIgnoresTransformations flag,這樣這個 item 就不會受到 view 視角變化的影響。

使用情境也很單純,像是在畫面上打個 marker 或是寫上文字,如果視窗縮小就看不到就奇怪了,所以這個 marker 就要設定這個 flag,放大縮小都會顯示一樣。
改變也就是呼叫一下
setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
就可以了。

要注意的是在設定了這個 flag 之後,在這個 item 裡面的位移似乎會失去效果(還是行為會變很怪,我有點忘了),一般要在一個位置例如 100, 100 畫一個正方形,我們可以用 QGraphicsRectItem,在 100, 100 的地方畫正方形;如果是 ignore transformation 的物件,我是變成在 0,0 的位置畫一個正方形,然後把物件的位置用 setPos 設定在 100, 100。
這部分當初真的弄超久,後來覺得這樣不行,把放在 dropbox 裡面的 <c++ GUI Programming With Qt 4> 拿出來翻翻,沒想到在第八章 Qt graphics 章節就講了要怎麼寫類似的東西,還有範例 code ,果然寫程式還是要多看書而不是瞎攪和,弄了好一陣子的東西其實書上的範例都寫了。

填充物:

上一篇文的最後一張圖,應該很明顯可以看到,我用 brush 填進去的東西,非常的…不均勻,一格一格的非常醜,實際運作的 code 也是,只要放大縮小填充物的就會變得不連續。
這是因為 QBrush 在填東西的時候,用固定密度在填充,不會隨著螢幕的放大縮小改變填充物的密度,要修成也只需要一行,在 paint 函式裡面加上這個:
QBrush m_brush;
m_brush.setTransform(QTransform(painter->worldTransform().inverted()));
painter->setBrush(m_brush)
把現在場景的變形反轉補償回去就可以了;這個解答出自 Stack Overflow

隨放大縮小調整長度:

同樣的是另一張圖的箭頭,在放大縮小的時候,箭頭的部分會跟著放大縮小,這是我們不想要的,因為縮太小的時候箭頭會看不到,這時候就要用到我們上篇提到的 QStyleOptionGraphicsItem,在 paint 函式裡面,可以用這個東西從 painter 的 transform 裡取得 level of detail (LOD):
qreal qScale = option->levelOfDetailFromTransform(painter->worldTransform());
qreal len = 10 / qScale;
qScale 就是目前放大值,可以用它調整我們要畫的長度 len;這個解答出自 Heresy's Space

下面列一下參考書目:
  • C++ GUI Programming With Qt 4
  • Game Programming using Qt 5 Beginner's Guide: Create amazing games with Qt 5, C++, and Qt Quick

2019年12月11日 星期三

用 Qt Graphics 做一個顯示座標的工具 - 客製化元件

上一篇我們介紹了 Scene, View, Item 的關係,這篇就來客製化一下,畢竟 Qt 的元件沒客製化功能都非常受限,預設行為幾乎什麼都沒有,這時候就是好好重溫 C++ 最美妙功能––繼承的時候了。
首先是 Scene 跟 View,都用繼承的方式建一個自己的 class,才能在裡面實作各種信號跟插槽,設計上 Scene 是 View 的 data member,View 上面接到什麼東西直接 pass 給 Scene,實作就不一一介紹,下面是一個簡單的修改列表,因為是內部用的工具就沒辦法把程式碼貼上來給大家笑了

View實作事件:

  • keyPressEvent:客製化按鍵盤的行為。
  • wheelEvent:連接到放大縮小的函式。


View實作插槽:

  • clearScene:清空畫面
  • addItem:接收讀進來的物件,往下直接呼叫 Scene 的 addItem。
  • zoomToAll 跟 zoomRect:放大縮小。


Scene 實作事件:

  • mousePressEvent/mouseMoveEvent/mouseReleaseEvent:定義滑鼠行為。


Scene 實作信號:

  • rectSelected:滑鼠事件會發出這個信號,通知 View 跟 MainWindow。
  • mouseClick:同樣用來通知 MainWindow 使用者點在哪裡。


當然我們也要客製化自己的 QGraphicsItem,當然如果要顯示的東西沒太多特別要求,只靠 Qt 提供的那些 QGraphicsXXXItem 也是 OK 的。
我記得在實作時候也有考慮過是不是繼承 QAbstractGraphicsShapeItem 就好,後來好像因為什麼原因,還是繼承 QGraphicsItem。

照著 Qt 的說明文件,要實作自己的 QGraphicsItem,重點在打造兩個函式:
QRectF boundingRect() const override
這個函式要回傳一個 QRectF,標示這個 Item 的大概位置,讓 Scene 能用它作分類跟檢索,如果設錯的話就有可能變成 item 明明在某個位置視窗卻打死不顯示它,因為 Scene 在檢索的時候就不認為這個 Item 有需要顯示。
void paint(
  QPainter *painter,
  const QStyleOptionGraphicsItem *option,
  QWidget *widget) override
Paint 就是操作 Painter 裡面的函式盡情的作畫,不過我自己是省得麻煩,都是創一個 QPainterPath 然後呼叫 Painter 的 drawPath,這樣比較簡單。
舉例來說我自己實作的一個物件是在視窗上面標上一個箭頭然後顯示文字,大略來說程式碼就是這樣:
QPainterPath path;
path.moveTo(0, 0);
path.lineTo(0, 8);
path.lineTo(5.656, 5.656);
path.closeSubpath();
path.lineTo(7.65, 18.48); // len 20, tilt 22.5
path.setFillRule(Qt::WindingFill);
QFont serif("Helvetica", 12);
path.addText(QPoint(0, 0), serif, m_text);
paint 裡再用 drawPath 把這條 path 畫出來就可以了。

Paint 的另外兩個參數 QStyleOptionGraphicsItem 和 QWidget,前者帶著顯示上要用的參數,在下篇做一些細部設定的時候會用到;後者則指向目前繪圖中的物件,通常可以不用管它,我也還不確定什麼時候會用到它。
這次實作全面採用 C++ 的新關鍵字 override,個人認為真的好用,像是上面的 boundingRect 如果沒寫 const 的話其實是在實作不同的函式,加了 override 編譯器就會跳錯誤,比較不會犯這種不容易注意到的錯。

實作 QGraphicsItem 以我們的應用這樣就夠了,如果還需要更精細的管理,可以再實作函式:
QPainterPath shape() const
這個函式用來檢查碰撞、滑鼠有沒有點在物件上等等,不實作預設就是以 boundingRect 代替。

自己幹完 QGraphicsItem 之後,整個程式也有了個樣子了,繼承自 QGraphicsItem 的物件可以直接用 addItem 塞進 Scene 裡面,下面截兩張運作起來的樣子,分別是顯示箭頭跟一個三角形的多邊形:



還有很多細部設定沒做所以看起來會有一點粗糙,下一篇預定就是要講這些細部的東西。

2019年12月5日 星期四

用 Qt Graphics 做一個顯示座標的工具

故事是這樣的,平常小弟在公司處理的東西多半是一些 polygon, line 之類的資料,除錯的時候總不能看著 gdb 印出來的 x, y 座標 debug,所以公司同仁有自幹一套 debug 的工具幫忙把這些資料畫出來;不過呢…這套工具好像在記憶體模型那邊有點問題,資料量大的時候會變超慢;畫圖是直接 call xlib,每次放大縮小都要重畫所有物件,對記憶體的負擔又更嚴重。只要 polygon 數量突破幾萬個的時候,一次的 refresh 就會花上好幾秒。

前陣子手上暫時沒其他緊急的事情,乾脆就用 Qt Graphics 重寫一個,不準在留言問我為什麼不用 nodejs 寫,MaDer 公司的工作站就沒有 nodejs。
完工後自己試了一陣發現幾十萬個物件的時候放大縮小都超流暢,不愧是 Qt Graphics,雖然程式行數比我預期的多了些,但架構比本來的東西清楚很多。
其實過程中一直參考強者我同學 AZ 大大的 QCamber,覺得 AZ大大實在太過神猛狂強溫,5-6 年前就寫得出這麼複雜的 project,我一直覺得我寫 code 的時候的整體感很不夠,都是在單一 class 裡面塗塗抹抹,小地方會動可是大架構沒辦法在一開始就訂好,後續要修改的成本就非常高。 Anyway 總之它現有個樣子了,我覺得中間碰過一些實作的問題值得記錄一下,預計可能寫個三篇左右吧。

----

首先圖形顯示的部分,使用的是 Qt 的 graphics framework,可以用來繪製大量的 2D 物件,支援選取、縮放等等,我們這裡只是要顯示而已,也不用搞得這麼複雜。
Graphics framework 裡的三個基本元件就是:
  • QGraphicsScene:場景,可以把它想成一塊巨大的畫布,可以在上面自由放上各種 item,Scene 會幫你管理物件的顯示和更新,個人經驗 Scene 負擔到 10 萬個元件左右還很流暢,上到百萬個的時候就會有點頓了(又或者是我把所有 item 都放在 scene 裡面的關係)。
  • QGraphicsItem:物件,可以想像成畫素描的時候放的那些石膏,在一個場景上擺上東西,Qt 有提供基本的幾種物件:橢圓 QGraphicsEllipseItem、路徑 QGraphicsPathItem、多邊形 QGraphicsPolygonItem、矩形 QGraphicsRectItem跟文字 QGraphicsSimpleTextItem。
  • QGraphicsView:View 是唯一可以在 Qt 的 MainWindow 畫面上出現的物件,可以把 View 想成一台相機,場景 Scene 是不動的,相機從各種角度自由取景,並把取到的景顯示出來,如果取景的尺寸比畫面還要大,跟其他的物件一樣, View 能自動出現捲軸,也可以接收畫面上的滑鼠、鍵盤事件。
只用 Qt 原生的 QGraphicsScene, QGraphicsView, QGraphicsItem 只能組出最基本的顯示工具,變化量非常少,以下就示範一個最基本的設定:
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>

int main(int argc, char *argv[]) {
  QApplication app(argc, argv);
  QGraphicsScene *scene = new QGraphicsScene;
  scene->setSceneRect(0, 0, 400, 400);
  scene->addItem(new QGraphicsRectItem(50, 50, 150, 100));

  QGraphicsView *view = new QGraphicsView;
  view->setScene(scene);
  view->show();

  return app.exec();
}
編譯執行就可以看到這個畫面:

反正這個設計只是一開始建來試驗用的,看一下顯示的效果,很快下一篇就會被我們拆掉了。

2016年9月14日 星期三

不為人知的gdb 使用方式系統-gdb pretty printer

前言:
最近因為jserv 大神的關係,看了下面這部Become a GDB Power User
https://www.youtube.com/watch?v=713ay4bZUrw
覺得裡面還不少生猛的用法之前都不會用,決定把它整理一下,寫個系列文。

這篇來介紹 gdb 的pretty printer:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing.html#Pretty-Printing

假設我們有一個客製的struct 或是class,例如Qt4 的QString,如果我們用原本gdb 的print 去印出QString的話:
QString s("Hello World");
(gdb) p s
$2 = {static null = {<No data fields>}, static shared_null = {ref = {_q_value = 2}, alloc = 0, size = 0, data = 0x7ffff7dd70fa <QString::shared_null+26>, clean = 0,
simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {_q_value = 1}, alloc = 0, size = 0,
data = 0x7ffff7dd70da <QString::shared_empty+26>, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}},
d = 0x603dc0, static codecForCStrings = 0x0}

因為QString 是個結構的關係,我們無法單純印個char array ,反而會印出裡面所有的資訊,它真的資訊是存在那個 d 裡面,要真的印字串就要從那個d 裡面去印。
之前在寫Qt 專案的時候有遇到類似的問題,那時有查到一個printqstring 的自訂函式,把下面這段加到 ~/.gdbinit 裡面:
define printqstring
  printf "(QString)0x%x (length=%i): \"",&$arg0,$arg0.d->size
  set $i=0
  while $i < $arg0.d->size
    set $c=$arg0.d->data[$i++]
    if $c < 32 || $c > 127 
      printf "\\u0x%04x", $c
    else
      printf "%c", (char)$c
    end 
  end
  printf "\"\n"
end

就可以在debug 時使用printqstring來印出QString
(gdb) printqstring s
(QString)0xffffe650 (length=12): "Hello World!"

當然我們可以用gdb 的pretty printer 來做到類似的事,而且不需要自訂函式,單純用p s 也能做到一樣的效果。
gdb v7 開始加入對python的支援,可以讓我們透過python和gdb 互動,在python 之中import gdb 之後,就可以利用一連串定義完善的介面去跟gdb 互動,詳細上用法還不少,這裡我們只先試著寫個pretty_printer。
Python 的pretty printer 就是實作一個class,至少要實作__init__跟to_string() 兩個介面,其他的函式可以參考:
https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API
之後使用gdb.printing的RegexpCollectionPrettyPrinter,產生一個叫QString 的printer,利用add_printer,只要型態名稱符合^QString$ 的物件,就用QStringPrinter來處理,最後用regeister_pretty_printer把這個RegexpCollectionPrettyPrinter 塞進去:
# qstring.py
import gdb.printing

class QStringPrinter(object):
  def __init__(self, val):
    self.val = val
  def to_string(self):
    return "I'm Qstring"
  def display_hint(self):
    return 'string'

pp = gdb.printing.RegexpCollectionPrettyPrinter('QString')
pp.add_printer('QString', '^QString$', QStringPrinter)
gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
進到gdb 之後,首先source qstring.py ,接著去print 一個QString 的話,gdb 就會呼叫我們寫的QStringPrinter 的to_string 來處理,就會得到垃圾訊息:I'm QString。

to_string 回傳的規則如下:
python 的integer, float, boolean, string 都是gdb 可處理的(可轉換為gdb.Value),會由gdb 直接印出;回傳None的話則不會印東西。
傳回其他的gdb.Value,那gdb 會接續處理這個value,處理中也會呼叫其他註冊的pretty printer;真的無法處理,gdb就會丟出exception。
另外我們可以實作display_hint 的介面,告知gdb 要如何處理這個值的輸出,這會影響到gdb 如何接續處理這個值;這裡我們表示這個輸出是個字串。

上面只是舉例,要印出同樣的結果,我們的to_string 要稍微改寫:
addr = self.val.address
size = self.val['d']['size']
i = 0 
content = []
while i < length:
  c = self.val['d']['data'][i]
  if c > 127:
    content.append("\\u%x" % (int(c)))
  else:
    content.append(chr(c))
  i = i + 1 
return '(QString)%s (length=%d): "%s"' % (addr, size, "".join(content))
當然我在查這個的時候,發現有另一個人的實作生猛得多:
http://nikosams.blogspot.tw/2009/10/gdb-qt-pretty-printers.html
dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer())
content = dataAsCharPointer.string(encoding='utf-16', length=size*2)
它直接用cast 得到一個新的gdb.Value(),型態為char pointer;再呼叫string()方法用utf-16 的方式encoding,就把字串給拿出來了,更多用法可以參考文件,這裡暫時不詳細說明:
https://sourceware.org/gdb/onlinedocs/gdb/Values-From-Inferior.html

不過,這樣的pretty printer 其實有個問題的,因為它寫死了一定會去讀取self.val['d']裡面的值,但這個值未必是有初始化的,這時使用pretty printer 就會出錯:
<error reading variable: Cannot access memory at address 0xa>

其實要用pretty printer 的話,在.gdbinit 裡面加上 set print pretty on 就是個不錯的開始,光這樣就能讓一些輸出漂亮很多(其實也就是塞在一行裡跟自動換行的差別XDDD),相對開了這個print 也會佔掉比較多行數。
一般除非是客製的資料結構又有大量debug 的需求,才會需要客製化pretty printer,像是gdb 內應該已內建C++ std 的pretty printer,Qt 的開發套件應該也有提供Qt 的;寫rust時所用的rust-gdb也只是對gdb 進行擴展,在開始時先引入設定檔,裡面也有Rust 的pretty printer。
# Run GDB with the additional arguments that load the pretty printers
PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIRECTORY" gdb \
  -d "$GDB_PYTHON_MODULE_DIRECTORY" \
  -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \
  "$@"

但一般人似乎不太用得到這個功能,所以這篇其實是廢文一篇(蓋章,感謝大家貢獻兩分鐘的時間給小弟的blog (炸。

2014年12月9日 星期二

Translate Qt translation file (.ts file) using Python and Google Translate

Pyliguist

A python script translates Qt ts file by google translate

Recently I'm working on project Qucs. This project only has a little group of developers.

The program got about 3000 entries need to be translated, which, however, no one wants to translate them. To translate them manually is a very hard, and time-consuming work. It makes me think of using Python to translate these text automatically.

Luckily, there do have an automatic tool: Python Goslate package.
It's very simple to use, just create a goslate object. Then you can call function translate to translate the text, like this:

>>>go="goslate.Goslate()
>>>go.translate(“worship”, “zh-tw”)
崇拜

I write a little script “PyLinguist” in Python, which use Python “xml” to parse Qt translation file. Then use Python package “Goslate” to translate the text.

It takes 30 seconds to translate 200 entries in test file, which is much faster than translate manually. Though there is some problem. For example it translate “Help” into “Save me” in Chinese, but generally it works.

The source code is here:
www.github.com/yodalee/PyLinguist

2014年12月1日 星期一

使用python 與Google Translate進行程式翻譯

最近Qucs Project有個德國佬加入,這個……一加入就做了不少苦力的工作,像換掉一些Qt3才支援的function,換個Qt4相對應的名字,他說他是用Xcode的取代功能寫的,老實說這個東西不是用sed就可以解決嗎(._.),不過算了,有人幫忙總是好事。

他後來又貢獻了一個PR,內容是把整個程式的德文翻譯加了一千多個翻譯,根本巨量苦力;同時他又開了一個issue,想要把德文的翻譯給補完,我覺得這樣一個一個翻譯有點太累了,雖然Qt 有linguist幫忙,可是其實還是很累,遇到沒翻過的,還是要自行輸入。

當下靈機一動,想到之前看過有人用Google Translate來自動進行Gnu Po檔的繁簡轉換,那一樣我能不能用Google Translate進行Qt 的翻譯呢?

為了這個我寫了一個PyLinguist的script,輔助工具選用的是Python的package goslate:
使用方法很簡單,產生一個goslate的物件後,叫個function 即可:
go=goslate.Goslate()
go.translate("worship", "zh_tw")
'崇拜'

同樣的,qt的翻譯檔就是一個xml 檔,python要對付xml也是小菜一碟,用xml.etree.Element即可,每個要翻譯的文字會以這樣的格式記錄:
<message>
  <source>Hu&amp;e:</source>
  <translation type="unfinished"></translation>
</message>
Source是原始文字,Translation則是翻譯文,如果還沒翻譯,就會在translation tag加上type=”unfinished”的屬性。

整體程式流程大概是這樣:

讀檔,把整個xml 讀到一個xml tree 物件裡:
self.tree = ET.parse(XXX.ts)
root = self.tree.getroot()

開始翻譯,這裡我設定self.maplist這個dict物件,記錄所有翻過的內容,這樣只要以後再次出現就取用之前的結果,省下網路回應的時間;之所以要在translate的外面加上try, except,是我發現goslate在翻譯OK這個字時,不知為何會出現錯誤,為了讓程式跑下去只好出此下策;如果找到翻譯文,就替代掉之前的文字並拿掉unfinished的屬性。
for msg in root.iter('message'):
  source = msg.find('source').text
  isTranslated = not (msg.find('translation').attrib.get('type') == "unfinished")
  if not isTranslated:
    if source in self.maplist:
      msg.find('translation').text = self.maplist[source]
      del msg.find('translation').attrib['type']
    else:
      try:
        text = self.gs.translate(source, target_lang)
        msg.find('translation').text = text
        self.maplist[source] = text
        del msg.find('translation').attrib['type']
      except Exception:
        pass

最後把程式寫出去即可,也是一行搞定:

self.tree.write(filename, xml_declaration=True, encoding="UTF-8", method="html")

結果:

目前除了輸出時如xml 的DOCTYPE宣告會消失,大致上的功能是可接受的,翻譯800行約100到200個翻譯文的檔案,大約30秒就翻完了,這之中可能還有一些是google translate回應的時間,這已經比人工還要快了不少。

結論:

感恩python,讚嘆python
老話一句:working hard, after you know you are working smart,翻譯這種事多無聊,丟給google做就好啦。

原始碼:

本程式(毫無反應,其實就只是個腳本XDD)為自由軟體,原始碼公布在:
https://github.com/yodalee/PyLinguist

參考資料:

1. Goslate:
http://pythonhosted.org/goslate/
2. python xml
https://docs.python.org/2/library/xml.etree.elementtree.html

2014年11月17日 星期一

使用autotool 編譯qt project

在寫這篇,我發現我曾經寫過類似的內容:
「使用gnu make編譯Qt 專案」
http://yodalee.blogspot.tw/2013/08/gnu-makeqt.html

總之,這次又是在qucs專案上遇到的問題,之前專案裡的使用者介面,不知道是哪根筋不對,竟然全部都是用手爆的啊啊啊!正好這個project現在進入巨量refactor階段,在改其中一個部分時,順手把其中一個使用者介面換用Qt的Designer來做。
結果,還要改編譯的autotool,讓它使用UIC解決才行,網路上找找沒什麼資料,只好印autotool的文件下來看,以下是我最後弄出來的Makefile.am設定:

首先,定義目標:此資料夾的靜態函式庫及它的原始碼:
noinst_LIBRARIES = libdialogs.a
libdialogs_a_SOURCES = xxxxx.cpp

原始碼後面可能有一大串,一般來說autotool有這樣有夠了,不過對Qt project,首先我們需要MOC: Meta object compiler將標題檔編譯為moc.cpp原始碼,並把它們加到靜態函式庫的原始碼中:
MOCHEADERS = xxxxx.h
MOCFILES = $(MOCHEADERS:.h=.moc.cpp)
.h.moc.cpp:
    $(MOC) -o $@ lt;
nodist_libdialogs_a_SOURCES = $(MOCFILES)
這會讓編譯器去編譯MOCFILES,找不到就用副檔名規則呼叫MOC產生.moc.cpp檔。

UIC比較麻煩一點,因為標頭檔不像原始碼一樣,會被加到編譯時的相依規則中,若像原始碼在相依規則裡,相依性不符時,Makefile會自動去尋找有沒有可以產生此相依的規則,如上面的.h.moc.cpp;但標頭檔就算全不見了,不設定的話Makefile也不會做什麼。
這裡我們使用autotool的BUILT_SOURCES來解,被加到這個變數的內容會優先被編譯:

UICFILES = ui_yyyy.h
BUILT_SOURCES = $(UICFILES)
ui_%.h: %.ui
    $(UIC) -o $@ lt;
noinst_HEADERS = $(MOCHEADERS) $(UIHEADERS)
如此一來就能順利的呼叫UIC幫我們產生標題檔了;不過含有%這種寫法是Makefile.am使用Gnu Extension達成的,因些在產生Makefile.in時會收到警告,如果不用這種寫法就只能寫明所有*.ui轉成ui_*.h的規則了。

rcc的話我是沒有用,不過它是將qrc檔轉成原始碼檔,因此估計使用方法跟moc比較像。

參考資料:
1. autotool BUILT_SOURCES:
http://www.gnu.org/savannah-checkouts/gnu/automake/manual/html_node/Sources.html

2013年8月8日 星期四

使用gnu make編譯Qt 專案

最近白天跑EM,夜深寫QT。

當然這完全沒什麼,其他同學至少兩年前就寫過了LOL。

只能說Qt 真是相當強大的工具,最基本的signal & slot的概念,如果對GTK的callback熟悉的話,很快就能上手。
一般在寫Qt時,最常用的還是用qmake來產生Makefile,畢竟qmake寫得還不賴,打一次就會產生好Makefile,接著make即可;不過有時個人習慣還是偏好用gnu-make,可以自己編寫Makefile,做一些細部的調整,用qmake的話只要重新產生一次Makefile,這些細部調整就要重新再修改一次。
這篇就是說明一下,要如何使用gnu-make來處理Qt專案。

--

基本上Qt的運作原理,是對原有的C++進行擴展,然後透過Qt提供的解析程式,產生額外的原始碼檔,少了這些額外的原始碼,在編譯的時候會產生一堆可怕的錯誤資訊,要用gnu-make編譯Qt專案,其實只要呼叫這些解析程式來產生原始碼即可;總共會用到的程式有三個:moc, rcc, uic;分別作用是:產生meta object(故名Meta-Object Compiler)、處理resource(resource compiler)檔、產生介面檔(User Interface Compiler)。

一般Makefile裡面會有這些東西,用來把所有的source轉換成object file。
SOURCE_FILE = main.cpp 
OBJECT_FILE += $(addsuffix .o, $(basename $(SOURCE_FILE)))

為了Qt,我們加上下列的變數定義:
#==================================================
# Qt special function
#==================================================
QT_LIBS = -lQtCore -lQtGui -lQtOpenGL
QT_PATH = /usr/lib/qt4/bin
QT_MOCFILE = mainwindow.h
QT_RCCFILE = resource.qrc
QT_UICFILE = first.ui
QT_MOCSOURCE = $(addprefix moc_, $(addsuffix .cpp, $(basename $(QT_MOCFILE))))
QT_RCCSOURCE = $(addprefix qrc_, $(addsuffix .cpp, $(basename $(QT_RCCFILE))))
QT_UICSOURCE = $(addprefix ui_, $(addsuffix .h, $(basename $(QT_UICFILE))))

這一整個區塊與QT有關,所有參數都加上QT為標示。
QT_PATH設定Qt的執行檔位置,如果安裝在其他地方、或要用其他版本就要自己換地方。
QT_*FILE是先定義,moc, rcc, uic分別要處理的檔案,moc會處理所有.h檔,產生含meta object的cpp檔;rcc會處理qrc file,產生相對應的cpp檔;uic則會處理ui,產生可包入的header file。
透過makefile的suffix, prefix,轉成我們需要轉出的檔案名稱,個人的習慣是在這些檔名前加上關鍵字moc_, qrc_, ui_。

--

接著是利用implicit rules來compile所有的物件檔:
TARGET = program
BIN_DIR = bin
LIBRARY_DIR = library
SOURCE_FILE = main.cpp $(QT_MOCSOURCE) $(QT_RCCSOURCE)
OBJECT_FILE += $(addsuffix .o, $(basename $(SOURCE_FILE)))

由於moc和rcc會產生新的cpp檔,因此需要將它們列入;然後就可以用implicit rules執行:

%.o:%.c
  $(CC) -c lt; -o $@ $(CFLAGS) $(INCLUDE)
  @$(MOVE) $@ $(LIBRARY_DIR)

%.o:%.cpp
  $(CXX) -c lt; -o $@ $(CXXFLAGS) $(INCLUDE)
  @$(MOVE) $@ $(LIBRARY_DIR)

moc_%.cpp: %.h 
  $(QT_PATH)moc $(DEFINES) $(INCLUDE) lt; -o $@

qrc_%.cpp: %.qrc
  $(QT_PATH)rcc lt; -o $@

ui_%.h: %.ui
  $(QT_PATH)uic lt; -o $@
前兩項是將c/cpp-編成 .o檔,當遇到QT_MOCSOURCE, QT_RCCSOURCE的檔案,就會由moc, rcc產生。
另外在編譯主程式的相依性中,原本我們只要它編譯所有的OBJECT_FILE,現在還要加上由uic產生的header file:
$(TARGET): $(OBJECT_FILE) $(QT_UICSOURCE)
  cd $(LIBRARY_DIR); \
  $(LINKER) -o $@ $(OBJECT_FILE) $(LIBS); \
  cd ..
  mv $(LIBRARY_DIR)/$@ $(BIN_DIR)
如此一來,就會觸發uic產生出header file。

--

透過以上的設定,即可完成Qt專案的編譯,不過最後還是要說,雖然我這裡是這麼寫,但其實真的用的時候還是用qmake來產生Makefile =w=,套句AZ大神的話「它寫得這麼好幹嘛不用?你白痴嗎。」

結論:這篇網誌是一篇垃圾,寫Qt請愛用qmake。 lol

參考資料:
1. C++ GUI Programming with Qt 4
2. http://woboq.com/blog/how-qt-signals-slots-work.html