歡迎您光臨本站 登入註冊首頁

QT 顯示機制

admin @ 2014-03-25 , reply:0

概述

   QT被Nokia收購了,我們以後的項目用QT做UI開發的可能性也不大,這些都無所謂,嵌入式系統的UI開發包大體架構應該還是相通的,深入了解QT對以後理解新的平台應該……

    QT 被Nokia 收購了,我們以後的項目用QT 做UI 開發的可能性也不大,這些都無所謂,嵌入式系統的UI 開發包大體架構應該還是相通的,深入了解QT 對以後理解新的平台應該是有幫助的,QT 有很多免費的版本在網上很容易找到並下載,這樣的話大家都可以參看源代碼共同學習。我不是個技術水平高的人,所以大家不要給我扔磚頭。
    QT 的窗體系統的管理,窗體事件是如何派發的,在前一篇《QT 窗體事件底層派發機制》中已經作了簡要的分析,QT 的顯示機制算是對上一篇文章的補充。
    了解QT 顯示機制,最重要的就是要了解QT 是如何管理窗體的顯示區域的,這裡有個重要的類:QRegion, 在QT 中可以通過QRegion 定義一個窗體的顯示區域,也可以通過QRegion 定義窗體的可修改區域,比如在QPainter()中通過QPainter::setClipRect 設定一個區域,我們繪圖則只能在這個區域,此區域外繪圖都是無效的。通過QRegion 可以作一系列的邏輯運算,如兩個區域相加,相減等。QRegion定義的區域不一定是連續的,但一定是由封閉的區域組成的,我們常會碰到一個窗體的顯示區域被其他窗體分割為幾塊的情況。QT 對這些顯示區域的管理,類似於對窗體的管理,也是通過伺服器與客戶端的方式。參照以前的說法Server 表示為全局的Global ,客戶端為本地得Local。那麼WindowsServer管理一個全局的顯示區域即所有的Top-Level widget 顯示區域。而其他的child windget 的管理則在每一個QT 應用程序中由QWSRegionManager 管理,Top_Level widget 顯示區域也會載入在其中,這個不難理解,因為Server 只是負責將窗體事件發送到客戶端,具體處理還是由客戶端來操作。具體的流程還是來看代碼吧。
顯示區域管理者QWSRegionManager 的初始化
伺服器:
通過調用openDisplay()。
客戶端:
在QWSDisplayData 類的構造函數中通過調用QWSDisplayData::init()完成。
    考慮一個比較簡單的情況,我們要顯示的widget 是一個Top_Leverl widget。在調用Show()函數中,這個widget 將通過showWindows()向伺服器請求做三件事:(以下窗體是指在global windows statck中的TOP_Level widget)

  1. 調用QWSDisplay::requestRegion 向伺服器請求窗體顯示區域。
  2. 調用QWSDdisplay::setAltitude 向伺服器請求設置窗體的優先順序。此優先順序是指在windows statck 中的位置,而不是指QWSWidow 中的窗體優先順序屬性。Windows statck 中的第一個窗體就是顯示在LCD上最前面的窗體。
  3. 調用QWSDisplay::requestFocus 向伺服器請求設置窗體為焦點窗體。焦點窗體能接收Key, Mouse 事件,但不是所有的焦點窗體都能接收Key,Mouse 事件,如果有窗體設置為GrabKey 或則GrabMouse 則Key, Mouse 事件將分別傳遞至此窗體。

    下面將通過代碼分析winddows Server 對這三個請求的處理過程:
一: QWSDisplay::requestRegion 的處理
void QWSServer::invokeRegion( QWSRegionCommand *cmd, QWSClient *client )
{
................
QRegion region;
region.setRects(cmd->rectangles, cmd->simpleData.nrectangles);
if ( !region.isEmpty() )
changingw->setNeedAck( TRUE );
bool isShow = !changingw->isVisible() && !region.isEmpty();
setWindowRegion( changingw, region ); //***設置窗體顯示區域
syncRegions( changingw ); //***通知客戶端 刷新顯示區域
if ( isShow )
emit windowEvent( changingw, Show );
if ( !region.isEmpty() )
emit windowEvent( changingw, Geometry );
else
emit windowEvent( changingw, Hide );
if ( focusw == changingw && region.isEmpty() )
setFocus(changingw,FALSE);
.................
}
    invokeRegion 調用setWindowRegion 設置窗體顯示區域,調用syncRegions 通知客戶端 刷新顯示區域,併產生一些窗體事件如:Show, Geometry,Hide 。
    setWindowRegion 函數的實現如下:
