diff --git a/src/common/Widgets/ScrollBar.cpp b/src/common/Widgets/ScrollBar.cpp --- a/src/common/Widgets/ScrollBar.cpp +++ b/src/common/Widgets/ScrollBar.cpp @@ -1,744 +1,741 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/LgiRes.h" #define DrawBorder(dc, r, edge) LThinBorder(dc, r, edge) #if defined(LGI_CARBON) #define MAC_SKIN 1 #elif defined(LGI_COCOA) #define MAC_LOOK 1 #else #define WINXP_LOOK 1 #endif enum ScrollZone { BTN_NONE, BTN_SUB, BTN_SLIDE, BTN_ADD, BTN_PAGE_SUB, BTN_PAGE_ADD, }; class LScrollBarPrivate { public: LScrollBar *Widget; bool Vertical; int64 Value, Min, Max, Page; LRect Sub, Add, Slide, PageSub, PageAdd; int Clicked; bool Over; int SlideOffset; int Ignore; LScrollBarPrivate(LScrollBar *w) { Ignore = 0; Widget = w; Vertical = true; Value = Min = 0; Max = -1; Page = 1; Clicked = BTN_NONE; Over = false; } bool IsVertical() { return Vertical; } int IsOver() { return Over ? Clicked : BTN_NONE; } void DrawIcon(LSurface *pDC, LRect &r, bool Add, LSystemColour c) { pDC->Colour(c); int IconSize = MAX(r.X(), r.Y()) * 2 / 6; int Cx = r.x1 + (r.X() >> 1); int Cy = r.y1 + (r.Y() >> 1); int Off = (IconSize >> 1) * (Add ? 1 : -1); int x = Cx + (IsVertical() ? 0 : Off); int y = Cy + (IsVertical() ? Off : 0); if (Add) { if (IsOver() == BTN_ADD) { x++; y++; } if (IsVertical()) { // down for (int i=0; iLine(x-i, y, x+i, y); } } else { // right for (int i=0; iLine(x, y-i, x, y+i); } } } else { if (IsOver() == BTN_SUB) { x++; y++; } if (IsVertical()) { // up for (int i=0; iLine(x-i, y, x+i, y); } } else { // left for (int i=0; iLine(x, y-i, x, y+i); } } } } void OnPaint(LSurface *pDC) { LColour SlideCol(L_MED); SlideCol.Rgb( (255 + SlideCol.r()) >> 1, (255 + SlideCol.g()) >> 1, (255 + SlideCol.b()) >> 1); #if MAC_LOOK || MAC_SKIN pDC->Colour(SlideCol); pDC->Rectangle(); if (IsValid()) { LRect r = Slide; r.Inset(3, 3); pDC->Colour(L_LOW); // pDC->Rectangle(&r); double rad = (IsVertical() ? (double)r.X() : (double)r.Y()) / 2; double cx = (double)r.x1 + rad; pDC->FilledArc(cx, r.y1 + rad, rad, 0, 180); pDC->Rectangle(r.x1, r.y1 + rad, r.x2, r.y2 - rad); pDC->FilledArc(cx, r.y2 - rad, rad, 180, 0); } #elif WINXP_LOOK // left/up button LRect r = Sub; DrawBorder(pDC, r, IsOver() == BTN_SUB ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, false, IsValid() ? L_BLACK : L_LOW); // right/down r = Add; DrawBorder(pDC, r, IsOver() == BTN_ADD ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, true, IsValid() ? L_BLACK : L_LOW); // printf("Paint %ix%i, %s\n", pDC->X(), pDC->Y(), Widget->GetPos().GetStr()); if (IsValid()) { // slide space pDC->Colour(SlideCol); pDC->Rectangle(&PageSub); pDC->Rectangle(&PageAdd); // slide button r = Slide; DrawBorder(pDC, r, DefaultRaisedEdge); // IsOver() == BTN_SLIDE ? SUNKEN : RAISED); pDC->Colour(L_MED); if (r.Valid()) pDC->Rectangle(&r); } else { pDC->Colour(SlideCol); pDC->Rectangle(&Slide); } #else #error "No look and feel defined." #endif } int GetWidth() { return IsVertical() ? Widget->X() : Widget->Y(); } int GetLength() { return (IsVertical() ? Widget->Y() : Widget->X()) #if !MAC_LOOK - (GetWidth() * 2) #endif ; } int64 GetRange() { return Max >= Min ? Max - Min + 1 : 0; } bool IsValid() { return Max >= Min; } void CalcRegions() { LRect r = Widget->GetPos(); Vertical = r.Y() > r.X(); int w = GetWidth(); int len = GetLength(); // Button sizes #if MAC_LOOK Sub.ZOff(-1, -1); Add.ZOff(-1, -1); #else Sub.ZOff(w-1, w-1); Add.ZOff(w-1, w-1); // Button positions if (IsVertical()) Add.Offset(0, Widget->GetPos().Y()-w); else Add.Offset(Widget->GetPos().X()-w, 0); #endif // Slider int64 Start, End; #if LGI_SDL int MinSize = w; // Touch UI needs large slide.... #else int MinSize = 18; #endif // printf("Calc %i, " LPrintfInt64 ", " LPrintfInt64 "\n", IsValid(), Min, Max); if (IsValid()) { int64 Range = GetRange(); int64 Size = Range ? MIN((int)Page, Range) * len / Range : len; if (Size < MinSize) Size = MinSize; Start = Range > Page ? Value * (len - Size) / (Range - (int)Page) : 0; End = Start + Size; /* printf("Range=%i Page=%i Size=%i Start=%i End=%i\n", (int)Range, (int)Page, (int)Size, (int)Start, (int)End); */ if (IsVertical()) { Slide.ZOff(w-1, (int) (End-Start-1)); #if MAC_LOOK Slide.Offset(0, (int) (r.y1+Start)); #else Slide.Offset(0, (int) (Sub.y2+1+Start)); #endif if (Start > 1) { PageSub.x1 = Slide.x1; #if MAC_LOOK PageSub.y1 = 0; #else PageSub.y1 = Sub.y2 + 1; #endif PageSub.x2 = Slide.x2; PageSub.y2 = Slide.y1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.y1 - 2) { PageAdd.x1 = Slide.x1; PageAdd.x2 = Slide.x2; PageAdd.y1 = Slide.y2 + 1; #if MAC_LOOK PageAdd.y2 = r.Y()-1; #else PageAdd.y2 = Add.y1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } else { Slide.ZOff((int)(End-Start-1), w-1); Slide.Offset((int)(Sub.x2+1+Start), 0); if (Start > 1) { PageSub.y1 = Slide.y1; #if MAC_LOOK PageSub.x1 = 0; #else PageSub.x1 = Sub.x2 + 1; #endif PageSub.y2 = Slide.y2; PageSub.x2 = Slide.x1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.x1 - 2) { PageAdd.y1 = Slide.y1; PageAdd.y2 = Slide.y2; PageAdd.x1 = Slide.x2 + 1; #if MAC_LOOK PageAdd.x2 = r.X() - 1; #else PageAdd.x2 = Add.x1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } } else { PageAdd.ZOff(-1, -1); PageSub.ZOff(-1, -1); Slide = Widget->GetClient(); if (IsVertical()) { Slide.Inset(0, Sub.y2 + 1); } else { Slide.Inset(Sub.x2 + 1, 0); } } } int OnHit(int x, int y) { #if MAC_SKIN HIThemeTrackDrawInfo Info; LRect Client = Widget->GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = Min; Info.max = Max - Page; Info.value = Value; Info.reserved = 0; Info.attributes = (Widget->Vertical() ? 0 : kThemeTrackHorizontal) | (Widget->Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Widget->Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = Page; Info.trackInfo.scrollbar.pressState = false; HIPoint pt = {(CGFloat)x, (CGFloat)y}; ControlPartCode part; Boolean b = HIThemeHitTestTrack(&Info, &pt, &part); if (b) { switch (part) { case kAppearancePartUpButton: return BTN_SUB; case kAppearancePartDownButton: return BTN_ADD; case 129: return BTN_SLIDE; case kControlPageUpPart: return BTN_PAGE_SUB; case kControlPageDownPart: return BTN_PAGE_ADD; default: printf("%s:%i - Unknown scroll bar hittest value: %i\n", _FL, part); break; } } #else if (Sub.Overlap(x, y)) return BTN_SUB; if (Slide.Overlap(x, y)) return BTN_SLIDE; if (Add.Overlap(x, y)) return BTN_ADD; if (PageSub.Overlap(x, y)) return BTN_PAGE_SUB; if (PageAdd.Overlap(x, y)) return BTN_PAGE_ADD; #endif return BTN_NONE; } int OnClick(int Btn, int x, int y) { if (IsValid()) { switch (Btn) { case BTN_SUB: { SetValue(Value-1); break; } case BTN_ADD: { SetValue(Value+1); break; } case BTN_PAGE_SUB: { SetValue(Value-Page); break; } case BTN_PAGE_ADD: { SetValue(Value+Page); break; } case BTN_SLIDE: { SlideOffset = IsVertical() ? y - Slide.y1 : x - Slide.x1; break; } } } return false; } void SetValue(int64 i) { if (i < Min) { i = Min; } if (IsValid() && i > Max - Page + 1) { i = MAX(Min, Max - Page + 1); } if (Value != i) { Value = i; CalcRegions(); Widget->Invalidate(); LViewI *n = Widget->GetNotify() ? Widget->GetNotify() : Widget->GetParent(); if (n) n->OnNotify(Widget, LNotifyItemChange); } } }; ///////////////////////////////////////////////////////////////////////////////////// LScrollBar::LScrollBar() : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); } LScrollBar::LScrollBar(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); SetId(id); if (name) Name(name); if (cx > cy) { SetVertical(false); } LResources::StyleElement(this); } LScrollBar::~LScrollBar() { DeleteObj(d); } bool LScrollBar::Valid() { return d->Max > d->Min; } int LScrollBar::SCROLL_BAR_SIZE = 0; int LScrollBar::GetScrollSize() { if (!SCROLL_BAR_SIZE) - { - SCROLL_BAR_SIZE = std::max(15, LScreenDpi().x / 5); - } + SCROLL_BAR_SIZE = std::max(15, LScreenDpi().x / 6); return SCROLL_BAR_SIZE; } bool LScrollBar::Attach(LViewI *p) { bool Status = LControl::Attach(p); #if 0 - printf("%p::Attach scroll bar to %s, Status=%i, _View=%p, Vis=%i\n", + printf("%p::Attach scroll bar to %s, Status=%i, Vis=%i\n", this, p->GetClass(), Status, - _View, Visible()); #endif return Status; } void LScrollBar::OnPaint(LSurface *pDC) { #if MAC_SKIN #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif HIThemeTrackDrawInfo Info; LRect Client = GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = d->Min; Info.max = d->Max - d->Page + 1; Info.value = d->Value; Info.reserved = 0; Info.attributes = (Vertical() ? 0 : kThemeTrackHorizontal) | (Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = d->Page; Info.trackInfo.scrollbar.pressState = false; CGContextRef Cr = pDC->Handle(); OSStatus e = HIThemeDrawTrack(&Info, NULL, Cr, kHIThemeOrientationNormal); if (e) printf("%s:%i - HIThemeDrawTrack failed with %li\n", _FL, e); #else d->OnPaint(pDC); #endif } void LScrollBar::OnPosChange() { d->CalcRegions(); } void LScrollBar::OnMouseClick(LMouse &m) { if (d->Max >= d->Min) { int Hit = d->OnHit(m.x, m.y); Capture(m.Down()); if (m.Down()) { if (Hit != d->Clicked) { d->Clicked = Hit; d->Over = true; Invalidate(); d->OnClick(Hit, m.x, m.y); if (Hit != BTN_SLIDE) { d->Ignore = 2; SetPulse(50); } } } else { if (d->Clicked) { d->Clicked = BTN_NONE; d->Over = false; Invalidate(); } } } } void LScrollBar::OnMouseMove(LMouse &m) { if (IsCapturing()) { if (d->Clicked == BTN_SLIDE) { if (d->GetLength()) { int64 Range = d->GetRange(); int Len = d->GetLength(); int Size = d->IsVertical() ? d->Slide.Y() : d->Slide.X(); int Px = (d->IsVertical() ? m.y : m.x) - d->GetWidth() - d->SlideOffset; int64 Value = Px * (Range - d->Page) / (Len - Size); d->SetValue(Value); } } else { int Hit = d->OnHit(m.x, m.y); bool Over = Hit == d->Clicked; if (Over != d->Over) { d->Over = Over; Invalidate(); } } } } bool LScrollBar::OnKey(LKey &k) { return false; } bool LScrollBar::OnMouseWheel(double Lines) { return false; } bool LScrollBar::Vertical() { return d->Vertical; } void LScrollBar::SetVertical(bool v) { d->Vertical = v; d->CalcRegions(); Invalidate(); } int64 LScrollBar::Value() { return d->Value; } void LScrollBar::Value(int64 v) { d->SetValue(v); } LRange LScrollBar::GetRange() const { return LRange(d->Min, d->Max - d->Min + 1); } void LScrollBar::Limits(int64 &Low, int64 &High) { Low = d->Min; High = d->Max; } bool LScrollBar::SetRange(const LRange &r) { if (d->Min != r.Start || d->Max != r.End() - 1) { d->Min = r.Start; d->Max = r.End() - 1; d->Page = MIN(d->Page, d->GetRange()); d->CalcRegions(); Invalidate(); OnConfigure(); } return true; } void LScrollBar::SetLimits(int64 Low, int64 High) { SetRange(LRange(Low, High-Low+1)); } int64 LScrollBar::Page() { return d->Page; } void LScrollBar::SetPage(int64 i) { if (d->Page != i) { d->Page = MAX(i, 1); d->CalcRegions(); Invalidate(); OnConfigure(); } } LMessage::Result LScrollBar::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } void LScrollBar::OnPulse() { if (d->Ignore > 0) { d->Ignore--; } else { LMouse m; if (GetMouse(m)) { int Hit = d->OnHit(m.x, m.y); if (Hit == d->Clicked) { d->OnClick(d->Clicked, m.x, m.y); } } } } diff --git a/src/common/Widgets/TableLayout.cpp b/src/common/Widgets/TableLayout.cpp --- a/src/common/Widgets/TableLayout.cpp +++ b/src/common/Widgets/TableLayout.cpp @@ -1,2529 +1,2529 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/CssTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Variant.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "lgi/common/CheckBox.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TabView.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Css.h" #undef _FL #define _FL LGetLeaf(__FILE__), __LINE__ enum CellFlag { // A null value when the call flag is not known yet SizeUnknown, // The cell contains fixed size objects SizeFixed, // The cell contains objects that have variable size SizeGrow, // The cell contains objects that will use all available space SizeFill, }; #define Izza(c) dynamic_cast(v) // #define DEBUG_LAYOUT 105 #define DEBUG_PROFILE 0 #define DEBUG_DRAW_CELLS 0 // #define DEBUG_CTRL_ID 105 #ifdef DEBUG_CTRL_ID static LString Indent(int Depth) { return LString(" ") * (Depth << 2); } #endif struct LPrintfLogger : public LStream { public: ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { if (!Ptr) return 0; #ifdef WINNATIVE LgiTrace("%.*s", (int)Size, (const char*)Ptr); return Size; #else return printf("%.*s", (int)Size, (const char*)Ptr); #endif } } PrintfLogger; int LTableLayout::CellSpacing = 4; const char *FlagToString(CellFlag f) { switch (f) { case SizeUnknown: return "Unknown"; case SizeFixed: return "Fixed"; case SizeGrow: return "Grow"; case SizeFill: return "Fill"; } return "error"; } template T CountRange(LArray &a, ssize_t Start, ssize_t End) { T c = 0; for (ssize_t i=Start; i<=End; i++) { c += a[i]; } return c; } struct UnderInfo { int Priority; int Col; // index of column int Grow; // in px }; static void DistributeUnusedSpace( LArray &Min, LArray &Max, LArray &Flags, int Total, int CellSpacing, LStream *Debug = NULL) { // Now allocate unused space int Borders = (int)Min.Length() - 1; int Sum = CountRange(Min, 0, Borders) + (Borders * CellSpacing); if (Sum >= Total) return; int i, Avail = Total - Sum; // Do growable ones first LArray Unders; int UnknownGrow = 0; for (i=0; iPrint("\t\tAdding[%i] fill, pri=%i\n", i, u.Priority); */ } else if (Max[i] > Min[i]) { UnderInfo &u = Unders.New(); u.Col = i; u.Grow = Max[i] - Min[i]; if (u.Grow > Avail) { u.Grow = 0; u.Priority = 2; UnknownGrow++; } else { u.Priority = u.Grow < Avail >> 1 ? 0 : 1; } /* if (Debug) Debug->Print("\t\tAdding[%i] grow, pri=%i\n", i, u.Priority); */ } } Unders.Sort([](auto a, auto b) { if (a->Priority != b->Priority) return a->Priority - b->Priority; return b->Grow - a->Grow; }); int UnknownSplit = 0; for (i=0; Avail>0 && iPrint("\t\tGrow[%i] %i += %i\n", i, Min[u.Col], u.Grow); */ Min[u.Col] += u.Grow; Avail -= u.Grow; } else { if (!UnknownSplit) UnknownSplit = Avail / UnknownGrow; if (!UnknownSplit) UnknownSplit = Avail; /* if (Debug) Debug->Print("\t\tFill[%i] %i += %i\n", i, Min[u.Col], UnknownSplit); */ Min[u.Col] += UnknownSplit; Avail -= UnknownSplit; } } } static void DistributeSize( LArray &a, LArray &Flags, int Start, int Span, int Size, int Border, LStream *Debug = NULL) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i= Size) return; // Get list of growable cells LArray Grow, Fill, Fixed, Unknown; int ExistingGrowPx = 0; int ExistingFillPx = 0; int UnknownPx = 0; for (int i=Start; i 0 && Unknown.Length() > 0) { // Distribute size amongst the unknown cells int AdditionalSize = Size - Cur; for (int i=0; i Children; LCss::DisplayType Disp; LString ClassName; TableCell(LTableLayout *t, int Cx, int Cy); LTableLayout *GetTable() override { return Table; } bool Add(LView *v) override; bool Remove(LView *v) override; LArray GetChildren() override; bool RemoveAll(); Child *HasView(LView *v); LStream &Log(); bool IsSpanned(); bool GetVariant(const char *Name, LVariant &Value, const char *Array) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array) override; int MaxCellWidth(); /// Calculates the minimum and maximum widths this cell can occupy. void LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag); /// Calculate the height of the cell based on the given width void LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags); /// Called after the layout has been done to move the controls into place void LayoutPost(int Depth); void OnPaint(LSurface *pDC); void OnChange(PropType Prop) override; }; class LTableLayoutPrivate { friend class TableCell; bool InLayout; bool DebugLayout; public: LPoint PrevSize, Dpi; bool LayoutDirty; LArray Rows, Cols; LArray Cells; int BorderSpacing; LRect LayoutBounds; int LayoutMinX, LayoutMaxX; LTableLayout *Ctrl; LStream &Log() { return PrintfLogger; } // Object LTableLayoutPrivate(LTableLayout *ctrl); ~LTableLayoutPrivate(); // Utils bool IsInLayout() { return InLayout; } TableCell *GetCellAt(int cx, int cy); void Empty(LRect *Range = NULL); bool CollectRadioButtons(LArray &Btns); void InitBorderSpacing(); double Scale() { if (!Dpi.x) { auto Wnd = Ctrl->GetWindow(); if (Wnd) Dpi = Wnd->GetDpi(); else Dpi.x = Dpi.y = 96; } LAssert(Dpi.x > 0); return Dpi.x / 96.0; } // Layout temporary values LArray MinCol, MaxCol; LArray MinRow, MaxRow; LArray ColFlags, RowFlags; // Layout staged methods, call in order to complete the layout void LayoutHorizontal(const LRect Client, int Depth, int *MinX = NULL, int *MaxX = NULL, CellFlag *Flag = NULL); void LayoutVertical(const LRect Client, int Depth, int *MinY = NULL, int *MaxY = NULL, CellFlag *Flag = NULL); void LayoutPost(const LRect Client, int Depth); // This does the whole layout, basically calling all the stages for you void Layout(const LRect Client, int Depth); }; /////////////////////////////////////////////////////////////////////////////////////////// TableCell::TableCell(LTableLayout *t, int Cx, int Cy) { TextAlign(AlignLeft); VerticalAlign(VerticalTop); Table = t; Cell.ZOff(0, 0); Cell.Offset(Cx, Cy); Padding.ZOff(0, 0); Pos.ZOff(-1, -1); Disp = LCss::DispBlock; Children.SetFixedLength(true); } void TableCell::OnChange(PropType Prop) { if (Prop == PropDisplay) { bool Vis = Display() != LCss::DispNone; for (auto c: Children) c.View->Visible(Vis); } } LStream &TableCell::Log() { /* if (!TopLevelLog) { // Find the top most table layout LTableLayout *Top = Table; for (LViewI* t = Top; t; t = t->GetParent()) { auto tbl = dynamic_cast(t); if (tbl) Top = tbl; if (t->GetId() == 24) break; } TopLevelLog = &Top->d->Dbg; } LAssert(TopLevelLog != NULL); return *TopLevelLog; */ return PrintfLogger; } TableCell::Child *TableCell::HasView(LView *v) { size_t Len = Children.Length(); if (!Len) return NULL; Child *s = &Children[0]; Child *e = s + Len; while (s < e) { if (s->View == v) return s; s++; } return NULL; } LArray TableCell::GetChildren() { LArray a; for (auto &c: Children) a.Add(c.View); return a; } bool TableCell::Add(LView *v) { if (!v || HasView(v)) return false; if (Table->IsAttached()) v->Attach(Table); else Table->AddView(v); Children.SetFixedLength(false); Children.New().View = v; Children.SetFixedLength(true); return true; } bool TableCell::Remove(LView *v) { Child *c = HasView(v); if (!c) return false; Table->DelView(v); if (Children.Length()) { int Idx = (int) (c - &Children.First()); Children.DeleteAt(Idx, true); } return true; } bool TableCell::RemoveAll() { Child *c = &Children[0]; for (unsigned i=0; i 1 || Cell.Y() > 1; } bool TableCell::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (!Value.SetList()) return false; for (unsigned i=0; iType = GV_GVIEW; v->Value.View = c.View; Value.Value.Lst->Insert(v); } } break; } case ContainerSpan: // Type: LRect { Value = Cell.GetStr(); break; } case ContainerAlign: // Type: String { LStringPipe p(128); if (TextAlign().ToString(p)) Value.OwnStr(p.NewStr()); else return false; break; } case ContainerVAlign: // Type: String { LStringPipe p(128); if (VerticalAlign().ToString(p)) Value.OwnStr(ToString()); else return false; break; } case ObjClass: // Type: String { Value = ClassName; break; } case ObjStyle: // Type: String { Value.OwnStr(ToString()); break; } case ObjDebug: // Type: Boolean { Value = Debug; break; } default: return false; } return true; } bool TableCell::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (Value.Type != GV_LIST) { LAssert(!"Should be a list."); return false; } // LProfile p("ContainerChildren"); for (auto v: *Value.Value.Lst) { if (v->Type != GV_VOID_PTR) continue; ResObject *o = (ResObject*)v->Value.Ptr; if (!o) continue; LView *gv = dynamic_cast(o); if (!gv) continue; // p.Add("c1"); Children.SetFixedLength(false); Children.New().View = gv; Children.SetFixedLength(true); // p.Add("c2"); Table->AddView(gv); // p.Add("c3"); gv->SetParent(Table); // p.Add("c4"); LTextLabel *t = dynamic_cast(gv); if (t) t->SetWrap(true); } break; } case ContainerSpan: { LRect r; if (r.SetStr(Value.Str())) Cell = r; else return false; break; } case ContainerAlign: { TextAlign ( Len ( ConvertAlign(Value.Str(), true) ) ); break; } case ContainerVAlign: { VerticalAlign ( Len ( ConvertAlign(Value.Str(), false) ) ); break; } case ObjClass: { ClassName = Value.Str(); LResources *r = LgiGetResObj(); if (r) { LCss::SelArray *a = r->CssStore.ClassMap.Find(ClassName); if (a) { for (int i=0; iLength(); i++) { LCss::Selector *s = (*a)[i]; // This is not exactly a smart matching algorithm. if (s && s->Parts.Length() == 1) { const char *style = s->Style; Parse(style, ParseRelaxed); } } } } break; } case ObjStyle: { const char *style = Value.Str(); if (style) Parse(style, ParseRelaxed); break; } case ObjDebug: { Debug = Value.CastInt32() != 0; break; } default: return false; } return true; } int TableCell::MaxCellWidth() { // Table size minus padding LCssTools t(Table->GetCss(), Table->GetFont()); LRect cli = Table->GetClient(); cli = t.ApplyPadding(cli); // Work out any borders on spanned cells... int BorderPx = Cell.X() > 1 ? (Cell.X() - 1) * Table->d->BorderSpacing : 0; // Return client - padding - border size. return cli.X() - BorderPx; } /// Calculates the minimum and maximum widths this cell can occupy. void TableCell::LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag) { int MaxBtnX = 0; int TotalBtnX = 0; int Min = 0, Max = 0; Pos = Pos.ZeroTranslate(); // Calculate CSS padding #define CalcCssPadding(Prop, Axis, Edge) \ { \ Len l = Prop(); \ if (l.Type == LCss::LenInherit) l = LCss::Padding(); \ if (l.Type) \ Padding.Edge = l.ToPx(Table->Axis(), Table->GetFont()); \ else \ Padding.Edge = 0; \ } Disp = Display(); if (Disp == DispNone) return; CalcCssPadding(PaddingLeft, X, x1) CalcCssPadding(PaddingRight, X, x2) CalcCssPadding(PaddingTop, Y, y1) CalcCssPadding(PaddingBottom, Y, y2) // Cell CSS size: auto Wid = Width(); auto MinWid = MinWidth(); auto MaxWid = MaxWidth(); auto Fnt = Table->GetFont(); int Tx = Table->X(); if (MinWid.IsValid()) Min = MAX(Min, MinWid.ToPx(Tx, Fnt)); if (Wid.IsValid()) { if (Wid.Type == LCss::LenAuto) Flag = SizeFill; else { // If we set Min here too the console app breaks because the percentage // is over-subscribe (95%) to force one cell to grow to fill available space. Max = Wid.ToPx(Tx, Fnt) - Padding.x1 - Padding.x2; if (!Wid.IsDynamic()) { Flag = SizeFixed; Min = Max; /* This is breaking normal usage, where 'Min' == 0. Need to redesign for edge case. if (Padding.x1 + Padding.x2 > Min) { // Remove padding as it's going to oversize the cell Padding.x1 = Padding.x2 = 0; } */ } else { Flag = SizeGrow; } } } if (!Wid.IsValid() || Flag != SizeFixed) { Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; ZeroObj(c->Inf); c->r = v->GetPos().ZeroTranslate(); // Child view CSS size: LCss *Css = v->GetCss(); LCss::Len ChildWid; // LCss::Len ChildMin, ChildMax; if (Css) { ChildWid = Css->Width(); // ChildMin = Css->MinWidth(); // ChildMax = Css->MaxWidth(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (ChildWid.IsValid()) { int MaxPx = MaxCellWidth(); c->Inf.Width.Min = c->Inf.Width.Max = ChildWid.ToPx(MaxPx, v->GetFont()); Min = MAX(Min, c->Inf.Width.Min); Max = MAX(Max, c->Inf.Width.Min); c->r = v->GetPos(); c->r.x2 = c->r.x1 + c->Inf.Width.Min - 1; } else if (v->OnLayout(c->Inf)) { if (c->Inf.Width.Max < 0) { if (Flag != SizeFixed) Flag = SizeFill; } else if (Max) Max += c->Inf.Width.Max + LTableLayout::CellSpacing; else Max = c->Inf.Width.Max; if (c->Inf.Width.Min) { Min = MAX(Min, c->Inf.Width.Min); if (c->Inf.Width.Max > c->Inf.Width.Min && Flag == SizeUnknown) Flag = SizeGrow; } } else if (Izza(LButton)) { LDisplayString ds(v->GetFont(), v->Name()); auto Scale = Table->d->Scale(); LPoint Pad((int)(Scale * LButton::Overhead.x + 0.5), (int)(Scale * LButton::Overhead.y + 0.5)); c->Inf.Width.Min = c->Inf.Width.Max = ds.X() + Pad.x; c->Inf.Height.Min = c->Inf.Height.Max = ds.Y() + Pad.y; MaxBtnX = MAX(MaxBtnX, c->Inf.Width.Min); TotalBtnX = TotalBtnX ? TotalBtnX + LTableLayout::CellSpacing + c->Inf.Width.Min : c->Inf.Width.Min; if (Flag < SizeFixed) Flag = SizeFixed; } else if (Izza(LEdit) || Izza(LScrollBar)) { Min = MAX(Min, 40); if (Flag != SizeFixed) Flag = SizeFill; } else if (Izza(LCombo)) { int PadX = LCombo::Pad.x1 + LCombo::Pad.x2; LCombo *Cbo = Izza(LCombo); LFont *f = Cbo->GetFont(); int min_x = -1, max_x = 0; char *t; for (int i=0; i < Cbo->Length() && (t = (*Cbo)[i]); i++) { LDisplayString ds(f, t); int x = ds.X(); min_x = min_x < 0 ? x : MIN(min_x, x); max_x = MAX(x + 4, max_x); } Min = MAX(Min, min_x + PadX); Max = MAX(Max, max_x + PadX); if (Flag < SizeGrow) Flag = SizeGrow; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { Min = MAX(Min, Dc->X() + 4); Max = MAX(Max, Dc->X() + 4); } else { Min = MAX(Min, 16); Max = MAX(Max, 16); if (Flag < SizeFill) Flag = SizeFill; } } else if (Izza(LList)) { LList *Lst = Izza(LList); int m = 0; for (int i=0; iGetColumns(); i++) { m += Lst->ColumnAt(i)->Width(); } m = MAX(m, 40); Min = MAX(Min, 40); Flag = SizeFill; } else if (Izza(LTree) || Izza(LTabView)) { Min = MAX(Min, 40); Flag = SizeFill; } else { LTableLayout *Tbl = Izza(LTableLayout); if (Tbl) { Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(Table->GetClient(), Depth+1, &Min, &Max, &Flag); if (Wid.IsDynamic()) { if (Min > Max) Min = Max; } } else { Min = MAX(Min, v->X()); Max = MAX(Max, v->X()); } } } } if (MaxBtnX) { Min = MAX(Min, MaxBtnX); Max = MAX(Max, TotalBtnX); } if (MaxWid.IsValid()) { int Tx = Table->X(); int Px = MaxWid.ToPx(Tx, Table->GetFont()) - Padding.x1 - Padding.x2; if (Min > Px) Min = Px; if (Max > Px) Max = Px; } MinX = MAX(MinX, Min + Padding.x1 + Padding.x2); MaxX = MAX(MaxX, Max + Padding.x1 + Padding.x2); } /// Calculate the height of the cell based on the given width void TableCell::LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags) { Pos.ZOff(Width-1, 0); if (Disp == DispNone) return; Len Ht = Height(); if (Ht.Type != LenInherit) { if (Ht.IsDynamic()) { MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); if (Flags < SizeGrow) Flags = SizeGrow; } else { MinY = MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); Pos.y2 = MinY - 1; if (Flags < SizeFixed) Flags = SizeFixed; return; } } LPoint Cur(Pos.x1, Pos.y1); int NextY = Pos.y1; LCss::Len CssWidth = LCss::Width(); Width -= Padding.x1 + Padding.x2; LAssert(Width >= 0); Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (CssWidth.Type != LenInherit) { // In the case where the CSS width is set, we need to default the // OnLayout info with valid widths otherwise the OnLayout calls will // not fill out the height, but initialize the width instead. // Without this !zero check the Bayesian Filter setting dialog in Scribe // has the wrong Help button size. ie if the PreLayout step gave a valid // layout size, don't override it here. if (!c->Inf.Width.Min) c->Inf.Width.Min = Width; if (!c->Inf.Width.Max) c->Inf.Width.Max = Width; } LTableLayout *Tbl = NULL; LCss *Css = v->GetCss(); LCss::Len Ht; if (Css) Ht = Css->Height(); if (!Izza(LButton) && i) Pos.y2 += Table->d->BorderSpacing; if (c->Inf.Width.Min > Width) c->Inf.Width.Min = Width; if (c->Inf.Width.Max > Width) c->Inf.Width.Max = Width; if (Ht.IsValid()) { int CtrlHeight = Ht.ToPx(Table->Y(), v->GetFont()); c->Inf.Height.Min = c->Inf.Height.Max = CtrlHeight; if (MaxY < CtrlHeight) MaxY = CtrlHeight; if (!Ht.IsDynamic() && MinY < CtrlHeight) MinY = CtrlHeight; c->r = v->GetPos().ZeroTranslate(); c->r.y2 = c->r.y1 + CtrlHeight - 1; Pos.y2 = MAX(Pos.y2, c->r.Y()-1); } else if (v->OnLayout(c->Inf)) { // Supports layout info c->r = v->GetPos(); // Process height if (c->Inf.Height.Max < 0) Flags = SizeFill; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; // Process width if (c->Inf.Width.Max < 0) c->r.x2 = c->r.x1 + Width - 1; else c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; if (Cur.x > Pos.x1 && Cur.x + c->r.X() > Pos.x2) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x - c->r.x1, Cur.y - c->r.y1); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); c->IsLayout = true; } else if (Izza(LScrollBar)) { Pos.y2 += 15; } else if (Izza(LButton)) { c->r.ZOff(c->Inf.Width.Min-1, c->Inf.Height.Min-1); if (Cur.x + c->r.X() > Width) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x, Cur.y); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); } else if (Izza(LEdit) || Izza(LCombo)) { LFont *f = v->GetFont(); int y = (f ? f : LSysFont)->GetHeight() + 8; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; if (Izza(LEdit) && Izza(LEdit)->MultiLine()) { Flags = SizeFill; // MaxY = MAX(MaxY, 1000); } } else if (Izza(LRadioButton)) { int y = v->GetFont()->GetHeight() + 2; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; } else if (Izza(LList) || Izza(LTree) || Izza(LTabView)) { Pos.y2 += v->GetFont()->GetHeight() + 8; // MaxY = MAX(MaxY, 1000); Flags = SizeFill; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { MaxY = MAX(MaxY, Dc->Y() + 4); } else { MaxY = MAX(MaxY, 1000); } } else if ((Tbl = Izza(LTableLayout))) { auto Children = Tbl->IterateViews(); c->r.ZOff(Width-1, Table->Y()-1); LCssTools tools(Tbl->GetCss(), Tbl->GetFont()); auto client = tools.ApplyBorder(c->r); Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(client, Depth+1); Tbl->d->LayoutVertical(client, Depth+1, &MinY, &MaxY, &Flags); Tbl->d->LayoutPost(client, Depth+1); Pos.y2 += MinY; c->Inf.Height.Min = MinY; c->Inf.Height.Max = MaxY; } else { // Doesn't support layout info Pos.y2 += v->Y(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); } #endif } // Fix: This if statement is needed to stop LFileSelect dialogs only growing in size, and // the Ok/Cancel shifting off the bottom of the dialog if you shrink the window. if (Flags != SizeFill) { MinY = MAX(MinY, Pos.Y() + Padding.y1 + Padding.y2); MaxY = MAX(MaxY, Pos.Y() + Padding.y1 + Padding.y2); } } /// Called after the layout has been done to move the controls into place void TableCell::LayoutPost(int Depth) { int Cx = Padding.x1; int Cy = Padding.y1; int MaxY = Padding.y1; int RowStart = 0; LArray New; int WidthPx = Pos.X() - Padding.x1 - Padding.x2; int HeightPx = Pos.Y() - Padding.y1 - Padding.y2; #ifdef DEBUG_CTRL_ID bool HasDebugCtrl = false; #endif Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; if (Disp == DispNone) { v->Visible(false); continue; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s padding=%s cur=%i,%i (%s:%i)\n", v->GetId(), c->r.GetStr(), Padding.GetStr(), Cx, Cy, _FL); } #endif LTableLayout *Tbl = Izza(LTableLayout); if (i > 0 && Cx + c->r.X() > Pos.X()) { // Do wrapping int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } for (int n=RowStart; n<=i; n++) { New[n].Offset(OffsetX, 0); } RowStart = i + 1; Cx = Padding.x1; Cy = MaxY + Table->d->BorderSpacing; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i offset %i %i %i, %i %i %i %s:%i\n", v->GetId(), Pos.x1, c->r.x1, Cx, Pos.y1, c->r.y1, Cy, _FL); } #endif c->r.Offset(Pos.x1 - c->r.x1 + Cx, Pos.y1 - c->r.y1 + Cy); if (c->Inf.Width.Max >= WidthPx) c->Inf.Width.Max = WidthPx; if (c->Inf.Height.Max >= HeightPx) c->Inf.Height.Max = HeightPx; if (c->r.Y() > HeightPx) { c->r.y2 = c->r.y1 + HeightPx - 1; } if (Tbl) { c->r.SetSize(Pos.X(), MIN(Pos.Y(), c->Inf.Height.Min)); } else if ( Izza(LList) || Izza(LTree) || Izza(LTabView) || (Izza(LEdit) && Izza(LEdit)->MultiLine()) ) { c->r.y2 = Pos.y2; if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } else if (c->IsLayout) { if (c->Inf.Height.Max < 0) c->r.y2 = Pos.y2; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; } else { if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } New[i] = c->r; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s (%s:%i)\n", v->GetId(), c->r.GetStr(), _FL); } #endif MaxY = MAX(MaxY, c->r.y2 - Pos.y1); Cx += c->r.X() + Table->d->BorderSpacing; } if (Disp == DispNone) { return; } int n; int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } if (OffsetX) { for (n=RowStart; nd->DebugLayout) { Log().Print("\tCell[%i,%i]=%s (%ix%i)\n", Cell.x1, Cell.y1, Pos.GetStr(), Pos.X(), Pos.Y()); } #endif for (n=0; nGetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i %s[%i]=%s, %ix%i, Offy=%i %s (%s:%i)\n", v->GetId(), v->GetClass(), n, New[n].GetStr(), New[n].X(), New[n].Y(), OffsetY, v->Name(), _FL); } #endif v->SetPos(New[n]); } } void TableCell::OnPaint(LSurface *pDC) { LCssTools t(this, Table->GetFont()); LRect r = Pos; t.PaintBorder(pDC, r); LColour Trans; auto bk = t.GetBack(&Trans); if (bk.IsValid()) { pDC->Colour(bk); pDC->Rectangle(&r); } } //////////////////////////////////////////////////////////////////////////// LTableLayoutPrivate::LTableLayoutPrivate(LTableLayout *ctrl) { PrevSize.Set(-1, -1); LayoutDirty = true; InLayout = false; DebugLayout = false; Ctrl = ctrl; BorderSpacing = LTableLayout::CellSpacing; LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } LTableLayoutPrivate::~LTableLayoutPrivate() { Empty(); } bool LTableLayoutPrivate::CollectRadioButtons(LArray &Btns) { for (LViewI *i: Ctrl->IterateViews()) { LRadioButton *b = dynamic_cast(i); if (b) Btns.Add(b); } return Btns.Length() > 0; } void LTableLayoutPrivate::Empty(LRect *Range) { if (Range) { // Clear a range of cells.. for (int i=0; iOverlap(&c->Cell)) { c->RemoveAll(); Cells.DeleteAt(i--, true); DeleteObj(c); } } } else { // Clear all the cells Ctrl->IterateViews().DeleteObjects(); Cells.DeleteObjects(); Rows.Length(0); Cols.Length(0); } PrevSize.Set(-1, -1); LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } TableCell *LTableLayoutPrivate::GetCellAt(int cx, int cy) { for (int i=0; iCell.Overlap(cx, cy)) return Cells[i]; return NULL; } void LTableLayoutPrivate::LayoutHorizontal(const LRect Client, int Depth, int *MinX, int *MaxX, CellFlag *Flag) { // This only gets called when you nest LTableLayout controls. It's // responsible for doing pre layout stuff for an entire control of cells. int Cx, Cy, i; LString::Array Ps; Ps.SetFixedLength(false); LAutoPtr Prof(/*Debug ? new LProfile("Layout") :*/ NULL); // Zero everything to start with MinCol.Length(0); MaxCol.Length(0); MinRow.Length(0); MaxRow.Length(0); ColFlags.Length(0); RowFlags.Length(0); #if DEBUG_LAYOUT if (DebugLayout) { int asd=0; } #endif // Do pre-layout to determine minimum and maximum column widths for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() == 1) { if (Prof) { LString &s = Ps.New(); s.Printf("pre layout %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } int &MinC = MinCol[Cx]; int &MaxC = MaxCol[Cx]; CellFlag &ColF = ColFlags[Cx]; c->LayoutWidth(Depth, MinC, MaxC, ColF); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { Log().Print("\tLayout Id=%i, Size=%i,%i (%s:%i)\n", Ctrl->GetId(), Client.X(), Client.Y(), _FL); for (i=0; iAdd("Pre layout spanned"); // Pre-layout column width for spanned cells for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() > 1) { int Min = 0, Max = 0; CellFlag Flag = SizeUnknown; if (Prof) { LString &s = Ps.New(); s.Printf("spanned %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } if (c->Width().IsValid()) { LCss::Len l = c->Width(); if (l.Type == LCss::LenAuto) { for (int i=c->Cell.x1; i<=c->Cell.x2; i++) { ColFlags[i] = SizeFill; } } else { int Px = l.ToPx(Client.X(), Ctrl->GetFont());; if (l.IsDynamic()) { c->LayoutWidth(Depth, Min, Max, Flag); } else { Min = Max = Px; } } } else { c->LayoutWidth(Depth, Min, Max, Flag); } // Log().Print("Spanned cell: %i,%i\n", Min, Max); if (Max > Client.X()) Max = Client.X(); if (Flag) { bool HasFlag = false; bool HasUnknown = false; for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (ColFlags[i] == Flag) { HasFlag = true; } else if (ColFlags[i] == SizeUnknown) { HasUnknown = true; } } if (!HasFlag) { for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (HasUnknown) { if (ColFlags[i] == SizeUnknown) ColFlags[i] = Flag; } else { if (ColFlags[i] < Flag) ColFlags[i] = Flag; } } } } // This is the total size of all the px currently allocated int AllPx = CountRange(MinCol, 0, Cols.Length()-1) + (((int)Cols.Length() - 1) * BorderSpacing); // This is the minimum size of this cell's cols int MyPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int Remaining = Client.X() - AllPx; // Log().Print("AllPx=%i MyPx=%i, Remaining=%i\n", AllPx, MyPx, Remaining); // This is the total remaining px we could add... if (Remaining > 0) { // Limit the max size of this cell to the existing + remaining px Max = MIN(Max, MyPx + Remaining); // Distribute the max px across the cell's columns. DistributeSize(MinCol, ColFlags, c->Cell.x1, c->Cell.X(), Min, BorderSpacing); DistributeSize(MaxCol, ColFlags, c->Cell.x1, c->Cell.X(), Max, BorderSpacing); } } Cx += c->Cell.X(); } else Cx++; } } LayoutMinX = -BorderSpacing; LayoutMaxX = -BorderSpacing; for (i=0; iAdd("DistributeUnusedSpace"); DistributeUnusedSpace(MinCol, MaxCol, ColFlags, Client.X(), BorderSpacing, DebugLayout?&Log():NULL); #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iAdd("Collect together our sizes"); int Spacing = BorderSpacing * ((int)MinCol.Length() - 1); auto Css = Ctrl->GetCss(); if (MinX) { int x = CountRange(MinCol, 0, MinCol.Length()-1) + Spacing; *MinX = MAX(*MinX, x); if (Css) { auto MinWid = Css->MinWidth(); if (MinWid.IsValid()) { int px = MinWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MinX = MAX(*MinX, px); } } } if (MaxX) { int x = CountRange(MaxCol, 0, MinCol.Length()-1) + Spacing; *MaxX = MAX(*MaxX, x); if (Css) { auto MaxWid = Css->MaxWidth(); if (MaxWid.IsValid()) { int px = MaxWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MaxX = MIN(*MaxX, px); } } } if (Flag) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() == 1) { int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int &Min = MinRow[Cy]; int &Max = MaxRow[Cy]; CellFlag &Flags = RowFlags[Cy]; c->LayoutHeight(Depth, x, Min, Max, Flags); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() > 1) { int WidthPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int InitMinY = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); int InitMaxY = CountRange(MaxRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); //int AllocY = CountRange(MinRow, 0, Rows.Length()-1) + ((Rows.Length() - 1) * BorderSpacing); int MinY = InitMinY; int MaxY = InitMaxY; // int RemainingY = Client.Y() - AllocY; CellFlag RowFlag = SizeUnknown; c->LayoutHeight(Depth, WidthPx, MinY, MaxY, RowFlag); // This code stops the max being set on spanned cells. LArray AddTo; for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if ( RowFlags[y] != SizeFixed && ( RowFlags[y] != SizeGrow || MaxRow[y] > MinRow[y] ) ) AddTo.Add(y); } if (AddTo.Length() == 0) { for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if (!AddTo.HasItem(y)) { if (RowFlags[y] != SizeFixed) AddTo.Add(y); } } } if (AddTo.Length()) { if (MinY > InitMinY) { // Allocate any extra min px somewhere.. int Amt = (MinY - InitMinY) / (int)AddTo.Length(); for (int i=0; i InitMaxY) { // Allocate any extra max px somewhere.. int Amt = (MaxY - InitMaxY) / (int)AddTo.Length(); for (int i=0; i SizeUnknown) { // Apply the size flag somewhere... for (int y=c->Cell.y2; y>=c->Cell.y1; y++) { if (RowFlags[y] == SizeUnknown) { RowFlags[y] = SizeFill; break; } } } } else { // Last chance... stuff extra px in last cell... MaxRow[c->Cell.y2] = MAX(MaxY, MaxRow[c->Cell.y2]); } } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iGetCss(); if (MinY) { int y = CountRange(MinRow, 0, MinRow.Length()-1) + (((int)MinRow.Length()-1) * BorderSpacing); *MinY = MAX(*MinY, y); if (Css) { auto MinHt = Css->MinHeight(); if (MinHt.IsValid()) { auto px = MinHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MinY = MAX(*MinY, px); } } } if (MaxY) { int y = CountRange(MaxRow, 0, MinRow.Length()-1) + (((int)MaxRow.Length()-1) * BorderSpacing); *MaxY = MAX(*MaxY, y); if (Css) { auto MaxHt = Css->MaxHeight(); if (MaxHt.IsValid()) { auto px = MaxHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MaxY = MIN(*MaxY, px); } } } if (Flag) { for (i=0; iGetFont(); // Move cells into their final positions #if DEBUG_LAYOUT if (DebugLayout && Cols.Length() == 7) Log().Print("\tLayoutPost %ix%i\n", Cols.Length(), Rows.Length()); #endif for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy) { LCss::PositionType PosType = c->Position(); int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int y = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); // Set the height of the cell c->Pos.x2 = c->Pos.x1 + x - 1; c->Pos.y2 = c->Pos.y1 + y - 1; if (PosType == LCss::PosAbsolute) { // Hmm this is a bit of a hack... we'll see LCss::Len Left = c->Left(); LCss::Len Top = c->Top(); int LeftPx = Left.IsValid() ? Left.ToPx(Client.X(), Fnt) : Px; int TopPx = Top.IsValid() ? Top.ToPx(Client.Y(), Fnt) : Py; c->Pos.Offset(Client.x1 + LeftPx, Client.y1 + TopPx); } else { c->Pos.Offset(Client.x1 + Px, Client.y1 + Py); #if 0//def DEBUG_CTRL_ID Log().Print("\t\tdbgCtrl=%i Client=%s pos=%s p=%i,%i (%s:%i)\n", DEBUG_CTRL_ID, Client.GetStr(), c->Pos.GetStr(), Px, Py, _FL); #endif } c->LayoutPost(Depth); MaxY = MAX(MaxY, c->Pos.y2); #if DEBUG_LAYOUT if (DebugLayout) { c->Log().Print("\tCell[%i][%i]: %s %s\n", Cx, Cy, c->Pos.GetStr(), Client.GetStr()); } #endif } Px = c->Pos.x2 + BorderSpacing - Client.x1 + 1; Cx += c->Cell.X(); } else { Px = MinCol[Cx] + BorderSpacing; Cx++; } } Py += MinRow[Cy] + BorderSpacing; } LayoutBounds.ZOff(Px-1, Py-1); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("\tLayoutBounds: %s\n", LayoutBounds.GetStr()); #endif } void LTableLayoutPrivate::InitBorderSpacing() { BorderSpacing = LTableLayout::CellSpacing; if (Ctrl->GetCss()) { LCss::Len bs = Ctrl->GetCss()->BorderSpacing(); if (bs.Type != LCss::LenInherit) BorderSpacing = bs.ToPx(Ctrl->X(), Ctrl->GetFont()); } } void LTableLayoutPrivate::Layout(const LRect Client, int Depth) { if (InLayout) { LAssert(!"In layout, no recursion should happen."); return; } InLayout = true; InitBorderSpacing(); #if DEBUG_LAYOUT int CtrlId = Ctrl->GetId(); // auto CtrlChildren = Ctrl->IterateViews(); DebugLayout = CtrlId == DEBUG_LAYOUT && Ctrl->IterateViews().Length() > 0; if (DebugLayout) { int asd=0; } #endif #if DEBUG_PROFILE int64 Start = LCurrentTime(); #endif LString s; s.Printf("Layout id %i: %i x %i", Ctrl->GetId(), Client.X(), Client.Y()); LAutoPtr Prof(/*Debug ? new LProfile(s) :*/ NULL); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("%s\n", s.Get()); #endif if (Prof) Prof->Add("Horz"); LayoutHorizontal(Client, Depth); if (Prof) Prof->Add("Vert"); LayoutVertical(Client, Depth); if (Prof) Prof->Add("Post"); LayoutPost(Client, Depth); if (Prof) Prof->Add("Notify"); #if DEBUG_PROFILE Log().Print("LTableLayout::Layout(%i) = %i ms\n", Ctrl->GetId(), (int)(LCurrentTime()-Start)); #endif InLayout = false; Ctrl->SendNotify(LNotifyTableLayoutChanged); } LTableLayout::LTableLayout(int id) : ResObject(Res_Table) { d = new LTableLayoutPrivate(this); SetPourLargest(true); Name("LTableLayout"); SetId(id); } LTableLayout::~LTableLayout() { DeleteObj(d); } void LTableLayout::OnFocus(bool b) { if (b) { LViewI *v = GetNextTabStop(this, false); if (v) v->Focus(true); } } void LTableLayout::OnCreate() { LResources::StyleElement(this); AttachChildren(); } int LTableLayout::CellX() { return (int)d->Cols.Length(); } int LTableLayout::CellY() { return (int)d->Rows.Length(); } LLayoutCell *LTableLayout::CellAt(int x, int y) { return d->GetCellAt(x, y); } bool LTableLayout::SizeChanged() { LRect r = GetClient(); return r.X() != d->PrevSize.x || r.Y() != d->PrevSize.y; } void LTableLayout::OnPosChange() { LRect r = GetClient(); - bool Up = SizeChanged() || d->LayoutDirty; - // LgiTrace("%s:%i - Up=%i for Id=%i\n", _FL, Up, GetId()); + bool Up = SizeChanged() || d->LayoutDirty; + // LgiTrace("%s:%i - Up=%i for Id=%i\n", _FL, Up, GetId()); if (Up) { d->PrevSize.x = r.X(); d->PrevSize.y = r.Y(); if (d->PrevSize.x > 0) { if (GetCss()) { LCssTools t(GetCss(), GetFont()); r = t.ApplyBorder(r); r = t.ApplyPadding(r); } d->LayoutDirty = false; d->Layout(r, 0); } } } LRect LTableLayout::GetUsedArea() { if (SizeChanged()) { OnPosChange(); } LRect r(0, 0, -1, -1); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; if (i) r.Union(&c->Pos); else r = c->Pos; } return r; } void LTableLayout::InvalidateLayout() { if (!d->LayoutDirty) { d->LayoutDirty = true; for (auto p = GetParent(); p; p = p->GetParent()) { LTableLayout *t = dynamic_cast(p); if (t) t->InvalidateLayout(); } if (IsAttached()) PostEvent(M_TABLE_LAYOUT); } if (!IsAttached()) Invalidate(); } LMessage::Result LTableLayout::OnEvent(LMessage *m) { switch (m->Msg()) { case M_TABLE_LAYOUT: { OnPosChange(); Invalidate(); return 0; } } return LLayout::OnEvent(m); } void LTableLayout::OnPaint(LSurface *pDC) { if (SizeChanged() || d->LayoutDirty) { if (GetId() == 20) Log().Print("20 : clearing layout dirty LGI_VIEW_HANDLE=%i\n", LGI_VIEW_HANDLE); #if LGI_VIEW_HANDLE if (!Handle()) #endif OnPosChange(); #if LGI_VIEW_HANDLE else if (PostEvent(M_TABLE_LAYOUT)) return; else LAssert(!"Post event failed."); #endif if (GetId() == 20) Log().Print("20 : painting\n"); } d->Dpi = GetWindow()->GetDpi(); LCssTools Tools(this); LRect Client = GetClient(); Client = Tools.PaintBorder(pDC, Client); Tools.PaintContent(pDC, Client); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; c->OnPaint(pDC); } #if 0 // DEBUG_DRAW_CELLS pDC->Colour(LColour(255, 0, 0)); pDC->Box(); #endif #if DEBUG_DRAW_CELLS #if defined(DEBUG_LAYOUT) if (GetId() == DEBUG_LAYOUT) #endif { pDC->LineStyle(LSurface::LineDot); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; LRect r = c->Pos; pDC->Colour(c->Debug ? Rgb24(255, 222, 0) : Rgb24(192, 192, 222), 24); pDC->Box(&r); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); } pDC->LineStyle(LSurface::LineSolid); } #endif } bool LTableLayout::GetVariant(const char *Name, LVariant &Value, const char *Array) { return false; } bool ConvertNumbers(LArray &a, char *s) { for (auto &i: LString(s).SplitDelimit(",")) a.Add(i.Float()); return a.Length() > 0; } bool LTableLayout::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case TableLayoutCols: return ConvertNumbers(d->Cols, Value.Str()); case TableLayoutRows: return ConvertNumbers(d->Rows, Value.Str()); case ObjStyle: { const char *Defs = Value.Str(); if (Defs) GetCss(true)->Parse(Defs, LCss::ParseRelaxed); break; } case TableLayoutCell: { auto Coords = LString(Array).SplitDelimit(","); if (Coords.Length() != 2) return false; auto Cx = Coords[0].Int(); auto Cy = Coords[1].Int(); TableCell *c = new TableCell(this, (int)Cx, (int)Cy); if (!c) return false; d->Cells.Add(c); if (Value.Type == GV_VOID_PTR) { LDom **d = (LDom**)Value.Value.Ptr; if (d) *d = c; } break; } default: { LAssert(!"Unsupported property."); return false; } } return true; } void LTableLayout::OnChildrenChanged(LViewI *Wnd, bool Attaching) { InvalidateLayout(); if (Attaching) return; for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; for (int n=0; nChildren.Length(); n++) { if (c->Children[n].View == Wnd) { c->Children.DeleteAt(n); return; } } } } int LTableLayout::OnNotify(LViewI *c, LNotification n) { if (n.Type == LNotifyTableLayoutRefresh) { bool hasTableParent = false; for (LViewI *p = this; p; p = p->GetParent()) { auto tbl = dynamic_cast(p); if (tbl) { hasTableParent = true; tbl->d->LayoutDirty = true; tbl->Invalidate(); break; } } if (hasTableParent) { // One of the parent controls is a table layout, which when it receives this // notification will lay this control out too... so don't do it twice. // LgiTrace("%s:%i - Ignoring LNotifyTableLayoutRefresh because hasTableParent.\n", _FL); } SendNotify(LNotifyTableLayoutRefresh); return 0; } return LLayout::OnNotify(c, n); } int64 LTableLayout::Value() { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue()) return i; } } return -1; } void LTableLayout::Value(int64 v) { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue(i == v); } } void LTableLayout::Empty(LRect *Range) { d->Empty(Range); } LLayoutCell *LTableLayout::GetCell(int cx, int cy, bool create, int colspan, int rowspan) { TableCell *c = d->GetCellAt(cx, cy); if (!c && create) { c = new TableCell(this, cx, cy); if (c) { d->LayoutDirty = true; if (colspan > 1) c->Cell.x2 += colspan - 1; if (rowspan > 1) c->Cell.y2 += rowspan - 1; d->Cells.Add(c); while (d->Cols.Length() <= c->Cell.x2) d->Cols.Add(1); while (d->Rows.Length() <= c->Cell.y2) d->Rows.Add(1); } } return c; } LStream <ableLayout::Log() { return PrintfLogger; } diff --git a/src/haiku/Layout.cpp b/src/haiku/Layout.cpp --- a/src/haiku/Layout.cpp +++ b/src/haiku/Layout.cpp @@ -1,316 +1,312 @@ /* ** FILE: Layout.cpp ** AUTHOR: Matthew Allen ** DATE: 1/12/2021 ** DESCRIPTION: Standard Views ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Notifications.h" ////////////////////////////////////////////////////////////////////////////// LLayout::LLayout() { _PourLargest = false; VScroll = 0; HScroll = 0; } LLayout::~LLayout() { DeleteObj(HScroll); DeleteObj(VScroll); } LViewI *LLayout::FindControl(int Id) { if (VScroll && VScroll->GetId() == Id) return VScroll; if (HScroll && HScroll->GetId() == Id) return HScroll; return LView::FindControl(Id); } bool LLayout::GetPourLargest() { return _PourLargest; } void LLayout::SetPourLargest(bool i) { _PourLargest = i; } bool LLayout::Pour(LRegion &r) { if (_PourLargest) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } } return false; } void LLayout::GetScrollPos(int64 &x, int64 &y) { if (HScroll) { x = HScroll->Value(); } else { x = 0; } if (VScroll) { y = VScroll->Value(); } else { y = 0; } } void LLayout::SetScrollPos(int64 x, int64 y) { if (HScroll) HScroll->Value(x); if (VScroll) VScroll->Value(y); } bool LLayout::Attach(LViewI *p) { bool Status = LView::Attach(p); AttachScrollBars(); return Status; } bool LLayout::Detach() { return LView::Detach(); } void LLayout::OnCreate() { AttachScrollBars(); OnPosChange(); } void LLayout::AttachScrollBars() { if (HScroll && !HScroll->IsAttached()) { - // LRect r = HScroll->GetPos(); + HScroll->Visible(true); HScroll->Attach(this); HScroll->SetNotify(this); } if (VScroll && !VScroll->IsAttached()) { - // LRect r = VScroll->GetPos(); + VScroll->Visible(true); VScroll->Attach(this); VScroll->SetNotify(this); } } bool LLayout::SetScrollBars(bool x, bool y) { if (x ^ (HScroll != NULL) || y ^ (VScroll != NULL)) { if (!IsAttached()) { _SetScrollBars(x, y); } else if (_SetScroll.x != x || - _SetScroll.y != y || - !_SetScroll.SentMsg) + _SetScroll.y != y || + !_SetScroll.SentMsg) { // This is to filter out masses of duplicate messages // before they have a chance to be processed. Esp important on // GTK systems where the message handling isn't very fast. _SetScroll.x = x; _SetScroll.y = y; _SetScroll.SentMsg = true; auto r = PostEvent(M_SET_SCROLL, x, y); if (!r) printf("%s:%i - sending M_SET_SCROLL(%i,%i) to myself=%s, r=%i, attached=%i.\n", _FL, x, y, GetClass(), r, IsAttached()); return r; } // Duplicate... ignore... return true; } return true; } bool LLayout::_SetScrollBars(bool x, bool y) { static bool Processing = false; if (!Processing && (((HScroll!=0) ^ x ) || ((VScroll!=0) ^ y )) ) { Processing = true; if (x) { if (!HScroll) { HScroll = new LScrollBar(IDC_HSCROLL, 0, 0, 100, 10, "LLayout->HScroll"); if (HScroll) { HScroll->SetVertical(false); HScroll->Visible(false); } } } else { DeleteObj(HScroll); } if (y) { if (!VScroll) { VScroll = new LScrollBar(IDC_VSCROLL, 0, 0, 10, 100, "LLayout->VScroll"); if (VScroll) { VScroll->Visible(false); } } } else if (VScroll) { DeleteObj(VScroll); } AttachScrollBars(); OnPosChange(); Invalidate(); Processing = false; } return true; } int LLayout::OnNotify(LViewI *c, LNotification n) { return LView::OnNotify(c, n.Type); } void LLayout::OnPosChange() { LRect r = LView::GetClient(); - LRect v(r.x2-LScrollBar::SCROLL_BAR_SIZE+1, r.y1, r.x2, r.y2); - LRect h(r.x1, r.y2-LScrollBar::SCROLL_BAR_SIZE+1, r.x2, r.y2); + auto Px = LScrollBar::GetScrollSize(); + LRect v(r.x2-Px+1, r.y1, r.x2, r.y2); + LRect h(r.x1, r.y2-Px+1, r.x2, r.y2); if (VScroll && HScroll) { h.x2 = v.x1 - 1; v.y2 = h.y1 - 1; } if (VScroll) { - // printf("%s.OnPos %s\n", GetClass(), v.GetStr()); - /* - v.Offset(-4, 0); - v.y2 -= 2; - */ VScroll->Visible(true); VScroll->SetPos(v, true); } if (HScroll) { HScroll->Visible(true); HScroll->SetPos(h, true); } } void LLayout::OnNcPaint(LSurface *pDC, LRect &r) { LView::OnNcPaint(pDC, r); if (VScroll && VScroll->Visible()) { r.x2 -= VScroll->X(); } if (HScroll && HScroll->Visible()) { r.y2 -= HScroll->Y(); } if (VScroll && VScroll->Visible() && HScroll && HScroll->Visible()) { // Draw square at the end of each scroll bar LRect s( VScroll->GetPos().x1, HScroll->GetPos().y1, VScroll->GetPos().x2, HScroll->GetPos().y2); pDC->Colour(L_MED); pDC->Rectangle(&s); } } LRect &LLayout::GetClient(bool ClientSpace) { static LRect r; r = LView::GetClient(ClientSpace); if (VScroll && VScroll->Visible()) { // printf("\tLayout.GetCli r=%s -> ", r.GetStr()); r.x2 = VScroll->GetPos().x1 - 1; // printf("%s\n", r.GetStr()); } if (HScroll && HScroll->Visible()) r.y2 = HScroll->GetPos().y1 - 1; return r; } LMessage::Param LLayout::OnEvent(LMessage *Msg) { if (Msg->Msg() == M_SET_SCROLL) { _SetScroll.SentMsg = false; // printf("%s:%i - receiving M_SET_SCROLL myself=%s.\n", _FL, GetClass()); _SetScrollBars(Msg->A(), Msg->B()); if (HScroll) HScroll->SendNotify(LNotifyScrollBarCreate); if (VScroll) VScroll->SendNotify(LNotifyScrollBarCreate); return 0; } int Status = LView::OnEvent(Msg); if (Msg->Msg() == M_CHANGE && Status == -1 && GetParent()) { Status = GetParent()->OnEvent(Msg); } return Status; } diff --git a/src/haiku/View.cpp b/src/haiku/View.cpp --- a/src/haiku/View.cpp +++ b/src/haiku/View.cpp @@ -1,1117 +1,1119 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku LView Implementation ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/Css.h" #include "ViewPriv.h" #include #define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #endif struct CursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32_t CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { LAppInst->Yield(); } void *IsAttached(BView *v) { auto pview = v->Parent(); auto pwnd = v->Window(); return pwnd ? (void*)pwnd : (void*)pview; } bool LgiIsKeyDown(int Key) { LAssert(0); return false; } LKey::LKey(int Vkey, uint32_t flags) { vkey = Vkey; Flags = flags; IsChar = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// template struct LBView : public Parent { LViewPrivate *d = NULL; static uint32 MouseButtons; LBView(LViewPrivate *priv) : d(priv), Parent ( "", B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS ) { Parent::SetName(d->View->GetClass()); } ~LBView() { if (d) d->Hnd = NULL; } void AttachedToWindow() { if (d) d->View->OnCreate(); } LKey ConvertKey(const char *bytes, int32 numBytes) { LKey k; uint8_t *utf = (uint8_t*)bytes; ssize_t len = numBytes; auto w = LgiUtf8To32(utf, len); key_info KeyInfo; if (get_key_info(&KeyInfo) == B_OK) { k.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); k.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); k.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); k.System(TestFlag(KeyInfo.modifiers, B_COMMAND_KEY)); } #if 0 LString::Array a; for (int i=0; iCurrentMessage(); if (bmsg) { int32 key = 0; if (bmsg->FindInt32("key", &key) == B_OK) { // Translate the function keys into the LGI address space... switch (key) { case B_F1_KEY: w = LK_F1; break; case B_F2_KEY: w = LK_F2; break; case B_F3_KEY: w = LK_F3; break; case B_F4_KEY: w = LK_F4; break; case B_F5_KEY: w = LK_F5; break; case B_F6_KEY: w = LK_F6; break; case B_F7_KEY: w = LK_F7; break; case B_F8_KEY: w = LK_F8; break; case B_F9_KEY: w = LK_F9; break; case B_F10_KEY: w = LK_F10; break; case B_F11_KEY: w = LK_F11; break; case B_F12_KEY: w = LK_F12; break; default: printf("%s:%i - Upsupported key %i.\n", _FL, key); break; } } else printf("%s:%i - No 'key' in BMessage.\n", _FL); } else printf("%s:%i - No BMessage.\n", _FL); } k.c16 = k.vkey = w; } k.IsChar = !( k.System() || k.Alt() ) && ( (k.c16 >= ' ' && k.c16 < LK_DELETE) || k.c16 == LK_BACKSPACE || k.c16 == LK_TAB || k.c16 == LK_RETURN ); return k; } void KeyDown(const char *bytes, int32 numBytes) { if (!d) return; auto k = ConvertKey(bytes, numBytes); k.Down(true); auto wnd = d->View->GetWindow(); if (wnd) wnd->HandleViewKey(d->View, k); else d->View->OnKey(k); } void KeyUp(const char *bytes, int32 numBytes) { if (!d) return; auto k = ConvertKey(bytes, numBytes); auto wnd = d->View->GetWindow(); if (wnd) wnd->HandleViewKey(d->View, k); else d->View->OnKey(k); } void FrameMoved(BPoint newPosition) { if (!d) return; d->View->Pos = Parent::Frame(); d->View->OnPosChange(); } void FrameResized(float newWidth, float newHeight) { if (!d) return; d->View->Pos = Parent::Frame(); d->View->OnPosChange(); } void MessageReceived(BMessage *message) { if (!d) return; void *v = NULL; if (message->FindPointer(LMessage::PropView, &v) == B_OK) { // Proxy'd event for child view... ((LView*)v)->OnEvent((LMessage*)message); return; } else d->View->OnEvent((LMessage*)message); if (message->what == B_MOUSE_WHEEL_CHANGED) { float x = 0.0f, y = 0.0f; message->FindFloat("be:wheel_delta_x", &x); message->FindFloat("be:wheel_delta_y", &y); d->View->OnMouseWheel(y * 3.0f); } else if (message->what == M_SET_SCROLL) { return; } Parent::MessageReceived(message); } void Draw(BRect updateRect) { if (!d) return; LScreenDC dc(d->View); LPoint off(0,0); d->View->_Paint(&dc, &off, NULL); } LMouse ConvertMouse(BPoint where, bool down = false) { LMouse m; BPoint loc; uint32 buttons = 0; m.Target = d->View; m.x = where.x; m.y = where.y; if (down) { m.Down(true); Parent::GetMouse(&loc, &buttons, false); MouseButtons = buttons; // save for up click } else { buttons = MouseButtons; } if (buttons & B_PRIMARY_MOUSE_BUTTON) m.Left(true); if (buttons & B_TERTIARY_MOUSE_BUTTON) m.Middle(true); if (buttons & B_SECONDARY_MOUSE_BUTTON) m.Right(true); uint32 mod = modifiers(); if (mod & B_SHIFT_KEY) m.Shift(true); if (mod & B_OPTION_KEY) m.Alt(true); if (mod & B_CONTROL_KEY) m.Ctrl(true); if (mod & B_COMMAND_KEY) m.System(true); return m; } void MouseDown(BPoint where) { if (!d) return; static uint64_t lastClick = 0; bigtime_t interval = 0; status_t r = get_click_speed(&interval); auto now = LCurrentTime(); bool doubleClick = now-lastClick < (interval/1000); lastClick = now; LMouse m = ConvertMouse(where, true); m.Double(doubleClick); d->View->_Mouse(m, false); } void MouseUp(BPoint where) { if (!d) return; LMouse m = ConvertMouse(where); m.Down(false); d->View->_Mouse(m, false); } void MouseMoved(BPoint where, uint32 code, const BMessage *dragMessage) { if (!d) return; LMouse m = ConvertMouse(where); m.Down( m.Left() || m.Middle() || m.Right()); m.IsMove(true); d->View->_Mouse(m, true); } void MakeFocus(bool focus=true) { if (!d) return; Parent::MakeFocus(focus); d->View->OnFocus(focus); } }; template uint32 LBView::MouseButtons = 0; //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view), Hnd(new LBView(this)) { } LViewPrivate::~LViewPrivate() { View->d = NULL; if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); if (Hnd) { auto *bv = dynamic_cast*>(Hnd); // printf("%p::~LViewPrivate View=%p bv=%p th=%u\n", this, View, bv, GetCurrentThreadId()); if (bv) bv->d = NULL; auto Wnd = Hnd->Window(); if (Wnd) Wnd->LockLooper(); if (Hnd->Parent()) Hnd->RemoveSelf(); DeleteObj(Hnd); if (Wnd) Wnd->UnlockLooper(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); LLocker lck(d->Hnd, _FL); if (lck.Lock()) { d->Hnd->MakeFocus(f); lck.Unlock(); } // OnFocus will be called by the LBview handler... Invalidate(); } void LView::_Delete() { SetPulse(); // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) _Capturing = 0; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) { LAppInst->AppWnd = 0; } // Hierarchy LViewI *c; while ((c = Children[0])) { if (c->GetParent() != (LViewI*)this) { printf("%s:%i - ~LView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); // Misc Pos.ZOff(-1, -1); } LView *&LView::PopupChild() { return d->Popup; } BCursorID LgiToHaiku(LCursor c) { switch (c) { #define _(l,h) case l: return h; _(LCUR_Blank, B_CURSOR_ID_NO_CURSOR) _(LCUR_Normal, B_CURSOR_ID_SYSTEM_DEFAULT) _(LCUR_UpArrow, B_CURSOR_ID_RESIZE_NORTH) _(LCUR_DownArrow, B_CURSOR_ID_RESIZE_SOUTH) _(LCUR_LeftArrow, B_CURSOR_ID_RESIZE_WEST) _(LCUR_RightArrow, B_CURSOR_ID_RESIZE_EAST) _(LCUR_Cross, B_CURSOR_ID_CROSS_HAIR) _(LCUR_Wait, B_CURSOR_ID_PROGRESS) _(LCUR_Ibeam, B_CURSOR_ID_I_BEAM) _(LCUR_SizeVer, B_CURSOR_ID_RESIZE_NORTH_SOUTH) _(LCUR_SizeHor, B_CURSOR_ID_RESIZE_EAST_WEST) _(LCUR_SizeBDiag, B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST) _(LCUR_SizeFDiag, B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST) _(LCUR_PointingHand, B_CURSOR_ID_GRAB) _(LCUR_Forbidden, B_CURSOR_ID_NOT_ALLOWED) _(LCUR_DropCopy, B_CURSOR_ID_COPY) _(LCUR_DropMove, B_CURSOR_ID_MOVE) // _(LCUR_SizeAll, // _(LCUR_SplitV, // _(LCUR_SplitH, #undef _ } return B_CURSOR_ID_SYSTEM_DEFAULT; } bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - %s\n", _FL, m.ToString().Get()); #endif if ( GetWindow() && !GetWindow()->HandleViewMouse(this, m) ) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass()); #endif return false; } #if 0 //DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (Move) { auto *o = m.Target; if (_Over != o) { #if DEBUG_MOUSE_EVENTS // if (!o) WindowFromPoint(m.x, m.y, true); LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } int cursor = GetCursor(m.x, m.y); if (cursor >= 0) { BCursorID haikuId = LgiToHaiku((LCursor)cursor); static BCursorID curId = B_CURSOR_ID_SYSTEM_DEFAULT; if (curId != haikuId) { curId = haikuId; LLocker lck(Handle(), _FL); if (lck.Lock()) { Handle()->SetViewCursor(new BCursor(curId)); lck.Unlock(); } } } } LView *Target = NULL; if (_Capturing) Target = dynamic_cast(_Capturing); else Target = dynamic_cast(_Over ? _Over : this); if (!Target) return false; LRect Client = Target->LView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y) || _Capturing) { if (Move) Target->OnMouseMove(m); else Target->OnMouseClick(m); } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } return true; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); c.Inset(Edge, Edge); } return c; } LViewI *LView::FindControl(OsView hCtrl) { if (d->Hnd == hCtrl) { return this; } for (auto i : Children) { LViewI *Ctrl = i->FindControl(hCtrl); if (Ctrl) { return Ctrl; } } return 0; } void LView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool LView::SetPos(LRect &p, bool Repaint) { if (Pos != p) { Pos = p; LLocker lck(d->Hnd, _FL); if (lck.Lock()) { d->Hnd->ResizeTo(Pos.X(), Pos.Y()); d->Hnd->MoveTo(Pos.x1, Pos.y1); lck.Unlock(); } OnPosChange(); } return true; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { auto *ParWnd = GetWindow(); if (!ParWnd) return false; // Nothing we can do till we attach if (!InThread()) { DEBUG_INVALIDATE("%s::Invalidate out of thread\n", GetClass()); return PostEvent(M_INVALIDATE, NULL, (LMessage::Param)this); } LRect r; if (rc) { r = *rc; } else { if (Frame) r = Pos.ZeroTranslate(); else r = GetClient().ZeroTranslate(); } DEBUG_INVALIDATE("%s::Invalidate r=%s frame=%i\n", GetClass(), r.GetStr(), Frame); if (!Frame) r.Offset(_BorderSize, _BorderSize); LPoint Offset; WindowVirtualOffset(&Offset); r.Offset(Offset.x, Offset.y); DEBUG_INVALIDATE(" voffset=%i,%i = %s\n", Offset.x, Offset.y, r.GetStr()); if (!r.Valid()) { DEBUG_INVALIDATE(" error: invalid\n"); return false; } static bool Repainting = false; if (!Repainting) { Repainting = true; if (d->Hnd) { LLocker lck(d->Hnd, _FL); if (lck.Lock()) d->Hnd->Invalidate(); } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { case M_HANDLE_IN_THREAD: { LMessage::InThreadCb *Cb = NULL; if (Msg->FindPointer(LMessage::PropCallback, (void**)&Cb) == B_OK) { // printf("M_HANDLE_IN_THREAD before call..\n"); (*Cb)(); // printf("M_HANDLE_IN_THREAD after call..\n"); delete Cb; // printf("M_HANDLE_IN_THREAD after delete..\n"); } else printf("%s:%i - No Callback.\n", _FL); break; } case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CHANGE: { LViewI *Ctrl = NULL; if (GetViewById(Msg->A(), Ctrl)) { LNotification n((LNotifyType)Msg->B()); return OnNotify(Ctrl, n); } break; } case M_COMMAND: { // printf("M_COMMAND %i\n", (int)Msg->A()); return OnCommand(Msg->A(), 0, 0); } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } } return 0; } OsView LView::Handle() const { if (!d) { printf("%s:%i - No priv?\n", _FL); return NULL; } return d->Hnd; } bool LView::PointToScreen(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); // printf("p=%i,%i offset=%i,%i\n", p.x, p.y, Offset.x, Offset.y); p += Offset; // printf("p=%i,%i\n", p.x, p.y); LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } BPoint pt = Handle()->ConvertToScreen(BPoint(p.x, p.y)); // printf("pt=%g,%g\n", pt.x, pt.y); p.x = pt.x; p.y = pt.y; // printf("p=%i,%i\n\n", p.x, p.y); return true; } bool LView::PointToView(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } BPoint pt = Handle()->ConvertFromScreen(BPoint(Offset.x, Offset.y)); Offset.x = pt.x; Offset.y = pt.y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { LLocker Locker(d->Hnd, _FL); if (!Locker.Lock()) return false; // get mouse state BPoint Cursor; uint32 Buttons; d->Hnd->GetMouse(&Cursor, &Buttons); if (ScreenCoords) d->Hnd->ConvertToScreen(&Cursor); // position m.x = Cursor.x; m.y = Cursor.y; // buttons m.Left(TestFlag(Buttons, B_PRIMARY_MOUSE_BUTTON)); m.Middle(TestFlag(Buttons, B_TERTIARY_MOUSE_BUTTON)); m.Right(TestFlag(Buttons, B_SECONDARY_MOUSE_BUTTON)); // key states key_info KeyInfo; if (get_key_info(&KeyInfo) == B_OK) { m.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); m.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); m.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); } return true; } bool LView::IsAttached() { bool attached = false; LLocker lck(d->Hnd, _FL); if (lck.Lock()) { auto pview = d->Hnd->Parent(); auto pwnd = d->Hnd->Window(); attached = pview != NULL || pwnd != NULL; } return attached; } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { bool Status = false; + bool Debug = false; // !Stricmp(GetClass(), "LScrollBar"); LView *Parent = d->GetParent(); LAssert(Parent == NULL || Parent == parent); SetParent(parent); Parent = d->GetParent(); auto WndNull = _Window == NULL; _Window = Parent ? Parent->_Window : this; if (!parent) { LgiTrace("%s:%i - No parent window.\n", _FL); } else { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); auto bview = parent->Handle(); if (bview) { LLocker lck(bview, _FL); if (lck.Lock()) { - #if 0 - LgiTrace("%s:%i - Attaching %s to view %s\n", - _FL, GetClass(), parent->GetClass()); - #endif + if (Debug) + LgiTrace("%s:%i - Attaching %s to view %s\n", + _FL, GetClass(), parent->GetClass()); d->Hnd->SetName(GetClass()); if (::IsAttached(d->Hnd)) d->Hnd->RemoveSelf(); bview->AddChild(d->Hnd); d->Hnd->ResizeTo(Pos.X(), Pos.Y()); d->Hnd->MoveTo(Pos.x1, Pos.y1); - if (TestFlag(GViewFlags, GWF_VISIBLE) && d->Hnd->IsHidden()) + + bool ShowView = TestFlag(GViewFlags, GWF_VISIBLE) && d->Hnd->IsHidden(); + if (Debug) + LgiTrace("%s:%i - IsHidden=%i ShowView=%i\n", _FL, d->Hnd->IsHidden(), ShowView); + if (ShowView) d->Hnd->Show(); Status = true; - #if 0 - LgiTrace("%s:%i - Attached %s/%p to view %s/%p, success\n", - _FL, - GetClass(), d->Hnd, - parent->GetClass(), bview); - #endif + if (Debug) + LgiTrace("%s:%i - Attached %s/%p to view %s/%p, success\n", + _FL, + GetClass(), d->Hnd, + parent->GetClass(), bview); } else { - #if 0 - LgiTrace("%s:%i - Error attaching %s to view %s, can't lock.\n", - _FL, GetClass(), parent->GetClass()); - #endif + if (Debug) + LgiTrace("%s:%i - Error attaching %s to view %s, can't lock.\n", + _FL, GetClass(), parent->GetClass()); } } if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } } return Status; } bool LView::Detach() { ThreadCheck(); // Detach view if (_Window) { auto *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } LViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); Par->Invalidate(&Pos); } d->Parent = 0; d->ParentI = 0; #if 0 // Windows is not doing a deep detach... so we shouldn't either? { int Count = Children.Length(); if (Count) { int Detached = 0; LViewI *c, *prev = NULL; while ((c = Children[0])) { LAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LAssert(Count == Detached); } } #endif return true; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, };