diff --git a/Lgi.xml b/Lgi.xml
--- a/Lgi.xml
+++ b/Lgi.xml
@@ -1,605 +1,612 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
Makefile.linux
Makefile.win64
Makefile.macosx
gcc
0
-
+
./include
./private/common
./include
./private/common
./include/lgi/linux
./include/lgi/linux/Gtk
./private/linux
./include/lgi/linux
./include/lgi/linux/Gtk
./private/linux
./include/lgi/win
./private/win
./include/lgi/win
./private/win
./include/lgi/haiku
./private/haiku
./include/lgi/haiku
./private/haiku
/usr/include/libappindicator3-0.1
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gstreamer-1.0`
/usr/include/libappindicator3-0.1
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gstreamer-1.0`
+
magic
appindicator3
crypt
-static-libgcc
`pkg-config --libs gtk+-3.0`
magic
appindicator3
crypt
-static-libgcc
`pkg-config --libs gtk+-3.0`
-static-libgcc
gnu
network
be
-static-libgcc
gnu
network
be
+
lgi-gtk3
lgi-gtk3
+
DynamicLibrary
LGI_LIBRARY
LGI_LIBRARY
POSIX
_GNU_SOURCE
POSIX
_GNU_SOURCE
-
+
-
+
-
+
diff --git a/include/lgi/haiku/LgiOsClasses.h b/include/lgi/haiku/LgiOsClasses.h
--- a/include/lgi/haiku/LgiOsClasses.h
+++ b/include/lgi/haiku/LgiOsClasses.h
@@ -1,22 +1,22 @@
#ifndef __OS_CLASS_H
#define __OS_CLASS_H
class LLocker
{
protected:
BHandler *hnd = NULL;
const char *file = NULL;
int line = 0;
bool locked = false;
bool noThread = false;
public:
LLocker(BHandler *h, const char *File, int Line);
~LLocker();
- bool Lock();
+ bool Lock(bool debug = false);
status_t LockWithTimeout(int64 time);
void Unlock();
};
#endif
diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp
--- a/src/common/Lgi/ViewCommon.cpp
+++ b/src/common/Lgi/ViewCommon.cpp
@@ -1,2737 +1,2737 @@
/// \file
/// \author Matthew Allen
#ifdef LINUX
#include
#endif
#include "lgi/common/Lgi.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/TableLayout.h"
#include "lgi/common/Button.h"
#include "lgi/common/Css.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/EventTargetThread.h"
#include "lgi/common/Popup.h"
#include "lgi/common/CssTools.h"
#include "ViewPriv.h"
#if 0
#define DEBUG_CAPTURE(...) printf(__VA_ARGS__)
#else
#define DEBUG_CAPTURE(...)
#endif
//////////////////////////////////////////////////////////////////////////////////////
// Helper
LPoint lgi_view_offset(LViewI *v, bool Debug = false)
{
LPoint Offset;
for (LViewI *p = v; p; p = p->GetParent())
{
if (dynamic_cast(p))
break;
LRect pos = p->GetPos();
LRect cli = p->GetClient(false);
if (Debug)
{
const char *cls = p->GetClass();
LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n",
cls,
pos.x1, pos.y1,
Offset.x + pos.x1, Offset.y + pos.y1,
cli.GetStr());
}
Offset.x += pos.x1 + cli.x1;
Offset.y += pos.y1 + cli.y1;
}
return Offset;
}
LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug)
{
static LMouse Temp;
Temp = Info;
if (Wnd)
{
if (Debug
#if 0
|| Capturing
#endif
)
LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n",
Info.Target?Info.Target->GetClass():"",
Wnd?Wnd->GetClass():"",
Info.x, Info.y);
if (Temp.Target != Wnd)
{
if (Temp.Target)
{
auto *TargetWnd = Temp.Target->GetWindow();
auto *WndWnd = Wnd->GetWindow();
if (TargetWnd == WndWnd)
{
LPoint TargetOff = lgi_view_offset(Temp.Target, Debug);
if (Debug)
LgiTrace(" WndOffset:\n");
LPoint WndOffset = lgi_view_offset(Wnd, Debug);
Temp.x += TargetOff.x - WndOffset.x;
Temp.y += TargetOff.y - WndOffset.y;
#if 0
LRect c = Wnd->GetClient(false);
Temp.x -= c.x1;
Temp.y -= c.y1;
if (Debug)
LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1);
#endif
Temp.Target = Wnd;
}
}
else
{
LPoint Offset;
Temp.Target = Wnd;
if (Wnd->WindowVirtualOffset(&Offset))
{
LRect c = Wnd->GetClient(false);
Temp.x -= Offset.x + c.x1;
Temp.y -= Offset.y + c.y1;
}
}
}
}
LAssert(Temp.Target != NULL);
return Temp;
}
//////////////////////////////////////////////////////////////////////////////////////
// LView class methods
LViewI *LView::_Capturing = 0;
LViewI *LView::_Over = 0;
#if LGI_VIEW_HASH
struct ViewTbl : public LMutex
{
typedef LHashTbl, int> T;
private:
T Map;
public:
ViewTbl() : Map(2000), LMutex("ViewTbl")
{
}
T *Lock()
{
if (!LMutex::Lock(_FL))
return NULL;
return ⤅
}
} ViewTblInst;
bool LView::LockHandler(LViewI *v, LView::LockOp Op)
{
ViewTbl::T *m = ViewTblInst.Lock();
if (!m)
return false;
int Ref = m->Find(v);
bool Status = false;
switch (Op)
{
case OpCreate:
{
if (Ref == 0)
Status = m->Add(v, 1);
else
LAssert(!"Already exists?");
break;
}
case OpDelete:
{
if (Ref == 1)
Status = m->Delete(v);
else
LAssert(!"Either locked or missing.");
break;
}
case OpExists:
{
Status = Ref > 0;
break;
}
}
ViewTblInst.Unlock();
return Status;
}
#endif
LView::LView(OsView view)
{
#ifdef _DEBUG
_Debug = false;
#endif
d = new LViewPrivate(this);
#ifdef LGI_SDL
_View = this;
#elif LGI_VIEW_HANDLE && !defined(HAIKU)
_View = view;
#endif
Pos.ZOff(-1, -1);
WndFlags = GWF_VISIBLE;
#ifndef LGI_VIEW_HASH
#error "LGI_VIEW_HASH needs to be defined"
#elif LGI_VIEW_HASH
LockHandler(this, OpCreate);
// printf("Adding %p to hash\n", (LViewI*)this);
#endif
}
LView::~LView()
{
if (d->SinkHnd >= 0)
{
LEventSinkMap::Dispatch.RemoveSink(this);
d->SinkHnd = -1;
}
#if LGI_VIEW_HASH
LockHandler(this, OpDelete);
#endif
for (unsigned i=0; iOwner == this)
pu->Owner = NULL;
}
_Delete();
// printf("%p::~LView delete %p th=%u\n", this, d, GetCurrentThreadId());
DeleteObj(d);
// printf("%p::~LView\n", this);
}
int LView::AddDispatch()
{
if (d->SinkHnd < 0)
d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this);
return d->SinkHnd;
}
LString LView::CssStyles(const char *Set)
{
if (Set)
{
d->Styles = Set;
d->CssDirty = true;
}
return d->Styles;
}
LString::Array *LView::CssClasses()
{
return &d->Classes;
}
LArray LView::IterateViews()
{
LArray a;
for (auto c: Children)
a.Add(c);
return a;
}
bool LView::AddView(LViewI *v, int Where)
{
LAssert(!Children.HasItem(v));
bool Add = Children.Insert(v, Where);
if (Add)
{
LView *gv = v->GetGView();
if (gv && gv->_Window != _Window)
{
LAssert(!_InLock);
gv->_Window = _Window;
}
v->SetParent(this);
v->OnAttach();
OnChildrenChanged(v, true);
}
return Add;
}
bool LView::DelView(LViewI *v)
{
bool Has = Children.HasItem(v);
bool b = Children.Delete(v);
if (Has)
OnChildrenChanged(v, false);
Has = Children.HasItem(v);
LAssert(!Has);
return b;
}
bool LView::HasView(LViewI *v)
{
return Children.HasItem(v);
}
OsWindow LView::WindowHandle()
{
auto w = GetWindow();
OsWindow h;
if (w)
h = w->WindowHandle();
return h;
}
LWindow *LView::GetWindow()
{
if (!_Window)
{
// Walk up parent list and find someone who has a window
auto *w = d->GetParent();
for (; w; w = w->d ? w->d->GetParent() : NULL)
{
if (w->_Window)
{
LAssert(!_InLock);
_Window = w->_Window;
break;
}
}
}
return dynamic_cast(_Window);
}
bool LView::Lock(const char *file, int line, int TimeOut)
{
#ifdef HAIKU
- bool Debug = !Stricmp("LList", GetClass());
+ bool Debug = false;
if (!d || !d->Hnd)
{
if (Debug)
printf("%s:%i - no handle %p %p\n", _FL, d, d ? d->Hnd : NULL);
return false;
}
if (d->Hnd->Parent() == NULL)
{
if (Debug)
printf("%s:%p - Lock() no parent.\n", GetClass(), this);
return true;
}
if (TimeOut >= 0)
{
auto r = d->Hnd->LockLooperWithTimeout(TimeOut * 1000);
if (r == B_OK)
{
_InLock++;
if (Debug)
printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent());
return true;
}
printf("%s:%i - Lock(%i) failed with %x.\n", _FL, TimeOut, r);
return false;
}
auto r = d->Hnd->LockLooper();
if (r)
{
_InLock++;
if (Debug)
{
auto w = WindowHandle();
printf("%s:%p - Lock() cnt=%i myThread=%i wndThread=%i.\n",
GetClass(),
this,
_InLock,
GetCurrentThreadId(),
w ? w->Thread() : -1);
}
return true;
}
if (Debug)
printf("%s:%i - Lock(%s:%i) failed.\n", _FL, file, line);
return false;
#else
if (!_Window)
GetWindow();
_InLock++;
// LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line);
if (_Window && _Window->_Lock)
{
if (TimeOut < 0)
{
return _Window->_Lock->Lock(file, line);
}
else
{
return _Window->_Lock->LockWithTimeout(TimeOut, file, line);
}
}
return true;
#endif
}
void LView::Unlock()
{
#ifdef HAIKU
if (!d || !d->Hnd)
{
printf("%s:%i - Unlock() error, no hnd.\n", _FL);
return;
}
if (!d->Hnd->Parent())
{
// printf("%s:%p - Unlock() no parent.\n", GetClass(), this);
return;
}
if (_InLock > 0)
{
// printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock);
d->Hnd->UnlockLooper();
_InLock--;
// printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock);
}
else
{
printf("%s:%i - Unlock() without lock.\n", _FL);
}
#else
if (_Window &&
_Window->_Lock)
{
_Window->_Lock->Unlock();
}
_InLock--;
// LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock);
#endif
}
void LView::OnMouseClick(LMouse &m)
{
}
void LView::OnMouseEnter(LMouse &m)
{
}
void LView::OnMouseExit(LMouse &m)
{
}
void LView::OnMouseMove(LMouse &m)
{
}
bool LView::OnMouseWheel(double Lines)
{
return false;
}
bool LView::OnKey(LKey &k)
{
return false;
}
void LView::OnAttach()
{
List::I it = Children.begin();
for (LViewI *v = *it; v; v = *++it)
{
if (!v->GetParent())
v->SetParent(this);
}
#if 0 // defined __GTK_H__
if (_View && !DropTarget())
{
// If one of our parents is drop capable we need to set a dest here
LViewI *p;
for (p = GetParent(); p; p = p->GetParent())
{
if (p->DropTarget())
{
break;
}
}
if (p)
{
Gtk::gtk_drag_dest_set( _View,
(Gtk::GtkDestDefaults)0,
NULL,
0,
Gtk::GDK_ACTION_DEFAULT);
// printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass());
}
else
{
Gtk::gtk_drag_dest_unset(_View);
// printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass());
}
}
#endif
}
void LView::OnCreate()
{
}
void LView::OnDestroy()
{
}
void LView::OnFocus(bool f)
{
// printf("%s::OnFocus(%i)\n", GetClass(), f);
}
void LView::OnPulse()
{
}
void LView::OnPosChange()
{
}
bool LView::OnRequestClose(bool OsShuttingDown)
{
return true;
}
int LView::OnHitTest(int x, int y)
{
return -1;
}
void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching)
{
}
void LView::OnPaint(LSurface *pDC)
{
auto c = GetClient();
LCssTools Tools(this);
Tools.PaintContent(pDC, c);
}
int LView::OnNotify(LViewI *Ctrl, LNotification Data)
{
if (!Ctrl)
return 0;
if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate)
{
// Default activation is to focus the current control.
Focus(true);
}
else if (d && d->Parent)
{
// default behaviour is just to pass the
// notification up to the parent
// FIXME: eventually we need to call the 'LNotification' parent fn...
return d->Parent->OnNotify(Ctrl, Data);
}
return 0;
}
int LView::OnCommand(int Cmd, int Event, OsView Wnd)
{
return 0;
}
void LView::OnNcPaint(LSurface *pDC, LRect &r)
{
int Border = Sunken() || Raised() ? _BorderSize : 0;
if (Border == 2)
{
LEdge e;
if (Sunken())
e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge;
else
e = DefaultRaisedEdge;
#if WINNATIVE
if (d->IsThemed)
DrawThemeBorder(pDC, r);
else
#endif
LWideBorder(pDC, r, e);
}
else if (Border == 1)
{
LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge);
}
}
#if LGI_COCOA || defined(__GTK_H__)
/*
uint64 nPaint = 0;
uint64 PaintTime = 0;
*/
void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update)
{
/*
uint64 StartTs = Update ? LCurrentTime() : 0;
d->InPaint = true;
*/
// Create temp DC if needed...
LAutoPtr Local;
if (!pDC)
{
if (!Local.Reset(new LScreenDC(this)))
return;
pDC = Local;
}
#if 0
// This is useful for coverage checking
pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle();
#endif
// Non-Client drawing
LRect r;
if (Offset)
{
r = Pos;
r.Offset(Offset);
}
else
{
r = GetClient().ZeroTranslate();
}
pDC->SetClient(&r);
LRect zr1 = r.ZeroTranslate(), zr2 = zr1;
OnNcPaint(pDC, zr1);
pDC->SetClient(NULL);
if (zr2 != zr1)
{
r.x1 -= zr2.x1 - zr1.x1;
r.y1 -= zr2.y1 - zr1.y1;
r.x2 -= zr2.x2 - zr1.x2;
r.y2 -= zr2.y2 - zr1.y2;
}
LPoint o(r.x1, r.y1); // Origin of client
// Paint this view's contents...
pDC->SetClient(&r);
#if 0
if (_Debug)
{
#if defined(__GTK_H__)
Gtk::cairo_matrix_t matrix;
cairo_get_matrix(pDC->Handle(), &matrix);
double ex[4];
cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3);
ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0;
LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n",
GetClass(), r.GetStr(),
ex[0], ex[1], ex[2], ex[3],
matrix.x0, matrix.y0);
#elif LGI_COCOA
auto Ctx = pDC->Handle();
CGAffineTransform t = CGContextGetCTM(Ctx);
LRect cr = CGContextGetClipBoundingBox(Ctx);
printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n",
GetClass(),
GetPos().GetStr(),
t.a, t.b, t.c, t.d, t.tx, t.ty,
cr.GetStr(),
r.GetStr());
#endif
}
#endif
OnPaint(pDC);
pDC->SetClient(NULL);
// Paint all the children...
for (auto i : Children)
{
LView *w = i->GetGView();
if (w && w->Visible())
{
if (!w->Pos.Valid())
continue;
#if 0
if (w->_Debug)
LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y);
#endif
w->_Paint(pDC, &o);
}
}
}
#else
void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update)
{
// Create temp DC if needed...
LAutoPtr Local;
if (!pDC)
{
Local.Reset(new LScreenDC(this));
pDC = Local;
}
if (!pDC)
{
printf("%s:%i - No context to draw in.\n", _FL);
return;
}
#if 0
// This is useful for coverage checking
pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle();
#endif
bool HasClient = false;
LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client;
LPoint o;
if (Offset)
o = *Offset;
#if WINNATIVE
if (!_View)
#endif
{
// Non-Client drawing
Client = r;
OnNcPaint(pDC, Client);
HasClient = GetParent() && (Client != r);
if (HasClient)
{
Client.Offset(o.x, o.y);
pDC->SetClient(&Client);
}
}
r.Offset(o.x, o.y);
// Paint this view's contents
if (Update)
{
LRect OldClip = pDC->ClipRgn();
pDC->ClipRgn(Update);
OnPaint(pDC);
pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL);
}
else
{
OnPaint(pDC);
}
#if PAINT_VIRTUAL_CHILDREN
// Paint any virtual children
for (auto i : Children)
{
LView *w = i->GetGView();
if (w && w->Visible())
{
#if LGI_VIEW_HANDLE
if (!w->Handle())
#endif
{
LRect p = w->GetPos();
p.Offset(o.x, o.y);
if (HasClient)
p.Offset(Client.x1 - r.x1, Client.y1 - r.y1);
LPoint co(p.x1, p.y1);
// LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1);
pDC->SetClient(&p);
w->_Paint(pDC, &co);
pDC->SetClient(NULL);
}
}
}
#endif
if (HasClient)
pDC->SetClient(0);
}
#endif
LViewI *LView::GetParent()
{
ThreadCheck();
return d ? d->Parent : NULL;
}
void LView::SetParent(LViewI *p)
{
ThreadCheck();
d->Parent = p ? p->GetGView() : NULL;
d->ParentI = p;
}
void LView::SendNotify(LNotification note)
{
LViewI *n = d->Notify ? d->Notify : d->Parent;
if (n)
{
if (
#if LGI_VIEW_HANDLE && !defined(HAIKU)
!_View ||
#endif
InThread())
{
n->OnNotify(this, note);
}
else
{
// We are not in the same thread as the target window. So we post a message
// across to the view.
if (GetId() <= 0)
{
// We are going to generate a control Id to help the receiver of the
// M_CHANGE message find out view later on. The reason for doing this
// instead of sending a pointer to the object, is that the object
// _could_ be deleted between the message being sent and being received.
// Which would result in an invalid memory access on that object.
LViewI *p = GetWindow();
if (!p)
{
// No window? Find the top most parent we can...
p = this;
while (p->GetParent())
p = p->GetParent();
}
if (p)
{
// Give the control a valid ID
int i;
for (i=10; i<1000; i++)
{
if (!p->FindControl(i))
{
printf("Giving the ctrl '%s' the id '%i' for SendNotify\n",
GetClass(),
i);
SetId(i);
break;
}
}
}
else
{
// Ok this is really bad... go random (better than nothing)
SetId(5000 + LRand(2000));
}
}
LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise
// the receiver will never be able to find our object.
// printf("Post M_CHANGE %i %i\n", GetId(), Data);
n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note));
}
}
}
LViewI *LView::GetNotify()
{
ThreadCheck();
return d->Notify;
}
void LView::SetNotify(LViewI *p)
{
ThreadCheck();
d->Notify = p;
}
#define ADJ_LEFT 1
#define ADJ_RIGHT 2
#define ADJ_UP 3
#define ADJ_DOWN 4
int IsAdjacent(LRect &a, LRect &b)
{
if ( (a.x1 == b.x2 + 1) &&
!(a.y1 > b.y2 || a.y2 < b.y1))
{
return ADJ_LEFT;
}
if ( (a.x2 == b.x1 - 1) &&
!(a.y1 > b.y2 || a.y2 < b.y1))
{
return ADJ_RIGHT;
}
if ( (a.y1 == b.y2 + 1) &&
!(a.x1 > b.x2 || a.x2 < b.x1))
{
return ADJ_UP;
}
if ( (a.y2 == b.y1 - 1) &&
!(a.x1 > b.x2 || a.x2 < b.x1))
{
return ADJ_DOWN;
}
return 0;
}
LRect JoinAdjacent(LRect &a, LRect &b, int Adj)
{
LRect t;
switch (Adj)
{
case ADJ_LEFT:
case ADJ_RIGHT:
{
t.y1 = MAX(a.y1, b.y1);
t.y2 = MIN(a.y2, b.y2);
t.x1 = MIN(a.x1, b.x1);
t.x2 = MAX(a.x2, b.x2);
break;
}
case ADJ_UP:
case ADJ_DOWN:
{
t.y1 = MIN(a.y1, b.y1);
t.y2 = MAX(a.y2, b.y2);
t.x1 = MAX(a.x1, b.x1);
t.x2 = MIN(a.x2, b.x2);
break;
}
}
return t;
}
LRect *LView::FindLargest(LRegion &r)
{
ThreadCheck();
int Pixels = 0;
LRect *Best = 0;
static LRect Final;
// do initial run through the list to find largest single region
for (LRect *i = r.First(); i; i = r.Next())
{
int Pix = i->X() * i->Y();
if (Pix > Pixels)
{
Pixels = Pix;
Best = i;
}
}
if (Best)
{
Final = *Best;
Pixels = Final.X() * Final.Y();
int LastPixels = Pixels;
LRect LastRgn = Final;
int ThisPixels = Pixels;
LRect ThisRgn = Final;
LRegion TempList;
for (LRect *i = r.First(); i; i = r.Next())
{
TempList.Union(i);
}
TempList.Subtract(Best);
do
{
LastPixels = ThisPixels;
LastRgn = ThisRgn;
// search for adjoining rectangles that maybe we can add
for (LRect *i = TempList.First(); i; i = TempList.Next())
{
int Adj = IsAdjacent(ThisRgn, *i);
if (Adj)
{
LRect t = JoinAdjacent(ThisRgn, *i, Adj);
int Pix = t.X() * t.Y();
if (Pix > ThisPixels)
{
ThisPixels = Pix;
ThisRgn = t;
TempList.Subtract(i);
}
}
}
} while (LastPixels < ThisPixels);
Final = ThisRgn;
}
else return 0;
return &Final;
}
LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy)
{
ThreadCheck();
int X = 1000000;
int Y = 1000000;
LRect *Best = 0;
for (LRect *i = r.First(); i; i = r.Next())
{
if ((i->X() >= Sx && i->Y() >= Sy) &&
(i->X() < X || i->Y() < Y))
{
X = i->X();
Y = i->Y();
Best = i;
}
}
return Best;
}
LRect *LView::FindLargestEdge(LRegion &r, int Edge)
{
LRect *Best = 0;
ThreadCheck();
for (LRect *i = r.First(); i; i = r.Next())
{
if (!Best)
{
Best = i;
}
if
(
((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1))
||
((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2))
||
((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2))
||
((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1))
)
{
Best = i;
}
if
(
(
((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1))
||
((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2))
)
&&
(
i->X() > Best->X()
)
)
{
Best = i;
}
if
(
(
((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2))
||
((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1))
)
&&
(
i->Y() > Best->Y()
)
)
{
Best = i;
}
}
return Best;
}
LViewI *LView::FindReal(LPoint *Offset)
{
ThreadCheck();
if (Offset)
{
Offset->x = 0;
Offset->y = 0;
}
#if !LGI_VIEW_HANDLE
LViewI *w = GetWindow();
#endif
LViewI *p = d->Parent;
while (p &&
#if !LGI_VIEW_HANDLE
p != w
#else
!p->Handle()
#endif
)
{
if (Offset)
{
Offset->x += Pos.x1;
Offset->y += Pos.y1;
}
p = p->GetParent();
}
if (p &&
#if !LGI_VIEW_HANDLE
p == w
#else
p->Handle()
#endif
)
{
return p;
}
return NULL;
}
bool LView::HandleCapture(LView *Wnd, bool c)
{
ThreadCheck();
DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd));
if (c)
{
if (_Capturing == Wnd)
{
DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0);
}
else
{
DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0);
_Capturing = Wnd;
#if defined(__GTK_H__)
if (d->CaptureThread)
d->CaptureThread->Cancel();
d->CaptureThread = new LCaptureThread(this);
#elif WINNATIVE
LPoint Offset;
LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset);
HWND h = v ? v->Handle() : NULL;
if (h)
SetCapture(h);
else
LAssert(0);
#elif defined(LGI_SDL)
#if SDL_VERSION_ATLEAST(2, 0, 4)
SDL_CaptureMouse(SDL_TRUE);
#else
LAppInst->CaptureMouse(true);
#endif
#endif
}
}
else if (_Capturing)
{
DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0);
_Capturing = NULL;
#if defined(__GTK_H__)
if (d->CaptureThread)
{
d->CaptureThread->Cancel();
d->CaptureThread = NULL; // It'll delete itself...
}
#elif WINNATIVE
ReleaseCapture();
#elif defined(LGI_SDL)
#if SDL_VERSION_ATLEAST(2, 0, 4)
SDL_CaptureMouse(SDL_FALSE);
#else
LAppInst->CaptureMouse(false);
#endif
#endif
}
return true;
}
bool LView::IsCapturing()
{
// DEBUG_CAPTURE("%s::IsCapturing()=%p==%p==%i\n", GetClass(), _Capturing, this, (int)(_Capturing == this));
return _Capturing == this;
}
bool LView::Capture(bool c)
{
ThreadCheck();
DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass(), c);
return HandleCapture(this, c);
}
bool LView::Enabled()
{
ThreadCheck();
#if WINNATIVE
if (_View)
return IsWindowEnabled(_View) != 0;
#endif
return !TestFlag(GViewFlags, GWF_DISABLED);
}
void LView::Enabled(bool i)
{
ThreadCheck();
if (!i) SetFlag(GViewFlags, GWF_DISABLED);
else ClearFlag(GViewFlags, GWF_DISABLED);
#if LGI_VIEW_HANDLE && !defined(HAIKU)
if (_View)
{
#if WINNATIVE
EnableWindow(_View, i);
#elif defined LGI_CARBON
if (i)
{
OSStatus e = EnableControl(_View);
if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e);
}
else
{
OSStatus e = DisableControl(_View);
if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e);
}
#endif
}
#endif
Invalidate();
}
bool LView::Visible()
{
// This is a read only operation... which is kinda thread-safe...
// ThreadCheck();
#if WINNATIVE
if (_View)
/* This takes into account all the parent windows as well...
Which is kinda not what I want. I want this to reflect just
this window.
return IsWindowVisible(_View);
*/
return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0;
#endif
return TestFlag(GViewFlags, GWF_VISIBLE);
}
void LView::Visible(bool v)
{
ThreadCheck();
bool HasChanged = TestFlag(GViewFlags, GWF_VISIBLE) ^ v;
if (v) SetFlag(GViewFlags, GWF_VISIBLE);
else ClearFlag(GViewFlags, GWF_VISIBLE);
#if defined(HAIKU)
LLocker lck(d->Hnd, _FL);
if (!IsAttached() || lck.Lock())
{
const int attempts = 3;
// printf("%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden());
if (v)
{
bool parentHidden = false;
for (auto p = d->Hnd->Parent(); p; p = p->Parent())
{
if (p->IsHidden())
{
parentHidden = true;
break;
}
}
if (!parentHidden) // Don't try and show if one of the parent's is hidden.
{
for (int i=0; iHnd->IsHidden(); i++)
{
// printf("\t%p Show\n", this);
d->Hnd->Show();
}
if (d->Hnd->IsHidden())
{
printf("%s:%i - Failed to show %s.\n", _FL, GetClass());
for (auto p = d->Hnd->Parent(); p; p = p->Parent())
printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden());
}
}
}
else
{
for (int i=0; iHnd->IsHidden(); i++)
{
// printf("\t%p Hide\n", this);
d->Hnd->Hide();
}
if (!d->Hnd->IsHidden())
{
printf("%s:%i - Failed to hide %s.\n", _FL, GetClass());
for (auto p = d->Hnd->Parent(); p; p = p->Parent())
printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden());
}
}
// printf("\t%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden());
}
else LgiTrace("%s:%i - Can't lock.\n", _FL);
#elif LGI_VIEW_HANDLE
if (_View)
{
#if WINNATIVE
ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE);
#elif LGI_COCOA
LAutoPool Pool;
[_View.p setHidden:!v];
#elif LGI_CARBON
Boolean is = HIViewIsVisible(_View);
if (v != is)
{
OSErr e = HIViewSetVisible(_View, v);
if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n",
_FL, _View, v, e, GetClass());
}
#endif
}
else
#endif
{
if (HasChanged)
Invalidate();
}
}
bool LView::Focus()
{
ThreadCheck();
bool Has = false;
#if defined(__GTK_H__)
LWindow *w = GetWindow();
if (w)
{
bool Active = w->IsActive();
if (Active)
Has = w->GetFocus() == static_cast(this);
}
#elif defined(HAIKU)
LLocker lck(d->Hnd, _FL);
if (lck.Lock())
{
Has = d->Hnd->IsFocus();
lck.Unlock();
}
#elif defined(WINNATIVE)
if (_View)
{
HWND hFocus = GetFocus();
Has = hFocus == _View;
}
#elif LGI_COCOA
Has = TestFlag(WndFlags, GWF_FOCUS);
#elif LGI_CARBON
LWindow *w = GetWindow();
if (w)
{
ControlRef Cur;
OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur);
if (e)
LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e);
else
Has = (Cur == _View);
}
#endif
#if !LGI_CARBON
if (Has)
SetFlag(WndFlags, GWF_FOCUS);
else
ClearFlag(WndFlags, GWF_FOCUS);
#endif
return Has;
}
void LView::Focus(bool i)
{
ThreadCheck();
if (i)
SetFlag(WndFlags, GWF_FOCUS);
else
ClearFlag(WndFlags, GWF_FOCUS);
auto *Wnd = GetWindow();
if (Wnd)
{
Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus);
}
#if LGI_VIEW_HANDLE && !defined(HAIKU)
if (_View)
#endif
{
#if defined(HAIKU)
_Focus(i);
#elif defined(LGI_SDL) || defined(__GTK_H__)
// Nop: Focus is all handled by Lgi's LWindow class.
#elif WINNATIVE
if (i)
{
HWND hCur = GetFocus();
if (hCur != _View)
{
if (In_SetWindowPos)
{
assert(0);
LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name());
}
SetFocus(_View);
}
}
else
{
if (In_SetWindowPos)
{
assert(0);
LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow());
}
SetFocus(GetDesktopWindow());
}
#elif defined LGI_CARBON
LViewI *Wnd = GetWindow();
if (Wnd && i)
{
OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1);
if (e)
{
// e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart);
// if (e)
{
HIViewRef p = HIViewGetSuperview(_View);
// errCouldntSetFocus
printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p);
}
}
// else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass());
}
else printf("%s:%i - no window?\n", _FL);
#endif
}
}
LDragDropSource *LView::DropSource(LDragDropSource *Set)
{
if (Set)
d->DropSource = Set;
return d->DropSource;
}
LDragDropTarget *LView::DropTarget(LDragDropTarget *Set)
{
if (Set)
d->DropTarget = Set;
return d->DropTarget;
}
#if defined LGI_CARBON
extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData);
#endif
#if defined __GTK_H__
// Recursively add drag dest to all view and all children
bool GtkAddDragDest(LViewI *v, bool IsTarget)
{
if (!v) return false;
LWindow *w = v->GetWindow();
if (!w) return false;
auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget);
if (IsTarget)
{
Gtk::gtk_drag_dest_set( wid,
(Gtk::GtkDestDefaults)0,
NULL,
0,
Gtk::GDK_ACTION_DEFAULT);
}
else
{
Gtk::gtk_drag_dest_unset(wid);
}
for (LViewI *c: v->IterateViews())
GtkAddDragDest(c, IsTarget);
return true;
}
#endif
bool LView::DropTarget(bool t)
{
ThreadCheck();
bool Status = false;
if (t) SetFlag(GViewFlags, GWF_DROP_TARGET);
else ClearFlag(GViewFlags, GWF_DROP_TARGET);
#if WINNATIVE
if (_View)
{
if (t)
{
if (!d->DropTarget)
DragAcceptFiles(_View, t);
else
Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK;
}
else
{
if (_View && d->DropTarget)
Status = RevokeDragDrop(_View) == S_OK;
}
}
#elif defined MAC && !defined(LGI_SDL)
LWindow *Wnd = dynamic_cast(GetWindow());
if (Wnd)
{
Wnd->SetDragHandlers(t);
if (!d->DropTarget)
d->DropTarget = t ? Wnd : 0;
}
#if LGI_COCOA
LWindow *w = GetWindow();
if (w)
{
OsWindow h = w->WindowHandle();
if (h)
{
NSMutableArray *a = [[NSMutableArray alloc] init];
if (a)
{
[a addObject:(NSString*)kUTTypeItem];
for (id item in NSFilePromiseReceiver.readableDraggedTypes)
[a addObject:item];
[h.p.contentView registerForDraggedTypes:a];
[a release];
}
}
}
#elif LGI_CARBON
if (t)
{
static EventTypeSpec DragEvents[] =
{
{ kEventClassControl, kEventControlDragEnter },
{ kEventClassControl, kEventControlDragWithin },
{ kEventClassControl, kEventControlDragLeave },
{ kEventClassControl, kEventControlDragReceive },
};
if (!d->DndHandler)
{
OSStatus e = ::InstallControlEventHandler( _View,
NewEventHandlerUPP(LgiViewDndHandler),
GetEventTypeCount(DragEvents),
DragEvents,
(void*)this,
&d->DndHandler);
if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e);
}
SetControlDragTrackingEnabled(_View, true);
}
else
{
SetControlDragTrackingEnabled(_View, false);
}
#endif
#elif defined __GTK_H__
Status = GtkAddDragDest(this, t);
if (Status && !d->DropTarget)
d->DropTarget = t ? GetWindow() : 0;
#endif
return Status;
}
bool LView::Sunken()
{
// ThreadCheck();
#if WINNATIVE
return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE);
#else
return TestFlag(GViewFlags, GWF_SUNKEN);
#endif
}
void LView::Sunken(bool i)
{
ThreadCheck();
#if WINNATIVE
if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE);
else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE);
if (_View)
SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle);
#else
if (i) SetFlag(GViewFlags, GWF_SUNKEN);
else ClearFlag(GViewFlags, GWF_SUNKEN);
#endif
if (i)
{
if (!_BorderSize)
_BorderSize = 2;
}
else _BorderSize = 0;
}
bool LView::Flat()
{
// ThreadCheck();
#if WINNATIVE
return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) &&
!TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE);
#else
return !TestFlag(GViewFlags, GWF_SUNKEN) &&
!TestFlag(GViewFlags, GWF_RAISED);
#endif
}
void LView::Flat(bool i)
{
ThreadCheck();
#if WINNATIVE
ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE));
#else
ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN));
#endif
}
bool LView::Raised()
{
// ThreadCheck();
#if WINNATIVE
return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE);
#else
return TestFlag(GViewFlags, GWF_RAISED);
#endif
}
void LView::Raised(bool i)
{
ThreadCheck();
#if WINNATIVE
if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE);
else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE);
#else
if (i) SetFlag(GViewFlags, GWF_RAISED);
else ClearFlag(GViewFlags, GWF_RAISED);
#endif
if (i)
{
if (!!_BorderSize)
_BorderSize = 2;
}
else _BorderSize = 0;
}
int LView::GetId()
{
// This is needed by SendNotify function which is thread safe.
// So no thread safety check here.
return d->CtrlId;
}
void LView::SetId(int i)
{
// This is needed by SendNotify function which is thread safe.
// So no thread safety check here.
d->CtrlId = i;
#if WINNATIVE
if (_View)
SetWindowLong(_View, GWL_ID, d->CtrlId);
#elif defined __GTK_H__
#elif defined MAC
#endif
}
bool LView::GetTabStop()
{
ThreadCheck();
#if WINNATIVE
return TestFlag(d->WndStyle, WS_TABSTOP);
#else
return d->TabStop;
#endif
}
void LView::SetTabStop(bool b)
{
ThreadCheck();
#if WINNATIVE
if (b)
SetFlag(d->WndStyle, WS_TABSTOP);
else
ClearFlag(d->WndStyle, WS_TABSTOP);
#else
d->TabStop = b;
#endif
}
int64 LView::GetCtrlValue(int Id)
{
ThreadCheck();
LViewI *w = FindControl(Id);
return (w) ? w->Value() : 0;
}
void LView::SetCtrlValue(int Id, int64 i)
{
ThreadCheck();
LViewI *w = FindControl(Id);
if (w) w->Value(i);
}
const char *LView::GetCtrlName(int Id)
{
ThreadCheck();
LViewI *w = FindControl(Id);
return (w) ? w->Name() : 0;
}
void LView::SetCtrlName(int Id, const char *s)
{
if (!IsAttached() || InThread())
{
if (auto w = FindControl(Id))
w->Name(s);
}
else
{
PostEvent( M_SET_CTRL_NAME,
(LMessage::Param)Id,
(LMessage::Param)new LString(s));
}
}
bool LView::GetCtrlEnabled(int Id)
{
ThreadCheck();
LViewI *w = FindControl(Id);
return (w) ? w->Enabled() : 0;
}
void LView::SetCtrlEnabled(int Id, bool Enabled)
{
if (!IsAttached() || InThread())
{
if (auto w = FindControl(Id))
w->Enabled(Enabled);
}
else
{
PostEvent( M_SET_CTRL_ENABLE,
(LMessage::Param)Id,
(LMessage::Param)Enabled);
}
}
bool LView::GetCtrlVisible(int Id)
{
ThreadCheck();
LViewI *w = FindControl(Id);
if (!w)
LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id);
return (w) ? w->Visible() : 0;
}
void LView::SetCtrlVisible(int Id, bool v)
{
if (!IsAttached() || InThread())
{
if (auto w = FindControl(Id))
w->Visible(v);
}
else
{
PostEvent( M_SET_CTRL_VISIBLE,
(LMessage::Param)Id,
(LMessage::Param)v);
}
}
bool LView::AttachChildren()
{
for (auto c: Children)
{
bool a = c->IsAttached();
if (!a)
{
if (!c->Attach(this))
{
LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass());
return false;
}
}
}
return true;
}
LFont *LView::GetFont()
{
if (!d->Font &&
d->Css &&
LResources::GetLoadStyles())
{
LFontCache *fc = LAppInst->GetFontCache();
if (fc)
{
LFont *f = fc->GetFont(d->Css);
if (f)
{
if (d->FontOwnType == GV_FontOwned)
DeleteObj(d->Font);
d->Font = f;
d->FontOwnType = GV_FontCached;
}
}
}
return d->Font ? d->Font : LSysFont;
}
void LView::SetFont(LFont *Font, bool OwnIt)
{
bool Change = d->Font != Font;
if (Change)
{
if (d->FontOwnType == GV_FontOwned)
{
LAssert(d->Font != LSysFont);
DeleteObj(d->Font);
}
d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr;
d->Font = Font;
#if WINNATIVE
if (_View)
SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0);
#endif
for (LViewI *p = GetParent(); p; p = p->GetParent())
{
LTableLayout *Tl = dynamic_cast(p);
if (Tl)
{
Tl->InvalidateLayout();
break;
}
}
Invalidate();
}
}
bool LView::IsOver(LMouse &m)
{
return (m.x >= 0) &&
(m.y >= 0) &&
(m.x < Pos.X()) &&
(m.y < Pos.Y());
}
bool LView::WindowVirtualOffset(LPoint *Offset)
{
bool Status = false;
if (Offset)
{
Offset->x = 0;
Offset->y = 0;
for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent())
{
#if !LGI_VIEW_HANDLE
auto IsWnd = dynamic_cast(Wnd);
if (!IsWnd)
#else
if (!Wnd->Handle())
#endif
{
LRect r = Wnd->GetPos();
LViewI *Par = Wnd->GetParent();
if (Par)
{
LRect c = Par->GetClient(false);
Offset->x += r.x1 + c.x1;
Offset->y += r.y1 + c.y1;
}
else
{
Offset->x += r.x1;
Offset->y += r.y1;
}
Status = true;
}
else break;
}
}
return Status;
}
LString _ViewDesc(LViewI *v)
{
LString s;
s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId());
return s;
}
LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth)
{
char Tabs[64];
if (DebugDepth)
{
memset(Tabs, 9, DebugDepth);
Tabs[DebugDepth] = 0;
LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length());
}
// We iterate over the child in reverse order because if they overlap the
// end of the list is on "top". So they should get the click or whatever
// before the the lower windows.
auto it = Children.rbegin();
int n = (int)Children.Length() - 1;
for (LViewI *c = *it; c; c = *--it)
{
LRect CPos = c->GetPos();
if (CPos.Overlap(x, y) && c->Visible())
{
LRect CClient;
CClient = c->GetClient(false);
int Ox = CPos.x1 + CClient.x1;
int Oy = CPos.y1 + CClient.y1;
if (DebugDepth)
{
LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n",
Tabs, n--,
_ViewDesc(c).Get(),
CPos.GetStr(),
CClient.GetStr(),
x, y,
x - Ox, y - Oy);
}
LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0);
if (Child)
return Child;
}
else if (DebugDepth)
{
LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y);
}
}
if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y())
{
return this;
}
return NULL;
}
LColour LView::StyleColour(int CssPropType, LColour Default, int Depth)
{
LColour c = Default;
if ((CssPropType >> 8) == LCss::TypeColor)
{
LViewI *v = this;
for (int i=0; v && iGetParent())
{
auto Style = v->GetCss();
if (Style)
{
auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType);
if (Colour)
{
if (Colour->Type == LCss::ColorRgb)
{
c.Set(Colour->Rgb32, 32);
break;
}
else if (Colour->Type == LCss::ColorTransparent)
{
c.Empty();
break;
}
}
}
if (dynamic_cast(v) ||
dynamic_cast(v))
break;
}
}
return c;
}
bool LView::InThread()
{
#if WINNATIVE
HWND Hnd = _View;
for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent())
{
Hnd = p->Handle();
}
auto CurThreadId = GetCurrentThreadId();
auto GuiThreadId = LAppInst->GetGuiThreadId();
DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId;
return CurThreadId == ViewThread;
#elif defined(HAIKU)
return true;
#else
OsThreadId Me = GetCurrentThreadId();
OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0;
#if 0
if (Gui != Me)
LgiTrace("%s:%i - Out of thread:"
#ifdef LGI_COCOA
"%llx, %llx"
#else
"%x, %x"
#endif
"\n", _FL, Gui, Me);
#endif
return Gui == Me;
#endif
}
bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t timeoutMs)
{
#ifdef LGI_SDL
return LPostEvent(this, Cmd, a, b);
#elif defined(HAIKU)
if (!d || !d->Hnd)
{
// printf("%s:%i - Bad pointers %p %p\n", _FL, d, d ? d->Hnd : NULL);
return false;
}
BWindow *bWnd = NULL;
LWindow *wnd = dynamic_cast(this);
if (wnd)
{
bWnd = wnd->WindowHandle();
}
else
{
// Look for an attached view to lock...
for (LViewI *v = this; v; v = v->GetParent())
{
auto vhnd = v->Handle();
if (vhnd && ::IsAttached(vhnd))
{
bWnd = vhnd->Window();
break;
}
}
}
BMessage m(Cmd);
auto r = m.AddInt64(LMessage::PropA, a);
if (r != B_OK)
printf("%s:%i - AddUInt64 failed.\n", _FL);
r = m.AddInt64(LMessage::PropB, b);
if (r != B_OK)
printf("%s:%i - AddUInt64 failed.\n", _FL);
r = m.AddPointer(LMessage::PropView, this);
if (r != B_OK)
printf("%s:%i - AddPointer failed.\n", _FL);
if (bWnd)
{
r = bWnd->PostMessage(&m);
if (r != B_OK)
printf("%s:%i - PostMessage failed.\n", _FL);
return r == B_OK;
}
// Not attached yet...
d->MsgQue.Add(new BMessage(m));
// printf("%s:%i - PostEvent.MsgQue=%i\n", _FL, (int)d->MsgQue.Length());
return true;
#elif WINNATIVE
if (!_View)
return false;
BOOL Res = ::PostMessage(_View, Cmd, a, b);
if (!Res)
{
auto Err = GetLastError();
int asd=0;
}
return Res != 0;
#elif !LGI_VIEW_HANDLE
return LAppInst->PostEvent(this, Cmd, a, b);
#else
if (_View)
return LPostEvent(_View, Cmd, a, b);
LAssert(0);
return false;
#endif
}
bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient)
{
if (r)
{
for (int i=0; iLength(); i++)
{
bool Last = i == r->Length()-1;
Invalidate((*r)[i], Last ? Repaint : false, NonClient);
}
return true;
}
return false;
}
LButton *FindDefault(LViewI *w)
{
LButton *But = 0;
for (auto c: w->IterateViews())
{
But = dynamic_cast(c);
if (But && But->Default())
{
break;
}
But = FindDefault(c);
if (But)
{
break;
}
}
return But;
}
bool LView::Name(const char *n)
{
LBase::Name(n);
#if LGI_VIEW_HANDLE && !defined(HAIKU)
if (_View)
{
#if WINNATIVE
auto Temp = LBase::NameW();
SetWindowTextW(_View, Temp ? Temp : L"");
#endif
}
#endif
Invalidate();
return true;
}
const char *LView::Name()
{
#if WINNATIVE
if (_View)
{
LView::NameW();
}
#endif
return LBase::Name();
}
bool LView::NameW(const char16 *n)
{
LBase::NameW(n);
#if WINNATIVE
if (_View && n)
{
auto Txt = LBase::NameW();
SetWindowTextW(_View, Txt);
}
#endif
Invalidate();
return true;
}
const char16 *LView::NameW()
{
#if WINNATIVE
if (_View)
{
int Length = GetWindowTextLengthW(_View);
if (Length > 0)
{
char16 *Buf = new char16[Length+1];
if (Buf)
{
Buf[0] = 0;
int Chars = GetWindowTextW(_View, Buf, Length+1);
Buf[Chars] = 0;
LBase::NameW(Buf);
}
DeleteArray(Buf);
}
else
{
LBase::NameW(0);
}
}
#endif
return LBase::NameW();
}
LViewI *LView::FindControl(int Id)
{
LAssert(Id != -1);
if (GetId() == Id)
{
return this;
}
for (auto c : Children)
{
LViewI *Ctrl = c->FindControl(Id);
if (Ctrl)
{
return Ctrl;
}
}
return 0;
}
LPoint LView::GetMinimumSize()
{
return d->MinimumSize;
}
void LView::SetMinimumSize(LPoint Size)
{
d->MinimumSize = Size;
bool Change = false;
LRect p = Pos;
if (X() < d->MinimumSize.x)
{
p.x2 = p.x1 + d->MinimumSize.x - 1;
Change = true;
}
if (Y() < d->MinimumSize.y)
{
p.y2 = p.y1 + d->MinimumSize.y - 1;
Change = true;
}
if (Change)
{
SetPos(p);
}
}
bool LView::SetColour(LColour &c, bool Fore)
{
LCss *css = GetCss(true);
if (!css)
return false;
if (Fore)
{
if (c.IsValid())
css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32()));
else
css->DeleteProp(LCss::PropColor);
}
else
{
if (c.IsValid())
css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32()));
else
css->DeleteProp(LCss::PropBackgroundColor);
}
return true;
}
/*
bool LView::SetCssStyle(const char *CssStyle)
{
if (!d->Css && !d->Css.Reset(new LCss))
return false;
const char *Defs = CssStyle;
bool b = d->Css->Parse(Defs, LCss::ParseRelaxed);
if (b && d->FontOwnType == GV_FontCached)
{
d->Font = NULL;
d->FontOwnType = GV_FontPtr;
}
return b;
}
*/
void LView::SetCss(LCss *css)
{
d->Css.Reset(css);
}
LCss *LView::GetCss(bool Create)
{
if (Create && !d->Css)
d->Css.Reset(new LCss);
if (d->CssDirty && d->Css)
{
const char *Defs = d->Styles;
if (d->Css->Parse(Defs, LCss::ParseRelaxed))
d->CssDirty = false;
}
return d->Css;
}
LPoint &LView::GetWindowBorderSize()
{
static LPoint s;
ZeroObj(s);
#if WINNATIVE
if (_View)
{
RECT Wnd, Client;
GetWindowRect(Handle(), &Wnd);
GetClientRect(Handle(), &Client);
s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left);
s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top);
}
#elif defined __GTK_H__
#elif defined MAC
s.x = 0;
s.y = 22;
#endif
return s;
}
#ifdef _DEBUG
#if defined(LGI_CARBON)
void DumpHiview(HIViewRef v, int Depth = 0)
{
char Sp[256];
memset(Sp, ' ', Depth << 2);
Sp[Depth<<2] = 0;
printf("%sHIView=%p", Sp, v);
if (v)
{
Boolean vis = HIViewIsVisible(v);
Boolean en = HIViewIsEnabled(v, NULL);
HIRect pos;
HIViewGetFrame(v, &pos);
char cls[128];
ZeroObj(cls);
GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls);
printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s",
vis, en,
pos.origin.x, pos.origin.y, pos.size.width, pos.size.height,
cls);
}
printf("\n");
for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c))
{
DumpHiview(c, Depth + 1);
}
}
#elif defined(__GTK_H__)
void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL)
{
using namespace Gtk;
if (!w)
return;
char Sp[65] = {0};
if (Depth)
memset(Sp, ' ', *((int*)Depth)*2);
auto *Obj = G_OBJECT(w);
LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI");
GtkAllocation a;
gtk_widget_get_allocation(w, &a);
LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height);
if (GTK_IS_CONTAINER(w))
{
auto *c = GTK_CONTAINER(w);
if (c)
{
int Next = Depth ? *((int*)Depth)+1 : 1;
gtk_container_foreach(c, DumpGtk, &Next);
}
}
}
#elif defined(HAIKU) || defined(WINDOWS)
template
void _Dump(int Depth, T v)
{
LString Sp, Name;
Sp.Length(Depth<<1);
memset(Sp.Get(), ' ', Depth<<1);
Sp.Get()[Depth<<1] = 0;
#if defined(HAIKU)
LRect Frame = v->Frame();
Name = v->Name();
bool IsHidden = v->IsHidden();
#else
RECT rc; GetWindowRect(v, &rc);
LRect Frame = rc;
wchar_t txt[256];
GetWindowTextW(v, txt, CountOf(txt));
Name = txt;
bool IsHidden = !(GetWindowLong(v, GWL_STYLE) & WS_VISIBLE);
#endif
LgiTrace("%s%p::%s frame=%s vis=%i\n",
Sp.Get(), v, Name.Get(), Frame.GetStr(), !IsHidden);
#if defined(HAIKU)
for (int32 i=0; iCountChildren(); i++)
_Dump(Depth + 1, v->ChildAt(i));
#else
for (auto h = GetWindow(v, GW_CHILD); h; h = GetWindow(h, GW_HWNDNEXT))
_Dump(Depth + 1, h);
#endif
}
#endif
void LView::_Dump(int Depth)
{
char Sp[65] = {0};
memset(Sp, ' ', Depth*2);
#if 0
char s[256];
sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View);
LgiTrace(s);
List::I i = Children.Start();
for (LViewI *c = *i; c; c = *++i)
{
LView *v = c->GetGView();
if (v)
v->_Dump(Depth+1);
}
#elif defined(LGI_CARBON)
DumpHiview(_View);
#elif defined(__GTK_H__)
// DumpGtk(_View);
#elif !defined(MAC)
#if defined(HAIKU)
LLocker lck(WindowHandle(), _FL);
if (lck.Lock())
#endif
::_Dump(0, WindowHandle());
#endif
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
static LArray *AllFactories = NULL;
#if defined(WIN32)
static HANDLE FactoryEvent;
#else
static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT;
static void GFactoryInitFactories()
{
AllFactories = new LArray;
}
#endif
LViewFactory::LViewFactory()
{
#if defined(WIN32)
char16 Name[64];
swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId());
HANDLE h = CreateEventW(NULL, false, false, Name);
DWORD err = GetLastError();
if (err != ERROR_ALREADY_EXISTS)
{
FactoryEvent = h;
AllFactories = new LArray;
}
else
{
LAssert(AllFactories != NULL);
}
#else
pthread_once(&FactoryOnce, GFactoryInitFactories);
#endif
if (AllFactories)
AllFactories->Add(this);
}
LViewFactory::~LViewFactory()
{
if (AllFactories)
{
AllFactories->Delete(this);
if (AllFactories->Length() == 0)
{
DeleteObj(AllFactories);
#if defined(WIN32)
CloseHandle(FactoryEvent);
#endif
}
}
}
LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text)
{
if (ValidStr(Class) && AllFactories)
{
for (int i=0; iLength(); i++)
{
LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text);
if (v)
{
return v;
}
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
LView::ViewEventTarget::ViewEventTarget(LView *View, int Msg)
{
LAssert(View != NULL);
view = View;
if (Msg)
Msgs.Add(Msg, true);
if (view)
view->d->EventTargets.Add(this);
}
LView::ViewEventTarget::~ViewEventTarget()
{
if (view)
view->d->EventTargets.Delete(this);
}
bool LView::ViewEventTarget::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t TimeoutMs)
{
if (view)
return view->PostEvent(Cmd, a, b, TimeoutMs);
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#if defined(__GTK_H__)
using namespace Gtk;
#include "LgiWidget.h"
#endif
void LView::Debug()
{
_Debug = true;
#if defined LGI_COCOA
d->ClassName = GetClass();
#endif
}
#endif
diff --git a/src/common/Text/TextView3.cpp b/src/common/Text/TextView3.cpp
--- a/src/common/Text/TextView3.cpp
+++ b/src/common/Text/TextView3.cpp
@@ -1,5439 +1,5442 @@
#include
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/TextView3.h"
#include "lgi/common/Input.h"
#include "lgi/common/ScrollBar.h"
#ifdef WIN32
#include
#endif
#include "lgi/common/ClipBoard.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/CssTools.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/Mail.h"
#include "lgi/common/FileSelect.h"
#include "lgi/common/Menu.h"
#include "lgi/common/DropFiles.h"
#include "ViewPriv.h"
#undef max
#ifdef _DEBUG
#define FEATURE_HILIGHT_ALL_MATCHES 1
#else
#define FEATURE_HILIGHT_ALL_MATCHES 0
#endif
#define DefaultCharset "utf-8"
#define SubtractPtr(a, b) ((a) - (b))
#define GDCF_UTF8 -1
#define POUR_DEBUG 0
#define PROFILE_POUR 0
#define PROFILE_PAINT 0
#define DRAW_LINE_BOXES 0
#define WRAP_POUR_TIMEOUT 90 // ms
#define PULSE_TIMEOUT 500 // ms
#define CURSOR_BLINK 1000 // ms
#define ALLOC_BLOCK 64
#define IDC_VS 1000
#ifdef WINDOWS
#define DOUBLE_BUFFER_PAINT 1
#endif
enum Cmds
{
IDM_COPY_URL = 100,
IDM_AUTO_INDENT,
IDM_UTF8,
IDM_PASTE_NO_CONVERT,
IDM_FIXED,
IDM_SHOW_WHITE,
IDM_HARD_TABS,
IDM_INDENT_SIZE,
IDM_TAB_SIZE,
IDM_DUMP,
IDM_RTL,
IDM_COPY_ALL,
IDM_SELECT_ALL,
#ifndef IDM_OPEN
IDM_OPEN,
#endif
#ifndef IDM_NEW
IDM_NEW,
#endif
#ifndef IDM_COPY
IDM_COPY,
#endif
#ifndef IDM_CUT
IDM_CUT,
#endif
#ifndef IDM_PASTE
IDM_PASTE,
#endif
#ifndef IDM_UNDO
IDM_UNDO,
#endif
#ifndef IDM_REDO
IDM_REDO,
#endif
};
#define PAINT_BORDER Back
#if DRAW_LINE_BOXES
#define PAINT_AFTER_LINE LColour(240, 240, 240)
#else
#define PAINT_AFTER_LINE Back
#endif
#define CODEPAGE_BASE 100
#define CONVERT_CODEPAGE_BASE 200
#if !defined(WIN32) && !defined(toupper)
#define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c))
#endif
#define THREAD_CHECK() \
if (!InThread()) \
{ \
LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \
return false; \
}
static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*";
#ifndef WINDOWS
static LArray Ctrls;
#endif
//////////////////////////////////////////////////////////////////////
class LDocFindReplaceParams3 :
public LDocFindReplaceParams,
public LMutex
{
public:
// Find/Replace History
LAutoWString LastFind;
LAutoWString LastReplace;
bool MatchCase;
bool MatchWord;
bool SelectionOnly;
bool SearchUpwards;
LDocFindReplaceParams3() : LMutex("LDocFindReplaceParams3")
{
MatchCase = false;
MatchWord = false;
SelectionOnly = false;
SearchUpwards = false;
}
};
class LTextView3Private : public LCss, public LMutex
{
public:
LTextView3 *View;
LRect rPadding;
int PourX;
bool LayoutDirty;
ssize_t DirtyStart, DirtyLen;
LColour UrlColour;
bool CenterCursor;
ssize_t WordSelectMode;
LString Eol;
LString LastError;
// If the scroll position is set before we get a scroll bar, store the index
// here and set it when the LNotifyScrollBarCreate arrives.
ssize_t VScrollCache;
// Find/Replace Params
bool OwnFindReplaceParams;
LDocFindReplaceParams3 *FindReplaceParams;
// Map buffer
ssize_t MapLen;
char16 *MapBuf;
//
// Thread safe Name(char*) impl
LString SetName;
//
#ifdef _DEBUG
LString PourLog;
#endif
LTextView3Private(LTextView3 *view) : LMutex("LTextView3Private")
{
View = view;
WordSelectMode = -1;
PourX = -1;
VScrollCache = -1;
DirtyStart = DirtyLen = 0;
UrlColour.Rgb(0, 0, 255);
LColour::GetConfigColour("colour.L_URL", UrlColour);
CenterCursor = false;
LayoutDirty = true;
rPadding.ZOff(0, 0);
MapBuf = 0;
MapLen = 0;
OwnFindReplaceParams = true;
FindReplaceParams = new LDocFindReplaceParams3;
}
~LTextView3Private()
{
if (OwnFindReplaceParams)
{
DeleteObj(FindReplaceParams);
}
DeleteArray(MapBuf);
}
void SetDirty(ssize_t Start, ssize_t Len = 0)
{
LayoutDirty = true;
DirtyStart = Start;
DirtyLen = Len;
}
void OnChange(PropType Prop)
{
if (Prop == LCss::PropPadding ||
Prop == LCss::PropPaddingLeft ||
Prop == LCss::PropPaddingRight ||
Prop == LCss::PropPaddingTop ||
Prop == LCss::PropPaddingBottom)
{
LCssTools t(this, View->GetFont());
rPadding.ZOff(0, 0);
rPadding = t.ApplyPadding(rPadding);
}
}
};
//////////////////////////////////////////////////////////////////////
enum UndoType
{
UndoDelete, UndoInsert, UndoChange
};
struct Change : public LRange
{
UndoType Type;
LArray Txt;
};
struct LTextView3Undo : public LUndoEvent
{
LTextView3 *View;
LArray Changes;
LTextView3Undo(LTextView3 *view)
{
View = view;
}
void AddChange(ssize_t At, ssize_t Len, UndoType Type)
{
Change &c = Changes.New();
c.Start = At;
c.Len = Len;
c.Txt.Add(View->Text + At, Len);
c.Type = Type;
}
void OnChange()
{
for (auto &c : Changes)
{
size_t Len = c.Len;
if (View->Text)
{
char16 *t = View->Text + c.Start;
for (size_t i=0; id->SetDirty(c.Start, c.Len);
}
}
// LUndoEvent
void ApplyChange()
{
View->UndoOn = false;
for (auto &c : Changes)
{
switch (c.Type)
{
case UndoInsert:
{
View->Insert(c.Start, c.Txt.AddressOf(), c.Len);
View->Cursor = c.Start + c.Len;
break;
}
case UndoDelete:
{
View->Delete(c.Start, c.Len);
View->Cursor = c.Start;
break;
}
case UndoChange:
{
OnChange();
break;
}
}
}
View->UndoOn = true;
View->Invalidate();
}
void RemoveChange()
{
View->UndoOn = false;
for (auto &c : Changes)
{
switch (c.Type)
{
case UndoInsert:
{
View->Delete(c.Start, c.Len);
break;
}
case UndoDelete:
{
View->Insert(c.Start, c.Txt.AddressOf(), c.Len);
break;
}
case UndoChange:
{
OnChange();
break;
}
}
View->Cursor = c.Start;
}
View->UndoOn = true;
View->Invalidate();
}
};
void LTextView3::LStyle::RefreshLayout(size_t Start, ssize_t Len)
{
View->PourText(Start, Len);
View->PourStyle(Start, Len);
}
//////////////////////////////////////////////////////////////////////
LTextView3::LTextView3( int Id,
int x, int y, int cx, int cy,
LFontType *FontType)
: ResObject(Res_Custom)
{
// init vars
LView::d->Css.Reset(d = new LTextView3Private(this));
TabSize = TAB_SIZE;
IndentSize = TAB_SIZE;
// setup window
SetId(Id);
// default options
#if WINNATIVE
CrLf = true;
SetDlgCode(DLGC_WANTALLKEYS);
#else
#endif
d->Padding(LCss::Len(LCss::LenPx, 2));
#ifdef _DEBUG
// debug times
_PourTime = 0;
_StyleTime = 0;
_PaintTime = 0;
#endif
// Data
Alloc = ALLOC_BLOCK;
Text = new char16[Alloc];
if (Text) *Text = 0;
Cursor = 0;
Size = 0;
// Display
if (FontType)
Font = FontType->Create();
else
{
LFontType Type;
if (Type.GetSystemFont("Fixed"))
Font = Type.Create();
else
printf("%s:%i - failed to create font.\n", _FL);
}
if (Font)
{
SetTabStop(true);
Underline = new LFont;
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
if (d->UrlColour.IsValid())
Underline->Fore(d->UrlColour);
Underline->Create();
}
Bold = new LFont;
if (Bold)
{
*Bold = *Font;
Bold->Bold(true);
Bold->Create();
}
OnFontChange();
}
else
{
LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType);
Font = LSysFont;
}
CursorPos.ZOff(1, LineY-1);
CursorPos.Offset(d->rPadding.x1, d->rPadding.y1);
LRect r;
r.ZOff(cx-1, cy-1);
r.Offset(x, y);
SetPos(r);
LResources::StyleElement(this);
}
LTextView3::~LTextView3()
{
#ifndef WINDOWS
Ctrls.Delete(this);
#endif
Line.DeleteObjects();
Style.Empty();
DeleteArray(TextCache);
DeleteArray(Text);
if (Font != LSysFont) DeleteObj(Font);
DeleteObj(FixedFont);
DeleteObj(Underline);
DeleteObj(Bold);
// 'd' is owned by the LView::Css auto ptr
}
char16 *LTextView3::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace)
{
if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace)
{
if (Len > d->MapLen)
{
DeleteArray(d->MapBuf);
d->MapBuf = new char16[Len + RtlTrailingSpace];
d->MapLen = Len;
}
if (d->MapBuf)
{
int n = 0;
if (RtlTrailingSpace)
{
d->MapBuf[n++] = ' ';
for (int i=0; iMapBuf[n++] = Str[i];
}
}
else if (ObscurePassword)
{
for (int i=0; iMapBuf[n++] = '*';
}
}
/*
else if (ShowWhiteSpace)
{
for (int i=0; iMapBuf[n++] = 0xb7;
}
else if (Str[i] == '\t')
{
d->MapBuf[n++] = 0x2192;
}
else
{
d->MapBuf[n++] = Str[i];
}
}
}
*/
return d->MapBuf;
}
}
return Str;
}
void LTextView3::SetFixedWidthFont(bool i)
{
if (FixedWidthFont ^ i)
{
if (i)
{
LFontType Type;
if (Type.GetSystemFont("Fixed"))
{
LFont *f = FixedFont;
FixedFont = Font;
Font = f;
if (!Font)
{
Font = Type.Create();
if (Font)
{
Font->PointSize(FixedFont->PointSize());
}
}
LDocView::SetFixedWidthFont(i);
}
}
else if (FixedFont)
{
LFont *f = FixedFont;
FixedFont = Font;
Font = f;
LDocView::SetFixedWidthFont(i);
}
OnFontChange();
Invalidate();
}
}
void LTextView3::SetReadOnly(bool i)
{
LDocView::SetReadOnly(i);
#if WINNATIVE
SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS);
#endif
}
void LTextView3::SetCrLf(bool crlf)
{
CrLf = crlf;
}
void LTextView3::SetTabSize(uint8_t i)
{
TabSize = limit(i, 2, 32);
OnFontChange();
OnPosChange();
Invalidate();
}
void LTextView3::SetWrapType(LDocWrapType i)
{
LDocView::SetWrapType(i);
CanScrollX = i != TEXTED_WRAP_REFLOW;
OnPosChange();
Invalidate();
}
LFont *LTextView3::GetFont()
{
return Font;
}
LFont *LTextView3::GetBold()
{
return Bold;
}
void LTextView3::SetFont(LFont *f, bool OwnIt)
{
if (!f)
return;
if (OwnIt)
{
if (Font != LSysFont)
DeleteObj(Font);
Font = f;
}
else if (!Font || Font == LSysFont)
{
Font = new LFont(*f);
}
else
{
*Font = *f;
}
if (Font)
{
if (!Underline)
Underline = new LFont;
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
Underline->Create();
if (d->UrlColour.IsValid())
Underline->Fore(d->UrlColour);
}
if (!Bold)
Bold = new LFont;
if (Bold)
{
*Bold = *Font;
Bold->Bold(true);
Bold->Create();
}
}
OnFontChange();
}
void LTextView3::OnFontChange()
{
if (Font)
{
// get line height
// int OldLineY = LineY;
if (!Font->Handle())
Font->Create();
LineY = Font->GetHeight();
if (LineY < 1) LineY = 1;
// get tab size
char Spaces[32];
memset(Spaces, 'A', TabSize);
Spaces[TabSize] = 0;
LDisplayString ds(Font, Spaces);
Font->TabSize(ds.X());
// repour doc
d->SetDirty(0, Size);
// validate blue underline font
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
Underline->Create();
}
#if WINNATIVE // Set the IME font.
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
COMPOSITIONFORM Cf;
Cf.dwStyle = CFS_POINT;
Cf.ptCurrentPos.x = CursorPos.x1;
Cf.ptCurrentPos.y = CursorPos.y1;
LOGFONT FontInfo;
GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo);
ImmSetCompositionFont(hIMC, &FontInfo);
ImmReleaseContext(Handle(), hIMC);
}
#endif
}
}
void LTextView3::LogLines()
{
int Idx = 0;
LgiTrace("DocSize: %i\n", (int)Size);
for (auto i : Line)
{
LgiTrace(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr());
Idx++;
}
#ifdef _DEBUG
if (d->PourLog)
LgiTrace("%s", d->PourLog.Get());
#endif
}
bool LTextView3::ValidateLines(bool CheckBox)
{
size_t Pos = 0;
char16 *c = Text;
size_t Idx = 0;
LTextLine *Prev = NULL;
for (auto i : Line)
{
LTextLine *l = i;
if (l->Start != Pos)
{
LogLines();
LAssert(!"Incorrect start.");
return false;
}
char16 *e = c;
if (WrapType == TEXTED_WRAP_NONE)
{
while (*e && *e != '\n')
e++;
}
else
{
char16 *end = Text + l->Start + l->Len;
while (*e && *e != '\n' && e < end)
e++;
}
ssize_t Len = e - c;
if (l->Len != Len)
{
LogLines();
LAssert(!"Incorrect length.");
return false;
}
if (CheckBox &&
Prev &&
Prev->r.y2 != l->r.y1 - 1)
{
LogLines();
LAssert(!"Lines not joined vertically");
}
if (*e)
{
if (*e == '\n')
e++;
else if (WrapType == TEXTED_WRAP_REFLOW)
e++;
}
Pos = e - Text;
c = e;
Idx++;
Prev = l;
}
if (WrapType == TEXTED_WRAP_NONE &&
Pos != Size)
{
LogLines();
LAssert(!"Last line != end of doc");
return false;
}
return true;
}
int LTextView3::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle)
{
int Changes = 0;
for (auto &s : Style)
{
if (s.Start == Start)
{
if (Diff < 0 || ExtendStyle)
s.Len += Diff;
else
s.Start += Diff;
Changes++;
}
else if (s.Start > Start)
{
s.Start += Diff;
Changes++;
}
}
return Changes;
}
// break array, break out of loop when we hit these chars
#define ExitLoop(c) ( (c) == 0 || \
(c) == '\n' || \
(c) == ' ' || \
(c) == '\t' \
)
// extra breaking opportunities
#define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \
( (c) >= 0x3300 && (c) <= 0x9FAF ) \
)
/*
Prerequisite:
The Line list must have either the objects with the correct Start/Len or be missing the lines altogether...
*/
void LTextView3::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */)
{
#if PROFILE_POUR
char _txt[256];
sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size);
LProfile Prof(_txt);
#endif
#if !defined(HAIKU)
LAssert(InThread());
#endif
LRect Client = GetClient();
int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2;
int Cy = 0;
MaxX = 0;
ssize_t Idx = -1;
LTextLine *Cur = GetTextLine(Start, &Idx);
// LgiTrace("Pour %i:%i Cur=%p Idx=%i\n", (int)Start, (int)Length, (int)Cur, (int)Idx);
if (!Cur || !Cur->r.Valid())
{
// Find the last line that has a valid position...
for (auto i = Idx >= 0 ? Line.begin(Idx) : Line.rbegin(); *i; i--, Idx--)
{
Cur = *i;
if (Cur->r.Valid())
{
Cy = Cur->r.y1;
if (Idx < 0)
Idx = Line.IndexOf(Cur);
break;
}
}
}
if (Cur && !Cur->r.Valid())
Cur = NULL;
if (Cur)
{
Cy = Cur->r.y1;
Start = Cur->Start;
Length = Size - Start;
// LgiTrace("Reset start to %i:%i because Cur!=NULL\n", (int)Start, (int)Length);
}
else
{
Idx = 0;
Start = 0;
Length = Size;
}
if (!Text || !Font || Mx <= 0)
return;
// Tracking vars
ssize_t e;
//int LastX = 0;
int WrapCol = GetWrapAtCol();
LDisplayString Sp(Font, " ", 1);
int WidthOfSpace = Sp.X();
if (WidthOfSpace < 1)
{
printf("%s:%i - WidthOfSpace test failed.\n", _FL);
return;
}
// Alright... lets pour!
uint64 StartTs = LCurrentTime();
if (WrapType == TEXTED_WRAP_NONE)
{
// Find the dimensions of each line that is missing a rect
#if PROFILE_POUR
Prof.Add("NoWrap: ExistingLines");
#endif
#ifdef _DEGBUG
LStringPipe Log(1024);
Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour);
#endif
ssize_t Pos = 0;
for (auto i = Line.begin(Idx); *i; i++, Idx++)
{
LTextLine *l = *i;
#ifdef _DEGBUG
Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid());
#endif
if (!l->r.Valid()) // If the layout is not valid...
{
LDisplayString ds(Font, Text + l->Start, l->Len);
l->r.x1 = d->rPadding.x1;
l->r.x2 = l->r.x1 + ds.X();
MaxX = MAX(MaxX, l->r.X());
}
// Adjust the y position anyway... it's free.
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Cy = l->r.y2 + 1;
Pos = l->Start + l->Len;
if (Text[Pos] == '\n')
Pos++;
}
// Now if we are missing lines as well, create them and lay them out
#if PROFILE_POUR
Prof.Add("NoWrap: NewLines");
#endif
while (Pos < Size)
{
LTextLine *l = new LTextLine;
l->Start = Pos;
char16 *c = Text + Pos;
char16 *e = c;
while (*e && *e != '\n')
e++;
l->Len = e - c;
#ifdef _DEGBUG
Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len);
#endif
l->r.x1 = d->rPadding.x1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
if (l->Len)
{
LDisplayString ds(Font, Text + l->Start, l->Len);
l->r.x2 = l->r.x1 + ds.X();
}
else
{
l->r.x2 = l->r.x1;
}
Line.Insert(l);
if (*e == '\n')
e++;
MaxX = MAX(MaxX, l->r.X());
Cy = l->r.y2 + 1;
Pos = e - Text;
Idx++;
}
#ifdef _DEGBUG
d->PourLog = Log.NewLStr();
#endif
PartialPour = false;
PartialPourLines = 0;
}
else // Wrap text
{
int DisplayStart = ScrollYLine();
int DisplayLines = (Client.Y() + LineY - 1) / LineY;
int DisplayEnd = DisplayStart + DisplayLines;
// Pouring is split into 2 parts...
// 1) pouring to the end of the displayed text.
// 2) pouring from there to the end of the document.
// potentially taking several goes to complete the full pour
// This allows the document to display and edit faster..
bool PourToDisplayEnd = Line.Length() < DisplayEnd;
#if 0
LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n",
Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd);
#endif
if ((ssize_t)Line.Length() > Idx)
{
for (auto i = Line.begin(Idx); *i; i++)
delete *i;
Line.Length(Idx);
Cur = NULL;
}
int Cx = 0;
ssize_t i;
for (i=Start; i= Size ||
Text[e] == '\n' ||
(e-i) >= WrapCol)
{
break;
}
e++;
}
// Seek back some characters if we are mid word
size_t OldE = e;
if (e < Size &&
Text[e] != '\n')
{
while (e > i)
{
if (ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
{
break;
}
e--;
}
}
if (e == i)
{
// No line break at all, so seek forward instead
for (e=OldE; e < Size && Text[e] != '\n'; e++)
{
if (ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
break;
}
}
// Calc the width
LDisplayString ds(Font, Text + i, e - i);
Width = ds.X();
}
else
{
// Wrap to edge of screen
ssize_t PrevExitChar = -1;
int PrevX = -1;
while (true)
{
if (e >= Size ||
ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
{
LDisplayString ds(Font, Text + i, e - i);
if (ds.X() + Cx > Mx)
{
if (PrevExitChar > 0)
{
e = PrevExitChar;
Width = PrevX;
}
else
{
Width = ds.X();
}
break;
}
else if (e >= Size ||
Text[e] == '\n')
{
Width = ds.X();
break;
}
PrevExitChar = e;
PrevX = ds.X();
}
e++;
}
}
// Create layout line
LTextLine *l = new LTextLine;
if (l)
{
l->Start = i;
l->Len = e - i;
l->r.x1 = d->rPadding.x1;
l->r.x2 = l->r.x1 + Width - 1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Line.Insert(l);
if (PourToDisplayEnd)
{
if (Line.Length() > DisplayEnd)
{
// We have reached the end of the displayed area... so
// exit out temporarily to display the layout to the user
PartialPour = true;
PartialPourLines = std::max(PartialPourLines, Line.Length());
break;
}
}
else
{
// Otherwise check if we are taking too long...
if (Line.Length() % 20 == 0)
{
uint64 Now = LCurrentTime();
if (Now - StartTs > WRAP_POUR_TIMEOUT)
{
PartialPour = true;
PartialPourLines = std::max(PartialPourLines, Line.Length());
break;
}
}
}
MaxX = MAX(MaxX, l->r.X());
Cy += LineY;
if (e < Size)
e++;
}
}
if (i >= Size)
{
PartialPour = false;
PartialPourLines = 0;
}
SendNotify(LNotifyCursorChanged);
}
#ifdef _DEBUG
// ValidateLines(true);
#endif
#if PROFILE_POUR
Prof.Add("LastLine");
#endif
if (!PartialPour)
{
auto It = Line.rbegin();
LTextLine *Last = It != Line.end() ? *It : NULL;
if (!Last ||
Last->Start + Last->Len < Size)
{
LTextLine *l = new LTextLine;
if (l)
{
l->Start = Size;
l->Len = 0;
l->r.x1 = l->r.x2 = d->rPadding.x1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Line.Insert(l);
MaxX = MAX(MaxX, l->r.X());
Cy += LineY;
}
}
}
bool ScrollYNeeded = Client.Y() < (std::max(PartialPourLines, Line.Length()) * LineY);
bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL);
d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange;
#if PROFILE_POUR
static LString _s;
_s.Printf("ScrollBars dirty=%i", d->LayoutDirty);
Prof.Add(_s);
#endif
if (ScrollChange)
{
#if 0
LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n",
_FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour);
#endif
SetScrollBars(false, ScrollYNeeded);
}
UpdateScrollBars();
#if 0 // def _DEBUG
if (GetWindow())
{
static char s[256];
sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000);
GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s);
}
#endif
#if POUR_DEBUG
printf("Lines=%i\n", Line.Length());
int Index = 0;
for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++)
{
printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe());
}
#endif
}
bool LTextView3::InsertStyle(LAutoPtr s)
{
if (!s)
return false;
LAssert(s->Start >= 0);
LAssert(s->Len > 0);
ssize_t Last = 0;
// int n = 0;
// LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr());
if (Style.Length() > 0)
{
// Optimize for last in the list
auto Last = Style.rbegin();
if (s->Start >= (ssize_t)Last->End())
{
Style.Insert(*s);
return true;
}
}
for (auto i = Style.begin(); i != Style.end(); i++)
{
if (s->Overlap(*i))
{
if (s->Owner > i->Owner)
{
// Fail the insert
return false;
}
else
{
// Replace mode...
*i = *s;
return true;
}
}
if (s->Start >= Last && s->Start < i->Start)
{
Style.Insert(*s, i);
return true;
}
}
Style.Insert(*s);
return true;
}
LTextView3::LStyle *LTextView3::GetNextStyle(StyleIter &s, ssize_t Where)
{
if (Where >= 0)
s = Style.begin();
else
s++;
while (s != Style.end())
{
// determine whether style is relevant..
// styles in the selected region are ignored
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (SelStart >= 0 &&
s->Start >= Min &&
s->Start+s->Len < Max)
{
// style is completely inside selection: ignore
s++;
}
else if (Where >= 0 &&
s->Start+s->Len < Where)
{
s++;
}
else
{
return &(*s);
}
}
return NULL;
}
#if 0
CURSOR_CHAR GetCursor()
{
#ifdef WIN32
LArray Ver;
int Os = LGetOs(&Ver);
if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) &&
Ver[0] >= 5)
{
return MAKEINTRESOURCE(32649); // hand
}
else
{
return IDC_ARROW;
}
#endif
return 0;
}
#endif
LTextView3::LStyle *LTextView3::HitStyle(ssize_t i)
{
for (auto &s : Style)
{
if (i >= s.Start && i < (ssize_t)s.End())
{
return &s;
}
}
return NULL;
}
void LTextView3::PourStyle(size_t Start, ssize_t EditSize)
{
#ifdef _DEBUG
int64 StartTime = LCurrentTime();
#endif
LAssert(InThread());
if (!Text || Size < 1)
return;
ssize_t Length = MAX(EditSize, 0);
if ((ssize_t)Start + Length >= Size)
Length = Size - Start; // For deletes, this sizes the edit length within bounds.
// Expand re-style are to word boundaries before and after the area of change
while (Start > 0 && UrlChar(Text[Start-1]))
{
// Move the start back
Start--;
Length++;
}
while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length]))
{
// Move the end back
Length++;
}
// Delete all the styles that we own inside the changed area
for (StyleIter s = Style.begin(); s != Style.end();)
{
if (s->Owner == STYLE_NONE)
{
if (EditSize > 0)
{
if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize))
{
Style.Delete(s);
continue;
}
}
else
{
if (s->Overlap(Start, -EditSize))
{
Style.Delete(s);
continue;
}
}
}
s++;
}
if (UrlDetect)
{
LArray Links;
LAssert((ssize_t)Start + Length <= Size);
if (LDetectLinks(Links, Text + Start, Length))
{
for (uint32_t i=0; i Url(new LStyle(STYLE_URL));
if (Url)
{
Url->View = this;
Url->Start = Inf.Start + Start;
Url->Len = Inf.Len;
// Url->Email = Inf.Email;
Url->Font = Underline;
Url->Fore = d->UrlColour;
InsertStyle(Url);
}
}
}
}
#ifdef _DEBUG
_StyleTime = LCurrentTime() - StartTime;
#endif
}
bool LTextView3::Insert(size_t At, const char16 *Data, ssize_t Len)
{
LProfile Prof("LTextView3::Insert");
Prof.HideResultsIfBelow(1000);
LAssert(InThread());
if (!ReadOnly && Len > 0)
{
if (!Data)
return false;
// limit input to valid data
At = MIN(Size, (ssize_t)At);
// make sure we have enough memory
size_t NewAlloc = Size + Len + 1;
NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK);
if (NewAlloc != Alloc)
{
char16 *NewText = new char16[NewAlloc];
if (NewText)
{
if (Text)
{
// copy any existing data across
memcpy(NewText, Text, (Size + 1) * sizeof(char16));
}
DeleteArray(Text);
Text = NewText;
Alloc = NewAlloc;
}
else
{
// memory allocation error
return false;
}
}
Prof.Add("MemChk");
if (Text)
{
// Insert the data
// Move the section after the insert to make space...
memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16));
Prof.Add("Cpy");
// Copy new data in...
memcpy(Text+At, Data, Len * sizeof(char16));
Size += Len;
Text[Size] = 0; // NULL terminate
Prof.Add("Undo");
// Add the undo object...
if (UndoOn)
{
LAutoPtr Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(At, Len, UndoInsert);
if (Obj)
UndoQue += Obj.Release();
}
// Clear layout info for the new text
ssize_t Idx = -1;
LTextLine *Cur = NULL;
if (Line.Length() == 0)
{
// Empty doc... set up the first line
Line.Insert(Cur = new LTextLine);
Idx = 0;
Cur->Start = 0;
}
else
{
Cur = GetTextLine(At, &Idx);
}
if (Cur)
{
if (WrapType == TEXTED_WRAP_NONE)
{
// Clear layout for current line...
Cur->r.ZOff(-1, -1);
Prof.Add("NoWrap add lines");
// Add any new lines that we need...
char16 *e = Text + At + Len;
char16 *c;
for (c = Text + At; c < e; c++)
{
if (*c == '\n')
{
// Set the size of the current line...
size_t Pos = c - Text;
Cur->Len = Pos - Cur->Start;
// Create a new line...
Cur = new LTextLine();
if (!Cur)
return false;
Cur->Start = Pos + 1;
Line.Insert(Cur, ++Idx);
}
}
Prof.Add("CalcLen");
// Make sure the last Line's length is set..
Cur->CalcLen(Text);
Prof.Add("UpdatePos");
// Now update all the positions of the following lines...
for (auto i = Line.begin(++Idx); *i; i++)
(*i)->Start += Len;
}
else
{
// Clear all lines to the end of the doc...
for (auto i = Line.begin(Idx); *i; i++)
delete *i;
Line.Length(Idx);
}
}
else
{
// If wrap is on then this can happen when an Insert happens before the
// OnPulse event has laid out the new text. Probably not a good thing in
// non-wrap mode
if (WrapType == TEXTED_WRAP_NONE)
{
LTextLine *l = *Line.rbegin();
printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n",
_FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType);
if (l)
printf("Last=%i, %i\n", (int)l->Start, (int)l->Len);
}
}
#ifdef _DEBUG
// Prof.Add("Validate");
// ValidateLines();
#endif
if (AdjustStylePos)
AdjustStyles(At, Len);
Dirty = true;
if (PourEnabled)
{
Prof.Add("PourText");
PourText(At, Len);
Prof.Add("PourStyle");
auto Start = LCurrentTime();
PourStyle(At, Len);
auto End = LCurrentTime();
if (End - Start > 1000)
{
PourStyle(At, Len);
}
}
SendNotify(LNotifyDocChanged);
return true;
}
}
return false;
}
bool LTextView3::Delete(size_t At, ssize_t Len)
{
bool Status = false;
LAssert(InThread());
if (!ReadOnly)
{
// limit input
At = MAX(At, 0);
At = MIN((ssize_t)At, Size);
Len = MIN(Size-(ssize_t)At, Len);
if (Len > 0)
{
int HasNewLine = 0;
for (int i=0; i Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(At, Len, UndoDelete);
if (Obj)
UndoQue += Obj.Release();
}
memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16));
Size -= Len;
Text[Size] = 0;
if (WrapType == TEXTED_WRAP_NONE)
{
ssize_t Idx = -1;
LTextLine *Cur = GetTextLine(At, &Idx);
if (Cur)
{
Cur->r.ZOff(-1, -1);
// Delete some lines...
for (int i=0; iCalcLen(Text);
// Shift all further lines down...
for (auto i = Line.begin(Idx + 1); *i; i++)
(*i)->Start -= Len;
}
}
else
{
ssize_t Index;
LTextLine *Cur = GetTextLine(At, &Index);
if (Cur)
{
for (auto i = Line.begin(Index); *i; i++)
delete *i;
Line.Length(Index);
}
}
Dirty = true;
Status = true;
#ifdef _DEBUG
// ValidateLines();
#endif
if (AdjustStylePos)
AdjustStyles(At, -Len);
if (PourEnabled)
{
PourText(At, -Len);
PourStyle(At, -Len);
}
if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len)
{
SetCaret(At, false, HasNewLine != 0);
}
// Handle repainting in flowed mode, when the line starts change
if (WrapType == TEXTED_WRAP_REFLOW)
{
ssize_t Index;
LTextLine *Cur = GetTextLine(At, &Index);
if (Cur)
{
LRect r = Cur->r;
r.x2 = GetClient().x2;
r.y2 = GetClient().y2;
Invalidate(&r);
}
}
SendNotify(LNotifyDocChanged);
Status = true;
}
}
return Status;
}
void LTextView3::DeleteSelection(char16 **Cut)
{
if (SelStart >= 0)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (Cut)
{
*Cut = NewStrW(Text + Min, Max - Min);
}
Delete(Min, Max - Min);
SetCaret(Min, false, true);
}
}
List::I LTextView3::GetTextLineIt(ssize_t Offset, ssize_t *Index)
{
int i = 0;
for (auto It = Line.begin(); It != Line.end(); It++)
{
auto l = *It;
if (Offset >= l->Start && Offset <= l->Start+l->Len)
{
if (Index)
*Index = i;
return It;
}
i++;
}
return Line.end();
}
int64 LTextView3::Value()
{
auto n = Name();
#ifdef _MSC_VER
return (n) ? _atoi64(n) : 0;
#else
return (n) ? atoll(n) : 0;
#endif
}
void LTextView3::Value(int64 i)
{
char Str[32];
sprintf_s(Str, sizeof(Str), LPrintfInt64, i);
Name(Str);
}
LString LTextView3::operator[](ssize_t LineIdx)
{
if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines())
return LString();
LTextLine *Ln = Line[LineIdx-1];
if (!Ln)
return LString();
LString s(Text + Ln->Start, Ln->Len);
return s;
}
const char *LTextView3::Name()
{
UndoQue.Empty();
DeleteArray(TextCache);
TextCache = WideToUtf8(Text);
return TextCache;
}
bool LTextView3::Name(const char *s)
{
if (InThread())
{
UndoQue.Empty();
DeleteArray(TextCache);
DeleteArray(Text);
Line.DeleteObjects();
Style.Empty();
LAssert(LIsUtf8(s));
Text = Utf8ToWide(s);
if (!Text)
{
Text = new char16[1];
if (Text) *Text = 0;
}
Size = Text ? StrlenW(Text) : 0;
Alloc = Size + 1;
Cursor = MIN(Cursor, Size);
if (Text)
{
// Remove '\r's
char16 *o = Text;
for (char16 *i=Text; *i; i++)
{
if (*i != '\r')
{
*o++ = *i;
}
else Size--;
}
*o++ = 0;
}
// update everything else
d->SetDirty(0, Size);
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars();
Invalidate();
}
else if (d->Lock(_FL))
{
if (IsAttached())
{
d->SetName = s;
PostEvent(M_TEXT_UPDATE_NAME);
}
else LAssert(!"Can't post event to detached/virtual window.");
d->Unlock();
}
return true;
}
const char16 *LTextView3::NameW()
{
return Text;
}
const char16 *LTextView3::TextAtLine(size_t Index)
{
if (Index >= Line.Length())
return NULL;
auto ln = Line[Index];
return Text + ln->Start;
}
bool LTextView3::NameW(const char16 *s)
{
DeleteArray(Text);
Size = s ? StrlenW(s) : 0;
Alloc = Size + 1;
Text = new char16[Alloc];
Cursor = MIN(Cursor, Size);
if (Text)
{
memcpy(Text, s, Size * sizeof(char16));
// remove LF's
int In = 0, Out = 0;
CrLf = false;
for (; InSetDirty(0, Size);
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars();
Invalidate();
return true;
}
LRange LTextView3::GetSelectionRange()
{
LRange r;
if (HasSelection())
{
r.Start = MIN(SelStart, SelEnd);
ssize_t End = MAX(SelStart, SelEnd);
r.Len = End - r.Start;
}
return r;
}
char *LTextView3::GetSelection()
{
LRange s = GetSelectionRange();
if (s.Len > 0)
{
return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) );
}
return 0;
}
bool LTextView3::HasSelection()
{
return (SelStart >= 0) && (SelStart != SelEnd);
}
void LTextView3::SelectAll()
{
SelStart = 0;
SelEnd = Size;
Invalidate();
}
void LTextView3::UnSelectAll()
{
bool Update = HasSelection();
SelStart = -1;
SelEnd = -1;
if (Update)
{
Invalidate();
}
}
size_t LTextView3::GetLines()
{
return Line.Length();
}
void LTextView3::GetTextExtent(int &x, int &y)
{
PourText(0, Size);
x = MaxX + d->rPadding.x1;
y = (int)(Line.Length() * LineY);
}
bool LTextView3::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index)
{
ssize_t FromIndex = 0;
LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex);
if (!From)
return false;
Pt.x = (int) (Cursor - From->Start);
Pt.y = (int) FromIndex;
return true;
}
ssize_t LTextView3::GetCaret(bool Cur)
{
if (Cur)
{
return Cursor;
}
return 0;
}
ssize_t LTextView3::IndexAt(int x, int y)
{
LTextLine *l = Line.ItemAt(y);
if (l)
{
return l->Start + MIN(x, l->Len);
}
return 0;
}
bool LTextView3::ScrollToOffset(size_t Off)
{
bool ForceFullUpdate = false;
ssize_t ToIndex = 0;
LTextLine *To = GetTextLine(Off, &ToIndex);
if (To)
{
LRect Client = GetClient();
int DisplayLines = Client.Y() / LineY;
if (VScroll)
{
if (ToIndex < VScroll->Value())
{
// Above the visible region...
if (d->CenterCursor)
{
ssize_t i = ToIndex - (DisplayLines >> 1);
VScroll->Value(MAX(0, i));
}
else
{
VScroll->Value(ToIndex);
}
ForceFullUpdate = true;
}
if (ToIndex >= VScroll->Value() + DisplayLines)
{
int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines;
ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines);
if (v != VScroll->Value())
{
// Below the visible region
VScroll->Value(v);
ForceFullUpdate = true;
}
}
}
else
{
d->VScrollCache = ToIndex;
}
}
return ForceFullUpdate;
}
void LTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate)
{
// int _Start = LCurrentTime();
Blink = true;
// Bound the new cursor position to the document
if ((ssize_t)i > Size) i = Size;
// Store the old selection and cursor
ssize_t s = SelStart, e = SelEnd, c = Cursor;
// If there is going to be a selected area
if (Select && i != SelStart)
{
// Then set the start
if (SelStart < 0)
{
// We are starting a new selection
SelStart = Cursor;
}
// And end
SelEnd = i;
}
else
{
// Clear the selection
SelStart = SelEnd = -1;
}
ssize_t FromIndex = 0;
LTextLine *From = GetTextLine(Cursor, &FromIndex);
Cursor = i;
// check the cursor is on the screen
ForceFullUpdate |= ScrollToOffset(Cursor);
// check whether we need to update the screen
ssize_t ToIndex = 0;
LTextLine *To = GetTextLine(Cursor, &ToIndex);
if (ForceFullUpdate ||
!To ||
!From)
{
// need full update
Invalidate();
}
else if
(
(
SelStart != s
||
SelEnd != e
)
)
{
// Update just the selection bounds
LRect Client = GetClient();
size_t Start, End;
if (SelStart >= 0 && s >= 0)
{
// Selection has changed, union the before and after regions
Start = MIN(Cursor, c);
End = MAX(Cursor, c);
}
else if (SelStart >= 0)
{
// Selection created...
Start = MIN(SelStart, SelEnd);
End = MAX(SelStart, SelEnd);
}
else if (s >= 0)
{
// Selection removed...
Start = MIN(s, e);
End = MAX(s, e);
}
else return;
auto SLine = GetTextLine(Start);
auto ELine = GetTextLine(End);
LRect u;
if (SLine && ELine)
{
if (SLine->r.Valid())
{
u = DocToScreen(SLine->r);
}
else
u.Set(0, 0, Client.X()-1, 1); // Start of visible page
LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1);
if (ELine->r.Valid())
{
b = DocToScreen(ELine->r);
}
else
{
b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1);
}
u.Union(&b);
u.x1 = 0;
u.x2 = X();
}
else
{
/*
printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n",
_FL,
(int)Start, SLine,
(int)End, ELine);
*/
u = Client;
}
Invalidate(&u);
}
else if (Cursor != c)
{
// just the cursor has moved
// update the line the cursor moved to
LRect r = To->r;
r.Offset(-ScrollX, d->rPadding.y1-DocOffset);
r.x2 = X();
Invalidate(&r);
if (To != From)
{
// update the line the cursor came from,
// if it's a different line from the "to"
r = From->r;
r.Offset(-ScrollX, d->rPadding.y1-DocOffset);
r.x2 = X();
Invalidate(&r);
}
}
if (c != Cursor)
{
// Send off notify
SendNotify(LNotifyCursorChanged);
}
//int _Time = LCurrentTime() - _Start;
//printf("Setcursor=%ims\n", _Time);
}
void LTextView3::SetBorder(int b)
{
}
bool LTextView3::Cut()
{
bool Status = false;
char16 *Txt16 = 0;
DeleteSelection(&Txt16);
if (Txt16)
{
#ifdef WIN32
Txt16 = ConvertToCrLf(Txt16);
#endif
char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset);
LClipBoard Clip(this);
Clip.Text(Txt8);
Status = Clip.TextW(Txt16, false);
DeleteArray(Txt8);
DeleteArray(Txt16);
}
return Status;
}
bool LTextView3::Copy()
{
bool Status = true;
printf("txt copy\n");
if (SelStart >= 0)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
#ifdef WIN32
char16 *Txt16 = NewStrW(Text+Min, Max-Min);
Txt16 = ConvertToCrLf(Txt16);
char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset);
#else
char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text));
#endif
LClipBoard Clip(this);
Clip.Text(Txt8);
#ifdef WIN32
Clip.TextW(Txt16, false);
DeleteArray(Txt16);
#endif
DeleteArray(Txt8);
}
else LgiTrace("%s:%i - No selection.\n", _FL);
return Status;
}
bool LTextView3::Paste()
{
LClipBoard Clip(this);
LAutoWString Mem;
char16 *t = Clip.TextW();
if (!t) // ala Win9x
{
char *s = Clip.Text();
if (s)
{
Mem.Reset(Utf8ToWide(s));
t = Mem;
}
}
if (!t)
return false;
if (SelStart >= 0)
{
DeleteSelection();
}
// remove '\r's
char16 *s = t, *d = t;
for (; *s; s++)
{
if (*s != '\r')
{
*d++ = *s;
}
}
*d++ = 0;
// insert text
ssize_t Len = StrlenW(t);
Insert(Cursor, t, Len);
SetCaret(Cursor+Len, false, true); // Multiline
return true;
}
void LTextView3::ClearDirty(std::function OnStatus, bool Ask, const char *FileName)
{
if (Dirty)
{
int Answer = (Ask) ? LgiMsg(this,
LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"),
LLoadString(L_TEXTCTRL_SAVE, "Save"),
MB_YESNOCANCEL) : IDYES;
if (Answer == IDYES)
{
auto DoSave = [this, OnStatus, FileName=LString(FileName)](bool ok)
{
Save(FileName);
if (OnStatus)
OnStatus(ok);
};
if (!FileName)
{
LFileSelect *Select = new LFileSelect;
Select->Parent(this);
Select->Save([&FileName, &DoSave](auto Select, auto ok)
{
if (ok)
FileName = Select->Name();
DoSave(ok);
delete Select;
});
}
else DoSave(true);
}
else if (Answer == IDCANCEL)
{
if (OnStatus)
OnStatus(false);
return;
}
}
if (OnStatus)
OnStatus(true);
}
bool LTextView3::Open(const char *Name, const char *CharSet)
{
bool Status = false;
LFile f;
if (f.Open(Name, O_READ|O_SHARE))
{
DeleteArray(Text);
int64 Bytes = f.GetSize();
if (Bytes < 0 || Bytes & 0xffff000000000000LL)
{
LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes);
return false;
}
SetCaret(0, false);
Line.DeleteObjects();
char *c8 = new char[Bytes + 4];
if (c8)
{
if (f.Read(c8, (int)Bytes) == Bytes)
{
char *DataStart = c8;
c8[Bytes] = 0;
c8[Bytes+1] = 0;
c8[Bytes+2] = 0;
c8[Bytes+3] = 0;
if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe)
{
// utf-16
if (!CharSet)
{
CharSet = "utf-16";
DataStart += 2;
}
}
// Convert to unicode first....
if (Bytes == 0)
{
Text = new char16[1];
if (Text) Text[0] = 0;
}
else
{
Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset);
}
if (Text)
{
// Remove LF's
char16 *In = Text, *Out = Text;
CrLf = false;
Size = 0;
while (*In)
{
if (*In >= ' ' ||
*In == '\t' ||
*In == '\n')
{
*Out++ = *In;
Size++;
}
else if (*In == '\r')
{
CrLf = true;
}
In++;
}
Size = (int) (Out - Text);
*Out = 0;
Alloc = Size + 1;
Dirty = false;
if (Text && Text[0] == 0xfeff) // unicode byte order mark
{
memmove(Text, Text+1, Size * sizeof(*Text));
Size--;
}
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars(true);
Status = true;
}
}
DeleteArray(c8);
}
else
{
Alloc = Size = 0;
}
Invalidate();
}
return Status;
}
template
bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf)
{
if (!in)
return false;
if (CrLf)
{
int BufLen = 1 << 20;
LAutoPtr Buf(new T[BufLen]);
T *b = Buf;
T *e = Buf + BufLen;
T *c = in;
T *end = c + len;
while (c < end)
{
if (b > e - 16)
{
auto Bytes = (b - Buf) * sizeof(T);
if (out.Write(Buf, Bytes) != Bytes)
return false;
b = Buf;
}
if (*c == '\n')
{
*b++ = '\r';
*b++ = '\n';
}
else
{
*b++ = *c;
}
c++;
}
auto Bytes = (b - Buf) * sizeof(T);
if (out.Write(Buf, Bytes) != Bytes)
return false;
}
else
{
auto Bytes = len * sizeof(T);
if (out.Write(in, Bytes) != Bytes)
return false;
}
return true;
}
bool LTextView3::Save(const char *Name, const char *CharSet)
{
LFile f;
LString TmpName;
bool Status = false;
d->LastError.Empty();
if (f.Open(Name, O_WRITE))
{
if (f.SetSize(0) != 0)
{
// Can't resize file, fall back to renaming it and
// writing a new file...
f.Close();
TmpName = Name;
TmpName += ".tmp";
if (!FileDev->Move(Name, TmpName))
{
LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name);
return false;
}
if (!f.Open(Name, O_WRITE))
{
LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name);
return false;
}
}
if (Text)
{
auto InSize = Size * sizeof(char16);
if (CharSet && !Stricmp(CharSet, "utf-16"))
{
if (sizeof(*Text) == 2)
{
// No conversion needed...
Status = WriteToStream(f, Text, Size, CrLf);
}
else
{
// 32->16 convert
LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize));
if (c16)
Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf);
}
}
else if (CharSet && !Stricmp(CharSet, "utf-32"))
{
if (sizeof(*Text) == 4)
{
// No conversion needed...
Status = WriteToStream(f, Text, Size, CrLf);
}
else
{
// 16->32 convert
LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize));
if (c32)
Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf);
}
}
else
{
LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize));
if (c8)
Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf);
}
if (Status)
Dirty = false;
}
}
else
{
int Err = f.GetError();
LString sErr = LErrorCodeToString(Err);
d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get());
}
if (TmpName)
FileDev->Delete(TmpName);
return Status;
}
const char *LTextView3::GetLastError()
{
return d->LastError;
}
void LTextView3::UpdateScrollBars(bool Reset)
{
if (!VScroll)
return;
LRect Before = GetClient();
int DisplayLines = Y() / LineY;
ssize_t Lines = std::max(PartialPourLines, Line.Length());
VScroll->SetRange(Lines);
if (VScroll)
{
VScroll->SetPage(DisplayLines);
ssize_t Max = Lines - DisplayLines + 1;
bool Inval = false;
if (VScroll->Value() > Max)
{
VScroll->Value(Max);
Inval = true;
}
if (Reset)
{
VScroll->Value(0);
SelStart = SelEnd = -1;
}
else if (d->VScrollCache >= 0)
{
VScroll->Value(d->VScrollCache);
d->VScrollCache = -1;
SelStart = SelEnd = -1;
}
LRect After = GetClient();
if (Before != After && GetWrapType())
{
d->SetDirty(0, Size);
Inval = true;
}
if (Inval)
{
Invalidate();
}
}
}
void LTextView3::DoCase(std::function Callback, bool Upper)
{
if (Text)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (Min < Max)
{
if (UndoOn)
{
LAutoPtr Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(Min, Max - Min, UndoChange);
if (Obj)
UndoQue += Obj.Release();
}
for (ssize_t i=Min; i= 'a' && Text[i] <= 'z')
Text[i] = Text[i] - 'a' + 'A';
}
else
{
if (Text[i] >= 'A' && Text[i] <= 'Z')
Text[i] = Text[i] - 'A' + 'a';
}
}
Dirty = true;
d->SetDirty(Min, 0);
Invalidate();
SendNotify(LNotifyDocChanged);
}
}
if (Callback)
Callback(Text != NULL);
}
ssize_t LTextView3::GetLine()
{
ssize_t Idx = 0;
GetTextLine(Cursor, &Idx);
return Idx + 1;
}
void LTextView3::SetLine(int64_t i, bool select)
{
LTextLine *l = Line.ItemAt(i - 1);
if (l)
{
d->CenterCursor = true;
SetCaret(l->Start, select);
d->CenterCursor = false;
}
}
void LTextView3::DoGoto(std::function Callback)
{
LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text");
Dlg->DoModal([this, Dlg, Callback](auto d, auto code)
{
auto ok = code == IDOK && Dlg->GetStr();
if (ok)
SetLine(Dlg->GetStr().Int());
if (Callback)
Callback(ok);
delete Dlg;
});
}
LDocFindReplaceParams *LTextView3::CreateFindReplaceParams()
{
return new LDocFindReplaceParams3;
}
void LTextView3::SetFindReplaceParams(LDocFindReplaceParams *Params)
{
if (Params)
{
if (d->OwnFindReplaceParams)
{
DeleteObj(d->FindReplaceParams);
}
d->OwnFindReplaceParams = false;
d->FindReplaceParams = (LDocFindReplaceParams3*) Params;
}
}
void LTextView3::DoFindNext(std::function OnStatus)
{
bool Status = false;
if (InThread())
{
if (d->FindReplaceParams->Lock(_FL))
{
if (d->FindReplaceParams->LastFind)
Status = OnFind(d->FindReplaceParams->LastFind,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
d->FindReplaceParams->Unlock();
}
}
else if (IsAttached())
{
Status = PostEvent(M_TEXTVIEW_FIND);
}
if (OnStatus)
OnStatus(Status);
}
void LTextView3::DoFind(std::function Callback)
{
LString u;
if (HasSelection())
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
u = LString(Text + Min, Max - Min);
}
else
{
u = d->FindReplaceParams->LastFind.Get();
}
auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto Action)
{
if (Params &&
Params->Lock(_FL))
{
Params->MatchWord = Dlg->MatchWord;
Params->MatchCase = Dlg->MatchCase;
Params->SelectionOnly = Dlg->SelectionOnly;
Params->SearchUpwards = Dlg->SearchUpwards;
Params->LastFind.Reset(Utf8ToWide(Dlg->Find));
Params->Unlock();
}
DoFindNext([this, Callback](bool ok)
{
Focus(true);
if (Callback)
Callback(ok);
});
},
u);
Dlg->DoModal(NULL);
}
void LTextView3::DoReplace(std::function Callback)
{
bool SingleLineSelection = false;
SingleLineSelection = HasSelection();
if (SingleLineSelection)
{
LRange Sel = GetSelectionRange();
for (ssize_t i = Sel.Start; i < Sel.End(); i++)
{
if (Text[i] == '\n')
{
SingleLineSelection = false;
break;
}
}
}
- LAutoString LastFind8(SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind));
- LAutoString LastReplace8(WideToUtf8(d->FindReplaceParams->LastReplace));
+ auto LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind);
+ auto LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace);
- auto Dlg = new LReplaceDlg(this, [this, LastFind8=LString(LastFind8), LastReplace8=LString(LastReplace8)](auto Dlg, auto Action)
+ auto Dlg = new LReplaceDlg(this, [this, LastFind8, LastReplace8](auto Dlg, auto Action)
{
LReplaceDlg *Replace = dynamic_cast(Dlg);
LAssert(Replace != NULL);
+
+ LAutoString FindMem(LastFind8);
+ LAutoString ReplaceMem(LastReplace8);
if (Action == IDCANCEL)
return;
if (d->FindReplaceParams->Lock(_FL))
{
d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find));
d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace));
d->FindReplaceParams->MatchWord = Replace->MatchWord;
d->FindReplaceParams->MatchCase = Replace->MatchCase;
d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly;
switch (Action)
{
case IDC_FR_FIND:
{
OnFind( d->FindReplaceParams->LastFind,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
break;
}
case IDOK:
case IDC_FR_REPLACE:
{
OnReplace( d->FindReplaceParams->LastFind,
d->FindReplaceParams->LastReplace,
Action == IDOK,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
break;
}
}
d->FindReplaceParams->Unlock();
}
},
LastFind8,
LastReplace8);
Dlg->MatchWord = d->FindReplaceParams->MatchWord;
Dlg->MatchCase = d->FindReplaceParams->MatchCase;
Dlg->SelectionOnly = HasSelection();
Dlg->DoModal(NULL);
}
void LTextView3::SelectWord(size_t From)
{
for (SelStart = From; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = From; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Invalidate();
}
typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n);
ptrdiff_t LTextView3::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
if (!ValidStrW(Find))
return -1;
ssize_t FindLen = StrlenW(Find);
// Setup range to search
ssize_t Begin, End;
if (SelectionOnly && HasSelection())
{
Begin = MIN(SelStart, SelEnd);
End = MAX(SelStart, SelEnd);
}
else
{
Begin = 0;
End = Size;
}
// Look through text...
ssize_t i;
bool Wrap = false;
if (Cursor > End - FindLen)
{
Wrap = true;
if (SearchUpwards)
i = End - FindLen;
else
i = Begin;
}
else
{
i = Cursor;
}
if (i < Begin) i = Begin;
if (i > End) i = End;
StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW;
char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]);
for (; SearchUpwards ? i >= Begin : i <= End; i += SearchUpwards ? -1 : 1)
{
if
(
(MatchCase ? Text[i] : toupper(Text[i]))
==
FindCh
)
{
char16 *Possible = Text + i;
if (CmpFn(Possible, Find, FindLen) == 0)
{
if (MatchWord)
{
// Check boundaries
if (Possible > Text) // Check off the start
{
if (!IsWordBoundry(Possible[-1]))
continue;
}
if (i + FindLen < Size) // Check off the end
{
if (!IsWordBoundry(Possible[FindLen]))
continue;
}
}
/* What was this even supposed to do?
LRange r(Possible - Text, FindLen);
if (!r.Overlap(Cursor))
*/
return i;
}
}
if (!Wrap && (i + 1 > End - FindLen))
{
Wrap = true;
i = Begin;
End = Cursor;
}
}
return -1;
}
bool LTextView3::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
// Not sure what this is doing???
if (HasSelection() &&
SelEnd < SelStart)
{
Cursor = SelStart;
}
#if FEATURE_HILIGHT_ALL_MATCHES
// Clear existing styles for matches
for (StyleIter s = Style.begin(); s != Style.end(); )
{
if (s->Owner == STYLE_FIND_MATCHES)
Style.Delete(s);
else
s++;
}
ssize_t FindLen = StrlenW(Find);
ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc;
if (FirstLoc >= 0)
{
SetCaret(FirstLoc, false);
SetCaret(FirstLoc + FindLen, true);
}
ssize_t Old = Cursor;
if (!SearchUpwards)
Cursor += FindLen;
while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc)
{
LAutoPtr s(new LStyle(STYLE_FIND_MATCHES));
s->Start = Loc;
s->Len = FindLen;
s->Fore = LColour(L_FOCUS_SEL_FORE);
s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE));
InsertStyle(s);
Cursor = Loc + FindLen;
}
Cursor = Old;
ScrollToOffset(Cursor);
Invalidate();
#else
ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (Loc >= 0)
{
SetCaret(Loc, false);
SetCaret(Loc + StrlenW(Find), true);
return true;
}
#endif
return false;
}
bool LTextView3::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
if (ValidStrW(Find))
{
// int Max = -1;
ssize_t FindLen = StrlenW(Find);
ssize_t ReplaceLen = StrlenW(Replace);
// size_t OldCursor = Cursor;
ptrdiff_t First = -1;
while (true)
{
ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (First < 0)
{
First = Loc;
}
else if (Loc == First)
{
break;
}
if (Loc >= 0)
{
ssize_t OldSelStart = SelStart;
ssize_t OldSelEnd = SelEnd;
Delete(Loc, FindLen);
Insert(Loc, Replace, ReplaceLen);
SelStart = OldSelStart;
SelEnd = OldSelEnd - FindLen + ReplaceLen;
Cursor = Loc + ReplaceLen;
}
if (!All)
{
return Loc >= 0;
}
if (Loc < 0) break;
}
}
return false;
}
ssize_t LTextView3::SeekLine(ssize_t Offset, GTextViewSeek Where)
{
THREAD_CHECK();
switch (Where)
{
case PrevLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset--;
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case NextLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
Offset++;
break;
}
case StartLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case EndLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
break;
}
default:
{
LAssert(false);
break;
}
}
return Offset;
}
bool LTextView3::OnMultiLineTab(bool In)
{
bool Status = false;
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd), i;
Min = SeekLine(Min, StartLine);
int Ls = 0;
LArray p;
for (i=Min; i=0; i--)
{
if (In)
{
// <-
ssize_t n = Indexes[i], Space = 0;
for (; Space
ssize_t Len = Indexes[i];
for (; Text[Len] != '\n' && Len Indexes[i])
{
if (HardTabs)
{
char16 Tab[] = {'\t', 0};
Insert(Indexes[i], Tab, 1);
Max++;
}
else
{
char16 *Sp = new char16[IndentSize];
if (Sp)
{
for (int n=0; nChanges.Length())
{
UndoQue += UndoCur;
UndoCur = NULL;
}
else
{
DeleteObj(UndoCur);
}
SelStart = Min;
SelEnd = Cursor = Max;
PourEnabled = true;
PourText(Min, Max - Min);
PourStyle(Min, Max - Min);
d->SetDirty(Min, Max-Min);
Invalidate();
Status = true;
return Status;
}
void LTextView3::OnSetHidden(int Hidden)
{
}
void LTextView3::OnPosChange()
{
static bool Processing = false;
if (!Processing)
{
Processing = true;
LLayout::OnPosChange();
LRect c = GetClient();
bool ScrollYNeeded = c.Y() < (std::max(PartialPourLines, Line.Length()) * LineY);
bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL);
if (ScrollChange)
{
#if 0
auto Client = GetClient();
LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n",
_FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour);
#endif
SetScrollBars(false, ScrollYNeeded);
}
UpdateScrollBars();
if (GetWrapType() && d->PourX != X())
{
d->PourX = X();
d->SetDirty(0, Size);
}
Processing = false;
}
}
int LTextView3::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState)
{
Formats.Supports("text/uri-list");
Formats.Supports("text/html");
Formats.Supports("UniformResourceLocatorW");
return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE;
}
int LTextView3::OnDrop(LArray &Data, LPoint Pt, int KeyState)
{
int Status = DROPEFFECT_NONE;
for (unsigned i=0; iIsBinary())
{
OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length);
OsChar *s = (OsChar*) Data->Value.Binary.Data;
int len = 0;
while (s < e && s[len])
{
len++;
}
LAutoWString w
(
(char16*)LNewConvertCp
(
LGI_WideCharset,
s,
(
sizeof(OsChar) == 1
?
"utf-8"
:
LGI_WideCharset
),
len * sizeof(*s)
)
);
Insert(Cursor, w, len);
Invalidate();
return DROPEFFECT_COPY;
}
}
else if (dd.IsFileDrop())
{
// We don't directly handle file drops... pass up to the parent
bool FoundTarget = false;
for (LViewI *p = GetParent(); p; p = p->GetParent())
{
LDragDropTarget *t = p->DropTarget();
if (t)
{
Status = t->OnDrop(Data, Pt, KeyState);
if (Status != DROPEFFECT_NONE)
{
FoundTarget = true;
break;
}
}
}
if (!FoundTarget)
{
auto Wnd = GetWindow();
if (Wnd)
{
LDropFiles files(dd);
Wnd->OnReceiveFiles(files);
}
}
}
}
return Status;
}
void LTextView3::OnCreate()
{
SetWindow(this);
DropTarget(true);
#ifndef WINDOWS
if (Ctrls.Length() == 0)
SetPulse(PULSE_TIMEOUT);
Ctrls.Add(this);
#else
SetPulse(PULSE_TIMEOUT);
#endif
}
void LTextView3::OnEscape(LKey &K)
{
}
bool LTextView3::OnMouseWheel(double l)
{
if (VScroll)
{
int64 NewPos = VScroll->Value() + (int)l;
NewPos = limit(NewPos, 0, (ssize_t)GetLines());
VScroll->Value(NewPos);
Invalidate();
}
return true;
}
void LTextView3::OnFocus(bool f)
{
Invalidate();
}
ssize_t LTextView3::HitText(int x, int y, bool Nearest)
{
if (!Text)
return 0;
bool Down = y >= 0;
int Y = (VScroll) ? (int)VScroll->Value() : 0;
auto It = Line.begin(Y);
if (It != Line.end())
y += (*It)->r.y1;
while (It != Line.end())
{
auto l = *It;
if (l->r.Overlap(x, y))
{
// Over a line
int At = x - l->r.x1;
ssize_t Char = 0;
LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0);
Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate);
return l->Start + Char;
}
else if (y >= l->r.y1 && y <= l->r.y2)
{
// Click horizontally before of after line
if (x < l->r.x1)
{
return l->Start;
}
else if (x > l->r.x2)
{
return l->Start + l->Len;
}
}
if (Down)
It++;
else
It--;
Y++;
}
// outside text area
if (Down)
{
It = Line.rbegin();
if (It != Line.end())
{
if (y > (*It)->r.y2)
{
// end of document
return Size;
}
}
}
return 0;
}
void LTextView3::Undo()
{
int Old = UndoQue.GetPos();
UndoQue.Undo();
if (Old && !UndoQue.GetPos())
{
Dirty = false;
SendNotify(LNotifyDocChanged);
}
}
void LTextView3::Redo()
{
UndoQue.Redo();
}
void LTextView3::DoContextMenu(LMouse &m)
{
LSubMenu RClick;
LAutoString ClipText;
{
LClipBoard Clip(this);
ClipText.Reset(NewStr(Clip.Text()));
}
LStyle *s = HitStyle(HitText(m.x, m.y, true));
if (s)
{
if (OnStyleMenu(s, &RClick))
{
RClick.AppendSeparator();
}
}
RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0);
RClick.AppendSeparator();
RClick.AppendItem("Copy All", IDM_COPY_ALL, true);
RClick.AppendItem("Select All", IDM_SELECT_ALL, true);
RClick.AppendSeparator();
RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo());
RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo());
RClick.AppendSeparator();
auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true);
if (i) i->Checked(GetFixedWidthFont());
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true);
if (i) i->Checked(AutoIndent);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true);
if (i) i->Checked(ShowWhiteSpace);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true);
if (i) i->Checked(HardTabs);
RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true);
RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true);
if (Environment)
Environment->AppendItems(&RClick, NULL);
int Id = 0;
m.ToScreen();
switch (Id = RClick.Float(this, m))
{
case IDM_FIXED:
{
SetFixedWidthFont(!GetFixedWidthFont());
SendNotify(LNotifyFixedWidthChanged);
break;
}
case IDM_CUT:
{
Cut();
break;
}
case IDM_COPY:
{
Copy();
break;
}
case IDM_PASTE:
{
Paste();
break;
}
case IDM_COPY_ALL:
{
SelectAll();
Copy();
break;
}
case IDM_SELECT_ALL:
{
SelectAll();
break;
}
case IDM_UNDO:
{
Undo();
break;
}
case IDM_REDO:
{
Redo();
break;
}
case IDM_AUTO_INDENT:
{
AutoIndent = !AutoIndent;
break;
}
case IDM_SHOW_WHITE:
{
ShowWhiteSpace = !ShowWhiteSpace;
Invalidate();
break;
}
case IDM_HARD_TABS:
{
HardTabs = !HardTabs;
break;
}
case IDM_INDENT_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", IndentSize);
LInput *i = new LInput(this, s, "Indent Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
if (code)
IndentSize = atoi(i->GetStr());
delete i;
});
break;
}
case IDM_TAB_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", TabSize);
LInput *i = new LInput(this, s, "Tab Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
SetTabSize(atoi(i->GetStr()));
delete i;
});
break;
}
default:
{
if (s)
{
OnStyleMenuClick(s, Id);
}
if (Environment)
{
Environment->OnMenu(this, Id, 0);
}
break;
}
}
}
bool LTextView3::OnStyleClick(LStyle *style, LMouse *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
if
(
(!m)
||
(m->Left() && m->Down() && m->Double())
)
{
LString s(Text + style->Start, style->Len);
if (s)
OnUrl(s);
return true;
}
break;
}
default:
break;
}
return false;
}
bool LTextView3::OnStyleMenu(LStyle *style, LSubMenu *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
if (LIsValidEmail(s))
m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true);
else
m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true);
m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true);
return true;
}
default:
break;
}
return false;
}
void LTextView3::OnStyleMenuClick(LStyle *style, int i)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
switch (i)
{
case IDM_NEW:
case IDM_OPEN:
{
if (s)
OnUrl(s);
break;
}
case IDM_COPY_URL:
{
if (s)
{
LClipBoard Clip(this);
Clip.Text(s);
}
break;
}
}
break;
}
default:
break;
}
}
void LTextView3::OnMouseClick(LMouse &m)
{
bool Processed = false;
m.x += ScrollX;
if (m.Down())
{
if (m.IsContextMenu())
{
DoContextMenu(m);
return;
}
else if (m.Left())
{
Focus(true);
ssize_t Hit = HitText(m.x, m.y, true);
if (Hit >= 0)
{
SetCaret(Hit, m.Shift());
LStyle *s = HitStyle(Hit);
if (s)
Processed = OnStyleClick(s, &m);
}
if (!Processed && m.Double())
{
d->WordSelectMode = Cursor;
SelectWord(Cursor);
}
else
{
d->WordSelectMode = -1;
}
}
}
if (!Processed)
{
Capture(m.Down());
}
}
int LTextView3::OnHitTest(int x, int y)
{
#ifdef WIN32
if (GetClient().Overlap(x, y))
{
return HTCLIENT;
}
#endif
return LView::OnHitTest(x, y);
}
void LTextView3::OnMouseMove(LMouse &m)
{
m.x += ScrollX;
ssize_t Hit = HitText(m.x, m.y, true);
if (IsCapturing())
{
if (d->WordSelectMode < 0)
{
SetCaret(Hit, m.Left());
}
else
{
ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode;
ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode;
for (SelStart = Min; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = Max; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Cursor = SelEnd;
Invalidate();
}
}
}
LCursor LTextView3::GetCursor(int x, int y)
{
LRect c = GetClient();
c.Offset(-c.x1, -c.y1);
LStyle *s = NULL;
if (c.Overlap(x, y))
{
ssize_t Hit = HitText(x, y, true);
s = HitStyle(Hit);
}
return s ? s->Cursor : LCUR_Ibeam;
}
int LTextView3::GetColumn()
{
int x = 0;
LTextLine *l = GetTextLine(Cursor);
if (l)
{
for (ssize_t i=l->Start; i> 1);
m.Target = this;
DoContextMenu(m);
}
else if (k.IsChar)
{
switch (k.vkey)
{
default:
{
// process single char input
if
(
!GetReadOnly()
&&
(
(k.c16 >= ' ' || k.vkey == LK_TAB)
&&
k.c16 != 127
)
)
{
if (k.Down())
{
// letter/number etc
if (SelStart >= 0)
{
bool MultiLine = false;
if (k.vkey == LK_TAB)
{
size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd);
for (size_t i=Min; iLen : 0;
if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize))
{
int x = GetColumn();
int Add = IndentSize - (x % IndentSize);
if (HardTabs && ((x + Add) % TabSize) == 0)
{
int Rx = x;
size_t Remove;
for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--);
ssize_t Chars = (ssize_t)Cursor - Remove;
Delete(Remove, Chars);
Insert(Remove, &k.c16, 1);
Cursor = Remove + 1;
Invalidate();
}
else
{
char16 *Sp = new char16[Add];
if (Sp)
{
for (int n=0; nLen : 0;
SetCaret(Cursor + Add, false, Len != NewLen - 1);
}
DeleteArray(Sp);
}
}
}
else
{
char16 In = k.GetChar();
if (In == '\t' &&
k.Shift() &&
Cursor > 0)
{
l = GetTextLine(Cursor);
if (Cursor > l->Start)
{
if (Text[Cursor-1] == '\t')
{
Delete(Cursor - 1, 1);
SetCaret(Cursor, false, false);
}
else if (Text[Cursor-1] == ' ')
{
ssize_t Start = (ssize_t)Cursor - 1;
while (Start >= l->Start && strchr(" \t", Text[Start-1]))
Start--;
int Depth = SpaceDepth(Text + Start, Text + Cursor);
int NewDepth = Depth - (Depth % IndentSize);
if (NewDepth == Depth && NewDepth > 0)
NewDepth -= IndentSize;
int Use = 0;
while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth)
Use++;
Delete(Start + Use, Cursor - Start - Use);
SetCaret(Start + Use, false, false);
}
}
}
else if (In && Insert(Cursor, &In, 1))
{
l = GetTextLine(Cursor);
size_t NewLen = (l) ? l->Len : 0;
SetCaret(Cursor + 1, false, Len != NewLen - 1);
}
}
}
return true;
}
break;
}
case LK_RETURN:
#if defined MAC
case LK_KEYPADENTER:
#endif
{
if (GetReadOnly())
break;
if (k.Down() && k.IsChar)
{
OnEnter(k);
}
return true;
break;
}
case LK_BACKSPACE:
{
if (GetReadOnly())
break;
if (k.Ctrl())
{
// Ctrl+H
}
else if (k.Down())
{
if (SelStart >= 0)
{
// delete selection
DeleteSelection();
}
else
{
char Del = Cursor > 0 ? Text[Cursor-1] : 0;
if (Del == ' ' && (!HardTabs || IndentSize != TabSize))
{
// Delete soft tab
int x = GetColumn();
int Max = x % IndentSize;
if (Max == 0) Max = IndentSize;
ssize_t i;
for (i=Cursor-1; i>=0; i--)
{
if (Max-- <= 0 || Text[i] != ' ')
{
i++;
break;
}
}
if (i < 0) i = 0;
if (i < Cursor - 1)
{
ssize_t Del = (ssize_t)Cursor - i;
Delete(i, Del);
// SetCursor(i, false, false);
// Invalidate();
break;
}
}
else if (Del == '\t' && HardTabs && IndentSize != TabSize)
{
int x = GetColumn();
Delete(--Cursor, 1);
for (int c=GetColumn(); c 0)
{
Delete(Cursor - 1, 1);
}
}
}
return true;
break;
}
}
}
else // not a char
{
switch (k.vkey)
{
case LK_TAB:
return true;
case LK_RETURN:
{
return !GetReadOnly();
}
case LK_BACKSPACE:
{
if (!GetReadOnly())
{
if (k.Alt())
{
if (k.Down())
{
if (k.Ctrl())
{
Redo();
}
else
{
Undo();
}
}
}
else if (k.Ctrl())
{
if (k.Down())
{
ssize_t Start = Cursor;
while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
Delete(Cursor, Start - Cursor);
Invalidate();
}
}
return true;
}
break;
}
case LK_F3:
{
if (k.Down())
{
DoFindNext(NULL);
}
return true;
break;
}
case LK_LEFT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MIN(SelStart, SelEnd), false);
}
else if (Cursor > 0)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_StartOfLine;
}
else
if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
bool StartWhiteSpace = IsWhiteSpace(Text[n]);
bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]);
if (!StartWhiteSpace ||
Text[n] == '\n')
{
n--;
}
// Skip ws
for (; n > 0 && strchr(" \t", Text[n]); n--)
;
if (Text[n] == '\n')
{
n--;
}
else if (!StartWhiteSpace || !LeftWhiteSpace)
{
if (IsDelimiter(Text[n]))
{
for (; n > 0 && IsDelimiter(Text[n]); n--);
}
else
{
for (; n > 0; n--)
{
//IsWordBoundry(Text[n])
if (IsWhiteSpace(Text[n]) ||
IsDelimiter(Text[n]))
{
break;
}
}
}
}
if (n > 0) n++;
}
else
{
// single char
n--;
}
SetCaret(n, k.Shift());
}
}
return true;
break;
}
case LK_RIGHT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MAX(SelStart, SelEnd), false);
}
else if (Cursor < Size)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_EndOfLine;
}
else if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
if (IsWhiteSpace(Text[n]))
{
for (; nStart, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len);
ssize_t CharX = PrevLine.CharAt(ScreenX);
SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_DOWN:
{
if (k.Alt())
return false;
if (k.Down())
{
#ifdef MAC
if (k.Ctrl())
goto LTextView3_PageDown;
#endif
auto It = GetTextLineIt(Cursor);
if (It != Line.end())
{
auto l = *It;
It++;
if (It != Line.end())
{
LTextLine *Next = *It;
LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString NextLine(Font, Text + Next->Start, Next->Len);
ssize_t CharX = NextLine.CharAt(ScreenX);
SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_END:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(Size, k.Shift());
}
else
{
#ifdef MAC
Jump_EndOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
SetCaret(l->Start + l->Len, k.Shift());
}
}
}
return true;
break;
}
case LK_HOME:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(0, k.Shift());
}
else
{
#ifdef MAC
Jump_StartOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
char16 *Line = Text + l->Start;
char16 *s;
char16 SpTab[] = {' ', '\t', 0};
for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++);
ssize_t Whitespace = SubtractPtr(s, Line);
if (l->Start + Whitespace == Cursor)
{
SetCaret(l->Start, k.Shift());
}
else
{
SetCaret(l->Start + Whitespace, k.Shift());
}
}
}
}
return true;
break;
}
case LK_PAGEUP:
{
#ifdef MAC
LTextView3_PageUp:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_PAGEDOWN:
{
#ifdef MAC
LTextView3_PageDown:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_INSERT:
{
if (k.Down())
{
if (k.Ctrl())
{
Copy();
}
else if (k.Shift())
{
if (!GetReadOnly())
{
Paste();
}
}
}
return true;
break;
}
case LK_DELETE:
{
if (!GetReadOnly())
{
if (k.Down())
{
if (SelStart >= 0)
{
if (k.Shift())
{
Cut();
}
else
{
DeleteSelection();
}
}
else if (Cursor < Size &&
Delete(Cursor, 1))
{
Invalidate();
}
}
return true;
}
break;
}
default:
{
if (k.c16 == 17) break;
if (k.CtrlCmd() &&
!k.Alt())
{
switch (k.GetChar())
{
case 0xbd: // Ctrl+'-'
{
if (k.Down() &&
Font->PointSize() > 1)
{
Font->PointSize(Font->PointSize() - 1);
OnFontChange();
Invalidate();
}
break;
}
case 0xbb: // Ctrl+'+'
{
if (k.Down() &&
Font->PointSize() < 100)
{
Font->PointSize(Font->PointSize() + 1);
OnFontChange();
Invalidate();
}
break;
}
case 'a':
case 'A':
{
if (k.Down())
{
// select all
SelStart = 0;
SelEnd = Size;
Invalidate();
}
return true;
break;
}
case 'y':
case 'Y':
{
if (!GetReadOnly())
{
if (k.Down())
{
Redo();
}
return true;
}
break;
}
case 'z':
case 'Z':
{
if (!GetReadOnly())
{
if (k.Down())
{
if (k.Shift())
{
Redo();
}
else
{
Undo();
}
}
return true;
}
break;
}
case 'x':
case 'X':
{
if (!GetReadOnly())
{
if (k.Down())
{
Cut();
}
return true;
}
break;
}
case 'c':
case 'C':
{
if (k.Shift())
return false;
if (k.Down())
Copy();
return true;
break;
}
case 'v':
case 'V':
{
if (!k.Shift() &&
!GetReadOnly())
{
if (k.Down())
{
Paste();
}
return true;
}
break;
}
case 'f':
{
if (k.Down())
{
DoFind(NULL);
}
return true;
break;
}
case 'g':
case 'G':
{
if (k.Down())
{
DoGoto(NULL);
}
return true;
break;
}
case 'h':
case 'H':
{
if (k.Down())
{
DoReplace(NULL);
}
return true;
break;
}
case 'u':
case 'U':
{
if (!GetReadOnly())
{
if (k.Down())
{
DoCase(NULL, k.Shift());
}
return true;
}
break;
}
case LK_RETURN:
{
if (!GetReadOnly() && !k.Shift())
{
if (k.Down())
{
OnEnter(k);
}
return true;
}
break;
}
}
}
break;
}
}
}
return false;
}
void LTextView3::OnEnter(LKey &k)
{
// enter
if (SelStart >= 0)
{
DeleteSelection();
}
char16 InsertStr[256] = {'\n', 0};
LTextLine *CurLine = GetTextLine(Cursor);
if (CurLine && AutoIndent)
{
int WsLen = 0;
for (; WsLen < CurLine->Len &&
WsLen < (Cursor - CurLine->Start) &&
strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++);
if (WsLen > 0)
{
memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16));
InsertStr[WsLen+1] = 0;
}
}
if (Insert(Cursor, InsertStr, StrlenW(InsertStr)))
{
SetCaret(Cursor + StrlenW(InsertStr), false, true);
}
}
int LTextView3::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin)
{
int w = x;
int Size = f->TabSize();
for (char16 *c = s; SubtractPtr(c, s) < Len; )
{
if (*c == 9)
{
w = ((((w-Origin) + Size) / Size) * Size) + Origin;
c++;
}
else
{
char16 *e;
for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++);
LDisplayString ds(f, c, SubtractPtr(e, c));
w += ds.X();
c = e;
}
}
return w - x;
}
int LTextView3::ScrollYLine()
{
return (VScroll) ? (int)VScroll->Value() : 0;
}
int LTextView3::ScrollYPixel()
{
return ScrollYLine() * LineY;
}
LRect LTextView3::DocToScreen(LRect r)
{
r.Offset(0, d->rPadding.y1 - ScrollYPixel());
return r;
}
void LTextView3::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour)
{
pDC->Colour(colour);
pDC->Rectangle(&r);
}
void LTextView3::OnPaint(LSurface *pDC)
{
#if LGI_EXCEPTIONS
try
{
#endif
#if DOUBLE_BUFFER_PAINT
LDoubleBuffer MemBuf(pDC);
#endif
#if PROFILE_PAINT
char s[256];
sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size);
LProfile Prof(s);
#endif
if (d->LayoutDirty)
{
#if PROFILE_PAINT
Prof.Add("PourText");
#endif
PourText(d->DirtyStart, d->DirtyLen);
#if PROFILE_PAINT
Prof.Add("PourStyle");
#endif
PourStyle(d->DirtyStart, d->DirtyLen);
d->LayoutDirty = false;
}
#if PROFILE_PAINT
Prof.Add("Setup");
#endif
LRect r = GetClient();
r.x2 += ScrollX;
int Ox, Oy;
pDC->GetOrigin(Ox, Oy);
pDC->SetOrigin(Ox+ScrollX, Oy);
#if 0
// Coverage testing...
pDC->Colour(Rgb24(255, 0, 255), 24);
pDC->Rectangle();
#endif
LSurface *pOut = pDC;
bool DrawSel = false;
bool HasFocus = Focus();
// printf("%s:%i - HasFocus = %i\n", _FL, HasFocus);
LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE));
LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK));
LCss::ColorDef ForeDef, BkDef;
if (GetCss())
{
ForeDef = GetCss()->Color();
BkDef = GetCss()->BackgroundColor();
}
LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT));
LColour Back
(
/*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb
?
LColour(BkDef.Rgb32, 32)
:
Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED)
);
// LColour Whitespace = Fore.Mix(Back, 0.85f);
if (!Enabled())
{
Fore = LColour(L_LOW);
Back = LColour(L_MED);
}
if (Text &&
Font)
{
ssize_t SelMin = MIN(SelStart, SelEnd);
ssize_t SelMax = MAX(SelStart, SelEnd);
// font properties
Font->Colour(Fore, Back);
// Font->WhitespaceColour(Whitespace);
Font->Transparent(false);
// draw margins
pDC->Colour(PAINT_BORDER);
// top margin
pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1);
// left margin
{
LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2);
OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER);
}
// draw lines of text
int k = ScrollYLine();
auto It = Line.begin(k);
LTextLine *l = NULL;
int Dy = 0;
if (It != Line.end())
Dy = -(*It)->r.y1;
ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes
if (It != Line.end() &&
(l = *It) &&
SelStart >= 0 &&
SelStart < l->Start &&
SelEnd > l->Start)
{
// start of visible area is in selection
// init to selection colour
DrawSel = true;
Font->Colour(SelectedText, SelectedBack);
NextSelection = SelMax;
}
StyleIter Si = Style.begin();
LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0);
DocOffset = (l) ? l->r.y1 : 0;
#if PROFILE_PAINT
Prof.Add("foreach Line loop");
#endif
// loop through all visible lines
int y = d->rPadding.y1;
while ((l = *It) &&
l->r.y1+Dy < r.Y())
{
LRect Tr = l->r;
Tr.Offset(0, y - Tr.y1);
//LRect OldTr = Tr;
// deal with selection change on beginning of line
if (NextSelection == l->Start)
{
// selection change
DrawSel = !DrawSel;
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
// How many chars on this line have we
// processed so far:
ssize_t Done = 0;
bool LineHasSelection = NextSelection >= l->Start &&
NextSelection < l->Start + l->Len;
// Fractional pixels we have moved so far:
int MarginF = d->rPadding.x1 << LDisplayString::FShift;
int FX = MarginF;
int FY = Tr.y1 << LDisplayString::FShift;
// loop through all sections of similar text on a line
while (Done < l->Len)
{
// decide how big this block is
int RtlTrailingSpace = 0;
ssize_t Cur = l->Start + Done;
ssize_t Block = l->Len - Done;
// check for style change
if (NextStyle &&
(ssize_t)NextStyle->End() <= l->Start)
NextStyle = GetNextStyle(Si);
if (NextStyle)
{
// start
if (l->Overlap(NextStyle->Start) &&
NextStyle->Start > Cur &&
NextStyle->Start - Cur < Block)
{
Block = NextStyle->Start - Cur;
}
// end
ssize_t StyleEnd = NextStyle->Start + NextStyle->Len;
if (l->Overlap(StyleEnd) &&
StyleEnd > Cur &&
StyleEnd - Cur < Block)
{
Block = StyleEnd - Cur;
}
}
// check for next selection change
// this may truncate the style
if (NextSelection > Cur &&
NextSelection - Cur < Block)
{
Block = NextSelection - Cur;
}
LAssert(Block != 0); // sanity check
if (NextStyle && // There is a style
(Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block
Cur >= NextStyle->Start && // && we're inside the styled area
Cur < NextStyle->Start+NextStyle->Len)
{
LFont *Sf = NextStyle->Font ? NextStyle->Font : Font;
if (Sf)
{
// draw styled text
if (NextStyle->Fore.IsValid())
Sf->Fore(NextStyle->Fore);
if (NextStyle->Back.IsValid())
Sf->Back(NextStyle->Back);
else if (l->Back.IsValid())
Sf->Back(l->Back);
else
Sf->Back(Back);
Sf->Transparent(false);
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Sf,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
if (NextStyle->Decor == LCss::TextDecorSquiggle)
{
pOut->Colour(NextStyle->DecorColour);
int x = FX >> LDisplayString::FShift;
int End = x + Ds.X();
while (x < End)
{
pOut->Set(x, Tr.y2-(x%2));
x++;
}
}
FX += Ds.FX();
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Sf->Colour(fore, back);
}
else LAssert(0);
}
else
{
// draw a block of normal text
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Font,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
FX += Ds.FX();
}
if (NextStyle &&
Cur+Block >= NextStyle->Start+NextStyle->Len)
{
// end of this styled block
NextStyle = GetNextStyle(Si);
}
if (NextSelection == Cur+Block)
{
// selection change
DrawSel = !DrawSel;
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
Done += Block + RtlTrailingSpace;
} // end block loop
Tr.x1 = FX >> LDisplayString::FShift;
// eol processing
ssize_t EndOfLine = l->Start+l->Len;
if (EndOfLine >= SelMin && EndOfLine < SelMax)
{
// draw the '\n' at the end of the line as selected
// LColour bk = Font->Back();
pOut->Colour(Font->Back());
pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2);
Tr.x2 += 7;
}
else Tr.x2 = Tr.x1;
// draw any space after text
pOut->Colour(PAINT_AFTER_LINE);
pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2);
// cursor?
if (HasFocus)
{
// draw the cursor if on this line
if (Cursor >= l->Start && Cursor <= l->Start+l->Len)
{
CursorPos.ZOff(1, LineY-1);
ssize_t At = Cursor-l->Start;
LDisplayString Ds(Font, MapText(Text+l->Start, At), At);
Ds.ShowVisibleTab(ShowWhiteSpace);
int CursorX = Ds.X();
CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1);
if (CanScrollX)
{
// Cursor on screen check
LRect Scr = GetClient();
Scr.Offset(ScrollX, 0);
LRect Cur = CursorPos;
if (Cur.x2 > Scr.x2 - 5) // right edge check
{
ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40;
Invalidate();
}
else if (Cur.x1 < Scr.x1 && ScrollX > 0)
{
ScrollX = MAX(0, Cur.x1 - 40);
Invalidate();
}
}
if (Blink)
{
LRect c = CursorPos;
pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192));
pOut->Rectangle(&c);
}
#if WINNATIVE
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
COMPOSITIONFORM Cf;
Cf.dwStyle = CFS_POINT;
Cf.ptCurrentPos.x = CursorPos.x1;
Cf.ptCurrentPos.y = CursorPos.y1;
ImmSetCompositionWindow(hIMC, &Cf);
ImmReleaseContext(Handle(), hIMC);
}
#endif
}
}
#if DRAW_LINE_BOXES
{
uint Style = pDC->LineStyle(LSurface::LineAlternate);
LColour Old = pDC->Colour(LColour::Red);
pDC->Box(&OldTr);
pDC->Colour(Old);
pDC->LineStyle(Style);
LString s;
s.Printf("%i, %i", Line.IndexOf(l), l->Start);
LDisplayString ds(LSysFont, s);
LSysFont->Transparent(true);
ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1);
}
#endif
y += LineY;
It++;
} // end of line loop
// draw any space under the lines
if (y <= r.y2)
{
pDC->Colour(Back);
// pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2);
}
}
else
{
// default drawing: nothing
pDC->Colour(Back);
pDC->Rectangle(&r);
}
// _PaintTime = LCurrentTime() - StartTime;
#ifdef PAINT_DEBUG
if (GetNotify())
{
char s[256];
sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime);
LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s);
GetNotify()->OnEvent(&m);
}
#endif
// printf("PaintTime: %ims\n", _PaintTime);
#if LGI_EXCEPTIONS
}
catch (...)
{
LgiMsg(this, "LTextView3::OnPaint crashed.", "Lgi");
}
#endif
}
LMessage::Result LTextView3::OnEvent(LMessage *Msg)
{
switch (Msg->Msg())
{
case M_TEXT_UPDATE_NAME:
{
if (d->Lock(_FL))
{
Name(d->SetName);
d->SetName.Empty();
d->Unlock();
}
break;
}
case M_TEXTVIEW_FIND:
{
if (InThread())
DoFindNext(NULL);
else
LgiTrace("%s:%i - Not in thread.\n", _FL);
break;
}
case M_TEXTVIEW_REPLACE:
{
// DoReplace();
break;
}
case M_CUT:
{
Cut();
break;
}
case M_COPY:
{
Copy();
break;
}
case M_PASTE:
{
Paste();
break;
}
#if defined WIN32
case WM_GETTEXTLENGTH:
{
return Size;
}
case WM_GETTEXT:
{
int Chars = (int)Msg->A();
char *Out = (char*)Msg->B();
if (Out)
{
char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars);
if (In)
{
int Len = (int)strlen(In);
memcpy(Out, In, Len);
DeleteArray(In);
return Len;
}
}
return 0;
}
/* This is broken... the IME returns garbage in the buffer. :(
case WM_IME_COMPOSITION:
{
if (Msg->b & GCS_RESULTSTR)
{
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
char *Buf = new char[Size];
if (Buf)
{
ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size);
char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size);
if (Utf)
{
Insert(Cursor, Utf, StrlenW(Utf));
DeleteArray(Utf);
}
DeleteArray(Buf);
}
ImmReleaseContext(Handle(), hIMC);
}
return 0;
}
break;
}
*/
#endif
}
return LLayout::OnEvent(Msg);
}
int LTextView3::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl->GetId() == IDC_VSCROLL && VScroll)
{
if (n.Type == LNotifyScrollBarCreate)
{
UpdateScrollBars();
}
Invalidate();
}
return 0;
}
void LTextView3::InternalPulse()
{
if (!ReadOnly)
{
uint64 Now = LCurrentTime();
if (!BlinkTs)
BlinkTs = Now;
else if (Now - BlinkTs > CURSOR_BLINK)
{
Blink = !Blink;
LRect p = CursorPos;
p.Offset(-ScrollX, 0);
Invalidate(&p);
BlinkTs = Now;
}
}
if (PartialPour)
PourText(Size, 0);
}
void LTextView3::OnPulse()
{
#ifdef WINDOWS
InternalPulse();
#else
for (auto c: Ctrls)
c->InternalPulse();
#endif
}
void LTextView3::OnUrl(char *Url)
{
if (Environment)
Environment->OnNavigate(this, Url);
else
{
LUri u(Url);
bool Email = LIsValidEmail(Url);
const char *Proto = Email ? "mailto" : u.sProtocol;
LString App = LGetAppForProtocol(Proto);
if (App)
LExecute(App, Url);
else
LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto);
}
}
bool LTextView3::OnLayout(LViewLayoutInfo &Inf)
{
Inf.Width.Min = 32;
Inf.Width.Max = -1;
Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4;
Inf.Height.Max = -1;
return true;
}
///////////////////////////////////////////////////////////////////////////////
class LTextView3_Factory : public LViewFactory
{
LView *NewView(const char *Class, LRect *Pos, const char *Text)
{
if (_stricmp(Class, "LTextView3") == 0)
{
return new LTextView3(-1, 0, 0, 2000, 2000);
}
return 0;
}
} TextView3_Factory;
diff --git a/src/haiku/App.cpp b/src/haiku/App.cpp
--- a/src/haiku/App.cpp
+++ b/src/haiku/App.cpp
@@ -1,1106 +1,1110 @@
#include
#include
#include
#include
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/SkinEngine.h"
#include "lgi/common/Array.h"
#include "lgi/common/Variant.h"
#include "lgi/common/Token.h"
#include "lgi/common/FontCache.h"
#include "AppPriv.h"
#include "MimeType.h"
#define DEBUG_MSG_TYPES 0
#define DEBUG_HND_WARNINGS 0
#define IDLE_ALWAYS 0
LString LgiArgsAppPath;
////////////////////////////////////////////////////////////////
struct OsAppArgumentsPriv
{
::LArray Ptr;
~OsAppArgumentsPriv()
{
Ptr.DeleteArrays();
}
};
OsAppArguments::OsAppArguments(int args, const char **arg)
{
d = new OsAppArgumentsPriv;
for (int i=0; iPtr.Add(NewStr(arg[i]));
}
Args = d->Ptr.Length();
Arg = &d->Ptr[0];
}
OsAppArguments::~OsAppArguments()
{
DeleteObj(d);
}
bool OsAppArguments::Get(const char *Name, const char **Val)
{
if (!Name)
return false;
for (int i=0; iPtr.DeleteArrays();
if (!CmdLine)
return;
for (char *s = CmdLine; *s; )
{
while (*s && strchr(WhiteSpace, *s)) s++;
if (*s == '\'' || *s == '\"')
{
char delim = *s++;
char *e = strchr(s, delim);
if (e)
d->Ptr.Add(NewStr(s, e - s));
else
break;
s = e + 1;
}
else
{
char *e = s;
while (*e && !strchr(WhiteSpace, *e))
e++;
d->Ptr.Add(NewStr(s, e-s));
s = e;
}
}
Args = d->Ptr.Length();
Arg = &d->Ptr[0];
}
OsAppArguments &OsAppArguments::operator =(OsAppArguments &a)
{
d->Ptr.DeleteArrays();
for (int i=0; iPtr.Add(NewStr(a.Arg[i]));
}
Args = d->Ptr.Length();
Arg = &d->Ptr[0];
return *this;
}
////////////////////////////////////////////////////////////////
#if HAS_SHARED_MIME
#include "GFilterUtils.h"
#include "mime-types.h"
class LSharedMime : public LLibrary
{
public:
LSharedMime() :
#ifdef _DEBUG
LLibrary("libsharedmime1d")
#else
LLibrary("libsharedmime1")
#endif
{
}
DynFunc0(int, mimetypes_init);
DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type);
DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags);
DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length);
DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type);
DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type);
DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan);
DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang);
};
#endif
#if 1
/////////////////////////////////////////////////////////////////////////////
//
// Attempts to cleanup and call drkonqi to process the crash
//
void LgiCrashHandler(int Sig)
{
// Don't get into an infinite loop
signal(SIGSEGV, SIG_DFL);
#ifndef _MSC_VER
// Our pid
int MyPid = getpid();
printf("LgiCrashHandler trigger MyPid=%i\n", MyPid);
#endif
exit(-1);
}
#endif
/////////////////////////////////////////////////////////////////////////////
#ifndef XK_Num_Lock
#define XK_Num_Lock 0xff7f
#endif
#ifndef XK_Shift_Lock
#define XK_Shift_Lock 0xffe6
#endif
#ifndef XK_Caps_Lock
#define XK_Caps_Lock 0xffe5
#endif
struct Msg
{
LViewI *v;
int m;
LMessage::Param a, b;
void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B)
{
v = V;
m = M;
a = A;
b = B;
}
};
// Out of thread messages... must lock before access.
class LMessageQue : public LMutex
{
public:
typedef ::LArray MsgArray;
LMessageQue() : LMutex("LMessageQue")
{
}
MsgArray *Lock(const char *file, int line)
{
if (!LMutex::Lock(file, line))
return NULL;
return &q;
}
operator bool()
{
return q.Length() > 0;
}
private:
MsgArray q;
} MsgQue;
/////////////////////////////////////////////////////////////////////////////
LApp *TheApp = NULL;
LSkinEngine *LApp::SkinEngine;
LMouseHook *LApp::MouseHook;
LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) :
OsApplication(AppArgs.Args, AppArgs.Arg)
{
TheApp = this;
LgiArgsAppPath = AppArgs.Arg[0];
Name(name);
d = new LAppPrivate(this);
if (LIsRelativePath(LgiArgsAppPath))
{
char Cwd[MAX_PATH_LEN];
getcwd(Cwd, sizeof(Cwd));
LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath);
LgiArgsAppPath = Cwd;
}
char AppPathLnk[MAX_PATH_LEN];
if (LResolveShortcut(LgiArgsAppPath, AppPathLnk, sizeof(AppPathLnk)))
LgiArgsAppPath = AppPathLnk;
int WCharSz = sizeof(wchar_t);
#if defined(_MSC_VER)
LAssert(WCharSz == 2);
::LFile::Path Dlls(LgiArgsAppPath);
Dlls--;
SetDllDirectoryA(Dlls);
#else
LAssert(WCharSz == 4);
#endif
#ifdef _MSC_VER
SetEnvironmentVariable(_T("GTK_CSD"), _T("0"));
#else
setenv("GTK_CSD", "0", true);
#endif
// We want our printf's NOW!
setvbuf(stdout,(char *)NULL,_IONBF,0); // print mesgs immediately.
// Setup the file and graphics sub-systems
d->FileSystem = new LFileSystem;
d->GdcSystem = new GdcDevice;
SetAppArgs(AppArgs);
srand(LCurrentTime());
LColour::OnChange();
MouseHook = new LMouseHook;
d->GetConfig();
// System font setup
LFontType SysFontType;
if (SysFontType.GetSystemFont("System"))
{
SystemNormal = SysFontType.Create();
if (SystemNormal)
SystemNormal->Transparent(true);
SystemBold = SysFontType.Create();
if (SystemBold)
{
SystemBold->Bold(true);
SystemBold->Transparent(true);
SystemBold->Create();
}
}
else
{
printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__);
}
if (!SystemNormal)
{
LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK);
LExitApp();
}
if (!GetOption("noskin"))
{
extern LSkinEngine *CreateSkinEngine(LApp *App);
SkinEngine = CreateSkinEngine(this);
}
}
LApp::~LApp()
{
CommonCleanup();
DeleteObj(AppWnd);
DeleteObj(SystemNormal);
DeleteObj(SystemBold);
DeleteObj(SkinEngine);
DeleteObj(MouseHook);
DeleteObj(d->FileSystem);
DeleteObj(d->GdcSystem);
DeleteObj(LFontSystem::Me);
DeleteObj(d);
TheApp = NULL;
}
LApp *LApp::ObjInstance()
{
return TheApp;
}
bool LApp::IsOk()
{
bool Status =
#ifndef __clang__
(this != 0) &&
#endif
(d != 0);
LAssert(Status);
return Status;
}
LMouseHook *LApp::GetMouseHook()
{
return MouseHook;
}
int LApp::GetMetric(LSystemMetric Metric)
{
switch (Metric)
{
case LGI_MET_DECOR_X:
return 8;
case LGI_MET_DECOR_Y:
return 8 + 19;
case LGI_MET_DECOR_CAPTION:
return 19;
default:
break;
}
return 0;
}
LViewI *LApp::GetFocus()
{
// GtkWidget *w = gtk_window_get_focus(GtkWindow *window);
return NULL;
}
OsThread LApp::_GetGuiThread()
{
return d->GuiThread;
}
OsThreadId LApp::GetGuiThreadId()
{
return d->GuiThreadId;
}
OsProcessId LApp::GetProcessId()
{
#ifdef WIN32
return GetCurrentProcessId();
#else
return getpid();
#endif
}
OsAppArguments *LApp::GetAppArgs()
{
return IsOk() ? &d->Args : 0;
}
void LApp::SetAppArgs(OsAppArguments &AppArgs)
{
if (IsOk())
{
d->Args = AppArgs;
}
}
bool LApp::InThread()
{
OsThreadId Me = GetCurrentThreadId();
OsThreadId Gui = GetGuiThreadId();
// printf("Me=%i Gui=%i\n", Me, Gui);
return Gui == Me;
}
bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam)
{
if (!InThread())
{
printf("%s:%i - Error: Out of thread.\n", _FL);
return false;
}
printf("Running main loop...\n");
d->Run();
printf("Main loop finished.\n");
return true;
}
bool LApp::Yield()
{
return false;
}
void LApp::Exit(int Code)
{
if (Code)
{
// hard exit
::exit(Code);
}
else
{
// soft exit
printf("Quitting main loop...\n");
if (d->Lock())
{
d->Quit();
d->Unlock();
}
}
}
void LApp::OnUrl(const char *Url)
{
if (AppWnd)
AppWnd->OnUrl(Url);
}
void LApp::OnReceiveFiles(::LArray &Files)
{
if (AppWnd)
AppWnd->OnReceiveFiles(Files);
else
LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe.");
}
const char *LApp::GetArgumentAt(int n)
{
return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0;
}
bool LApp::GetOption(const char *Option, char *Dest, int DestLen)
{
::LString Buf;
if (GetOption(Option, Buf))
{
if (Dest)
{
if (DestLen > 0)
{
strcpy_s(Dest, DestLen, Buf);
}
else return false;
}
return true;
}
return false;
}
bool LApp::GetOption(const char *Option, ::LString &Buf)
{
if (IsOk() && Option)
{
int OptLen = strlen(Option);
for (int i=1; iArgs.Args; i++)
{
auto *a = d->Args.Arg[i];
if (!a)
continue;
if (strchr("-/\\", a[0]))
{
if (strnicmp(a+1, Option, OptLen) == 0)
{
const char *Arg = NULL;
if (strlen(a+1+OptLen) > 0)
{
Arg = a + 1 + OptLen;
}
else if (i < d->Args.Args - 1)
{
Arg = d->Args.Arg[i + 1];
}
if (Arg)
{
if (strchr("\'\"", *Arg))
{
char Delim = *Arg++;
char *End = strchr(Arg, Delim);
if (End)
{
auto Len = End-Arg;
if (Len > 0)
Buf.Set(Arg, Len);
else
return false;
}
else return false;
}
else
{
Buf = Arg;
}
}
return true;
}
}
}
}
return false;
}
void LApp::OnCommandLine()
{
::LArray Files;
for (int i=1; iArgs; i++)
{
auto a = GetAppArgs()->Arg[i];
if (LFileExists(a))
{
Files.Add(NewStr(a));
}
}
// call app
if (Files.Length() > 0)
{
OnReceiveFiles(Files);
}
// clear up
Files.DeleteArrays();
}
LString LApp::GetFileMimeType(const char *File)
{
BMimeType mt;
auto r = BMimeType::GuessMimeType(File, &mt);
if (r != B_OK)
{
LgiTrace("%s:%i - GuessMimeType(%s) failed: %i\n", _FL, File, r);
return LString();
}
return mt.Type();
}
bool LApp::GetAppsForMimeType(const char *Mime, LArray &Apps)
{
// Find alternative version of the MIME type (e.g. x-type and type).
char AltMime[256];
strcpy(AltMime, Mime);
char *s = strchr(AltMime, '/');
if (s)
{
s++;
int Len = strlen(s) + 1;
if (strnicmp(s, "x-", 2) == 0)
{
memmove(s, s+2, Len - 2);
}
else
{
memmove(s+2, s, Len);
s[0] = 'x';
s[1] = '-';
}
}
LGetAppsForMimeType(Mime, Apps);
LGetAppsForMimeType(AltMime, Apps);
return Apps.Length() > 0;
}
#if defined(LINUX)
LLibrary *LApp::GetWindowManagerLib()
{
if (this != NULL && !d->WmLib)
{
char Lib[32];
WindowManager Wm = LGetWindowManager();
switch (Wm)
{
case WM_Kde:
strcpy(Lib, "liblgikde3");
break;
case WM_Gnome:
strcpy(Lib, "liblgignome2");
break;
default:
strcpy(Lib, "liblgiother");
break;
}
#ifdef _DEBUG
strcat(Lib, "d");
#endif
d->WmLib = new LLibrary(Lib, true);
if (d->WmLib)
{
if (d->WmLib->IsLoaded())
{
Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit");
if (WmInit)
{
WmInitParams Params;
// Params.Dsp = XObject::XDisplay();
Params.Args = d->Args.Args;
Params.Arg = d->Args.Arg;
WmInit(&Params);
}
// else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__);
}
// else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib);
}
// else printf("%s:%i - alloc error\n", __FILE__, __LINE__);
}
return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0;
}
void LApp::DeleteMeLater(LViewI *v)
{
d->DeleteLater.Add(v);
}
void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v)
{
// Store the clipboard data we will serve
d->ClipData = v;
}
bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types)
{
return false;
}
#endif
LSymLookup *LApp::GetSymLookup()
{
return NULL;
}
bool LApp::IsElevated()
{
#ifdef WIN32
LAssert(!"What API works here?");
return false;
#else
return geteuid() == 0;
#endif
}
int LApp::GetCpuCount()
{
return 1;
}
LFontCache *LApp::GetFontCache()
{
if (!d->FontCache)
d->FontCache.Reset(new LFontCache(SystemNormal));
return d->FontCache;
}
#ifdef LINUX
LApp::DesktopInfo::DesktopInfo(const char *file)
{
File = file;
Dirty = false;
if (File)
Serialize(false);
}
bool LApp::DesktopInfo::Serialize(bool Write)
{
::LFile f;
if (Write)
{
::LFile::Path p(File);
p--;
if (!p.Exists())
return false;
}
else if (!LFileExists(File))
return false;
if (!f.Open(File, Write?O_WRITE:O_READ))
{
LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get());
return false;
}
if (Write)
{
f.SetSize(0);
for (unsigned i=0; i= 0)
{
int e = l.Find("]", ++s);
if (e >= 0)
{
Cur = &Data.New();
Cur->Name = l(s, e - s + 1);
}
}
else if ((s = l.Find("=")) >= 0)
{
if (!Cur)
Cur = &Data.New();
KeyPair &kp = Cur->Values.New();
kp.Key = l(0, s).Strip();
kp.Value = l(++s, -1).Strip();
// printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get());
}
}
}
return true;
}
LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create)
{
for (unsigned i=0; iGet(Field, false, Dirty);
if (kp)
{
return kp->Value;
}
}
}
return ::LString();
}
bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect)
{
if (!Field)
return false;
Section *s = GetSection(Sect ? Sect : DefaultSection, true);
if (!s)
return false;
KeyPair *kp = s->Get(Field, true, Dirty);
if (!kp)
return false;
if (kp->Value != Value)
{
kp->Value = Value;
Dirty = true;
}
return true;
}
LApp::DesktopInfo *LApp::GetDesktopInfo()
{
auto sExe = LGetExeFile();
::LFile::Path Exe(sExe);
::LFile::Path Desktop(LSP_HOME);
::LString Leaf;
Leaf.Printf("%s.desktop", Exe.Last().Get());
Desktop += ".local/share/applications";
Desktop += Leaf;
const char *Ex = Exe;
const char *Fn = Desktop;
if (d->DesktopInfo.Reset(new DesktopInfo(Desktop)))
{
// Do a sanity check...
::LString s = d->DesktopInfo->Get("Name");
if (!s && Name())
d->DesktopInfo->Set("Name", Name());
s = d->DesktopInfo->Get("Exec");
if (!s || s != (const char*)sExe)
d->DesktopInfo->Set("Exec", sExe);
s = d->DesktopInfo->Get("Type");
if (!s) d->DesktopInfo->Set("Type", "Application");
s = d->DesktopInfo->Get("Categories");
if (!s) d->DesktopInfo->Set("Categories", "Application;");
s = d->DesktopInfo->Get("Terminal");
if (!s) d->DesktopInfo->Set("Terminal", "false");
d->DesktopInfo->Update();
}
return d->DesktopInfo;
}
bool LApp::SetApplicationIcon(const char *FileName)
{
DesktopInfo *di = GetDesktopInfo();
if (!di)
return false;
::LString IcoPath = di->Get("Icon");
if (IcoPath == FileName)
return true;
di->Set("Icon", FileName);
return di->Update();
}
#endif
////////////////////////////////////////////////////////////////
OsApplication *OsApplication::Inst = 0;
class OsApplicationPriv
{
public:
OsApplicationPriv()
{
}
};
OsApplication::OsApplication(int Args, const char **Arg)
{
Inst = this;
d = new OsApplicationPriv;
}
OsApplication::~OsApplication()
{
DeleteObj(d);
Inst = 0;
}
////////////////////////////////////////////////////////////////
int LMessage::Msg()
{
return what;
}
LMessage::Param LMessage::A()
{
int64 a = 0;
if (FindInt64(PropA, &a) != B_OK)
{
int32 c = CountNames(B_ANY_TYPE);
printf("%s:%i - Failed to find PropA (%i names)\n", _FL, c);
for (int32 i=0; iPostEvent(what, A(), B());
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
LLocker::LLocker(BHandler *h, const char *File, int Line)
{
hnd = h;
file = File;
line = Line;
}
LLocker::~LLocker()
{
Unlock();
}
-bool LLocker::Lock()
+bool LLocker::Lock(bool debug)
{
if (locked)
{
printf("%s:%i - Locker already locked.\n", file, line);
LAssert(!"Locker already locked.");
return false;
}
+
if (!hnd)
{
- // printf("%s:%i - Locker hnd is NULL.\n", file, line);
+ if (debug)
+ printf("%s:%i - Locker hnd is NULL.\n", file, line);
return false;
}
auto looper = hnd->Looper();
if (!looper)
{
- // printf("%s:%i - Locker looper is NULL %i.\n", file, line, count);
+ if (debug)
+ printf("%s:%i - Locker looper is NULL.\n", file, line);
return false;
}
thread_id threadId = looper->Thread();
if (threadId <= 0)
{
- // printf("%s:%i - Looper has no thread?!?!\n", file, line);
+ if (debug)
+ printf("%s:%i - Looper has no thread?!?!\n", file, line);
noThread = true;
return locked = true;
}
while (!locked)
{
status_t result = hnd->LockLooperWithTimeout(1000 * 1000);
if (result == B_OK)
{
locked = true;
break;
}
else if (result == B_TIMED_OUT)
{
// Warn about failure to lock...
auto cur = GetCurrentThreadId();
auto locking = hnd->Looper()->LockingThread();
printf("%s:%i - Warning: can't lock. cur=%i locking=%i\n",
_FL,
cur,
locking);
}
else if (result == B_BAD_VALUE)
{
break;
}
else
{
// Warn about error
printf("%s:%i - Error from LockLooperWithTimeout = 0x%x\n", _FL, result);
}
}
return locked;
}
status_t LLocker::LockWithTimeout(int64 time)
{
LAssert(!locked);
if (!hnd)
{
// printf("%s:%i - Locker hnd is NULL.\n", file, line);
return false;
}
auto looper = hnd->Looper();
if (!looper)
{
// printf("%s:%i - Locker looper is NULL %i.\n", file, line, count);
return false;
}
thread_id threadId = looper->Thread();
if (threadId <= 0)
{
// printf("%s:%i - Looper has no thread?!?!\n", file, line);
noThread = true;
return locked = true;
}
status_t result = hnd->LockLooperWithTimeout(time);
if (result == B_OK)
locked = true;
return result;
}
void LLocker::Unlock()
{
if (noThread)
{
locked = false;
return;
}
if (locked)
{
hnd->UnlockLooper();
locked = false;
}
}
diff --git a/src/haiku/View.cpp b/src/haiku/View.cpp
--- a/src/haiku/View.cpp
+++ b/src/haiku/View.cpp
@@ -1,1151 +1,1155 @@
/*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);
}
// LWindow's get their events from their LWindowPrivate
#define IsLWindow dynamic_cast(d->View)
void FrameMoved(BPoint p)
{
if (!d || IsLWindow) return;
d->View->Pos.Offset(p.x - d->View->Pos.x1, p.y - d->View->Pos.y1);
d->View->OnPosChange();
}
void FrameResized(float newWidth, float newHeight)
{
if (!d || IsLWindow) return;
d->View->Pos.SetSize(newWidth, newHeight);
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;
LView *LViewFromHandle(OsView hWnd)
{
if (!hWnd)
return NULL;
- auto bv = dynamic_cast>(hWnd);
+ auto bv = dynamic_cast*>(hWnd);
if (!bv || !bv->d)
return NULL;
return bv->d->View;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
LViewPrivate::LViewPrivate(LView *view) :
View(view),
Hnd(new LBView(this))
{
}
LViewPrivate::~LViewPrivate()
{
View->d = NULL;
MsgQue.DeleteObjects();
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();
bool locked = false;
if (Wnd)
locked = Wnd->LockLooper();
if (Hnd->Parent())
Hnd->RemoveSelf();
DeleteObj(Hnd);
if (Wnd && locked)
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)
{
LgiTrace("%s:%i - No bview for %s\n", _FL, GetClass());
}
else
{
LLocker lck(bview, _FL);
- if (lck.Lock())
+
+ auto looper = bview->Looper();
+ auto thread = looper ? looper->Thread() : 0;
+
+ if (thread <= 0 || lck.Lock())
{
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);
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();
if (TestFlag(WndFlags, GWF_FOCUS))
d->Hnd->MakeFocus();
Status = true;
if (d->MsgQue.Length())
{
printf("%s:%i - %s.Attach msgQue=%i\n", _FL, GetClass(), (int)d->MsgQue.Length());
for (auto bmsg: d->MsgQue)
d->Hnd->Window()->PostMessage(bmsg);
d->MsgQue.DeleteObjects();
}
if (Debug)
LgiTrace("%s:%i - Attached %s/%p to view %s/%p, success\n",
_FL,
GetClass(), d->Hnd,
parent->GetClass(), bview);
+
+ auto w = GetWindow();
+ if (w && TestFlag(WndFlags, GWF_FOCUS))
+ w->SetFocus(this, LWindow::GainFocus);
}
else
{
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,
};
diff --git a/src/haiku/Widgets.cpp b/src/haiku/Widgets.cpp
--- a/src/haiku/Widgets.cpp
+++ b/src/haiku/Widgets.cpp
@@ -1,304 +1,305 @@
/*hdr
** FILE: GuiDlg.cpp
** AUTHOR: Matthew Allen
** DATE: 8/9/1998
** DESCRIPTION: Dialog components
**
** Copyright (C) 1998 Matthew Allen
** fret@memecode.com
*/
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Slider.h"
#include "lgi/common/Bitmap.h"
#include "lgi/common/TableLayout.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/Button.h"
///////////////////////////////////////////////////////////////////////////////////////////
#define GreyBackground()
struct LDialogPriv
{
int ModalStatus = 0;
int BtnId = -1;
bool IsModal = false;
bool IsModeless = false;
bool Resizable = true;
thread_id CallingThread = NULL;
/// The callback for the modal dialog:
LDialog::OnClose ModalCb;
/// This is set when the parent window has a pointer
/// to this dialog. We mustn't delete or change parent
/// while they have that.
bool ParentModal = false;
};
///////////////////////////////////////////////////////////////////////////////////////////
LDialog::LDialog(LViewI *parent)
:
#ifdef __GTK_H__
// , LWindow(gtk_dialog_new())
LWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
#endif
ResObject(Res_Dialog)
{
d = new LDialogPriv();
Name("Dialog");
if (parent)
SetParent(parent);
}
LDialog::~LDialog()
{
LAssert(!d->ParentModal);
DeleteObj(d);
}
bool LDialog::IsModal()
{
return d->IsModal;
}
int LDialog::GetButtonId()
{
return d->BtnId;
}
int LDialog::OnNotify(LViewI *Ctrl, LNotification n)
{
LButton *b = dynamic_cast(Ctrl);
if (b)
{
d->BtnId = b->GetId();
if (d->IsModal)
EndModal();
else if (d->IsModeless)
EndModeless();
}
return 0;
}
void LDialog::Quit(bool DontDelete)
{
if (d->IsModal)
EndModal(0);
else
LView::Quit(DontDelete);
}
void LDialog::OnPosChange()
{
LWindow::OnPosChange();
if (Children.Length() == 1)
{
List::I it = Children.begin();
LTableLayout *t = dynamic_cast((LViewI*)it);
if (t)
{
LRect r = GetClient();
r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing);
t->SetPos(r);
// _Dump();
}
}
}
bool LDialog::LoadFromResource(int Resource, char *TagList)
{
LAutoString n;
LRect p;
LProfile Prof("LDialog::LoadFromResource");
bool Status = LResourceLoad::LoadFromResource(Resource, this, &p, &n, TagList);
if (Status)
{
Prof.Add("Name.");
Name(n);
SetPos(p);
}
return Status;
}
bool LDialog::OnRequestClose(bool OsClose)
{
if (d->IsModal)
{
EndModal(0);
return false;
}
return true;
}
void LDialog::DoModal(OnClose Cb, OsView OverrideParent)
{
d->ModalStatus = -1;
auto Parent = GetParent();
if (Parent)
MoveSameScreen(Parent);
d->IsModal = true;
d->IsModeless = false;
d->ModalCb = Cb;
d->CallingThread = find_thread(NULL);
if (WindowHandle() &&
Parent &&
Parent->WindowHandle())
{
- #if 0
+ #if 1
// Keep this dialog above the parent window...
WindowHandle()->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL);
- WindowHandle()->AddToSubset(Parent->WindowHandle());
+ auto result = WindowHandle()->AddToSubset(Parent->WindowHandle());
+ printf("%s:%i - %s::AddToSubset=%i\n", _FL, GetClass(), result);
#else
auto Wnd = dynamic_cast(Parent);
LAssert(Wnd);
if (Wnd)
{
d->ParentModal = true;
Wnd->SetModalDialog(this);
}
#endif
}
else LgiTrace("%s:%i - Can't set parent for modal.\n", _FL);
BLooper *looper = BLooper::LooperForThread(d->CallingThread);
if (!looper)
printf("%s:%i - no looper for domodal thread.\n",_FL);
if (Attach(0))
{
AttachChildren();
Visible(true);
}
else printf("%s:%i - attach failed..\n", _FL);
}
void LDialog::EndModal(int Code)
{
if (!d->IsModal)
{
LgiTrace("%s:%i - EndModal error: LDialog is not model.\n", _FL);
return;
}
if (d->ParentModal)
{
auto Wnd = dynamic_cast(GetParent());
LAssert(Wnd);
if (Wnd)
{
Wnd->SetModalDialog(NULL);
d->ParentModal = false;
}
}
d->IsModal = false;
if (!d->ModalCb)
{
// If no callback is supplied, the default option is to just delete the
// dialog, closing it.
delete this;
return;
}
BLooper *looper = BLooper::LooperForThread(d->CallingThread);
if (!looper)
{
LgiTrace("%s:%i - Failed to get looper for %p\n", _FL, d->CallingThread);
delete this;
return;
}
BMessage *m = new BMessage(M_HANDLE_IN_THREAD);
m->AddPointer
(
LMessage::PropCallback,
new LMessage::InThreadCb
(
[dlg=this,cb=d->ModalCb,code=Code]()
{
// printf("%s:%i - Calling LDialog callback.. in original thread\n", _FL);
cb(dlg, code);
// printf("%s:%i - Calling LDialog callback.. done\n", _FL);
}
)
);
looper->PostMessage(m);
}
int LDialog::DoModeless()
{
d->IsModal = false;
d->IsModeless = true;
Visible(true);
return 0;
}
void LDialog::EndModeless(int Code)
{
Quit(Code);
}
extern LButton *FindDefault(LView *w);
LMessage::Param LDialog::OnEvent(LMessage *Msg)
{
return LView::OnEvent(Msg);
}
///////////////////////////////////////////////////////////////////////////////////////////
LControl::LControl(OsView view) : LView(view)
{
Pos.ZOff(10, 10);
}
LControl::~LControl()
{
}
LMessage::Param LControl::OnEvent(LMessage *Msg)
{
return 0;
}
LPoint LControl::SizeOfStr(const char *Str)
{
int y = LSysFont->GetHeight();
LPoint Pt(0, 0);
if (Str)
{
const char *e = 0;
for (const char *s = Str; s && *s; s = e?e+1:0)
{
e = strchr(s, '\n');
auto Len = e ? e - s : strlen(s);
LDisplayString ds(LSysFont, s, Len);
Pt.x = MAX(Pt.x, ds.X());
Pt.y += y;
}
}
return Pt;
}
diff --git a/src/haiku/Window.cpp b/src/haiku/Window.cpp
--- a/src/haiku/Window.cpp
+++ b/src/haiku/Window.cpp
@@ -1,1130 +1,1139 @@
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Token.h"
#include "lgi/common/Popup.h"
#include "lgi/common/Panel.h"
#include "lgi/common/Notifications.h"
#include "lgi/common/Menu.h"
#include "ViewPriv.h"
#include "MenuBar.h"
#define DEBUG_SETFOCUS 0
#define DEBUG_HANDLEVIEWKEY 0
#define DEBUG_WAIT_THREAD 1
#define DEBUG_SERIALIZE_STATE 0
#if DEBUG_WAIT_THREAD
#define WAIT_LOG(...) LgiTrace(__VA_ARGS__)
#else
#define WAIT_LOG(...)
#endif
LString ToString(BRect &r)
{
LString s;
s.Printf("%g,%g,%g,%g", r.left, r.top, r.right, r.bottom);
return s;
}
///////////////////////////////////////////////////////////////////////
class HookInfo
{
public:
LWindowHookType Flags;
LView *Target;
};
enum LAttachState
{
LUnattached,
LAttaching,
LAttached,
LDetaching,
};
class LWindowPrivate : public BWindow
{
public:
LWindow *Wnd;
bool SnapToEdge = false;
LArray Hooks;
LWindow *ModalDlg = NULL;
bool ShowTitleBar = true;
LWindowPrivate(LWindow *wnd) :
Wnd(wnd),
BWindow(BRect(100,100,400,400),
"...loading...",
B_DOCUMENT_WINDOW_LOOK,
B_NORMAL_WINDOW_FEEL,
0)
{
}
~LWindowPrivate()
{
LAssert(ModalDlg == NULL);
DeleteObj(Wnd->Menu);
if (IsMinimized())
Wnd->_PrevZoom = LZoomMin;
Wnd->d = NULL;
}
- window_feel DefaultFeel()
+ window_look DefaultLook()
{
if (ShowTitleBar)
return B_DOCUMENT_WINDOW_LOOK;
else
- return B_NO_BORDER_WINDOW_LOOK
+ return B_NO_BORDER_WINDOW_LOOK;
}
int GetHookIndex(LView *Target, bool Create = false)
{
for (int i=0; iTarget = Target;
n->Flags = LNoEvents;
return Hooks.Length() - 1;
}
}
return -1;
}
void FrameMoved(BPoint newPosition)
{
auto Pos = Frame();
if (Pos != Wnd->Pos)
{
Wnd->Pos = Pos;
Wnd->OnPosChange();
}
BWindow::FrameMoved(newPosition);
}
void FrameResized(float newWidth, float newHeight)
{
auto Pos = Frame();
if (Pos != Wnd->Pos)
{
Wnd->Pos.SetSize(newWidth, newHeight);
Wnd->OnPosChange();
}
BWindow::FrameResized(newWidth, newHeight);
}
bool QuitRequested()
{
printf("%s::QuitRequested() starting.. %s\n", Wnd->GetClass(), Wnd->Pos.GetStr());
auto r = Wnd->OnRequestClose(false);
// printf("%s::QuitRequested()=%i\n", Wnd->GetClass(), r);
return r;
}
void MessageReceived(BMessage *message)
{
if (message->what == M_LWINDOW_DELETE)
{
// printf("Processing M_LWINDOW_DELETE th=%u\n", GetCurrentThreadId());
Wnd->Handle()->RemoveSelf();
Quit();
// printf("Processed M_LWINDOW_DELETE\n");
}
else
{
BWindow::MessageReceived(message);
LView *view = NULL;
auto r = message->FindPointer(LMessage::PropView, &view);
if (r == B_OK)
view->OnEvent((LMessage*)message);
else
Wnd->OnEvent((LMessage*)message);
}
}
};
///////////////////////////////////////////////////////////////////////
LWindow::LWindow() : LView(0)
{
d = new LWindowPrivate(this);
_QuitOnClose = false;
Menu = NULL;
_Default = 0;
_Window = this;
ClearFlag(WndFlags, GWF_VISIBLE);
}
LWindow::~LWindow()
{
if (LAppInst->AppWnd == this)
LAppInst->AppWnd = NULL;
LAssert(!Menu);
WaitThread();
}
void LWindow::SetModalDialog(LWindow *dlg)
{
d->ModalDlg = dlg;
}
int LWindow::WaitThread()
{
if (!d)
return -1;
thread_id id = d->Thread();
bool thisThread = id == GetCurrentThreadId();
WAIT_LOG("%s::~LWindow thread=%u lock=%u\n", Name(), GetCurrentThreadId(), d->LockingThread());
if (thisThread)
{
// We are in thread... can delete easily.
if (d->Lock())
{
// printf("%s::~LWindow Quiting\n", Name());
Handle()->RemoveSelf();
d->Quit();
// printf("%s::~LWindow Quit finished\n", Name());
}
d = NULL;
return 0; // If we're in thread... no need to wait.
}
// Post event to the window's thread to delete itself...
WAIT_LOG("%s::~LWindow posting M_LWINDOW_DELETE from th=%u\n", Name(), GetCurrentThreadId());
d->PostMessage(new BMessage(M_LWINDOW_DELETE));
status_t value = 0;
WAIT_LOG("wait_for_thread(%u) start..\n", id);
wait_for_thread(id, &value);
WAIT_LOG("wait_for_thread(%u) end=%i\n", id, value);
d = NULL;
return value;
}
OsWindow LWindow::WindowHandle()
{
return d;
}
bool LWindow::SetTitleBar(bool ShowTitleBar)
{
if (!d)
return false;
d->ShowTitleBar = ShowTitleBar;
LLocker lck(d, _FL);
- auto r = d->SetFeel(d->DefaultFeel());
+ auto r = d->SetLook(d->DefaultLook());
if (r)
printf("%s:%i - SetFeel failed: %i\n", _FL, r);
return r == B_OK;
}
bool LWindow::SetIcon(const char *FileName)
{
LString a;
if (!LFileExists(FileName))
{
if (a = LFindFile(FileName))
FileName = a;
}
return false;
}
bool LWindow::GetSnapToEdge()
{
return d->SnapToEdge;
}
void LWindow::SetSnapToEdge(bool s)
{
d->SnapToEdge = s;
}
bool LWindow::IsActive()
{
LLocker lck(d, _FL);
if (!lck.Lock())
return false;
return d->IsActive();
}
bool LWindow::SetActive()
{
LLocker lck(d, _FL);
if (!lck.Lock())
return false;
d->Activate();
return true;
}
bool LWindow::Visible()
{
LLocker lck(d, _FL);
if (!lck.Lock())
return false;
return !d->IsHidden();
}
void LWindow::Visible(bool i)
{
LLocker lck(d, _FL);
if (!lck.Lock())
{
printf("%s:%i - Can't lock.\n", _FL);
return;
}
if (i)
{
if (d->IsHidden())
{
printf("%s show %s\n", GetClass(), GetPos().GetStr());
d->MoveTo(Pos.x1, Pos.y1);
d->ResizeTo(Pos.X(), Pos.Y());
d->Show();
}
else
printf("%s already shown\n", GetClass());
}
else
{
if (!d->IsHidden())
d->Hide();
}
}
bool LWindow::Obscured()
{
return false;
}
bool DndPointMap(LViewI *&v, LPoint &p, LDragDropTarget *&t, LWindow *Wnd, int x, int y)
{
LRect cli = Wnd->GetClient();
t = NULL;
v = Wnd->WindowFromPoint(x - cli.x1, y - cli.y1, false);
if (!v)
{
LgiTrace("%s:%i - @ %i,%i\n", _FL, x, y);
return false;
}
v->WindowVirtualOffset(&p);
p.x = x - p.x;
p.y = y - p.y;
for (LViewI *view = v; !t && view; view = view->GetParent())
t = view->DropTarget();
if (t)
return true;
LgiTrace("%s:%i - No target for %s\n", _FL, v->GetClass());
return false;
}
bool LWindow::Attach(LViewI *p)
{
LLocker lck(d, _FL);
if (!lck.Lock())
return false;
auto rootView = Handle();
auto wnd = WindowHandle();
// printf("%s:%i attach %p to %p\n", _FL, Handle(), WindowHandle());
if (rootView && wnd)
{
wnd->AddChild(rootView);
if (rootView->IsHidden())
rootView->Show();
auto menu = wnd->KeyMenuBar();
BRect menuPos = menu ? menu->Frame() : BRect(0, 0, 0, 0);
auto f = wnd->Frame();
rootView->ResizeTo(f.Width(), f.Height() - menuPos.Height());
if (menu)
rootView->MoveTo(0, menuPos.Height());
rootView->SetResizingMode(B_FOLLOW_ALL_SIDES);
}
// Setup default button...
if (!_Default)
_Default = FindControl(IDOK);
// Do a rough layout of child windows
PourAll();
return true;
}
bool LWindow::OnRequestClose(bool OsShuttingDown)
{
if (GetQuitOnClose())
LCloseApp();
return LView::OnRequestClose(OsShuttingDown);
}
bool LWindow::HandleViewMouse(LView *v, LMouse &m)
{
if (d->ModalDlg)
{
printf("%s:%i - %s ignoring mouse event while %s is shown.\n",
_FL,
GetClass(),
d->ModalDlg->GetClass());
return false;
}
if (m.Down() && !m.IsMove())
{
bool InPopup = false;
for (LViewI *p = v; p; p = p->GetParent())
{
if (dynamic_cast(p))
{
InPopup = true;
break;
}
}
if (!InPopup && LPopup::CurrentPopups.Length())
{
for (int i=0; iVisible())
p->Visible(false);
}
}
}
for (int i=0; iHooks.Length(); i++)
{
if (d->Hooks[i].Flags & LMouseEvents)
{
if (!d->Hooks[i].Target->OnViewMouse(v, m))
{
return false;
}
}
}
return true;
}
bool LWindow::HandleViewKey(LView *v, LKey &k)
{
bool Status = false;
LViewI *Ctrl = 0;
#if DEBUG_HANDLEVIEWKEY
bool Debug = 1; // k.vkey == LK_RETURN;
char SafePrint = k.c16 < ' ' ? ' ' : k.c16;
// if (Debug)
{
LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s\n",
v->GetClass(), v,
k.c16,
k.IsChar,
(char*)(k.Down()?" Down":" Up"),
(char*)(k.Shift()?" Shift":""),
(char*)(k.Alt()?" Alt":""),
(char*)(k.Ctrl()?" Ctrl":""));
}
#endif
if (d->ModalDlg)
{
printf("%s:%i - %s ignoring key event while %s is shown.\n",
_FL,
GetClass(),
d->ModalDlg->GetClass());
return false;
}
// Any window in a popup always gets the key...
LViewI *p;
for (p = v->GetParent(); p; p = p->GetParent())
{
if (dynamic_cast(p))
{
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tSending key to popup\n");
#endif
return v->OnKey(k);
}
}
// Give key to popups
if (LAppInst &&
LAppInst->GetMouseHook() &&
LAppInst->GetMouseHook()->OnViewKey(v, k))
{
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tMouseHook got key\n");
#endif
goto AllDone;
}
// Allow any hooks to see the key...
for (int i=0; iHooks.Length(); i++)
{
#if DEBUG_HANDLEVIEWKEY
// if (Debug) LgiTrace("\tHook[%i]\n", i);
#endif
if (d->Hooks[i].Flags & LKeyEvents)
{
LView *Target = d->Hooks[i].Target;
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tHook[%i].Target=%p %s\n", i, Target, Target->GetClass());
#endif
if (Target->OnViewKey(v, k))
{
Status = true;
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tHook[%i] ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", i, SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift());
#endif
goto AllDone;
}
}
}
// Give the key to the window...
if (v->OnKey(k))
{
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tView ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n",
SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift());
#endif
Status = true;
goto AllDone;
}
#if DEBUG_HANDLEVIEWKEY
else if (Debug)
LgiTrace("\t%s didn't eat '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n",
v->GetClass(), SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift());
#endif
// Window didn't want the key...
switch (k.vkey)
{
case LK_RETURN:
#ifdef LK_KEYPADENTER
case LK_KEYPADENTER:
#endif
{
Ctrl = _Default;
break;
}
case LK_ESCAPE:
{
Ctrl = FindControl(IDCANCEL);
break;
}
}
// printf("Ctrl=%p\n", Ctrl);
if (Ctrl)
{
if (Ctrl->Enabled())
{
if (Ctrl->OnKey(k))
{
Status = true;
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tDefault Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n",
SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift());
#endif
goto AllDone;
}
// else printf("OnKey()=false\n");
}
// else printf("Ctrl=disabled\n");
}
#if DEBUG_HANDLEVIEWKEY
else if (Debug)
LgiTrace("\tNo default ctrl to handle key.\n");
#endif
if (Menu)
{
Status = Menu->OnKey(v, k);
if (Status)
{
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tMenu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift());
#endif
}
}
// Tab through controls
if (k.vkey == LK_TAB && k.Down() && !k.IsChar)
{
LViewI *Wnd = GetNextTabStop(v, k.Shift());
#if DEBUG_HANDLEVIEWKEY
if (Debug)
LgiTrace("\tTab moving focus shift=%i Wnd=%p\n", k.Shift(), Wnd);
#endif
if (Wnd)
Wnd->Focus(true);
}
// Control shortcut?
if (k.Down() && k.Alt() && k.c16 > ' ')
{
ShortcutMap Map;
BuildShortcuts(Map);
LViewI *c = Map.Find(ToUpper(k.c16));
if (c)
{
c->OnNotify(c, LNotifyActivate);
return true;
}
}
AllDone:
return Status;
}
void LWindow::Raise()
{
}
LWindowZoom LWindow::GetZoom()
{
if (!d)
return _PrevZoom;
LLocker lck(d, _FL);
if (!IsAttached() || lck.Lock())
{
if (d->IsMinimized())
return LZoomMin;
}
return LZoomNormal;
}
void LWindow::SetZoom(LWindowZoom i)
{
LLocker lck(d, _FL);
if (lck.Lock())
{
d->Minimize(i == LZoomMin);
}
}
LViewI *LWindow::GetDefault()
{
return _Default;
}
void LWindow::SetDefault(LViewI *v)
{
if (v &&
v->GetWindow() == this)
{
if (_Default != v)
{
LViewI *Old = _Default;
_Default = v;
if (Old) Old->Invalidate();
if (_Default) _Default->Invalidate();
}
}
else
{
_Default = 0;
}
}
bool LWindow::Name(const char *n)
{
LLocker lck(d, _FL);
if (lck.Lock())
d->SetTitle(n);
return LBase::Name(n);
}
const char *LWindow::Name()
{
return LBase::Name();
}
LPoint LWindow::GetDpi()
{
return LPoint(96, 96);
}
LPointF LWindow::GetDpiScale()
{
auto Dpi = GetDpi();
return LPointF((double)Dpi.x/96.0, (double)Dpi.y/96.0);
}
LRect &LWindow::GetClient(bool ClientSpace)
{
static LRect r;
r = Pos.ZeroTranslate();
LLocker lck(WindowHandle(), _FL);
if (lck.Lock())
{
auto br = Handle()->Bounds();
r = br;
auto frm = d->Frame();
frm.OffsetBy(-frm.left, -frm.top);
// printf("Frame=%s Bounds=%s r=%s\n", ToString(frm).Get(), ToString(br).Get(), r.GetStr());
lck.Unlock();
}
return r;
}
#if DEBUG_SERIALIZE_STATE
#define SERIALIZE_LOG(...) printf(__VA_ARGS__)
#else
#define SERIALIZE_LOG(...)
#endif
bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load)
{
if (!Store || !FieldName)
return false;
if (Load)
{
::LVariant v;
if (Store->GetValue(FieldName, v) && v.Str())
{
LRect Position(0, 0, -1, -1);
LWindowZoom State = LZoomNormal;
auto vars = LString(v.Str()).SplitDelimit(";");
SERIALIZE_LOG("SerializeState: %s=%s, vars=%i\n", FieldName, v.Str(), (int)vars.Length());
for (auto var: vars)
{
auto parts = var.SplitDelimit("=", 1);
SERIALIZE_LOG("SerializeState: parts=%i\n", (int)parts.Length());
if (parts.Length() == 2)
{
if (parts[0].Equals("State"))
{
State = (LWindowZoom) parts[1].Int();
SERIALIZE_LOG("SerializeState: part=%s state=%i\n", parts[0].Get(), State);
}
else if (parts[0].Equals("Pos"))
{
Position.SetStr(parts[1]);
SERIALIZE_LOG("SerializeState: part=%s pos=%s\n", parts[0].Get(), Position.GetStr());
}
}
}
if (Position.Valid())
{
SERIALIZE_LOG("SerializeState setpos %s\n", Position.GetStr());
SetPos(Position);
}
SetZoom(State);
}
else
{
SERIALIZE_LOG("SerializeState: no '%s' var\n", FieldName);
return false;
}
}
else
{
char s[256];
LWindowZoom State = GetZoom();
sprintf_s(s, sizeof(s), "State=%i;Pos=%s", State, GetPos().GetStr());
LVariant v = s;
SERIALIZE_LOG("SerializeState: saving '%s' = '%s'\n", FieldName, s);
if (!Store->SetValue(FieldName, v))
{
SERIALIZE_LOG("SerializeState: SetValue failed\n");
return false;
}
}
return true;
}
LRect &LWindow::GetPos()
{
return Pos;
}
bool LWindow::SetPos(LRect &p, bool Repaint)
{
Pos = p;
LLocker lck(d, _FL);
if (lck.Lock())
{
d->MoveTo(Pos.x1, Pos.y1);
d->ResizeTo(Pos.X(), Pos.Y());
}
else printf("%s:%i - Failed to lock.\n", _FL);
return true;
}
void LWindow::OnChildrenChanged(LViewI *Wnd, bool Attaching)
{
// Force repour
}
void LWindow::OnCreate()
{
AttachChildren();
}
void LWindow::OnPaint(LSurface *pDC)
{
pDC->Colour(L_MED);
pDC->Rectangle();
}
void LWindow::OnPosChange()
{
LLocker lck(WindowHandle(), _FL);
if (lck.Lock())
{
auto frame = WindowHandle()->Bounds();
auto menu = WindowHandle()->KeyMenuBar();
auto menuPos = menu ? menu->Frame() : BRect(0, 0, 0, 0);
auto rootPos = Handle()->Frame();
if (menu)
{
if (menu->IsHidden()) // Why?
{
menu->Show();
if (menu->IsHidden())
{
// printf("Can't show menu?\n");
for (auto p = menu->Parent(); p; p = p->Parent())
printf(" par=%s %i\n", p->Name(), p->IsHidden());
}
}
if (menuPos.Width() < 1) // Again... WHHHHY? FFS
{
float x = 0.0f, y = 0.0f;
menu->GetPreferredSize(&x, &y);
// printf("Pref=%g,%g\n", x, y);
if (y > 0.0f)
menu->ResizeTo(frame.Width(), y);
}
}
#if 0
printf("frame=%s menu=%p,%i,%s rootpos=%s\n",
ToString(frame).Get(), menu, menu?menu->IsHidden():0, ToString(menuPos).Get(), ToString(rootPos).Get());
#endif
int rootTop = menu ? menuPos.bottom + 1 : 0;
if (rootPos.top != rootTop)
{
Handle()->MoveTo(0, rootTop);
Handle()->ResizeTo(rootPos.Width(), frame.Height() - menuPos.Height());
}
lck.Unlock();
}
LView::OnPosChange();
PourAll();
}
#define IsTool(v) \
( \
dynamic_cast(v) \
&& \
dynamic_cast(v)->_IsToolBar \
)
void LWindow::PourAll()
{
LRect c = GetClient();
LRegion Client(c);
LViewI *MenuView = 0;
LRegion Update(Client);
bool HasTools = false;
LViewI *v;
List::I Lst = Children.begin();
{
LRegion Tools;
for (v = *Lst; v; v = *++Lst)
{
bool IsMenu = MenuView == v;
if (!IsMenu && IsTool(v))
{
LRect OldPos = v->GetPos();
if (OldPos.Valid())
Update.Union(&OldPos);
if (HasTools)
{
// 2nd and later toolbars
if (v->Pour(Tools))
{
if (!v->Visible())
{
v->Visible(true);
}
auto vpos = v->GetPos();
if (OldPos != vpos)
{
// position has changed update...
v->Invalidate();
}
// Has it increased the size of the toolbar area?
auto b = Tools.Bound();
if (vpos.y2 >= b.y2)
{
LRect Bar = Client;
Bar.y2 = vpos.y2;
Client.Subtract(&Bar);
// LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr());
}
Tools.Subtract(&vpos);
Update.Subtract(&vpos);
// LgiTrace("vpos=%s\n", vpos.GetStr());
}
}
else
{
// First toolbar
if (v->Pour(Client))
{
HasTools = true;
if (!v->Visible())
{
v->Visible(true);
}
if (OldPos != v->GetPos())
{
v->Invalidate();
}
LRect Bar(v->GetPos());
// LgiTrace("%s = %s\n", v->GetClass(), Bar.GetStr());
Bar.x2 = GetClient().x2;
Tools = Bar;
Tools.Subtract(&v->GetPos());
Client.Subtract(&Bar);
Update.Subtract(&Bar);
}
}
}
}
}
Lst = Children.begin();
for (LViewI *v = *Lst; v; v = *++Lst)
{
bool IsMenu = MenuView == v;
if (!IsMenu && !IsTool(v))
{
LRect OldPos = v->GetPos();
if (OldPos.Valid())
Update.Union(&OldPos);
if (v->Pour(Client))
{
// LRect p = v->GetPos();
// LgiTrace("%s = %s\n", v->GetClass(), p.GetStr());
if (!v->Visible())
v->Visible(true);
v->Invalidate();
Client.Subtract(&v->GetPos());
Update.Subtract(&v->GetPos());
}
else
{
// non-pourable
}
}
}
for (int i=0; iMsg())
{
case M_CLOSE:
{
if (OnRequestClose(false))
{
Quit();
return 0;
}
break;
}
}
return LView::OnEvent(m);
}
bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority)
{
bool Status = false;
if (Target && EventType)
{
int i = d->GetHookIndex(Target, true);
if (i >= 0)
{
d->Hooks[i].Flags = EventType;
Status = true;
}
}
return Status;
}
bool LWindow::UnregisterHook(LView *Target)
{
int i = d->GetHookIndex(Target);
if (i >= 0)
{
d->Hooks.DeleteAt(i);
return true;
}
return false;
}
void LWindow::OnFrontSwitch(bool b)
{
}
-void LWindow::SetWillFocus(bool f)
+bool LWindow::SetWillFocus(bool f)
{
if (!d)
- return;
+ return false;
LLocker lck(d, _FL);
auto flags = d->Flags();
if (f)
flags &= ~B_AVOID_FOCUS; // clear flag
else
flags |= B_AVOID_FOCUS; // set flag
auto r = d->SetFlags(flags);
if (r)
printf("%s:%i - SetFlags failed: %i\n", _FL, r);
+
+ return true;
}
LViewI *LWindow::GetFocus()
{
if (!d)
return NULL;
LLocker lck(d, _FL);
- auto f = d->CurrentFocus();
-
+ if (!lck.Lock())
+ return NULL;
+
+ auto f = d->CurrentFocus();
return LViewFromHandle(f);
}
void LWindow::SetFocus(LViewI *ctrl, FocusType type)
{
if (!ctrl)
return;
- ctrl->Focus(true);
+ LLocker lck(d, _FL);
+ if (lck.Lock())
+ {
+ auto h = ctrl->Handle();
+ h->MakeFocus(type == GainFocus ? true : false);
+ }
}
void LWindow::SetDragHandlers(bool On)
{
}
void LWindow::OnTrayClick(LMouse &m)
{
if (m.Down() || m.IsContextMenu())
{
LSubMenu RClick;
OnTrayMenu(RClick);
if (GetMouse(m, true))
{
int Result = RClick.Float(this, m.x, m.y);
OnTrayMenuResult(Result);
}
}
}
void LWindow::SetAlwaysOnTop(bool b)
{
if (!d)
return;
LLocker lck(d, _FL);
status_t r;
if (b)
r = d->SetFeel(B_FLOATING_APP_WINDOW_FEEL);
else
- r = d->SetFeel(d->DefaultFeel());
+ r = d->SetFeel(B_NORMAL_WINDOW_FEEL);
if (r)
printf("%s:%i - SetFeel failed: %i\n", _FL, r);
}