diff --git a/include/lgi/common/Html.h b/include/lgi/common/Html.h
--- a/include/lgi/common/Html.h
+++ b/include/lgi/common/Html.h
@@ -1,170 +1,170 @@
/// \file
/// \author Matthew Allen
#pragma once
#include "lgi/common/DocView.h"
#include "lgi/common/HtmlCommon.h"
#include "lgi/common/HtmlParser.h"
#include "lgi/common/Capabilities.h"
#include "lgi/common/ToolTip.h"
#include "lgi/common/FindReplaceDlg.h"
namespace Html1
{
class LTag;
class LFontCache;
/// A lightwight scripting safe HTML control. It has limited CSS support, renders
/// most tables, even when nested. You can provide support for loading external
/// images by implementing the LDocumentEnv::GetImageUri method of the
/// LDocumentEnv interface and passing it into LHtml2::SetEnv.
/// All attempts to open URL's are passed into LDocumentEnv::OnNavigate method of the
/// environment if set. Likewise any content inside active scripting tags, e.g. <? ?>
/// will be striped out of the document and passed to LDocumentEnv::OnDynamicContent, which
/// should return the relevant HTML that the script resolves to. A reasonable default
/// implementation of the LDocumentEnv interface is the LDefaultDocumentEnv object.
///
/// You can set the content of the control through the LHtml2::Name method.
///
/// Retreive any selected content through LHtml2::GetSelection.
class LHtml :
public LDocView,
public ResObject,
public LHtmlParser,
public LCapabilityClient
{
friend class LTag;
friend class LFlowRegion;
class LHtmlPrivate *d;
protected:
// Data
LFontCache *FontCache;
LTag *Tag; // Tree root
LTag *Cursor; // Cursor location..
LTag *Selection; // Edge of selection or NULL
char IsHtml;
int ViewWidth;
uint64_t PaintStart;
LToolTip Tip;
LCss::Store CssStore;
LHashTbl, bool> CssHref;
// Display
LAutoPtr MemDC;
// This lock is separate from the window lock to avoid deadlocks.
struct GJobSem : public LMutex
{
// Data that has to be accessed under Lock
LArray Jobs;
GJobSem() : LMutex("GJobSem") {}
} JobSem;
// Methods
void _New();
void _Delete() override;
LFont *DefFont();
void CloseTag(LTag *t);
void ParseDocument(const char *Doc);
void OnAddStyle(const char *MimeType, const char *Styles) override;
int ScrollY();
void SetCursorVis(bool b);
bool GetCursorVis();
LRect *GetCursorPos();
bool IsCursorFirst();
bool CompareTagPos(LTag *a, ssize_t AIdx, LTag *b, ssize_t BIdx);
int GetTagDepth(LTag *Tag);
LTag *PrevTag(LTag *t);
LTag *NextTag(LTag *t);
LTag *GetLastChild(LTag *t);
public:
LHtml(int Id, int x, int y, int cx, int cy, LDocumentEnv *system = 0);
~LHtml();
// Html
const char *GetClass() override { return "LHtml"; }
bool GetFormattedContent(const char *MimeType, LString &Out, LArray *Media = 0) override;
/// Get the tag at an x,y location
LTag *GetTagByPos( int x, int y,
ssize_t *Index,
LPoint *LocalCoords = NULL,
bool DebugLog = false);
/// Layout content and return size.
LPoint Layout(bool ForceLayout = false);
- bool OnLayout(LViewLayoutInfo &Inf);
+ bool OnLayout(LViewLayoutInfo &Inf) override;
// Options
bool GetLinkDoubleClick();
void SetLinkDoubleClick(bool b);
void SetLoadImages(bool i) override;
bool GetEmoji();
void SetEmoji(bool i);
void SetMaxPaintTime(int Ms);
bool GetMaxPaintTimeout();
// LDocView
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
/// Copy the selection to the clipboard
bool Copy() override;
/// Returns true if there is a selection
bool HasSelection() override;
/// Unselect all the text in the control
void UnSelectAll() override;
/// Select all the text in the control (not impl)
void SelectAll() override;
/// Return the selection in a dynamically allocated string
char *GetSelection() override;
// Prop
// Window
/// Sets the HTML content of the control
bool Name(const char *s) override;
/// Returns the HTML content
const char *Name() override;
/// Sets the HTML content of the control
bool NameW(const char16 *s) override;
/// Returns the HTML content
const char16 *NameW() override;
// Impl
void OnPaint(LSurface *pDC) override;
void OnMouseClick(LMouse &m) override;
void OnMouseMove(LMouse &m) override;
LCursor GetCursor(int x, int y) override;
bool OnMouseWheel(double Lines) override;
bool OnKey(LKey &k) override;
int OnNotify(LViewI *c, LNotification n) override;
void OnPosChange() override;
void OnPulse() override;
LMessage::Result OnEvent(LMessage *Msg) override;
const char *GetMimeType() override { return "text/html"; }
void OnContent(LDocumentEnv::LoadJob *Res) override;
bool GotoAnchor(char *Name);
LHtmlElement *CreateElement(LHtmlElement *Parent) override;
bool EvaluateCondition(const char *Cond) override;
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
void DoFind(std::function Callback) override;
LPointF GetDpiScale();
void SetVScroll(int64 v);
// Javascript handlers
LDom *getElementById(char *Id);
// Events
bool OnFind(LFindReplaceCommon *Params);
virtual bool OnSubmitForm(LTag *Form);
virtual void OnCursorChanged() {}
virtual void OnLoad();
virtual bool OnContextMenuCreate(struct LTagHit &Hit, LSubMenu &RClick) { return true; }
virtual void OnContextMenuCommand(struct LTagHit &Hit, int Cmd) {}
};
}
diff --git a/src/common/Text/TextView3.cpp b/src/common/Text/TextView3.cpp
--- a/src/common/Text/TextView3.cpp
+++ b/src/common/Text/TextView3.cpp
@@ -1,5439 +1,5439 @@
#include
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/TextView3.h"
#include "lgi/common/Input.h"
#include "lgi/common/ScrollBar.h"
#ifdef WIN32
#include
#endif
#include "lgi/common/ClipBoard.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/CssTools.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/Mail.h"
#include "lgi/common/FileSelect.h"
#include "lgi/common/Menu.h"
#include "lgi/common/DropFiles.h"
#include "ViewPriv.h"
#undef max
#ifdef _DEBUG
#define FEATURE_HILIGHT_ALL_MATCHES 1
#else
#define FEATURE_HILIGHT_ALL_MATCHES 0
#endif
#define DefaultCharset "utf-8"
#define SubtractPtr(a, b) ((a) - (b))
#define GDCF_UTF8 -1
#define POUR_DEBUG 0
#define PROFILE_POUR 0
#define PROFILE_PAINT 0
#define DRAW_LINE_BOXES 0
#define WRAP_POUR_TIMEOUT 90 // ms
#define PULSE_TIMEOUT 500 // ms
#define CURSOR_BLINK 1000 // ms
#define ALLOC_BLOCK 64
#define IDC_VS 1000
#ifdef WINDOWS
#define DOUBLE_BUFFER_PAINT 1
#endif
enum Cmds
{
IDM_COPY_URL = 100,
IDM_AUTO_INDENT,
IDM_UTF8,
IDM_PASTE_NO_CONVERT,
IDM_FIXED,
IDM_SHOW_WHITE,
IDM_HARD_TABS,
IDM_INDENT_SIZE,
IDM_TAB_SIZE,
IDM_DUMP,
IDM_RTL,
IDM_COPY_ALL,
IDM_SELECT_ALL,
#ifndef IDM_OPEN
IDM_OPEN,
#endif
#ifndef IDM_NEW
IDM_NEW,
#endif
#ifndef IDM_COPY
IDM_COPY,
#endif
#ifndef IDM_CUT
IDM_CUT,
#endif
#ifndef IDM_PASTE
IDM_PASTE,
#endif
#ifndef IDM_UNDO
IDM_UNDO,
#endif
#ifndef IDM_REDO
IDM_REDO,
#endif
};
#define PAINT_BORDER Back
#if DRAW_LINE_BOXES
#define PAINT_AFTER_LINE LColour(240, 240, 240)
#else
#define PAINT_AFTER_LINE Back
#endif
#define CODEPAGE_BASE 100
#define CONVERT_CODEPAGE_BASE 200
#if !defined(WIN32) && !defined(toupper)
#define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c))
#endif
#define THREAD_CHECK() \
if (!InThread()) \
{ \
LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \
return false; \
}
static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*";
#ifndef WINDOWS
static LArray Ctrls;
#endif
//////////////////////////////////////////////////////////////////////
class LDocFindReplaceParams3 :
public LDocFindReplaceParams,
public LMutex
{
public:
// Find/Replace History
LAutoWString LastFind;
LAutoWString LastReplace;
bool MatchCase;
bool MatchWord;
bool SelectionOnly;
bool SearchUpwards;
LDocFindReplaceParams3() : LMutex("LDocFindReplaceParams3")
{
MatchCase = false;
MatchWord = false;
SelectionOnly = false;
SearchUpwards = false;
}
};
class LTextView3Private : public LCss, public LMutex
{
public:
LTextView3 *View;
LRect rPadding;
int PourX;
bool LayoutDirty;
ssize_t DirtyStart, DirtyLen;
LColour UrlColour;
bool CenterCursor;
ssize_t WordSelectMode;
LString Eol;
LString LastError;
// If the scroll position is set before we get a scroll bar, store the index
// here and set it when the LNotifyScrollBarCreate arrives.
ssize_t VScrollCache;
// Find/Replace Params
bool OwnFindReplaceParams;
LDocFindReplaceParams3 *FindReplaceParams;
// Map buffer
ssize_t MapLen;
char16 *MapBuf;
//
// Thread safe Name(char*) impl
LString SetName;
//
#ifdef _DEBUG
LString PourLog;
#endif
LTextView3Private(LTextView3 *view) : LMutex("LTextView3Private")
{
View = view;
WordSelectMode = -1;
PourX = -1;
VScrollCache = -1;
DirtyStart = DirtyLen = 0;
UrlColour.Rgb(0, 0, 255);
LColour::GetConfigColour("colour.L_URL", UrlColour);
CenterCursor = false;
LayoutDirty = true;
rPadding.ZOff(0, 0);
MapBuf = 0;
MapLen = 0;
OwnFindReplaceParams = true;
FindReplaceParams = new LDocFindReplaceParams3;
}
~LTextView3Private()
{
if (OwnFindReplaceParams)
{
DeleteObj(FindReplaceParams);
}
DeleteArray(MapBuf);
}
void SetDirty(ssize_t Start, ssize_t Len = 0)
{
LayoutDirty = true;
DirtyStart = Start;
DirtyLen = Len;
}
void OnChange(PropType Prop)
{
if (Prop == LCss::PropPadding ||
Prop == LCss::PropPaddingLeft ||
Prop == LCss::PropPaddingRight ||
Prop == LCss::PropPaddingTop ||
Prop == LCss::PropPaddingBottom)
{
LCssTools t(this, View->GetFont());
rPadding.ZOff(0, 0);
rPadding = t.ApplyPadding(rPadding);
}
}
};
//////////////////////////////////////////////////////////////////////
enum UndoType
{
UndoDelete, UndoInsert, UndoChange
};
struct Change : public LRange
{
UndoType Type;
LArray Txt;
};
struct LTextView3Undo : public LUndoEvent
{
LTextView3 *View;
LArray Changes;
LTextView3Undo(LTextView3 *view)
{
View = view;
}
void AddChange(ssize_t At, ssize_t Len, UndoType Type)
{
Change &c = Changes.New();
c.Start = At;
c.Len = Len;
c.Txt.Add(View->Text + At, Len);
c.Type = Type;
}
void OnChange()
{
for (auto &c : Changes)
{
size_t Len = c.Len;
if (View->Text)
{
char16 *t = View->Text + c.Start;
for (size_t i=0; id->SetDirty(c.Start, c.Len);
}
}
// LUndoEvent
void ApplyChange()
{
View->UndoOn = false;
for (auto &c : Changes)
{
switch (c.Type)
{
case UndoInsert:
{
View->Insert(c.Start, c.Txt.AddressOf(), c.Len);
View->Cursor = c.Start + c.Len;
break;
}
case UndoDelete:
{
View->Delete(c.Start, c.Len);
View->Cursor = c.Start;
break;
}
case UndoChange:
{
OnChange();
break;
}
}
}
View->UndoOn = true;
View->Invalidate();
}
void RemoveChange()
{
View->UndoOn = false;
for (auto &c : Changes)
{
switch (c.Type)
{
case UndoInsert:
{
View->Delete(c.Start, c.Len);
break;
}
case UndoDelete:
{
View->Insert(c.Start, c.Txt.AddressOf(), c.Len);
break;
}
case UndoChange:
{
OnChange();
break;
}
}
View->Cursor = c.Start;
}
View->UndoOn = true;
View->Invalidate();
}
};
void LTextView3::LStyle::RefreshLayout(size_t Start, ssize_t Len)
{
View->PourText(Start, Len);
View->PourStyle(Start, Len);
}
//////////////////////////////////////////////////////////////////////
LTextView3::LTextView3( int Id,
int x, int y, int cx, int cy,
LFontType *FontType)
: ResObject(Res_Custom)
{
// init vars
LView::d->Css.Reset(d = new LTextView3Private(this));
TabSize = TAB_SIZE;
IndentSize = TAB_SIZE;
// setup window
SetId(Id);
// default options
#if WINNATIVE
CrLf = true;
SetDlgCode(DLGC_WANTALLKEYS);
#else
#endif
d->Padding(LCss::Len(LCss::LenPx, 2));
#ifdef _DEBUG
// debug times
_PourTime = 0;
_StyleTime = 0;
_PaintTime = 0;
#endif
// Data
Alloc = ALLOC_BLOCK;
Text = new char16[Alloc];
if (Text) *Text = 0;
Cursor = 0;
Size = 0;
// Display
if (FontType)
Font = FontType->Create();
else
{
LFontType Type;
if (Type.GetSystemFont("Fixed"))
Font = Type.Create();
else
printf("%s:%i - failed to create font.\n", _FL);
}
if (Font)
{
SetTabStop(true);
Underline = new LFont;
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
if (d->UrlColour.IsValid())
Underline->Fore(d->UrlColour);
Underline->Create();
}
Bold = new LFont;
if (Bold)
{
*Bold = *Font;
Bold->Bold(true);
Bold->Create();
}
OnFontChange();
}
else
{
LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType);
Font = LSysFont;
}
CursorPos.ZOff(1, LineY-1);
CursorPos.Offset(d->rPadding.x1, d->rPadding.y1);
LRect r;
r.ZOff(cx-1, cy-1);
r.Offset(x, y);
SetPos(r);
LResources::StyleElement(this);
}
LTextView3::~LTextView3()
{
#ifndef WINDOWS
Ctrls.Delete(this);
#endif
Line.DeleteObjects();
Style.Empty();
DeleteArray(TextCache);
DeleteArray(Text);
if (Font != LSysFont) DeleteObj(Font);
DeleteObj(FixedFont);
DeleteObj(Underline);
DeleteObj(Bold);
// 'd' is owned by the LView::Css auto ptr
}
char16 *LTextView3::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace)
{
if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace)
{
if (Len > d->MapLen)
{
DeleteArray(d->MapBuf);
d->MapBuf = new char16[Len + RtlTrailingSpace];
d->MapLen = Len;
}
if (d->MapBuf)
{
int n = 0;
if (RtlTrailingSpace)
{
d->MapBuf[n++] = ' ';
for (int i=0; iMapBuf[n++] = Str[i];
}
}
else if (ObscurePassword)
{
for (int i=0; iMapBuf[n++] = '*';
}
}
/*
else if (ShowWhiteSpace)
{
for (int i=0; iMapBuf[n++] = 0xb7;
}
else if (Str[i] == '\t')
{
d->MapBuf[n++] = 0x2192;
}
else
{
d->MapBuf[n++] = Str[i];
}
}
}
*/
return d->MapBuf;
}
}
return Str;
}
void LTextView3::SetFixedWidthFont(bool i)
{
if (FixedWidthFont ^ i)
{
if (i)
{
LFontType Type;
if (Type.GetSystemFont("Fixed"))
{
LFont *f = FixedFont;
FixedFont = Font;
Font = f;
if (!Font)
{
Font = Type.Create();
if (Font)
{
Font->PointSize(FixedFont->PointSize());
}
}
LDocView::SetFixedWidthFont(i);
}
}
else if (FixedFont)
{
LFont *f = FixedFont;
FixedFont = Font;
Font = f;
LDocView::SetFixedWidthFont(i);
}
OnFontChange();
Invalidate();
}
}
void LTextView3::SetReadOnly(bool i)
{
LDocView::SetReadOnly(i);
#if WINNATIVE
SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS);
#endif
}
void LTextView3::SetCrLf(bool crlf)
{
CrLf = crlf;
}
void LTextView3::SetTabSize(uint8_t i)
{
TabSize = limit(i, 2, 32);
OnFontChange();
OnPosChange();
Invalidate();
}
void LTextView3::SetWrapType(LDocWrapType i)
{
LDocView::SetWrapType(i);
CanScrollX = i != TEXTED_WRAP_REFLOW;
OnPosChange();
Invalidate();
}
LFont *LTextView3::GetFont()
{
return Font;
}
LFont *LTextView3::GetBold()
{
return Bold;
}
void LTextView3::SetFont(LFont *f, bool OwnIt)
{
if (!f)
return;
if (OwnIt)
{
if (Font != LSysFont)
DeleteObj(Font);
Font = f;
}
else if (!Font || Font == LSysFont)
{
Font = new LFont(*f);
}
else
{
*Font = *f;
}
if (Font)
{
if (!Underline)
Underline = new LFont;
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
Underline->Create();
if (d->UrlColour.IsValid())
Underline->Fore(d->UrlColour);
}
if (!Bold)
Bold = new LFont;
if (Bold)
{
*Bold = *Font;
Bold->Bold(true);
Bold->Create();
}
}
OnFontChange();
}
void LTextView3::OnFontChange()
{
if (Font)
{
// get line height
// int OldLineY = LineY;
if (!Font->Handle())
Font->Create();
LineY = Font->GetHeight();
if (LineY < 1) LineY = 1;
// get tab size
char Spaces[32];
memset(Spaces, 'A', TabSize);
Spaces[TabSize] = 0;
LDisplayString ds(Font, Spaces);
Font->TabSize(ds.X());
// repour doc
d->SetDirty(0, Size);
// validate blue underline font
if (Underline)
{
*Underline = *Font;
Underline->Underline(true);
Underline->Create();
}
#if WINNATIVE // Set the IME font.
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
COMPOSITIONFORM Cf;
Cf.dwStyle = CFS_POINT;
Cf.ptCurrentPos.x = CursorPos.x1;
Cf.ptCurrentPos.y = CursorPos.y1;
LOGFONT FontInfo;
GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo);
ImmSetCompositionFont(hIMC, &FontInfo);
ImmReleaseContext(Handle(), hIMC);
}
#endif
}
}
void LTextView3::LogLines()
{
int Idx = 0;
LgiTrace("DocSize: %i\n", (int)Size);
for (auto i : Line)
{
LgiTrace(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr());
Idx++;
}
#ifdef _DEBUG
if (d->PourLog)
LgiTrace("%s", d->PourLog.Get());
#endif
}
bool LTextView3::ValidateLines(bool CheckBox)
{
size_t Pos = 0;
char16 *c = Text;
size_t Idx = 0;
LTextLine *Prev = NULL;
for (auto i : Line)
{
LTextLine *l = i;
if (l->Start != Pos)
{
LogLines();
LAssert(!"Incorrect start.");
return false;
}
char16 *e = c;
if (WrapType == TEXTED_WRAP_NONE)
{
while (*e && *e != '\n')
e++;
}
else
{
char16 *end = Text + l->Start + l->Len;
while (*e && *e != '\n' && e < end)
e++;
}
ssize_t Len = e - c;
if (l->Len != Len)
{
LogLines();
LAssert(!"Incorrect length.");
return false;
}
if (CheckBox &&
Prev &&
Prev->r.y2 != l->r.y1 - 1)
{
LogLines();
LAssert(!"Lines not joined vertically");
}
if (*e)
{
if (*e == '\n')
e++;
else if (WrapType == TEXTED_WRAP_REFLOW)
e++;
}
Pos = e - Text;
c = e;
Idx++;
Prev = l;
}
if (WrapType == TEXTED_WRAP_NONE &&
Pos != Size)
{
LogLines();
LAssert(!"Last line != end of doc");
return false;
}
return true;
}
int LTextView3::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle)
{
int Changes = 0;
for (auto &s : Style)
{
if (s.Start == Start)
{
if (Diff < 0 || ExtendStyle)
s.Len += Diff;
else
s.Start += Diff;
Changes++;
}
else if (s.Start > Start)
{
s.Start += Diff;
Changes++;
}
}
return Changes;
}
// break array, break out of loop when we hit these chars
#define ExitLoop(c) ( (c) == 0 || \
(c) == '\n' || \
(c) == ' ' || \
(c) == '\t' \
)
// extra breaking opportunities
#define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \
( (c) >= 0x3300 && (c) <= 0x9FAF ) \
)
/*
Prerequisite:
The Line list must have either the objects with the correct Start/Len or be missing the lines altogether...
*/
void LTextView3::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */)
{
#if PROFILE_POUR
char _txt[256];
sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size);
LProfile Prof(_txt);
#endif
#if !defined(HAIKU)
LAssert(InThread());
#endif
LRect Client = GetClient();
int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2;
int Cy = 0;
MaxX = 0;
ssize_t Idx = -1;
LTextLine *Cur = GetTextLine(Start, &Idx);
// LgiTrace("Pour %i:%i Cur=%p Idx=%i\n", (int)Start, (int)Length, (int)Cur, (int)Idx);
if (!Cur || !Cur->r.Valid())
{
// Find the last line that has a valid position...
for (auto i = Idx >= 0 ? Line.begin(Idx) : Line.rbegin(); *i; i--, Idx--)
{
Cur = *i;
if (Cur->r.Valid())
{
Cy = Cur->r.y1;
if (Idx < 0)
Idx = Line.IndexOf(Cur);
break;
}
}
}
if (Cur && !Cur->r.Valid())
Cur = NULL;
if (Cur)
{
Cy = Cur->r.y1;
Start = Cur->Start;
Length = Size - Start;
// LgiTrace("Reset start to %i:%i because Cur!=NULL\n", (int)Start, (int)Length);
}
else
{
Idx = 0;
Start = 0;
Length = Size;
}
if (!Text || !Font || Mx <= 0)
return;
// Tracking vars
ssize_t e;
//int LastX = 0;
int WrapCol = GetWrapAtCol();
LDisplayString Sp(Font, " ", 1);
int WidthOfSpace = Sp.X();
if (WidthOfSpace < 1)
{
printf("%s:%i - WidthOfSpace test failed.\n", _FL);
return;
}
// Alright... lets pour!
uint64 StartTs = LCurrentTime();
if (WrapType == TEXTED_WRAP_NONE)
{
// Find the dimensions of each line that is missing a rect
#if PROFILE_POUR
Prof.Add("NoWrap: ExistingLines");
#endif
#ifdef _DEGBUG
LStringPipe Log(1024);
Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour);
#endif
ssize_t Pos = 0;
for (auto i = Line.begin(Idx); *i; i++, Idx++)
{
LTextLine *l = *i;
#ifdef _DEGBUG
Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid());
#endif
if (!l->r.Valid()) // If the layout is not valid...
{
LDisplayString ds(Font, Text + l->Start, l->Len);
l->r.x1 = d->rPadding.x1;
l->r.x2 = l->r.x1 + ds.X();
MaxX = MAX(MaxX, l->r.X());
}
// Adjust the y position anyway... it's free.
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Cy = l->r.y2 + 1;
Pos = l->Start + l->Len;
if (Text[Pos] == '\n')
Pos++;
}
// Now if we are missing lines as well, create them and lay them out
#if PROFILE_POUR
Prof.Add("NoWrap: NewLines");
#endif
while (Pos < Size)
{
LTextLine *l = new LTextLine;
l->Start = Pos;
char16 *c = Text + Pos;
char16 *e = c;
while (*e && *e != '\n')
e++;
l->Len = e - c;
#ifdef _DEGBUG
Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len);
#endif
l->r.x1 = d->rPadding.x1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
if (l->Len)
{
LDisplayString ds(Font, Text + l->Start, l->Len);
l->r.x2 = l->r.x1 + ds.X();
}
else
{
l->r.x2 = l->r.x1;
}
Line.Insert(l);
if (*e == '\n')
e++;
MaxX = MAX(MaxX, l->r.X());
Cy = l->r.y2 + 1;
Pos = e - Text;
Idx++;
}
#ifdef _DEGBUG
d->PourLog = Log.NewLStr();
#endif
PartialPour = false;
PartialPourLines = 0;
}
else // Wrap text
{
int DisplayStart = ScrollYLine();
int DisplayLines = (Client.Y() + LineY - 1) / LineY;
int DisplayEnd = DisplayStart + DisplayLines;
// Pouring is split into 2 parts...
// 1) pouring to the end of the displayed text.
// 2) pouring from there to the end of the document.
// potentially taking several goes to complete the full pour
// This allows the document to display and edit faster..
bool PourToDisplayEnd = Line.Length() < DisplayEnd;
#if 0
LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n",
Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd);
#endif
if ((ssize_t)Line.Length() > Idx)
{
for (auto i = Line.begin(Idx); *i; i++)
delete *i;
Line.Length(Idx);
Cur = NULL;
}
int Cx = 0;
ssize_t i;
for (i=Start; i= Size ||
Text[e] == '\n' ||
(e-i) >= WrapCol)
{
break;
}
e++;
}
// Seek back some characters if we are mid word
size_t OldE = e;
if (e < Size &&
Text[e] != '\n')
{
while (e > i)
{
if (ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
{
break;
}
e--;
}
}
if (e == i)
{
// No line break at all, so seek forward instead
for (e=OldE; e < Size && Text[e] != '\n'; e++)
{
if (ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
break;
}
}
// Calc the width
LDisplayString ds(Font, Text + i, e - i);
Width = ds.X();
}
else
{
// Wrap to edge of screen
ssize_t PrevExitChar = -1;
int PrevX = -1;
while (true)
{
if (e >= Size ||
ExitLoop(Text[e]) ||
ExtraBreak(Text[e]))
{
LDisplayString ds(Font, Text + i, e - i);
if (ds.X() + Cx > Mx)
{
if (PrevExitChar > 0)
{
e = PrevExitChar;
Width = PrevX;
}
else
{
Width = ds.X();
}
break;
}
else if (e >= Size ||
Text[e] == '\n')
{
Width = ds.X();
break;
}
PrevExitChar = e;
PrevX = ds.X();
}
e++;
}
}
// Create layout line
LTextLine *l = new LTextLine;
if (l)
{
l->Start = i;
l->Len = e - i;
l->r.x1 = d->rPadding.x1;
l->r.x2 = l->r.x1 + Width - 1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Line.Insert(l);
if (PourToDisplayEnd)
{
if (Line.Length() > DisplayEnd)
{
// We have reached the end of the displayed area... so
// exit out temporarily to display the layout to the user
PartialPour = true;
PartialPourLines = std::max(PartialPourLines, Line.Length());
break;
}
}
else
{
// Otherwise check if we are taking too long...
if (Line.Length() % 20 == 0)
{
uint64 Now = LCurrentTime();
if (Now - StartTs > WRAP_POUR_TIMEOUT)
{
PartialPour = true;
PartialPourLines = std::max(PartialPourLines, Line.Length());
break;
}
}
}
MaxX = MAX(MaxX, l->r.X());
Cy += LineY;
if (e < Size)
e++;
}
}
if (i >= Size)
{
PartialPour = false;
PartialPourLines = 0;
}
SendNotify(LNotifyCursorChanged);
}
#ifdef _DEBUG
// ValidateLines(true);
#endif
#if PROFILE_POUR
Prof.Add("LastLine");
#endif
if (!PartialPour)
{
auto It = Line.rbegin();
LTextLine *Last = It != Line.end() ? *It : NULL;
if (!Last ||
Last->Start + Last->Len < Size)
{
LTextLine *l = new LTextLine;
if (l)
{
l->Start = Size;
l->Len = 0;
l->r.x1 = l->r.x2 = d->rPadding.x1;
l->r.y1 = Cy;
l->r.y2 = l->r.y1 + LineY - 1;
Line.Insert(l);
MaxX = MAX(MaxX, l->r.X());
Cy += LineY;
}
}
}
bool ScrollYNeeded = Client.Y() < (std::max(PartialPourLines, Line.Length()) * LineY);
bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL);
d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange;
#if PROFILE_POUR
static LString _s;
_s.Printf("ScrollBars dirty=%i", d->LayoutDirty);
Prof.Add(_s);
#endif
if (ScrollChange)
{
#if 0
LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n",
_FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour);
#endif
SetScrollBars(false, ScrollYNeeded);
}
UpdateScrollBars();
#if 0 // def _DEBUG
if (GetWindow())
{
static char s[256];
sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000);
GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s);
}
#endif
#if POUR_DEBUG
printf("Lines=%i\n", Line.Length());
int Index = 0;
for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++)
{
printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe());
}
#endif
}
bool LTextView3::InsertStyle(LAutoPtr s)
{
if (!s)
return false;
LAssert(s->Start >= 0);
LAssert(s->Len > 0);
ssize_t Last = 0;
// int n = 0;
// LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr());
if (Style.Length() > 0)
{
// Optimize for last in the list
auto Last = Style.rbegin();
if (s->Start >= (ssize_t)Last->End())
{
Style.Insert(*s);
return true;
}
}
for (auto i = Style.begin(); i != Style.end(); i++)
{
if (s->Overlap(*i))
{
if (s->Owner > i->Owner)
{
// Fail the insert
return false;
}
else
{
// Replace mode...
*i = *s;
return true;
}
}
if (s->Start >= Last && s->Start < i->Start)
{
Style.Insert(*s, i);
return true;
}
}
Style.Insert(*s);
return true;
}
LTextView3::LStyle *LTextView3::GetNextStyle(StyleIter &s, ssize_t Where)
{
if (Where >= 0)
s = Style.begin();
else
s++;
while (s != Style.end())
{
// determine whether style is relevant..
// styles in the selected region are ignored
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (SelStart >= 0 &&
s->Start >= Min &&
s->Start+s->Len < Max)
{
// style is completely inside selection: ignore
s++;
}
else if (Where >= 0 &&
s->Start+s->Len < Where)
{
s++;
}
else
{
return &(*s);
}
}
return NULL;
}
#if 0
CURSOR_CHAR GetCursor()
{
#ifdef WIN32
LArray Ver;
int Os = LGetOs(&Ver);
if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) &&
Ver[0] >= 5)
{
return MAKEINTRESOURCE(32649); // hand
}
else
{
return IDC_ARROW;
}
#endif
return 0;
}
#endif
LTextView3::LStyle *LTextView3::HitStyle(ssize_t i)
{
for (auto &s : Style)
{
if (i >= s.Start && i < (ssize_t)s.End())
{
return &s;
}
}
return NULL;
}
void LTextView3::PourStyle(size_t Start, ssize_t EditSize)
{
#ifdef _DEBUG
int64 StartTime = LCurrentTime();
#endif
LAssert(InThread());
if (!Text || Size < 1)
return;
ssize_t Length = MAX(EditSize, 0);
if ((ssize_t)Start + Length >= Size)
Length = Size - Start; // For deletes, this sizes the edit length within bounds.
// Expand re-style are to word boundaries before and after the area of change
while (Start > 0 && UrlChar(Text[Start-1]))
{
// Move the start back
Start--;
Length++;
}
while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length]))
{
// Move the end back
Length++;
}
// Delete all the styles that we own inside the changed area
for (StyleIter s = Style.begin(); s != Style.end();)
{
if (s->Owner == STYLE_NONE)
{
if (EditSize > 0)
{
if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize))
{
Style.Delete(s);
continue;
}
}
else
{
if (s->Overlap(Start, -EditSize))
{
Style.Delete(s);
continue;
}
}
}
s++;
}
if (UrlDetect)
{
LArray Links;
LAssert((ssize_t)Start + Length <= Size);
if (LDetectLinks(Links, Text + Start, Length))
{
for (uint32_t i=0; i Url(new LStyle(STYLE_URL));
if (Url)
{
Url->View = this;
Url->Start = Inf.Start + Start;
Url->Len = Inf.Len;
// Url->Email = Inf.Email;
Url->Font = Underline;
Url->Fore = d->UrlColour;
InsertStyle(Url);
}
}
}
}
#ifdef _DEBUG
_StyleTime = LCurrentTime() - StartTime;
#endif
}
bool LTextView3::Insert(size_t At, const char16 *Data, ssize_t Len)
{
LProfile Prof("LTextView3::Insert");
Prof.HideResultsIfBelow(1000);
LAssert(InThread());
if (!ReadOnly && Len > 0)
{
if (!Data)
return false;
// limit input to valid data
At = MIN(Size, (ssize_t)At);
// make sure we have enough memory
size_t NewAlloc = Size + Len + 1;
NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK);
if (NewAlloc != Alloc)
{
char16 *NewText = new char16[NewAlloc];
if (NewText)
{
if (Text)
{
// copy any existing data across
memcpy(NewText, Text, (Size + 1) * sizeof(char16));
}
DeleteArray(Text);
Text = NewText;
Alloc = NewAlloc;
}
else
{
// memory allocation error
return false;
}
}
Prof.Add("MemChk");
if (Text)
{
// Insert the data
// Move the section after the insert to make space...
memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16));
Prof.Add("Cpy");
// Copy new data in...
memcpy(Text+At, Data, Len * sizeof(char16));
Size += Len;
Text[Size] = 0; // NULL terminate
Prof.Add("Undo");
// Add the undo object...
if (UndoOn)
{
LAutoPtr Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(At, Len, UndoInsert);
if (Obj)
UndoQue += Obj.Release();
}
// Clear layout info for the new text
ssize_t Idx = -1;
LTextLine *Cur = NULL;
if (Line.Length() == 0)
{
// Empty doc... set up the first line
Line.Insert(Cur = new LTextLine);
Idx = 0;
Cur->Start = 0;
}
else
{
Cur = GetTextLine(At, &Idx);
}
if (Cur)
{
if (WrapType == TEXTED_WRAP_NONE)
{
// Clear layout for current line...
Cur->r.ZOff(-1, -1);
Prof.Add("NoWrap add lines");
// Add any new lines that we need...
char16 *e = Text + At + Len;
char16 *c;
for (c = Text + At; c < e; c++)
{
if (*c == '\n')
{
// Set the size of the current line...
size_t Pos = c - Text;
Cur->Len = Pos - Cur->Start;
// Create a new line...
Cur = new LTextLine();
if (!Cur)
return false;
Cur->Start = Pos + 1;
Line.Insert(Cur, ++Idx);
}
}
Prof.Add("CalcLen");
// Make sure the last Line's length is set..
Cur->CalcLen(Text);
Prof.Add("UpdatePos");
// Now update all the positions of the following lines...
for (auto i = Line.begin(++Idx); *i; i++)
(*i)->Start += Len;
}
else
{
// Clear all lines to the end of the doc...
for (auto i = Line.begin(Idx); *i; i++)
delete *i;
Line.Length(Idx);
}
}
else
{
// If wrap is on then this can happen when an Insert happens before the
// OnPulse event has laid out the new text. Probably not a good thing in
// non-wrap mode
if (WrapType == TEXTED_WRAP_NONE)
{
LTextLine *l = *Line.rbegin();
printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n",
_FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType);
if (l)
printf("Last=%i, %i\n", (int)l->Start, (int)l->Len);
}
}
#ifdef _DEBUG
// Prof.Add("Validate");
// ValidateLines();
#endif
if (AdjustStylePos)
AdjustStyles(At, Len);
Dirty = true;
if (PourEnabled)
{
Prof.Add("PourText");
PourText(At, Len);
Prof.Add("PourStyle");
auto Start = LCurrentTime();
PourStyle(At, Len);
auto End = LCurrentTime();
if (End - Start > 1000)
{
PourStyle(At, Len);
}
}
SendNotify(LNotifyDocChanged);
return true;
}
}
return false;
}
bool LTextView3::Delete(size_t At, ssize_t Len)
{
bool Status = false;
LAssert(InThread());
if (!ReadOnly)
{
// limit input
At = MAX(At, 0);
At = MIN((ssize_t)At, Size);
Len = MIN(Size-(ssize_t)At, Len);
if (Len > 0)
{
int HasNewLine = 0;
for (int i=0; i Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(At, Len, UndoDelete);
if (Obj)
UndoQue += Obj.Release();
}
memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16));
Size -= Len;
Text[Size] = 0;
if (WrapType == TEXTED_WRAP_NONE)
{
ssize_t Idx = -1;
LTextLine *Cur = GetTextLine(At, &Idx);
if (Cur)
{
Cur->r.ZOff(-1, -1);
// Delete some lines...
for (int i=0; iCalcLen(Text);
// Shift all further lines down...
for (auto i = Line.begin(Idx + 1); *i; i++)
(*i)->Start -= Len;
}
}
else
{
ssize_t Index;
LTextLine *Cur = GetTextLine(At, &Index);
if (Cur)
{
for (auto i = Line.begin(Index); *i; i++)
delete *i;
Line.Length(Index);
}
}
Dirty = true;
Status = true;
#ifdef _DEBUG
// ValidateLines();
#endif
if (AdjustStylePos)
AdjustStyles(At, -Len);
if (PourEnabled)
{
PourText(At, -Len);
PourStyle(At, -Len);
}
if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len)
{
SetCaret(At, false, HasNewLine != 0);
}
// Handle repainting in flowed mode, when the line starts change
if (WrapType == TEXTED_WRAP_REFLOW)
{
ssize_t Index;
LTextLine *Cur = GetTextLine(At, &Index);
if (Cur)
{
LRect r = Cur->r;
r.x2 = GetClient().x2;
r.y2 = GetClient().y2;
Invalidate(&r);
}
}
SendNotify(LNotifyDocChanged);
Status = true;
}
}
return Status;
}
void LTextView3::DeleteSelection(char16 **Cut)
{
if (SelStart >= 0)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (Cut)
{
*Cut = NewStrW(Text + Min, Max - Min);
}
Delete(Min, Max - Min);
SetCaret(Min, false, true);
}
}
List::I LTextView3::GetTextLineIt(ssize_t Offset, ssize_t *Index)
{
int i = 0;
for (auto It = Line.begin(); It != Line.end(); It++)
{
auto l = *It;
if (Offset >= l->Start && Offset <= l->Start+l->Len)
{
if (Index)
*Index = i;
return It;
}
i++;
}
return Line.end();
}
int64 LTextView3::Value()
{
auto n = Name();
#ifdef _MSC_VER
return (n) ? _atoi64(n) : 0;
#else
return (n) ? atoll(n) : 0;
#endif
}
void LTextView3::Value(int64 i)
{
char Str[32];
sprintf_s(Str, sizeof(Str), LPrintfInt64, i);
Name(Str);
}
LString LTextView3::operator[](ssize_t LineIdx)
{
if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines())
return LString();
LTextLine *Ln = Line[LineIdx-1];
if (!Ln)
return LString();
LString s(Text + Ln->Start, Ln->Len);
return s;
}
const char *LTextView3::Name()
{
UndoQue.Empty();
DeleteArray(TextCache);
TextCache = WideToUtf8(Text);
return TextCache;
}
bool LTextView3::Name(const char *s)
{
if (InThread())
{
UndoQue.Empty();
DeleteArray(TextCache);
DeleteArray(Text);
Line.DeleteObjects();
Style.Empty();
LAssert(LIsUtf8(s));
Text = Utf8ToWide(s);
if (!Text)
{
Text = new char16[1];
if (Text) *Text = 0;
}
Size = Text ? StrlenW(Text) : 0;
Alloc = Size + 1;
Cursor = MIN(Cursor, Size);
if (Text)
{
// Remove '\r's
char16 *o = Text;
for (char16 *i=Text; *i; i++)
{
if (*i != '\r')
{
*o++ = *i;
}
else Size--;
}
*o++ = 0;
}
// update everything else
d->SetDirty(0, Size);
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars();
Invalidate();
}
else if (d->Lock(_FL))
{
if (IsAttached())
{
d->SetName = s;
PostEvent(M_TEXT_UPDATE_NAME);
}
else LAssert(!"Can't post event to detached/virtual window.");
d->Unlock();
}
return true;
}
const char16 *LTextView3::NameW()
{
return Text;
}
const char16 *LTextView3::TextAtLine(size_t Index)
{
if (Index >= Line.Length())
return NULL;
auto ln = Line[Index];
return Text + ln->Start;
}
bool LTextView3::NameW(const char16 *s)
{
DeleteArray(Text);
Size = s ? StrlenW(s) : 0;
Alloc = Size + 1;
Text = new char16[Alloc];
Cursor = MIN(Cursor, Size);
if (Text)
{
memcpy(Text, s, Size * sizeof(char16));
// remove LF's
int In = 0, Out = 0;
CrLf = false;
for (; InSetDirty(0, Size);
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars();
Invalidate();
return true;
}
LRange LTextView3::GetSelectionRange()
{
LRange r;
if (HasSelection())
{
r.Start = MIN(SelStart, SelEnd);
ssize_t End = MAX(SelStart, SelEnd);
r.Len = End - r.Start;
}
return r;
}
char *LTextView3::GetSelection()
{
LRange s = GetSelectionRange();
if (s.Len > 0)
{
return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) );
}
return 0;
}
bool LTextView3::HasSelection()
{
return (SelStart >= 0) && (SelStart != SelEnd);
}
void LTextView3::SelectAll()
{
SelStart = 0;
SelEnd = Size;
Invalidate();
}
void LTextView3::UnSelectAll()
{
bool Update = HasSelection();
SelStart = -1;
SelEnd = -1;
if (Update)
{
Invalidate();
}
}
size_t LTextView3::GetLines()
{
return Line.Length();
}
void LTextView3::GetTextExtent(int &x, int &y)
{
PourText(0, Size);
x = MaxX + d->rPadding.x1;
y = (int)(Line.Length() * LineY);
}
bool LTextView3::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index)
{
ssize_t FromIndex = 0;
LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex);
if (!From)
return false;
Pt.x = (int) (Cursor - From->Start);
Pt.y = (int) FromIndex;
return true;
}
ssize_t LTextView3::GetCaret(bool Cur)
{
if (Cur)
{
return Cursor;
}
return 0;
}
ssize_t LTextView3::IndexAt(int x, int y)
{
LTextLine *l = Line.ItemAt(y);
if (l)
{
return l->Start + MIN(x, l->Len);
}
return 0;
}
bool LTextView3::ScrollToOffset(size_t Off)
{
bool ForceFullUpdate = false;
ssize_t ToIndex = 0;
LTextLine *To = GetTextLine(Off, &ToIndex);
if (To)
{
LRect Client = GetClient();
int DisplayLines = Client.Y() / LineY;
if (VScroll)
{
if (ToIndex < VScroll->Value())
{
// Above the visible region...
if (d->CenterCursor)
{
ssize_t i = ToIndex - (DisplayLines >> 1);
VScroll->Value(MAX(0, i));
}
else
{
VScroll->Value(ToIndex);
}
ForceFullUpdate = true;
}
if (ToIndex >= VScroll->Value() + DisplayLines)
{
int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines;
ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines);
if (v != VScroll->Value())
{
// Below the visible region
VScroll->Value(v);
ForceFullUpdate = true;
}
}
}
else
{
d->VScrollCache = ToIndex;
}
}
return ForceFullUpdate;
}
void LTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate)
{
// int _Start = LCurrentTime();
Blink = true;
// Bound the new cursor position to the document
if ((ssize_t)i > Size) i = Size;
// Store the old selection and cursor
ssize_t s = SelStart, e = SelEnd, c = Cursor;
// If there is going to be a selected area
if (Select && i != SelStart)
{
// Then set the start
if (SelStart < 0)
{
// We are starting a new selection
SelStart = Cursor;
}
// And end
SelEnd = i;
}
else
{
// Clear the selection
SelStart = SelEnd = -1;
}
ssize_t FromIndex = 0;
LTextLine *From = GetTextLine(Cursor, &FromIndex);
Cursor = i;
// check the cursor is on the screen
ForceFullUpdate |= ScrollToOffset(Cursor);
// check whether we need to update the screen
ssize_t ToIndex = 0;
LTextLine *To = GetTextLine(Cursor, &ToIndex);
if (ForceFullUpdate ||
!To ||
!From)
{
// need full update
Invalidate();
}
else if
(
(
SelStart != s
||
SelEnd != e
)
)
{
// Update just the selection bounds
LRect Client = GetClient();
size_t Start, End;
if (SelStart >= 0 && s >= 0)
{
// Selection has changed, union the before and after regions
Start = MIN(Cursor, c);
End = MAX(Cursor, c);
}
else if (SelStart >= 0)
{
// Selection created...
Start = MIN(SelStart, SelEnd);
End = MAX(SelStart, SelEnd);
}
else if (s >= 0)
{
// Selection removed...
Start = MIN(s, e);
End = MAX(s, e);
}
else return;
auto SLine = GetTextLine(Start);
auto ELine = GetTextLine(End);
LRect u;
if (SLine && ELine)
{
if (SLine->r.Valid())
{
u = DocToScreen(SLine->r);
}
else
u.Set(0, 0, Client.X()-1, 1); // Start of visible page
LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1);
if (ELine->r.Valid())
{
b = DocToScreen(ELine->r);
}
else
{
b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1);
}
u.Union(&b);
u.x1 = 0;
u.x2 = X();
}
else
{
/*
printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n",
_FL,
(int)Start, SLine,
(int)End, ELine);
*/
u = Client;
}
Invalidate(&u);
}
else if (Cursor != c)
{
// just the cursor has moved
// update the line the cursor moved to
LRect r = To->r;
r.Offset(-ScrollX, d->rPadding.y1-DocOffset);
r.x2 = X();
Invalidate(&r);
if (To != From)
{
// update the line the cursor came from,
// if it's a different line from the "to"
r = From->r;
r.Offset(-ScrollX, d->rPadding.y1-DocOffset);
r.x2 = X();
Invalidate(&r);
}
}
if (c != Cursor)
{
// Send off notify
SendNotify(LNotifyCursorChanged);
}
//int _Time = LCurrentTime() - _Start;
//printf("Setcursor=%ims\n", _Time);
}
void LTextView3::SetBorder(int b)
{
}
bool LTextView3::Cut()
{
bool Status = false;
char16 *Txt16 = 0;
DeleteSelection(&Txt16);
if (Txt16)
{
#ifdef WIN32
Txt16 = ConvertToCrLf(Txt16);
#endif
char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset);
LClipBoard Clip(this);
Clip.Text(Txt8);
Status = Clip.TextW(Txt16, false);
DeleteArray(Txt8);
DeleteArray(Txt16);
}
return Status;
}
bool LTextView3::Copy()
{
bool Status = true;
printf("txt copy\n");
if (SelStart >= 0)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
#ifdef WIN32
char16 *Txt16 = NewStrW(Text+Min, Max-Min);
Txt16 = ConvertToCrLf(Txt16);
char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset);
#else
char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text));
#endif
LClipBoard Clip(this);
Clip.Text(Txt8);
#ifdef WIN32
Clip.TextW(Txt16, false);
DeleteArray(Txt16);
#endif
DeleteArray(Txt8);
}
else LgiTrace("%s:%i - No selection.\n", _FL);
return Status;
}
bool LTextView3::Paste()
{
LClipBoard Clip(this);
LAutoWString Mem;
char16 *t = Clip.TextW();
if (!t) // ala Win9x
{
char *s = Clip.Text();
if (s)
{
Mem.Reset(Utf8ToWide(s));
t = Mem;
}
}
if (!t)
return false;
if (SelStart >= 0)
{
DeleteSelection();
}
// remove '\r's
char16 *s = t, *d = t;
for (; *s; s++)
{
if (*s != '\r')
{
*d++ = *s;
}
}
*d++ = 0;
// insert text
ssize_t Len = StrlenW(t);
Insert(Cursor, t, Len);
SetCaret(Cursor+Len, false, true); // Multiline
return true;
}
void LTextView3::ClearDirty(std::function OnStatus, bool Ask, const char *FileName)
{
if (Dirty)
{
int Answer = (Ask) ? LgiMsg(this,
LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"),
LLoadString(L_TEXTCTRL_SAVE, "Save"),
MB_YESNOCANCEL) : IDYES;
if (Answer == IDYES)
{
auto DoSave = [this, OnStatus, FileName=LString(FileName)](bool ok)
{
Save(FileName);
if (OnStatus)
OnStatus(ok);
};
if (!FileName)
{
LFileSelect *Select = new LFileSelect;
Select->Parent(this);
Select->Save([&FileName, &DoSave](auto Select, auto ok)
{
if (ok)
FileName = Select->Name();
DoSave(ok);
delete Select;
});
}
else DoSave(true);
}
else if (Answer == IDCANCEL)
{
if (OnStatus)
OnStatus(false);
return;
}
}
if (OnStatus)
OnStatus(true);
}
bool LTextView3::Open(const char *Name, const char *CharSet)
{
bool Status = false;
LFile f;
if (f.Open(Name, O_READ|O_SHARE))
{
DeleteArray(Text);
int64 Bytes = f.GetSize();
if (Bytes < 0 || Bytes & 0xffff000000000000LL)
{
LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes);
return false;
}
SetCaret(0, false);
Line.DeleteObjects();
char *c8 = new char[Bytes + 4];
if (c8)
{
if (f.Read(c8, (int)Bytes) == Bytes)
{
char *DataStart = c8;
c8[Bytes] = 0;
c8[Bytes+1] = 0;
c8[Bytes+2] = 0;
c8[Bytes+3] = 0;
if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe)
{
// utf-16
if (!CharSet)
{
CharSet = "utf-16";
DataStart += 2;
}
}
// Convert to unicode first....
if (Bytes == 0)
{
Text = new char16[1];
if (Text) Text[0] = 0;
}
else
{
Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset);
}
if (Text)
{
// Remove LF's
char16 *In = Text, *Out = Text;
CrLf = false;
Size = 0;
while (*In)
{
if (*In >= ' ' ||
*In == '\t' ||
*In == '\n')
{
*Out++ = *In;
Size++;
}
else if (*In == '\r')
{
CrLf = true;
}
In++;
}
Size = (int) (Out - Text);
*Out = 0;
Alloc = Size + 1;
Dirty = false;
if (Text && Text[0] == 0xfeff) // unicode byte order mark
{
memmove(Text, Text+1, Size * sizeof(*Text));
Size--;
}
PourText(0, Size);
PourStyle(0, Size);
UpdateScrollBars(true);
Status = true;
}
}
DeleteArray(c8);
}
else
{
Alloc = Size = 0;
}
Invalidate();
}
return Status;
}
template
bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf)
{
if (!in)
return false;
if (CrLf)
{
int BufLen = 1 << 20;
LAutoPtr Buf(new T[BufLen]);
T *b = Buf;
T *e = Buf + BufLen;
T *c = in;
T *end = c + len;
while (c < end)
{
if (b > e - 16)
{
auto Bytes = (b - Buf) * sizeof(T);
if (out.Write(Buf, Bytes) != Bytes)
return false;
b = Buf;
}
if (*c == '\n')
{
*b++ = '\r';
*b++ = '\n';
}
else
{
*b++ = *c;
}
c++;
}
auto Bytes = (b - Buf) * sizeof(T);
if (out.Write(Buf, Bytes) != Bytes)
return false;
}
else
{
auto Bytes = len * sizeof(T);
if (out.Write(in, Bytes) != Bytes)
return false;
}
return true;
}
bool LTextView3::Save(const char *Name, const char *CharSet)
{
LFile f;
LString TmpName;
bool Status = false;
d->LastError.Empty();
if (f.Open(Name, O_WRITE))
{
if (f.SetSize(0) != 0)
{
// Can't resize file, fall back to renaming it and
// writing a new file...
f.Close();
TmpName = Name;
TmpName += ".tmp";
if (!FileDev->Move(Name, TmpName))
{
LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name);
return false;
}
if (!f.Open(Name, O_WRITE))
{
LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name);
return false;
}
}
if (Text)
{
auto InSize = Size * sizeof(char16);
if (CharSet && !Stricmp(CharSet, "utf-16"))
{
if (sizeof(*Text) == 2)
{
// No conversion needed...
Status = WriteToStream(f, Text, Size, CrLf);
}
else
{
// 32->16 convert
LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize));
if (c16)
Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf);
}
}
else if (CharSet && !Stricmp(CharSet, "utf-32"))
{
if (sizeof(*Text) == 4)
{
// No conversion needed...
Status = WriteToStream(f, Text, Size, CrLf);
}
else
{
// 16->32 convert
LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize));
if (c32)
Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf);
}
}
else
{
LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize));
if (c8)
Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf);
}
if (Status)
Dirty = false;
}
}
else
{
int Err = f.GetError();
LString sErr = LErrorCodeToString(Err);
d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get());
}
if (TmpName)
FileDev->Delete(TmpName);
return Status;
}
const char *LTextView3::GetLastError()
{
return d->LastError;
}
void LTextView3::UpdateScrollBars(bool Reset)
{
if (!VScroll)
return;
LRect Before = GetClient();
int DisplayLines = Y() / LineY;
ssize_t Lines = std::max(PartialPourLines, Line.Length());
VScroll->SetRange(Lines);
if (VScroll)
{
VScroll->SetPage(DisplayLines);
ssize_t Max = Lines - DisplayLines + 1;
bool Inval = false;
if (VScroll->Value() > Max)
{
VScroll->Value(Max);
Inval = true;
}
if (Reset)
{
VScroll->Value(0);
SelStart = SelEnd = -1;
}
else if (d->VScrollCache >= 0)
{
VScroll->Value(d->VScrollCache);
d->VScrollCache = -1;
SelStart = SelEnd = -1;
}
LRect After = GetClient();
if (Before != After && GetWrapType())
{
d->SetDirty(0, Size);
Inval = true;
}
if (Inval)
{
Invalidate();
}
}
}
void LTextView3::DoCase(std::function Callback, bool Upper)
{
if (Text)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (Min < Max)
{
if (UndoOn)
{
LAutoPtr Obj(new LTextView3Undo(this));
LTextView3Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(Min, Max - Min, UndoChange);
if (Obj)
UndoQue += Obj.Release();
}
for (ssize_t i=Min; i= 'a' && Text[i] <= 'z')
Text[i] = Text[i] - 'a' + 'A';
}
else
{
if (Text[i] >= 'A' && Text[i] <= 'Z')
Text[i] = Text[i] - 'A' + 'a';
}
}
Dirty = true;
d->SetDirty(Min, 0);
Invalidate();
SendNotify(LNotifyDocChanged);
}
}
if (Callback)
Callback(Text != NULL);
}
ssize_t LTextView3::GetLine()
{
ssize_t Idx = 0;
GetTextLine(Cursor, &Idx);
return Idx + 1;
}
void LTextView3::SetLine(int64_t i, bool select)
{
LTextLine *l = Line.ItemAt(i - 1);
if (l)
{
d->CenterCursor = true;
SetCaret(l->Start, select);
d->CenterCursor = false;
}
}
void LTextView3::DoGoto(std::function Callback)
{
LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text");
Dlg->DoModal([this, Dlg, Callback](auto d, auto code)
{
auto ok = code == IDOK && Dlg->GetStr();
if (ok)
SetLine(Dlg->GetStr().Int());
if (Callback)
Callback(ok);
delete Dlg;
});
}
LDocFindReplaceParams *LTextView3::CreateFindReplaceParams()
{
return new LDocFindReplaceParams3;
}
void LTextView3::SetFindReplaceParams(LDocFindReplaceParams *Params)
{
if (Params)
{
if (d->OwnFindReplaceParams)
{
DeleteObj(d->FindReplaceParams);
}
d->OwnFindReplaceParams = false;
d->FindReplaceParams = (LDocFindReplaceParams3*) Params;
}
}
void LTextView3::DoFindNext(std::function OnStatus)
{
bool Status = false;
if (InThread())
{
if (d->FindReplaceParams->Lock(_FL))
{
if (d->FindReplaceParams->LastFind)
Status = OnFind(d->FindReplaceParams->LastFind,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
d->FindReplaceParams->Unlock();
}
}
else if (IsAttached())
{
Status = PostEvent(M_TEXTVIEW_FIND);
}
if (OnStatus)
OnStatus(Status);
}
void LTextView3::DoFind(std::function Callback)
{
LString u;
if (HasSelection())
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
u = LString(Text + Min, Max - Min);
}
else
{
u = d->FindReplaceParams->LastFind.Get();
}
auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto Action)
{
if (Params &&
Params->Lock(_FL))
{
Params->MatchWord = Dlg->MatchWord;
Params->MatchCase = Dlg->MatchCase;
Params->SelectionOnly = Dlg->SelectionOnly;
Params->SearchUpwards = Dlg->SearchUpwards;
Params->LastFind.Reset(Utf8ToWide(Dlg->Find));
Params->Unlock();
}
DoFindNext([this, Callback](bool ok)
{
Focus(true);
if (Callback)
Callback(ok);
});
},
u);
Dlg->DoModal(NULL);
}
void LTextView3::DoReplace(std::function Callback)
{
bool SingleLineSelection = false;
SingleLineSelection = HasSelection();
if (SingleLineSelection)
{
LRange Sel = GetSelectionRange();
for (ssize_t i = Sel.Start; i < Sel.End(); i++)
{
if (Text[i] == '\n')
{
SingleLineSelection = false;
break;
}
}
}
LAutoString LastFind8(SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind));
LAutoString LastReplace8(WideToUtf8(d->FindReplaceParams->LastReplace));
- auto Dlg = new LReplaceDlg(this, [this, LastFind8, LastReplace8](LFindReplaceCommon *Dlg, int Action)
+ auto Dlg = new LReplaceDlg(this, [this, LastFind8=LString(LastFind8), LastReplace8=LString(LastReplace8)](auto Dlg, auto Action)
{
LReplaceDlg *Replace = dynamic_cast(Dlg);
LAssert(Replace != NULL);
if (Action == IDCANCEL)
return;
if (d->FindReplaceParams->Lock(_FL))
{
d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find));
d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace));
d->FindReplaceParams->MatchWord = Replace->MatchWord;
d->FindReplaceParams->MatchCase = Replace->MatchCase;
d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly;
switch (Action)
{
case IDC_FR_FIND:
{
OnFind( d->FindReplaceParams->LastFind,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
break;
}
case IDOK:
case IDC_FR_REPLACE:
{
OnReplace( d->FindReplaceParams->LastFind,
d->FindReplaceParams->LastReplace,
Action == IDOK,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
break;
}
}
d->FindReplaceParams->Unlock();
}
},
LastFind8,
LastReplace8);
Dlg->MatchWord = d->FindReplaceParams->MatchWord;
Dlg->MatchCase = d->FindReplaceParams->MatchCase;
Dlg->SelectionOnly = HasSelection();
Dlg->DoModal(NULL);
}
void LTextView3::SelectWord(size_t From)
{
for (SelStart = From; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = From; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Invalidate();
}
typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n);
ptrdiff_t LTextView3::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
if (!ValidStrW(Find))
return -1;
ssize_t FindLen = StrlenW(Find);
// Setup range to search
ssize_t Begin, End;
if (SelectionOnly && HasSelection())
{
Begin = MIN(SelStart, SelEnd);
End = MAX(SelStart, SelEnd);
}
else
{
Begin = 0;
End = Size;
}
// Look through text...
ssize_t i;
bool Wrap = false;
if (Cursor > End - FindLen)
{
Wrap = true;
if (SearchUpwards)
i = End - FindLen;
else
i = Begin;
}
else
{
i = Cursor;
}
if (i < Begin) i = Begin;
if (i > End) i = End;
StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW;
char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]);
for (; SearchUpwards ? i >= Begin : i <= End; i += SearchUpwards ? -1 : 1)
{
if
(
(MatchCase ? Text[i] : toupper(Text[i]))
==
FindCh
)
{
char16 *Possible = Text + i;
if (CmpFn(Possible, Find, FindLen) == 0)
{
if (MatchWord)
{
// Check boundaries
if (Possible > Text) // Check off the start
{
if (!IsWordBoundry(Possible[-1]))
continue;
}
if (i + FindLen < Size) // Check off the end
{
if (!IsWordBoundry(Possible[FindLen]))
continue;
}
}
/* What was this even supposed to do?
LRange r(Possible - Text, FindLen);
if (!r.Overlap(Cursor))
*/
return i;
}
}
if (!Wrap && (i + 1 > End - FindLen))
{
Wrap = true;
i = Begin;
End = Cursor;
}
}
return -1;
}
bool LTextView3::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
// Not sure what this is doing???
if (HasSelection() &&
SelEnd < SelStart)
{
Cursor = SelStart;
}
#if FEATURE_HILIGHT_ALL_MATCHES
// Clear existing styles for matches
for (StyleIter s = Style.begin(); s != Style.end(); )
{
if (s->Owner == STYLE_FIND_MATCHES)
Style.Delete(s);
else
s++;
}
ssize_t FindLen = StrlenW(Find);
ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc;
if (FirstLoc >= 0)
{
SetCaret(FirstLoc, false);
SetCaret(FirstLoc + FindLen, true);
}
ssize_t Old = Cursor;
if (!SearchUpwards)
Cursor += FindLen;
while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc)
{
LAutoPtr s(new LStyle(STYLE_FIND_MATCHES));
s->Start = Loc;
s->Len = FindLen;
s->Fore = LColour(L_FOCUS_SEL_FORE);
s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE));
InsertStyle(s);
Cursor = Loc + FindLen;
}
Cursor = Old;
ScrollToOffset(Cursor);
Invalidate();
#else
ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (Loc >= 0)
{
SetCaret(Loc, false);
SetCaret(Loc + StrlenW(Find), true);
return true;
}
#endif
return false;
}
bool LTextView3::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
if (ValidStrW(Find))
{
// int Max = -1;
ssize_t FindLen = StrlenW(Find);
ssize_t ReplaceLen = StrlenW(Replace);
// size_t OldCursor = Cursor;
ptrdiff_t First = -1;
while (true)
{
ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (First < 0)
{
First = Loc;
}
else if (Loc == First)
{
break;
}
if (Loc >= 0)
{
ssize_t OldSelStart = SelStart;
ssize_t OldSelEnd = SelEnd;
Delete(Loc, FindLen);
Insert(Loc, Replace, ReplaceLen);
SelStart = OldSelStart;
SelEnd = OldSelEnd - FindLen + ReplaceLen;
Cursor = Loc + ReplaceLen;
}
if (!All)
{
return Loc >= 0;
}
if (Loc < 0) break;
}
}
return false;
}
ssize_t LTextView3::SeekLine(ssize_t Offset, GTextViewSeek Where)
{
THREAD_CHECK();
switch (Where)
{
case PrevLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset--;
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case NextLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
Offset++;
break;
}
case StartLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case EndLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
break;
}
default:
{
LAssert(false);
break;
}
}
return Offset;
}
bool LTextView3::OnMultiLineTab(bool In)
{
bool Status = false;
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd), i;
Min = SeekLine(Min, StartLine);
int Ls = 0;
LArray p;
for (i=Min; i=0; i--)
{
if (In)
{
// <-
ssize_t n = Indexes[i], Space = 0;
for (; Space
ssize_t Len = Indexes[i];
for (; Text[Len] != '\n' && Len Indexes[i])
{
if (HardTabs)
{
char16 Tab[] = {'\t', 0};
Insert(Indexes[i], Tab, 1);
Max++;
}
else
{
char16 *Sp = new char16[IndentSize];
if (Sp)
{
for (int n=0; nChanges.Length())
{
UndoQue += UndoCur;
UndoCur = NULL;
}
else
{
DeleteObj(UndoCur);
}
SelStart = Min;
SelEnd = Cursor = Max;
PourEnabled = true;
PourText(Min, Max - Min);
PourStyle(Min, Max - Min);
d->SetDirty(Min, Max-Min);
Invalidate();
Status = true;
return Status;
}
void LTextView3::OnSetHidden(int Hidden)
{
}
void LTextView3::OnPosChange()
{
static bool Processing = false;
if (!Processing)
{
Processing = true;
LLayout::OnPosChange();
LRect c = GetClient();
bool ScrollYNeeded = c.Y() < (std::max(PartialPourLines, Line.Length()) * LineY);
bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL);
if (ScrollChange)
{
#if 0
auto Client = GetClient();
LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n",
_FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour);
#endif
SetScrollBars(false, ScrollYNeeded);
}
UpdateScrollBars();
if (GetWrapType() && d->PourX != X())
{
d->PourX = X();
d->SetDirty(0, Size);
}
Processing = false;
}
}
int LTextView3::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState)
{
Formats.Supports("text/uri-list");
Formats.Supports("text/html");
Formats.Supports("UniformResourceLocatorW");
return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE;
}
int LTextView3::OnDrop(LArray &Data, LPoint Pt, int KeyState)
{
int Status = DROPEFFECT_NONE;
for (unsigned i=0; iIsBinary())
{
OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length);
OsChar *s = (OsChar*) Data->Value.Binary.Data;
int len = 0;
while (s < e && s[len])
{
len++;
}
LAutoWString w
(
(char16*)LNewConvertCp
(
LGI_WideCharset,
s,
(
sizeof(OsChar) == 1
?
"utf-8"
:
LGI_WideCharset
),
len * sizeof(*s)
)
);
Insert(Cursor, w, len);
Invalidate();
return DROPEFFECT_COPY;
}
}
else if (dd.IsFileDrop())
{
// We don't directly handle file drops... pass up to the parent
bool FoundTarget = false;
for (LViewI *p = GetParent(); p; p = p->GetParent())
{
LDragDropTarget *t = p->DropTarget();
if (t)
{
Status = t->OnDrop(Data, Pt, KeyState);
if (Status != DROPEFFECT_NONE)
{
FoundTarget = true;
break;
}
}
}
if (!FoundTarget)
{
auto Wnd = GetWindow();
if (Wnd)
{
LDropFiles files(dd);
Wnd->OnReceiveFiles(files);
}
}
}
}
return Status;
}
void LTextView3::OnCreate()
{
SetWindow(this);
DropTarget(true);
#ifndef WINDOWS
if (Ctrls.Length() == 0)
SetPulse(PULSE_TIMEOUT);
Ctrls.Add(this);
#else
SetPulse(PULSE_TIMEOUT);
#endif
}
void LTextView3::OnEscape(LKey &K)
{
}
bool LTextView3::OnMouseWheel(double l)
{
if (VScroll)
{
int64 NewPos = VScroll->Value() + (int)l;
NewPos = limit(NewPos, 0, (ssize_t)GetLines());
VScroll->Value(NewPos);
Invalidate();
}
return true;
}
void LTextView3::OnFocus(bool f)
{
Invalidate();
}
ssize_t LTextView3::HitText(int x, int y, bool Nearest)
{
if (!Text)
return 0;
bool Down = y >= 0;
int Y = (VScroll) ? (int)VScroll->Value() : 0;
auto It = Line.begin(Y);
if (It != Line.end())
y += (*It)->r.y1;
while (It != Line.end())
{
auto l = *It;
if (l->r.Overlap(x, y))
{
// Over a line
int At = x - l->r.x1;
ssize_t Char = 0;
LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0);
Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate);
return l->Start + Char;
}
else if (y >= l->r.y1 && y <= l->r.y2)
{
// Click horizontally before of after line
if (x < l->r.x1)
{
return l->Start;
}
else if (x > l->r.x2)
{
return l->Start + l->Len;
}
}
if (Down)
It++;
else
It--;
Y++;
}
// outside text area
if (Down)
{
It = Line.rbegin();
if (It != Line.end())
{
if (y > (*It)->r.y2)
{
// end of document
return Size;
}
}
}
return 0;
}
void LTextView3::Undo()
{
int Old = UndoQue.GetPos();
UndoQue.Undo();
if (Old && !UndoQue.GetPos())
{
Dirty = false;
SendNotify(LNotifyDocChanged);
}
}
void LTextView3::Redo()
{
UndoQue.Redo();
}
void LTextView3::DoContextMenu(LMouse &m)
{
LSubMenu RClick;
LAutoString ClipText;
{
LClipBoard Clip(this);
ClipText.Reset(NewStr(Clip.Text()));
}
LStyle *s = HitStyle(HitText(m.x, m.y, true));
if (s)
{
if (OnStyleMenu(s, &RClick))
{
RClick.AppendSeparator();
}
}
RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0);
RClick.AppendSeparator();
RClick.AppendItem("Copy All", IDM_COPY_ALL, true);
RClick.AppendItem("Select All", IDM_SELECT_ALL, true);
RClick.AppendSeparator();
RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo());
RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo());
RClick.AppendSeparator();
auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true);
if (i) i->Checked(GetFixedWidthFont());
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true);
if (i) i->Checked(AutoIndent);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true);
if (i) i->Checked(ShowWhiteSpace);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true);
if (i) i->Checked(HardTabs);
RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true);
RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true);
if (Environment)
Environment->AppendItems(&RClick, NULL);
int Id = 0;
m.ToScreen();
switch (Id = RClick.Float(this, m))
{
case IDM_FIXED:
{
SetFixedWidthFont(!GetFixedWidthFont());
SendNotify(LNotifyFixedWidthChanged);
break;
}
case IDM_CUT:
{
Cut();
break;
}
case IDM_COPY:
{
Copy();
break;
}
case IDM_PASTE:
{
Paste();
break;
}
case IDM_COPY_ALL:
{
SelectAll();
Copy();
break;
}
case IDM_SELECT_ALL:
{
SelectAll();
break;
}
case IDM_UNDO:
{
Undo();
break;
}
case IDM_REDO:
{
Redo();
break;
}
case IDM_AUTO_INDENT:
{
AutoIndent = !AutoIndent;
break;
}
case IDM_SHOW_WHITE:
{
ShowWhiteSpace = !ShowWhiteSpace;
Invalidate();
break;
}
case IDM_HARD_TABS:
{
HardTabs = !HardTabs;
break;
}
case IDM_INDENT_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", IndentSize);
LInput *i = new LInput(this, s, "Indent Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
if (code)
IndentSize = atoi(i->GetStr());
delete i;
});
break;
}
case IDM_TAB_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", TabSize);
LInput *i = new LInput(this, s, "Tab Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
SetTabSize(atoi(i->GetStr()));
delete i;
});
break;
}
default:
{
if (s)
{
OnStyleMenuClick(s, Id);
}
if (Environment)
{
Environment->OnMenu(this, Id, 0);
}
break;
}
}
}
bool LTextView3::OnStyleClick(LStyle *style, LMouse *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
if
(
(!m)
||
(m->Left() && m->Down() && m->Double())
)
{
LString s(Text + style->Start, style->Len);
if (s)
OnUrl(s);
return true;
}
break;
}
default:
break;
}
return false;
}
bool LTextView3::OnStyleMenu(LStyle *style, LSubMenu *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
if (LIsValidEmail(s))
m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true);
else
m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true);
m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true);
return true;
}
default:
break;
}
return false;
}
void LTextView3::OnStyleMenuClick(LStyle *style, int i)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
switch (i)
{
case IDM_NEW:
case IDM_OPEN:
{
if (s)
OnUrl(s);
break;
}
case IDM_COPY_URL:
{
if (s)
{
LClipBoard Clip(this);
Clip.Text(s);
}
break;
}
}
break;
}
default:
break;
}
}
void LTextView3::OnMouseClick(LMouse &m)
{
bool Processed = false;
m.x += ScrollX;
if (m.Down())
{
if (m.IsContextMenu())
{
DoContextMenu(m);
return;
}
else if (m.Left())
{
Focus(true);
ssize_t Hit = HitText(m.x, m.y, true);
if (Hit >= 0)
{
SetCaret(Hit, m.Shift());
LStyle *s = HitStyle(Hit);
if (s)
Processed = OnStyleClick(s, &m);
}
if (!Processed && m.Double())
{
d->WordSelectMode = Cursor;
SelectWord(Cursor);
}
else
{
d->WordSelectMode = -1;
}
}
}
if (!Processed)
{
Capture(m.Down());
}
}
int LTextView3::OnHitTest(int x, int y)
{
#ifdef WIN32
if (GetClient().Overlap(x, y))
{
return HTCLIENT;
}
#endif
return LView::OnHitTest(x, y);
}
void LTextView3::OnMouseMove(LMouse &m)
{
m.x += ScrollX;
ssize_t Hit = HitText(m.x, m.y, true);
if (IsCapturing())
{
if (d->WordSelectMode < 0)
{
SetCaret(Hit, m.Left());
}
else
{
ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode;
ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode;
for (SelStart = Min; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = Max; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Cursor = SelEnd;
Invalidate();
}
}
}
LCursor LTextView3::GetCursor(int x, int y)
{
LRect c = GetClient();
c.Offset(-c.x1, -c.y1);
LStyle *s = NULL;
if (c.Overlap(x, y))
{
ssize_t Hit = HitText(x, y, true);
s = HitStyle(Hit);
}
return s ? s->Cursor : LCUR_Ibeam;
}
int LTextView3::GetColumn()
{
int x = 0;
LTextLine *l = GetTextLine(Cursor);
if (l)
{
for (ssize_t i=l->Start; i> 1);
m.Target = this;
DoContextMenu(m);
}
else if (k.IsChar)
{
switch (k.vkey)
{
default:
{
// process single char input
if
(
!GetReadOnly()
&&
(
(k.c16 >= ' ' || k.vkey == LK_TAB)
&&
k.c16 != 127
)
)
{
if (k.Down())
{
// letter/number etc
if (SelStart >= 0)
{
bool MultiLine = false;
if (k.vkey == LK_TAB)
{
size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd);
for (size_t i=Min; iLen : 0;
if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize))
{
int x = GetColumn();
int Add = IndentSize - (x % IndentSize);
if (HardTabs && ((x + Add) % TabSize) == 0)
{
int Rx = x;
size_t Remove;
for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--);
ssize_t Chars = (ssize_t)Cursor - Remove;
Delete(Remove, Chars);
Insert(Remove, &k.c16, 1);
Cursor = Remove + 1;
Invalidate();
}
else
{
char16 *Sp = new char16[Add];
if (Sp)
{
for (int n=0; nLen : 0;
SetCaret(Cursor + Add, false, Len != NewLen - 1);
}
DeleteArray(Sp);
}
}
}
else
{
char16 In = k.GetChar();
if (In == '\t' &&
k.Shift() &&
Cursor > 0)
{
l = GetTextLine(Cursor);
if (Cursor > l->Start)
{
if (Text[Cursor-1] == '\t')
{
Delete(Cursor - 1, 1);
SetCaret(Cursor, false, false);
}
else if (Text[Cursor-1] == ' ')
{
ssize_t Start = (ssize_t)Cursor - 1;
while (Start >= l->Start && strchr(" \t", Text[Start-1]))
Start--;
int Depth = SpaceDepth(Text + Start, Text + Cursor);
int NewDepth = Depth - (Depth % IndentSize);
if (NewDepth == Depth && NewDepth > 0)
NewDepth -= IndentSize;
int Use = 0;
while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth)
Use++;
Delete(Start + Use, Cursor - Start - Use);
SetCaret(Start + Use, false, false);
}
}
}
else if (In && Insert(Cursor, &In, 1))
{
l = GetTextLine(Cursor);
size_t NewLen = (l) ? l->Len : 0;
SetCaret(Cursor + 1, false, Len != NewLen - 1);
}
}
}
return true;
}
break;
}
case LK_RETURN:
#if defined MAC
case LK_KEYPADENTER:
#endif
{
if (GetReadOnly())
break;
if (k.Down() && k.IsChar)
{
OnEnter(k);
}
return true;
break;
}
case LK_BACKSPACE:
{
if (GetReadOnly())
break;
if (k.Ctrl())
{
// Ctrl+H
}
else if (k.Down())
{
if (SelStart >= 0)
{
// delete selection
DeleteSelection();
}
else
{
char Del = Cursor > 0 ? Text[Cursor-1] : 0;
if (Del == ' ' && (!HardTabs || IndentSize != TabSize))
{
// Delete soft tab
int x = GetColumn();
int Max = x % IndentSize;
if (Max == 0) Max = IndentSize;
ssize_t i;
for (i=Cursor-1; i>=0; i--)
{
if (Max-- <= 0 || Text[i] != ' ')
{
i++;
break;
}
}
if (i < 0) i = 0;
if (i < Cursor - 1)
{
ssize_t Del = (ssize_t)Cursor - i;
Delete(i, Del);
// SetCursor(i, false, false);
// Invalidate();
break;
}
}
else if (Del == '\t' && HardTabs && IndentSize != TabSize)
{
int x = GetColumn();
Delete(--Cursor, 1);
for (int c=GetColumn(); c 0)
{
Delete(Cursor - 1, 1);
}
}
}
return true;
break;
}
}
}
else // not a char
{
switch (k.vkey)
{
case LK_TAB:
return true;
case LK_RETURN:
{
return !GetReadOnly();
}
case LK_BACKSPACE:
{
if (!GetReadOnly())
{
if (k.Alt())
{
if (k.Down())
{
if (k.Ctrl())
{
Redo();
}
else
{
Undo();
}
}
}
else if (k.Ctrl())
{
if (k.Down())
{
ssize_t Start = Cursor;
while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
Delete(Cursor, Start - Cursor);
Invalidate();
}
}
return true;
}
break;
}
case LK_F3:
{
if (k.Down())
{
DoFindNext(NULL);
}
return true;
break;
}
case LK_LEFT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MIN(SelStart, SelEnd), false);
}
else if (Cursor > 0)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_StartOfLine;
}
else
if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
bool StartWhiteSpace = IsWhiteSpace(Text[n]);
bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]);
if (!StartWhiteSpace ||
Text[n] == '\n')
{
n--;
}
// Skip ws
for (; n > 0 && strchr(" \t", Text[n]); n--)
;
if (Text[n] == '\n')
{
n--;
}
else if (!StartWhiteSpace || !LeftWhiteSpace)
{
if (IsDelimiter(Text[n]))
{
for (; n > 0 && IsDelimiter(Text[n]); n--);
}
else
{
for (; n > 0; n--)
{
//IsWordBoundry(Text[n])
if (IsWhiteSpace(Text[n]) ||
IsDelimiter(Text[n]))
{
break;
}
}
}
}
if (n > 0) n++;
}
else
{
// single char
n--;
}
SetCaret(n, k.Shift());
}
}
return true;
break;
}
case LK_RIGHT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MAX(SelStart, SelEnd), false);
}
else if (Cursor < Size)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_EndOfLine;
}
else if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
if (IsWhiteSpace(Text[n]))
{
for (; nStart, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len);
ssize_t CharX = PrevLine.CharAt(ScreenX);
SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_DOWN:
{
if (k.Alt())
return false;
if (k.Down())
{
#ifdef MAC
if (k.Ctrl())
goto LTextView3_PageDown;
#endif
auto It = GetTextLineIt(Cursor);
if (It != Line.end())
{
auto l = *It;
It++;
if (It != Line.end())
{
LTextLine *Next = *It;
LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString NextLine(Font, Text + Next->Start, Next->Len);
ssize_t CharX = NextLine.CharAt(ScreenX);
SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_END:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(Size, k.Shift());
}
else
{
#ifdef MAC
Jump_EndOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
SetCaret(l->Start + l->Len, k.Shift());
}
}
}
return true;
break;
}
case LK_HOME:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(0, k.Shift());
}
else
{
#ifdef MAC
Jump_StartOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
char16 *Line = Text + l->Start;
char16 *s;
char16 SpTab[] = {' ', '\t', 0};
for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++);
ssize_t Whitespace = SubtractPtr(s, Line);
if (l->Start + Whitespace == Cursor)
{
SetCaret(l->Start, k.Shift());
}
else
{
SetCaret(l->Start + Whitespace, k.Shift());
}
}
}
}
return true;
break;
}
case LK_PAGEUP:
{
#ifdef MAC
LTextView3_PageUp:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_PAGEDOWN:
{
#ifdef MAC
LTextView3_PageDown:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_INSERT:
{
if (k.Down())
{
if (k.Ctrl())
{
Copy();
}
else if (k.Shift())
{
if (!GetReadOnly())
{
Paste();
}
}
}
return true;
break;
}
case LK_DELETE:
{
if (!GetReadOnly())
{
if (k.Down())
{
if (SelStart >= 0)
{
if (k.Shift())
{
Cut();
}
else
{
DeleteSelection();
}
}
else if (Cursor < Size &&
Delete(Cursor, 1))
{
Invalidate();
}
}
return true;
}
break;
}
default:
{
if (k.c16 == 17) break;
if (k.CtrlCmd() &&
!k.Alt())
{
switch (k.GetChar())
{
case 0xbd: // Ctrl+'-'
{
if (k.Down() &&
Font->PointSize() > 1)
{
Font->PointSize(Font->PointSize() - 1);
OnFontChange();
Invalidate();
}
break;
}
case 0xbb: // Ctrl+'+'
{
if (k.Down() &&
Font->PointSize() < 100)
{
Font->PointSize(Font->PointSize() + 1);
OnFontChange();
Invalidate();
}
break;
}
case 'a':
case 'A':
{
if (k.Down())
{
// select all
SelStart = 0;
SelEnd = Size;
Invalidate();
}
return true;
break;
}
case 'y':
case 'Y':
{
if (!GetReadOnly())
{
if (k.Down())
{
Redo();
}
return true;
}
break;
}
case 'z':
case 'Z':
{
if (!GetReadOnly())
{
if (k.Down())
{
if (k.Shift())
{
Redo();
}
else
{
Undo();
}
}
return true;
}
break;
}
case 'x':
case 'X':
{
if (!GetReadOnly())
{
if (k.Down())
{
Cut();
}
return true;
}
break;
}
case 'c':
case 'C':
{
if (k.Shift())
return false;
if (k.Down())
Copy();
return true;
break;
}
case 'v':
case 'V':
{
if (!k.Shift() &&
!GetReadOnly())
{
if (k.Down())
{
Paste();
}
return true;
}
break;
}
case 'f':
{
if (k.Down())
{
DoFind(NULL);
}
return true;
break;
}
case 'g':
case 'G':
{
if (k.Down())
{
DoGoto(NULL);
}
return true;
break;
}
case 'h':
case 'H':
{
if (k.Down())
{
DoReplace(NULL);
}
return true;
break;
}
case 'u':
case 'U':
{
if (!GetReadOnly())
{
if (k.Down())
{
DoCase(NULL, k.Shift());
}
return true;
}
break;
}
case LK_RETURN:
{
if (!GetReadOnly() && !k.Shift())
{
if (k.Down())
{
OnEnter(k);
}
return true;
}
break;
}
}
}
break;
}
}
}
return false;
}
void LTextView3::OnEnter(LKey &k)
{
// enter
if (SelStart >= 0)
{
DeleteSelection();
}
char16 InsertStr[256] = {'\n', 0};
LTextLine *CurLine = GetTextLine(Cursor);
if (CurLine && AutoIndent)
{
int WsLen = 0;
for (; WsLen < CurLine->Len &&
WsLen < (Cursor - CurLine->Start) &&
strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++);
if (WsLen > 0)
{
memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16));
InsertStr[WsLen+1] = 0;
}
}
if (Insert(Cursor, InsertStr, StrlenW(InsertStr)))
{
SetCaret(Cursor + StrlenW(InsertStr), false, true);
}
}
int LTextView3::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin)
{
int w = x;
int Size = f->TabSize();
for (char16 *c = s; SubtractPtr(c, s) < Len; )
{
if (*c == 9)
{
w = ((((w-Origin) + Size) / Size) * Size) + Origin;
c++;
}
else
{
char16 *e;
for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++);
LDisplayString ds(f, c, SubtractPtr(e, c));
w += ds.X();
c = e;
}
}
return w - x;
}
int LTextView3::ScrollYLine()
{
return (VScroll) ? (int)VScroll->Value() : 0;
}
int LTextView3::ScrollYPixel()
{
return ScrollYLine() * LineY;
}
LRect LTextView3::DocToScreen(LRect r)
{
r.Offset(0, d->rPadding.y1 - ScrollYPixel());
return r;
}
void LTextView3::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour)
{
pDC->Colour(colour);
pDC->Rectangle(&r);
}
void LTextView3::OnPaint(LSurface *pDC)
{
#if LGI_EXCEPTIONS
try
{
#endif
#if DOUBLE_BUFFER_PAINT
LDoubleBuffer MemBuf(pDC);
#endif
#if PROFILE_PAINT
char s[256];
sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size);
LProfile Prof(s);
#endif
if (d->LayoutDirty)
{
#if PROFILE_PAINT
Prof.Add("PourText");
#endif
PourText(d->DirtyStart, d->DirtyLen);
#if PROFILE_PAINT
Prof.Add("PourStyle");
#endif
PourStyle(d->DirtyStart, d->DirtyLen);
d->LayoutDirty = false;
}
#if PROFILE_PAINT
Prof.Add("Setup");
#endif
LRect r = GetClient();
r.x2 += ScrollX;
int Ox, Oy;
pDC->GetOrigin(Ox, Oy);
pDC->SetOrigin(Ox+ScrollX, Oy);
#if 0
// Coverage testing...
pDC->Colour(Rgb24(255, 0, 255), 24);
pDC->Rectangle();
#endif
LSurface *pOut = pDC;
bool DrawSel = false;
bool HasFocus = Focus();
// printf("%s:%i - HasFocus = %i\n", _FL, HasFocus);
LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE));
LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK));
LCss::ColorDef ForeDef, BkDef;
if (GetCss())
{
ForeDef = GetCss()->Color();
BkDef = GetCss()->BackgroundColor();
}
LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT));
LColour Back
(
/*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb
?
LColour(BkDef.Rgb32, 32)
:
Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED)
);
// LColour Whitespace = Fore.Mix(Back, 0.85f);
if (!Enabled())
{
Fore = LColour(L_LOW);
Back = LColour(L_MED);
}
if (Text &&
Font)
{
ssize_t SelMin = MIN(SelStart, SelEnd);
ssize_t SelMax = MAX(SelStart, SelEnd);
// font properties
Font->Colour(Fore, Back);
// Font->WhitespaceColour(Whitespace);
Font->Transparent(false);
// draw margins
pDC->Colour(PAINT_BORDER);
// top margin
pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1);
// left margin
{
LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2);
OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER);
}
// draw lines of text
int k = ScrollYLine();
auto It = Line.begin(k);
LTextLine *l = NULL;
int Dy = 0;
if (It != Line.end())
Dy = -(*It)->r.y1;
ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes
if (It != Line.end() &&
(l = *It) &&
SelStart >= 0 &&
SelStart < l->Start &&
SelEnd > l->Start)
{
// start of visible area is in selection
// init to selection colour
DrawSel = true;
Font->Colour(SelectedText, SelectedBack);
NextSelection = SelMax;
}
StyleIter Si = Style.begin();
LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0);
DocOffset = (l) ? l->r.y1 : 0;
#if PROFILE_PAINT
Prof.Add("foreach Line loop");
#endif
// loop through all visible lines
int y = d->rPadding.y1;
while ((l = *It) &&
l->r.y1+Dy < r.Y())
{
LRect Tr = l->r;
Tr.Offset(0, y - Tr.y1);
//LRect OldTr = Tr;
// deal with selection change on beginning of line
if (NextSelection == l->Start)
{
// selection change
DrawSel = !DrawSel;
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
// How many chars on this line have we
// processed so far:
ssize_t Done = 0;
bool LineHasSelection = NextSelection >= l->Start &&
NextSelection < l->Start + l->Len;
// Fractional pixels we have moved so far:
int MarginF = d->rPadding.x1 << LDisplayString::FShift;
int FX = MarginF;
int FY = Tr.y1 << LDisplayString::FShift;
// loop through all sections of similar text on a line
while (Done < l->Len)
{
// decide how big this block is
int RtlTrailingSpace = 0;
ssize_t Cur = l->Start + Done;
ssize_t Block = l->Len - Done;
// check for style change
if (NextStyle &&
(ssize_t)NextStyle->End() <= l->Start)
NextStyle = GetNextStyle(Si);
if (NextStyle)
{
// start
if (l->Overlap(NextStyle->Start) &&
NextStyle->Start > Cur &&
NextStyle->Start - Cur < Block)
{
Block = NextStyle->Start - Cur;
}
// end
ssize_t StyleEnd = NextStyle->Start + NextStyle->Len;
if (l->Overlap(StyleEnd) &&
StyleEnd > Cur &&
StyleEnd - Cur < Block)
{
Block = StyleEnd - Cur;
}
}
// check for next selection change
// this may truncate the style
if (NextSelection > Cur &&
NextSelection - Cur < Block)
{
Block = NextSelection - Cur;
}
LAssert(Block != 0); // sanity check
if (NextStyle && // There is a style
(Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block
Cur >= NextStyle->Start && // && we're inside the styled area
Cur < NextStyle->Start+NextStyle->Len)
{
LFont *Sf = NextStyle->Font ? NextStyle->Font : Font;
if (Sf)
{
// draw styled text
if (NextStyle->Fore.IsValid())
Sf->Fore(NextStyle->Fore);
if (NextStyle->Back.IsValid())
Sf->Back(NextStyle->Back);
else if (l->Back.IsValid())
Sf->Back(l->Back);
else
Sf->Back(Back);
Sf->Transparent(false);
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Sf,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
if (NextStyle->Decor == LCss::TextDecorSquiggle)
{
pOut->Colour(NextStyle->DecorColour);
int x = FX >> LDisplayString::FShift;
int End = x + Ds.X();
while (x < End)
{
pOut->Set(x, Tr.y2-(x%2));
x++;
}
}
FX += Ds.FX();
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Sf->Colour(fore, back);
}
else LAssert(0);
}
else
{
// draw a block of normal text
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Font,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
FX += Ds.FX();
}
if (NextStyle &&
Cur+Block >= NextStyle->Start+NextStyle->Len)
{
// end of this styled block
NextStyle = GetNextStyle(Si);
}
if (NextSelection == Cur+Block)
{
// selection change
DrawSel = !DrawSel;
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
Done += Block + RtlTrailingSpace;
} // end block loop
Tr.x1 = FX >> LDisplayString::FShift;
// eol processing
ssize_t EndOfLine = l->Start+l->Len;
if (EndOfLine >= SelMin && EndOfLine < SelMax)
{
// draw the '\n' at the end of the line as selected
// LColour bk = Font->Back();
pOut->Colour(Font->Back());
pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2);
Tr.x2 += 7;
}
else Tr.x2 = Tr.x1;
// draw any space after text
pOut->Colour(PAINT_AFTER_LINE);
pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2);
// cursor?
if (HasFocus)
{
// draw the cursor if on this line
if (Cursor >= l->Start && Cursor <= l->Start+l->Len)
{
CursorPos.ZOff(1, LineY-1);
ssize_t At = Cursor-l->Start;
LDisplayString Ds(Font, MapText(Text+l->Start, At), At);
Ds.ShowVisibleTab(ShowWhiteSpace);
int CursorX = Ds.X();
CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1);
if (CanScrollX)
{
// Cursor on screen check
LRect Scr = GetClient();
Scr.Offset(ScrollX, 0);
LRect Cur = CursorPos;
if (Cur.x2 > Scr.x2 - 5) // right edge check
{
ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40;
Invalidate();
}
else if (Cur.x1 < Scr.x1 && ScrollX > 0)
{
ScrollX = MAX(0, Cur.x1 - 40);
Invalidate();
}
}
if (Blink)
{
LRect c = CursorPos;
pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192));
pOut->Rectangle(&c);
}
#if WINNATIVE
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
COMPOSITIONFORM Cf;
Cf.dwStyle = CFS_POINT;
Cf.ptCurrentPos.x = CursorPos.x1;
Cf.ptCurrentPos.y = CursorPos.y1;
ImmSetCompositionWindow(hIMC, &Cf);
ImmReleaseContext(Handle(), hIMC);
}
#endif
}
}
#if DRAW_LINE_BOXES
{
uint Style = pDC->LineStyle(LSurface::LineAlternate);
LColour Old = pDC->Colour(LColour::Red);
pDC->Box(&OldTr);
pDC->Colour(Old);
pDC->LineStyle(Style);
LString s;
s.Printf("%i, %i", Line.IndexOf(l), l->Start);
LDisplayString ds(LSysFont, s);
LSysFont->Transparent(true);
ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1);
}
#endif
y += LineY;
It++;
} // end of line loop
// draw any space under the lines
if (y <= r.y2)
{
pDC->Colour(Back);
// pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2);
}
}
else
{
// default drawing: nothing
pDC->Colour(Back);
pDC->Rectangle(&r);
}
// _PaintTime = LCurrentTime() - StartTime;
#ifdef PAINT_DEBUG
if (GetNotify())
{
char s[256];
sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime);
LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s);
GetNotify()->OnEvent(&m);
}
#endif
// printf("PaintTime: %ims\n", _PaintTime);
#if LGI_EXCEPTIONS
}
catch (...)
{
LgiMsg(this, "LTextView3::OnPaint crashed.", "Lgi");
}
#endif
}
LMessage::Result LTextView3::OnEvent(LMessage *Msg)
{
switch (Msg->Msg())
{
case M_TEXT_UPDATE_NAME:
{
if (d->Lock(_FL))
{
Name(d->SetName);
d->SetName.Empty();
d->Unlock();
}
break;
}
case M_TEXTVIEW_FIND:
{
if (InThread())
DoFindNext(NULL);
else
LgiTrace("%s:%i - Not in thread.\n", _FL);
break;
}
case M_TEXTVIEW_REPLACE:
{
// DoReplace();
break;
}
case M_CUT:
{
Cut();
break;
}
case M_COPY:
{
Copy();
break;
}
case M_PASTE:
{
Paste();
break;
}
#if defined WIN32
case WM_GETTEXTLENGTH:
{
return Size;
}
case WM_GETTEXT:
{
int Chars = (int)Msg->A();
char *Out = (char*)Msg->B();
if (Out)
{
char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars);
if (In)
{
int Len = (int)strlen(In);
memcpy(Out, In, Len);
DeleteArray(In);
return Len;
}
}
return 0;
}
/* This is broken... the IME returns garbage in the buffer. :(
case WM_IME_COMPOSITION:
{
if (Msg->b & GCS_RESULTSTR)
{
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
char *Buf = new char[Size];
if (Buf)
{
ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size);
char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size);
if (Utf)
{
Insert(Cursor, Utf, StrlenW(Utf));
DeleteArray(Utf);
}
DeleteArray(Buf);
}
ImmReleaseContext(Handle(), hIMC);
}
return 0;
}
break;
}
*/
#endif
}
return LLayout::OnEvent(Msg);
}
int LTextView3::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl->GetId() == IDC_VSCROLL && VScroll)
{
if (n.Type == LNotifyScrollBarCreate)
{
UpdateScrollBars();
}
Invalidate();
}
return 0;
}
void LTextView3::InternalPulse()
{
if (!ReadOnly)
{
uint64 Now = LCurrentTime();
if (!BlinkTs)
BlinkTs = Now;
else if (Now - BlinkTs > CURSOR_BLINK)
{
Blink = !Blink;
LRect p = CursorPos;
p.Offset(-ScrollX, 0);
Invalidate(&p);
BlinkTs = Now;
}
}
if (PartialPour)
PourText(Size, 0);
}
void LTextView3::OnPulse()
{
#ifdef WINDOWS
InternalPulse();
#else
for (auto c: Ctrls)
c->InternalPulse();
#endif
}
void LTextView3::OnUrl(char *Url)
{
if (Environment)
Environment->OnNavigate(this, Url);
else
{
LUri u(Url);
bool Email = LIsValidEmail(Url);
const char *Proto = Email ? "mailto" : u.sProtocol;
LString App = LGetAppForProtocol(Proto);
if (App)
LExecute(App, Url);
else
LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto);
}
}
bool LTextView3::OnLayout(LViewLayoutInfo &Inf)
{
Inf.Width.Min = 32;
Inf.Width.Max = -1;
Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4;
Inf.Height.Max = -1;
return true;
}
///////////////////////////////////////////////////////////////////////////////
class LTextView3_Factory : public LViewFactory
{
LView *NewView(const char *Class, LRect *Pos, const char *Text)
{
if (_stricmp(Class, "LTextView3") == 0)
{
return new LTextView3(-1, 0, 0, 2000, 2000);
}
return 0;
}
} TextView3_Factory;
diff --git a/src/common/Widgets/Edit.cpp b/src/common/Widgets/Edit.cpp
--- a/src/common/Widgets/Edit.cpp
+++ b/src/common/Widgets/Edit.cpp
@@ -1,281 +1,281 @@
// \file
// \author Matthew Allen (fret@memecode.com)
#include "lgi/common/Lgi.h"
#include "lgi/common/Edit.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/LgiRes.h"
///////////////////////////////////////////////////////////////////////////////////////////
// Edit
#include "lgi/common/TextView3.h"
class _OsFontType : public LFontType
{
public:
LFont *Create(LSurface *pDC = NULL)
{
return LSysFont;
}
};
static _OsFontType SysFontType;
class LEditPrivate
{
public:
bool FocusOnCreate;
bool Multiline;
bool Password;
bool NotificationProcessed;
LAutoString EmptyTxt;
LEditPrivate()
{
FocusOnCreate = false;
NotificationProcessed = false;
}
};
void LEdit::KeyProcessed()
{
d->NotificationProcessed = true;
}
LEdit::LEdit(int id, int x, int y, int cx, int cy, const char *name) :
#if WINNATIVE
ResObject(Res_EditBox)
#else
LTextView3(id, x, y, cx, cy, &SysFontType)
#endif
{
#if !WINNATIVE
- _ObjName = Res_EditBox;
+ SetObjectName(Res_EditBox);
SetUrlDetect(false);
SetWrapType(TEXTED_WRAP_NONE);
#endif
_OsFontType Type;
d = new LEditPrivate;
LDisplayString Ds(LSysFont, (char*)(name?name:"A"));
if (cx < 0) cx = Ds.X() + 6;
if (cy < 0) cy = Ds.Y() + 4;
d->Multiline = false;
d->Password = false;
Sunken(true);
if (name) Name(name);
LRect r(x, y, x+MAX(cx, 10), y+MAX(cy, 10));
SetPos(r);
LResources::StyleElement(this);
}
LEdit::~LEdit()
{
DeleteObj(d);
}
void LEdit::SetEmptyText(const char *EmptyText)
{
d->EmptyTxt.Reset(NewStr(EmptyText));
Invalidate();
}
void LEdit::SendNotify(LNotification n)
{
if (n.Type == LNotifyDocChanged)
return LTextView3::SendNotify(n);
else if (n.Type == LNotifyReturnKey ||
n.Type == LNotifyEscapeKey)
return LTextView3::SendNotify(n);
}
LRange LEdit::GetSelectionRange()
{
LRange r;
ssize_t Sel = LTextView3::GetCaret(false);
ssize_t Cur = LTextView3::GetCaret();
if (Sel < Cur)
{
r.Start = Sel;
r.Len = Cur - Sel;
}
else
{
r.Start = Cur;
r.Len = Sel - Cur;
}
return r;
}
void LEdit::Select(int Start, int Len)
{
LTextView3::SetCaret(Start, false);
LTextView3::SetCaret(Start + (Len > 0 ? Len : 0x7fffffff) - 1, true);
}
ssize_t LEdit::GetCaret(bool Cursor)
{
return LTextView3::GetCaret(Cursor);
}
void LEdit::SetCaret(size_t Pos, bool Select, bool ForceFullUpdate)
{
LTextView3::SetCaret(Pos, Select, ForceFullUpdate);
}
void LEdit::Value(int64 i)
{
char Str[32];
sprintf(Str, LPrintfInt64, i);
Name(Str);
}
int64 LEdit::Value()
{
auto n = Name();
return (n) ? atoi(n) : 0;
}
bool LEdit::MultiLine()
{
return d->Multiline;
}
void LEdit::MultiLine(bool m)
{
d->Multiline = m;
}
bool LEdit::Password()
{
return d->Password;
}
void LEdit::Password(bool m)
{
SetObscurePassword(d->Password = m);
}
bool LEdit::SetScrollBars(bool x, bool y)
{
// Prevent scrollbars if the control is not multiline.
return LTextView3::SetScrollBars(x, y && d->Multiline);
}
void LEdit::OnPaint(LSurface *pDC)
{
LTextView3::OnPaint(pDC);
if (!ValidStr(Name()) && d->EmptyTxt && !Focus())
{
LFont *f = GetFont();
LDisplayString ds(f, d->EmptyTxt);
static int diff = -1;
if (diff < 0)
diff = abs(LColour(L_MED).GetGray()-LColour(L_WORKSPACE).GetGray());
f->Colour(diff < 30 ? L_LOW : L_MED, L_WORKSPACE);
f->Transparent(true);
ds.Draw(pDC, 2, 2);
}
}
bool LEdit::OnKey(LKey &k)
{
d->NotificationProcessed = false;
switch (k.vkey)
{
case LK_RETURN:
#ifndef LINUX
case LK_KEYPADENTER:
#endif
case LK_ESCAPE:
case LK_BACKSPACE:
case LK_DELETE:
{
if (k.Down())
SendNotify(LNotification(k));
break;
}
}
if
(
!d->Multiline &&
(
k.vkey == LK_TAB ||
k.vkey == LK_RETURN ||
#ifndef LINUX
k.vkey == LK_KEYPADENTER ||
#endif
k.vkey == LK_ESCAPE
)
)
{
return d->NotificationProcessed;
}
return LTextView3::OnKey(k);
}
void LEdit::OnEnter(LKey &k)
{
if (d->Multiline)
LTextView3::OnEnter(k);
else
SendNotify(LNotification(k));
}
bool LEdit::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 *in = t, *out = t;
for (; *in; in++)
{
if (*in == '\n')
{
if (d->Multiline)
*out++ = *in;
}
else if (*in != '\r')
{
*out++ = *in;
}
}
*out++ = 0;
// insert text
size_t Len = StrlenW(t);
Insert(Cursor, t, Len);
LTextView3::SetCaret(Cursor+Len, false, true); // Multiline
return true;
}
diff --git a/src/mac/cocoa/File.mm b/src/mac/cocoa/File.mm
--- a/src/mac/cocoa/File.mm
+++ b/src/mac/cocoa/File.mm
@@ -1,2003 +1,2003 @@
/*hdr
** FILE: File.mm
** AUTHOR: Matthew Allen
** DATE: 8/10/2000
** DESCRIPTION: The new file subsystem
**
** Copyright (C) 2000, Matthew Allen
** fret@memecode.com
*/
/****************************** Includes **********************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include "lgi/common/File.h"
#include "lgi/common/Containers.h"
#include "lgi/common/Gdc2.h"
#include "lgi/common/LgiCommon.h"
#include "lgi/common/LgiString.h"
#include "lgi/common/SubProcess.h"
#if LGI_COCOA
#include
#endif
/****************************** Defines ***********************************/
#define stat64 stat
#define lseek64 lseek
#define ftrucate64 ftruncate
#define O_LARGEFILE 0
/****************************** Globals ***********************************/
LString LFile::Path::Sep(DIR_STR);
bool LFile::Path::FixCase()
{
return false;
}
struct ErrorCodeType
{
const char *Name;
int Code;
const char *Desc;
}
ErrorCodes[] =
{
#if defined(WIN32)
{"EPERM", 1, "Not owner"},
{"ENOENT", 2, "No such file"},
{"ESRCH", 3, "No such process"},
{"EINTR", 4, "Interrupted system"},
{"EIO", 5, "I/O error"},
{"ENXIO", 6, "No such device"},
{"E2BIG", 7, "Argument list too long"},
{"ENOEXEC", 8, "Exec format error"},
{"EBADF", 9, "Bad file number"},
{"ECHILD", 10, "No children"},
{"EAGAIN", 11, "No more processes"},
{"ENOMEM", 12, "Not enough core"},
{"EACCES", 13, "Permission denied"},
{"EFAULT", 14, "Bad address"},
{"ENOTBLK", 15, "Block device required"},
{"EBUSY", 16, "Mount device busy"},
{"EEXIST", 17, "File exists"},
{"EXDEV", 18, "Cross-device link"},
{"ENODEV", 19, "No such device"},
{"ENOTDIR", 20, "Not a directory"},
{"EISDIR", 21, "Is a directory"},
{"EINVAL", 22, "Invalid argument"},
{"ENFILE", 23, "File table overflow"},
{"EMFILE", 24, "Too many open file"},
{"ENOTTY", 25, "Not a typewriter"},
{"ETXTBSY", 26, "Text file busy"},
{"EFBIG", 27, "File too large"},
{"ENOSPC", 28, "No space left on"},
{"ESPIPE", 29, "Illegal seek"},
{"EROFS", 30, "Read-only file system"},
{"EMLINK", 31, "Too many links"},
{"EPIPE", 32, "Broken pipe"},
{"EWOULDBLOCK", 35, "Operation would block"},
{"EINPROGRESS", 36, "Operation now in progress"},
{"EALREADY", 37, "Operation already in progress"},
{"ENOTSOCK", 38, "Socket operation on"},
{"EDESTADDRREQ", 39, "Destination address required"},
{"EMSGSIZE", 40, "Message too long"},
{"EPROTOTYPE", 41, "Protocol wrong type"},
{"ENOPROTOOPT", 42, "Protocol not available"},
{"EPROTONOSUPPORT", 43, "Protocol not supported"},
{"ESOCKTNOSUPPORT", 44, "Socket type not supported"},
{"EOPNOTSUPP", 45, "Operation not supported"},
{"EPFNOSUPPORT", 46, "Protocol family not supported"},
{"EAFNOSUPPORT", 47, "Address family not supported"},
{"EADDRINUSE", 48, "Address already in use"},
{"EADDRNOTAVAIL", 49, "Can't assign requested address"},
{"ENETDOWN", 50, "Network is down"},
{"ENETUNREACH", 51, "Network is unreachable"},
{"ENETRESET", 52, "Network dropped connection"},
{"ECONNABORTED", 53, "Software caused connection"},
{"ECONNRESET", 54, "Connection reset by peer"},
{"ENOBUFS", 55, "No buffer space available"},
{"EISCONN", 56, "Socket is already connected"},
{"ENOTCONN", 57, "Socket is not connected" },
{"ESHUTDOWN", 58, "Can't send after shutdown"},
{"ETOOMANYREFS", 59, "Too many references"},
{"ETIMEDOUT", 60, "Connection timed out"},
{"ECONNREFUSED", 61, "Connection refused"},
{"ELOOP", 62, "Too many levels of nesting"},
{"ENAMETOOLONG", 63, "File name too long" },
{"EHOSTDOWN", 64, "Host is down"},
{"EHOSTUNREACH", 65, "No route to host"},
{"ENOTEMPTY", 66, "Directory not empty"},
{"EPROCLIM", 67, "Too many processes"},
{"EUSERS", 68, "Too many users"},
{"EDQUOT", 69, "Disc quota exceeded"},
{"ESTALE", 70, "Stale NFS file handle"},
{"EREMOTE", 71, "Too many levels of remote in the path"},
{"ENOSTR", 72, "Device is not a stream"},
{"ETIME", 73, "Timer expired"},
{"ENOSR", 74, "Out of streams resources"},
{"ENOMSG", 75, "No message"},
{"EBADMSG", 76, "Trying to read unreadable message"},
{"EIDRM", 77, "Identifier removed"},
{"EDEADLK", 78, "Deadlock condition"},
{"ENOLCK", 79, "No record locks available"},
{"ENONET", 80, "Machine is not on network"},
{"ERREMOTE", 81, "Object is remote"},
{"ENOLINK", 82, "The link has been severed"},
{"EADV", 83, "ADVERTISE error"},
{"ESRMNT", 84, "SRMOUNT error"},
{"ECOMM", 85, "Communication error"},
{"EPROTO", 86, "Protocol error"},
{"EMULTIHOP", 87, "Multihop attempted"},
{"EDOTDOT", 88, "Cross mount point"},
{"EREMCHG", 89, "Remote address change"},
#elif defined(MAC)
{"EPERM", EPERM, "Operation not permitted"},
{"ENOENT", ENOENT, "No such file or directory"},
{"ESRCH", ESRCH, "No such process"},
{"EINTR", EINTR, "Interrupted system call"},
{"EIO", EIO, "I/O error"},
{"ENXIO", ENXIO, "No such device or address"},
{"E2BIG", E2BIG, "Argument list too long"},
{"ENOEXEC", ENOEXEC, "Exec format error"},
{"EBADF", EBADF, "Bad file number"},
{"ECHILD", ECHILD, "No child processes"},
{"EAGAIN", EAGAIN, "Try again"},
{"ENOMEM", ENOMEM, "Out of memory"},
{"EACCES", EACCES, "Permission denied"},
{"EFAULT", EFAULT, "Bad address"},
{"ENOTBLK", ENOTBLK, "Block device required"},
{"EBUSY", EBUSY, "Device or resource busy"},
{"EEXIST", EEXIST, "File exists"},
{"EXDEV", EXDEV, "Cross-device link"},
{"ENODEV", ENODEV, "No such device"},
{"ENOTDIR", ENOTDIR, "Not a directory"},
{"EISDIR", EISDIR, "Is a directory"},
{"EINVAL", EINVAL, "Invalid argument"},
{"ENFILE", ENFILE, "File table overflow"},
{"EMFILE", EMFILE, "Too many open files"},
{"ENOTTY", ENOTTY, "Not a typewriter"},
{"ETXTBSY", ETXTBSY, "Text file busy"},
{"EFBIG", EFBIG, "File too large"},
{"ENOSPC", ENOSPC, "No space left on device"},
{"ESPIPE", ESPIPE, "Illegal seek"},
{"EROFS", EROFS, "Read-only file system"},
{"EMLINK", EMLINK, "Too many links"},
{"EPIPE", EPIPE, "Broken pipe"},
{"EDOM", EDOM, "Math argument out of domain of func"},
{"ERANGE", ERANGE, "Math result not representable"},
{"EDEADLK", EDEADLK, "Resource deadlock would occur"},
{"ENAMETOOLONG", ENAMETOOLONG, "File name too long"},
{"ENOLCK", ENOLCK, "No record locks available"},
{"ENOSYS", ENOSYS, "Function not implemented"},
{"ENOTEMPTY", ENOTEMPTY, "Directory not empty"},
{"ELOOP", ELOOP, "Too many symbolic links encountered"},
{"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"},
{"ENOMSG", ENOMSG, "No message of desired type"},
{"EIDRM", EIDRM, "Identifier removed"},
{"EREMOTE", EREMOTE, "Object is remote"},
{"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"},
{"EILSEQ", EILSEQ, "Illegal byte sequence"},
{"EUSERS", EUSERS, "Too many users"},
{"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"},
{"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"},
{"EMSGSIZE", EMSGSIZE, "Message too long"},
{"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"},
{"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"},
{"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"},
{"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"},
{"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"},
{"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"},
{"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"},
{"EADDRINUSE", EADDRINUSE, "Address already in use"},
{"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"},
{"ENETDOWN", ENETDOWN, "Network is down"},
{"ENETUNREACH", ENETUNREACH, "Network is unreachable"},
{"ENETRESET", ENETRESET, "Network dropped connection because of reset"},
{"ECONNABORTED", ECONNABORTED, "Software caused connection abort"},
{"ECONNRESET", ECONNRESET, "Connection reset by peer"},
{"ENOBUFS", ENOBUFS, "No buffer space available"},
{"EISCONN", EISCONN, "Transport endpoint is already connected"},
{"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"},
{"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"},
{"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"},
{"ETIMEDOUT", ETIMEDOUT, "Connection timed out"},
{"ECONNREFUSED", ECONNREFUSED, "Connection refused"},
{"EHOSTDOWN", EHOSTDOWN, "Host is down"},
{"EHOSTUNREACH", EHOSTUNREACH, "No route to host"},
{"EALREADY", EALREADY, "Operation already in progress"},
{"EINPROGRESS", EINPROGRESS, "Operation now in progress"},
{"ESTALE", ESTALE, "Stale NFS file handle"},
{"EDQUOT", EDQUOT, "Quota exceeded"},
#else
#error impl me
#endif
{"NONE", 0, "No error"},
};
const char *GetErrorName(int e)
{
for (ErrorCodeType *c=ErrorCodes; c->Code; c++)
{
if (e == c->Code)
{
return c->Name;
}
}
static char s[32];
sprintf(s, "Unknown(%i)", e);
return s;
}
const char *GetErrorDesc(int e)
{
for (ErrorCodeType *c=ErrorCodes; c->Code; c++)
{
if (e == c->Code)
{
return c->Desc;
}
}
return 0;
}
/****************************** Helper Functions **************************/
char *LReadTextFile(const char *File)
{
char *s = 0;
LFile f;
if (File && f.Open(File, O_READ))
{
int64 Len = f.GetSize();
s = new char[Len+1];
if (s)
{
ssize_t Read = f.Read(s, (int)Len);
s[Read] = 0;
}
}
return s;
}
int64 LFileSize(const char *FileName)
{
struct stat64 s;
if (FileName &&
stat64(FileName, &s) == 0)
{
return s.st_size;
}
return 0;
}
bool LDirExists(const char *FileName, char *CorrectCase)
{
bool Status = false;
if (FileName)
{
struct stat s;
// Check for exact match...
if (stat(FileName, &s) == 0)
{
Status = S_ISDIR(s.st_mode);
}
}
return Status;
}
bool LFileExists(const char *FileName, char *CorrectCase)
{
bool Status = false;
if (FileName)
{
struct stat s;
// Check for exact match...
if (stat(FileName, &s) == 0)
{
Status = true;
}
else if (strlen(FileName) < MAX_PATH_LEN)
{
// Look for altenate case by enumerating the directory
char d[MAX_PATH_LEN];
strcpy_s(d, sizeof(d), FileName);
char *e = strrchr(d, DIR_CHAR);
if (e)
{
*e++ = 0;
DIR *Dir = opendir(d);
if (Dir)
{
dirent *De;
while ((De = readdir(Dir)))
{
if (stricmp(De->d_name, e) == 0)
{
try
{
// Tell the calling program the actual case of the file...
e = (char*)strrchr(FileName, DIR_CHAR);
// If this crashes because the argument is read only then we get caught by the try catch
strcpy(e+1, De->d_name);
// It worked!
Status = true;
}
catch (...)
{
// It didn't work :(
#ifdef _DEBUG
printf("%s,%i - FileExists(%s) found an alternate case version but couldn't return it to the caller.\n",
__FILE__, __LINE__, FileName);
#endif
}
break;
}
}
closedir(Dir);
}
}
}
}
return Status;
}
bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len)
{
return readlink (LinkFile, Path, Len) > 0;
}
void WriteStr(LFile &f, const char *s)
{
size_t Len = (s) ? strlen(s) : 0;
f << Len;
if (Len > 0)
{
f.Write(s, (int)Len);
}
}
char *ReadStr(LFile &f DeclDebugArgs)
{
char *s = 0;
// read the strings length...
uint32 Len;
f >> Len;
if (Len > 0)
{
// 16mb sanity check.... anything over this
// is _probably_ an error
if (Len >= (16 << 20))
{
// LgiAssert(0);
return 0;
}
// allocate the memory buffer
#if defined(_DEBUG) && defined MEMORY_DEBUG
s = new(_file, _line) char[Len+1];
#else
s = new char[Len+1];
#endif
if (s)
{
// read the bytes from disk
f.Read(s, Len);
s[Len] = 0;
}
else
{
// memory allocation error, skip the data
// on disk so the caller is where they think
// they are in the file.
f.Seek(Len, SEEK_CUR);
}
}
return s;
}
ssize_t SizeofStr(const char *s)
{
return sizeof(uint32) + ((s) ? strlen(s) : 0);
}
bool LGetDriveInfo
(
char *Path,
uint64 *Free,
uint64 *Size,
uint64 *Available
)
{
bool Status = false;
if (Path)
{
struct stat s;
if (lstat(Path, &s) == 0)
{
// printf("LGetDriveInfo dev=%i\n", s.st_dev);
}
}
return Status;
}
/////////////////////////////////////////////////////////////////////////
#include
#include
struct LVolumePriv
{
LString Name;
LString Path;
int Type = VT_NONE;
int Flags = 0;
int64 Size = 0;
int64 Free = 0;
LAutoPtr Icon;
LSystemPath SysPath = LSP_ROOT;
LVolume *Next = NULL, *Child = NULL;
LVolumePriv(const char *init)
{
if (init)
{
Name = LGetLeaf(init);
Type = VT_FOLDER;
Path = init;
}
}
LVolumePriv(LSystemPath type, const char *name)
{
if (type)
{
Name = name;
switch (SysPath = type)
{
case LSP_DESKTOP:
Type = VT_DESKTOP;
break;
default:
Type = VT_FOLDER;
break;
}
Path = LGetSystemPath(type);
}
}
void Insert(LVolume *v)
{
if (!Child)
Child = v;
else
{
for (auto i = Child; i; i = i->Next())
{
if (!i->d->Next)
{
i->d->Next = v;
break;
}
}
}
}
};
LVolume::LVolume(const char *init)
{
d = new LVolumePriv(init);
}
LVolume::LVolume(LSystemPath syspath, const char *name)
{
d = new LVolumePriv(syspath, name);
}
LVolume::~LVolume()
{
DeleteObj(d->Child);
DeleteObj(d->Next);
DeleteObj(d);
}
const char *LVolume::Name() const
{
return d->Name;
}
const char *LVolume::Path() const
{
return d->Path;
}
int LVolume::Type() const
{
return d->Type;
}
int LVolume::Flags() const
{
return d->Flags;
}
uint64 LVolume::Size() const
{
return d->Size;
}
uint64 LVolume::Free() const
{
return d->Free;
}
LSurface *LVolume::Icon() const
{
return d->Icon;
}
LDirectory *LVolume::GetContents()
{
LDirectory *Dir = NULL;
if (d->Path)
{
Dir = new LDirectory;
if (Dir && !Dir->First(d->Path))
DeleteObj(Dir);
}
return Dir;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // IDGF Apple.. provide an alternative you dumbass
LVolume *LVolume::First()
{
if (d->SysPath == LSP_DESKTOP &&
!d->Child)
{
LVolume *v = NULL;
LString DesktopPath = LGetSystemPath(LSP_DESKTOP);
// List any favorites
UInt32 seed;
auto sflRef = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
auto items = LSSharedFileListCopySnapshot( sflRef, &seed );
for( size_t i = 0; i < CFArrayGetCount(items); i++ )
{
auto item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(items, i);
if( !item )
continue;
auto outURL = LSSharedFileListItemCopyResolvedURL(item,kLSSharedFileListNoUserInteraction, NULL);
if( !outURL )
continue;
CFStringRef itemPath = CFURLCopyFileSystemPath(outURL,kCFURLPOSIXPathStyle);
LString s = itemPath;
if (!s.Equals(DesktopPath)) // This is the root item, don't duplicate
{
v = new LVolume();
if (v)
{
v->d->Path = s;
v->d->Name = LGetLeaf(s);
v->d->Type = VT_FOLDER;
auto IcoRef = LSSharedFileListItemCopyIconRef(item);
if (IcoRef)
{
NSImage *img = [[NSImage alloc] initWithIconRef:IcoRef];
v->d->Icon.Reset(new LMemDC(img));
[img release];
CFRelease(IcoRef);
}
d->Insert(v);
}
}
CFRelease(outURL);
CFRelease(itemPath);
}
CFRelease(items);
CFRelease(sflRef);
}
return d->Child;
}
LVolume *LVolume::Next()
{
if (d->SysPath == LSP_DESKTOP &&
!d->Next)
{
d->Next = new LVolume("/Volumes");
// List the local hard disks
auto *ws = [NSWorkspace sharedWorkspace];
auto *vols = [ws mountedLocalVolumePaths];
auto *fm = [NSFileManager defaultManager];
for (NSString *path in vols)
{
NSDictionary* fsAttributes;
NSString *description, *type, *volName;
BOOL removable, writable, unmountable, res;
res = [ws getFileSystemInfoForPath:path
isRemovable:&removable
isWritable:&writable
isUnmountable:&unmountable
description:&description
type:&type];
if (!res) continue;
// fsAttributes = [fm fileSystemAttributesAtPath:path];
NSError *err = nil;
fsAttributes = [fm attributesOfFileSystemForPath:path error:&err];
volName = [fm displayNameAtPath:path];
NSNumber *size = [fsAttributes objectForKey:NSFileSystemSize];
NSNumber *freeSize = [fsAttributes objectForKey:NSFileSystemFreeSize];
#if 0
NSLog(@"path=%@\nname=%@\nremovable=%d\nwritable=%d\nunmountable=%d\n"
"description=%@\ntype=%@, size=%@\n\n",
path, name, removable, writable, unmountable, description, type, size);
#endif
LString s = [type UTF8String];
LString p = [path UTF8String];
LString name = [volName UTF8String];
if (!s.Equals("autofs") &&
p.Find("/System/Volumes") < 0 &&
p.Find("/private") < 0)
{
auto v = new LVolume();
if (v)
{
v->d->Path = p;
v->d->Name = name;
v->d->Type = VT_HARDDISK;
v->d->Size = size.longLongValue;
v->d->Free = freeSize.longLongValue;
d->Next->d->Insert(v);
// printf("Vol: s=%s p=%s name=%s\n", s.Get(), p.Get(), v->d->Name.Get());
}
}
}
}
return d->Next;
}
#pragma GCC diagnostic pop
bool LVolume::IsMounted() const
{
return false;
}
bool LVolume::SetMounted(bool Mount)
{
return Mount;
}
void LVolume::Insert(LAutoPtr v)
{
d->Insert(v.Release());
}
///////////////////////////////////////////////////////////////////////////////
LFileSystem *LFileSystem::Instance = 0;
LFileSystem::LFileSystem()
{
Instance = this;
Root = 0;
}
LFileSystem::~LFileSystem()
{
DeleteObj(Root);
}
void LFileSystem::OnDeviceChange(char *Reserved)
{
}
LVolume *LFileSystem::GetRootVolume()
{
if (!Root)
Root = new LVolume(LSP_DESKTOP, "Desktop");
return Root;
}
int FloppyType(int Letter)
{
return 0;
}
#if 0
OSType gFinderSignature = 'MACS';
OSStatus MoveFileToTrash(CFURLRef fileURL)
{
AppleEvent event, reply;
OSStatus err;
FSRef fileRef;
AliasHandle fileAlias;
if (CFURLGetFSRef(fileURL, &fileRef) == false)
return coreFoundationUnknownErr;
err = FSNewAliasMinimal(&fileRef, &fileAlias);
if (err == noErr)
{
err = AEBuildAppleEvent(kAECoreSuite,
kAEDelete,
typeApplSignature,
&gFinderSignature,
sizeof(OSType),
kAutoGenerateReturnID,
kAnyTransactionID,
&event,
NULL,
"'----':alis(@@)",
fileAlias);
if (err == noErr)
{
err = AESendMessage(&event, &reply, kAEWaitReply, kAEDefaultTimeout);
if (err == noErr)
AEDisposeDesc(&reply);
AEDisposeDesc(&event);
}
DisposeHandle((Handle)fileAlias);
}
return err;
}
#endif
bool LFileSystem::Copy(const char *From, const char *To, LError *ErrorCode, CopyFileCallback Callback, void *Token)
{
if (!From || !To)
{
#if LGI_COCOA
if (ErrorCode) *ErrorCode = NSFileReadInvalidFileNameError;
#else
if (ErrorCode) *ErrorCode = paramErr;
#endif
return false;
}
LFile In, Out;
if (!In.Open(From, O_READ))
{
if (ErrorCode) *ErrorCode = In.GetError();
return false;
}
if (!Out.Open(To, O_WRITE))
{
if (ErrorCode) *ErrorCode = Out.GetError();
return false;
}
if (Out.SetSize(0))
{
if (ErrorCode) *ErrorCode =
#if LGI_COCOA
NSFileWriteUnknownError;
#else
writErr;
#endif
return false;
}
int64 Size = In.GetSize();
if (!Size)
{
return true;
}
int64 Block = MIN((1 << 20), Size);
char *Buf = new char[Block];
if (!Buf)
{
if (ErrorCode) *ErrorCode =
#if LGI_COCOA
NSFileWriteOutOfSpaceError;
#else
notEnoughBufferSpace;
#endif
return false;
}
int64 i = 0;
while (i < Size)
{
ssize_t r = In.Read(Buf, Block);
if (r > 0)
{
int Written = 0;
while (Written < r)
{
ssize_t w = Out.Write(Buf + Written, r - Written);
if (w > 0)
{
Written += w;
}
else
{
if (ErrorCode) *ErrorCode =
#if LGI_COCOA
NSFileWriteUnknownError;
#else
writErr;
#endif
goto ExitCopyLoop;
}
}
i += Written;
if (Callback)
{
if (!Callback(Token, i, Size))
{
break;
}
}
}
else break;
}
ExitCopyLoop:
DeleteArray(Buf);
if (i == Size)
{
if (ErrorCode) *ErrorCode = noErr;
}
else
{
Out.Close();
Delete(To, false);
}
return i == Size;
/*
bool Status = false;
if (From AND To)
{
LFile In, Out;
if (In.Open(From, O_READ) AND
Out.Open(To, O_WRITE))
{
Out.SetSize(0);
int64 Size = In.GetSize();
int64 Block = min((1 << 20), Size);
char *Buf = new char[Block];
if (Buf)
{
int64 i = 0;
while (i < Size)
{
int r = In.Read(Buf, Block);
if (r > 0)
{
int Written = 0;
while (Written < r)
{
int w = Out.Write(Buf + Written, r - Written);
if (w > 0)
{
Written += w;
}
else goto ExitCopyLoop;
}
i += Written;
if (Callback)
{
if (!Callback(Token, i, Size))
{
break;
}
}
}
else break;
}
ExitCopyLoop:
DeleteArray(Buf);
Status = i == Size;
if (!Status)
{
Delete(To, false);
}
}
}
}
return Status;
*/
}
bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash)
{
bool Error = false;
if (ToTrash)
{
#if defined MAC
#if LGI_COCOA
NSMutableArray *urls = [[NSMutableArray alloc] initWithCapacity:Files.Length()];
if (urls)
{
for (auto f : Files)
{
id u = (NSURL *)CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)f, strlen(f), 0);
[urls addObject:u];
CFRelease(u);
}
[[NSWorkspace sharedWorkspace] recycleURLs:urls completionHandler:NULL];
[urls release];
}
#else
// Apple events method
for (int i=0; i f;
f.Add(FileName);
return Delete(f, 0, ToTrash);
}
return false;
}
bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentFolders, LError *ErrorCode)
{
int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH);
if (r)
{
if (ErrorCode)
*ErrorCode = errno;
if (CreateParentFolders)
{
char Base[MAX_PATH_LEN];
strcpy_s(Base, sizeof(Base), PathName);
do
{
char *Leaf = strrchr(Base, DIR_CHAR);
if (!Leaf) return false;
*Leaf = 0;
}
while (!LDirExists(Base));
auto Parts = LString(PathName + strlen(Base)).SplitDelimit(DIR_STR);
for (int i=0; id_name, ".") == 0
||
strcmp(De->d_name, "..") == 0
||
(
Pattern
&&
!MatchStr(Pattern, De->d_name)
)
);
}
};
LDirectory::LDirectory()
{
d = new LDirectoryPriv;
}
LDirectory::~LDirectory()
{
Close();
DeleteObj(d);
}
LDirectory *LDirectory::Clone()
{
return new LDirectory;
}
int LDirectory::First(const char *Name, const char *Pattern)
{
Close();
if (Name)
{
strcpy_s(d->BasePath, sizeof(d->BasePath), Name);
d->BaseEnd = d->BasePath + strlen(d->BasePath);
if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0)
{
struct stat S;
if (lstat(Name, &S) == 0)
{
if (S_ISREG(S.st_mode))
{
char *Dir = strrchr(d->BasePath, DIR_CHAR);
if (Dir)
{
*Dir++ = 0;
d->Pattern = NewStr(Dir);
}
}
}
}
else
{
d->Pattern = NewStr(Pattern);
}
auto e = d->BasePath + strlen(d->BasePath);
while (e > d->BasePath && e[-1] == DIR_CHAR)
*--e = 0;
d->Dir = opendir(d->BasePath);
if (d->Dir)
{
d->De = readdir(d->Dir);
if (d->De)
{
char s[512];
LMakePath(s, sizeof(s), d->BasePath, GetName());
lstat(s, &d->Stat);
if (d->Ignore())
{
if (!Next())
{
return false;
}
}
}
}
else if (!Stricmp(Name, "/"))
{
// Really Apple? REALLY???
// Can't opendir("/")... sigh. Clearly there is more than one way to get this done.
LSubProcess p("ls", "-l /");
LStringPipe o;
if (p.Start() && p.Communicate(&o) == 0)
{
strcpy_s(d->BasePath, sizeof(d->BasePath), Name);
d->BaseEnd = d->BasePath + strlen(d->BasePath);
d->Cache.SetFixedLength(false);
- auto Lines = o.NewGStr().Split("\n");
+ auto Lines = o.NewLStr().Split("\n");
for (auto Ln: Lines)
{
auto p = Ln.SplitDelimit(" \t", 8);
if (p.Length() > 8)
{
auto Name = p.Last();
auto Lnk = Name.Split(" -> ");
if (Lnk.Length() > 1)
d->Cache.New().Printf("/%s", Lnk.Last().Get());
else
d->Cache.New().Printf("/%s", Name.Get());
}
}
d->CachePos = 0;
return !lstat(d->Cache[d->CachePos], &d->Stat);
}
else printf("%s:%i - ls failed.\n", _FL);
}
else
{
printf("%s:%i - opendir(%s) failed with %i\n", _FL, d->BasePath, errno);
}
}
return d->Dir != 0 && d->De != 0;
}
int LDirectory::Next()
{
int Status = false;
char s[512];
if (d->CachePos >= 0)
{
d->CachePos++;
if (d->CachePos >= d->Cache.Length())
Status = false;
else
{
*d->BaseEnd = 0;
auto &c = d->Cache[d->CachePos];
if (!lstat(c, &d->Stat))
Status = true;
}
}
else
{
while (d->Dir && d->De)
{
if ((d->De = readdir(d->Dir)))
{
*d->BaseEnd = 0;
LMakePath(s, sizeof(s), d->BasePath, GetName());
lstat(s, &d->Stat);
if (!d->Ignore())
{
Status = true;
break;
}
}
}
}
return Status;
}
int LDirectory::Close()
{
if (d->Dir)
{
closedir(d->Dir);
d->Dir = 0;
}
d->De = 0;
return true;
}
bool LDirectory::Path(char *s, int BufLen) const
{
if (!s)
{
return false;
}
return LMakePath(s, BufLen, d->BasePath, GetName());
}
int LDirectory::GetType() const
{
return IsDir() ? VT_FOLDER : VT_FILE;
}
int LDirectory::GetUser(bool Group) const
{
if (Group)
{
return d->Stat.st_gid;
}
else
{
return d->Stat.st_uid;
}
}
bool LDirectory::IsReadOnly() const
{
if (getuid() == d->Stat.st_uid)
{
// Check user perms
return !TestFlag(GetAttributes(), S_IWUSR);
}
else if (getgid() == d->Stat.st_gid)
{
// Check group perms
return !TestFlag(GetAttributes(), S_IWGRP);
}
// Check global perms
return !TestFlag(GetAttributes(), S_IWOTH);
}
bool LDirectory::IsSymLink() const
{
long a = GetAttributes();
return S_ISLNK(a);
}
bool LDirectory::IsHidden() const
{
return GetName() && GetName()[0] == '.';
}
bool LDirectory::IsDir() const
{
long a = GetAttributes();
return !S_ISLNK(a) && S_ISDIR(a);
}
long LDirectory::GetAttributes() const
{
return d->Stat.st_mode;
}
char *LDirectory::GetName() const
{
if (d->CachePos >= 0)
return d->Cache[d->CachePos];
if (d->De)
return d->De->d_name;
LAssert(!"Invalid state.");
return NULL;
}
uint64 LDirectory::GetCreationTime() const
{
return (uint64)d->Stat.st_ctime * 1000;
}
uint64 LDirectory::GetLastAccessTime() const
{
return (uint64)d->Stat.st_atime * 1000;
}
uint64 LDirectory::GetLastWriteTime() const
{
return (uint64)d->Stat.st_mtime * 1000;
}
uint64 LDirectory::GetSize() const
{
return d->Stat.st_size;
}
int64 LDirectory::GetSizeOnDisk()
{
return d->Stat.st_size;
}
const char *LDirectory::FullPath()
{
auto n = GetName();
if (!n)
return NULL;
char *s = d->BaseEnd;
char *e = d->BasePath + sizeof(d->BasePath);
if (s > d->BasePath &&
s[-1] != DIR_CHAR)
*s++ = DIR_CHAR;
strncpy(s, n, e - s);
return d->BasePath;
}
LString LDirectory::FileName() const
{
return GetName();
}
/////////////////////////////////////////////////////////////////////////////////
//////////////////////////// File ///////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
class LFilePrivate
{
public:
int hFile;
char *Name;
bool Swap;
int Status;
int Attributes;
int LastError;
LFilePrivate()
{
hFile = INVALID_HANDLE;
Name = 0;
Swap = false;
Status = true;
Attributes = 0;
#if LGI_COCOA
LastError = 0;
#else
LastError = noErr;
#endif
}
~LFilePrivate()
{
DeleteArray(Name);
}
};
LFile::LFile(const char *path, int mode)
{
d = new LFilePrivate;
if (path)
Open(path, mode);
}
LFile::~LFile()
{
if (d && ValidHandle(d->hFile))
{
Close();
}
DeleteObj(d);
}
int LFile::GetError()
{
return d->LastError;
}
OsFile LFile::Handle()
{
return d->hFile;
}
bool LFile::IsOpen()
{
return ValidHandle(d->hFile);
}
uint64_t LFile::GetModifiedTime()
{
struct stat s;
stat(d->Name, &s);
return s.st_mtime;
}
bool LFile::SetModifiedTime(uint64_t dt)
{
struct stat s;
stat(d->Name, &s);
struct timeval times[2] = {};
times[0].tv_sec = s.st_atime;
times[1].tv_sec = dt;
int r = utimes(d->Name, times);
return r == 0;
}
void LFile::ChangeThread()
{
}
// #define _FILE_OPEN
#ifdef _FILE_OPEN
GSemaphore _FileLock;
GHashTable _FileOpen;
LgiFunc void _DumpOpenFiles()
{
if (_FileLock.Lock(_FL))
{
char *k;
int i=0;
for (void *p=_FileOpen.First(&k); p; p=_FileOpen.Next(&k))
{
printf("File[%i]='%s'\n", i++, k);
}
_FileLock.Unlock();
}
}
#endif
int LFile::Open(const char *File, int Mode)
{
if (!File)
{
#if LGI_COCOA
d->LastError = NSFileReadInvalidFileNameError;
#else
d->LastError = paramErr;
#endif
return false;
}
if (TestFlag(Mode, O_WRITE) ||
TestFlag(Mode, O_READWRITE))
{
Mode |= O_CREAT;
}
Close();
d->hFile = open(File, Mode | O_LARGEFILE, S_IRUSR | S_IWUSR);
if (!ValidHandle(d->hFile))
{
d->LastError = errno;
printf("LFile::Open failed\n\topen(%s,%8.8x) = %i\n\terrno=%s (%s)\n", File, Mode, d->hFile, GetErrorName(errno), GetErrorDesc(errno));
return false;
}
#ifdef _FILE_OPEN
if (_FileLock.Lock(_FL))
{5
_FileOpen.Add(File, this);
_FileLock.Unlock();
}
#endif
d->Attributes = Mode;
d->Name = new char[strlen(File)+1];
if (d->Name)
{
strcpy(d->Name, File);
}
d->Status = true;
return true;
}
int LFile::Close()
{
if (ValidHandle(d->hFile))
{
#ifdef _FILE_OPEN
if (_FileLock.Lock(_FL))
{
_FileOpen.Delete(d->Name);
_FileLock.Unlock();
}
#endif
close(d->hFile);
d->hFile = INVALID_HANDLE;
DeleteArray(d->Name);
}
return true;
}
#define CHUNK 0xFFF0
ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags)
{
ssize_t Red = 0;
if (Buffer && Size > 0)
{
Red = read(d->hFile, Buffer, Size);
#ifdef _DEBUG
if (Red < 0)
{
int Err = errno;
int64 Pos = GetPos();
printf("Read error: %i, " LPrintfInt64 "\n", Err, Pos);
}
#endif
}
d->Status = Red == Size;
return MAX(Red, 0);
}
ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags)
{
ssize_t Written = 0;
if (Buffer && Size > 0)
{
Written = write(d->hFile, Buffer, Size);
#ifdef _DEBUG
if (Written < 0)
{
int Err = errno;
int64 Pos = GetPos();
printf("Write error: %i, " LPrintfInt64 "\n", Err, Pos);
}
#endif
}
d->Status = Written == Size;
return MAX(Written, 0);
}
int64 LFile::Seek(int64 To, int Whence)
{
#if LINUX64
return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64
#else
return lseek(d->hFile, To, Whence);
#endif
}
int64 LFile::SetPos(int64 Pos)
{
#if LINUX64
int64 p = lseek64(d->hFile, Pos, SEEK_SET);
if (p < 0)
{
int e = errno;
printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n",
__FILE__, __LINE__,
Pos, e, GetErrorName(e));
}
#else
return lseek(d->hFile, Pos, SEEK_SET);
#endif
}
int64 LFile::GetPos()
{
#if LINUX64
int64 p = lseek64(d->hFile, 0, SEEK_CUR);
if (p < 0)
{
int e = errno;
printf("%s:%i - lseek64 failed (error %i: %s).\n",
__FILE__, __LINE__,
e, GetErrorName(e));
}
return p;
#else
return lseek(d->hFile, 0, SEEK_CUR);
#endif
}
int64 LFile::GetSize()
{
int64 Here = GetPos();
#if LINUX64
int64 Ret = lseek64(d->hFile, 0, SEEK_END);
#else
off_t Ret = lseek(d->hFile, 0, SEEK_END);
#endif
SetPos(Here);
return Ret;
}
int64 LFile::SetSize(int64 Size)
{
if (ValidHandle(d->hFile))
{
int64 Pos = GetPos();
/*
close(d->hFile);
if (d->Name)
{
#if LINUX64
truncate64(Name, Size);
#else
truncate(Name, Size);
#endif
}
d->hFile = open(Name, Attributes, 0);
*/
#if LINUX64
ftruncate64(d->hFile, Size);
#else
ftruncate(d->hFile, Size);
#endif
if (d->hFile)
{
SetPos(Pos);
}
}
return GetSize();
}
bool LFile::Eof()
{
return GetPos() >= GetSize();
}
ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size)
{
ssize_t r = Read(Buf, Size);
if (r == Size)
{
uint8 *s = Buf, *e = Buf + r - 1;
for (; s < e; s++, e--)
{
uchar c = *s;
*s = *e;
*e = c;
}
}
else return 0;
return r;
}
ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size)
{
switch (Size)
{
case 1:
{
return Write(Buf, Size);
break;
}
case 2:
{
uint16 i = *((uint16*)Buf);
i = LgiSwap16(i);
return Write(&i, Size);
break;
}
case 4:
{
uint32 i = *((uint32*)Buf);
i = LgiSwap32(i);
return Write(&i, Size);
break;
}
case 8:
{
uint64 i = *((uint64*)Buf);
i = LgiSwap64(i);
return Write(&i, Size);
break;
}
default:
{
ssize_t i, n;
for (i=0, n=Size-1; i 0)
{
char c;
Size--;
do
{
r = read(d->hFile, &c, 1);
if (Eof())
{
break;
}
*Buf++ = c;
i++;
} while (i < Size - 1 && c != '\n');
*Buf = 0;
}
return i;
}
ssize_t LFile::WriteStr(char *Buf, ssize_t Size)
{
ssize_t i = 0;
ssize_t w;
while (i <= Size)
{
w = write(d->hFile, Buf, 1);
Buf++;
i++;
if (*Buf == '\n') break;
}
return i;
}
void LFile::SetStatus(bool s)
{
d->Status = s;
}
bool LFile::GetStatus()
{
return d->Status;
}
void LFile::SetSwap(bool s)
{
d->Swap = s;
}
bool LFile::GetSwap()
{
return d->Swap;
}
int LFile::GetOpenMode()
{
return d->Attributes;
}
const char *LFile::GetName()
{
return d->Name;
}
#define GFileOp(type) LFile &LFile::operator >> (type &i) \
{ \
d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); \
return *this; \
}
GFileOps();
#undef GFileOp
#define GFileOp(type) LFile &LFile::operator << (type i) \
{ \
d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); \
return *this; \
}
GFileOps();
#undef GFileOp
diff --git a/src/mac/cocoa/General.mm b/src/mac/cocoa/General.mm
--- a/src/mac/cocoa/General.mm
+++ b/src/mac/cocoa/General.mm
@@ -1,602 +1,602 @@
// Mac Implementation of General LGI functions
#include
#include
#include
#include
#include
// #define _POSIX_TIMERS
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Process.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/Button.h"
#include "lgi/common/Net.h"
#include
#include
#include
////////////////////////////////////////////////////////////////
// Local helper functions
CFStringRef Utf8ToCFString(const char *s, ssize_t len = -1)
{
if (s && len < 0)
len = strlen(s);
return CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)s, len, kCFStringEncodingUTF8, false);
}
char *CFStringToUtf8(CFStringRef r)
{
if (r == NULL)
return 0;
char *Buffer = 0;
CFRange g = { 0, CFStringGetLength(r) };
CFIndex Used;
if (CFStringGetBytes(r,
g,
kCFStringEncodingUTF8,
0,
false,
0,
0,
&Used))
{
if ((Buffer = new char[Used+1]))
{
CFStringGetBytes(r,
g,
kCFStringEncodingUTF8,
0,
false,
(UInt8*)Buffer,
Used,
&Used);
Buffer[Used] = 0;
}
}
return Buffer;
}
bool _lgi_check_file(char *Path)
{
if (Path)
{
if (LFileExists(Path))
{
// file is there
return true;
}
else
{
// shortcut?
char *e = Path + strlen(Path);
strcpy(e, ".lnk");
if (LFileExists(Path))
{
// resolve shortcut
char Link[256];
if (LResolveShortcut(Path, Link, sizeof(Link)))
{
// check destination of link
if (LFileExists(Link))
{
strcpy(Path, Link);
return true;
}
}
}
*e = 0;
}
}
return false;
}
void LSleep(uint32 i)
{
struct timespec request, remain;
ZeroObj(request);
ZeroObj(remain);
request.tv_sec = i / 1000;
request.tv_nsec = (i % 1000) * 1000000;
while (nanosleep(&request, &remain) == -1)
{
request = remain;
}
}
char *p2c(unsigned char *s)
{
if (s)
{
s[1+s[0]] = 0;
return (char*)s + 1;
}
return 0;
}
void c2p255(Str255 &d, char *s)
{
if (s)
{
size_t Len = strlen(s);
if (Len > 255) Len = 255;
d[0] = Len;
for (int i=0; iHandle();
[hnd.p performSelectorOnMainThread:@selector(assert:) withObject:ca waitUntilDone:true];
switch (ca.result)
{
case NSAlertFirstButtonReturn: // Debug/Break
Result = 2;
break;
case NSAlertSecondButtonReturn: // Ingore/Continue
Result = 3;
break;
case NSAlertThirdButtonReturn: // Exit/Abort
Result = 1;
break;
}
[ca release];
}
#else
GAlert a(0, "Assert Failed", Assert.Msg, "Abort", "Debug", "Ignore");
Result = a.DoModal();
#endif
switch (Result)
{
default:
{
exit(-1);
break;
}
case 2:
{
// Crash here to bring up the debugger...
int *p = 0;
*p = 0;
break;
}
case 3:
{
break;
}
}
#endif
Asserting = false;
}
}
////////////////////////////////////////////////////////////////////////
// Implementations
LMessage CreateMsg(int m, LMessage::Param a, LMessage::Param b)
{
static class LMessage Msg(0);
Msg.Set(m, a, b);
return Msg;
}
OsView DefaultOsView(LView *v)
{
return NULL;
}
LString LGetFileMimeType(const char *File)
{
return LAppInst ? LAppInst->GetFileMimeType(File) : NULL;
}
bool _GetIniField(char *Grp, char *Field, char *In, char *Out, int OutSize)
{
if (ValidStr(In))
{
bool InGroup = false;
auto t = LString(In).SplitDelimit("\r\n");
for (int i=0; i Line && strchr(" \t", *e)) e--;
// Seek v through any whitespace after the equals
while (*v && strchr(" \t", *v)) v++;
// Calculate the length of the field
size_t flen = e - Line;
// Check the current field against the input field
if (strnicmp(Field, Line, flen) == 0)
{
while ( *v &&
strchr("\r\n", *v) == 0 &&
OutSize > 1) // leave space for NULL
{
*Out++ = *v++;
OutSize--;
}
// Add the NULL
*Out++ = 0;
return true;
}
}
}
}
}
return false;
}
bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit)
{
auto mt = Utf8ToCFString(Mime);
if (mt)
{
auto uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mt, NULL);
if (uti)
{
CFURLRef appUrl = LSCopyDefaultApplicationURLForContentType(uti, kLSRolesAll, nil);
if (appUrl)
{
NSString *path = [(__bridge NSURL *)appUrl path];
NSString *name = [[NSFileManager defaultManager] displayNameAtPath:path];
if (path && name)
{
auto &a = Apps.New();
a.Path = path;
a.Name = name;
}
CFRelease(appUrl);
if (path) [path release];
if (name) [name release];
}
CFRelease(uti);
}
CFRelease(mt);
}
return Apps.Length() > 0;
}
LString LGetAppForMimeType(const char *Mime)
{
LArray Apps;
if (LGetAppsForMimeType(Mime, Apps, 1))
return Apps[0].Path.Get();
return LString();
}
int LgiRand(int Limit)
{
return rand() % Limit;
}
bool LPlaySound(const char *FileName, int ASync)
{
return LExecute(FileName);
}
#if LGI_CARBON
OSErr FinderLaunch(long nTargets, FSRef *targetList)
{
OSErr err = unimpErr;
AppleEvent theAEvent, theReply;
AEAddressDesc fndrAddress;
AEDescList targetListDesc;
OSType fndrCreator;
AliasHandle targetAlias;
long index;
/* verify parameters */
if ((nTargets == 0) || (targetList == NULL))
return paramErr;
/* set up locals */
AECreateDesc(typeNull, NULL, 0, &theAEvent);
AECreateDesc(typeNull, NULL, 0, &fndrAddress);
AECreateDesc(typeNull, NULL, 0, &theReply);
AECreateDesc(typeNull, NULL, 0, &targetListDesc);
targetAlias = NULL;
fndrCreator = 'MACS';
/* create an open documents event targeting the finder */
err = AECreateDesc(typeApplSignature, (Ptr) &fndrCreator,
sizeof(fndrCreator), &fndrAddress);
if (err == noErr)
{
err = AECreateAppleEvent(kCoreEventClass,
kAEOpenDocuments,
&fndrAddress,
kAutoGenerateReturnID,
kAnyTransactionID,
&theAEvent);
}
if (err == noErr)
{
/* create the list of files to open */
err = AECreateList(NULL, 0, false, &targetListDesc);
}
if (err == noErr)
{
for (index=0; index < nTargets; index++)
{
if (targetAlias == NULL)
{
// err = NewAlias(NULL, (targetList + index), &targetAlias);
err = FSNewAlias(NULL, (targetList + index), &targetAlias);
}
else
{
Boolean Changed;
err = FSUpdateAlias(NULL, (targetList + index), targetAlias, &Changed);
}
if (err == noErr)
{
HLock((Handle) targetAlias);
err = AEPutPtr( &targetListDesc,
(index + 1),
typeAlias,
*targetAlias,
GetHandleSize((Handle) targetAlias));
HUnlock((Handle) targetAlias);
}
}
}
/* add the file list to the apple event */
if( err == noErr )
{
err = AEPutParamDesc(&theAEvent, keyDirectObject, &targetListDesc);
}
if (err == noErr)
{
/* send the event to the Finder */
err = AESend(&theAEvent,
&theReply,
kAENoReply,
kAENormalPriority,
kAEDefaultTimeout,
NULL,
NULL);
}
/* clean up and leave */
if (targetAlias != NULL)
DisposeHandle((Handle) targetAlias);
AEDisposeDesc(&targetListDesc);
AEDisposeDesc(&theAEvent);
AEDisposeDesc(&fndrAddress);
AEDisposeDesc(&theReply);
return err;
}
#endif
LString LErrorCodeToString(uint32 ErrorCode)
{
const char *e = strerror(ErrorCode);
static char tmp[32];
if (!e)
{
sprintf_s(tmp, sizeof(tmp), "Error(%i)", ErrorCode);
e = tmp;
}
return e;
}
bool LExecute(const char *File, const char *Args, const char *Dir, LString *ErrorMsg)
{
bool Status = false;
if (File)
{
LUri uri(File);
if (uri.sProtocol)
{
CFStringRef s = CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)File, strlen(File), kCFStringEncodingUTF8, false);
CFURLRef u = CFURLCreateWithString(NULL, s, NULL);
Status = LSOpenCFURLRef(u, 0) == 0;
CFRelease(u);
CFRelease(s);
}
else
{
#if LGI_CARBON
FSRef r;
OSStatus e = FSPathMakeRef((UInt8*)File, &r, NULL);
char Path[MAX_PATH];
if (e)
{
// Check if this is an application
snprintf(Path, sizeof(Path), "/Applications/%s", File);
e = FSPathMakeRef((UInt8*)Path, &r, NULL);
if (!e)
File = Path;
}
if (e) printf("%s:%i - FSPathMakeRef failed with %i\n", _FL, (int)e);
else
#endif
{
// Is this an app bundle?
bool IsAppBundle = false;
auto Last = strrchr(File, '/');
if (Last)
{
auto Dot = strrchr(Last, '.');
IsAppBundle = Dot && !stricmp(Dot, ".app");
/* Ideally in OSX before 10.6 we'd convert to calling the executable in
the bundle rather the 'open' the app bundle, because open doesn't
support arguments before 10.6
if (ValidStr(Args))
{
GArray Ver;
LgiGetOs(Ver);
if (Ver.Length() > 1)
{
if (Ver[0] < 10 ||
Ver[1] < 6)
{
IsAppBundle = false;
}
}
}
*/
}
struct stat s;
int st = stat(File, &s);
if (IsAppBundle)
{
char cmd[512];
if (ValidStr((char*)Args))
snprintf(cmd, sizeof(cmd), "open -a \"%s\" %s", File, Args);
else
snprintf(cmd, sizeof(cmd), "open -a \"%s\"", File);
system(cmd);
}
else if (st == 0 &&
S_ISREG(s.st_mode) &&
(s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
{
// This is an executable file
if (!fork())
{
if (Dir)
chdir(Dir);
LArray a;
a.Add(File);
char *p;
while ((p = LTokStr(Args)))
{
a.Add(p);
}
a.Add(0);
char *env[] = {0};
execve(File, (char*const*)&a[0], env);
}
return true;
}
else
{
// Document
#if LGI_CARBON
e = FinderLaunch(1, &r);
if (e) printf("%s:%i - FinderLaunch faied with %i\n", _FL, (int)e);
else Status = true;
#elif LGI_COCOA
LString file = File;
auto url = [[NSURL alloc] initFileURLWithPath:file.NsStr()];
Status = [[NSWorkspace sharedWorkspace] openURL:url];
#endif
}
}
}
}
return Status;
}
bool LGetMimeTypeExtensions(const char *Mime, LArray &Ext)
{
size_t Start = Ext.Length();
#define HardCodeExtention(Mime, Ext1, Ext2) \
else if (!stricmp(Mime, Mime)) \
{ if (Ext1) Ext.Add(Ext1); \
if (Ext2) Ext.Add(Ext2); }
if (!Mime);
HardCodeExtention("text/calendar", "ics", (const char*)NULL)
HardCodeExtention("text/x-vcard", "vcf", (const char*)NULL)
HardCodeExtention("text/mbox", "mbx", "mbox");
return Ext.Length() > Start;
}
LString LCurrentUserName()
{
struct passwd *pw = getpwuid(geteuid());
if (pw)
return pw->pw_name;
return "";
}