X Window研究笔记(11)
X Window研究笔记(11)
转载时请注明出处和作者联系方式
作者联系方式:李先静 <xianjimli at hotmail dot com>
11.X Window扩展机制--对象装饰
Decorator模式是一个非常重要的模式,它在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。X Server是用C开发的,不方便使用正统的装饰模式,但大量使用了类似装饰模式的扩展方式。可以被装饰的对象有:
- 屏幕: ScreenRec
- 窗口:WindowRec
- 图片: PixmapRec
- 绘图上下文: GC
- 颜色映射: Colormap
- 客户端:ClientRec
其实现过程可以这样理解:
- 初始化时,把原始对象的部分函数指针保存下来,并用自己的函数去替代它。
- 调用时,装饰之后的函数被调用,在该函数中,完成一些装饰性功能,并适当的位置调用原始的函数。
其中,用得最多的是对ScreenRec的装饰,下面我们以sprite模块中对ScreenRec的装饰为例,分析一下它的实现原理:
sprite指像鼠标指针一类的屏幕精灵,它的特点是,形状可能不规则,可以在屏幕上移动,会覆盖当前位置的图像,当它移动到新的位置时,自动恢复先前位置的图像。
sprite的初始化是在miSpriteInitialize中完成的:
Bool
miSpriteInitialize (pScreen, cursorFuncs, screenFuncs)
ScreenPtr pScreen;
miSpriteCursorFuncPtr cursorFuncs;
miPointerScreenFuncPtr screenFuncs;
{
miSpriteScreenPtr pPriv;
VisualPtr pVisual;
#ifdef RENDER
PictureScreenPtr ps = GetPictureScreenIfSet(pScreen);
#endif
if (miSpriteGeneration != serverGeneration)
{
miSpriteScreenIndex = AllocateScreenPrivateIndex ();
if (miSpriteScreenIndex < 0)
return FALSE;
miSpriteGeneration = serverGeneration;
miSpriteGCIndex = AllocateGCPrivateIndex ();
}
if (!AllocateGCPrivate(pScreen, miSpriteGCIndex, sizeof(miSpriteGCRec)))
return FALSE;
pPriv = (miSpriteScreenPtr) xalloc (sizeof (miSpriteScreenRec));
if (!pPriv)
return FALSE;
if (!miPointerInitialize (pScreen, &miSpritePointerFuncs, screenFuncs,TRUE))
{
xfree ((pointer) pPriv);
return FALSE;
}
for (pVisual = pScreen->visuals;
pVisual->vid != pScreen->rootVisual;
pVisual++)
;
pPriv->pVisual = pVisual;
pPriv->CloseScreen = pScreen->CloseScreen;
pPriv->GetImage = pScreen->GetImage;
pPriv->GetSpans = pScreen->GetSpans;
pPriv->SourceValidate = pScreen->SourceValidate;
pPriv->CreateGC = pScreen->CreateGC;
pPriv->BlockHandler = pScreen->BlockHandler;
pPriv->InstallColormap = pScreen->InstallColormap;
pPriv->StoreColors = pScreen->StoreColors;
pPriv->PaintWindowBackground = pScreen->PaintWindowBackground;
pPriv->PaintWindowBorder = pScreen->PaintWindowBorder;
pPriv->CopyWindow = pScreen->CopyWindow;
pPriv->ClearToBackground = pScreen->ClearToBackground;
pPriv->SaveDoomedAreas = pScreen->SaveDoomedAreas;
pPriv->RestoreAreas = pScreen->RestoreAreas;
#ifdef RENDER
if (ps)
{
pPriv->Composite = ps->Composite;
pPriv->Glyphs = ps->Glyphs;
}
#endif
pPriv->pCursor = NULL;
pPriv->x = 0;
pPriv->y = 0;
pPriv->isUp = FALSE;
pPriv->shouldBeUp = FALSE;
pPriv->pCacheWin = NullWindow;
pPriv->isInCacheWin = FALSE;
pPriv->checkPixels = TRUE;
pPriv->pInstalledMap = NULL;
pPriv->pColormap = NULL;
pPriv->funcs = cursorFuncs;
pPriv->colors[SOURCE_COLOR].red = 0;
pPriv->colors[SOURCE_COLOR].green = 0;
pPriv->colors[SOURCE_COLOR].blue = 0;
pPriv->colors[MASK_COLOR].red = 0;
pPriv->colors[MASK_COLOR].green = 0;
pPriv->colors[MASK_COLOR].blue = 0;
pScreen->devPrivates[miSpriteScreenIndex].ptr = (pointer) pPriv;
pScreen->CloseScreen = miSpriteCloseScreen;
pScreen->GetImage = miSpriteGetImage;
pScreen->GetSpans = miSpriteGetSpans;
pScreen->SourceValidate = miSpriteSourceValidate;
pScreen->CreateGC = miSpriteCreateGC;
pScreen->BlockHandler = miSpriteBlockHandler;
pScreen->InstallColormap = miSpriteInstallColormap;
pScreen->StoreColors = miSpriteStoreColors;
pScreen->PaintWindowBackground = miSpritePaintWindowBackground;
pScreen->PaintWindowBorder = miSpritePaintWindowBorder;
pScreen->CopyWindow = miSpriteCopyWindow;
pScreen->ClearToBackground = miSpriteClearToBackground;
pScreen->SaveDoomedAreas = miSpriteSaveDoomedAreas;
pScreen->RestoreAreas = miSpriteRestoreAreas;
#ifdef RENDER
if (ps)
{
ps->Composite = miSpriteComposite;
ps->Glyphs = miSpriteGlyphs;
}
#endif
return TRUE;
}
miSpriteInitialize (pScreen, cursorFuncs, screenFuncs)
ScreenPtr pScreen;
miSpriteCursorFuncPtr cursorFuncs;
miPointerScreenFuncPtr screenFuncs;
{
miSpriteScreenPtr pPriv;
VisualPtr pVisual;
#ifdef RENDER
PictureScreenPtr ps = GetPictureScreenIfSet(pScreen);
#endif
if (miSpriteGeneration != serverGeneration)
{
miSpriteScreenIndex = AllocateScreenPrivateIndex ();
if (miSpriteScreenIndex < 0)
return FALSE;
miSpriteGeneration = serverGeneration;
miSpriteGCIndex = AllocateGCPrivateIndex ();
}
if (!AllocateGCPrivate(pScreen, miSpriteGCIndex, sizeof(miSpriteGCRec)))
return FALSE;
pPriv = (miSpriteScreenPtr) xalloc (sizeof (miSpriteScreenRec));
if (!pPriv)
return FALSE;
if (!miPointerInitialize (pScreen, &miSpritePointerFuncs, screenFuncs,TRUE))
{
xfree ((pointer) pPriv);
return FALSE;
}
for (pVisual = pScreen->visuals;
pVisual->vid != pScreen->rootVisual;
pVisual++)
;
pPriv->pVisual = pVisual;
pPriv->CloseScreen = pScreen->CloseScreen;
pPriv->GetImage = pScreen->GetImage;
pPriv->GetSpans = pScreen->GetSpans;
pPriv->SourceValidate = pScreen->SourceValidate;
pPriv->CreateGC = pScreen->CreateGC;
pPriv->BlockHandler = pScreen->BlockHandler;
pPriv->InstallColormap = pScreen->InstallColormap;
pPriv->StoreColors = pScreen->StoreColors;
pPriv->PaintWindowBackground = pScreen->PaintWindowBackground;
pPriv->PaintWindowBorder = pScreen->PaintWindowBorder;
pPriv->CopyWindow = pScreen->CopyWindow;
pPriv->ClearToBackground = pScreen->ClearToBackground;
pPriv->SaveDoomedAreas = pScreen->SaveDoomedAreas;
pPriv->RestoreAreas = pScreen->RestoreAreas;
#ifdef RENDER
if (ps)
{
pPriv->Composite = ps->Composite;
pPriv->Glyphs = ps->Glyphs;
}
#endif
pPriv->pCursor = NULL;
pPriv->x = 0;
pPriv->y = 0;
pPriv->isUp = FALSE;
pPriv->shouldBeUp = FALSE;
pPriv->pCacheWin = NullWindow;
pPriv->isInCacheWin = FALSE;
pPriv->checkPixels = TRUE;
pPriv->pInstalledMap = NULL;
pPriv->pColormap = NULL;
pPriv->funcs = cursorFuncs;
pPriv->colors[SOURCE_COLOR].red = 0;
pPriv->colors[SOURCE_COLOR].green = 0;
pPriv->colors[SOURCE_COLOR].blue = 0;
pPriv->colors[MASK_COLOR].red = 0;
pPriv->colors[MASK_COLOR].green = 0;
pPriv->colors[MASK_COLOR].blue = 0;
pScreen->devPrivates[miSpriteScreenIndex].ptr = (pointer) pPriv;
pScreen->CloseScreen = miSpriteCloseScreen;
pScreen->GetImage = miSpriteGetImage;
pScreen->GetSpans = miSpriteGetSpans;
pScreen->SourceValidate = miSpriteSourceValidate;
pScreen->CreateGC = miSpriteCreateGC;
pScreen->BlockHandler = miSpriteBlockHandler;
pScreen->InstallColormap = miSpriteInstallColormap;
pScreen->StoreColors = miSpriteStoreColors;
pScreen->PaintWindowBackground = miSpritePaintWindowBackground;
pScreen->PaintWindowBorder = miSpritePaintWindowBorder;
pScreen->CopyWindow = miSpriteCopyWindow;
pScreen->ClearToBackground = miSpriteClearToBackground;
pScreen->SaveDoomedAreas = miSpriteSaveDoomedAreas;
pScreen->RestoreAreas = miSpriteRestoreAreas;
#ifdef RENDER
if (ps)
{
ps->Composite = miSpriteComposite;
ps->Glyphs = miSpriteGlyphs;
}
#endif
return TRUE;
}
这个函数有点长,但我们只需要理解关键几点:
- AllocateScreenPrivateIndex分配私有数据空间,用于保存原始函数指针等信息。
- pPriv->PaintWindowBackground = pScreen->PaintWindowBackground; 之类的语句用于保存原始的函数指针。
- pScreen->PaintWindowBackground = miSpritePaintWindowBackground; 之类的语句用于把原始的函数指针替换为装饰之后的函数。
这里要特别说明的是,所谓的原始函数指针,并非一定是原装正品,可能已经是被别的模块装饰之后的函数。
下面我们继续看函数调用的实现:
static void
miSpritePaintWindowBackground (pWin, pRegion, what)
WindowPtr pWin;
RegionPtr pRegion;
int what;
{
ScreenPtr pScreen;
miSpriteScreenPtr pScreenPriv;
pScreen = pWin->drawable.pScreen;
SCREEN_PROLOGUE (pScreen, PaintWindowBackground);
pScreenPriv = (miSpriteScreenPtr) pScreen->devPrivates[miSpriteScreenIndex].ptr;
if (pScreenPriv->isUp)
{
/*
* If the cursor is on the same screen as the window, check the
* region to paint for the cursor and remove it as necessary
*/
if (RECT_IN_REGION( pScreen, pRegion, &pScreenPriv->saved) != rgnOUT)
miSpriteRemoveCursor (pScreen);
}
(*pScreen->PaintWindowBackground) (pWin, pRegion, what);
SCREEN_EPILOGUE (pScreen, PaintWindowBackground, miSpritePaintWindowBackground);
}
miSpritePaintWindowBackground (pWin, pRegion, what)
WindowPtr pWin;
RegionPtr pRegion;
int what;
{
ScreenPtr pScreen;
miSpriteScreenPtr pScreenPriv;
pScreen = pWin->drawable.pScreen;
SCREEN_PROLOGUE (pScreen, PaintWindowBackground);
pScreenPriv = (miSpriteScreenPtr) pScreen->devPrivates[miSpriteScreenIndex].ptr;
if (pScreenPriv->isUp)
{
/*
* If the cursor is on the same screen as the window, check the
* region to paint for the cursor and remove it as necessary
*/
if (RECT_IN_REGION( pScreen, pRegion, &pScreenPriv->saved) != rgnOUT)
miSpriteRemoveCursor (pScreen);
}
(*pScreen->PaintWindowBackground) (pWin, pRegion, what);
SCREEN_EPILOGUE (pScreen, PaintWindowBackground, miSpritePaintWindowBackground);
}
为了看明白这段程序,先得弄清楚两个宏:
SCREEN_PROLOGUE: 用于取出原始的函数指针,后面可以调用原始函数。
#define SCREEN_PROLOGUE(pScreen, field)/
((pScreen)->field = /
((miSpriteScreenPtr) (pScreen)->devPrivates[miSpriteScreenIndex].ptr)->field)
SCREEN_EPILOGUE:重新把装饰过的放回去,以便于下次再调。
#define SCREEN_EPILOGUE(pScreen, field, wrapper)/
((pScreen)->field = wrapper)
弄清楚了这两个宏,上面的程序不难理解了。这种扩展方式的好处在于,运行时动态为对象添加功能,同时又避免了扩展功能与框架的耦合,这是通过子类继承父类,然后重载部分虚函数无法实现的。
(待续)