QRegion QWSServer::setWindowRegion( QWSWindow* changingw, QRegion r )
{
QRegion exposed;
if (changingw) {
changingw->requested_region = r;
r = r - serverRegion; //exposed不為空則有顯示區域被釋放
exposed = changingw->allocation() - r; //低等級窗體增加可見區域
} else {
exposed = serverRegion-r;
serverRegion = r;
}
QRegion extra_allocation;
int windex = -1;
bool deeper = changingw == 0;
for (uint i=0; i<windows.count(); i++) {
QWSWindow* w = windows.at(i);
if ( w == changingw ) {
windex = i;
extra_allocation = r - w->allocation(); //如果extra_allocation不為空
deeper = TRUE; //需要增加新的新的顯示區域
} else if ( deeper ) {
w->removeAllocation(rgnMan, r);//低優先順序窗體去掉被覆蓋的區域
r -= w->allocation();//如果r為空 則更低優先順序的窗體被完全覆蓋
} else { //如果窗體是第一次調用Show 直接走這
//higher windows
r -= w->allocation();//如果r為空 則窗體被高優先順序窗體完全覆蓋
}
if ( r.isEmpty() ) { //窗體被完全覆蓋
break; // Nothing left for deeper windows
}
}
...................
if ( changingw && !changingw->requested_region.isEmpty() )
changingw->addAllocation( rgnMan, extra_allocation & screenRegion );
//為changingw窗體增加新的可見區域 置modifed標誌為TRUE
else if ( !disablePainting )
paintServerRegion();
exposeRegion( exposed, windex+1 );//增加低級窗體可見區域。
return exposed;
}
註:增加新的顯示區域不一定是整個顯示區域的面積增大了,而是顯示區域的塊變多了。 一個顯示區域可能由多個不連續和連續的Region 組成。
void QWSServer::exposeRegion( QRegion r, int start )
{
r &= screenRegion;
for (uint i=start; i<windows.count(); i++) {
if ( r.isEmpty() ) //可見區域為空
break; // Nothing left for deeper windows
QWSWindow* w = windows.at(i);
w->addAllocation( rgnMan, r ); //增加新的可見區域 置modifed標誌為TRUE
r -= w->allocation(); //r 更低級窗體可見區域
}
dirtyBackground |= r; //得到需要刷新的背景區域 如果r為空 則新增區域為0
}
exposeRegion 為低等級窗體增加可見區域。
syncRegions:此函數主要是向客戶端發送RegionModified 事件,真正的繪圖也是由客戶端來完成。 還是通過代碼來分析:
void QWSServer::syncRegions( QWSWindow *active )
{
rgnMan->commit(); //拷貝數據到一段共享內存,伺服器為讀寫許可權,客戶端為只讀
notifyModified( active );//通過客戶端顯示區域已更改,客戶端繪製相關區域
paintBackground( dirtyBackground );//繪製背景區域修改部分。
dirtyBackground = QRegion();
}
void QWSServer::notifyModified( QWSWindow *active )
{
// notify active window first
if ( active )
active->updateAllocation(); //首先通知active 窗體
// now the rest //通知所有modified標誌為TRUE的窗體
for (uint i=0; i<windows.count(); i++) {
QWSWindow* w = windows.at(i);
w->updateAllocation();
}
}
void QWSWindow::updateAllocation()
{
if ( modified || needAck) {
c->sendRegionModifyEvent( id, exposed, needAck ); // 發送消息
exposed = QRegion(); //複位低級窗體新增顯示區域
modified = FALSE; //modified為真表示窗體的顯示區域被修改。
needAck = FALSE;
}
}
 
 
客戶端對RegionModifyEvent的處理。
客戶端接收到消息後會調用translateRegionModifiedEvent函數來進行處理
bool QETWidget::translateRegionModifiedEvent( const QWSRegionModifiedEvent
*event )
{
QWSRegionManager *rgnMan = qt_fbdpy->regionManager();
if ( alloc_region_index < 0 ) {
alloc_region_index = rgnMan->find( winId() ); //從共享內存中得到region索引
if ( alloc_region_index < 0 ) {
return FALSE;
}
}
QWSDisplay::grab();
int revision = *rgnMan->revision( alloc_region_index );
if ( revision != alloc_region_revision ) {
alloc_region_revision = revision;
QRegion newRegion = rgnMan->region( alloc_region_index );//得到顯示區域
QWSDisplay::ungrab();
alloc_region = newRegion;
// set children's allocated region dirty
................
} else {
QWSDisplay::ungrab();
}
if ( event->simpleData.nrectangles )
{ // alloc_region >= exposed
QRegion exposed; //需要刷新區域的大小
exposed.setRects( event->rectangles, event->simpleData.nrectangles );
QSize s( qt_screen->deviceWidth(), qt_screen->deviceHeight() );
exposed = qt_screen->mapFromDevice( exposed, s );
qwsUpdateActivePainters();
repaintDecoration( exposed, FALSE );//繪製窗體的一些修飾如邊框,caption等
repaintHierarchy( exposed, FALSE ); //繪製窗體顯示區域及子窗體通過發送
} //PaintEvent事件到各窗體
qws_regionRequest = FALSE;
return TRUE;
}
    repaintHierarchy函數中所有需要刷新的子窗體都會收到Paint事件。在Paint事件中,開始繪圖。顯示中只刷新exposed這個區域而不是將分配的區域alloc_region 全部刷新一次,這樣做可以提高效率。

