aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer/src')
-rw-r--r--lib/observer/src/observer_app_wx.erl95
-rw-r--r--lib/observer/src/observer_perf_wx.erl147
2 files changed, 168 insertions, 74 deletions
diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl
index cb5f8c1259..900fba911a 100644
--- a/lib/observer/src/observer_app_wx.erl
+++ b/lib/observer/src/observer_app_wx.erl
@@ -27,6 +27,12 @@
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
+%% Import drawing wrappers
+-import(observer_perf_wx, [haveGC/1,
+ setPen/2, setFont/3, setBrush/2,
+ strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
+ drawText/4, getTextExtent/2]).
+
-record(state,
{
parent,
@@ -37,7 +43,8 @@
current,
app,
sel,
- appmon
+ appmon,
+ usegc = false
}).
-record(paint, {font, pen, brush, sel, links}).
@@ -102,8 +109,11 @@ init([Notebook, Parent]) ->
wxPanel:connect(DrawingArea, left_dclick),
wxPanel:connect(DrawingArea, right_down),
- %% DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
- DefFont = wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL),
+ UseGC = haveGC(DrawingArea),
+ Font = case UseGC of
+ true -> wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT);
+ false -> wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL)
+ end,
SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT),
GreyBrush = wxBrush:new({230,230,240}),
SelBrush = wxBrush:new(SelCol),
@@ -113,7 +123,8 @@ init([Notebook, Parent]) ->
panel =Panel,
apps_w=Apps,
app_w =DrawingArea,
- paint=#paint{font = DefFont,
+ usegc = UseGC,
+ paint=#paint{font = Font,
pen = wxPen:new({80,80,80}, [{width, 2}]),
brush= GreyBrush,
sel = SelBrush,
@@ -218,16 +229,23 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{event = #wxPaint{}},_,
- #state{app_w=DA, app=App, sel=Sel, paint=Paint}) ->
+ #state{app_w=DA, app=App, sel=Sel, paint=Paint, usegc=UseGC}) ->
%% PaintDC must be created in a callback to work on windows.
DC = wxPaintDC:new(DA),
- GC = ?wxGC:create(DC),
- %% Argh must handle scrolling when using ?wxGC
- {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}),
- ?wxGC:translate(GC, Sx,Sy),
+ GC = case UseGC of
+ true ->
+ GC0 = ?wxGC:create(DC),
+ %% Argh must handle scrolling when using ?wxGC
+ {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}),
+ ?wxGC:translate(GC0, Sx,Sy),
+ GC0;
+ false ->
+ wxScrolledWindow:doPrepareDC(DA,DC),
+ DC
+ end,
%% Nothing is drawn until wxPaintDC is destroyed.
- draw(GC, App, Sel, Paint),
- ?wxGC:destroy(GC),
+ draw({UseGC, GC}, App, Sel, Paint),
+ UseGC andalso ?wxGC:destroy(GC),
wxPaintDC:destroy(DC),
ok.
%%%%%%%%%%
@@ -274,12 +292,17 @@ handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}},
{noreply, State#state{app=undefined, sel=undefined}};
handle_info({delivery, Pid, app, Curr, AppData},
- State = #state{panel=Panel, appmon=Pid, current=Curr,
+ State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC,
app_w=AppWin, paint=#paint{font=Font}}) ->
- GC = ?wxGC:create(AppWin),
- ?wxGC:setFont(GC, Font, {0,0,0}),
- App = build_tree(AppData, {GC,Font}),
- ?wxGC:destroy(GC),
+ GC = if UseGC -> ?wxGC:create(AppWin);
+ true -> wxWindowDC:new(AppWin)
+ end,
+ FontW = {UseGC, GC},
+ setFont(FontW, Font, {0,0,0}),
+ App = build_tree(AppData, FontW),
+ if UseGC -> ?wxGC:destroy(GC);
+ true -> wxWindowDC:destroy(GC)
+ end,
setup_scrollbar(AppWin, App),
wxWindow:refresh(Panel),
wxWindow:layout(Panel),
@@ -374,11 +397,11 @@ locate_box(From, []) -> {false, From}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-build_tree({Root, P2Name, Links, XLinks0}, Font) ->
+build_tree({Root, P2Name, Links, XLinks0}, FontW) ->
Fam = sofs:relation_to_family(sofs:relation(Links)),
Name2P = gb_trees:from_orddict(lists:sort([{Name,Pid} || {Pid,Name} <- P2Name])),
Lookup = gb_trees:from_orddict(sofs:to_external(Fam)),
- {_, Tree0} = build_tree2(Root, Lookup, Name2P, Font),
+ {_, Tree0} = build_tree2(Root, Lookup, Name2P, FontW),
{Tree, Dim} = calc_tree_size(Tree0),
Fetch = fun({From, To}, Acc) ->
try {value, ToPid} = gb_trees:lookup(To, Name2P),
@@ -391,18 +414,18 @@ build_tree({Root, P2Name, Links, XLinks0}, Font) ->
XLinks = lists:foldl(Fetch, [], XLinks0),
#app{ptree=Tree, dim=Dim, links=XLinks}.
-build_tree2(Root, Tree0, N2P, Font) ->
+build_tree2(Root, Tree0, N2P, FontW) ->
case gb_trees:lookup(Root, Tree0) of
- none -> {Tree0, {box(Root, N2P, Font), []}};
+ none -> {Tree0, {box(Root, N2P, FontW), []}};
{value, Children} ->
Tree1 = gb_trees:delete(Root, Tree0),
{Tree, CHs} = lists:foldr(fun("port " ++_, Acc) ->
Acc; %% Skip ports
(Child,{T0, Acc}) ->
- {T, C} = build_tree2(Child, T0, N2P, Font),
+ {T, C} = build_tree2(Child, T0, N2P, FontW),
{T, [C|Acc]}
end, {Tree1, []}, Children),
- {Tree, {box(Root, N2P, Font), CHs}}
+ {Tree, {box(Root, N2P, FontW), CHs}}
end.
calc_tree_size(Tree) ->
@@ -447,12 +470,12 @@ middle([{#box{y=Y0},_}|List], _) ->
{#box{y=Y1},_} = lists:last(List),
(Y0+Y1) div 2.
-box(Str0, N2P, {Win,_Font}) ->
+box(Str0, N2P, FontW) ->
Pid = gb_trees:get(Str0, N2P),
Str = if hd(Str0) =:= $< -> lists:append(io_lib:format("~w", [Pid]));
true -> Str0
end,
- {TW,TH, _, _} = ?wxGC:getTextExtent(Win, Str),
+ {TW,TH} = getTextExtent(FontW, Str),
Data = #str{text=Str, x=?BX_HE, y=?BY_HE, pid=Pid},
%% Add pid
#box{w=round(TW)+?BX_E, h=round(TH)+?BY_E, s1=Data}.
@@ -473,30 +496,30 @@ draw(_DC, undefined, _, _) ->
ok;
draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel,
#paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) ->
- ?wxGC:setPen(DC, LPen),
+ setPen(DC, LPen),
[draw_xlink(Link, DC) || Link <- Links],
- ?wxGC:setPen(DC, Pen),
+ setPen(DC, Pen),
%% ?wxGC:drawRectangle(DC, 2,2, _W-2,_H-2), %% DEBUG
- ?wxGC:setBrush(DC, Brush),
- ?wxGC:setFont(DC, Font, {0,0,0}),
+ setBrush(DC, Brush),
+ setFont(DC, Font, {0,0,0}),
draw_tree(Tree, root, DC),
case Sel of
undefined -> ok;
{#box{x=X,y=Y,w=W,h=H,s1=Str1}, _} ->
- ?wxGC:setBrush(DC, SelBrush),
- ?wxGC:drawRoundedRectangle(DC, X-1,Y-1, W+2,H+2, 8.0),
+ setBrush(DC, SelBrush),
+ drawRoundedRectangle(DC, X-1,Y-1, W+2,H+2, 8.0),
draw_str(DC, Str1, X, Y)
end.
draw_tree({Box=#box{x=X,y=Y,w=W,h=H,s1=Str1}, Chs}, Parent, DC) ->
- ?wxGC:drawRoundedRectangle(DC, X,Y, W,H, 8.0),
+ drawRoundedRectangle(DC, X,Y, W,H, 8.0),
draw_str(DC, Str1, X, Y),
Dot = case Chs of
[] -> ok;
[{#box{x=CX0},_}|_] ->
CY = Y+(H div 2),
CX = CX0-(?BB_X div 2),
- ?wxGC:strokeLine(DC, X+W, CY, CX, CY),
+ strokeLine(DC, X+W, CY, CX, CY),
{CX, CY}
end,
draw_link(Parent, Box, DC),
@@ -506,9 +529,9 @@ draw_link({CX,CY}, #box{x=X,y=Y0,h=H}, DC) ->
Y = Y0+(H div 2),
case Y =:= CY of
true ->
- ?wxGC:strokeLine(DC, CX, CY, X, CY);
+ strokeLine(DC, CX, CY, X, CY);
false ->
- ?wxGC:strokeLines(DC, [{CX, CY}, {CX, Y}, {X,Y}])
+ strokeLines(DC, [{CX, CY}, {CX, Y}, {X,Y}])
end;
draw_link(_, _, _) -> ok.
@@ -527,8 +550,8 @@ draw_xlink(X0, Y00, X1, Y11, BH, DC) ->
{Y0,Y1} = if Y00 < Y11 -> {Y00+BH-6, Y11+6};
true -> {Y00+6, Y11+BH-6}
end,
- ?wxGC:strokeLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]).
+ strokeLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]).
draw_str(DC, #str{x=Sx,y=Sy, text=Text}, X, Y) ->
- ?wxGC:drawText(DC, Text, X+Sx,Y+Sy);
+ drawText(DC, Text, X+Sx,Y+Sy);
draw_str(_, _, _, _) -> ok.
diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl
index 96e433cbf2..c4659d1ea1 100644
--- a/lib/observer/src/observer_perf_wx.erl
+++ b/lib/observer/src/observer_perf_wx.erl
@@ -23,6 +23,12 @@
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
handle_event/2, handle_sync_event/3, handle_cast/2]).
+%% Drawing wrappers for DC and GC areas
+-export([haveGC/1,
+ setPen/2, setFont/3, setBrush/2,
+ strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
+ drawText/4, getTextExtent/2]).
+
-behaviour(wx_object).
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
@@ -36,7 +42,8 @@
data = {0, queue:new()},
panel,
paint,
- appmon
+ appmon,
+ usegc = false
}).
-define(wxGC, wxGraphicsContext).
@@ -76,11 +83,18 @@ init([Notebook, Parent]) ->
wxPanel:connect(IO, paint, [callback]),
wxPanel:connect(MEM, paint, [callback]),
- % DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
- %% DefSize = wxFont:getPointSize(DefFont),
- %% DefFamily = wxFont:getFamily(DefFont),
- %% Font = wxFont:new(DefSize, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
- Font = wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD),
+ UseGC = haveGC(Panel),
+ Font = case UseGC of
+ true ->
+ %% Def font is really small when using Graphics contexts for some reason
+ %% Hardcode it
+ wxFont:new(12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD);
+ false ->
+ DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
+ DefSize = wxFont:getPointSize(DefFont),
+ DefFamily = wxFont:getFamily(DefFont),
+ wxFont:new(DefSize, DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD)
+ end,
SmallFont = wxFont:new(10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
BlackPen = wxPen:new({0,0,0}, [{width, 2}]),
Pens = [wxPen:new(Col, [{width, 2}]) || Col <- tuple_to_list(colors())],
@@ -88,6 +102,7 @@ init([Notebook, Parent]) ->
{Panel, #state{parent=Parent,
panel =Panel,
windows = {CPU, MEM, IO},
+ usegc=UseGC,
paint=#paint{font = Font,
small = SmallFont,
pen = ?wxGREY_PEN,
@@ -112,7 +127,7 @@ handle_event(Event, _State) ->
%%%%%%%%%%
handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
#state{active=Active, offset=Offset, paint=Paint,
- windows=Windows, data=Data}) ->
+ windows=Windows, data=Data, usegc=UseGC}) ->
%% PaintDC must be created in a callback to work on windows.
%% Sigh workaround bug on MacOSX (Id in paint event is always 0)
%% Panel = element(Id, Windows),
@@ -121,14 +136,17 @@ handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
Panel =:= element(?IO_W, Windows) -> ?IO_W
end,
DC = wxPaintDC:new(Panel),
- GC = ?wxGC:create(DC),
+ GC = case UseGC of
+ true -> ?wxGC:create(DC);
+ false -> DC
+ end,
%% Nothing is drawn until wxPaintDC is destroyed.
try
- draw(Offset, Id, GC, Panel, Paint, Data, Active)
+ draw(Offset, Id, {UseGC, GC}, Panel, Paint, Data, Active)
catch _:Err ->
io:format("Internal error ~p ~p~n",[Err, erlang:get_stacktrace()])
end,
- ?wxGC:destroy(GC),
+ UseGC andalso ?wxGC:destroy(GC),
wxPaintDC:destroy(DC),
ok.
%%%%%%%%%%
@@ -269,8 +287,8 @@ draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active)
_ ->
Draw = fun(N) ->
Lines = make_lines(Hs, Start, N, {X0,Max*HS,Last}, Y0, WS, HS),
- ?wxGC:setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)),
- ?wxGC:strokeLines(DC, Lines),
+ setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)),
+ strokeLines(DC, Lines),
N+1
end,
[Draw(I) || I <- lists:seq(NoGraphs, 1, -1)]
@@ -278,8 +296,8 @@ draw(Offset, Id, DC, Panel, Paint=#paint{pens=Pens, small=Small}, Data, Active)
case Active of
false ->
NotActive = "Service not available",
- ?wxGC:setFont(DC, Small, {0,0,0}),
- ?wxGC:drawText(DC, NotActive, X0 + 100, element(2,Size) div 2);
+ setFont(DC, Small, {0,0,0}),
+ drawText(DC, NotActive, X0 + 100, element(2,Size) div 2);
true ->
ignore
end,
@@ -363,9 +381,9 @@ draw_borders(Type, NoGraphs, DC, {W,H}, Max,
Str2 = observer_lib:to_str(MaxUnit div 2),
Str3 = observer_lib:to_str(0),
- ?wxGC:setFont(DC, Font, {0,0,0}),
- {TW,TH,_,_} = ?wxGC:getTextExtent(DC, Str1),
- {SpaceW, _,_,_} = ?wxGC:getTextExtent(DC, "W"),
+ setFont(DC, Font, {0,0,0}),
+ {TW,TH} = getTextExtent(DC, Str1),
+ {SpaceW, _} = getTextExtent(DC, "W"),
GraphX0 = ?BW+TW+?BW,
GraphX1 = W-?BW*4,
@@ -383,50 +401,50 @@ draw_borders(Type, NoGraphs, DC, {W,H}, Max,
ScaleW = GraphW / 60,
ScaleH = GraphH / Max,
- ?wxGC:setFont(DC, Small, {0,0,0}),
+ setFont(DC, Small, {0,0,0}),
Align = fun(Str, Y) ->
- {StrW, _, _, _} = ?wxGC:getTextExtent(DC, Str),
- ?wxGC:drawText(DC, Str, GraphX0 - StrW - ?BW, Y)
+ {StrW, _} = getTextExtent(DC, Str),
+ drawText(DC, Str, GraphX0 - StrW - ?BW, Y)
end,
Align(Str1, MaxTextY),
Align(Str2, GraphY50 - (TH / 2)),
Align(Str3, GraphY1 - (TH / 2) + 1),
- ?wxGC:setPen(DC, Pen),
+ setPen(DC, Pen),
DrawSecs = fun(Secs, Pos) ->
Str = [observer_lib:to_str(Secs)|" s"],
X = GraphX0+Pos,
- ?wxGC:drawText(DC, Str, X-SpaceW, SecondsY),
- ?wxGC:strokeLine(DC, X, GraphY0, X, GraphY1+5),
+ drawText(DC, Str, X-SpaceW, SecondsY),
+ strokeLine(DC, X, GraphY0, X, GraphY1+5),
Pos + 10*ScaleW
end,
lists:foldl(DrawSecs, 0, lists:seq(60,0, -10)),
- ?wxGC:strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25),
- ?wxGC:strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50),
- ?wxGC:strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75),
+ strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25),
+ strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50),
+ strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75),
- ?wxGC:setPen(DC, Pen2),
- ?wxGC:strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1},
- {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1},
- {GraphX0, GraphY0-1}]),
+ setPen(DC, Pen2),
+ strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1},
+ {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1},
+ {GraphX0, GraphY0-1}]),
- ?wxGC:setFont(DC, Font, {0,0,0}),
+ setFont(DC, Font, {0,0,0}),
case Type of
- ?RQ_W -> ?wxGC:drawText(DC, "Scheduler Utilization (%) ", TopTextX,?BH);
- ?MEM_W -> ?wxGC:drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH);
- ?IO_W -> ?wxGC:drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH)
+ ?RQ_W -> drawText(DC, "Scheduler Utilization (%) ", TopTextX,?BH);
+ ?MEM_W -> drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH);
+ ?IO_W -> drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH)
end,
Text = fun(X,Y, Str, PenId) ->
if PenId == 0 ->
- ?wxGC:setFont(DC, Font, {0,0,0});
+ setFont(DC, Font, {0,0,0});
PenId > 0 ->
Id = 1 + ((PenId-1) rem tuple_size(colors())),
- ?wxGC:setFont(DC, Font, element(Id, colors()))
+ setFont(DC, Font, element(Id, colors()))
end,
- ?wxGC:drawText(DC, Str, X, Y),
- {StrW, _, _, _} = ?wxGC:getTextExtent(DC, Str),
+ drawText(DC, Str, X, Y),
+ {StrW, _} = getTextExtent(DC, Str),
StrW + X + SpaceW
end,
case Type of
@@ -483,3 +501,56 @@ colors() ->
{240, 200, 80}, {140, 2, 140},
{100, 200, 240}, {100, 240, 100}
}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% wxDC and ?wxGC wrappers
+
+haveGC(Win) ->
+ try
+ GC = ?wxGC:create(Win),
+ ?wxGC:destroy(GC),
+ true
+ catch _:_ -> false
+ end.
+
+setPen({false, DC}, Pen) ->
+ wxDC:setPen(DC, Pen);
+setPen({true, GC}, Pen) ->
+ ?wxGC:setPen(GC, Pen).
+
+setFont({false, DC}, Font, Color) ->
+ wxDC:setTextForeground(DC, Color),
+ wxDC:setFont(DC, Font);
+setFont({true, GC}, Font, Color) ->
+ ?wxGC:setFont(GC, Font, Color).
+
+setBrush({false, DC}, Brush) ->
+ wxDC:setBrush(DC, Brush);
+setBrush({true, GC}, Brush) ->
+ ?wxGC:setBrush(GC, Brush).
+
+strokeLine({false, DC}, X0, Y0, X1, Y1) ->
+ wxDC:drawLine(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)});
+strokeLine({true, GC}, X0, Y0, X1, Y1) ->
+ ?wxGC:strokeLine(GC, X0, Y0, X1, Y1).
+
+strokeLines({false, DC}, Lines) ->
+ wxDC:drawLines(DC, [{round(X), round(Y)} || {X,Y} <- Lines]);
+strokeLines({true, GC}, Lines) ->
+ ?wxGC:strokeLines(GC, Lines).
+
+drawRoundedRectangle({false, DC}, X0, Y0, X1, Y1, R) ->
+ wxDC:drawRoundedRectangle(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}, round(R));
+drawRoundedRectangle({true, GC}, X0, Y0, X1, Y1, R) ->
+ ?wxGC:drawRoundedRectangle(GC, X0, Y0, X1, Y1, R).
+
+drawText({false, DC}, Str, X, Y) ->
+ wxDC:drawText(DC, Str, {round(X),round(Y)});
+drawText({true, GC}, Str, X, Y) ->
+ ?wxGC:drawText(GC, Str, X, Y).
+
+getTextExtent({false, DC}, Str) ->
+ wxDC:getTextExtent(DC, Str);
+getTextExtent({true, GC}, Str) ->
+ {W,H,_,_} = ?wxGC:getTextExtent(GC, Str),
+ {W,H}.