//------------------------------------------------------------------------------
// emPanel.cpp
//
// Copyright (C) 2004-2008,2011,2014-2017 Oliver Hamann.
//
// Homepage: http://eaglemode.sourceforge.net/
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License version 3 as published by the
// Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
// more details.
//
// You should have received a copy of the GNU General Public License version 3
// along with this program. If not, see .
//------------------------------------------------------------------------------
#include
#include
emPanel::emPanel(ParentArg parent, const emString & name)
: emEngine(parent.GetView().GetScheduler()),
View(parent.GetView()),
Name(name)
{
# define EM_PANEL_DEFAULT_AE_THRESHOLD 100.0 //???
if (parent.GetPanel()) {
AvlTree=NULL;
Parent=parent.GetPanel();
FirstChild=NULL;
LastChild=NULL;
Prev=Parent->LastChild;
Next=NULL;
if (Prev) Prev->Next=this; else Parent->FirstChild=this;
Parent->LastChild=this;
NoticeNode.Prev=NULL;
NoticeNode.Next=NULL;
LayoutX=-2;
LayoutY=-2;
LayoutWidth=1;
LayoutHeight=1;
ViewedX=-1;
ViewedY=-1;
ViewedWidth=1;
ViewedHeight=1;
ClipX1=0;
ClipY1=0;
ClipX2=0;
ClipY2=0;
AEThresholdValue=EM_PANEL_DEFAULT_AE_THRESHOLD;
CanvasColor=0;
PendingNoticeFlags=0;
Viewed=0;
InViewedPath=0;
EnableSwitch=1;
Enabled=Parent->Enabled;
Focusable=1;
Active=0;
InActivePath=0;
PendingInput=0;
ChildrenLayoutInvalid=0;
AEInvalid=0;
AEDecisionInvalid=0;
AECalling=0;
AEExpanded=0;
CreatedByAE=Parent->AECalling;
AEThresholdType=VCT_AREA;
AutoplayHandling=APH_ITEM;
Parent->AvlInsertChild(this);
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
AddPendingNotice(
NF_CHILD_LIST_CHANGED |
NF_LAYOUT_CHANGED |
NF_VIEWING_CHANGED |
NF_ENABLE_CHANGED |
NF_ACTIVE_CHANGED |
NF_FOCUS_CHANGED |
NF_VIEW_FOCUS_CHANGED |
NF_UPDATE_PRIORITY_CHANGED |
NF_MEMORY_LIMIT_CHANGED |
NF_SOUGHT_NAME_CHANGED
);
}
else {
if (View.RootPanel) {
emFatalError(
"Root panel created for an emView which has already a root panel."
);
}
View.RootPanel=this;
View.SupremeViewedPanel=this;
View.MinSVP=this;
View.MaxSVP=this;
View.ActivePanel=this;
AvlTree=NULL;
Parent=NULL;
FirstChild=NULL;
LastChild=NULL;
Prev=NULL;
Next=NULL;
NoticeNode.Prev=NULL;
NoticeNode.Next=NULL;
LayoutX=0;
LayoutY=0;
LayoutWidth=1.0;
LayoutHeight=View.GetHomeTallness();
ViewedX=View.CurrentX;
ViewedY=View.CurrentY;
ViewedWidth=View.CurrentWidth;
ViewedHeight=View.CurrentHeight;
ClipX1=ViewedX;
ClipY1=ViewedY;
ClipX2=ViewedX+ViewedWidth;
ClipY2=ViewedY+ViewedHeight;
AEThresholdValue=EM_PANEL_DEFAULT_AE_THRESHOLD;
CanvasColor=0;
PendingNoticeFlags=0;
Viewed=1;
InViewedPath=1;
EnableSwitch=1;
Enabled=1;
Focusable=1;
Active=1;
InActivePath=1;
PendingInput=0;
ChildrenLayoutInvalid=0;
AEInvalid=0;
AEDecisionInvalid=0;
AECalling=0;
AEExpanded=0;
CreatedByAE=0;
AEThresholdType=VCT_AREA;
AutoplayHandling=APH_ITEM;
InvalidatePainting();
AddPendingNotice(
NF_CHILD_LIST_CHANGED |
NF_LAYOUT_CHANGED |
NF_VIEWING_CHANGED |
NF_ENABLE_CHANGED |
NF_ACTIVE_CHANGED |
NF_FOCUS_CHANGED |
NF_VIEW_FOCUS_CHANGED |
NF_UPDATE_PRIORITY_CHANGED |
NF_MEMORY_LIMIT_CHANGED |
NF_SOUGHT_NAME_CHANGED
);
View.TitleInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
emPanel::~emPanel()
{
InvalidatePainting();
if (View.SeekPosPanel==this) View.SetSeekPos(NULL,NULL);
DeleteAllChildren();
if (!Parent) {
if (View.IsPoppedUp()) View.RawZoomOut();
View.RootPanel=NULL;
View.SupremeViewedPanel=NULL;
View.MinSVP=NULL;
View.MaxSVP=NULL;
View.ActivePanel=NULL;
View.ActivationAdherent=false;
View.TitleInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
else {
if (InActivePath || View.SupremeViewedPanel==this) {
SetFocusable(false);
if (View.SupremeViewedPanel==this) {
LayoutX=-2.0;
LayoutY=-2.0;
LayoutWidth=1.0;
LayoutHeight=1.0;
CanvasColor=0;
if ((View.GetViewFlags()&emView::VF_POPUP_ZOOM)!=0 && !View.IsPoppedUp()) {
View.RawZoomOut();
}
else {
View.RawVisitFullsized(Parent);
}
}
if (InActivePath || View.SupremeViewedPanel==this) {
emFatalError("emPanel::~emPanel: Could not to get rid of activation or SVP status.");
}
}
if (View.MinSVP==this) {
View.MinSVP=Parent;
}
View.RestartInputRecursion=true;
if (InViewedPath) {
View.SVPChoiceInvalid=true;
View.TitleInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
Parent->AvlRemoveChild(this);
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
if (Next) Next->Prev=Prev;
else Parent->LastChild=Prev;
if (Prev) Prev->Next=Next;
else Parent->FirstChild=Next;
Next=NULL;
Prev=NULL;
}
if (NoticeNode.Next) {
NoticeNode.Next->Prev=NoticeNode.Prev;
NoticeNode.Prev->Next=NoticeNode.Next;
NoticeNode.Next=NULL;
NoticeNode.Prev=NULL;
}
}
emString emPanel::GetIdentity() const
{
const emPanel * p;
emArray a;
int i;
p=this;
i=0;
do {
i++;
p=p->Parent;
} while (p);
a.SetTuningLevel(1);
a.SetCount(i);
p=this;
do {
i--;
a.Set(i,p->Name);
p=p->Parent;
} while (p);
return EncodeIdentity(a);
}
emString emPanel::EncodeIdentity(const emArray & names)
{
emString res;
const char * r, * s;
char * t;
int i,cnt,len;
char c;
cnt=names.GetCount();
len=cnt-1;
for (i=0; i0) *t++=':';
s=names[i].Get();
c=*s;
while (c) {
if (c==':' || c=='\\') *t++='\\';
*t++=c;
c=*++s;
}
}
return res;
}
emArray emPanel::DecodeIdentity(const char * identity)
{
emArray a;
const char * p, * t;
char * s;
int i,q;
a.SetTuningLevel(1);
p=identity;
for (i=0; ; i++, p++) {
a.SetCount(i+1);
if (!*p) break;
if (*p==':') continue;
t=p;
q=0;
do {
if (*t=='\\') {
t++;
q++;
if (!*t) break;
}
t++;
} while (*t && *t!=':');
s=a.GetWritable(i).SetLenGetWritable(t-p-q);
do {
if (*p=='\\') {
p++;
if (!*p) break;
}
*s++=*p++;
} while (*p && *p!=':');
if (!*p) break;
}
return a;
}
emString emPanel::GetTitle() const
{
if (Parent) return Parent->GetTitle();
else return "untitled";
}
emString emPanel::GetIconFileName() const
{
if (Parent) return Parent->GetIconFileName();
else return emString();
}
emPanel * emPanel::GetChild(const char * name) const
{
EM_AVL_SEARCH_VARS(emPanel)
int d;
EM_AVL_SEARCH_BEGIN(emPanel,AvlNode,AvlTree)
d=strcmp(name,element->Name.Get());
if (d<0) EM_AVL_SEARCH_GO_LEFT
else if (d>0) EM_AVL_SEARCH_GO_RIGHT
EM_AVL_SEARCH_END
return element;
}
void emPanel::BeFirst()
{
if (Prev) {
Prev->Next=Next;
if (Next) Next->Prev=Prev;
else Parent->LastChild=Prev;
Prev=NULL;
Next=Parent->FirstChild;
Next->Prev=this;
Parent->FirstChild=this;
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
View.RestartInputRecursion=true;
if (InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
}
void emPanel::BeLast()
{
if (Next) {
Next->Prev=Prev;
if (Prev) Prev->Next=Next;
else Parent->FirstChild=Next;
Next=NULL;
Prev=Parent->LastChild;
Prev->Next=this;
Parent->LastChild=this;
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
View.RestartInputRecursion=true;
if (Parent->InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
}
void emPanel::BePrevOf(emPanel * sister)
{
if (!sister) {
BeLast();
return;
}
if (sister==this || sister==Next || sister->Parent!=Parent) return;
if (Prev) Prev->Next=Next;
else Parent->FirstChild=Next;
if (Next) Next->Prev=Prev;
else Parent->LastChild=Prev;
Next=sister;
Prev=sister->Prev;
sister->Prev=this;
if (Prev) Prev->Next=this; else Parent->FirstChild=this;
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
View.RestartInputRecursion=true;
if (Parent->InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
void emPanel::BeNextOf(emPanel * sister)
{
if (!sister) {
BeFirst();
return;
}
if (sister==this || sister==Prev || sister->Parent!=Parent) return;
if (Next) Next->Prev=Prev;
else Parent->LastChild=Prev;
if (Prev) Prev->Next=Next;
else Parent->FirstChild=Next;
Prev=sister;
Next=sister->Next;
sister->Next=this;
if (Next) Next->Prev=this; else Parent->LastChild=this;
Parent->AddPendingNotice(NF_CHILD_LIST_CHANGED);
View.RestartInputRecursion=true;
if (Parent->InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
void emPanel::SortChildren(
int(*compare)(emPanel * c1, emPanel * c2, void * context),
void * context
)
{
if (
emSortDoubleLinkedList(
(void**)(void*)&FirstChild,
(void**)(void*)&LastChild,
offsetof(emPanel,Next),
offsetof(emPanel,Prev),
(int(*)(void*,void*,void*))compare,
context
)
) {
AddPendingNotice(NF_CHILD_LIST_CHANGED);
View.RestartInputRecursion=true;
if (InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
}
void emPanel::DeleteAllChildren()
{
while (LastChild) delete LastChild;
}
void emPanel::Layout(
double layoutX, double layoutY, double layoutWidth, double layoutHeight,
emColor canvasColor
)
{
emPanel * p;
double x1,y1,x2,y2,rx,ry,ra;
bool zoomedOut;
if (LayoutWidth<1E-100) LayoutWidth=1E-100;
if (LayoutHeight<1E-100) LayoutHeight=1E-100;
if (!Parent) {
layoutX=0.0;
layoutY=0.0;
if ((View.VFlags&emView::VF_ROOT_SAME_TALLNESS)!=0) {
layoutHeight=View.GetHomeTallness();
}
else {
layoutHeight/=layoutWidth;
}
layoutWidth=1.0;
}
if (
LayoutX==layoutX && LayoutY==layoutY &&
LayoutWidth==layoutWidth && LayoutHeight==layoutHeight
) {
if (CanvasColor!=canvasColor) {
CanvasColor=canvasColor;
AddPendingNotice(NF_LAYOUT_CHANGED);
InvalidatePainting();
}
return;
}
AddPendingNotice(NF_LAYOUT_CHANGED);
View.RestartInputRecursion=true;
if (!Parent || Parent->InViewedPath) {
InvalidatePainting();
View.SVPChoiceInvalid=true;
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
if (!Parent) {
zoomedOut=View.IsZoomedOut();
p=View.GetVisitedPanel(&rx,&ry,&ra);
LayoutX=layoutX;
LayoutY=layoutY;
LayoutWidth=layoutWidth;
LayoutHeight=layoutHeight;
CanvasColor=canvasColor;
if (!View.SettingGeometry) {
if (zoomedOut) {
View.RawZoomOut(true);
}
else if (p) {
View.RawVisit(p,rx,ry,ra,true);
}
}
}
else if (
InViewedPath &&
(InActivePath || !Parent->Viewed) &&
!View.SettingGeometry &&
!View.IsZoomedOut()
) {
p=View.GetVisitedPanel(&rx,&ry,&ra);
LayoutX=layoutX;
LayoutY=layoutY;
LayoutWidth=layoutWidth;
LayoutHeight=layoutHeight;
CanvasColor=canvasColor;
View.RawVisit(p,rx,ry,ra,true);
}
else if (Parent->Viewed) {
LayoutX=layoutX;
LayoutY=layoutY;
LayoutWidth=layoutWidth;
LayoutHeight=layoutHeight;
CanvasColor=canvasColor;
x1=Parent->ViewedX+LayoutX*Parent->ViewedWidth;
x2=LayoutWidth*Parent->ViewedWidth;
y1=Parent->ViewedY+LayoutY*(Parent->ViewedWidth/View.CurrentPixelTallness);
y2=LayoutHeight*(Parent->ViewedWidth/View.CurrentPixelTallness);
ViewedX=x1;
ViewedY=y1;
ViewedWidth=x2;
ViewedHeight=y2;
x2+=x1;
y2+=y1;
if (x1ClipX1) x1=Parent->ClipX1;
if (x2>Parent->ClipX2) x2=Parent->ClipX2;
if (y1ClipY1) y1=Parent->ClipY1;
if (y2>Parent->ClipY2) y2=Parent->ClipY2;
ClipX1=x1;
ClipX2=x2;
ClipY1=y1;
ClipY2=y2;
if (x1=1.0 || y<0.0 || y>=GetHeight()) return false;
GetSubstanceRect(&sx,&sy,&sw,&sh,&sr);
sw2=sw*0.5;
dx=fabs(x-sx-sw2);
if (dx>sw2) return false;
sh2=sh*0.5;
dy=fabs(y-sy-sh2);
if (dy>sh2) return false;
if (sr>sw2) sr=sw2;
if (sr>sh2) sr=sh2;
dx-=sw2-sr;
dy-=sh2-sr;
return dx<0.0 || dy<0.0 || dx*dx+dy*dy <= sr*sr;
}
void emPanel::GetEssenceRect(
double * pX, double * pY, double * pW, double * pH
) const
{
double r;
GetSubstanceRect(pX,pY,pW,pH,&r);
}
double emPanel::GetViewCondition(ViewConditionType vcType) const
{
if (Viewed) {
switch (vcType) {
case VCT_AREA : return ViewedWidth*ViewedHeight;
case VCT_WIDTH : return ViewedWidth;
case VCT_HEIGHT : return ViewedHeight;
case VCT_MIN_EXT: return emMin(ViewedWidth, ViewedHeight);
case VCT_MAX_EXT: return emMax(ViewedWidth, ViewedHeight);
};
}
else if (InViewedPath) {
return 1E100;
}
return 0.0;
}
void emPanel::SetAutoExpansionThreshold(
double thresholdValue, ViewConditionType vcType
)
{
if (
AEThresholdValue!=thresholdValue ||
((ViewConditionType)AEThresholdType)!=vcType
) {
AEThresholdValue=thresholdValue;
AEThresholdType=vcType;
AEDecisionInvalid=1;
if (!NoticeNode.Next) View.AddToNoticeList(&NoticeNode);
}
}
void emPanel::SetEnableSwitch(bool enableSwitch)
{
emPanel * p;
if (enableSwitch) {
if (!EnableSwitch) {
EnableSwitch=1;
if (!Parent || Parent->Enabled) {
p=this;
for (;;) {
if (p->EnableSwitch) {
p->Enabled=1;
p->AddPendingNotice(NF_ENABLE_CHANGED);
if (p->FirstChild) {
p=p->FirstChild;
continue;
}
}
while (p!=this && !p->Next) {
p=p->Parent;
}
if (p==this) break;
p=p->Next;
}
}
}
}
else {
if (EnableSwitch) {
EnableSwitch=0;
p=this;
for (;;) {
if (p->Enabled) {
p->Enabled=0;
p->AddPendingNotice(NF_ENABLE_CHANGED);
if (p->FirstChild) {
p=p->FirstChild;
continue;
}
}
while (p!=this && !p->Next) {
p=p->Parent;
}
if (p==this) break;
p=p->Next;
}
}
}
}
void emPanel::SetFocusable(bool focusable)
{
if (((bool)Focusable)!=focusable) {
if (!Parent && !focusable) {
emDLog("emPanel::SetFocusable: a root panel cannot be set unfocusable");
return;
}
Focusable=focusable;
if (!Focusable && Active) {
View.SetActivePanel(Parent,false);
}
}
}
emPanel * emPanel::GetFocusableParent() const
{
const emPanel * p;
p=this;
do {
p=p->Parent;
} while (p && !p->Focusable);
return (emPanel*)p;
}
emPanel * emPanel::GetFocusableFirstChild() const
{
const emPanel * p;
p=FirstChild;
if (!p) return NULL;
for (;;) {
if (p->Focusable) return (emPanel*)p;
if (p->FirstChild) p=p->FirstChild;
else {
while (!p->Next) {
p=p->Parent;
if (p==this) return NULL;
}
p=p->Next;
}
}
}
emPanel * emPanel::GetFocusableLastChild() const
{
const emPanel * p;
p=LastChild;
if (!p) return NULL;
for (;;) {
if (p->Focusable) return (emPanel*)p;
if (p->LastChild) p=p->LastChild;
else {
while (!p->Prev) {
p=p->Parent;
if (p==this) return NULL;
}
p=p->Prev;
}
}
}
emPanel * emPanel::GetFocusablePrev() const
{
const emPanel * p;
for (p=this;;) {
while (!p->Prev) {
p=p->Parent;
if (!p || p->Focusable) return NULL;
}
p=p->Prev;
for (;;) {
if (p->Focusable) return (emPanel*)p;
if (!p->LastChild) break;
p=p->LastChild;
}
}
}
emPanel * emPanel::GetFocusableNext() const
{
const emPanel * p;
for (p=this;;) {
while (!p->Next) {
p=p->Parent;
if (!p || p->Focusable) return NULL;
}
p=p->Next;
for (;;) {
if (p->Focusable) return (emPanel*)p;
if (!p->FirstChild) break;
p=p->FirstChild;
}
}
}
void emPanel::Activate(bool adherent)
{
View.SetActivePanel(this,adherent);
}
void emPanel::Focus(bool adherent)
{
View.Focus();
View.SetActivePanel(this,adherent);
}
double emPanel::GetUpdatePriority() const
{
double x1,y1,x2,y2,vx,vy,vw,vh,k,pri;
if (Viewed) {
vx=View.GetCurrentX();
vw=View.GetCurrentWidth();
vy=View.GetCurrentY();
vh=View.GetCurrentHeight();
x1=(ClipX1-vx)/vw-0.5;
x2=(ClipX2-vx)/vw-0.5;
y1=(ClipY1-vy)/vh-0.5;
y2=(ClipY2-vy)/vh-0.5;
if (x1MaxMegabytesPerView*1000000.0;
if (!Viewed || View.SeekPosPanel==this) return (emUInt64)maxPerView;
return (emUInt64)(
(ClipX2-ClipX1)*(ClipY2-ClipY1)*maxPerView/
(View.GetCurrentWidth()*View.GetCurrentHeight())
);
#elif MEMORY_LIMIT_VARIANT==2
// This trivial one did it for years. It assumes that memory intensive
// panels are making up no more than about 36% of the areas (like with
// emDirEntryPanel), and that the view size is about 1280*1024 pixels.
double maxPerView,maxPerPanel,maxPerPixel,m;
if (!InViewedPath) return 0;
maxPerView=View.CoreConfig->MaxMegabytesPerView*1000000.0;
maxPerPanel=maxPerView/600.0*75.0;
maxPerPixel=maxPerView/600000000.0*500.0;
if (!Viewed || View.SeekPosPanel==this) return (emUInt64)maxPerPanel;
m=ViewedWidth*ViewedHeight*maxPerPixel;
if (m>maxPerPanel) m=maxPerPanel;
return (emUInt64)m;
#elif MEMORY_LIMIT_VARIANT==3
// This algorithm solves a good balance between reducing flicker and not
// wasting too much memory for invisible things. But it assumes that
// memory intensive panels are never overlapping each other. And it does
// not take advantage of unused space between memory intensive panels.
// And the limit is still for the sub-tree, not for the node itself.
double maxPerViewByUser,maxPerView,maxPerPanel;
double viewExtension,viewExtensionValence;
double vx,vy,vw,vh,evx1,evy1,evx2,evy2,ecx1,ecy1,ecx2,ecy2,fe,fn,f;
if (!InViewedPath) return 0;
maxPerViewByUser=View.CoreConfig->MaxMegabytesPerView*1000000.0;
maxPerView =maxPerViewByUser*2.0;
maxPerPanel=maxPerViewByUser*0.33;
// Explanation of the above two lines: When there are many small
// panels, it is unlikely that every panel would be at the
// limit, and the average memory usage is assumed to be small.
// But with just one big panel, the limit can be easily reached.
// On the other hand, it's the case of many panels for which a
// user normally wants to configure a higher limit.
if (!Viewed || View.SeekPosPanel==this) return (emUInt64)maxPerPanel;
viewExtension=0.5;
viewExtensionValence=0.5;
vx=View.GetCurrentX();
vy=View.GetCurrentY();
vw=View.GetCurrentWidth();
vh=View.GetCurrentHeight();
evx1=vx-vw*(viewExtension*0.5);
evy1=vy-vh*(viewExtension*0.5);
evx2=evx1+vw*(1.0+viewExtension);
evy2=evy1+vh*(1.0+viewExtension);
ecx1=ViewedX;
ecy1=ViewedY;
ecx2=ecx1+ViewedWidth;
ecy2=ecy1+ViewedHeight;
if (ecx1evx2) ecx2=evx2;
if (ecy2>evy2) ecy2=evy2;
fe=(ecx2-ecx1)*(ecy2-ecy1)/((evx2-evx1)*(evy2-evy1));
fn=(ClipX2-ClipX1)*(ClipY2-ClipY1)/(vw*vh);
f=fe*viewExtensionValence+fn*(1.0-viewExtensionValence);
f*=maxPerView;
if (f>maxPerPanel) f=maxPerPanel;
if (f<0.0) f=0.0;
return (emUInt64)f;
#elif MEMORY_LIMIT_VARIANT==4
// This experimental algorithm distributes the memory limit of a parent
// to its children depending on their visible areas and sizes. It solves
// the problem of overlapped panels and it takes advantage of unused
// space between panels. The result is quite nice, but if the value for
// condensationLimit is not some less than the actual condensation of
// memory intensive panels, we get the flicker problem again. Also,
// before really using this algorithm, it should get a good optimization
// by some caching or an update mechanism, because in its current form
// it is very expensive (see the loop and the recursive call). And in
// addition, the sending of NF_MEMORY_LIMIT_CHANGED would have to be
// reviewed and adapted.
double maxPerView,viewExtension,viewExtensionValence,maxPerPanelFactor;
double condensationLimit,ftotal,fself;
double vx,vy,vw,vh,evx1,evy1,evx2,evy2,ecx1,ecy1,ecx2,ecy2,fe,fn,f;
const emPanel * p;
if (!InViewedPath) return 0;
maxPerView=View.CoreConfig->MaxMegabytesPerView*1000000.0;
if (!Viewed || !Parent || View.SeekPosPanel==this) return (emUInt64)maxPerView;
maxPerPanelFactor=0.75;
viewExtension=0.5;
viewExtensionValence=0.5;
condensationLimit=2.0; // 2.0 is good for emDirEntryPanel
ftotal=0.0;
fself=0.0;
for (p=Parent->FirstChild; p; p=p->Next) {
if (!p->Viewed) continue;
vx=View.GetCurrentX();
vy=View.GetCurrentY();
vw=View.GetCurrentWidth();
vh=View.GetCurrentHeight();
evx1=vx-vw*(viewExtension*0.5);
evy1=vy-vh*(viewExtension*0.5);
evx2=evx1+vw*(1.0+viewExtension);
evy2=evy1+vh*(1.0+viewExtension);
ecx1=p->ViewedX;
ecy1=p->ViewedY;
ecx2=ecx1+p->ViewedWidth;
ecy2=ecy1+p->ViewedHeight;
if (ecx1evx2) ecx2=evx2;
if (ecy2>evy2) ecy2=evy2;
fe=(ecx2-ecx1)*(ecy2-ecy1)/((evx2-evx1)*(evy2-evy1));
fn=(p->ClipX2-p->ClipX1)*(p->ClipY2-p->ClipY1)/(vw*vh);
f=fe*viewExtensionValence+fn*(1.0-viewExtensionValence);
if (f>maxPerPanelFactor) f=maxPerPanelFactor;
if (f<0.0) f=0.0;
ftotal+=f;
if (p==this) fself=f;
}
f=Parent->GetMemoryLimit()*fself/ftotal/maxPerView;
if (f>fself*condensationLimit) f=fself*condensationLimit;
if (f>maxPerPanelFactor) f=maxPerPanelFactor;
if (f<0.0) f=0.0;
return (emUInt64)(maxPerView*f);
#endif
}
double emPanel::GetTouchEventPriority(double touchX, double touchY) const
{
return Focusable ? 1.0 : 0.0;
}
void emPanel::SetAutoplayHandling(AutoplayHandlingFlags flags)
{
AutoplayHandling=flags;
}
bool emPanel::IsContentReady(bool * pReadying) const
{
if (pReadying) *pReadying=false;
return IsAutoExpanded();
}
bool emPanel::GetPlaybackState(bool * pPlaying, double * pPos) const
{
if (pPlaying) *pPlaying=false;
if (pPos) *pPos=0.0;
return false;
}
bool emPanel::SetPlaybackState(bool playing, double pos)
{
return false;
}
bool emPanel::Cycle()
{
return false;
}
void emPanel::Notice(NoticeFlags flags)
{
}
void emPanel::Input(
emInputEvent & event, const emInputState & state, double mx, double my
)
{
if (Focusable && (event.IsMouseEvent() || event.IsTouchEvent())) {
Focus();
event.Eat();
}
else if (Active && event.IsKeyboardEvent()) {
switch (event.GetKey()) {
case EM_KEY_TAB:
if (state.IsNoMod()) {
View.VisitNext();
event.Eat();
}
else if (state.IsShiftMod()) {
View.VisitPrev();
event.Eat();
}
break;
case EM_KEY_CURSOR_LEFT:
if (state.IsNoMod()) {
View.VisitLeft();
event.Eat();
}
break;
case EM_KEY_CURSOR_RIGHT:
if (state.IsNoMod()) {
View.VisitRight();
event.Eat();
}
break;
case EM_KEY_CURSOR_UP:
if (state.IsNoMod()) {
View.VisitUp();
event.Eat();
}
break;
case EM_KEY_CURSOR_DOWN:
if (state.IsNoMod()) {
View.VisitDown();
event.Eat();
}
break;
case EM_KEY_PAGE_UP:
if (state.IsNoMod()) {
View.VisitIn();
event.Eat();
}
break;
case EM_KEY_PAGE_DOWN:
if (state.IsNoMod()) {
View.VisitOut();
event.Eat();
}
break;
case EM_KEY_HOME:
if (state.IsNoMod()) {
View.VisitFirst();
event.Eat();
}
else if (state.IsAltMod()) {
View.VisitFullsized(this,View.IsActivationAdherent());
event.Eat();
}
else if (state.IsShiftAltMod()) {
View.VisitFullsized(this,View.IsActivationAdherent(),true);
event.Eat();
}
break;
case EM_KEY_END:
if (state.IsNoMod()) {
View.VisitLast();
event.Eat();
}
break;
default:
break;
}
}
}
emCursor emPanel::GetCursor() const
{
if (Parent) return Parent->GetCursor();
else return emCursor::NORMAL;
}
bool emPanel::IsOpaque() const
{
return false;
}
void emPanel::Paint(const emPainter & painter, emColor canvasColor) const
{
}
void emPanel::AutoExpand()
{
}
void emPanel::AutoShrink()
{
emPanel * p, * t;
for (p=LastChild; p;) {
t=p;
p=p->Prev;
if (t->CreatedByAE) delete t;
}
}
void emPanel::LayoutChildren()
{
}
emPanel * emPanel::CreateControlPanel(ParentArg parent, const emString & name)
{
if (Parent) return Parent->CreateControlPanel(parent,name);
else return NULL;
}
const char * emPanel::GetSoughtName() const
{
if (View.SeekPosPanel==this) return View.SeekPosChildName;
else return NULL;
}
bool emPanel::IsHopeForSeeking() const
{
return false;
}
void emPanel::InvalidateTitle()
{
if (InActivePath) {
View.TitleInvalid=true;
View.UpdateEngine->WakeUp();
}
}
void emPanel::InvalidateCursor()
{
if (InViewedPath) {
View.CursorInvalid=true;
View.UpdateEngine->WakeUp();
}
}
void emPanel::InvalidatePainting()
{
if (Viewed) {
if (!View.SVPChoiceByOpacityInvalid) {
View.SVPChoiceByOpacityInvalid=true;
View.UpdateEngine->WakeUp();
}
View.InvalidatePainting(ClipX1,ClipY1,ClipX2-ClipX1,ClipY2-ClipY1);
}
}
void emPanel::InvalidatePainting(double x, double y, double w, double h)
{
if (Viewed) {
if (!View.SVPChoiceByOpacityInvalid) {
View.SVPChoiceByOpacityInvalid=true;
View.UpdateEngine->WakeUp();
}
x=x*ViewedWidth+ViewedX;
w=w*ViewedWidth;
y=y*(ViewedWidth/View.CurrentPixelTallness)+ViewedY;
h=h*(ViewedWidth/View.CurrentPixelTallness);
if (xClipX2-x) w=ClipX2-x;
if (h>ClipY2-y) h=ClipY2-y;
View.InvalidatePainting(x,y,w,h);
}
}
void emPanel::InvalidateAutoExpansion()
{
if (!AEInvalid && AEExpanded) {
AEInvalid=1;
if (!NoticeNode.Next) View.AddToNoticeList(&NoticeNode);
}
}
void emPanel::InvalidateControlPanel()
{
if (InActivePath) Signal(View.ControlPanelSignal);
}
emString emPanel::GetTitle()
{
return ((const emPanel*)this)->GetTitle();
}
emString emPanel::GetIconFileName()
{
return ((const emPanel*)this)->GetIconFileName();
}
void emPanel::GetSubstanceRect(
double * pX, double * pY, double * pW, double * pH, double * pR
)
{
return ((const emPanel*)this)->GetSubstanceRect(pX,pY,pW,pH,pR);
}
void emPanel::GetEssenceRect(
double * pX, double * pY, double * pW, double * pH
)
{
return ((const emPanel*)this)->GetEssenceRect(pX,pY,pW,pH);
}
double emPanel::GetTouchEventPriority(double touchX, double touchY)
{
return ((const emPanel*)this)->GetTouchEventPriority(touchX,touchY);
}
emCursor emPanel::GetCursor()
{
return ((const emPanel*)this)->GetCursor();
}
bool emPanel::IsOpaque()
{
return ((const emPanel*)this)->IsOpaque();
}
void emPanel::Paint(const emPainter & painter, emColor canvasColor)
{
((const emPanel*)this)->Paint(painter,canvasColor);
}
bool emPanel::IsHopeForSeeking()
{
return ((const emPanel*)this)->IsHopeForSeeking();
}
void emPanel::HandleNotice()
{
NoticeFlags flags;
if (AEInvalid) {
AEInvalid=0;
if (AEExpanded) {
AEExpanded=0;
AEDecisionInvalid=1;
AutoShrink();
}
}
flags=PendingNoticeFlags;
if (flags) {
if (flags&(NF_SOUGHT_NAME_CHANGED|NF_VIEWING_CHANGED)) {
if (
View.SeekPosPanel==this ||
GetViewCondition((ViewConditionType)AEThresholdType)>=AEThresholdValue
) {
if (!AEExpanded) AEDecisionInvalid=1;
}
else {
if (AEExpanded) AEDecisionInvalid=1;
}
}
if (flags&(NF_LAYOUT_CHANGED|NF_CHILD_LIST_CHANGED)) {
if (FirstChild) ChildrenLayoutInvalid=1;
}
if (AEDecisionInvalid || ChildrenLayoutInvalid) {
if (!NoticeNode.Next) View.AddToNoticeList(&NoticeNode);
}
PendingNoticeFlags=0;
Notice(flags);
return; // Because Notice() is allowed to do a "delete this".
}
if (AEDecisionInvalid) {
AEDecisionInvalid=0;
if (
View.SeekPosPanel==this ||
GetViewCondition((ViewConditionType)AEThresholdType)>=AEThresholdValue
) {
if (!AEExpanded) {
AEExpanded=1;
AECalling=1;
AutoExpand();
AECalling=0;
if (PendingNoticeFlags) return;
}
}
else {
if (AEExpanded) {
AEExpanded=0;
AutoShrink();
if (PendingNoticeFlags) return;
}
}
}
if (ChildrenLayoutInvalid) {
if (FirstChild) LayoutChildren();
ChildrenLayoutInvalid=0;
}
}
void emPanel::UpdateChildrenViewing()
{
emPanel * p;
double x1,y1,x2,y2;
if (!Viewed) {
if (InViewedPath) {
emFatalError("Illegal use of emPanel::UpdateChildrenViewing.");
}
for (p=FirstChild; p; p=p->Next) {
if (p->InViewedPath) {
p->Viewed=0;
p->InViewedPath=0;
p->AddPendingNotice(
NF_VIEWING_CHANGED |
NF_UPDATE_PRIORITY_CHANGED |
NF_MEMORY_LIMIT_CHANGED
);
if (p->FirstChild) p->UpdateChildrenViewing();
}
}
}
else {
for (p=FirstChild; p; p=p->Next) {
x1=ViewedX+p->LayoutX*ViewedWidth;
x2=p->LayoutWidth*ViewedWidth;
y1=ViewedY+p->LayoutY*(ViewedWidth/View.CurrentPixelTallness);
y2=p->LayoutHeight*(ViewedWidth/View.CurrentPixelTallness);
p->ViewedX=x1;
p->ViewedY=y1;
p->ViewedWidth=x2;
p->ViewedHeight=y2;
x2+=x1;
y2+=y1;
if (x1ClipX2) x2=ClipX2;
if (y1ClipY2) y2=ClipY2;
p->ClipX1=x1;
p->ClipX2=x2;
p->ClipY1=y1;
p->ClipY2=y2;
if (x1InViewedPath=1;
p->Viewed=1;
p->AddPendingNotice(
NF_VIEWING_CHANGED |
NF_UPDATE_PRIORITY_CHANGED |
NF_MEMORY_LIMIT_CHANGED
);
if (p->FirstChild) p->UpdateChildrenViewing();
}
else if (p->InViewedPath) {
p->InViewedPath=0;
p->Viewed=0;
p->AddPendingNotice(
NF_VIEWING_CHANGED |
NF_UPDATE_PRIORITY_CHANGED |
NF_MEMORY_LIMIT_CHANGED
);
if (p->FirstChild) p->UpdateChildrenViewing();
}
}
}
}
void emPanel::AvlInsertChild(emPanel * child)
{
EM_AVL_INSERT_VARS(emPanel)
int d;
EM_AVL_INSERT_BEGIN_SEARCH(emPanel,AvlNode,AvlTree)
d=strcmp(child->Name.Get(),element->Name.Get());
if (d<0) EM_AVL_INSERT_GO_LEFT
else if (d>0) EM_AVL_INSERT_GO_RIGHT
else {
emFatalError(
"emPanel: Panel name \"%s\" not unique within \"%s\".",
child->Name.Get(),
GetIdentity().Get()
);
}
EM_AVL_INSERT_END_SEARCH
element=child;
EM_AVL_INSERT_NOW(AvlNode)
}
void emPanel::AvlRemoveChild(emPanel * child)
{
EM_AVL_REMOVE_VARS(emPanel)
int d;
EM_AVL_REMOVE_BEGIN(emPanel,AvlNode,AvlTree)
d=strcmp(child->Name.Get(),element->Name.Get());
if (d<0) EM_AVL_REMOVE_GO_LEFT
else if (d>0) EM_AVL_REMOVE_GO_RIGHT
else EM_AVL_REMOVE_NOW
EM_AVL_REMOVE_END
}