二:QWSDdisplay::setAltitude 的處理
invokeSetAltitude(const QWSChangeAltitudeCommand *cmd,
QWSClient *client)
{
int winId = cmd->simpleData.windowid;
int alt = cmd->simpleData.altitude;
bool fixed = cmd->simpleData.fixed;
...................
QWSWindow* changingw = findWindow(winId, 0);
...................
changingw->setNeedAck( TRUE );
if ( fixed && alt >= 1) {
changingw->onTop = TRUE;
}
if ( alt < 0 )
lowerWindow( changingw, alt ); //窗體優先順序下降
else
raiseWindow( changingw, alt ); // 提升窗體優先順序
if ( !changingw->forClient(client) ) {
refresh();
}
}
    invokeSetAltitude 通過調用lowerWindow,raiseWindow 來調整窗體的優先順序,如果一個Widget 被顯示,即調用Show 此時alt == 0; 如果alt == 1 則此窗體應該為最上層,如果alt == 2則窗體位FULL-SCREEN 即全屏顯示的窗體,可以通過setWFlags(WStyle_StaysOnTop) 來設定這個屬性。 優先順序較高的窗體將被優先顯示, 在沒有顯式通過SetRegionPriority 命令來改變窗體優先順序的話, 在Windows Stack 中窗體將按照後進的優先順序較高為原則。 可以參考insertPrioritizedWindow 函數,在qt-embedded-free-3.3.6 可能沒有這個函數,因為在這個版本中不存在窗體優先順序,除了WStyle_StaysOnTop 屬性的窗體為第一級優先順序外,其他窗體都按照後進的優先為原則。

三:QWSDisplay::requestFocus 的處理請參考invokeSetFocus 函數。
繪圖的底層操作
    LCD 屏幕上的每個點都與顯示緩衝區中的數據有特定的關係,對16 位色的顯示緩衝區,每兩個位元組對應LCD 上的一個像素,因此我們只要修改緩衝區某兩個地址上的數據就可以改變LCD 上這個點的色彩,如果我們能夠得到這個顯示緩衝區的地址,應用程序編寫者就可以拋開驅動程序那些抽象的介面,直接對顯示緩衝區操作。Linux 上有這種專門的驅動支持就是Framebuffer 驅動程序,Framebuffer 也就是幀緩衝,驅動程序創建一個緩衝區用做顯示buffer。應用程序開發人員可以通過MMAP 將Framebuffer設備重新映射,這樣我們對LCD 的操作就可以象操作一個二位數組一樣方便。QT 正是這麼做的。QWidget 繼承了QPaintDevice,通過graphicsContext 介面,我們可以方便地得到這個窗體的設備上下文。這一過程是如何實現的呢?
QGfx * QWidget::graphicsContext(bool clip_children) const
{
QGfx * qgfx_qws;
qgfx_qws=qwsDisplay()->screenGfx(); //創建設備上下文
updateGraphicsContext( qgfx_qws, clip_children );
return qgfx_qws;
}
    Qapplication 調qt_get_screen( int display_id, const char *spec )來獲得相應的驅動程序。創建驅動程序后調用qt_screen->connect 將客戶端與FrameBuffer 即幀緩衝映射起來,具體代碼可以參考QLinuxFbScreen 。當一個窗體就收到PaintEvent 事件后就可以創建QPaint 對象繪圖,通過設備上下文獲得的繪圖介面實際上都是在對幀緩衝進行操作。 如果驅動程序不支持雙緩衝操作的話,繪圖的結果將直接顯示在LCD 上。


[admin via 研發互助社區 ] QT 顯示機制已經有3387次圍觀

http://cocdig.com/docs/show-post-42107.html