diff --git a/Lgi.xml b/Lgi.xml --- a/Lgi.xml +++ b/Lgi.xml @@ -1,562 +1,563 @@ + + + + - - - - + Makefile.linux Makefile.win64 Makefile.macosx gcc 0 ./include ./private/common ./include ./private/common ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux include/lgi/win include/lgi/win /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` lgi-gtk3 lgi-gtk3 DynamicLibrary diff --git a/include/lgi/common/DocView.h b/include/lgi/common/DocView.h --- a/include/lgi/common/DocView.h +++ b/include/lgi/common/DocView.h @@ -1,543 +1,526 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief This is the base data and code for all the text controls (inc. HTML) #ifndef __GDOCVIEW_H #define __GDOCVIEW_H #include "lgi/common/Variant.h" #include "lgi/common/Notifications.h" #include "lgi/common/Thread.h" #include "lgi/common/Layout.h" // Word wrap enum LDocWrapType { /// No word wrapping TEXTED_WRAP_NONE = 0, /// Dynamically wrap line to editor width TEXTED_WRAP_REFLOW = 1, }; // Util macros /// Returns true if 'c' is whitespace #define IsWhiteSpace(c) ((c) < 126 && strchr(LDocView::WhiteSpace, c) != 0) /// Returns true if 'c' is a delimiter #define IsDelimiter(c) ((c) < 126 && strchr(LDocView::Delimiters, c) != 0) /// Returns true if 'c' is a letter or number #define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_') /// Returns true if 'c' is word boundry #define IsWordBoundry(c) (strchr(LDocView::WhiteSpace, c) || strchr(LDocView::Delimiters, c)) /// Returns true if 'c' is alphanumeric or a digit #define AlphaOrDigit(c) (IsDigit(c) || IsAlpha(c)) /// Returns true if 'c' is a valid URL character #define UrlChar(c) ( \ strchr(LDocView::UrlDelim, (c)) || \ AlphaOrDigit((c)) || \ ((c) >= 256) \ ) /// Returns true if 'c' is email address character #define EmailChar(c) (strchr("._-:+", (c)) || AlphaOrDigit((c))) LgiFunc char16 *ConvertToCrLf(char16 *Text); /// This class contains information about a link. /// \sa LDetectLinks struct LLinkInfo { ssize_t Start; ssize_t Len; bool Email; void Set(ssize_t start, ssize_t len, bool email) { Start = start; Len = len; Email = email; } }; // Call back class to handle viewer events class LDocView; /// An environment class to handle requests from the text view to the outside world. class LgiClass LDocumentEnv : public LThreadOwner { LArray Viewers; public: LDocumentEnv(LDocView *v = 0); virtual ~LDocumentEnv(); enum LoadType { LoadError, LoadNotImpl, LoadImmediate, LoadDeferred, }; struct #ifdef MAC LgiClass #endif LoadJob : public LThreadJob { enum PrefFormat { FmtNone, FmtStream, FmtSurface, FmtFilename, }; enum JobStatus { JobInit, JobOk, JobErr_Uri, JobErr_Path, JobErr_FileOpen, JobErr_GetUri, JobErr_NoCachedFile, JobErr_ImageFilter, JobErr_NoMem, }; // View data LDocumentEnv *Env; void *UserData; uint32_t UserUid; PrefFormat Pref; // Input data LAutoString Uri; LAutoString PostData; // Output data LAutoPtr Stream; LAutoPtr pDC; LString Filename; LString Error; JobStatus Status; LString MimeType, ContentId; LoadJob(LThreadTarget *o) : LThreadJob(o) { Env = NULL; UserUid = 0; UserData = NULL; Pref = FmtNone; Status = JobInit; } LStreamI *GetStream() { if (!Stream && Filename) { LFile *file = new LFile; if (file && file->Open(Filename, O_READ)) Stream.Reset(file); else DeleteObj(file); } return Stream; } }; LoadJob *NewJob() { return new LoadJob(this); } bool AttachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(!Viewers.HasItem(v)); Viewers.Add(v); Unlock(); return true; } bool DetachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(Viewers.HasItem(v)); Viewers.Delete(v); Unlock(); return true; } int NextUid(); /// Creating a context menu, usually when the user right clicks on the /// document. virtual bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) { return false; } /// Do something when the menu items created by LDocumentEnv::AppendItems /// are clicked. virtual bool OnMenu(LDocView *View, int Id, void *Context) { return false; } /// Asks the env to get some data linked from the document, e.g. a css file or an iframe source etc. /// If the GetContent implementation takes ownership of the job pointer then it should set 'j' to NULL. virtual LoadType GetContent(LoadJob *&j) { return LoadNotImpl; } /// After the env's thread loads the resource it calls this to pass it to the doc void OnDone(LAutoPtr j); /// Handle a click on URI virtual bool OnNavigate(LDocView *Parent, const char *Uri) { return false; } /// Handle a form post virtual bool OnPostForm(LDocView *Parent, const char *Uri, const char *Data) { return false; } /// Process dynamic content, returning a dynamically allocated string /// for the result of the executed script. Dynamic content is enclosed /// between <? and ?>. virtual char *OnDynamicContent(LDocView *Parent, const char *Code) { return 0; } /// Some script was received, the owner should compile it virtual bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { return false; } /// Some script needs to be executed, the owner should compile it virtual bool OnExecuteScript(LDocView *Parent, char *Script) { return false; } }; /// Default text view environment /// /// This class defines the default behavior of the environment, /// However you will need to instantiate this yourself and call /// SetEnv with your instance. i.e. it's not automatic. class LgiClass LDefaultDocumentEnv : public LDocumentEnv { public: LoadType GetContent(LoadJob *&j); bool OnNavigate(LDocView *Parent, const char *Uri); }; /// Find params class LDocFindReplaceParams { public: virtual ~LDocFindReplaceParams() {} }; /// TextView class is a base for all text controls class LgiClass LDocView : public LLayout, virtual public LDom { friend class LDocumentEnv; protected: - LDocumentEnv *Environment; + LDocumentEnv *Environment = NULL; LString Charset; public: // Static static const char *WhiteSpace; static const char *Delimiters; static const char *UrlDelim; /////////////////////////////////////////////////////////////////////// // Properties - #define _TvMenuProp(Type, Name) \ + #define _TvMenuProp(Type, Name, Default) \ protected: \ - Type Name; \ + Type Name = Default; \ public: \ virtual void Set##Name(Type i) { Name=i; } \ Type Get##Name() { return Name; } - _TvMenuProp(uint16, WrapAtCol) - _TvMenuProp(bool, UrlDetect) - _TvMenuProp(bool, ReadOnly) - _TvMenuProp(LDocWrapType, WrapType) - _TvMenuProp(uint8_t, TabSize) - _TvMenuProp(uint8_t, IndentSize) - _TvMenuProp(bool, HardTabs) - _TvMenuProp(bool, ShowWhiteSpace) - _TvMenuProp(bool, ObscurePassword) - _TvMenuProp(bool, CrLf) - _TvMenuProp(bool, AutoIndent) - _TvMenuProp(bool, FixedWidthFont) - _TvMenuProp(bool, LoadImages) - _TvMenuProp(bool, OverideDocCharset) + _TvMenuProp(uint16, WrapAtCol, 0) + _TvMenuProp(bool, UrlDetect, true) + _TvMenuProp(bool, ReadOnly, false) + _TvMenuProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW) + _TvMenuProp(uint8_t, TabSize, 4) + _TvMenuProp(uint8_t, IndentSize, 4) + _TvMenuProp(bool, HardTabs, true) + _TvMenuProp(bool, ShowWhiteSpace, false) + _TvMenuProp(bool, ObscurePassword, false) + _TvMenuProp(bool, CrLf, false) + _TvMenuProp(bool, AutoIndent, true) + _TvMenuProp(bool, FixedWidthFont, false) + _TvMenuProp(bool, LoadImages, false) + _TvMenuProp(bool, OverideDocCharset, false) // This UID is used to match data load events with their source document. // Sometimes data will arrive after the document that asked for it has // already been unloaded. So by assigned each document an UID we can check // the job UID against it and discard old data. - _TvMenuProp(int, DocumentUid) + _TvMenuProp(int, DocumentUid, 0) #undef _TvMenuProp virtual const char *GetCharset() { return Charset.Get() ? Charset.Get() : "utf-8"; } virtual void SetCharset(const char *s) { Charset = s; } virtual const char *GetMimeType() = 0; /////////////////////////////////////////////////////////////////////// // Object - LDocView(LDocumentEnv *e = 0) + LDocView(LDocumentEnv *e = NULL) { - WrapAtCol = 0; - UrlDetect = true; - ReadOnly = false; - WrapType = TEXTED_WRAP_REFLOW; - TabSize = 4; - IndentSize = 4; - HardTabs = true; - ShowWhiteSpace = false; - ObscurePassword = false; - CrLf = false; - AutoIndent = true; - FixedWidthFont = false; - LoadImages = false; - OverideDocCharset = false; - - Environment = 0; - SetEnv(e); } virtual ~LDocView() { SetEnv(0); } const char *GetClass() { return "LDocView"; } /// Open a file handler virtual bool Open(const char *Name, const char *Cs = 0) { return false; } /// Save a file handler virtual bool Save(const char *Name, const char *Cs = 0) { return false; } /////////////////////////////////////////////////////////////////////// /// Find window handler virtual bool DoFind() { return false; } /// Replace window handler virtual bool DoReplace() { return false; } virtual LDocFindReplaceParams *CreateFindReplaceParams() { return 0; } virtual void SetFindReplaceParams(LDocFindReplaceParams *Params) { } /////////////////////////////////////////////////////////////////////// /// Get the current environment virtual LDocumentEnv *GetEnv() { return Environment; } /// Set the current environment virtual void SetEnv(LDocumentEnv *e) { if (Environment) Environment->DetachView(this); Environment = e; if (Environment) Environment->AttachView(this); } /// When the env has loaded a resource it can pass it to the doc control via this method. /// It MUST be thread safe. Often an environment will call this function directly from /// it's worker thread. virtual void OnContent(LDocumentEnv::LoadJob *Res) {} /////////////////////////////////////////////////////////////////////// // State / Selection /// Set the cursor position, to select an area, move the cursor with Select=false /// then set the other end of the region with Select=true. virtual void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) {} /// Cursor=false means the other end of the selection if any. The cursor is alwasy /// at one end of the selection. virtual ssize_t GetCaret(bool Cursor = true) { return 0; } /// True if there is a selection virtual bool HasSelection() { return false; } /// Unselect all the text virtual void UnSelectAll() {} /// Select the word from index 'From' virtual void SelectWord(size_t From) {} /// Select all the text in the control virtual void SelectAll() {} /// Get the selection as a dynamicially allocated utf-8 string virtual char *GetSelection() { return 0; } /// Returns the character index at the x,y location virtual ssize_t IndexAt(int x, int y) { return 0; } /// Index=-1 returns the x,y of the cursor, Index >=0 returns the specified x,y virtual bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) { return false; } /// True if the document has changed virtual bool IsDirty() { return false; } /// Gets the number of lines of text virtual size_t GetLines() { return 0; } /// Gets the pixels required to display all the text virtual void GetTextExtent(int &x, int &y) {} /////////////////////////////////////////////////////////////////////// /// Cuts the selection from the document and puts it on the clipboard virtual bool Cut() { return false; } /// Copies the selection from the document to the clipboard virtual bool Copy() { return false; } /// Pastes the current contents of the clipboard into the document virtual bool Paste() { return false; } /////////////////////////////////////////////////////////////////////// /// Called when the user hits the escape key virtual void OnEscape(LKey &K) {} /// Called when the user hits the enter key virtual void OnEnter(LKey &k) {} /// Called when the user clicks a URL virtual void OnUrl(char *Url) {} /// Called to add styling to the document virtual void OnAddStyle(const char *MimeType, const char *Styles) {} /////////////////////////////////////////////////////////////////////// struct ContentMedia { LString Id; LString FileName; LString MimeType; LVariant Data; LAutoPtr Stream; bool Valid() { return MimeType.Get() != NULL && FileName.Get() != NULL && ( (Data.Type == GV_BINARY && Data.Value.Binary.Data != NULL) || (Stream.Get() != NULL) ); } }; /// Gets the document in format of a desired MIME type virtual bool GetFormattedContent ( /// [In] The desired mime type of the content const char *MimeType, /// [Out] The content in the specified mime type LString &Out, /// [Out/Optional] Any attached media files that the content references LArray *Media = NULL ) { return false; } }; /// Detects links in text, returning their location and type template bool LDetectLinks(LArray &Links, T *Text, ssize_t TextCharLen = -1) { if (!Text) return false; if (TextCharLen < 0) TextCharLen = Strlen(Text); T *End = Text + TextCharLen; static T Http[] = {'h', 't', 't', 'p', ':', '/', '/', 0 }; static T Https[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0}; for (int64 i=0; i= 7 && ( Strnicmp(Text+i, Http, 6) == 0 || Strnicmp(Text+i, Https, 7) == 0 ) ) { // find end T *s = Text + i; T *e = s + 6; for ( ; e < End && UrlChar(*e); e++) ; while ( e > s && ! ( IsAlpha(e[-1]) || IsDigit(e[-1]) || e[-1] == '/' ) ) e--; Links.New().Set(s - Text, e - s, false); i = e - Text; } break; } case '@': { // find start T *s = Text + (MAX(i, 1) - 1); for ( ; s > Text && EmailChar(*s); s--) ; if (s < Text + i) { if (!EmailChar(*s)) s++; bool FoundDot = false; T *Start = Text + i + 1; T *e = Start; for ( ; e < End && EmailChar(*e); e++) { if (*e == '.') FoundDot = true; } while (e > Start && e[-1] == '.') e--; if (FoundDot) { Links.New().Set(s - Text, e - s, true); i = e - Text; } } break; } } } return true; } #endif diff --git a/include/lgi/common/TextLog.h b/include/lgi/common/TextLog.h --- a/include/lgi/common/TextLog.h +++ b/include/lgi/common/TextLog.h @@ -1,171 +1,170 @@ /// \file /// \author Matthew Allen #ifndef _LTEXTLOG_H_ #define _LTEXTLOG_H_ #include "lgi/common/TextView3.h" #include "lgi/common/Net.h" template class LThreadSafeTextView : public TView, public LStream { protected: bool ProcessReturns; size_t Pos; LMutex Sem; LArray Txt; void ProcessTxt() { if (Txt.Length() == 0) return; LArray Local; if (Sem.LockWithTimeout(250, _FL)) { // Minimize time locked by moving the text to a local var Local.Swap(Txt); Sem.Unlock(); } else { TView::PostEvent(M_LOG_TEXT); // Try again later... return; } if (Local.Length()) { LString msg; msg.Printf("LTextLog::ProcessTxt(%" PRIu64 ")", (uint64)Txt.Length()); LProfile p(msg, 200); Add(Local.AddressOf(), Local.Length()); } } public: LThreadSafeTextView(int id) : TView(id, 0, 0, 2000, 1000), Sem("LThreadSafeTextView") { ProcessReturns = true; Pos = 0; TView::Sunken(true); TView::SetPourLargest(true); TView::SetUndoOn(false); TView::SetWrapType(TEXTED_WRAP_NONE); } void OnCreate() { TView::OnCreate(); // ProcessTxt(); - TView::SetPulse(250); } void OnPulse() { ProcessTxt(); } virtual void Add(char16 *w, ssize_t chars = -1) { ssize_t Len = chars >= 0 ? chars : StrlenW(w); bool AtEnd = TView::GetCaret() == TView::Size; if (ProcessReturns) { auto *s = w, *prev = w; auto Ins = [this](char16 *s, ssize_t len) { if (len > 0) { auto del = MIN(TView::Size - (ssize_t)Pos, len); if (del > 0) TView::Delete(Pos, del); TView::Insert(Pos, s, len); Pos += len; } }; for (s = w; chars >= 0 ? s < w + chars : *s; s++) { if (*s == '\r') { Ins(prev, s - prev); prev = s + 1; while (Pos > 0 && TView::Text[Pos-1] != '\n') Pos--; } else if (*s == '\n') { Pos = TView::Size; } } Ins(prev, s - prev); } else { TView::Insert(TView::Size, w, Len); } TView::Invalidate(); if (AtEnd) TView::SetCaret(TView::Size, false); } int64 SetSize(int64 s) { TView::Name(0); return 0; } bool Write(const LString &s) { return Write(s.Get(), s.Length()) == s.Length(); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { LAutoWString w(Utf8ToWide((char*)Buffer, Size)); if (!w) return 0; size_t OldLen = 0; if (Sem.Lock(_FL)) { OldLen = Txt.Length(); Txt.Add(w.Get(), Strlen(w.Get())); Sem.Unlock(); } if (!OldLen #if LGI_VIEW_HANDLE && TView::Handle() #endif ) { TView::PostEvent(M_LOG_TEXT); } return Size; } LMessage::Result OnEvent(LMessage *m) { if (m->Msg() == M_LOG_TEXT) { // ProcessTxt(); } return TView::OnEvent(m); } }; typedef LThreadSafeTextView LTextLog; #ifdef _GTEXTVIEW4_H typedef LThreadSafeTextView LTextLog4; #endif #endif diff --git a/include/lgi/common/TextView3.h b/include/lgi/common/TextView3.h --- a/include/lgi/common/TextView3.h +++ b/include/lgi/common/TextView3.h @@ -1,437 +1,437 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef __GTEXTVIEW3_H #define __GTEXTVIEW3_H #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Css.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/FindReplaceDlg.h" // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str extern char Delimiters[]; class LTextView3; /// Unicode text editor control. class LgiClass LTextView3 : public LDocView, public ResObject, public LDragDropTarget { friend struct LTextView3Undo; friend bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum Messages { M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421, M_TEXTVIEW_FIND, M_TEXTVIEW_REPLACE, M_TEXT_POUR_CONTINUE, }; enum StyleOwners { STYLE_NONE, STYLE_IDE, STYLE_SPELLING, STYLE_FIND_MATCHES, STYLE_ADDRESS, STYLE_URL, }; class LStyle { protected: void RefreshLayout(size_t Start, ssize_t Len); public: /// The view the style is for LTextView3 *View; /// When you write several bits of code to do styling assign them /// different owner id's so that they can manage the lifespan of their /// own styles. LTextView3::PourStyle is owner '0', anything else it /// will leave alone. StyleOwners Owner; /// The start index into the text buffer of the region to style. ssize_t Start; /// The length of the styled region ssize_t Len; /// The font to draw the styled text in LFont *Font; /// The colour to draw with. If transparent, then the default /// line colour is used. LColour Fore, Back; /// Cursor LCursor Cursor; /// Optional extra decor not supported by the fonts LCss::TextDecorType Decor; /// Colour for the optional decor. LColour DecorColour; /// Application base data LVariant Data; LStyle(StyleOwners owner = STYLE_NONE) { Owner = owner; View = NULL; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; } LStyle(const LStyle &s) { Owner = s.Owner; View = s.View; Font = s.Font; Start = s.Start; Len = s.Len; Decor = s.Decor; DecorColour = s.DecorColour; Fore = s.Fore; Back = s.Back; Data = s.Data; Cursor = s.Cursor; } LStyle &Construct(LTextView3 *view, StyleOwners owner) { View = view; Owner = owner; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; return *this; } void Empty() { Start = -1; Len = 0; } bool Valid() { return Start >= 0 && Len > 0; } size_t End() const { return Start + Len; } /// \returns true if style is the same bool operator ==(const LStyle &s) { return Owner == s.Owner && Start == s.Start && Len == s.Len && Fore == s.Fore && Back == s.Back && Decor == s.Decor; } /// Returns true if this style overlaps the position of 's' bool Overlap(LStyle &s) { return Overlap(s.Start, s.Len); } /// Returns true if this style overlaps the position of 's' bool Overlap(ssize_t sStart, ssize_t sLen) { if (sStart + sLen - 1 < Start || sStart >= Start + Len) return false; return true; } void Union(const LStyle &s) { if (Start < 0) { Start = s.Start; Len = s.Len; } else { Start = MIN(Start, s.Start); Len = MAX(End(), s.End()) - Start; } } }; friend class LTextView3::LStyle; protected: // Internal classes enum GTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; class LTextLine : public LRange { public: LRect r; // Screen location LColour c; // Colour of line... transparent = default colour LColour Back; // Background colour or transparent LTextLine() { Start = -1; Len = 0; r.ZOff(-1, -1); } virtual ~LTextLine() {} bool Overlap(ssize_t i) { return i>=Start && i<=Start+Len; } size_t CalcLen(char16 *Text) { char16 *c = Text + Start, *e = c; while (*e && *e != '\n') e++; return Len = e - c; } }; class LTextView3Private *d; friend class LTextView3Private; // Options - bool Dirty; - bool CanScrollX; + bool Dirty = false; + bool CanScrollX = false; // Display - LFont *Font; - LFont *Bold; // Bold variant of 'Font' - LFont *Underline; // Underline variant of 'Font' + LFont *Font = NULL; + LFont *Bold = NULL; // Bold variant of 'Font' + LFont *Underline = NULL; // Underline variant of 'Font' - LFont *FixedFont; - int LineY; - ssize_t SelStart, SelEnd; - int DocOffset; - int MaxX; - bool Blink; - uint64 BlinkTs; - int ScrollX; + LFont *FixedFont = NULL; + int LineY = 1; + ssize_t SelStart = -1, SelEnd = -1; + int DocOffset = 0; + int MaxX = 0; + bool Blink = true; + uint64 BlinkTs = 0; + int ScrollX = 0; LRect CursorPos; /// true if the text pour process is still ongoing - bool PourEnabled; // True if pouring the text happens on edit. Turn off if doing lots + bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots // of related edits at the same time. And then manually pour once // finished. - bool PartialPour; // True if the pour is happening in the background. It's not threaded + bool PartialPour = false; // True if the pour is happening in the background. It's not threaded // but taking place in the GUI thread via timer. - size_t PartialPourLines; // Partial pour max lines, if we restart this tracks the max we saw... - bool AdjustStylePos; // Insert/Delete moved styles automatically to match (default: true) + size_t PartialPourLines = 0;// Partial pour max lines, if we restart this tracks the max we saw... + bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true) List Line; LUnrolledList Style; // sorted in 'Start' order typedef LUnrolledList::Iter StyleIter; // For ::Name(...) - char *TextCache; + char *TextCache = NULL; // Data char16 *Text; ssize_t Cursor; ssize_t Size; ssize_t Alloc; // Undo stuff - bool UndoOn; + bool UndoOn = true; LUndo UndoQue; - struct LTextView3Undo *UndoCur; + struct LTextView3Undo *UndoCur = NULL; // private methods List::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0); LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { return *GetTextLineIt(Offset, Index); } ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where); int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin); bool ScrollToOffset(size_t Off); int ScrollYLine(); int ScrollYPixel(); LRect DocToScreen(LRect r); ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); void InternalPulse(); // styles bool InsertStyle(LAutoPtr s); LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1); LStyle *HitStyle(ssize_t i); int GetColumn(); int SpaceDepth(char16 *Start, char16 *End); int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false); // Overridables virtual void PourText(size_t Start, ssize_t Length); virtual void PourStyle(size_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false); #ifdef _DEBUG // debug uint64 _PourTime; uint64 _StyleTime; uint64 _PaintTime; #endif void LogLines(); bool ValidateLines(bool CheckBox = false); public: // Construction LTextView3( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = NULL); ~LTextView3(); const char *GetClass() override { return "LTextView3"; } // Data const char *Name() override; bool Name(const char *s) override; const char16 *NameW() override; bool NameW(const char16 *s) override; int64 Value() override; void Value(int64 i) override; const char *GetMimeType() override { return "text/plain"; } size_t Length() { return Size; } LString operator[](ssize_t LineIdx); const char16 *TextAtLine(size_t Index); ssize_t HitText(int x, int y, bool Nearest); void DeleteSelection(char16 **Cut = 0); // Font LFont *GetFont() override; LFont *GetBold(); void SetFont(LFont *f, bool OwnIt = false) override; void SetFixedWidthFont(bool i) override; // Options void SetTabSize(uint8_t i) override; void SetBorder(int b); void SetReadOnly(bool i) override; void SetCrLf(bool crlf) override; /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW void SetWrapType(LDocWrapType i) override; // State / Selection ssize_t GetCaret(bool Cursor = true) override; virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override; ssize_t IndexAt(int x, int y) override; bool IsDirty() override { return Dirty; } void IsDirty(bool d) { Dirty = d; } bool HasSelection() override; void UnSelectAll() override; void SelectWord(size_t From) override; void SelectAll() override; bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override; size_t GetLines() override; void GetTextExtent(int &x, int &y) override; char *GetSelection() override; LRange GetSelectionRange(); // File IO bool Open(const char *Name, const char *Cs = NULL) override; bool Save(const char *Name, const char *Cs = NULL) override; const char *GetLastError(); // Clipboard IO bool Cut() override; bool Copy() override; bool Paste() override; // Undo/Redo void Undo(); void Redo(); bool GetUndoOn() { return UndoOn; } void SetUndoOn(bool b) { UndoOn = b; } // Action UI virtual bool DoGoto(); virtual bool DoCase(bool Upper); virtual bool DoFind() override; virtual bool DoFindNext(); virtual bool DoReplace() override; // Action Processing bool ClearDirty(bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); ssize_t GetLine(); void SetLine(int Line, bool select = false); LDocFindReplaceParams *CreateFindReplaceParams() override; void SetFindReplaceParams(LDocFindReplaceParams *Params) override; // Object Events virtual bool OnFind( const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); virtual bool OnReplace( const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange() override; void OnCreate() override; void OnEscape(LKey &K) override; bool OnMouseWheel(double Lines) override; // Window Events void OnFocus(bool f) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPulse() override; int OnHitTest(int x, int y) override; bool OnLayout(LViewLayoutInfo &Inf) override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; LCursor GetCursor(int x, int y) override; // Virtuals virtual bool Insert(size_t At, const char16 *Data, ssize_t Len); virtual bool Delete(size_t At, ssize_t Len); virtual void OnEnter(LKey &k) override; virtual void OnUrl(char *Url) override; virtual void DoContextMenu(LMouse &m); virtual bool OnStyleClick(LStyle *style, LMouse *m); virtual bool OnStyleMenu(LStyle *style, LSubMenu *m); virtual void OnStyleMenuClick(LStyle *style, int i); }; #endif diff --git a/private/common/ViewPriv.h b/private/common/ViewPriv.h --- a/private/common/ViewPriv.h +++ b/private/common/ViewPriv.h @@ -1,234 +1,232 @@ // Private LView definations #pragma once #if WINNATIVE #if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x501 #undef _WIN32_WINNT #define _WIN32_WINNT 0x501 #endif #include "commctrl.h" #include "Uxtheme.h" #define GViewFlags d->WndStyle #else #define GViewFlags WndFlags #endif #if defined(__GTK_H__) struct LCaptureThread : public LThread, public LCancel { int view = -1; + LString name; public: constexpr static int EventMs = 150; LCaptureThread(LView *v); ~LCaptureThread(); int Main(); }; #elif defined(MAC) extern OsThread LgiThreadInPaint; #elif defined(HAIKU) #include #endif #define PAINT_VIRTUAL_CHILDREN 1 extern bool In_SetWindowPos; extern LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing = false, bool Debug = false); #ifdef __GTK_H__ extern LPoint GtkAbsPos(Gtk::GtkWidget *w); extern LRect GtkGetPos(Gtk::GtkWidget *w); #endif #if !WINNATIVE #include "lgi/common/ThreadEvent.h" -class LPulseThread : public LThread +class LPulseThread : public LThread, public LCancel { - LView *View; - int Length; + LView *View = NULL; + int Length = 0; LThreadEvent Event; LString MakeName(LView *v, const char *Type) { LString s; s.Printf("LPulseThread.%s.%s", v->GetClass(), Type); return s; } public: - bool Loop; + static int PulseThreadCount; LPulseThread(LView *view, int len) : View(view), LThread(MakeName(view, "Thread")), Event(MakeName(view, "Event")) { LAssert(View); + Length = len; + PulseThreadCount++; - Loop = true; - Length = len; + printf("PulseThread=%i, %s, %i\n", PulseThreadCount, View->GetClass(), Length); Run(); } ~LPulseThread() { - Loop = false; View = NULL; + Cancel(); Event.Signal(); - - while (!IsExited()) - LSleep(0); + WaitForExit(); + PulseThreadCount--; } int Main() { - while (Loop && LAppInst) + while (!IsCancelled() && LAppInst) { auto s = Event.Wait(Length); - if (!Loop || s == LThreadEvent::WaitError) + if (IsCancelled() || s == LThreadEvent::WaitError) break; - if (View) - { - if (!View->PostEvent(M_PULSE)) - Loop = false; - } + if (View && !View->PostEvent(M_PULSE)) + Cancel(); } return 0; } }; #endif enum LViewFontType { /// The LView has a pointer to an externally owned font. GV_FontPtr, /// The LView owns the font object, and must free it. GV_FontOwned, /// The LApp's font cache owns the object. In this case, /// calling GetCssStyle on the LView will invalidate the /// font ptr causing it to be re-calculated. GV_FontCached, }; class LViewPrivate { public: // General LView *View = NULL; // Owning view LDragDropSource *DropSource = NULL; LDragDropTarget *DropTarget = NULL; bool IsThemed = false; int CtrlId = -1; int WantsPulse = -1; // Hierarchy LViewI *ParentI = NULL; LView *Parent = NULL; LViewI *Notify = NULL; // Size LPoint MinimumSize; // Font LFont *Font = NULL; LViewFontType FontOwnType = GV_FontPtr; // Style LAutoPtr Css; bool CssDirty = false; // This is set when 'Styles' changes, the next call to GetCss(...) parses // the styles into the 'Css' object. LString Styles; // Somewhat temporary object to store unparsed styles particular to // this view until runtime, where the view heirarchy is known. LString::Array Classes; // Event dispatch handle int SinkHnd = -1; // OS Specific #if WINNATIVE static OsView hPrevCapture; int WndStyle = 0; // Windows hWnd Style int WndExStyle = 0; // Windows hWnd ExStyle int WndDlgCode = 0; // Windows Dialog Code (WM_GETDLGCODE) LString WndClass; UINT_PTR TimerId = 0; HTHEME hTheme = NULL; #else // Cursor LPulseThread *PulseThread = NULL; LView *Popup = NULL; bool TabStop = false; bool WantsFocus = false; #if defined __GTK_H__ bool InPaint = false; bool GotOnCreate = false; #elif defined(MAC) && !defined(LGI_COCOA) static HIObjectClassRef BaseClass; #endif #endif #if defined(__GTK_H__) - LCaptureThread *CaptureThread = NULL; + static LCaptureThread *CaptureThread; LMouse PrevMouse; #elif defined(MAC) #ifdef LGI_COCOA LString ClassName; bool AttachEvent; #elif defined LGI_CARBON EventHandlerRef DndHandler; LAutoString AcceptedDropFormat; #endif #elif defined(LGI_SDL) SDL_TimerID PulseId; int PulseLength = -1; #elif defined(HAIKU) BView *Hnd = NULL; #endif LViewPrivate(LView *view); ~LViewPrivate(); LView *GetParent() { if (Parent) return Parent; if (ParentI) return ParentI->GetGView(); return 0; } }; diff --git a/src/common/Lgi/ToolTip.cpp b/src/common/Lgi/ToolTip.cpp --- a/src/common/Lgi/ToolTip.cpp +++ b/src/common/Lgi/ToolTip.cpp @@ -1,362 +1,362 @@ #define _WIN32_WINNT 0x500 #include "lgi/common/Lgi.h" #include "lgi/common/ToolTip.h" #if defined(WIN32) && !defined(__GTK_H__) #include #elif defined(LGI_CARBON) #include #endif #if LGI_COCOA || defined(__GTK_H__) #define LGI_NATIVE_TIPS 1 #endif #define DEBUG_TOOLTIPS 0 #if DEBUG_TOOLTIPS #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif #if LGI_NATIVE_TIPS #include "lgi/common/DisplayString.h" #include "lgi/common/Popup.h" class NativeTip : public LPopup { LAutoPtr s; public: static LArray All; static NativeTip *PulseRunning; static LPoint Padding; int Id; LRect Watch; const char *GetClass() { return "NativeTip"; } NativeTip(int id, LView *p) : LPopup(p) { All.Add(this); Id = id; Owner = p; ClearFlag(WndFlags, GWF_VISIBLE); Watch.ZOff(-1, -1); } ~NativeTip() { All.Delete(this); if (PulseRunning == this) { PulseRunning = NULL; if (All.Length() > 0) { PulseRunning = All[0]; PulseRunning->SetPulse(300); } } } void OnCreate() { LPopup::OnCreate(); if (!PulseRunning) { PulseRunning = this; - SetPulse(300); + SetPulse(1000); } } void OnPulse() { // Check mouse position... for (unsigned i=0; iOwner) { auto *Wnd = t->Owner->GetWindow(); bool Active = Wnd ? Wnd->IsActive() : false; if (t->Owner->GetMouse(m)) { m.Target = t->Owner; LRect w = t->Watch; bool Vis = w.Overlap(m.x, m.y); LOG("Tip %s, in=%i, act=%i\n", t->LView::Name(), Vis, Active); Vis = Vis && Active; if (Vis ^ t->Visible()) { LRect r = t->GetPos(); LPoint pt(w.x1, w.y2); #ifdef __GTK_H__ pt.y += 8; #endif t->Owner->PointToScreen(pt); LOG("Vis(%i): r=%s pt=%i,%i->%i,%i\n", Vis, r.GetStr(), w.x1, w.y2, pt.x, pt.y); r.Offset(pt.x - r.x1, pt.y - r.y1); t->SetPos(r); t->Visible(Vis); } } } else { All.DeleteAt(i--); } } } bool Name(const char *n) { bool Status = LView::Name(n); if (s.Reset(new LDisplayString(LSysFont, LView::Name()))) { LRect r = GetPos(); r.SetSize(s->X()+NativeTip::Padding.x, s->Y()+NativeTip::Padding.y); SetPos(r); } return Status; } void OnPaint(LSurface *pDC) { LRect c = GetClient(); auto b = L_TOOL_TIP; // Draw border #ifdef MAC pDC->Colour(L_LIGHT); #else pDC->Colour(L_BLACK); #endif pDC->Box(&c); c.Inset(1, 1); // Draw text interior LSysFont->Colour(L_TEXT, b); LSysFont->Transparent(false); if (s) { s->Draw(pDC, c.x1+((c.X()-s->X())>>1), c.y1+((c.Y()-s->Y())>>1), &c); } else { pDC->Colour(b); pDC->Rectangle(&c); } } bool IsOverParent(int x, int y) { // Implement this code to return true if the x, y coordinate passed in is over the // window 'Parent'. It must return false if another window obsures the location given // be 'x' and 'y'. return false; } }; #ifdef MAC LPoint NativeTip::Padding(8, 4); #else LPoint NativeTip::Padding(4, 2); #endif NativeTip *NativeTip::PulseRunning = NULL; LArray NativeTip::All; #endif class LToolTipPrivate { public: int NextUid; #if LGI_NATIVE_TIPS LView *Parent; LHashTbl, NativeTip*> Tips; #elif defined(LGI_CARBON) HMHelpContentRec Tag; #endif LToolTipPrivate() { NextUid = 1; #if LGI_NATIVE_TIPS Parent = NULL; #endif } ~LToolTipPrivate() { #if LGI_NATIVE_TIPS for (auto t : Tips) { LOG("Deleting tip %p\n", t.value); delete t.value; } #endif } }; LToolTip::LToolTip() : LView(NULL) { d = new LToolTipPrivate; } LToolTip::~LToolTip() { DeleteObj(d); } int LToolTip::NewTip(const char *Name, LRect &Pos) { int Status = 0; LOG("%s:%i NewTip(%s,%s)\n", _FL, Name, Pos.GetStr()); #if LGI_NATIVE_TIPS if (ValidStr(Name) && d->Parent) { NativeTip *t = new NativeTip(d->NextUid++, d->Parent); if (t) { t->Watch = Pos; t->Name(Name); // This is a hack to get it to create a window... t->Visible(true); // But not show it yet... duh... t->Visible(false); d->Tips.Add(t->Id, t); Status = t->Id; } else LOG("Error: Alloc failed."); } else LOG("Error: Invalid params."); #elif LGI_CARBON #ifdef __MACHELP__ HMSetHelpTagsDisplayed(true); #else #error "__MACHELP__ not defined" #endif if (Name) { d->Tag.version = kMacHelpVersion; d->Tag.tagSide = kHMDefaultSide; d->Tag.content[kHMMinimumContentIndex].contentType = kHMCFStringLocalizedContent; d->Tag.content[kHMMinimumContentIndex].u.tagCFString = CFStringCreateWithCString(NULL, Name, kCFStringEncodingUTF8); d->Tag.absHotRect = Pos; } #elif WINNATIVE if (_View && Name && GetParent()) { TOOLINFOW ti; ZeroObj(ti); ti.cbSize = sizeof(ti); ti.uFlags = TTF_SUBCLASS; ti.hwnd = GetParent()->Handle(); ti.rect = Pos; ti.lpszText = Utf8ToWide(Name); ti.uId = Status = d->NextUid++; auto Result = SendMessage(_View, TTM_ADDTOOLW, 0, (LPARAM) &ti); DeleteArray(ti.lpszText); } #endif return Status; } void LToolTip::DeleteTip(int Id) { #if LGI_NATIVE_TIPS auto t = d->Tips.Find(Id); LOG("%s:%i DeleteTip(%i)=%p\n", _FL, Id, t); if (t) { d->Tips.Delete(Id); delete t; } #elif WINNATIVE if (GetParent()) { TOOLINFOW ti; ZeroObj(ti); ti.cbSize = sizeof(ti); ti.hwnd = GetParent()->Handle(); ti.uId = Id; SendMessage(_View, TTM_DELTOOL, 0, (LPARAM) &ti); LOG("DeleteTip: Tip %i.\n", Id); } else LOG("Error: No parent?\n"); #endif } bool LToolTip::Attach(LViewI *p) { #if LGI_NATIVE_TIPS d->Parent = p->GetGView(); return false; #elif WINNATIVE if (!p) return false; if (!_View) { SetParent(p); _View = CreateWindowEx( NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // p->Handle(), NULL, NULL, NULL); } if (!_View) return false; SetWindowLongPtr( _View, GWLP_USERDATA, (LONG_PTR)(LViewI*)this); SetWindowLong( _View, GWL_LGI_MAGIC, LGI_GViewMagic); SetWindowPos( _View, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); #endif return true; } diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp --- a/src/common/Lgi/ViewCommon.cpp +++ b/src/common/Lgi/ViewCommon.cpp @@ -1,2535 +1,2539 @@ /// \file /// \author Matthew Allen #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/LgiRes.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Popup.h" #include "lgi/common/CssTools.h" #include "ViewPriv.h" #if 0 #define DEBUG_CAPTURE(...) printf(__VA_ARGS__) #else #define DEBUG_CAPTURE(...) #endif +#if !WINNATIVE +int LPulseThread::PulseThreadCount = 0; +#endif + ////////////////////////////////////////////////////////////////////////////////////// // Helper LPoint lgi_view_offset(LViewI *v, bool Debug = false) { LPoint Offset; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) break; LRect pos = p->GetPos(); LRect cli = p->GetClient(false); if (Debug) { const char *cls = p->GetClass(); LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n", cls, pos.x1, pos.y1, Offset.x + pos.x1, Offset.y + pos.y1, cli.GetStr()); } Offset.x += pos.x1 + cli.x1; Offset.y += pos.y1 + cli.y1; } return Offset; } LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug) { static LMouse Temp; Temp = Info; if (Wnd) { if (Debug #if 0 || Capturing #endif ) LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n", Info.Target?Info.Target->GetClass():"", Wnd?Wnd->GetClass():"", Info.x, Info.y); if (Temp.Target != Wnd) { if (Temp.Target) { auto *TargetWnd = Temp.Target->GetWindow(); auto *WndWnd = Wnd->GetWindow(); if (TargetWnd == WndWnd) { LPoint TargetOff = lgi_view_offset(Temp.Target, Debug); if (Debug) LgiTrace(" WndOffset:\n"); LPoint WndOffset = lgi_view_offset(Wnd, Debug); Temp.x += TargetOff.x - WndOffset.x; Temp.y += TargetOff.y - WndOffset.y; #if 0 LRect c = Wnd->GetClient(false); Temp.x -= c.x1; Temp.y -= c.y1; if (Debug) LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1); #endif Temp.Target = Wnd; } } else { LPoint Offset; Temp.Target = Wnd; if (Wnd->WindowVirtualOffset(&Offset)) { LRect c = Wnd->GetClient(false); Temp.x -= Offset.x + c.x1; Temp.y -= Offset.y + c.y1; } } } } LAssert(Temp.Target != NULL); return Temp; } ////////////////////////////////////////////////////////////////////////////////////// // LView class methods LViewI *LView::_Capturing = 0; LViewI *LView::_Over = 0; #if LGI_VIEW_HASH struct ViewTbl : public LMutex { typedef LHashTbl, int> T; private: T Map; public: ViewTbl() : Map(2000), LMutex("ViewTbl") { } T *Lock() { if (!LMutex::Lock(_FL)) return NULL; return ⤅ } } ViewTblInst; bool LView::LockHandler(LViewI *v, LView::LockOp Op) { ViewTbl::T *m = ViewTblInst.Lock(); if (!m) return false; int Ref = m->Find(v); bool Status = false; switch (Op) { case OpCreate: { if (Ref == 0) Status = m->Add(v, 1); else LAssert(!"Already exists?"); break; } case OpDelete: { if (Ref == 1) Status = m->Delete(v); else LAssert(!"Either locked or missing."); break; } case OpExists: { Status = Ref > 0; break; } } ViewTblInst.Unlock(); return Status; } #endif LView::LView(OsView view) { #ifdef _DEBUG _Debug = false; #endif d = new LViewPrivate(this); #ifdef LGI_SDL _View = this; #elif LGI_VIEW_HANDLE && !defined(HAIKU) _View = view; #endif _Window = 0; _Lock = 0; _InLock = 0; _BorderSize = 0; _IsToolBar = false; Pos.ZOff(-1, -1); WndFlags = GWF_VISIBLE; #ifndef LGI_VIEW_HASH #error "LGI_VIEW_HASH needs to be defined" #elif LGI_VIEW_HASH LockHandler(this, OpCreate); // printf("Adding %p to hash\n", (LViewI*)this); #endif } LView::~LView() { if (d->SinkHnd >= 0) { LEventSinkMap::Dispatch.RemoveSink(this); d->SinkHnd = -1; } #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif for (unsigned i=0; iOwner == this) { // printf("%s:%i - ~%s setting %s->Owner to NULL\n", _FL, GetClass(), pu->GetClass()); pu->Owner = NULL; } } _Delete(); DeleteObj(d); } int LView::AddDispatch() { if (d->SinkHnd < 0) d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this); return d->SinkHnd; } LString LView::CssStyles(const char *Set) { if (Set) { d->Styles = Set; d->CssDirty = true; } return d->Styles; } LString::Array *LView::CssClasses() { return &d->Classes; } LArray LView::IterateViews() { LArray a; for (auto c: Children) a.Add(c); return a; } bool LView::AddView(LViewI *v, int Where) { LAssert(!Children.HasItem(v)); bool Add = Children.Insert(v, Where); if (Add) { LView *gv = v->GetGView(); if (gv && gv->_Window != _Window) { LAssert(!_InLock); gv->_Window = _Window; } v->SetParent(this); v->OnAttach(); OnChildrenChanged(v, true); } return Add; } bool LView::DelView(LViewI *v) { bool Has = Children.HasItem(v); bool b = Children.Delete(v); if (Has) OnChildrenChanged(v, false); Has = Children.HasItem(v); LAssert(!Has); return b; } bool LView::HasView(LViewI *v) { return Children.HasItem(v); } OsWindow LView::WindowHandle() { auto *w = GetWindow(); return (w) ? w->WindowHandle() : (OsWindow)NULL; } LWindow *LView::GetWindow() { if (!_Window) { // Walk up parent list and find someone who has a window auto *w = d->GetParent(); for (; w; w = w->d ? w->d->GetParent() : NULL) { if (w->_Window) { LAssert(!_InLock); _Window = w->_Window; break; } } } return dynamic_cast(_Window); } bool LView::Lock(const char *file, int line, int TimeOut) { if (!_Window) GetWindow(); _InLock++; // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); if (_Window && _Window->_Lock) { if (TimeOut < 0) { return _Window->_Lock->Lock(file, line); } else { return _Window->_Lock->LockWithTimeout(TimeOut, file, line); } } return true; } void LView::Unlock() { #ifdef HAIKU if (!d || !d->Hnd) { printf("%s:%i - Unlock() error, no hnd.\n", _FL); return; } if (!d->Hnd->Parent()) { // printf("%s:%p - Unlock() no parent.\n", GetClass(), this); return; } if (_InLock > 0) { // printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock); d->Hnd->UnlockLooper(); _InLock--; // printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock); } else { printf("%s:%i - Unlock() without lock.\n", _FL); } #else if (_Window && _Window->_Lock) { _Window->_Lock->Unlock(); } _InLock--; // LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock); #endif } void LView::OnMouseClick(LMouse &m) { } void LView::OnMouseEnter(LMouse &m) { } void LView::OnMouseExit(LMouse &m) { } void LView::OnMouseMove(LMouse &m) { } bool LView::OnMouseWheel(double Lines) { return false; } bool LView::OnKey(LKey &k) { return false; } void LView::OnAttach() { List::I it = Children.begin(); for (LViewI *v = *it; v; v = *++it) { if (!v->GetParent()) v->SetParent(this); } #if 0 // defined __GTK_H__ if (_View && !DropTarget()) { // If one of our parents is drop capable we need to set a dest here LViewI *p; for (p = GetParent(); p; p = p->GetParent()) { if (p->DropTarget()) { break; } } if (p) { Gtk::gtk_drag_dest_set( _View, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); // printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass()); } else { Gtk::gtk_drag_dest_unset(_View); // printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass()); } } #endif } void LView::OnCreate() { } void LView::OnDestroy() { } void LView::OnFocus(bool f) { // printf("%s::OnFocus(%i)\n", GetClass(), f); } void LView::OnPulse() { } void LView::OnPosChange() { } bool LView::OnRequestClose(bool OsShuttingDown) { return true; } int LView::OnHitTest(int x, int y) { return -1; } void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { } void LView::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } int LView::OnNotify(LViewI *Ctrl, LNotification Data) { if (!Ctrl) return 0; if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate) { // Default activation is to focus the current control. Focus(true); } else if (d && d->Parent) { // default behaviour is just to pass the // notification up to the parent // FIXME: eventually we need to call the 'LNotification' parent fn... return d->Parent->OnNotify(Ctrl, Data); } return 0; } int LView::OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void LView::OnNcPaint(LSurface *pDC, LRect &r) { int Border = Sunken() || Raised() ? _BorderSize : 0; if (Border == 2) { LEdge e; if (Sunken()) e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge; else e = DefaultRaisedEdge; #if WINNATIVE if (d->IsThemed) DrawThemeBorder(pDC, r); else #endif LWideBorder(pDC, r, e); } else if (Border == 1) { LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); } } #if LGI_COCOA || defined(__GTK_H__) /* uint64 nPaint = 0; uint64 PaintTime = 0; */ void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { /* uint64 StartTs = Update ? LCurrentTime() : 0; d->InPaint = true; */ // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif // Non-Client drawing LRect r; if (Offset) { r = Pos; r.Offset(Offset); } else { r = GetClient().ZeroTranslate(); } pDC->SetClient(&r); LRect zr1 = r.ZeroTranslate(), zr2 = zr1; OnNcPaint(pDC, zr1); pDC->SetClient(NULL); if (zr2 != zr1) { r.x1 -= zr2.x1 - zr1.x1; r.y1 -= zr2.y1 - zr1.y1; r.x2 -= zr2.x2 - zr1.x2; r.y2 -= zr2.y2 - zr1.y2; } LPoint o(r.x1, r.y1); // Origin of client // Paint this view's contents... pDC->SetClient(&r); #if 0 if (_Debug) { #if defined(__GTK_H__) Gtk::cairo_matrix_t matrix; cairo_get_matrix(pDC->Handle(), &matrix); double ex[4]; cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", GetClass(), r.GetStr(), ex[0], ex[1], ex[2], ex[3], matrix.x0, matrix.y0); #elif LGI_COCOA auto Ctx = pDC->Handle(); CGAffineTransform t = CGContextGetCTM(Ctx); LRect cr = CGContextGetClipBoundingBox(Ctx); printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", GetClass(), GetPos().GetStr(), t.a, t.b, t.c, t.d, t.tx, t.ty, cr.GetStr(), r.GetStr()); #endif } #endif OnPaint(pDC); pDC->SetClient(NULL); // Paint all the children... for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { if (!w->Pos.Valid()) continue; #if 0 if (w->_Debug) LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); #endif w->_Paint(pDC, &o); } } } #else void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { Local.Reset(new LScreenDC(this)); pDC = Local; } if (!pDC) { printf("%s:%i - No context to draw in.\n", _FL); return; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif bool HasClient = false; LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; LPoint o; if (Offset) o = *Offset; #if WINNATIVE if (!_View) #endif { // Non-Client drawing Client = r; OnNcPaint(pDC, Client); HasClient = GetParent() && (Client != r); if (HasClient) { Client.Offset(o.x, o.y); pDC->SetClient(&Client); } } r.Offset(o.x, o.y); // Paint this view's contents if (Update) { LRect OldClip = pDC->ClipRgn(); pDC->ClipRgn(Update); OnPaint(pDC); pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); } else { OnPaint(pDC); } #if PAINT_VIRTUAL_CHILDREN // Paint any virtual children for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { #if LGI_VIEW_HANDLE if (!w->Handle()) #endif { LRect p = w->GetPos(); p.Offset(o.x, o.y); if (HasClient) p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); LPoint co(p.x1, p.y1); // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); pDC->SetClient(&p); w->_Paint(pDC, &co); pDC->SetClient(NULL); } } } #endif if (HasClient) pDC->SetClient(0); } #endif LViewI *LView::GetParent() { ThreadCheck(); return d ? d->Parent : NULL; } void LView::SetParent(LViewI *p) { ThreadCheck(); d->Parent = p ? p->GetGView() : NULL; d->ParentI = p; } void LView::SendNotify(LNotification note) { LViewI *n = d->Notify ? d->Notify : d->Parent; if (n) { if ( #if LGI_VIEW_HANDLE && !defined(HAIKU) !_View || #endif InThread()) { n->OnNotify(this, note); } else { // We are not in the same thread as the target window. So we post a message // across to the view. if (GetId() <= 0) { // We are going to generate a control Id to help the receiver of the // M_CHANGE message find out view later on. The reason for doing this // instead of sending a pointer to the object, is that the object // _could_ be deleted between the message being sent and being received. // Which would result in an invalid memory access on that object. LViewI *p = GetWindow(); if (!p) { // No window? Find the top most parent we can... p = this; while (p->GetParent()) p = p->GetParent(); } if (p) { // Give the control a valid ID int i; for (i=10; i<1000; i++) { if (!p->FindControl(i)) { printf("Giving the ctrl '%s' the id '%i' for SendNotify\n", GetClass(), i); SetId(i); break; } } } else { // Ok this is really bad... go random (better than nothing) SetId(5000 + LRand(2000)); } } LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise // the receiver will never be able to find our object. // printf("Post M_CHANGE %i %i\n", GetId(), Data); n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note)); } } } LViewI *LView::GetNotify() { ThreadCheck(); return d->Notify; } void LView::SetNotify(LViewI *p) { ThreadCheck(); d->Notify = p; } #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 int IsAdjacent(LRect &a, LRect &b) { if ( (a.x1 == b.x2 + 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_LEFT; } if ( (a.x2 == b.x1 - 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_RIGHT; } if ( (a.y1 == b.y2 + 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_UP; } if ( (a.y2 == b.y1 - 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_DOWN; } return 0; } LRect JoinAdjacent(LRect &a, LRect &b, int Adj) { LRect t; switch (Adj) { case ADJ_LEFT: case ADJ_RIGHT: { t.y1 = MAX(a.y1, b.y1); t.y2 = MIN(a.y2, b.y2); t.x1 = MIN(a.x1, b.x1); t.x2 = MAX(a.x2, b.x2); break; } case ADJ_UP: case ADJ_DOWN: { t.y1 = MIN(a.y1, b.y1); t.y2 = MAX(a.y2, b.y2); t.x1 = MAX(a.x1, b.x1); t.x2 = MIN(a.x2, b.x2); break; } } return t; } LRect *LView::FindLargest(LRegion &r) { ThreadCheck(); int Pixels = 0; LRect *Best = 0; static LRect Final; // do initial run through the list to find largest single region for (LRect *i = r.First(); i; i = r.Next()) { int Pix = i->X() * i->Y(); if (Pix > Pixels) { Pixels = Pix; Best = i; } } if (Best) { Final = *Best; Pixels = Final.X() * Final.Y(); int LastPixels = Pixels; LRect LastRgn = Final; int ThisPixels = Pixels; LRect ThisRgn = Final; LRegion TempList; for (LRect *i = r.First(); i; i = r.Next()) { TempList.Union(i); } TempList.Subtract(Best); do { LastPixels = ThisPixels; LastRgn = ThisRgn; // search for adjoining rectangles that maybe we can add for (LRect *i = TempList.First(); i; i = TempList.Next()) { int Adj = IsAdjacent(ThisRgn, *i); if (Adj) { LRect t = JoinAdjacent(ThisRgn, *i, Adj); int Pix = t.X() * t.Y(); if (Pix > ThisPixels) { ThisPixels = Pix; ThisRgn = t; TempList.Subtract(i); } } } } while (LastPixels < ThisPixels); Final = ThisRgn; } else return 0; return &Final; } LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy) { ThreadCheck(); int X = 1000000; int Y = 1000000; LRect *Best = 0; for (LRect *i = r.First(); i; i = r.Next()) { if ((i->X() >= Sx && i->Y() >= Sy) && (i->X() < X || i->Y() < Y)) { X = i->X(); Y = i->Y(); Best = i; } } return Best; } LRect *LView::FindLargestEdge(LRegion &r, int Edge) { LRect *Best = 0; ThreadCheck(); for (LRect *i = r.First(); i; i = r.Next()) { if (!Best) { Best = i; } if ( ((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1)) || ((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1)) ) { Best = i; } if ( ( ((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2)) ) && ( i->X() > Best->X() ) ) { Best = i; } if ( ( ((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1)) ) && ( i->Y() > Best->Y() ) ) { Best = i; } } return Best; } LViewI *LView::FindReal(LPoint *Offset) { ThreadCheck(); if (Offset) { Offset->x = 0; Offset->y = 0; } #if !LGI_VIEW_HANDLE LViewI *w = GetWindow(); #endif LViewI *p = d->Parent; while (p && #if !LGI_VIEW_HANDLE p != w #else !p->Handle() #endif ) { if (Offset) { Offset->x += Pos.x1; Offset->y += Pos.y1; } p = p->GetParent(); } if (p && #if !LGI_VIEW_HANDLE p == w #else p->Handle() #endif ) { return p; } return NULL; } bool LView::HandleCapture(LView *Wnd, bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd)); if (c) { if (_Capturing == Wnd) { DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0); } else { DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0); _Capturing = Wnd; #if defined(__GTK_H__) if (d->CaptureThread) d->CaptureThread->Cancel(); d->CaptureThread = new LCaptureThread(this); #elif WINNATIVE LPoint Offset; LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset); HWND h = v ? v->Handle() : NULL; if (h) SetCapture(h); else LAssert(0); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_TRUE); #else LAppInst->CaptureMouse(true); #endif #endif } } else if (_Capturing) { DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0); _Capturing = NULL; #if defined(__GTK_H__) if (d->CaptureThread) { d->CaptureThread->Cancel(); d->CaptureThread = NULL; // It'll delete itself... } #elif WINNATIVE ReleaseCapture(); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_FALSE); #else LAppInst->CaptureMouse(false); #endif #endif } return true; } bool LView::IsCapturing() { DEBUG_CAPTURE("%s::IsCapturing()=%i\n", GetClass(), (int)(_Capturing == this)); return _Capturing == this; } bool LView::Capture(bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass()); return HandleCapture(this, c); } bool LView::Enabled() { ThreadCheck(); #if WINNATIVE if (_View) return IsWindowEnabled(_View) != 0; #endif return !TestFlag(GViewFlags, GWF_DISABLED); } void LView::Enabled(bool i) { ThreadCheck(); if (!i) SetFlag(GViewFlags, GWF_DISABLED); else ClearFlag(GViewFlags, GWF_DISABLED); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE EnableWindow(_View, i); #elif defined LGI_CARBON if (i) { OSStatus e = EnableControl(_View); if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e); } else { OSStatus e = DisableControl(_View); if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e); } #endif } #endif Invalidate(); } bool LView::Visible() { // This is a read only operation... which is kinda thread-safe... // ThreadCheck(); #if WINNATIVE if (_View) /* This takes into account all the parent windows as well... Which is kinda not what I want. I want this to reflect just this window. return IsWindowVisible(_View); */ return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0; #endif return TestFlag(GViewFlags, GWF_VISIBLE); } void LView::Visible(bool v) { ThreadCheck(); if (v) SetFlag(GViewFlags, GWF_VISIBLE); else ClearFlag(GViewFlags, GWF_VISIBLE); #if defined(HAIKU) LLocker lck(d->Hnd, _FL); if (!IsAttached() || lck.Lock()) { if (v) d->Hnd->Show(); else d->Hnd->Hide(); } else LgiTrace("%s:%i - Can't lock.\n", _FL); #elif LGI_VIEW_HANDLE if (_View) { #if WINNATIVE ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE); #elif LGI_COCOA LAutoPool Pool; [_View.p setHidden:!v]; #elif LGI_CARBON Boolean is = HIViewIsVisible(_View); if (v != is) { OSErr e = HIViewSetVisible(_View, v); if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n", _FL, _View, v, e, GetClass()); } #endif } else #endif { Invalidate(); } } bool LView::Focus() { ThreadCheck(); bool Has = false; #if defined(__GTK_H__) LWindow *w = GetWindow(); if (w) { bool Active = w->IsActive(); if (Active) Has = w->GetFocus() == static_cast(this); } #elif defined(WINNATIVE) if (_View) { HWND hFocus = GetFocus(); Has = hFocus == _View; } #elif LGI_COCOA Has = TestFlag(WndFlags, GWF_FOCUS); #elif LGI_CARBON LWindow *w = GetWindow(); if (w) { ControlRef Cur; OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur); if (e) LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e); else Has = (Cur == _View); } #endif #if !LGI_CARBON if (Has) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); #endif return Has; } void LView::Focus(bool i) { ThreadCheck(); if (i) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); auto *Wnd = GetWindow(); if (Wnd) Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) #endif { #if defined(LGI_SDL) || defined(__GTK_H__) // Nop: Focus is all handled by Lgi's LWindow class. #elif WINNATIVE if (i) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name()); } SetFocus(_View); } } else { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow()); } SetFocus(GetDesktopWindow()); } #elif defined LGI_CARBON LViewI *Wnd = GetWindow(); if (Wnd && i) { OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1); if (e) { // e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart); // if (e) { HIViewRef p = HIViewGetSuperview(_View); // errCouldntSetFocus printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p); } } // else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass()); } else printf("%s:%i - no window?\n", _FL); #endif } } LDragDropSource *LView::DropSource(LDragDropSource *Set) { if (Set) d->DropSource = Set; return d->DropSource; } LDragDropTarget *LView::DropTarget(LDragDropTarget *Set) { if (Set) d->DropTarget = Set; return d->DropTarget; } #if defined LGI_CARBON extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #if defined __GTK_H__ // Recursively add drag dest to all view and all children bool GtkAddDragDest(LViewI *v, bool IsTarget) { if (!v) return false; LWindow *w = v->GetWindow(); if (!w) return false; auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget); if (IsTarget) { Gtk::gtk_drag_dest_set( wid, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); } else { Gtk::gtk_drag_dest_unset(wid); } for (LViewI *c: v->IterateViews()) GtkAddDragDest(c, IsTarget); return true; } #endif bool LView::DropTarget(bool t) { ThreadCheck(); bool Status = false; if (t) SetFlag(GViewFlags, GWF_DROP_TARGET); else ClearFlag(GViewFlags, GWF_DROP_TARGET); #if WINNATIVE if (_View) { if (t) { if (!d->DropTarget) DragAcceptFiles(_View, t); else Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK; } else { if (_View && d->DropTarget) Status = RevokeDragDrop(_View) == S_OK; } } #elif defined MAC && !defined(LGI_SDL) LWindow *Wnd = dynamic_cast(GetWindow()); if (Wnd) { Wnd->SetDragHandlers(t); if (!d->DropTarget) d->DropTarget = t ? Wnd : 0; } #if LGI_COCOA LWindow *w = GetWindow(); if (w) { OsWindow h = w->WindowHandle(); if (h) { NSMutableArray *a = [[NSMutableArray alloc] init]; if (a) { [a addObject:(NSString*)kUTTypeItem]; for (id item in NSFilePromiseReceiver.readableDraggedTypes) [a addObject:item]; [h.p.contentView registerForDraggedTypes:a]; [a release]; } } } #elif LGI_CARBON if (t) { static EventTypeSpec DragEvents[] = { { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragReceive }, }; if (!d->DndHandler) { OSStatus e = ::InstallControlEventHandler( _View, NewEventHandlerUPP(LgiViewDndHandler), GetEventTypeCount(DragEvents), DragEvents, (void*)this, &d->DndHandler); if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e); } SetControlDragTrackingEnabled(_View, true); } else { SetControlDragTrackingEnabled(_View, false); } #endif #elif defined __GTK_H__ Status = GtkAddDragDest(this, t); if (Status && !d->DropTarget) d->DropTarget = t ? GetWindow() : 0; #endif return Status; } bool LView::Sunken() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE); #else return TestFlag(GViewFlags, GWF_SUNKEN); #endif } void LView::Sunken(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE); else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE); if (_View) SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle); #else if (i) SetFlag(GViewFlags, GWF_SUNKEN); else ClearFlag(GViewFlags, GWF_SUNKEN); #endif if (i) { if (!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } bool LView::Flat() { // ThreadCheck(); #if WINNATIVE return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) && !TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return !TestFlag(GViewFlags, GWF_SUNKEN) && !TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Flat(bool i) { ThreadCheck(); #if WINNATIVE ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE)); #else ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN)); #endif } bool LView::Raised() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Raised(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE); else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else if (i) SetFlag(GViewFlags, GWF_RAISED); else ClearFlag(GViewFlags, GWF_RAISED); #endif if (i) { if (!!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } int LView::GetId() { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. return d->CtrlId; } void LView::SetId(int i) { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. d->CtrlId = i; #if WINNATIVE if (_View) SetWindowLong(_View, GWL_ID, d->CtrlId); #elif defined __GTK_H__ #elif defined MAC #endif } bool LView::GetTabStop() { ThreadCheck(); #if WINNATIVE return TestFlag(d->WndStyle, WS_TABSTOP); #else return d->TabStop; #endif } void LView::SetTabStop(bool b) { ThreadCheck(); #if WINNATIVE if (b) SetFlag(d->WndStyle, WS_TABSTOP); else ClearFlag(d->WndStyle, WS_TABSTOP); #else d->TabStop = b; #endif } int64 LView::GetCtrlValue(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Value() : 0; } void LView::SetCtrlValue(int Id, int64 i) { ThreadCheck(); LViewI *w = FindControl(Id); if (w) w->Value(i); } const char *LView::GetCtrlName(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Name() : 0; } void LView::SetCtrlName(int Id, const char *s) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Name(s); } else { PostEvent( M_SET_CTRL_NAME, (LMessage::Param)Id, (LMessage::Param)new LString(s)); } } bool LView::GetCtrlEnabled(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Enabled() : 0; } void LView::SetCtrlEnabled(int Id, bool Enabled) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Enabled(Enabled); } else { PostEvent( M_SET_CTRL_ENABLE, (LMessage::Param)Id, (LMessage::Param)Enabled); } } bool LView::GetCtrlVisible(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); if (!w) LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Visible() : 0; } void LView::SetCtrlVisible(int Id, bool v) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Visible(v); } else { PostEvent( M_SET_CTRL_VISIBLE, (LMessage::Param)Id, (LMessage::Param)v); } } bool LView::AttachChildren() { for (auto c: Children) { bool a = c->IsAttached(); if (!a) { if (!c->Attach(this)) { LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass()); return false; } } } return true; } LFont *LView::GetFont() { if (!d->Font && d->Css && LResources::GetLoadStyles()) { LFontCache *fc = LAppInst->GetFontCache(); if (fc) { LFont *f = fc->GetFont(d->Css); if (f) { if (d->FontOwnType == GV_FontOwned) DeleteObj(d->Font); d->Font = f; d->FontOwnType = GV_FontCached; } } } return d->Font ? d->Font : LSysFont; } void LView::SetFont(LFont *Font, bool OwnIt) { bool Change = d->Font != Font; if (Change) { if (d->FontOwnType == GV_FontOwned) { LAssert(d->Font != LSysFont); DeleteObj(d->Font); } d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr; d->Font = Font; #if WINNATIVE if (_View) SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0); #endif for (LViewI *p = GetParent(); p; p = p->GetParent()) { LTableLayout *Tl = dynamic_cast(p); if (Tl) { Tl->InvalidateLayout(); break; } } Invalidate(); } } bool LView::IsOver(LMouse &m) { return (m.x >= 0) && (m.y >= 0) && (m.x < Pos.X()) && (m.y < Pos.Y()); } bool LView::WindowVirtualOffset(LPoint *Offset) { bool Status = false; if (Offset) { Offset->x = 0; Offset->y = 0; for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent()) { #if !LGI_VIEW_HANDLE auto IsWnd = dynamic_cast(Wnd); if (!IsWnd) #else if (!Wnd->Handle()) #endif { LRect r = Wnd->GetPos(); LViewI *Par = Wnd->GetParent(); if (Par) { LRect c = Par->GetClient(false); Offset->x += r.x1 + c.x1; Offset->y += r.y1 + c.y1; } else { Offset->x += r.x1; Offset->y += r.y1; } Status = true; } else break; } } return Status; } LString _ViewDesc(LViewI *v) { LString s; s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId()); return s; } LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth) { char Tabs[64]; if (DebugDepth) { memset(Tabs, 9, DebugDepth); Tabs[DebugDepth] = 0; LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length()); } // We iterate over the child in reverse order because if they overlap the // end of the list is on "top". So they should get the click or whatever // before the the lower windows. auto it = Children.rbegin(); int n = (int)Children.Length() - 1; for (LViewI *c = *it; c; c = *--it) { LRect CPos = c->GetPos(); if (CPos.Overlap(x, y) && c->Visible()) { LRect CClient; CClient = c->GetClient(false); int Ox = CPos.x1 + CClient.x1; int Oy = CPos.y1 + CClient.y1; if (DebugDepth) { LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), CClient.GetStr(), x, y, x - Ox, y - Oy); } LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0); if (Child) return Child; } else if (DebugDepth) { LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y); } } if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y()) { return this; } return NULL; } LColour LView::StyleColour(int CssPropType, LColour Default, int Depth) { LColour c = Default; if ((CssPropType >> 8) == LCss::TypeColor) { LViewI *v = this; for (int i=0; v && iGetParent()) { auto Style = v->GetCss(); if (Style) { auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType); if (Colour) { if (Colour->Type == LCss::ColorRgb) { c.Set(Colour->Rgb32, 32); break; } else if (Colour->Type == LCss::ColorTransparent) { c.Empty(); break; } } } if (dynamic_cast(v) || dynamic_cast(v)) break; } } return c; } bool LView::InThread() { #if WINNATIVE HWND Hnd = _View; for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent()) { Hnd = p->Handle(); } auto CurThreadId = GetCurrentThreadId(); auto GuiThreadId = LAppInst->GetGuiThreadId(); DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId; return CurThreadId == ViewThread; #elif defined(HAIKU) return true; #else OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0; #if 0 if (Gui != Me) LgiTrace("%s:%i - Out of thread:" #ifdef LGI_COCOA "%llx, %llx" #else "%x, %x" #endif "\n", _FL, Gui, Me); #endif return Gui == Me; #endif } bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b) { #ifdef LGI_SDL return LPostEvent(this, Cmd, a, b); #elif defined(HAIKU) if (!d || !d->Hnd || !d->Hnd->LockLooper()) return false; BMessage *m = new BMessage(Cmd); if (!m) return false; m->AddUInt64(LMessage::PropNames[0], a); m->AddUInt64(LMessage::PropNames[1], b); auto w = d && d->Hnd ? d->Hnd->Window() : NULL; status_t r = w->PostMessage(m); d->Hnd->UnlockLooper(); return r == B_OK; #elif WINNATIVE if (!_View) return false; BOOL Res = ::PostMessage(_View, Cmd, a, b); if (!Res) { auto Err = GetLastError(); int asd=0; } return Res != 0; #elif !LGI_VIEW_HANDLE return LAppInst->PostEvent(this, Cmd, a, b); #else if (_View) return LPostEvent(_View, Cmd, a, b); LAssert(0); return false; #endif } bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient) { if (r) { for (int i=0; iLength(); i++) { bool Last = i == r->Length()-1; Invalidate((*r)[i], Last ? Repaint : false, NonClient); } return true; } return false; } LButton *FindDefault(LViewI *w) { LButton *But = 0; for (auto c: w->IterateViews()) { But = dynamic_cast(c); if (But && But->Default()) { break; } But = FindDefault(c); if (But) { break; } } return But; } bool LView::Name(const char *n) { LBase::Name(n); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE auto Temp = LBase::NameW(); SetWindowTextW(_View, Temp ? Temp : L""); #endif } #endif Invalidate(); return true; } const char *LView::Name() { #if WINNATIVE if (_View) { LView::NameW(); } #endif return LBase::Name(); } bool LView::NameW(const char16 *n) { LBase::NameW(n); #if WINNATIVE if (_View && n) { auto Txt = LBase::NameW(); SetWindowTextW(_View, Txt); } #endif Invalidate(); return true; } const char16 *LView::NameW() { #if WINNATIVE if (_View) { int Length = GetWindowTextLengthW(_View); if (Length > 0) { char16 *Buf = new char16[Length+1]; if (Buf) { Buf[0] = 0; int Chars = GetWindowTextW(_View, Buf, Length+1); Buf[Chars] = 0; LBase::NameW(Buf); } DeleteArray(Buf); } else { LBase::NameW(0); } } #endif return LBase::NameW(); } LViewI *LView::FindControl(int Id) { LAssert(Id != -1); if (GetId() == Id) { return this; } for (auto c : Children) { LViewI *Ctrl = c->FindControl(Id); if (Ctrl) { return Ctrl; } } return 0; } LPoint LView::GetMinimumSize() { return d->MinimumSize; } void LView::SetMinimumSize(LPoint Size) { d->MinimumSize = Size; bool Change = false; LRect p = Pos; if (X() < d->MinimumSize.x) { p.x2 = p.x1 + d->MinimumSize.x - 1; Change = true; } if (Y() < d->MinimumSize.y) { p.y2 = p.y1 + d->MinimumSize.y - 1; Change = true; } if (Change) { SetPos(p); } } bool LView::SetColour(LColour &c, bool Fore) { LCss *css = GetCss(true); if (!css) return false; if (Fore) { if (c.IsValid()) css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropColor); } else { if (c.IsValid()) css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropBackgroundColor); } return true; } /* bool LView::SetCssStyle(const char *CssStyle) { if (!d->Css && !d->Css.Reset(new LCss)) return false; const char *Defs = CssStyle; bool b = d->Css->Parse(Defs, LCss::ParseRelaxed); if (b && d->FontOwnType == GV_FontCached) { d->Font = NULL; d->FontOwnType = GV_FontPtr; } return b; } */ void LView::SetCss(LCss *css) { d->Css.Reset(css); } LCss *LView::GetCss(bool Create) { if (Create && !d->Css) d->Css.Reset(new LCss); if (d->CssDirty && d->Css) { const char *Defs = d->Styles; if (d->Css->Parse(Defs, LCss::ParseRelaxed)) d->CssDirty = false; } return d->Css; } LPoint &LView::GetWindowBorderSize() { static LPoint s; ZeroObj(s); #if WINNATIVE if (_View) { RECT Wnd, Client; GetWindowRect(Handle(), &Wnd); GetClientRect(Handle(), &Client); s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left); s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top); } #elif defined __GTK_H__ #elif defined MAC s.x = 0; s.y = 22; #endif return s; } #ifdef _DEBUG #if defined(LGI_CARBON) void DumpHiview(HIViewRef v, int Depth = 0) { char Sp[256]; memset(Sp, ' ', Depth << 2); Sp[Depth<<2] = 0; printf("%sHIView=%p", Sp, v); if (v) { Boolean vis = HIViewIsVisible(v); Boolean en = HIViewIsEnabled(v, NULL); HIRect pos; HIViewGetFrame(v, &pos); char cls[128]; ZeroObj(cls); GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls); printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s", vis, en, pos.origin.x, pos.origin.y, pos.size.width, pos.size.height, cls); } printf("\n"); for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c)) { DumpHiview(c, Depth + 1); } } #elif defined(__GTK_H__) void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL) { using namespace Gtk; if (!w) return; char Sp[65] = {0}; if (Depth) memset(Sp, ' ', *((int*)Depth)*2); auto *Obj = G_OBJECT(w); LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI"); GtkAllocation a; gtk_widget_get_allocation(w, &a); LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height); if (GTK_IS_CONTAINER(w)) { auto *c = GTK_CONTAINER(w); if (c) { int Next = Depth ? *((int*)Depth)+1 : 1; gtk_container_foreach(c, DumpGtk, &Next); } } } #elif defined(HAIKU) template void _Dump(int Depth, T *v) { LString Sp, Name; Sp.Length(Depth<<1); memset(Sp.Get(), ' ', Depth<<1); LRect Frame = v->Frame(); LViewPrivate *Priv = dynamic_cast(v); if (Priv) Name = Priv->View->GetClass(); printf("%s### %p::%s frame=%s vis=%i\n", Sp.Get(), v, Name.Get(), Frame.GetStr(), !v->IsHidden()); for (int32 i=0; iCountChildren(); i++) { _Dump(Depth + 1, v->ChildAt(i)); } } #endif void LView::_Dump(int Depth) { char Sp[65] = {0}; memset(Sp, ' ', Depth*2); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View); LgiTrace(s); List::I i = Children.Start(); for (LViewI *c = *i; c; c = *++i) { LView *v = c->GetGView(); if (v) v->_Dump(Depth+1); } #elif defined(LGI_CARBON) DumpHiview(_View); #elif defined(__GTK_H__) // DumpGtk(_View); #elif defined(HAIKU) LLocker lck(WindowHandle(), _FL); if (lck.Lock()) ::_Dump(0, WindowHandle()); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// static LArray *AllFactories = NULL; #if defined(WIN32) static HANDLE FactoryEvent; #else static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT; static void GFactoryInitFactories() { AllFactories = new LArray; } #endif LViewFactory::LViewFactory() { #if defined(WIN32) char16 Name[64]; swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId()); HANDLE h = CreateEventW(NULL, false, false, Name); DWORD err = GetLastError(); if (err != ERROR_ALREADY_EXISTS) { FactoryEvent = h; AllFactories = new LArray; } else { LAssert(AllFactories != NULL); } #else pthread_once(&FactoryOnce, GFactoryInitFactories); #endif if (AllFactories) AllFactories->Add(this); } LViewFactory::~LViewFactory() { if (AllFactories) { AllFactories->Delete(this); if (AllFactories->Length() == 0) { DeleteObj(AllFactories); #if defined(WIN32) CloseHandle(FactoryEvent); #endif } } } LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text) { if (ValidStr(Class) && AllFactories) { for (int i=0; iLength(); i++) { LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text); if (v) { return v; } } } return 0; } #ifdef _DEBUG #if defined(__GTK_H__) using namespace Gtk; #include "LgiWidget.h" #endif void LView::Debug() { _Debug = true; #if defined LGI_COCOA d->ClassName = GetClass(); #endif } #endif diff --git a/src/common/Text/TextView3.cpp b/src/common/Text/TextView3.cpp --- a/src/common/Text/TextView3.cpp +++ b/src/common/Text/TextView3.cpp @@ -1,5456 +1,5428 @@ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #ifdef WIN32 #include #endif #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #undef max #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms -#define PULSE_TIMEOUT 250 // ms +#define PULSE_TIMEOUT 500 // ms #define CURSOR_BLINK 1000 // ms #define ALLOC_BLOCK 64 #define IDC_VS 1000 enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class LDocFindReplaceParams3 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; bool SearchUpwards; LDocFindReplaceParams3() : LMutex("LDocFindReplaceParams3") { MatchCase = false; MatchWord = false; SelectionOnly = false; SearchUpwards = false; } }; class LTextView3Private : public LCss, public LMutex { public: LTextView3 *View; LRect rPadding; int PourX; bool LayoutDirty; ssize_t DirtyStart, DirtyLen; LColour UrlColour; bool CenterCursor; ssize_t WordSelectMode; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache; // Find/Replace Params bool OwnFindReplaceParams; LDocFindReplaceParams3 *FindReplaceParams; // Map buffer ssize_t MapLen; char16 *MapBuf; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView3Private(LTextView3 *view) : LMutex("LTextView3Private") { View = view; WordSelectMode = -1; PourX = -1; VScrollCache = -1; DirtyStart = DirtyLen = 0; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); CenterCursor = false; LayoutDirty = true; rPadding.ZOff(0, 0); MapBuf = 0; MapLen = 0; OwnFindReplaceParams = true; FindReplaceParams = new LDocFindReplaceParams3; } ~LTextView3Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView3Undo : public LUndoEvent { LTextView3 *View; LArray Changes; LTextView3Undo(LTextView3 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView3::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView3::LTextView3( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView3Private(this)); - PourEnabled = true; - PartialPour = false; - PartialPourLines = 0; - AdjustStylePos = true; - BlinkTs = 0; - LineY = 1; - MaxX = 0; - TextCache = 0; - UndoOn = true; - UndoCur = NULL; - Font = 0; - FixedWidthFont = false; - FixedFont = 0; - ShowWhiteSpace = false; - ObscurePassword = false; TabSize = TAB_SIZE; IndentSize = TAB_SIZE; - HardTabs = true; - CanScrollX = false; - Blink = true; - + // setup window SetId(Id); // default options - Dirty = false; #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else - CrLf = false; #endif - Underline = NULL; - Bold = NULL; d->Padding(LCss::Len(LCss::LenPx, 2)); #ifdef _DEBUG // debug times _PourTime = 0; _StyleTime = 0; _PaintTime = 0; #endif // Data Alloc = ALLOC_BLOCK; Text = new char16[Alloc]; if (Text) *Text = 0; Cursor = 0; Size = 0; // Display - SelStart = SelEnd = -1; - DocOffset = 0; - ScrollX = 0; - if (FontType) - { Font = FontType->Create(); - } else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView3::~LTextView3() { #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView3::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView3::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView3::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView3::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView3::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView3::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); CanScrollX = i != TEXTED_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView3::GetFont() { return Font; } LFont *LTextView3::GetBold() { return Bold; } void LTextView3::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView3::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } void LTextView3::LogLines() { int Idx = 0; LgiTrace("DocSize: %i\n", (int)Size); for (auto i : Line) { LgiTrace(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr()); Idx++; } #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif } bool LTextView3::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto i : Line) { LTextLine *l = i; if (l->Start != Pos) { LogLines(); LAssert(!"Incorrect start."); return false; } char16 *e = c; if (WrapType == TEXTED_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { LogLines(); LAssert(!"Incorrect length."); return false; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { LogLines(); LAssert(!"Lines not joined vertically"); } if (*e) { if (*e == '\n') e++; else if (WrapType == TEXTED_WRAP_REFLOW) e++; } Pos = e - Text; c = e; Idx++; Prev = l; } if (WrapType == TEXTED_WRAP_NONE && Pos != Size) { LogLines(); LAssert(!"Last line != end of doc"); return false; } return true; } int LTextView3::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView3::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif #if !defined(HAIKU) LAssert(InThread()); #endif LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); // LgiTrace("Pour %i:%i Cur=%p Idx=%i\n", (int)Start, (int)Length, (int)Cur, (int)Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (auto i = Idx >= 0 ? Line.begin(Idx) : Line.rbegin(); *i; i--, Idx--) { Cur = *i; if (Cur->r.Valid()) { Cy = Cur->r.y1; if (Idx < 0) Idx = Line.IndexOf(Cur); break; } } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; // LgiTrace("Reset start to %i:%i because Cur!=NULL\n", (int)Start, (int)Length); } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; //int LastX = 0; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); if (WrapType == TEXTED_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (auto i = Line.begin(Idx); *i; i++, Idx++) { LTextLine *l = *i; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine; l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } Line.Insert(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewGStr(); #endif PartialPour = false; PartialPourLines = 0; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); Cur = NULL; } int Cx = 0; ssize_t i; for (i=Start; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine; if (l) { l->Start = i; l->Len = e - i; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) { PartialPour = false; PartialPourLines = 0; } SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG // ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || Last->Start + Last->Len < Size) { LTextLine *l = new LTextLine; if (l) { l->Start = Size; l->Len = 0; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { #if 0 LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); #endif SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView3::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView3::LStyle *LTextView3::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView3::LStyle *LTextView3::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView3::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; // Url->Email = Inf.Email; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView3::Insert(size_t At, const char16 *Data, ssize_t Len) { LProfile Prof("LTextView3::Insert"); Prof.HideResultsIfBelow(1000); LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } Prof.Add("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16)); Prof.Add("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate Prof.Add("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Insert(Cur = new LTextLine); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { if (WrapType == TEXTED_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); Prof.Add("NoWrap add lines"); // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; // Create a new line... Cur = new LTextLine(); if (!Cur) return false; Cur->Start = Pos + 1; Line.Insert(Cur, ++Idx); } } Prof.Add("CalcLen"); // Make sure the last Line's length is set.. Cur->CalcLen(Text); Prof.Add("UpdatePos"); // Now update all the positions of the following lines... for (auto i = Line.begin(++Idx); *i; i++) (*i)->Start += Len; } else { // Clear all lines to the end of the doc... for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode if (WrapType == TEXTED_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG // Prof.Add("Validate"); // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { Prof.Add("PourText"); PourText(At, Len); Prof.Add("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView3::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; if (WrapType == TEXTED_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (auto i = Line.begin(Idx + 1); *i; i++) (*i)->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (auto i = Line.begin(Index); *i; i++) delete *i; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change if (WrapType == TEXTED_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView3::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } List::I LTextView3::GetTextLineIt(ssize_t Offset, ssize_t *Index) { int i = 0; for (auto It = Line.begin(); It != Line.end(); It++) { auto l = *It; if (Offset >= l->Start && Offset <= l->Start+l->Len) { if (Index) *Index = i; return It; } i++; } return Line.end(); } int64 LTextView3::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView3::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView3::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView3::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView3::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView3::NameW() { return Text; } const char16 *LTextView3::TextAtLine(size_t Index) { if (Index >= Line.Length()) return NULL; auto ln = Line[Index]; return Text + ln->Start; } bool LTextView3::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView3::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView3::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView3::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView3::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView3::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView3::GetLines() { return Line.Length(); } void LTextView3::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView3::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView3::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView3::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView3::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; - LTextLine *SLine = GetTextLine(Start); - LTextLine *ELine = GetTextLine(End); + auto SLine = GetTextLine(Start); + auto ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView3::SetBorder(int b) { } bool LTextView3::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView3::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView3::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } bool LTextView3::ClearDirty(bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { LFileSelect Select; Select.Parent(this); if (!FileName && Select.Save()) { FileName = Select.Name(); } Save(FileName); } else if (Answer == IDCANCEL) { return false; } } return true; } bool LTextView3::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView3::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView3::GetLastError() { return d->LastError; } void LTextView3::UpdateScrollBars(bool Reset) { if (!VScroll) return; LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = std::max(PartialPourLines, Line.Length()); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } bool LTextView3::DoCase(bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } return true; } ssize_t LTextView3::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } void LTextView3::SetLine(int i, bool select) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, select); d->CenterCursor = false; } } bool LTextView3::DoGoto() { LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); if (Dlg.DoModal() == IDOK && Dlg.GetStr()) { SetLine(atoi(Dlg.GetStr())); } return true; } LDocFindReplaceParams *LTextView3::CreateFindReplaceParams() { return new LDocFindReplaceParams3; } void LTextView3::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (LDocFindReplaceParams3*) Params; } } bool LTextView3::DoFindNext() { bool Status = false; if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } return Status; } bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User) { LTextView3 *v = (LTextView3*) User; if (v->d->FindReplaceParams && v->d->FindReplaceParams->Lock(_FL)) { v->d->FindReplaceParams->MatchWord = Dlg->MatchWord; v->d->FindReplaceParams->MatchCase = Dlg->MatchCase; v->d->FindReplaceParams->SelectionOnly = Dlg->SelectionOnly; v->d->FindReplaceParams->SearchUpwards = Dlg->SearchUpwards; v->d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg->Find)); v->d->FindReplaceParams->Unlock(); } return v->DoFindNext(); } bool LTextView3::DoFind() { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } LFindDlg Dlg(this, u, Text3_FindCallback, this); Dlg.DoModal(); Focus(true); return false; } bool LTextView3::DoReplace() { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } char *LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind); char *LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace); LReplaceDlg Dlg(this, LastFind8, LastReplace8); Dlg.MatchWord = d->FindReplaceParams->MatchWord; Dlg.MatchCase = d->FindReplaceParams->MatchCase; Dlg.SelectionOnly = HasSelection(); int Action = Dlg.DoModal(); DeleteArray(LastFind8); DeleteArray(LastReplace8); if (Action != IDCANCEL) { d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg.Find)); d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Dlg.Replace)); d->FindReplaceParams->MatchWord = Dlg.MatchWord; d->FindReplaceParams->MatchCase = Dlg.MatchCase; d->FindReplaceParams->SelectionOnly = Dlg.SelectionOnly; } switch (Action) { case IDC_FR_FIND: { OnFind( d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } case IDOK: case IDC_FR_REPLACE: { OnReplace( d->FindReplaceParams->LastFind, d->FindReplaceParams->LastReplace, Action == IDOK, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } } return false; } void LTextView3::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView3::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } /* What was this even supposed to do? LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) */ return i; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView3::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView3::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView3::SeekLine(ssize_t Offset, GTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView3::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView3::OnSetHidden(int Hidden) { } void LTextView3::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { /* auto Client = GetClient(); LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); */ SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView3::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView3::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView3::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS - if (Ctrls.Length() == 0) - #endif + if (Ctrls.Length() == 0) + SetPulse(PULSE_TIMEOUT); + Ctrls.Add(this); + #else SetPulse(PULSE_TIMEOUT); - #ifndef WINDOWS - Ctrls.Add(this); #endif } void LTextView3::OnEscape(LKey &K) { } bool LTextView3::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView3::OnFocus(bool f) { Invalidate(); } ssize_t LTextView3::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; int Y = (VScroll) ? (int)VScroll->Value() : 0; auto It = Line.begin(Y); if (It != Line.end()) y += (*It)->r.y1; while (It != Line.end()) { auto l = *It; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) { return l->Start; } else if (x > l->r.x2) { return l->Start + l->Len; } } if (Down) It++; else It--; Y++; } // outside text area if (Down) { It = Line.rbegin(); if (It != Line.end()) { if (y > (*It)->r.y2) { // end of document return Size; } } } return 0; } void LTextView3::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView3::Redo() { UndoQue.Redo(); } void LTextView3::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); LInput i(this, s, "Indent Size:", "Text"); if (i.DoModal()) { IndentSize = atoi(i.GetStr()); } break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); LInput i(this, s, "Tab Size:", "Text"); if (i.DoModal()) { SetTabSize(atoi(i.GetStr())); } break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView3::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool LTextView3::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView3::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView3::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (m.IsContextMenu()) { DoContextMenu(m); return; } else if (m.Left()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } } if (!Processed) { Capture(m.Down()); } } int LTextView3::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView3::OnMouseMove(LMouse &m) { m.x += ScrollX; ssize_t Hit = HitText(m.x, m.y, true); if (IsCapturing()) { if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } } LCursor LTextView3::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView3::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { DoFindNext(); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView3_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView3_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView3_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) { DoFind(); } return true; break; } case 'g': case 'G': { if (k.Down()) { DoGoto(); } return true; break; } case 'h': case 'H': { if (k.Down()) { DoReplace(); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { DoCase(k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView3::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView3::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView3::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView3::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView3::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView3::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView3::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } #ifdef DOUBLE_BUFFER_PAINT LMemDC *pMem = new LMemDC; pOut = pMem; #endif if (Text && Font #ifdef DOUBLE_BUFFER_PAINT && pMem && pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits()) #endif ) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); auto It = Line.begin(k); LTextLine *l = NULL; int Dy = 0; if (It != Line.end()) Dy = -(*It)->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (It != Line.end() && (l = *It) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ((l = *It) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; #ifdef DOUBLE_BUFFER_PAINT Tr.Offset(-Tr.x1, -Tr.y1); #else Tr.Offset(0, y - Tr.y1); #endif //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; #ifdef DOUBLE_BUFFER_PAINT c.Offset(-d->rPadding.x1, -y); #endif pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif #ifdef DOUBLE_BUFFER_PAINT // dump to screen pDC->Blt(d->rPadding.x1, y, pOut); #endif y += LineY; It++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } #ifdef DOUBLE_BUFFER_PAINT DeleteObj(pMem); #endif } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView3::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView3::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) DoFindNext(); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView3::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView3::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView3::OnPulse() { #ifdef WINDOWS - InternalPulse(); + InternalPulse(); #else - for (auto c: Ctrls) - c->InternalPulse(); + for (auto c: Ctrls) + c->InternalPulse(); #endif } void LTextView3::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView3::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } /////////////////////////////////////////////////////////////////////////////// class LTextView3_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView3") == 0) { return new LTextView3(-1, 0, 0, 2000, 2000); } return 0; } } TextView3_Factory; diff --git a/src/linux/Lgi/App.cpp b/src/linux/Lgi/App.cpp --- a/src/linux/Lgi/App.cpp +++ b/src/linux/Lgi/App.cpp @@ -1,1462 +1,1476 @@ #include #include #include #include #ifndef WIN32 #include #include #endif #include #define _GNU_SOURCE #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/FontCache.h" #include "AppPriv.h" #define DEBUG_MSG_TYPES 0 #define DEBUG_HND_WARNINGS 0 #define IDLE_ALWAYS 0 using namespace Gtk; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth = 0); //////////////////////////////////////////////////////////////// struct OsAppArgumentsPriv { ::LArray Ptr; ~OsAppArgumentsPriv() { Ptr.DeleteArrays(); } }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; for (int i=0; iPtr.Add(NewStr(arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } bool OsAppArguments::Get(const char *Name, const char **Val) { if (!Name) return false; for (int i=0; iPtr.DeleteArrays(); if (!CmdLine) return; for (char *s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr(s, delim); if (e) d->Ptr.Add(NewStr(s, e - s)); else break; s = e + 1; } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; d->Ptr.Add(NewStr(s, e-s)); s = e; } } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { d->Ptr.DeleteArrays(); for (int i=0; iPtr.Add(NewStr(a.Arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if HAS_SHARED_MIME #include "GFilterUtils.h" #include "mime-types.h" class LSharedMime : public LLibrary { public: LSharedMime() : #ifdef _DEBUG LLibrary("libsharedmime1d") #else LLibrary("libsharedmime1") #endif { } DynFunc0(int, mimetypes_init); DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type); DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags); DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length); DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type); DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type); DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan); DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang); }; #endif ///////////////////////////////////////////////////////////////////////////// // // Attempts to cleanup and call drkonqi to process the crash // static LString CrashHandlerApp; void LgiCrashHandler(int Sig) { // Don't get into an infinite loop signal(SIGSEGV, SIG_DFL); #ifndef _MSC_VER // Our pid LString pid; pid.Printf("%i", getpid()); LgiTrace("LgiCrashHandler trigger pid=%s\n", pid.Get()); auto child = fork(); if (!child) { LFile::Path workingDir = CrashHandlerApp; workingDir--; chdir(workingDir); const char *args[] = { CrashHandlerApp, "--pid", pid, NULL }; execvp(CrashHandlerApp, args); exit(0); } if (LAppInst->InThread()) { LgiTrace("LgiCrashHandler showing dlg\n"); LgiMsg(NULL, "Application crashed... dumping details.", "Crash"); } else { LgiTrace("LgiCrashHandler called from worker thread.\n"); LSleep(10000); // Wait for the crash handler to do it's thing... } #endif exit(-1); } ///////////////////////////////////////////////////////////////////////////// #ifndef XK_Num_Lock #define XK_Num_Lock 0xff7f #endif #ifndef XK_Shift_Lock #define XK_Shift_Lock 0xffe6 #endif #ifndef XK_Caps_Lock #define XK_Caps_Lock 0xffe5 #endif struct Msg { LViewI *v; int m; LMessage::Param a, b; void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B) { v = V; m = M; a = A; b = B; } }; // Out of thread messages... must lock before access. class LMessageQue : public LMutex { public: - typedef ::LArray MsgArray; + typedef LArray MsgArray; LMessageQue() : LMutex("LMessageQue") { } MsgArray *Lock(const char *file, int line) { if (!LMutex::Lock(file, line)) return NULL; return &q; } operator bool() { return q.Length() > 0; } private: MsgArray q; } MsgQue; ///////////////////////////////////////////////////////////////////////////// LSkinEngine *LApp::SkinEngine = 0; LApp *TheApp = 0; LMouseHook *LApp::MouseHook = 0; LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; d = new LAppPrivate(this); Name(name); LgiArgsAppPath = AppArgs.Arg[0]; if (LIsRelativePath(LgiArgsAppPath)) { char Cwd[MAX_PATH_LEN]; getcwd(Cwd, sizeof(Cwd)); LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath); LgiArgsAppPath = Cwd; } int WCharSz = sizeof(wchar_t); #if defined(_MSC_VER) LAssert(WCharSz == 2); ::LFile::Path Dlls(LgiArgsAppPath); Dlls--; SetDllDirectoryA(Dlls); #else LAssert(WCharSz == 4); #endif #ifdef _MSC_VER SetEnvironmentVariable(_T("GTK_CSD"), _T("0")); #else setenv("GTK_CSD", "0", true); #endif // We want our printf's NOW! setvbuf(stdout, (char*)NULL,_IONBF, 0); // print mesgs immediately. // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; SetAppArgs(AppArgs); srand(LCurrentTime()); LColour::OnChange(); Gtk::gchar id[256]; sprintf_s(id, sizeof(id), "com.memecode.%s", name); d->App = Gtk::gtk_application_new(id, Gtk::G_APPLICATION_FLAGS_NONE); LAssert(d->App != NULL); MouseHook = new LMouseHook; // Setup the SIGSEGV signal to call the crash handler if (!GetOption("nch")) { auto programName = "crash-handler"; LFile::Path p(LSP_APP_INSTALL); p += programName; if (!p.Exists()) { // Check alternate location for development builds Dl_info dlInfo; dladdr(LgiCrashHandler, &dlInfo); if (dlInfo.dli_sname != NULL && dlInfo.dli_saddr != NULL) { p = dlInfo.dli_fname; p += "../../src/linux/CrashHandler"; p += programName; printf("Alternative path %s: %s\n", p.Exists() ? "found" : "missing", p.GetFull().Get()); } } if (p.Exists()) { CrashHandlerApp = p; signal(SIGSEGV, LgiCrashHandler); LgiTrace("Crash handler: '%s' installed.\n", CrashHandlerApp.Get()); } else { LgiTrace("Crash handler: No crash handler '%s' found, SIGSEGV handler not installed.\n", p.GetFull().Get()); } } else { LgiTrace("Crash handler: disabled.\n"); } d->GetConfig(); // System font setup LFontType SysFontType; Gtk::PangoFontMap *fm = Gtk::pango_cairo_font_map_get_default(); if (fm) { using namespace Gtk; auto cfm = PANGO_CAIRO_FONT_MAP(fm); #ifdef MAC double Dpi = 80.0; #else double Dpi = 96.0; #endif ::LFile::Path p(LSP_APP_ROOT); p += "lgi-conf.json"; if (p.IsFile()) { ::LFile f(p, O_READ); LJson j(f.Read()); auto sDpi = j.Get("DPI"); if (sDpi) Dpi = sDpi.Float(); } pango_cairo_font_map_set_resolution(cfm, Dpi); } if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) SystemNormal->Transparent(true); SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } } LApp::~LApp() { DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(SkinEngine); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = 0; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #ifndef __clang__ (this != 0) && #endif (d != 0); LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { case LGI_MET_DECOR_X: return 8; case LGI_MET_DECOR_Y: return 8 + 19; case LGI_MET_DECOR_CAPTION: return 19; default: break; } return 0; } LViewI *LApp::GetFocus() { // GtkWidget *w = gtk_window_get_focus(GtkWindow *window); return NULL; } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } OsProcessId LApp::GetProcessId() { #ifdef WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } bool LApp::InThread() { OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = GetGuiThreadId(); // printf("Me=%i Gui=%i\n", Me, Gui); return Gui == Me; } struct GtkIdle { LAppPrivate *d; LAppI::OnIdleProc cb; void *param; }; Gtk::gboolean IdleWrapper(Gtk::gpointer data) { #if 0 static int64 ts = LCurrentTime(); static int count = 0; int64 now = LCurrentTime(); if (now - ts > 300) { printf("IdleWrapper = %i\n", count); count = 0; ts = now; } else count++; #endif GtkIdle *i = (GtkIdle*) data; if (i->cb) i->cb(i->param); LMessageQue::MsgArray *Msgs; if (MsgQue && (Msgs = MsgQue.Lock(_FL))) { // printf("IdleWrapper start %i\n", (int)Msgs->Length()); // Copy the messages out of the locked structure.. // This allows new messages to arrive independent // of us processing them here... LMessageQue::MsgArray q = *Msgs; Msgs->Empty(); MsgQue.Unlock(); for (auto m : q) { if (!LView::LockHandler(m.v, LView::OpExists)) { // LgiTrace("%s:%i - Invalid view: %p.\n", _FL, m.v); } else { LMessage Msg(m.m, m.a, m.b); // LgiTrace("%s::OnEvent %i,%i,%i\n", m.v->GetClass(), m.m, m.a, m.b); m.v->OnEvent(&Msg); } } } else { // printf("IdleWrapper start no lock\n"); } // printf("IdleWrapper end\n"); return i->cb != NULL; // return false; } static GtkIdle idle = {0}; bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!InThread()) { printf("%s:%i - Error: Out of thread.\n", _FL); return false; } if (!idle.d) { idle.d = d; idle.cb = IdleCallback; idle.param = IdleParam; } static bool CmdLineDone = false; if (!CmdLineDone) { CmdLineDone = true; OnCommandLine(); } Gtk::gtk_main(); return true; } bool LApp::Yield() { Gtk::gtk_main_iteration_do(false); return true; } void LApp::Exit(int Code) { if (Code) { // hard exit ::exit(Code); } else { // soft exit Gtk::gtk_main_quit(); if (d->IdleId.Length() > 0) { size_t Last = d->IdleId.Length() - 1; Gtk::g_source_remove(d->IdleId[Last]); d->IdleId.Length(Last); } } } bool LApp::PostEvent(LViewI *View, int Msg, LMessage::Param a, LMessage::Param b) { - LMessageQue::MsgArray *q = MsgQue.Lock(_FL); + auto q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return false; } q->New().Set(View, Msg, a, b); #if 1 // defined(_DEBUG) if (q->Length() > 200 && (q->Length()%20)==0) { static uint64 prev = 0; auto now = LCurrentTime(); - if (now - prev >= 500) + if (now - prev >= 1000) { prev = now; #if defined(WIN32) char s[256]; sprintf_s(s, sizeof(s), #else printf( #endif "PostEvent Que=" LPrintfSizeT " (msg=%i)\n", q->Length(), Msg); #if defined(WIN32) OutputDebugStringA(s); #endif + + #ifdef LINUX + LHashTbl, size_t> MsgCounts; + for (auto &msg: *q) + MsgCounts.Add(msg.m, MsgCounts.Find(msg.m) + 1); + + for (auto c: MsgCounts) + printf(" %i->%i\n", c.key, c.value); + + if (Msg == 916) + { + int asd=0; + } + #endif } } #endif MsgQue.Unlock(); // g_idle_add((GSourceFunc)IdleWrapper, &idle); g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)IdleWrapper, &idle, NULL); return true; } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); } void LApp::OnReceiveFiles(::LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); else LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe."); } const char *LApp::KeyModFlags::FlagName(int Flag) { #define CHECK(f) if (Flag & f) return #f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return NULL; } int LApp::KeyModFlags::FlagValue(const char *Name) { #define CHECK(f) if (!Stricmp(Name, #f)) return f; CHECK(GDK_SHIFT_MASK) CHECK(GDK_LOCK_MASK) CHECK(GDK_CONTROL_MASK) CHECK(GDK_MOD1_MASK) CHECK(GDK_MOD2_MASK) CHECK(GDK_MOD3_MASK) CHECK(GDK_MOD4_MASK) CHECK(GDK_MOD5_MASK) CHECK(GDK_BUTTON1_MASK) CHECK(GDK_BUTTON2_MASK) CHECK(GDK_BUTTON3_MASK) CHECK(GDK_BUTTON4_MASK) CHECK(GDK_BUTTON5_MASK) CHECK(GDK_SUPER_MASK) CHECK(GDK_HYPER_MASK) CHECK(GDK_META_MASK) CHECK(GDK_RELEASE_MASK) #undef CHECK return 0; } ::LString LApp::KeyModFlags::FlagsToString(int s) { ::LString::Array a; for (int i=0; i<32; i++) { if (((1 << i) & s) != 0) a.New() = FlagName(1 << i); } return ::LString(",").Join(a); } LApp::KeyModFlags *LApp::GetKeyModFlags() { return d->GetModFlags(); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { ::LString Buf; if (GetOption(Option, Buf)) { if (Dest) { if (DestLen > 0) { strcpy_s(Dest, DestLen, Buf); } else return false; } return true; } return false; } bool LApp::GetOption(const char *Option, ::LString &Buf) { if (IsOk() && Option) { int OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { char *a = d->Args.Arg[i]; if (!a) continue; if (strchr("-/\\", a[0])) { if (strnicmp(a+1, Option, OptLen) == 0) { char *Arg = 0; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { auto Len = End-Arg; if (Len > 0) Buf.Set(Arg, Len); else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { ::LArray Files; for (int i=1; iArgs; i++) { char *a = GetAppArgs()->Arg[i]; if (LFileExists(a)) { Files.Add(NewStr(a)); } } // call app if (Files.Length() > 0) { OnReceiveFiles(Files); } // clear up Files.DeleteArrays(); } ::LString LApp::GetFileMimeType(const char *File) { ::LString Status; char Full[MAX_PATH_LEN] = ""; if (!LFileExists(File)) { // Look in the path LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; i 0) Status = s.SplitDelimit(":").Last().Strip(); } return Status; } } #endif #if HAS_LIB_MAGIC static bool MagicError = false; if (d->MagicLock.Lock(_FL)) { if (!d->hMagic && !MagicError) { d->hMagic = magic_open(MAGIC_MIME_TYPE); if (d->hMagic) { if (magic_load(d->hMagic, NULL) != 0) { printf("%s:%i - magic_load failed - %s\n", _FL, magic_error(d->hMagic)); magic_close(d->hMagic); d->hMagic = NULL; MagicError = true; } } else { printf("%s:%i - magic_open failed.\n", _FL); MagicError = true; } } if (d->hMagic && !MagicError) { const char *mt = magic_file(d->hMagic, File); if (mt) { Status = mt; } } d->MagicLock.Unlock(); if (Status) return Status; } #endif #if HAS_SHARED_MIME // freedesktop.org rocks... if (!d->Sm) { // Not loaded, go and try to load it... d->Sm = new LSharedMime; if (d->Sm && d->Sm->IsLoaded()) { d->Sm->mimetypes_init(); } } if (d->Sm && d->Sm->IsLoaded()) { // Loaded... char *m = (char*)d->Sm->mimetypes_get_file_type(File, MIMETYPES_CHECK_ALL); if (m) { #if HAS_FILE_CMD if (stricmp(m, "application/x-not-recognised") != 0) #endif { strcpy(Mime, m); return true; } } else { printf("%s:%i - mimetypes_get_file_type failed for '%s'\n", __FILE__, __LINE__, File); } } else { printf("%s:%i - Shared Mime not loaded!!!\n", __FILE__, __LINE__); } #endif #if HAS_FILE_CMD // doh! not installed... :( LStringPipe Output; char Args[256]; sprintf(Args, "-i \"%s\"", File); LSubProcess p("file", Args); if (p.Start()) { p.Communicate(&Output); char *Out = Output.NewStr(); if (Out) { char *s = strchr(Out, ':'); if (s && strchr(Out, '/')) { s += 2; char *e = s; while ( *e && ( IsAlpha(*e) || IsDigit(*e) || strchr(".-_/", *e) ) ) e++; *e = 0; Status.Reset(NewStr(s)); } DeleteArray(Out); } } #endif return Status; } bool LApp::GetAppsForMimeType(char *Mime, ::LArray<::LAppInfo*> &Apps) { // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; int Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } LGetAppsForMimeType(Mime, Apps); LGetAppsForMimeType(AltMime, Apps); return Apps.Length() > 0; } #if defined(LINUX) LLibrary *LApp::GetWindowManagerLib() { if (this != NULL && !d->WmLib) { char Lib[32]; WindowManager Wm = LGetWindowManager(); switch (Wm) { case WM_Kde: strcpy(Lib, "liblgikde3"); break; case WM_Gnome: strcpy(Lib, "liblgignome2"); break; default: strcpy(Lib, "liblgiother"); break; } #ifdef _DEBUG strcat(Lib, "d"); #endif d->WmLib = new LLibrary(Lib, true); if (d->WmLib) { if (d->WmLib->IsLoaded()) { Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit"); if (WmInit) { WmInitParams Params; // Params.Dsp = XObject::XDisplay(); Params.Args = d->Args.Args; Params.Arg = d->Args.Arg; WmInit(&Params); } // else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__); } // else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib); } // else printf("%s:%i - alloc error\n", __FILE__, __LINE__); } return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0; } void LApp::DeleteMeLater(LViewI *v) { d->DeleteLater.Add(v); } void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v) { // Store the clipboard data we will serve d->ClipData = v; } bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types) { return false; } #endif LSymLookup *LApp::GetSymLookup() { return d; } bool LApp::IsElevated() { #ifdef WIN32 LAssert(!"What API works here?"); return false; #else return geteuid() == 0; #endif } int LApp::GetCpuCount() { return 1; } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } #ifdef LINUX LApp::DesktopInfo::DesktopInfo(const char *file) { File = file; Dirty = false; if (File) Serialize(false); } bool LApp::DesktopInfo::Serialize(bool Write) { ::LFile f; if (Write) { ::LFile::Path p(File); p--; if (!p.Exists()) return false; } else if (!LFileExists(File)) return false; if (!f.Open(File, Write?O_WRITE:O_READ)) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get()); return false; } if (Write) { f.SetSize(0); for (unsigned i=0; i= 0) { int e = l.Find("]", ++s); if (e >= 0) { Cur = &Data.New(); Cur->Name = l(s, e - s + 1); } } else if ((s = l.Find("=")) >= 0) { if (!Cur) Cur = &Data.New(); KeyPair &kp = Cur->Values.New(); kp.Key = l(0, s).Strip(); kp.Value = l(++s, -1).Strip(); // printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get()); } } } return true; } LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create) { for (unsigned i=0; iGet(Field, false, Dirty); if (kp) { return kp->Value; } } } return ::LString(); } bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect) { if (!Field) return false; Section *s = GetSection(Sect ? Sect : DefaultSection, true); if (!s) return false; KeyPair *kp = s->Get(Field, true, Dirty); if (!kp) return false; if (kp->Value != Value) { kp->Value = Value; Dirty = true; } return true; } LApp::DesktopInfo *LApp::GetDesktopInfo() { auto sExe = LGetExeFile(); ::LFile::Path Exe(sExe); ::LFile::Path Desktop(LSP_HOME); ::LString Leaf; Leaf.Printf("%s.desktop", Exe.Last().Get()); Desktop += ".local/share/applications"; Desktop += Leaf; const char *Ex = Exe; const char *Fn = Desktop; if (d->DesktopInfo.Reset(new DesktopInfo(Desktop))) { // Do a sanity check... ::LString s = d->DesktopInfo->Get("Name"); if (!s && Name()) d->DesktopInfo->Set("Name", Name()); s = d->DesktopInfo->Get("Exec"); if (!s || s != (const char*)sExe) d->DesktopInfo->Set("Exec", sExe); s = d->DesktopInfo->Get("Type"); if (!s) d->DesktopInfo->Set("Type", "Application"); s = d->DesktopInfo->Get("Categories"); if (!s) d->DesktopInfo->Set("Categories", "Application;"); s = d->DesktopInfo->Get("Terminal"); if (!s) d->DesktopInfo->Set("Terminal", "false"); d->DesktopInfo->Update(); } return d->DesktopInfo; } bool LApp::SetApplicationIcon(const char *FileName) { DesktopInfo *di = GetDesktopInfo(); if (!di) return false; ::LString IcoPath = di->Get("Icon"); if (IcoPath == FileName) return true; di->Set("Icon", FileName); return di->Update(); } #endif //////////////////////////////////////////////////////////////// OsApplication *OsApplication::Inst = 0; class OsApplicationPriv { public: OsApplicationPriv() { } }; OsApplication::OsApplication(int Args, char **Arg) { Inst = this; d = new OsApplicationPriv; } OsApplication::~OsApplication() { DeleteObj(d); Inst = 0; } //////////////////////////////////////////////////////////////// void LMessage::Set(int Msg, Param ParamA, Param ParamB) { m = Msg; a = ParamA; b = ParamB; } struct GlibEventParams : public LMessage { GtkWidget *w; }; bool GlibWidgetSearch(GtkWidget *p, GtkWidget *w, bool Debug, int depth) { char indent[256] = ""; if (Debug) { int ch = depth * 2; memset(indent, ' ', ch); indent[ch] = 0; printf("%sGlibWidgetSearch: %p, %p\n", indent, p, w); } if (p == w) return true; if (GTK_IS_CONTAINER(p)) { int n = 0; Gtk::GList *top = gtk_container_get_children(GTK_CONTAINER(p)); Gtk::GList *i = top; while (i) { if (Debug) printf("%s[%i]=%s\n", indent, n, gtk_widget_get_name((GtkWidget*)i->data)); if (i->data == w) return true; else if (GlibWidgetSearch((GtkWidget*)i->data, w, Debug, depth + 1)) return true; i = i->next; n++; } g_list_free(top); } else if (GTK_IS_BIN(p)) { GtkWidget *child = gtk_bin_get_child(GTK_BIN(p)); if (child) { if (Debug) printf("%schild=%s\n", indent, gtk_widget_get_name(child)); if (child == w) return true; else if (GlibWidgetSearch(child, w, Debug, depth + 1)) return true; } } else if (Debug) { printf("%sUnknown=%s\n", indent, gtk_widget_get_name(p)); } return false; } void LApp::OnDetach(LViewI *View) { LMessageQue::MsgArray *q = MsgQue.Lock(_FL); if (!q) { printf("%s:%i - Couldn't lock app.\n", _FL); return; } MsgQue.Unlock(); } bool LMessage::Send(LViewI *View) { if (!View) { LAssert(!"No view"); return false; } return LAppInst->PostEvent(View, m, a, b); } diff --git a/src/linux/Lgi/View.cpp b/src/linux/Lgi/View.cpp --- a/src/linux/Lgi/View.cpp +++ b/src/linux/Lgi/View.cpp @@ -1,1119 +1,1124 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Linux LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/Css.h" #include "lgi/common/EventTargetThread.h" #include "ViewPriv.h" using namespace Gtk; #include "LgiWidget.h" #define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #endif #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 #if GtkVer(2, 14) #else #define gtk_widget_get_window(widget) ((widget)->window) #endif struct CursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32_t CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; +LCaptureThread *LViewPrivate::CaptureThread = NULL; + //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { LAppInst->Yield(); } bool LgiIsKeyDown(int Key) { LAssert(0); return false; } LKey::LKey(int vkey, uint32_t flags) { } /////////////////////////////////////////////////////////////////////////////////////////////////// LCaptureThread::LCaptureThread(LView *v) : LThread("CaptureThread") { + name = v->GetClass(); + printf("Capture thread for %s\n", name.Get()); view = v->AddDispatch(); - DeleteOnExit = true; + DeleteOnExit = true; Run(); } LCaptureThread::~LCaptureThread() { + printf("~Capture thread for %s\n", name.Get()); Cancel(); // Don't wait.. the thread will exit and delete itself asnyronously. } int LCaptureThread::Main() { while (!IsCancelled()) { LSleep(EventMs); if (!IsCancelled()) PostThreadEvent(view, M_CAPTURE_PULSE); } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view) { PrevMouse.x = PrevMouse.y = -1; } LViewPrivate::~LViewPrivate() { LAssert(PulseThread == 0); if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); } void LView::OnGtkRealize() { if (!d->GotOnCreate) { d->GotOnCreate = true; if (d->WantsFocus) { d->WantsFocus = false; if (GetWindow()) GetWindow()->SetFocus(this, LWindow::GainFocus); } OnCreate(); } for (auto c : Children) { auto gv = c->GetGView(); if (gv) gv->OnGtkRealize(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); OnFocus(f); Invalidate(); if (f) { auto w = GetWindow(); if (w && w->_Root) gtk_widget_grab_focus(w->_Root); else d->WantsFocus = f; } } void LView::_Delete() { ThreadCheck(); SetPulse(); // Remove static references to myself if (_Over == this) _Over = NULL; if (_Capturing == this) _Capturing = NULL; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; // Hierarchy LViewI *c; while ((c = Children[0])) { if (c->GetParent() != (LViewI*)this) { printf("%s:%i - ~LView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); // Misc Pos.ZOff(-1, -1); } LView *&LView::PopupChild() { return d->Popup; } void LgiToGtkCursor(LViewI *v, LCursor c) { static LCursor CurrentCursor = LCUR_Normal; if (!v || c == CurrentCursor) return; CurrentCursor = c; GdkCursorType type = GDK_ARROW; switch (c) { // No cursor #ifdef GDK_BLANK_CURSOR case LCUR_Blank: type = GDK_BLANK_CURSOR; break; #endif /// Normal arrow case LCUR_Normal: type = GDK_ARROW; break; /// Upwards arrow case LCUR_UpArrow: type = GDK_SB_UP_ARROW; break; /// Downwards arrow case LCUR_DownArrow: type = GDK_SB_DOWN_ARROW; break; /// Left arrow case LCUR_LeftArrow: type = GDK_SB_LEFT_ARROW; break; /// Right arrow case LCUR_RightArrow: type = GDK_SB_RIGHT_ARROW; break; /// Crosshair case LCUR_Cross: type = GDK_CROSSHAIR; break; /// Hourglass/watch case LCUR_Wait: type = GDK_WATCH; break; /// Ibeam/text entry case LCUR_Ibeam: type = GDK_XTERM; break; /// Vertical resize (|) case LCUR_SizeVer: type = GDK_DOUBLE_ARROW; break; /// Horizontal resize (-) case LCUR_SizeHor: type = GDK_SB_H_DOUBLE_ARROW; break; /// Diagonal resize (/) case LCUR_SizeBDiag: type = GDK_BOTTOM_LEFT_CORNER; break; /// Diagonal resize (\) case LCUR_SizeFDiag: type = GDK_BOTTOM_RIGHT_CORNER; break; /// All directions resize case LCUR_SizeAll: type = GDK_FLEUR; break; /// A pointing hand case LCUR_PointingHand: type = GDK_HAND2; break; /// A slashed circle case LCUR_Forbidden: type = GDK_X_CURSOR; break; default: type = GDK_ARROW; break; /* case LCUR_SplitV: case LCUR_SplitH: case LCUR_DropCopy: case LCUR_DropMove: LAssert(0); break; */ } OsView h = NULL; auto *w = v->GetWindow(); if (w) h = GTK_WIDGET(w->WindowHandle()); LAssert(v->InThread()); auto wnd = gtk_widget_get_window(h); // LAssert(wnd); if (wnd) { if (type == GDK_ARROW) gdk_window_set_cursor(wnd, NULL); else { GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), type); if (cursor) gdk_window_set_cursor(wnd, cursor); else gdk_window_set_cursor(wnd, NULL); } } } bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if 0 if (!Move) { m.Trace("_Mouse"); ::LArray _m; for (LViewI *i=this; i; i=i->GetParent()) { _m.Add(i); } for (int n=0; n<_m.Length(); n++) { LViewI *i=_m[_m.Length()-1-n]; char s[256]; ZeroObj(s); memset(s, ' ', (n+1)*2); LgiTrace("%s%s %s\n", s, i->GetClass(), i->GetPos().GetStr()); } } #endif #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - _Mouse([%i,%i], %i) View=%p/%s\n", _FL, m.x, m.y, Move, _Over, _Over ? _Over->GetClass() : NULL); #endif if ( /* !_View || */ ( GetWindow() && !GetWindow()->HandleViewMouse(this, m) ) ) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass()); #endif return false; } #if DEBUG_MOUSE_EVENTS // if (!Move) LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (Move) { auto *o = m.Target; if (_Over != o) { #if DEBUG_MOUSE_EVENTS // if (!o) WindowFromPoint(m.x, m.y, true); LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } } LView *Target = NULL; if (_Capturing) Target = dynamic_cast(_Capturing); else Target = dynamic_cast(_Over ? _Over : this); if (!Target) return false; LRect Client = Target->LView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y) || _Capturing) { LgiToGtkCursor(Target, Target->GetCursor(m.x, m.y)); if (Move) { Target->OnMouseMove(m); } else { #if 0 if (!Move) { char Msg[256]; sprintf(Msg, "_Mouse Target %s", Target->GetClass()); m.Trace(Msg); } #endif Target->OnMouseClick(m); } } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } return true; } const char *EventTypeToString(int i) { switch (i) { case GDK_DELETE: return "GDK_DELETE"; case GDK_DESTROY: return "GDK_DESTROY"; case GDK_EXPOSE: return "GDK_EXPOSE"; case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY"; case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS"; case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS"; case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS"; case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE"; case GDK_KEY_PRESS: return "GDK_KEY_PRESS"; case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE"; case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY"; case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY"; case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE"; case GDK_CONFIGURE: return "GDK_CONFIGURE"; case GDK_MAP: return "GDK_MAP"; case GDK_UNMAP: return "GDK_UNMAP"; case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY"; case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR"; case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST"; case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY"; case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN"; case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT"; case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER"; case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE"; case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION"; case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS"; case GDK_DROP_START: return "GDK_DROP_START"; case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED"; case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT"; case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY"; case GDK_SCROLL: return "GDK_SCROLL"; case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE"; case GDK_SETTING: return "GDK_SETTING"; case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE"; case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN"; case GDK_DAMAGE: return "GDK_DAMAGE"; case GDK_TOUCH_BEGIN: return "GDK_TOUCH_BEGIN"; case GDK_TOUCH_UPDATE: return "GDK_TOUCH_UPDATE"; case GDK_TOUCH_END: return "GDK_TOUCH_END"; case GDK_TOUCH_CANCEL: return "GDK_TOUCH_CANCEL"; case GDK_TOUCHPAD_SWIPE: return "GDK_TOUCHPAD_SWIPE"; case GDK_TOUCHPAD_PINCH: return "GDK_TOUCHPAD_PINCH"; #ifdef GDK_PAD_BUTTON_PRESS case GDK_PAD_BUTTON_PRESS: return "GDK_PAD_BUTTON_PRESS"; #endif #ifdef GDK_PAD_BUTTON_RELEASE case GDK_PAD_BUTTON_RELEASE: return "GDK_PAD_BUTTON_RELEASE"; #endif #ifdef GDK_PAD_RING case GDK_PAD_RING: return "GDK_PAD_RING"; #endif #ifdef GDK_PAD_STRIP case GDK_PAD_STRIP: return "GDK_PAD_STRIP"; #endif #ifdef GDK_PAD_GROUP_MODE case GDK_PAD_GROUP_MODE: return "GDK_PAD_GROUP_MODE"; #endif } return "#error"; } gboolean GtkViewCallback(GtkWidget *widget, GdkEvent *event, LView *This) { #if 0 LgiTrace("GtkViewCallback, Event=%s, This=%p(%s\"%s\")\n", EventTypeToString(event->type), This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif if (event->type < 0 || (int)event->type > 1000) { printf("%s:%i - CORRUPT EVENT %i\n", _FL, event->type); return false; } return This->OnGtkEvent(widget, event); } gboolean LView::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { printf("LView::OnGtkEvent ?????\n"); return false; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); c.Inset(Edge, Edge); } return c; } void LView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool LView::SetPos(LRect &p, bool Repaint) { if (Pos != p) { Pos = p; OnPosChange(); } return true; } LRect GtkGetPos(GtkWidget *w) { GtkAllocation a = {0}; gtk_widget_get_allocation(w, &a); return a; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { auto *ParWnd = GetWindow(); if (!ParWnd) return false; // Nothing we can do till we attach if (!InThread()) { DEBUG_INVALIDATE("%s::Invalidate out of thread\n", GetClass()); return PostEvent(M_INVALIDATE, NULL, (LMessage::Param)this); } LRect r; if (rc) { r = *rc; } else { if (Frame) r = Pos.ZeroTranslate(); else r = GetClient().ZeroTranslate(); } DEBUG_INVALIDATE("%s::Invalidate r=%s frame=%i\n", GetClass(), r.GetStr(), Frame); if (!Frame) r.Offset(_BorderSize, _BorderSize); LPoint Offset; WindowVirtualOffset(&Offset); r.Offset(Offset.x, Offset.y); DEBUG_INVALIDATE(" voffset=%i,%i = %s\n", Offset.x, Offset.y, r.GetStr()); if (!r.Valid()) { DEBUG_INVALIDATE(" error: invalid\n"); return false; } static bool Repainting = false; if (!Repainting) { Repainting = true; GtkWidget *w = GTK_WIDGET(ParWnd->WindowHandle()); if (w) { GdkWindow *h; if (gtk_widget_get_has_window(w) && (h = gtk_widget_get_window(w))) { GdkRectangle grc = r; DEBUG_INVALIDATE(" gdk_window_invalidate_rect %i,%i %i,%i\n", grc.x, grc.y, grc.width, grc.height); gdk_window_invalidate_rect(h, &grc, true); } else { DEBUG_INVALIDATE(" gtk_widget_queue_draw\n"); gtk_widget_queue_draw(w); } } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { ThreadCheck(); DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { case M_SET_CTRL_NAME: { LAutoPtr s((LString*)Msg->B()); SetCtrlName(Msg->A(), s ? *s : NULL); break; } case M_SET_CTRL_ENABLE: { SetCtrlEnabled(Msg->A(), Msg->B()); break; } case M_SET_CTRL_VISIBLE: { SetCtrlVisible(Msg->A(), Msg->B()); break; } case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CAPTURE_PULSE: { auto wnd = GetWindow(); if (!wnd) { printf("%s:%i - No window.\n", _FL); break; } LMouse m; if (!wnd->GetMouse(m, true)) { printf("%s:%i - GetMouse failed.\n", _FL); break; } auto c = wnd->GetPos(); if (c.Overlap(m)) break; // Over the window, so we'll get normal events... if (!_Capturing) break; // Don't care now, there is no window capturing... if (d->PrevMouse.x < 0) { d->PrevMouse = m; // Initialize the previous mouse var. break; } int mask = LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT; bool coordsChanged = m.x != d->PrevMouse.x || m.y != d->PrevMouse.y; bool buttonsChanged = (m.Flags & mask) != (d->PrevMouse.Flags & mask); if (!coordsChanged && !buttonsChanged) break; // Nothing changed since last poll.. int prevFlags = d->PrevMouse.Flags; d->PrevMouse = m; // Convert coords to local view... m.Target = _Capturing; #if 0 printf("%s:%i - Mouse(%i,%i) outside window(%s, %s).. %s\n", _FL, m.x, m.y, c.GetStr(), wnd->Name(), m.ToString().Get()); #endif m.ToView(); // Process events... if (coordsChanged) { // printf("Move event: %s\n", m.ToString().Get()); _Capturing->OnMouseMove(m); } /* This seems to happen anyway? Ok then... whatevs if (buttonsChanged) { m.Flags &= ~mask; // clear any existing m.Flags |= prevFlags & mask; m.Down(false); printf("Click event: %s, mask=%x flags=%x\n", m.ToString().Get(), mask, m.Flags); _Capturing->OnMouseClick(m); } */ break; } case M_CHANGE: { LViewI *Ctrl; if (GetViewById(Msg->A(), Ctrl)) return OnNotify(Ctrl, Msg->B()); break; } case M_COMMAND: { return OnCommand(Msg->A(), 0, (OsView) Msg->B()); } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } } return 0; } LPoint GtkGetOrigin(LWindow *w) { auto Hnd = w->WindowHandle(); if (Hnd) { auto Wnd = gtk_widget_get_window(GTK_WIDGET(Hnd)); if (Wnd) { GdkRectangle rect; gdk_window_get_frame_extents(Wnd, &rect); return LPoint(rect.x, rect.y); /* gint x = 0, y = 0; gdk_window_get_origin(Wnd, &x, &y); gdk_window_get_root_origin(Wnd, &x, &y); return LPoint(x, y); */ } else { LgiTrace("%s:%i - can't get Wnd for %s\n", _FL, G_OBJECT_TYPE_NAME(Hnd)); } } return LPoint(); } bool LView::PointToScreen(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p += Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x += x; p.y += y; return true; } bool LView::PointToView(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x -= x; p.y -= y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { bool Status = true; ThreadCheck(); auto *w = GetWindow(); GdkModifierType mask = (GdkModifierType)0; auto display = gdk_display_get_default(); auto deviceManager = gdk_display_get_device_manager(display); auto device = gdk_device_manager_get_client_pointer(deviceManager); gdouble axes[2] = {0}; if (gdk_device_get_axis_use(device, 0) != GDK_AXIS_X) gdk_device_set_axis_use(device, 0, GDK_AXIS_X); if (gdk_device_get_axis_use(device, 1) != GDK_AXIS_Y) gdk_device_set_axis_use(device, 1, GDK_AXIS_Y); if (ScreenCoords || !w) { gdk_device_get_state(device, gdk_get_default_root_window(), axes, &mask); m.x = (int)axes[0]; m.y = (int)axes[1]; } else if (auto widget = GTK_WIDGET(w->WindowHandle())) { auto wnd = gtk_widget_get_window(widget); gdk_device_get_state(device, wnd, axes, &mask); if (axes[0] == 0.0 || axes[0] > 10000) { // LgiTrace("%s:%i - gdk_device_get_state failed.\n", _FL); Status = false; } else { LPoint p; WindowVirtualOffset(&p); m.x = (int)axes[0] - p.x - _BorderSize; m.y = (int)axes[1] - p.y - _BorderSize; // printf("GetMs %g,%g %i,%i %i,%i device=%p wnd=%p\n", axes[0], axes[1], p.x, p.y, m.x, m.y, device, wnd); } } m.SetModifer(mask); m.Left((mask & GDK_BUTTON1_MASK) != 0); m.Middle((mask & GDK_BUTTON2_MASK) != 0); m.Right((mask & GDK_BUTTON3_MASK) != 0); m.Down(m.Left() || m.Middle() || m.Right()); m.ViewCoords = !ScreenCoords; return Status; } bool LView::IsAttached() { auto w = GetWindow(); if (!w) return false; w = dynamic_cast(this); if (!w) { auto p = GetParent(); return d->GotOnCreate && p && p->HasView(this); } return w->IsAttached(); } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { ThreadCheck(); bool Status = false; LView *Parent = d->GetParent(); LAssert(Parent == NULL || Parent == parent); SetParent(parent); Parent = d->GetParent(); auto WndNull = _Window == NULL; _Window = Parent ? Parent->_Window : this; if (parent) { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); Status = true; if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } if (_Window) OnGtkRealize(); } return Status; } bool LView::Detach() { ThreadCheck(); // Detach view if (_Window) { auto *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } LViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); Par->Invalidate(&Pos); } d->Parent = 0; d->ParentI = 0; #if 0 // Windows is not doing a deep detach... so we shouldn't either? { int Count = Children.Length(); if (Count) { int Detached = 0; LViewI *c, *prev = NULL; while ((c = Children[0])) { LAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LAssert(Count == Detached); } } #endif return true; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } void LView::OnGtkDelete() { List::I it = Children.begin(); for (LViewI *c = *it; c; c = *++it) { LView *v = c->GetGView(); if (v) v->OnGtkDelete(); } } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, };