diff --git a/Lgi.xml b/Lgi.xml
--- a/Lgi.xml
+++ b/Lgi.xml
@@ -1,562 +1,563 @@
+
+
+
+
-
-
-
-
+
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
include/lgi/win
/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`
lgi-gtk3
lgi-gtk3
DynamicLibrary
diff --git a/include/lgi/common/DocView.h b/include/lgi/common/DocView.h
--- a/include/lgi/common/DocView.h
+++ b/include/lgi/common/DocView.h
@@ -1,543 +1,526 @@
/// \file
/// \author Matthew Allen (fret@memecode.com)
/// \brief This is the base data and code for all the text controls (inc. HTML)
#ifndef __GDOCVIEW_H
#define __GDOCVIEW_H
#include "lgi/common/Variant.h"
#include "lgi/common/Notifications.h"
#include "lgi/common/Thread.h"
#include "lgi/common/Layout.h"
// Word wrap
enum LDocWrapType
{
/// No word wrapping
TEXTED_WRAP_NONE = 0,
/// Dynamically wrap line to editor width
TEXTED_WRAP_REFLOW = 1,
};
// Util macros
/// Returns true if 'c' is whitespace
#define IsWhiteSpace(c) ((c) < 126 && strchr(LDocView::WhiteSpace, c) != 0)
/// Returns true if 'c' is a delimiter
#define IsDelimiter(c) ((c) < 126 && strchr(LDocView::Delimiters, c) != 0)
/// Returns true if 'c' is a letter or number
#define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_')
/// Returns true if 'c' is word boundry
#define IsWordBoundry(c) (strchr(LDocView::WhiteSpace, c) || strchr(LDocView::Delimiters, c))
/// Returns true if 'c' is alphanumeric or a digit
#define AlphaOrDigit(c) (IsDigit(c) || IsAlpha(c))
/// Returns true if 'c' is a valid URL character
#define UrlChar(c) ( \
strchr(LDocView::UrlDelim, (c)) || \
AlphaOrDigit((c)) || \
((c) >= 256) \
)
/// Returns true if 'c' is email address character
#define EmailChar(c) (strchr("._-:+", (c)) || AlphaOrDigit((c)))
LgiFunc char16 *ConvertToCrLf(char16 *Text);
/// This class contains information about a link.
/// \sa LDetectLinks
struct LLinkInfo
{
ssize_t Start;
ssize_t Len;
bool Email;
void Set(ssize_t start, ssize_t len, bool email)
{
Start = start;
Len = len;
Email = email;
}
};
// Call back class to handle viewer events
class LDocView;
/// An environment class to handle requests from the text view to the outside world.
class LgiClass
LDocumentEnv : public LThreadOwner
{
LArray Viewers;
public:
LDocumentEnv(LDocView *v = 0);
virtual ~LDocumentEnv();
enum LoadType
{
LoadError,
LoadNotImpl,
LoadImmediate,
LoadDeferred,
};
struct
#ifdef MAC
LgiClass
#endif
LoadJob : public LThreadJob
{
enum PrefFormat
{
FmtNone,
FmtStream,
FmtSurface,
FmtFilename,
};
enum JobStatus
{
JobInit,
JobOk,
JobErr_Uri,
JobErr_Path,
JobErr_FileOpen,
JobErr_GetUri,
JobErr_NoCachedFile,
JobErr_ImageFilter,
JobErr_NoMem,
};
// View data
LDocumentEnv *Env;
void *UserData;
uint32_t UserUid;
PrefFormat Pref;
// Input data
LAutoString Uri;
LAutoString PostData;
// Output data
LAutoPtr Stream;
LAutoPtr pDC;
LString Filename;
LString Error;
JobStatus Status;
LString MimeType, ContentId;
LoadJob(LThreadTarget *o) : LThreadJob(o)
{
Env = NULL;
UserUid = 0;
UserData = NULL;
Pref = FmtNone;
Status = JobInit;
}
LStreamI *GetStream()
{
if (!Stream && Filename)
{
LFile *file = new LFile;
if (file && file->Open(Filename, O_READ))
Stream.Reset(file);
else
DeleteObj(file);
}
return Stream;
}
};
LoadJob *NewJob()
{
return new LoadJob(this);
}
bool AttachView(LDocView *v)
{
if (!v)
return false;
if (!Lock(_FL))
return false;
LAssert(!Viewers.HasItem(v));
Viewers.Add(v);
Unlock();
return true;
}
bool DetachView(LDocView *v)
{
if (!v)
return false;
if (!Lock(_FL))
return false;
LAssert(Viewers.HasItem(v));
Viewers.Delete(v);
Unlock();
return true;
}
int NextUid();
/// Creating a context menu, usually when the user right clicks on the
/// document.
virtual bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) { return false; }
/// Do something when the menu items created by LDocumentEnv::AppendItems
/// are clicked.
virtual bool OnMenu(LDocView *View, int Id, void *Context) { return false; }
/// Asks the env to get some data linked from the document, e.g. a css file or an iframe source etc.
/// If the GetContent implementation takes ownership of the job pointer then it should set 'j' to NULL.
virtual LoadType GetContent(LoadJob *&j) { return LoadNotImpl; }
/// After the env's thread loads the resource it calls this to pass it to the doc
void OnDone(LAutoPtr j);
/// Handle a click on URI
virtual bool OnNavigate(LDocView *Parent, const char *Uri) { return false; }
/// Handle a form post
virtual bool OnPostForm(LDocView *Parent, const char *Uri, const char *Data) { return false; }
/// Process dynamic content, returning a dynamically allocated string
/// for the result of the executed script. Dynamic content is enclosed
/// between <? and ?>.
virtual char *OnDynamicContent(LDocView *Parent, const char *Code) { return 0; }
/// Some script was received, the owner should compile it
virtual bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { return false; }
/// Some script needs to be executed, the owner should compile it
virtual bool OnExecuteScript(LDocView *Parent, char *Script) { return false; }
};
/// Default text view environment
///
/// This class defines the default behavior of the environment,
/// However you will need to instantiate this yourself and call
/// SetEnv with your instance. i.e. it's not automatic.
class LgiClass LDefaultDocumentEnv :
public LDocumentEnv
{
public:
LoadType GetContent(LoadJob *&j);
bool OnNavigate(LDocView *Parent, const char *Uri);
};
/// Find params
class LDocFindReplaceParams
{
public:
virtual ~LDocFindReplaceParams() {}
};
/// TextView class is a base for all text controls
class LgiClass LDocView :
public LLayout,
virtual public LDom
{
friend class LDocumentEnv;
protected:
- LDocumentEnv *Environment;
+ LDocumentEnv *Environment = NULL;
LString Charset;
public:
// Static
static const char *WhiteSpace;
static const char *Delimiters;
static const char *UrlDelim;
///////////////////////////////////////////////////////////////////////
// Properties
- #define _TvMenuProp(Type, Name) \
+ #define _TvMenuProp(Type, Name, Default) \
protected: \
- Type Name; \
+ Type Name = Default; \
public: \
virtual void Set##Name(Type i) { Name=i; } \
Type Get##Name() { return Name; }
- _TvMenuProp(uint16, WrapAtCol)
- _TvMenuProp(bool, UrlDetect)
- _TvMenuProp(bool, ReadOnly)
- _TvMenuProp(LDocWrapType, WrapType)
- _TvMenuProp(uint8_t, TabSize)
- _TvMenuProp(uint8_t, IndentSize)
- _TvMenuProp(bool, HardTabs)
- _TvMenuProp(bool, ShowWhiteSpace)
- _TvMenuProp(bool, ObscurePassword)
- _TvMenuProp(bool, CrLf)
- _TvMenuProp(bool, AutoIndent)
- _TvMenuProp(bool, FixedWidthFont)
- _TvMenuProp(bool, LoadImages)
- _TvMenuProp(bool, OverideDocCharset)
+ _TvMenuProp(uint16, WrapAtCol, 0)
+ _TvMenuProp(bool, UrlDetect, true)
+ _TvMenuProp(bool, ReadOnly, false)
+ _TvMenuProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW)
+ _TvMenuProp(uint8_t, TabSize, 4)
+ _TvMenuProp(uint8_t, IndentSize, 4)
+ _TvMenuProp(bool, HardTabs, true)
+ _TvMenuProp(bool, ShowWhiteSpace, false)
+ _TvMenuProp(bool, ObscurePassword, false)
+ _TvMenuProp(bool, CrLf, false)
+ _TvMenuProp(bool, AutoIndent, true)
+ _TvMenuProp(bool, FixedWidthFont, false)
+ _TvMenuProp(bool, LoadImages, false)
+ _TvMenuProp(bool, OverideDocCharset, false)
// This UID is used to match data load events with their source document.
// Sometimes data will arrive after the document that asked for it has
// already been unloaded. So by assigned each document an UID we can check
// the job UID against it and discard old data.
- _TvMenuProp(int, DocumentUid)
+ _TvMenuProp(int, DocumentUid, 0)
#undef _TvMenuProp
virtual const char *GetCharset()
{
return Charset.Get() ? Charset.Get() : "utf-8";
}
virtual void SetCharset(const char *s)
{
Charset = s;
}
virtual const char *GetMimeType() = 0;
///////////////////////////////////////////////////////////////////////
// Object
- LDocView(LDocumentEnv *e = 0)
+ LDocView(LDocumentEnv *e = NULL)
{
- WrapAtCol = 0;
- UrlDetect = true;
- ReadOnly = false;
- WrapType = TEXTED_WRAP_REFLOW;
- TabSize = 4;
- IndentSize = 4;
- HardTabs = true;
- ShowWhiteSpace = false;
- ObscurePassword = false;
- CrLf = false;
- AutoIndent = true;
- FixedWidthFont = false;
- LoadImages = false;
- OverideDocCharset = false;
-
- Environment = 0;
-
SetEnv(e);
}
virtual ~LDocView()
{
SetEnv(0);
}
const char *GetClass() { return "LDocView"; }
/// Open a file handler
virtual bool Open(const char *Name, const char *Cs = 0) { return false; }
/// Save a file handler
virtual bool Save(const char *Name, const char *Cs = 0) { return false; }
///////////////////////////////////////////////////////////////////////
/// Find window handler
virtual bool DoFind() { return false; }
/// Replace window handler
virtual bool DoReplace() { return false; }
virtual LDocFindReplaceParams *CreateFindReplaceParams() { return 0; }
virtual void SetFindReplaceParams(LDocFindReplaceParams *Params) { }
///////////////////////////////////////////////////////////////////////
/// Get the current environment
virtual LDocumentEnv *GetEnv() { return Environment; }
/// Set the current environment
virtual void SetEnv(LDocumentEnv *e)
{
if (Environment) Environment->DetachView(this);
Environment = e;
if (Environment) Environment->AttachView(this);
}
/// When the env has loaded a resource it can pass it to the doc control via this method.
/// It MUST be thread safe. Often an environment will call this function directly from
/// it's worker thread.
virtual void OnContent(LDocumentEnv::LoadJob *Res) {}
///////////////////////////////////////////////////////////////////////
// State / Selection
/// Set the cursor position, to select an area, move the cursor with Select=false
/// then set the other end of the region with Select=true.
virtual void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) {}
/// Cursor=false means the other end of the selection if any. The cursor is alwasy
/// at one end of the selection.
virtual ssize_t GetCaret(bool Cursor = true) { return 0; }
/// True if there is a selection
virtual bool HasSelection() { return false; }
/// Unselect all the text
virtual void UnSelectAll() {}
/// Select the word from index 'From'
virtual void SelectWord(size_t From) {}
/// Select all the text in the control
virtual void SelectAll() {}
/// Get the selection as a dynamicially allocated utf-8 string
virtual char *GetSelection() { return 0; }
/// Returns the character index at the x,y location
virtual ssize_t IndexAt(int x, int y) { return 0; }
/// Index=-1 returns the x,y of the cursor, Index >=0 returns the specified x,y
virtual bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) { return false; }
/// True if the document has changed
virtual bool IsDirty() { return false; }
/// Gets the number of lines of text
virtual size_t GetLines() { return 0; }
/// Gets the pixels required to display all the text
virtual void GetTextExtent(int &x, int &y) {}
///////////////////////////////////////////////////////////////////////
/// Cuts the selection from the document and puts it on the clipboard
virtual bool Cut() { return false; }
/// Copies the selection from the document to the clipboard
virtual bool Copy() { return false; }
/// Pastes the current contents of the clipboard into the document
virtual bool Paste() { return false; }
///////////////////////////////////////////////////////////////////////
/// Called when the user hits the escape key
virtual void OnEscape(LKey &K) {}
/// Called when the user hits the enter key
virtual void OnEnter(LKey &k) {}
/// Called when the user clicks a URL
virtual void OnUrl(char *Url) {}
/// Called to add styling to the document
virtual void OnAddStyle(const char *MimeType, const char *Styles) {}
///////////////////////////////////////////////////////////////////////
struct ContentMedia
{
LString Id;
LString FileName;
LString MimeType;
LVariant Data;
LAutoPtr Stream;
bool Valid()
{
return MimeType.Get() != NULL &&
FileName.Get() != NULL &&
(
(Data.Type == GV_BINARY && Data.Value.Binary.Data != NULL)
||
(Stream.Get() != NULL)
);
}
};
/// Gets the document in format of a desired MIME type
virtual bool GetFormattedContent
(
/// [In] The desired mime type of the content
const char *MimeType,
/// [Out] The content in the specified mime type
LString &Out,
/// [Out/Optional] Any attached media files that the content references
LArray *Media = NULL
)
{ return false; }
};
/// Detects links in text, returning their location and type
template
bool LDetectLinks(LArray &Links, T *Text, ssize_t TextCharLen = -1)
{
if (!Text)
return false;
if (TextCharLen < 0)
TextCharLen = Strlen(Text);
T *End = Text + TextCharLen;
static T Http[] = {'h', 't', 't', 'p', ':', '/', '/', 0 };
static T Https[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0};
for (int64 i=0; i= 7
&&
(
Strnicmp(Text+i, Http, 6) == 0 ||
Strnicmp(Text+i, Https, 7) == 0
)
)
{
// find end
T *s = Text + i;
T *e = s + 6;
for ( ; e < End && UrlChar(*e); e++)
;
while
(
e > s &&
!
(
IsAlpha(e[-1]) ||
IsDigit(e[-1]) ||
e[-1] == '/'
)
)
e--;
Links.New().Set(s - Text, e - s, false);
i = e - Text;
}
break;
}
case '@':
{
// find start
T *s = Text + (MAX(i, 1) - 1);
for ( ; s > Text && EmailChar(*s); s--)
;
if (s < Text + i)
{
if (!EmailChar(*s))
s++;
bool FoundDot = false;
T *Start = Text + i + 1;
T *e = Start;
for ( ;
e < End && EmailChar(*e);
e++)
{
if (*e == '.')
FoundDot = true;
}
while (e > Start && e[-1] == '.')
e--;
if (FoundDot)
{
Links.New().Set(s - Text, e - s, true);
i = e - Text;
}
}
break;
}
}
}
return true;
}
#endif
diff --git a/include/lgi/common/TextLog.h b/include/lgi/common/TextLog.h
--- a/include/lgi/common/TextLog.h
+++ b/include/lgi/common/TextLog.h
@@ -1,171 +1,170 @@
/// \file
/// \author Matthew Allen
#ifndef _LTEXTLOG_H_
#define _LTEXTLOG_H_
#include "lgi/common/TextView3.h"
#include "lgi/common/Net.h"
template
class LThreadSafeTextView : public TView, public LStream
{
protected:
bool ProcessReturns;
size_t Pos;
LMutex Sem;
LArray Txt;
void ProcessTxt()
{
if (Txt.Length() == 0)
return;
LArray Local;
if (Sem.LockWithTimeout(250, _FL))
{
// Minimize time locked by moving the text to a local var
Local.Swap(Txt);
Sem.Unlock();
}
else
{
TView::PostEvent(M_LOG_TEXT); // Try again later...
return;
}
if (Local.Length())
{
LString msg;
msg.Printf("LTextLog::ProcessTxt(%" PRIu64 ")", (uint64)Txt.Length());
LProfile p(msg, 200);
Add(Local.AddressOf(), Local.Length());
}
}
public:
LThreadSafeTextView(int id) : TView(id, 0, 0, 2000, 1000), Sem("LThreadSafeTextView")
{
ProcessReturns = true;
Pos = 0;
TView::Sunken(true);
TView::SetPourLargest(true);
TView::SetUndoOn(false);
TView::SetWrapType(TEXTED_WRAP_NONE);
}
void OnCreate()
{
TView::OnCreate();
// ProcessTxt();
- TView::SetPulse(250);
}
void OnPulse()
{
ProcessTxt();
}
virtual void Add(char16 *w, ssize_t chars = -1)
{
ssize_t Len = chars >= 0 ? chars : StrlenW(w);
bool AtEnd = TView::GetCaret() == TView::Size;
if (ProcessReturns)
{
auto *s = w, *prev = w;
auto Ins = [this](char16 *s, ssize_t len)
{
if (len > 0)
{
auto del = MIN(TView::Size - (ssize_t)Pos, len);
if (del > 0)
TView::Delete(Pos, del);
TView::Insert(Pos, s, len);
Pos += len;
}
};
for (s = w;
chars >= 0 ? s < w + chars : *s;
s++)
{
if (*s == '\r')
{
Ins(prev, s - prev);
prev = s + 1;
while (Pos > 0 && TView::Text[Pos-1] != '\n')
Pos--;
}
else if (*s == '\n')
{
Pos = TView::Size;
}
}
Ins(prev, s - prev);
}
else
{
TView::Insert(TView::Size, w, Len);
}
TView::Invalidate();
if (AtEnd)
TView::SetCaret(TView::Size, false);
}
int64 SetSize(int64 s)
{
TView::Name(0);
return 0;
}
bool Write(const LString &s)
{
return Write(s.Get(), s.Length()) == s.Length();
}
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
{
LAutoWString w(Utf8ToWide((char*)Buffer, Size));
if (!w)
return 0;
size_t OldLen = 0;
if (Sem.Lock(_FL))
{
OldLen = Txt.Length();
Txt.Add(w.Get(), Strlen(w.Get()));
Sem.Unlock();
}
if (!OldLen
#if LGI_VIEW_HANDLE
&& TView::Handle()
#endif
)
{
TView::PostEvent(M_LOG_TEXT);
}
return Size;
}
LMessage::Result OnEvent(LMessage *m)
{
if (m->Msg() == M_LOG_TEXT)
{
// ProcessTxt();
}
return TView::OnEvent(m);
}
};
typedef LThreadSafeTextView LTextLog;
#ifdef _GTEXTVIEW4_H
typedef LThreadSafeTextView LTextLog4;
#endif
#endif
diff --git a/include/lgi/common/TextView3.h b/include/lgi/common/TextView3.h
--- a/include/lgi/common/TextView3.h
+++ b/include/lgi/common/TextView3.h
@@ -1,437 +1,437 @@
/// \file
/// \author Matthew Allen
/// \brief A unicode text editor
#ifndef __GTEXTVIEW3_H
#define __GTEXTVIEW3_H
#include "lgi/common/DocView.h"
#include "lgi/common/Undo.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Css.h"
#include "lgi/common/UnrolledList.h"
#include "lgi/common/FindReplaceDlg.h"
// use CRLF as opposed to just LF
// internally it uses LF only... this is just to remember what to
// save out as.
#define TEXTED_USES_CR 0x00000001
#define TAB_SIZE 4
#define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str
extern char Delimiters[];
class LTextView3;
/// Unicode text editor control.
class LgiClass
LTextView3 :
public LDocView,
public ResObject,
public LDragDropTarget
{
friend struct LTextView3Undo;
friend bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User);
public:
enum Messages
{
M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421,
M_TEXTVIEW_FIND,
M_TEXTVIEW_REPLACE,
M_TEXT_POUR_CONTINUE,
};
enum StyleOwners
{
STYLE_NONE,
STYLE_IDE,
STYLE_SPELLING,
STYLE_FIND_MATCHES,
STYLE_ADDRESS,
STYLE_URL,
};
class LStyle
{
protected:
void RefreshLayout(size_t Start, ssize_t Len);
public:
/// The view the style is for
LTextView3 *View;
/// When you write several bits of code to do styling assign them
/// different owner id's so that they can manage the lifespan of their
/// own styles. LTextView3::PourStyle is owner '0', anything else it
/// will leave alone.
StyleOwners Owner;
/// The start index into the text buffer of the region to style.
ssize_t Start;
/// The length of the styled region
ssize_t Len;
/// The font to draw the styled text in
LFont *Font;
/// The colour to draw with. If transparent, then the default
/// line colour is used.
LColour Fore, Back;
/// Cursor
LCursor Cursor;
/// Optional extra decor not supported by the fonts
LCss::TextDecorType Decor;
/// Colour for the optional decor.
LColour DecorColour;
/// Application base data
LVariant Data;
LStyle(StyleOwners owner = STYLE_NONE)
{
Owner = owner;
View = NULL;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
}
LStyle(const LStyle &s)
{
Owner = s.Owner;
View = s.View;
Font = s.Font;
Start = s.Start;
Len = s.Len;
Decor = s.Decor;
DecorColour = s.DecorColour;
Fore = s.Fore;
Back = s.Back;
Data = s.Data;
Cursor = s.Cursor;
}
LStyle &Construct(LTextView3 *view, StyleOwners owner)
{
View = view;
Owner = owner;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
return *this;
}
void Empty()
{
Start = -1;
Len = 0;
}
bool Valid()
{
return Start >= 0 && Len > 0;
}
size_t End() const { return Start + Len; }
/// \returns true if style is the same
bool operator ==(const LStyle &s)
{
return Owner == s.Owner &&
Start == s.Start &&
Len == s.Len &&
Fore == s.Fore &&
Back == s.Back &&
Decor == s.Decor;
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(LStyle &s)
{
return Overlap(s.Start, s.Len);
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(ssize_t sStart, ssize_t sLen)
{
if (sStart + sLen - 1 < Start ||
sStart >= Start + Len)
return false;
return true;
}
void Union(const LStyle &s)
{
if (Start < 0)
{
Start = s.Start;
Len = s.Len;
}
else
{
Start = MIN(Start, s.Start);
Len = MAX(End(), s.End()) - Start;
}
}
};
friend class LTextView3::LStyle;
protected:
// Internal classes
enum GTextViewSeek
{
PrevLine,
NextLine,
StartLine,
EndLine
};
class LTextLine : public LRange
{
public:
LRect r; // Screen location
LColour c; // Colour of line... transparent = default colour
LColour Back; // Background colour or transparent
LTextLine()
{
Start = -1;
Len = 0;
r.ZOff(-1, -1);
}
virtual ~LTextLine() {}
bool Overlap(ssize_t i)
{
return i>=Start && i<=Start+Len;
}
size_t CalcLen(char16 *Text)
{
char16 *c = Text + Start, *e = c;
while (*e && *e != '\n')
e++;
return Len = e - c;
}
};
class LTextView3Private *d;
friend class LTextView3Private;
// Options
- bool Dirty;
- bool CanScrollX;
+ bool Dirty = false;
+ bool CanScrollX = false;
// Display
- LFont *Font;
- LFont *Bold; // Bold variant of 'Font'
- LFont *Underline; // Underline variant of 'Font'
+ LFont *Font = NULL;
+ LFont *Bold = NULL; // Bold variant of 'Font'
+ LFont *Underline = NULL; // Underline variant of 'Font'
- LFont *FixedFont;
- int LineY;
- ssize_t SelStart, SelEnd;
- int DocOffset;
- int MaxX;
- bool Blink;
- uint64 BlinkTs;
- int ScrollX;
+ LFont *FixedFont = NULL;
+ int LineY = 1;
+ ssize_t SelStart = -1, SelEnd = -1;
+ int DocOffset = 0;
+ int MaxX = 0;
+ bool Blink = true;
+ uint64 BlinkTs = 0;
+ int ScrollX = 0;
LRect CursorPos;
/// true if the text pour process is still ongoing
- bool PourEnabled; // True if pouring the text happens on edit. Turn off if doing lots
+ bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots
// of related edits at the same time. And then manually pour once
// finished.
- bool PartialPour; // True if the pour is happening in the background. It's not threaded
+ bool PartialPour = false; // True if the pour is happening in the background. It's not threaded
// but taking place in the GUI thread via timer.
- size_t PartialPourLines; // Partial pour max lines, if we restart this tracks the max we saw...
- bool AdjustStylePos; // Insert/Delete moved styles automatically to match (default: true)
+ size_t PartialPourLines = 0;// Partial pour max lines, if we restart this tracks the max we saw...
+ bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true)
List Line;
LUnrolledList Style; // sorted in 'Start' order
typedef LUnrolledList::Iter StyleIter;
// For ::Name(...)
- char *TextCache;
+ char *TextCache = NULL;
// Data
char16 *Text;
ssize_t Cursor;
ssize_t Size;
ssize_t Alloc;
// Undo stuff
- bool UndoOn;
+ bool UndoOn = true;
LUndo UndoQue;
- struct LTextView3Undo *UndoCur;
+ struct LTextView3Undo *UndoCur = NULL;
// private methods
List::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0);
LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { return *GetTextLineIt(Offset, Index); }
ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where);
int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin);
bool ScrollToOffset(size_t Off);
int ScrollYLine();
int ScrollYPixel();
LRect DocToScreen(LRect r);
ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards);
void InternalPulse();
// styles
bool InsertStyle(LAutoPtr s);
LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1);
LStyle *HitStyle(ssize_t i);
int GetColumn();
int SpaceDepth(char16 *Start, char16 *End);
int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false);
// Overridables
virtual void PourText(size_t Start, ssize_t Length);
virtual void PourStyle(size_t Start, ssize_t Length);
virtual void OnFontChange();
virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour);
virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false);
#ifdef _DEBUG
// debug
uint64 _PourTime;
uint64 _StyleTime;
uint64 _PaintTime;
#endif
void LogLines();
bool ValidateLines(bool CheckBox = false);
public:
// Construction
LTextView3( int Id,
int x = 0, int y = 0,
int cx = 100, int cy = 100,
LFontType *FontInfo = NULL);
~LTextView3();
const char *GetClass() override { return "LTextView3"; }
// Data
const char *Name() override;
bool Name(const char *s) override;
const char16 *NameW() override;
bool NameW(const char16 *s) override;
int64 Value() override;
void Value(int64 i) override;
const char *GetMimeType() override { return "text/plain"; }
size_t Length() { return Size; }
LString operator[](ssize_t LineIdx);
const char16 *TextAtLine(size_t Index);
ssize_t HitText(int x, int y, bool Nearest);
void DeleteSelection(char16 **Cut = 0);
// Font
LFont *GetFont() override;
LFont *GetBold();
void SetFont(LFont *f, bool OwnIt = false) override;
void SetFixedWidthFont(bool i) override;
// Options
void SetTabSize(uint8_t i) override;
void SetBorder(int b);
void SetReadOnly(bool i) override;
void SetCrLf(bool crlf) override;
/// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW
void SetWrapType(LDocWrapType i) override;
// State / Selection
ssize_t GetCaret(bool Cursor = true) override;
virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override;
ssize_t IndexAt(int x, int y) override;
bool IsDirty() override { return Dirty; }
void IsDirty(bool d) { Dirty = d; }
bool HasSelection() override;
void UnSelectAll() override;
void SelectWord(size_t From) override;
void SelectAll() override;
bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override;
size_t GetLines() override;
void GetTextExtent(int &x, int &y) override;
char *GetSelection() override;
LRange GetSelectionRange();
// File IO
bool Open(const char *Name, const char *Cs = NULL) override;
bool Save(const char *Name, const char *Cs = NULL) override;
const char *GetLastError();
// Clipboard IO
bool Cut() override;
bool Copy() override;
bool Paste() override;
// Undo/Redo
void Undo();
void Redo();
bool GetUndoOn() { return UndoOn; }
void SetUndoOn(bool b) { UndoOn = b; }
// Action UI
virtual bool DoGoto();
virtual bool DoCase(bool Upper);
virtual bool DoFind() override;
virtual bool DoFindNext();
virtual bool DoReplace() override;
// Action Processing
bool ClearDirty(bool Ask, const char *FileName = 0);
void UpdateScrollBars(bool Reset = false);
ssize_t GetLine();
void SetLine(int Line, bool select = false);
LDocFindReplaceParams *CreateFindReplaceParams() override;
void SetFindReplaceParams(LDocFindReplaceParams *Params) override;
// Object Events
virtual bool OnFind( const char16 *Find,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
virtual bool OnReplace( const char16 *Find,
const char16 *Replace,
bool All,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
bool OnMultiLineTab(bool In);
void OnSetHidden(int Hidden);
void OnPosChange() override;
void OnCreate() override;
void OnEscape(LKey &K) override;
bool OnMouseWheel(double Lines) override;
// Window Events
void OnFocus(bool f) override;
void OnMouseClick(LMouse &m) override;
void OnMouseMove(LMouse &m) override;
bool OnKey(LKey &k) override;
void OnPaint(LSurface *pDC) override;
LMessage::Result OnEvent(LMessage *Msg) override;
int OnNotify(LViewI *Ctrl, LNotification n) override;
void OnPulse() override;
int OnHitTest(int x, int y) override;
bool OnLayout(LViewLayoutInfo &Inf) override;
int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override;
int OnDrop(LArray &Data, LPoint Pt, int KeyState) override;
LCursor GetCursor(int x, int y) override;
// Virtuals
virtual bool Insert(size_t At, const char16 *Data, ssize_t Len);
virtual bool Delete(size_t At, ssize_t Len);
virtual void OnEnter(LKey &k) override;
virtual void OnUrl(char *Url) override;
virtual void DoContextMenu(LMouse &m);
virtual bool OnStyleClick(LStyle *style, LMouse *m);
virtual bool OnStyleMenu(LStyle *style, LSubMenu *m);
virtual void OnStyleMenuClick(LStyle *style, int i);
};
#endif
diff --git a/private/common/ViewPriv.h b/private/common/ViewPriv.h
--- a/private/common/ViewPriv.h
+++ b/private/common/ViewPriv.h
@@ -1,234 +1,232 @@
// Private LView definations
#pragma once
#if WINNATIVE
#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x501
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x501
#endif
#include "commctrl.h"
#include "Uxtheme.h"
#define GViewFlags d->WndStyle
#else
#define GViewFlags WndFlags
#endif
#if defined(__GTK_H__)
struct LCaptureThread : public LThread, public LCancel
{
int view = -1;
+ LString name;
public:
constexpr static int EventMs = 150;
LCaptureThread(LView *v);
~LCaptureThread();
int Main();
};
#elif defined(MAC)
extern OsThread LgiThreadInPaint;
#elif defined(HAIKU)
#include
#endif
#define PAINT_VIRTUAL_CHILDREN 1
extern bool In_SetWindowPos;
extern LMouse &lgi_adjust_click(LMouse &Info,
LViewI *Wnd,
bool Capturing = false,
bool Debug = false);
#ifdef __GTK_H__
extern LPoint GtkAbsPos(Gtk::GtkWidget *w);
extern LRect GtkGetPos(Gtk::GtkWidget *w);
#endif
#if !WINNATIVE
#include "lgi/common/ThreadEvent.h"
-class LPulseThread : public LThread
+class LPulseThread : public LThread, public LCancel
{
- LView *View;
- int Length;
+ LView *View = NULL;
+ int Length = 0;
LThreadEvent Event;
LString MakeName(LView *v, const char *Type)
{
LString s;
s.Printf("LPulseThread.%s.%s", v->GetClass(), Type);
return s;
}
public:
- bool Loop;
+ static int PulseThreadCount;
LPulseThread(LView *view, int len) :
View(view),
LThread(MakeName(view, "Thread")),
Event(MakeName(view, "Event"))
{
LAssert(View);
+ Length = len;
+ PulseThreadCount++;
- Loop = true;
- Length = len;
+ printf("PulseThread=%i, %s, %i\n", PulseThreadCount, View->GetClass(), Length);
Run();
}
~LPulseThread()
{
- Loop = false;
View = NULL;
+ Cancel();
Event.Signal();
-
- while (!IsExited())
- LSleep(0);
+ WaitForExit();
+ PulseThreadCount--;
}
int Main()
{
- while (Loop && LAppInst)
+ while (!IsCancelled() && LAppInst)
{
auto s = Event.Wait(Length);
- if (!Loop || s == LThreadEvent::WaitError)
+ if (IsCancelled() || s == LThreadEvent::WaitError)
break;
- if (View)
- {
- if (!View->PostEvent(M_PULSE))
- Loop = false;
- }
+ if (View && !View->PostEvent(M_PULSE))
+ Cancel();
}
return 0;
}
};
#endif
enum LViewFontType
{
/// The LView has a pointer to an externally owned font.
GV_FontPtr,
/// The LView owns the font object, and must free it.
GV_FontOwned,
/// The LApp's font cache owns the object. In this case,
/// calling GetCssStyle on the LView will invalidate the
/// font ptr causing it to be re-calculated.
GV_FontCached,
};
class LViewPrivate
{
public:
// General
LView *View = NULL; // Owning view
LDragDropSource *DropSource = NULL;
LDragDropTarget *DropTarget = NULL;
bool IsThemed = false;
int CtrlId = -1;
int WantsPulse = -1;
// Hierarchy
LViewI *ParentI = NULL;
LView *Parent = NULL;
LViewI *Notify = NULL;
// Size
LPoint MinimumSize;
// Font
LFont *Font = NULL;
LViewFontType FontOwnType = GV_FontPtr;
// Style
LAutoPtr Css;
bool CssDirty = false; // This is set when 'Styles' changes, the next call to GetCss(...) parses
// the styles into the 'Css' object.
LString Styles; // Somewhat temporary object to store unparsed styles particular to
// this view until runtime, where the view heirarchy is known.
LString::Array Classes;
// Event dispatch handle
int SinkHnd = -1;
// OS Specific
#if WINNATIVE
static OsView hPrevCapture;
int WndStyle = 0; // Windows hWnd Style
int WndExStyle = 0; // Windows hWnd ExStyle
int WndDlgCode = 0; // Windows Dialog Code (WM_GETDLGCODE)
LString WndClass;
UINT_PTR TimerId = 0;
HTHEME hTheme = NULL;
#else
// Cursor
LPulseThread *PulseThread = NULL;
LView *Popup = NULL;
bool TabStop = false;
bool WantsFocus = false;
#if defined __GTK_H__
bool InPaint = false;
bool GotOnCreate = false;
#elif defined(MAC) && !defined(LGI_COCOA)
static HIObjectClassRef BaseClass;
#endif
#endif
#if defined(__GTK_H__)
- LCaptureThread *CaptureThread = NULL;
+ static LCaptureThread *CaptureThread;
LMouse PrevMouse;
#elif defined(MAC)
#ifdef LGI_COCOA
LString ClassName;
bool AttachEvent;
#elif defined LGI_CARBON
EventHandlerRef DndHandler;
LAutoString AcceptedDropFormat;
#endif
#elif defined(LGI_SDL)
SDL_TimerID PulseId;
int PulseLength = -1;
#elif defined(HAIKU)
BView *Hnd = NULL;
#endif
LViewPrivate(LView *view);
~LViewPrivate();
LView *GetParent()
{
if (Parent)
return Parent;
if (ParentI)
return ParentI->GetGView();
return 0;
}
};
diff --git a/src/common/Lgi/ToolTip.cpp b/src/common/Lgi/ToolTip.cpp
--- a/src/common/Lgi/ToolTip.cpp
+++ b/src/common/Lgi/ToolTip.cpp
@@ -1,362 +1,362 @@
#define _WIN32_WINNT 0x500
#include "lgi/common/Lgi.h"
#include "lgi/common/ToolTip.h"
#if defined(WIN32) && !defined(__GTK_H__)
#include
#elif defined(LGI_CARBON)
#include
#endif
#if LGI_COCOA || defined(__GTK_H__)
#define LGI_NATIVE_TIPS 1
#endif
#define DEBUG_TOOLTIPS 0
#if DEBUG_TOOLTIPS
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif
#if LGI_NATIVE_TIPS
#include "lgi/common/DisplayString.h"
#include "lgi/common/Popup.h"
class NativeTip : public LPopup
{
LAutoPtr s;
public:
static LArray All;
static NativeTip *PulseRunning;
static LPoint Padding;
int Id;
LRect Watch;
const char *GetClass() { return "NativeTip"; }
NativeTip(int id, LView *p) : LPopup(p)
{
All.Add(this);
Id = id;
Owner = p;
ClearFlag(WndFlags, GWF_VISIBLE);
Watch.ZOff(-1, -1);
}
~NativeTip()
{
All.Delete(this);
if (PulseRunning == this)
{
PulseRunning = NULL;
if (All.Length() > 0)
{
PulseRunning = All[0];
PulseRunning->SetPulse(300);
}
}
}
void OnCreate()
{
LPopup::OnCreate();
if (!PulseRunning)
{
PulseRunning = this;
- SetPulse(300);
+ SetPulse(1000);
}
}
void OnPulse()
{
// Check mouse position...
for (unsigned i=0; iOwner)
{
auto *Wnd = t->Owner->GetWindow();
bool Active = Wnd ? Wnd->IsActive() : false;
if (t->Owner->GetMouse(m))
{
m.Target = t->Owner;
LRect w = t->Watch;
bool Vis = w.Overlap(m.x, m.y);
LOG("Tip %s, in=%i, act=%i\n", t->LView::Name(), Vis, Active);
Vis = Vis && Active;
if (Vis ^ t->Visible())
{
LRect r = t->GetPos();
LPoint pt(w.x1, w.y2);
#ifdef __GTK_H__
pt.y += 8;
#endif
t->Owner->PointToScreen(pt);
LOG("Vis(%i): r=%s pt=%i,%i->%i,%i\n", Vis, r.GetStr(), w.x1, w.y2, pt.x, pt.y);
r.Offset(pt.x - r.x1, pt.y - r.y1);
t->SetPos(r);
t->Visible(Vis);
}
}
}
else
{
All.DeleteAt(i--);
}
}
}
bool Name(const char *n)
{
bool Status = LView::Name(n);
if (s.Reset(new LDisplayString(LSysFont, LView::Name())))
{
LRect r = GetPos();
r.SetSize(s->X()+NativeTip::Padding.x, s->Y()+NativeTip::Padding.y);
SetPos(r);
}
return Status;
}
void OnPaint(LSurface *pDC)
{
LRect c = GetClient();
auto b = L_TOOL_TIP;
// Draw border
#ifdef MAC
pDC->Colour(L_LIGHT);
#else
pDC->Colour(L_BLACK);
#endif
pDC->Box(&c);
c.Inset(1, 1);
// Draw text interior
LSysFont->Colour(L_TEXT, b);
LSysFont->Transparent(false);
if (s)
{
s->Draw(pDC, c.x1+((c.X()-s->X())>>1), c.y1+((c.Y()-s->Y())>>1), &c);
}
else
{
pDC->Colour(b);
pDC->Rectangle(&c);
}
}
bool IsOverParent(int x, int y)
{
// Implement this code to return true if the x, y coordinate passed in is over the
// window 'Parent'. It must return false if another window obsures the location given
// be 'x' and 'y'.
return false;
}
};
#ifdef MAC
LPoint NativeTip::Padding(8, 4);
#else
LPoint NativeTip::Padding(4, 2);
#endif
NativeTip *NativeTip::PulseRunning = NULL;
LArray NativeTip::All;
#endif
class LToolTipPrivate
{
public:
int NextUid;
#if LGI_NATIVE_TIPS
LView *Parent;
LHashTbl, NativeTip*> Tips;
#elif defined(LGI_CARBON)
HMHelpContentRec Tag;
#endif
LToolTipPrivate()
{
NextUid = 1;
#if LGI_NATIVE_TIPS
Parent = NULL;
#endif
}
~LToolTipPrivate()
{
#if LGI_NATIVE_TIPS
for (auto t : Tips)
{
LOG("Deleting tip %p\n", t.value);
delete t.value;
}
#endif
}
};
LToolTip::LToolTip() : LView(NULL)
{
d = new LToolTipPrivate;
}
LToolTip::~LToolTip()
{
DeleteObj(d);
}
int LToolTip::NewTip(const char *Name, LRect &Pos)
{
int Status = 0;
LOG("%s:%i NewTip(%s,%s)\n", _FL, Name, Pos.GetStr());
#if LGI_NATIVE_TIPS
if (ValidStr(Name) && d->Parent)
{
NativeTip *t = new NativeTip(d->NextUid++, d->Parent);
if (t)
{
t->Watch = Pos;
t->Name(Name);
// This is a hack to get it to create a window...
t->Visible(true);
// But not show it yet... duh...
t->Visible(false);
d->Tips.Add(t->Id, t);
Status = t->Id;
}
else LOG("Error: Alloc failed.");
}
else LOG("Error: Invalid params.");
#elif LGI_CARBON
#ifdef __MACHELP__
HMSetHelpTagsDisplayed(true);
#else
#error "__MACHELP__ not defined"
#endif
if (Name)
{
d->Tag.version = kMacHelpVersion;
d->Tag.tagSide = kHMDefaultSide;
d->Tag.content[kHMMinimumContentIndex].contentType = kHMCFStringLocalizedContent;
d->Tag.content[kHMMinimumContentIndex].u.tagCFString = CFStringCreateWithCString(NULL, Name, kCFStringEncodingUTF8);
d->Tag.absHotRect = Pos;
}
#elif WINNATIVE
if (_View && Name && GetParent())
{
TOOLINFOW ti;
ZeroObj(ti);
ti.cbSize = sizeof(ti);
ti.uFlags = TTF_SUBCLASS;
ti.hwnd = GetParent()->Handle();
ti.rect = Pos;
ti.lpszText = Utf8ToWide(Name);
ti.uId = Status = d->NextUid++;
auto Result = SendMessage(_View, TTM_ADDTOOLW, 0, (LPARAM) &ti);
DeleteArray(ti.lpszText);
}
#endif
return Status;
}
void LToolTip::DeleteTip(int Id)
{
#if LGI_NATIVE_TIPS
auto t = d->Tips.Find(Id);
LOG("%s:%i DeleteTip(%i)=%p\n", _FL, Id, t);
if (t)
{
d->Tips.Delete(Id);
delete t;
}
#elif WINNATIVE
if (GetParent())
{
TOOLINFOW ti;
ZeroObj(ti);
ti.cbSize = sizeof(ti);
ti.hwnd = GetParent()->Handle();
ti.uId = Id;
SendMessage(_View, TTM_DELTOOL, 0, (LPARAM) &ti);
LOG("DeleteTip: Tip %i.\n", Id);
}
else LOG("Error: No parent?\n");
#endif
}
bool LToolTip::Attach(LViewI *p)
{
#if LGI_NATIVE_TIPS
d->Parent = p->GetGView();
return false;
#elif WINNATIVE
if (!p)
return false;
if (!_View)
{
SetParent(p);
_View = CreateWindowEx( NULL, TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // p->Handle(),
NULL,
NULL,
NULL);
}
if (!_View)
return false;
SetWindowLongPtr( _View, GWLP_USERDATA, (LONG_PTR)(LViewI*)this);
SetWindowLong( _View, GWL_LGI_MAGIC, LGI_GViewMagic);
SetWindowPos( _View,
HWND_TOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
#endif
return true;
}
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,2535 +1,2539 @@
/// \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
+#if !WINNATIVE
+int LPulseThread::PulseThreadCount = 0;
+#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
_Window = 0;
_Lock = 0;
_InLock = 0;
_BorderSize = 0;
_IsToolBar = false;
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)
{
// printf("%s:%i - ~%s setting %s->Owner to NULL\n", _FL, GetClass(), pu->GetClass());
pu->Owner = NULL;
}
}
_Delete();
DeleteObj(d);
}
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();
return (w) ? w->WindowHandle() : (OsWindow)NULL;
}
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)
{
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;
}
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()=%i\n", GetClass(), (int)(_Capturing == this));
return _Capturing == this;
}
bool LView::Capture(bool c)
{
ThreadCheck();
DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass());
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();
if (v) SetFlag(GViewFlags, GWF_VISIBLE);
else ClearFlag(GViewFlags, GWF_VISIBLE);
#if defined(HAIKU)
LLocker lck(d->Hnd, _FL);
if (!IsAttached() || lck.Lock())
{
if (v)
d->Hnd->Show();
else
d->Hnd->Hide();
}
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
{
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(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(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)
{
#ifdef LGI_SDL
return LPostEvent(this, Cmd, a, b);
#elif defined(HAIKU)
if (!d || !d->Hnd || !d->Hnd->LockLooper())
return false;
BMessage *m = new BMessage(Cmd);
if (!m)
return false;
m->AddUInt64(LMessage::PropNames[0], a);
m->AddUInt64(LMessage::PropNames[1], b);
auto w = d && d->Hnd ? d->Hnd->Window() : NULL;
status_t r = w->PostMessage(m);
d->Hnd->UnlockLooper();
return r == B_OK;
#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)
template
void _Dump(int Depth, T *v)
{
LString Sp, Name;
Sp.Length(Depth<<1);
memset(Sp.Get(), ' ', Depth<<1);
LRect Frame = v->Frame();
LViewPrivate *Priv = dynamic_cast(v);
if (Priv)
Name = Priv->View->GetClass();
printf("%s### %p::%s frame=%s vis=%i\n",
Sp.Get(), v, Name.Get(), Frame.GetStr(), !v->IsHidden());
for (int32 i=0; iCountChildren(); i++)
{
_Dump(Depth + 1, v->ChildAt(i));
}
}
#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(HAIKU)
LLocker lck(WindowHandle(), _FL);
if (lck.Lock())
::_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;
}
#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,5456 +1,5428 @@
#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 250 // ms
+#define PULSE_TIMEOUT 500 // ms
#define CURSOR_BLINK 1000 // ms
#define ALLOC_BLOCK 64
#define IDC_VS 1000
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));
- PourEnabled = true;
- PartialPour = false;
- PartialPourLines = 0;
- AdjustStylePos = true;
- BlinkTs = 0;
- LineY = 1;
- MaxX = 0;
- TextCache = 0;
- UndoOn = true;
- UndoCur = NULL;
- Font = 0;
- FixedWidthFont = false;
- FixedFont = 0;
- ShowWhiteSpace = false;
- ObscurePassword = false;
TabSize = TAB_SIZE;
IndentSize = TAB_SIZE;
- HardTabs = true;
- CanScrollX = false;
- Blink = true;
-
+
// setup window
SetId(Id);
// default options
- Dirty = false;
#if WINNATIVE
CrLf = true;
SetDlgCode(DLGC_WANTALLKEYS);
#else
- CrLf = false;
#endif
- Underline = NULL;
- Bold = NULL;
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
- SelStart = SelEnd = -1;
- DocOffset = 0;
- ScrollX = 0;
-
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.NewGStr();
#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;
- LTextLine *SLine = GetTextLine(Start);
- LTextLine *ELine = GetTextLine(End);
+ 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;
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;
}
bool LTextView3::ClearDirty(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)
{
LFileSelect Select;
Select.Parent(this);
if (!FileName &&
Select.Save())
{
FileName = Select.Name();
}
Save(FileName);
}
else if (Answer == IDCANCEL)
{
return false;
}
}
return 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();
}
}
}
bool LTextView3::DoCase(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);
}
}
return true;
}
ssize_t LTextView3::GetLine()
{
ssize_t Idx = 0;
GetTextLine(Cursor, &Idx);
return Idx + 1;
}
void LTextView3::SetLine(int i, bool select)
{
LTextLine *l = Line.ItemAt(i - 1);
if (l)
{
d->CenterCursor = true;
SetCaret(l->Start, select);
d->CenterCursor = false;
}
}
bool LTextView3::DoGoto()
{
LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text");
if (Dlg.DoModal() == IDOK &&
Dlg.GetStr())
{
SetLine(atoi(Dlg.GetStr()));
}
return true;
}
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;
}
}
bool LTextView3::DoFindNext()
{
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);
}
return Status;
}
bool
Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User)
{
LTextView3 *v = (LTextView3*) User;
if (v->d->FindReplaceParams &&
v->d->FindReplaceParams->Lock(_FL))
{
v->d->FindReplaceParams->MatchWord = Dlg->MatchWord;
v->d->FindReplaceParams->MatchCase = Dlg->MatchCase;
v->d->FindReplaceParams->SelectionOnly = Dlg->SelectionOnly;
v->d->FindReplaceParams->SearchUpwards = Dlg->SearchUpwards;
v->d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg->Find));
v->d->FindReplaceParams->Unlock();
}
return v->DoFindNext();
}
bool LTextView3::DoFind()
{
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();
}
LFindDlg Dlg(this, u, Text3_FindCallback, this);
Dlg.DoModal();
Focus(true);
return false;
}
bool LTextView3::DoReplace()
{
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;
}
}
}
char *LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind);
char *LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace);
LReplaceDlg Dlg(this, LastFind8, LastReplace8);
Dlg.MatchWord = d->FindReplaceParams->MatchWord;
Dlg.MatchCase = d->FindReplaceParams->MatchCase;
Dlg.SelectionOnly = HasSelection();
int Action = Dlg.DoModal();
DeleteArray(LastFind8);
DeleteArray(LastReplace8);
if (Action != IDCANCEL)
{
d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg.Find));
d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Dlg.Replace));
d->FindReplaceParams->MatchWord = Dlg.MatchWord;
d->FindReplaceParams->MatchCase = Dlg.MatchCase;
d->FindReplaceParams->SelectionOnly = Dlg.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;
}
}
return false;
}
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)
{
/*
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);
*/
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)
- #endif
+ if (Ctrls.Length() == 0)
+ SetPulse(PULSE_TIMEOUT);
+ Ctrls.Add(this);
+ #else
SetPulse(PULSE_TIMEOUT);
- #ifndef WINDOWS
- Ctrls.Add(this);
#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(this, s, "Indent Size:", "Text");
if (i.DoModal())
{
IndentSize = atoi(i.GetStr());
}
break;
}
case IDM_TAB_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", TabSize);
LInput i(this, s, "Tab Size:", "Text");
if (i.DoModal())
{
SetTabSize(atoi(i.GetStr()));
}
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();
}
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();
}
return true;
break;
}
case 'g':
case 'G':
{
if (k.Down())
{
DoGoto();
}
return true;
break;
}
case 'h':
case 'H':
{
if (k.Down())
{
DoReplace();
}
return true;
break;
}
case 'u':
case 'U':
{
if (!GetReadOnly())
{
if (k.Down())
{
DoCase(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 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);
}
#ifdef DOUBLE_BUFFER_PAINT
LMemDC *pMem = new LMemDC;
pOut = pMem;
#endif
if (Text &&
Font
#ifdef DOUBLE_BUFFER_PAINT
&&
pMem &&
pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits())
#endif
)
{
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;
#ifdef DOUBLE_BUFFER_PAINT
Tr.Offset(-Tr.x1, -Tr.y1);
#else
Tr.Offset(0, y - Tr.y1);
#endif
//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;
#ifdef DOUBLE_BUFFER_PAINT
c.Offset(-d->rPadding.x1, -y);
#endif
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
#ifdef DOUBLE_BUFFER_PAINT
// dump to screen
pDC->Blt(d->rPadding.x1, y, pOut);
#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);
}
#ifdef DOUBLE_BUFFER_PAINT
DeleteObj(pMem);
#endif
}
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();
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();
+ InternalPulse();
#else
- for (auto c: Ctrls)
- c->InternalPulse();
+ 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/linux/Lgi/App.cpp b/src/linux/Lgi/App.cpp
--- a/src/linux/Lgi/App.cpp
+++ b/src/linux/Lgi/App.cpp
@@ -1,1462 +1,1476 @@
#include
#include
#include
#include
#ifndef WIN32
#include
#include
#endif
#include
#define _GNU_SOURCE
#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"
#define DEBUG_MSG_TYPES 0
#define DEBUG_HND_WARNINGS 0
#define IDLE_ALWAYS 0
using namespace Gtk;
bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth = 0);
////////////////////////////////////////////////////////////////
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
/////////////////////////////////////////////////////////////////////////////
//
// Attempts to cleanup and call drkonqi to process the crash
//
static LString CrashHandlerApp;
void LgiCrashHandler(int Sig)
{
// Don't get into an infinite loop
signal(SIGSEGV, SIG_DFL);
#ifndef _MSC_VER
// Our pid
LString pid;
pid.Printf("%i", getpid());
LgiTrace("LgiCrashHandler trigger pid=%s\n", pid.Get());
auto child = fork();
if (!child)
{
LFile::Path workingDir = CrashHandlerApp;
workingDir--;
chdir(workingDir);
const char *args[] = { CrashHandlerApp, "--pid", pid, NULL };
execvp(CrashHandlerApp, args);
exit(0);
}
if (LAppInst->InThread())
{
LgiTrace("LgiCrashHandler showing dlg\n");
LgiMsg(NULL, "Application crashed... dumping details.", "Crash");
}
else
{
LgiTrace("LgiCrashHandler called from worker thread.\n");
LSleep(10000); // Wait for the crash handler to do it's thing...
}
#endif
exit(-1);
}
/////////////////////////////////////////////////////////////////////////////
#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;
+ 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;
/////////////////////////////////////////////////////////////////////////////
LSkinEngine *LApp::SkinEngine = 0;
LApp *TheApp = 0;
LMouseHook *LApp::MouseHook = 0;
LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) :
OsApplication(AppArgs.Args, AppArgs.Arg)
{
TheApp = this;
d = new LAppPrivate(this);
Name(name);
LgiArgsAppPath = AppArgs.Arg[0];
if (LIsRelativePath(LgiArgsAppPath))
{
char Cwd[MAX_PATH_LEN];
getcwd(Cwd, sizeof(Cwd));
LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath);
LgiArgsAppPath = Cwd;
}
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();
Gtk::gchar id[256];
sprintf_s(id, sizeof(id), "com.memecode.%s", name);
d->App = Gtk::gtk_application_new(id, Gtk::G_APPLICATION_FLAGS_NONE);
LAssert(d->App != NULL);
MouseHook = new LMouseHook;
// Setup the SIGSEGV signal to call the crash handler
if (!GetOption("nch"))
{
auto programName = "crash-handler";
LFile::Path p(LSP_APP_INSTALL);
p += programName;
if (!p.Exists())
{
// Check alternate location for development builds
Dl_info dlInfo;
dladdr(LgiCrashHandler, &dlInfo);
if (dlInfo.dli_sname != NULL && dlInfo.dli_saddr != NULL)
{
p = dlInfo.dli_fname;
p += "../../src/linux/CrashHandler";
p += programName;
printf("Alternative path %s: %s\n",
p.Exists() ? "found" : "missing",
p.GetFull().Get());
}
}
if (p.Exists())
{
CrashHandlerApp = p;
signal(SIGSEGV, LgiCrashHandler);
LgiTrace("Crash handler: '%s' installed.\n", CrashHandlerApp.Get());
}
else
{
LgiTrace("Crash handler: No crash handler '%s' found, SIGSEGV handler not installed.\n",
p.GetFull().Get());
}
}
else
{
LgiTrace("Crash handler: disabled.\n");
}
d->GetConfig();
// System font setup
LFontType SysFontType;
Gtk::PangoFontMap *fm = Gtk::pango_cairo_font_map_get_default();
if (fm)
{
using namespace Gtk;
auto cfm = PANGO_CAIRO_FONT_MAP(fm);
#ifdef MAC
double Dpi = 80.0;
#else
double Dpi = 96.0;
#endif
::LFile::Path p(LSP_APP_ROOT);
p += "lgi-conf.json";
if (p.IsFile())
{
::LFile f(p, O_READ);
LJson j(f.Read());
auto sDpi = j.Get("DPI");
if (sDpi)
Dpi = sDpi.Float();
}
pango_cairo_font_map_set_resolution(cfm, Dpi);
}
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()
{
DeleteObj(AppWnd);
DeleteObj(SystemNormal);
DeleteObj(SystemBold);
DeleteObj(SkinEngine);
DeleteObj(MouseHook);
DeleteObj(d->FileSystem);
DeleteObj(d->GdcSystem);
DeleteObj(LFontSystem::Me);
DeleteObj(d);
TheApp = 0;
}
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;
}
struct GtkIdle
{
LAppPrivate *d;
LAppI::OnIdleProc cb;
void *param;
};
Gtk::gboolean IdleWrapper(Gtk::gpointer data)
{
#if 0
static int64 ts = LCurrentTime();
static int count = 0;
int64 now = LCurrentTime();
if (now - ts > 300)
{
printf("IdleWrapper = %i\n", count);
count = 0;
ts = now;
}
else count++;
#endif
GtkIdle *i = (GtkIdle*) data;
if (i->cb)
i->cb(i->param);
LMessageQue::MsgArray *Msgs;
if (MsgQue &&
(Msgs = MsgQue.Lock(_FL)))
{
// printf("IdleWrapper start %i\n", (int)Msgs->Length());
// Copy the messages out of the locked structure..
// This allows new messages to arrive independent
// of us processing them here...
LMessageQue::MsgArray q = *Msgs;
Msgs->Empty();
MsgQue.Unlock();
for (auto m : q)
{
if (!LView::LockHandler(m.v, LView::OpExists))
{
// LgiTrace("%s:%i - Invalid view: %p.\n", _FL, m.v);
}
else
{
LMessage Msg(m.m, m.a, m.b);
// LgiTrace("%s::OnEvent %i,%i,%i\n", m.v->GetClass(), m.m, m.a, m.b);
m.v->OnEvent(&Msg);
}
}
}
else
{
// printf("IdleWrapper start no lock\n");
}
// printf("IdleWrapper end\n");
return i->cb != NULL;
// return false;
}
static GtkIdle idle = {0};
bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam)
{
if (!InThread())
{
printf("%s:%i - Error: Out of thread.\n", _FL);
return false;
}
if (!idle.d)
{
idle.d = d;
idle.cb = IdleCallback;
idle.param = IdleParam;
}
static bool CmdLineDone = false;
if (!CmdLineDone)
{
CmdLineDone = true;
OnCommandLine();
}
Gtk::gtk_main();
return true;
}
bool LApp::Yield()
{
Gtk::gtk_main_iteration_do(false);
return true;
}
void LApp::Exit(int Code)
{
if (Code)
{
// hard exit
::exit(Code);
}
else
{
// soft exit
Gtk::gtk_main_quit();
if (d->IdleId.Length() > 0)
{
size_t Last = d->IdleId.Length() - 1;
Gtk::g_source_remove(d->IdleId[Last]);
d->IdleId.Length(Last);
}
}
}
bool LApp::PostEvent(LViewI *View, int Msg, LMessage::Param a, LMessage::Param b)
{
- LMessageQue::MsgArray *q = MsgQue.Lock(_FL);
+ auto q = MsgQue.Lock(_FL);
if (!q)
{
printf("%s:%i - Couldn't lock app.\n", _FL);
return false;
}
q->New().Set(View, Msg, a, b);
#if 1 // defined(_DEBUG)
if (q->Length() > 200 && (q->Length()%20)==0)
{
static uint64 prev = 0;
auto now = LCurrentTime();
- if (now - prev >= 500)
+ if (now - prev >= 1000)
{
prev = now;
#if defined(WIN32)
char s[256];
sprintf_s(s, sizeof(s),
#else
printf(
#endif
"PostEvent Que=" LPrintfSizeT " (msg=%i)\n", q->Length(), Msg);
#if defined(WIN32)
OutputDebugStringA(s);
#endif
+
+ #ifdef LINUX
+ LHashTbl, size_t> MsgCounts;
+ for (auto &msg: *q)
+ MsgCounts.Add(msg.m, MsgCounts.Find(msg.m) + 1);
+
+ for (auto c: MsgCounts)
+ printf(" %i->%i\n", c.key, c.value);
+
+ if (Msg == 916)
+ {
+ int asd=0;
+ }
+ #endif
}
}
#endif
MsgQue.Unlock();
// g_idle_add((GSourceFunc)IdleWrapper, &idle);
g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)IdleWrapper, &idle, NULL);
return true;
}
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::KeyModFlags::FlagName(int Flag)
{
#define CHECK(f) if (Flag & f) return #f;
CHECK(GDK_SHIFT_MASK)
CHECK(GDK_LOCK_MASK)
CHECK(GDK_CONTROL_MASK)
CHECK(GDK_MOD1_MASK)
CHECK(GDK_MOD2_MASK)
CHECK(GDK_MOD3_MASK)
CHECK(GDK_MOD4_MASK)
CHECK(GDK_MOD5_MASK)
CHECK(GDK_BUTTON1_MASK)
CHECK(GDK_BUTTON2_MASK)
CHECK(GDK_BUTTON3_MASK)
CHECK(GDK_BUTTON4_MASK)
CHECK(GDK_BUTTON5_MASK)
CHECK(GDK_SUPER_MASK)
CHECK(GDK_HYPER_MASK)
CHECK(GDK_META_MASK)
CHECK(GDK_RELEASE_MASK)
#undef CHECK
return NULL;
}
int LApp::KeyModFlags::FlagValue(const char *Name)
{
#define CHECK(f) if (!Stricmp(Name, #f)) return f;
CHECK(GDK_SHIFT_MASK)
CHECK(GDK_LOCK_MASK)
CHECK(GDK_CONTROL_MASK)
CHECK(GDK_MOD1_MASK)
CHECK(GDK_MOD2_MASK)
CHECK(GDK_MOD3_MASK)
CHECK(GDK_MOD4_MASK)
CHECK(GDK_MOD5_MASK)
CHECK(GDK_BUTTON1_MASK)
CHECK(GDK_BUTTON2_MASK)
CHECK(GDK_BUTTON3_MASK)
CHECK(GDK_BUTTON4_MASK)
CHECK(GDK_BUTTON5_MASK)
CHECK(GDK_SUPER_MASK)
CHECK(GDK_HYPER_MASK)
CHECK(GDK_META_MASK)
CHECK(GDK_RELEASE_MASK)
#undef CHECK
return 0;
}
::LString LApp::KeyModFlags::FlagsToString(int s)
{
::LString::Array a;
for (int i=0; i<32; i++)
{
if (((1 << i) & s) != 0)
a.New() = FlagName(1 << i);
}
return ::LString(",").Join(a);
}
LApp::KeyModFlags *LApp::GetKeyModFlags()
{
return d->GetModFlags();
}
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++)
{
char *a = d->Args.Arg[i];
if (!a)
continue;
if (strchr("-/\\", a[0]))
{
if (strnicmp(a+1, Option, OptLen) == 0)
{
char *Arg = 0;
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++)
{
char *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)
{
::LString Status;
char Full[MAX_PATH_LEN] = "";
if (!LFileExists(File))
{
// Look in the path
LToken p(getenv("PATH"), LGI_PATH_SEPARATOR);
for (int i=0; i 0)
Status = s.SplitDelimit(":").Last().Strip();
}
return Status;
}
}
#endif
#if HAS_LIB_MAGIC
static bool MagicError = false;
if (d->MagicLock.Lock(_FL))
{
if (!d->hMagic && !MagicError)
{
d->hMagic = magic_open(MAGIC_MIME_TYPE);
if (d->hMagic)
{
if (magic_load(d->hMagic, NULL) != 0)
{
printf("%s:%i - magic_load failed - %s\n", _FL, magic_error(d->hMagic));
magic_close(d->hMagic);
d->hMagic = NULL;
MagicError = true;
}
}
else
{
printf("%s:%i - magic_open failed.\n", _FL);
MagicError = true;
}
}
if (d->hMagic && !MagicError)
{
const char *mt = magic_file(d->hMagic, File);
if (mt)
{
Status = mt;
}
}
d->MagicLock.Unlock();
if (Status)
return Status;
}
#endif
#if HAS_SHARED_MIME
// freedesktop.org rocks...
if (!d->Sm)
{
// Not loaded, go and try to load it...
d->Sm = new LSharedMime;
if (d->Sm && d->Sm->IsLoaded())
{
d->Sm->mimetypes_init();
}
}
if (d->Sm && d->Sm->IsLoaded())
{
// Loaded...
char *m = (char*)d->Sm->mimetypes_get_file_type(File, MIMETYPES_CHECK_ALL);
if (m)
{
#if HAS_FILE_CMD
if (stricmp(m, "application/x-not-recognised") != 0)
#endif
{
strcpy(Mime, m);
return true;
}
}
else
{
printf("%s:%i - mimetypes_get_file_type failed for '%s'\n", __FILE__, __LINE__, File);
}
}
else
{
printf("%s:%i - Shared Mime not loaded!!!\n", __FILE__, __LINE__);
}
#endif
#if HAS_FILE_CMD
// doh! not installed... :(
LStringPipe Output;
char Args[256];
sprintf(Args, "-i \"%s\"", File);
LSubProcess p("file", Args);
if (p.Start())
{
p.Communicate(&Output);
char *Out = Output.NewStr();
if (Out)
{
char *s = strchr(Out, ':');
if (s && strchr(Out, '/'))
{
s += 2;
char *e = s;
while
(
*e &&
(
IsAlpha(*e) ||
IsDigit(*e) ||
strchr(".-_/", *e)
)
)
e++;
*e = 0;
Status.Reset(NewStr(s));
}
DeleteArray(Out);
}
}
#endif
return Status;
}
bool LApp::GetAppsForMimeType(char *Mime, ::LArray<::LAppInfo*> &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 d;
}
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, char **Arg)
{
Inst = this;
d = new OsApplicationPriv;
}
OsApplication::~OsApplication()
{
DeleteObj(d);
Inst = 0;
}
////////////////////////////////////////////////////////////////
void LMessage::Set(int Msg, Param ParamA, Param ParamB)
{
m = Msg;
a = ParamA;
b = ParamB;
}
struct GlibEventParams : public LMessage
{
GtkWidget *w;
};
bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth)
{
char indent[256] = "";
if (Debug)
{
int ch = depth * 2;
memset(indent, ' ', ch);
indent[ch] = 0;
printf("%sGlibWidgetSearch: %p, %p\n", indent, p, w);
}
if (p == w)
return true;
if (GTK_IS_CONTAINER(p))
{
int n = 0;
Gtk::GList *top = gtk_container_get_children(GTK_CONTAINER(p));
Gtk::GList *i = top;
while (i)
{
if (Debug)
printf("%s[%i]=%s\n", indent, n, gtk_widget_get_name((GtkWidget*)i->data));
if (i->data == w)
return true;
else if (GlibWidgetSearch((GtkWidget*)i->data, w, Debug, depth + 1))
return true;
i = i->next;
n++;
}
g_list_free(top);
}
else if (GTK_IS_BIN(p))
{
GtkWidget *child = gtk_bin_get_child(GTK_BIN(p));
if (child)
{
if (Debug)
printf("%schild=%s\n", indent, gtk_widget_get_name(child));
if (child == w)
return true;
else if (GlibWidgetSearch(child, w, Debug, depth + 1))
return true;
}
}
else if (Debug)
{
printf("%sUnknown=%s\n", indent, gtk_widget_get_name(p));
}
return false;
}
void LApp::OnDetach(LViewI *View)
{
LMessageQue::MsgArray *q = MsgQue.Lock(_FL);
if (!q)
{
printf("%s:%i - Couldn't lock app.\n", _FL);
return;
}
MsgQue.Unlock();
}
bool LMessage::Send(LViewI *View)
{
if (!View)
{
LAssert(!"No view");
return false;
}
return LAppInst->PostEvent(View, m, a, b);
}
diff --git a/src/linux/Lgi/View.cpp b/src/linux/Lgi/View.cpp
--- a/src/linux/Lgi/View.cpp
+++ b/src/linux/Lgi/View.cpp
@@ -1,1119 +1,1124 @@
/*hdr
** FILE: LView.cpp
** AUTHOR: Matthew Allen
** DATE: 23/4/98
** DESCRIPTION: Linux LView Implementation
**
** Copyright (C) 1998-2003, 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 "lgi/common/EventTargetThread.h"
#include "ViewPriv.h"
using namespace Gtk;
#include "LgiWidget.h"
#define DEBUG_MOUSE_EVENTS 0
#if 0
#define DEBUG_INVALIDATE(...) printf(__VA_ARGS__)
#else
#define DEBUG_INVALIDATE(...)
#endif
#define ADJ_LEFT 1
#define ADJ_RIGHT 2
#define ADJ_UP 3
#define ADJ_DOWN 4
#if GtkVer(2, 14)
#else
#define gtk_widget_get_window(widget) ((widget)->window)
#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
};
+LCaptureThread *LViewPrivate::CaptureThread = NULL;
+
////////////////////////////////////////////////////////////////////////////
void _lgi_yield()
{
LAppInst->Yield();
}
bool LgiIsKeyDown(int Key)
{
LAssert(0);
return false;
}
LKey::LKey(int vkey, uint32_t flags)
{
}
///////////////////////////////////////////////////////////////////////////////////////////////////
LCaptureThread::LCaptureThread(LView *v) : LThread("CaptureThread")
{
+ name = v->GetClass();
+ printf("Capture thread for %s\n", name.Get());
view = v->AddDispatch();
- DeleteOnExit = true;
+ DeleteOnExit = true;
Run();
}
LCaptureThread::~LCaptureThread()
{
+ printf("~Capture thread for %s\n", name.Get());
Cancel();
// Don't wait.. the thread will exit and delete itself asnyronously.
}
int LCaptureThread::Main()
{
while (!IsCancelled())
{
LSleep(EventMs);
if (!IsCancelled())
PostThreadEvent(view, M_CAPTURE_PULSE);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
LViewPrivate::LViewPrivate(LView *view) : View(view)
{
PrevMouse.x = PrevMouse.y = -1;
}
LViewPrivate::~LViewPrivate()
{
LAssert(PulseThread == 0);
if (Font && FontOwnType == GV_FontOwned)
DeleteObj(Font);
}
void LView::OnGtkRealize()
{
if (!d->GotOnCreate)
{
d->GotOnCreate = true;
if (d->WantsFocus)
{
d->WantsFocus = false;
if (GetWindow())
GetWindow()->SetFocus(this, LWindow::GainFocus);
}
OnCreate();
}
for (auto c : Children)
{
auto gv = c->GetGView();
if (gv)
gv->OnGtkRealize();
}
}
void LView::_Focus(bool f)
{
ThreadCheck();
if (f)
SetFlag(WndFlags, GWF_FOCUS);
else
ClearFlag(WndFlags, GWF_FOCUS);
OnFocus(f);
Invalidate();
if (f)
{
auto w = GetWindow();
if (w && w->_Root)
gtk_widget_grab_focus(w->_Root);
else
d->WantsFocus = f;
}
}
void LView::_Delete()
{
ThreadCheck();
SetPulse();
// Remove static references to myself
if (_Over == this)
_Over = NULL;
if (_Capturing == this)
_Capturing = NULL;
auto *Wnd = GetWindow();
if (Wnd && Wnd->GetFocus() == static_cast(this))
Wnd->SetFocus(this, LWindow::ViewDelete);
if (LAppInst && LAppInst->AppWnd == this)
LAppInst->AppWnd = NULL;
// 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;
}
void LgiToGtkCursor(LViewI *v, LCursor c)
{
static LCursor CurrentCursor = LCUR_Normal;
if (!v || c == CurrentCursor)
return;
CurrentCursor = c;
GdkCursorType type = GDK_ARROW;
switch (c)
{
// No cursor
#ifdef GDK_BLANK_CURSOR
case LCUR_Blank:
type = GDK_BLANK_CURSOR;
break;
#endif
/// Normal arrow
case LCUR_Normal:
type = GDK_ARROW;
break;
/// Upwards arrow
case LCUR_UpArrow:
type = GDK_SB_UP_ARROW;
break;
/// Downwards arrow
case LCUR_DownArrow:
type = GDK_SB_DOWN_ARROW;
break;
/// Left arrow
case LCUR_LeftArrow:
type = GDK_SB_LEFT_ARROW;
break;
/// Right arrow
case LCUR_RightArrow:
type = GDK_SB_RIGHT_ARROW;
break;
/// Crosshair
case LCUR_Cross:
type = GDK_CROSSHAIR;
break;
/// Hourglass/watch
case LCUR_Wait:
type = GDK_WATCH;
break;
/// Ibeam/text entry
case LCUR_Ibeam:
type = GDK_XTERM;
break;
/// Vertical resize (|)
case LCUR_SizeVer:
type = GDK_DOUBLE_ARROW;
break;
/// Horizontal resize (-)
case LCUR_SizeHor:
type = GDK_SB_H_DOUBLE_ARROW;
break;
/// Diagonal resize (/)
case LCUR_SizeBDiag:
type = GDK_BOTTOM_LEFT_CORNER;
break;
/// Diagonal resize (\)
case LCUR_SizeFDiag:
type = GDK_BOTTOM_RIGHT_CORNER;
break;
/// All directions resize
case LCUR_SizeAll:
type = GDK_FLEUR;
break;
/// A pointing hand
case LCUR_PointingHand:
type = GDK_HAND2;
break;
/// A slashed circle
case LCUR_Forbidden:
type = GDK_X_CURSOR;
break;
default:
type = GDK_ARROW;
break;
/*
case LCUR_SplitV:
case LCUR_SplitH:
case LCUR_DropCopy:
case LCUR_DropMove:
LAssert(0);
break;
*/
}
OsView h = NULL;
auto *w = v->GetWindow();
if (w)
h = GTK_WIDGET(w->WindowHandle());
LAssert(v->InThread());
auto wnd = gtk_widget_get_window(h);
// LAssert(wnd);
if (wnd)
{
if (type == GDK_ARROW)
gdk_window_set_cursor(wnd, NULL);
else
{
GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), type);
if (cursor)
gdk_window_set_cursor(wnd, cursor);
else
gdk_window_set_cursor(wnd, NULL);
}
}
}
bool LView::_Mouse(LMouse &m, bool Move)
{
ThreadCheck();
#if 0
if (!Move)
{
m.Trace("_Mouse");
::LArray _m;
for (LViewI *i=this; i; i=i->GetParent())
{
_m.Add(i);
}
for (int n=0; n<_m.Length(); n++)
{
LViewI *i=_m[_m.Length()-1-n];
char s[256];
ZeroObj(s);
memset(s, ' ', (n+1)*2);
LgiTrace("%s%s %s\n", s, i->GetClass(), i->GetPos().GetStr());
}
}
#endif
#if DEBUG_MOUSE_EVENTS
if (!Move)
LgiTrace("%s:%i - _Mouse([%i,%i], %i) View=%p/%s\n", _FL, m.x, m.y, Move, _Over, _Over ? _Over->GetClass() : NULL);
#endif
if
(
/*
!_View
||
*/
(
GetWindow()
&&
!GetWindow()->HandleViewMouse(this, m)
)
)
{
#if DEBUG_MOUSE_EVENTS
LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass());
#endif
return false;
}
#if 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));
}
}
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)
{
LgiToGtkCursor(Target, Target->GetCursor(m.x, m.y));
if (Move)
{
Target->OnMouseMove(m);
}
else
{
#if 0
if (!Move)
{
char Msg[256];
sprintf(Msg, "_Mouse Target %s", Target->GetClass());
m.Trace(Msg);
}
#endif
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;
}
const char *EventTypeToString(int i)
{
switch (i)
{
case GDK_DELETE: return "GDK_DELETE";
case GDK_DESTROY: return "GDK_DESTROY";
case GDK_EXPOSE: return "GDK_EXPOSE";
case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY";
case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS";
case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS";
case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS";
case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE";
case GDK_KEY_PRESS: return "GDK_KEY_PRESS";
case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE";
case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY";
case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY";
case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE";
case GDK_CONFIGURE: return "GDK_CONFIGURE";
case GDK_MAP: return "GDK_MAP";
case GDK_UNMAP: return "GDK_UNMAP";
case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY";
case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR";
case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST";
case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY";
case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN";
case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT";
case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER";
case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE";
case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION";
case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS";
case GDK_DROP_START: return "GDK_DROP_START";
case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED";
case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT";
case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY";
case GDK_SCROLL: return "GDK_SCROLL";
case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE";
case GDK_SETTING: return "GDK_SETTING";
case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE";
case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN";
case GDK_DAMAGE: return "GDK_DAMAGE";
case GDK_TOUCH_BEGIN: return "GDK_TOUCH_BEGIN";
case GDK_TOUCH_UPDATE: return "GDK_TOUCH_UPDATE";
case GDK_TOUCH_END: return "GDK_TOUCH_END";
case GDK_TOUCH_CANCEL: return "GDK_TOUCH_CANCEL";
case GDK_TOUCHPAD_SWIPE: return "GDK_TOUCHPAD_SWIPE";
case GDK_TOUCHPAD_PINCH: return "GDK_TOUCHPAD_PINCH";
#ifdef GDK_PAD_BUTTON_PRESS
case GDK_PAD_BUTTON_PRESS: return "GDK_PAD_BUTTON_PRESS";
#endif
#ifdef GDK_PAD_BUTTON_RELEASE
case GDK_PAD_BUTTON_RELEASE: return "GDK_PAD_BUTTON_RELEASE";
#endif
#ifdef GDK_PAD_RING
case GDK_PAD_RING: return "GDK_PAD_RING";
#endif
#ifdef GDK_PAD_STRIP
case GDK_PAD_STRIP: return "GDK_PAD_STRIP";
#endif
#ifdef GDK_PAD_GROUP_MODE
case GDK_PAD_GROUP_MODE: return "GDK_PAD_GROUP_MODE";
#endif
}
return "#error";
}
gboolean GtkViewCallback(GtkWidget *widget, GdkEvent *event, LView *This)
{
#if 0
LgiTrace("GtkViewCallback, Event=%s, This=%p(%s\"%s\")\n",
EventTypeToString(event->type),
This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0);
#endif
if (event->type < 0 || (int)event->type > 1000)
{
printf("%s:%i - CORRUPT EVENT %i\n", _FL, event->type);
return false;
}
return This->OnGtkEvent(widget, event);
}
gboolean LView::OnGtkEvent(GtkWidget *widget, GdkEvent *event)
{
printf("LView::OnGtkEvent ?????\n");
return false;
}
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;
}
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;
OnPosChange();
}
return true;
}
LRect GtkGetPos(GtkWidget *w)
{
GtkAllocation a = {0};
gtk_widget_get_allocation(w, &a);
return a;
}
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;
GtkWidget *w = GTK_WIDGET(ParWnd->WindowHandle());
if (w)
{
GdkWindow *h;
if (gtk_widget_get_has_window(w) &&
(h = gtk_widget_get_window(w)))
{
GdkRectangle grc = r;
DEBUG_INVALIDATE(" gdk_window_invalidate_rect %i,%i %i,%i\n", grc.x, grc.y, grc.width, grc.height);
gdk_window_invalidate_rect(h, &grc, true);
}
else
{
DEBUG_INVALIDATE(" gtk_widget_queue_draw\n");
gtk_widget_queue_draw(w);
}
}
Repainting = false;
}
else
{
DEBUG_INVALIDATE(" error: repainting\n");
}
return true;
}
void LView::SetPulse(int Length)
{
ThreadCheck();
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_SET_CTRL_NAME:
{
LAutoPtr s((LString*)Msg->B());
SetCtrlName(Msg->A(), s ? *s : NULL);
break;
}
case M_SET_CTRL_ENABLE:
{
SetCtrlEnabled(Msg->A(), Msg->B());
break;
}
case M_SET_CTRL_VISIBLE:
{
SetCtrlVisible(Msg->A(), Msg->B());
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_CAPTURE_PULSE:
{
auto wnd = GetWindow();
if (!wnd)
{
printf("%s:%i - No window.\n", _FL);
break;
}
LMouse m;
if (!wnd->GetMouse(m, true))
{
printf("%s:%i - GetMouse failed.\n", _FL);
break;
}
auto c = wnd->GetPos();
if (c.Overlap(m))
break; // Over the window, so we'll get normal events...
if (!_Capturing)
break; // Don't care now, there is no window capturing...
if (d->PrevMouse.x < 0)
{
d->PrevMouse = m; // Initialize the previous mouse var.
break;
}
int mask = LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT;
bool coordsChanged = m.x != d->PrevMouse.x || m.y != d->PrevMouse.y;
bool buttonsChanged = (m.Flags & mask) != (d->PrevMouse.Flags & mask);
if (!coordsChanged && !buttonsChanged)
break; // Nothing changed since last poll..
int prevFlags = d->PrevMouse.Flags;
d->PrevMouse = m;
// Convert coords to local view...
m.Target = _Capturing;
#if 0
printf("%s:%i - Mouse(%i,%i) outside window(%s, %s).. %s\n",
_FL, m.x, m.y, c.GetStr(), wnd->Name(),
m.ToString().Get());
#endif
m.ToView();
// Process events...
if (coordsChanged)
{
// printf("Move event: %s\n", m.ToString().Get());
_Capturing->OnMouseMove(m);
}
/* This seems to happen anyway? Ok then... whatevs
if (buttonsChanged)
{
m.Flags &= ~mask; // clear any existing
m.Flags |= prevFlags & mask;
m.Down(false);
printf("Click event: %s, mask=%x flags=%x\n", m.ToString().Get(), mask, m.Flags);
_Capturing->OnMouseClick(m);
}
*/
break;
}
case M_CHANGE:
{
LViewI *Ctrl;
if (GetViewById(Msg->A(), Ctrl))
return OnNotify(Ctrl, Msg->B());
break;
}
case M_COMMAND:
{
return OnCommand(Msg->A(), 0, (OsView) Msg->B());
}
case M_THREAD_COMPLETED:
{
auto Th = (LThread*)Msg->A();
if (!Th)
break;
Th->OnComplete();
if (Th->GetDeleteOnExit())
delete Th;
return true;
}
}
return 0;
}
LPoint GtkGetOrigin(LWindow *w)
{
auto Hnd = w->WindowHandle();
if (Hnd)
{
auto Wnd = gtk_widget_get_window(GTK_WIDGET(Hnd));
if (Wnd)
{
GdkRectangle rect;
gdk_window_get_frame_extents(Wnd, &rect);
return LPoint(rect.x, rect.y);
/*
gint x = 0, y = 0;
gdk_window_get_origin(Wnd, &x, &y);
gdk_window_get_root_origin(Wnd, &x, &y);
return LPoint(x, y);
*/
}
else
{
LgiTrace("%s:%i - can't get Wnd for %s\n", _FL, G_OBJECT_TYPE_NAME(Hnd));
}
}
return LPoint();
}
bool LView::PointToScreen(LPoint &p)
{
ThreadCheck();
LPoint Offset;
WindowVirtualOffset(&Offset);
p += Offset;
auto w = GetWindow();
if (!w)
return false;
auto wnd = w->WindowHandle();
if (!wnd)
return false;
auto wid = GTK_WIDGET(wnd);
auto hnd = gtk_widget_get_window(wid);
gint x = 0, y = 0;
gdk_window_get_origin(hnd, &x, &y);
p.x += x;
p.y += y;
return true;
}
bool LView::PointToView(LPoint &p)
{
ThreadCheck();
LPoint Offset;
WindowVirtualOffset(&Offset);
p -= Offset;
auto w = GetWindow();
if (!w)
return false;
auto wnd = w->WindowHandle();
if (!wnd)
return false;
auto wid = GTK_WIDGET(wnd);
auto hnd = gtk_widget_get_window(wid);
gint x = 0, y = 0;
gdk_window_get_origin(hnd, &x, &y);
p.x -= x;
p.y -= y;
return true;
}
bool LView::GetMouse(LMouse &m, bool ScreenCoords)
{
bool Status = true;
ThreadCheck();
auto *w = GetWindow();
GdkModifierType mask = (GdkModifierType)0;
auto display = gdk_display_get_default();
auto deviceManager = gdk_display_get_device_manager(display);
auto device = gdk_device_manager_get_client_pointer(deviceManager);
gdouble axes[2] = {0};
if (gdk_device_get_axis_use(device, 0) != GDK_AXIS_X)
gdk_device_set_axis_use(device, 0, GDK_AXIS_X);
if (gdk_device_get_axis_use(device, 1) != GDK_AXIS_Y)
gdk_device_set_axis_use(device, 1, GDK_AXIS_Y);
if (ScreenCoords || !w)
{
gdk_device_get_state(device, gdk_get_default_root_window(), axes, &mask);
m.x = (int)axes[0];
m.y = (int)axes[1];
}
else if (auto widget = GTK_WIDGET(w->WindowHandle()))
{
auto wnd = gtk_widget_get_window(widget);
gdk_device_get_state(device, wnd, axes, &mask);
if (axes[0] == 0.0 || axes[0] > 10000)
{
// LgiTrace("%s:%i - gdk_device_get_state failed.\n", _FL);
Status = false;
}
else
{
LPoint p;
WindowVirtualOffset(&p);
m.x = (int)axes[0] - p.x - _BorderSize;
m.y = (int)axes[1] - p.y - _BorderSize;
// printf("GetMs %g,%g %i,%i %i,%i device=%p wnd=%p\n", axes[0], axes[1], p.x, p.y, m.x, m.y, device, wnd);
}
}
m.SetModifer(mask);
m.Left((mask & GDK_BUTTON1_MASK) != 0);
m.Middle((mask & GDK_BUTTON2_MASK) != 0);
m.Right((mask & GDK_BUTTON3_MASK) != 0);
m.Down(m.Left() || m.Middle() || m.Right());
m.ViewCoords = !ScreenCoords;
return Status;
}
bool LView::IsAttached()
{
auto w = GetWindow();
if (!w)
return false;
w = dynamic_cast(this);
if (!w)
{
auto p = GetParent();
return d->GotOnCreate && p && p->HasView(this);
}
return w->IsAttached();
}
const char *LView::GetClass()
{
return "LView";
}
bool LView::Attach(LViewI *parent)
{
ThreadCheck();
bool Status = false;
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)
{
auto w = GetWindow();
if (w && TestFlag(WndFlags, GWF_FOCUS))
w->SetFocus(this, LWindow::GainFocus);
Status = true;
if (!Parent->HasView(this))
{
OnAttach();
if (!d->Parent->HasView(this))
d->Parent->AddView(this);
d->Parent->OnChildrenChanged(this, true);
}
if (_Window)
OnGtkRealize();
}
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;
}
void LView::OnGtkDelete()
{
List::I it = Children.begin();
for (LViewI *c = *it; c; c = *++it)
{
LView *v = c->GetGView();
if (v)
v->OnGtkDelete();
}
}
///////////////////////////////////////////////////////////////////
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,
};