diff --git a/Ide/Code/DocEditStyling.cpp b/Ide/Code/DocEditStyling.cpp --- a/Ide/Code/DocEditStyling.cpp +++ b/Ide/Code/DocEditStyling.cpp @@ -1,1252 +1,1252 @@ #include "Lgi.h" #include "LgiIde.h" #include "DocEdit.h" #define COMP_STYLE 1 #define LOG_STYLE 0 #define PROFILE_STYLE 0 #if PROFILE_STYLE #define PROF(str) Prof.Add(str) #else #define PROF(str) #endif #define ColourComment GColour(0, 140, 0) #define ColourHashDef GColour(0, 0, 222) #define ColourLiteral GColour(192, 0, 0) #define ColourKeyword GColour::Black #define ColourType GColour(0, 0, 222) #define ColourPhp GColour(140, 140, 180) #define ColourHtml GColour(80, 80, 255) #define ColourPre GColour(150, 110, 110) #define ColourStyle GColour(110, 110, 150) #define IsSymbolChar(ch) (IsAlpha(ch) || (ch) == '_') #define DetectKeyword() \ Node *n = &Root; \ char16 *e = s; \ do \ { \ int idx = n->Map(*e); \ if (idx < 0) \ break; \ n = n->Next[idx]; \ e++; \ } \ while (n); struct LanguageParams { const char **Keywords; const char **Types; const char **Edges; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CPP const char *DefaultKeywords[] = {"if", "elseif", "endif", "else", "ifeq", "ifdef", "ifndef", "ifneq", "include", NULL}; const char *CppKeywords[] = {"extern", "class", "struct", "static", "default", "case", "break", "switch", "new", "delete", "sizeof", "return", "enum", "else", "if", "for", "while", "do", "continue", "public", "virtual", "protected", "friend", "union", "template", "typedef", "dynamic_cast", NULL}; -const char *CppTypes[] = { "int", "char", "unsigned", "double", "float", "bool", "const", "void", +const char *CppTypes[] = { "int", "char", "short", "long", "signed", "unsigned", "double", "float", "bool", "const", "void", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "char16", "wchar_t", "GArray", "GHashTbl", "List", "GString", "GAutoString", "GAutoWString", "GAutoPtr", "LHashTbl", NULL}; const char *CppEdges[] = { "/*", "*/", "\"", NULL }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Python const char *PythonKeywords[] = {"def", "try", "except", "import", "if", "for", "elif", "else", "class", NULL}; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // XML /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // HTML const char *HtmlEdges[] = { "", "/*", "*/", "", "", "\"", "\'", NULL }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LanguageParams LangParam[] = { // Unknown {NULL, NULL, NULL}, // Plain text {DefaultKeywords, NULL, NULL}, // C/C++ {CppKeywords, CppTypes, CppEdges}, // Python {PythonKeywords, NULL, NULL}, // Xml {NULL, NULL, NULL}, // Html/Php {NULL, NULL, HtmlEdges} }; DocEditStyling::DocEditStyling(DocEdit *view) : View(view), ParentState(KWaiting), WorkerState(KWaiting), LThread("DocEditStyling.Thread"), LMutex("DocEditStyling.Lock"), Event("DocEditStyling.Event"), Params(view) { } void DocEdit::OnApplyStyles() { #if PROFILE_STYLE GProfile Prof("OnApplyStyles"); #endif PROF("Lock"); GTextView3::GStyle Vis(STYLE_NONE); GetVisible(Vis); if (DocEditStyling::Lock(_FL)) { PROF("Insert"); if (Params.Styles.Length()) { Style.Swap(Params.Styles); #if LOG_STYLE LgiTrace("Swapped in %i styles.\n", (int)Style.Length()); #endif PROF("Inval"); if (Params.Dirty.Start >= 0) { #if LOG_STYLE LgiTrace("Visible rgn: %i + %i = %i\n", Vis.Start, Vis.Len, Vis.End()); LgiTrace("Dirty rgn: %i + %i = %i\n", Params.Dirty.Start, Params.Dirty.Len, Params.Dirty.End()); #endif ssize_t CurLine = -1, DirtyStartLine = -1, DirtyEndLine = -1; GetTextLine(Cursor, &CurLine); GTextLine *Start = GetTextLine(Params.Dirty.Start, &DirtyStartLine); GTextLine *End = GetTextLine(MIN(Size, Params.Dirty.End()), &DirtyEndLine); if (CurLine >= 0 && DirtyStartLine >= 0 && DirtyEndLine >= 0) { #if LOG_STYLE LgiTrace("Dirty lines %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine); #endif if (DirtyStartLine != CurLine || DirtyEndLine != CurLine) { GRect c = GetClient(); GRect r(c.x1, Start->r.Valid() ? DocToScreen(Start->r).y1 : c.y1, c.x2, Params.Dirty.End() >= Vis.End() ? c.y2 : DocToScreen(End->r).y2); #if LOG_STYLE LgiTrace("Cli: %s, Start rgn: %s, End rgn: %s, Update: %s\n", c.GetStr(), Start->r.GetStr(), End->r.GetStr(), r.GetStr()); #endif Invalidate(&r); } } else { #if LOG_STYLE LgiTrace("No Change: %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine); #endif } } else { #if LOG_STYLE LgiTrace("Invalidate everything\n"); #endif Invalidate(); } } PROF("Unlock"); DocEditStyling::Unlock(); } } int DocEditStyling::Main() { LThreadEvent::WaitStatus s; while (ParentState != KExiting && (s = Event.Wait()) == LThreadEvent::WaitSignaled) { StylingParams p(View); if (Lock(_FL)) { Params.Dirty.Empty(); Params.Styles.Empty(); p = Params; Unlock(); } WorkerState = KStyling; #if LOG_STYLE LgiTrace("DocEdit.Worker starting style...\n"); #endif switch (FileType) { case SrcCpp: StyleCpp(p); break; case SrcPython: StylePython(p); break; case SrcXml: StyleXml(p); break; case SrcHtml: StyleHtml(p); break; default: StyleDefault(p); break; } if (ParentState != KCancel) { #if LOG_STYLE LgiTrace("DocEdit.Worker finished style... Items=%i ParentState=%i\n", (int)p.Styles.Length(), ParentState); #endif auto r = View->PostEvent(M_STYLING_DONE); if (ParentState != KExiting) { LgiAssert(r); } } else { #if LOG_STYLE LgiTrace("DocEdit.Worker canceled style...\n"); #endif } if (LMutex::Lock(_FL)) { Params.Dirty = p.Dirty; Params.Styles.Swap(p.Styles); LMutex::Unlock(); } WorkerState = KWaiting; } return 0; } void DocEditStyling::StyleCpp(StylingParams &p) { #if PROFILE_STYLE GProfile Prof("DocEdit::StyleCpp"); #endif if (!p.Text.Length()) return; char16 *Text = p.Text.AddressOf(); char16 *s = Text; char16 *e = s + p.Text.Length(); PROF("Scan"); LUnrolledList Out; for (; ParentState != KCancel && s < e; s++) { switch (*s) { case '\"': case '\'': p.StyleString(s, e, ColourLiteral, &Out); break; case '#': { // Check that the '#' is the first non-whitespace on the line bool IsWhite = true; for (char16 *w = s - 1; w >= Text && *w != '\n'; w--) { if (!IsWhiteSpace(*w)) { IsWhite = false; break; } } if (IsWhite) { auto &st = Out.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); char LastNonWhite = 0; while (s < e) { if ( // Break at end of line (*s == '\n' && LastNonWhite != '\\') || // Or the start of a comment (*s == '/' && s[1] == '/') || (*s == '/' && s[1] == '*') ) break; if (!IsWhiteSpace(*s)) LastNonWhite = *s; s++; } st.Len = (s - Text) - st.Start; st.Fore = ColourHashDef; s--; } break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Out.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } case '/': { if (s[1] == '/') { auto &st = Out.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } else if (s[1] == '*') { auto &st = Out.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); s += 2; while (s < e && !(s[-2] == '*' && s[-1] == '/')) s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } break; } default: { wchar_t Ch = ToLower(*s); if (Ch >= 'a' && Ch <= 'z') { DetectKeyword(); if (n && n->Type) { auto &st = Out.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = n->Type == KType ? View->GetFont() : View->GetBold(); st.Len = e - s; st.Fore = n->Type == KType ? ColourType : ColourKeyword; } else { while ( IsAlpha(*e) || IsDigit(*e) || *e == '_') e++; } s = e - 1; } } } } #if COMP_STYLE PROF("Compare"); auto &Vis = p.Visible; if (Vis.Valid() && ParentState != KCancel && PrevStyle.Length()) { GArray Old, Cur; for (auto s : PrevStyle) { if (s.Overlap(Vis)) Old.Add(&s); else if (Old.Length()) break; } for (auto s : Out) { if (s.Overlap(Vis)) Cur.Add(&s); else if (Cur.Length()) break; } for (int o=0; oGetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } default: { wchar_t Ch = ToLower(*s); if (Ch >= 'a' && Ch <= 'z') { DetectKeyword(); if (n && n->Type) { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = n->Type == KType ? View->GetFont() : View->GetBold(); st.Len = e - s; st.Fore = n->Type == KType ? ColourType : ColourKeyword; } s = e - 1; } break; } } } } void DocEditStyling::StyleDefault(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; for (char16 *s = Text; s < e; s++) { switch (*s) { case '\"': case '\'': case '`': { auto &st = Style.New().Construct(View, STYLE_IDE); bool Quoted = s > Text && s[-1] == '\\'; st.Start = s - Text - Quoted; st.Font = View->GetFont(); char16 Delim = *s++; while ( s < e && *s != Delim && !(Delim == '`' && *s == '\'') ) { if (*s == '\\') { if (!Quoted || s[1] != Delim) s++; } else if (*s == '\n') break; s++; } st.Len = (s - Text) - st.Start + 1; st.Fore = ColourLiteral; break; } case '#': { // Single line comment auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text - ((s > Text && strchr("-+", s[-1])) ? 1 : 0); st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } if (*s == '%') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } default: { if (*s >= 'a' && *s <= 'z') { /* if (HasKeyword[*s - 'a']) { static char16 buf[64], *o = buf; char16 *e = s; while (IsSymbolChar(*e)) *o++ = *e++; *o = 0; KeyworkType type = Keywords.Find(buf); if (type != KNone) { GAutoPtr st(new GTextView3::GStyle(STYLE_IDE)); if (st) { st->View = this; st->Start = s - Text; st->Font = Bold; st->Len = e - s; st->Fore = ColourKeyword; InsertStyle(st); s = e - 1; } } } */ } break; } } } } void DocEditStyling::StyleXml(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; for (char16 *s = Text; s < e; s++) { switch (*s) { case '\"': case '\'': p.StyleString(s, e, ColourLiteral); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } case '<': { if (s[1] == '!' && s[2] == '-' && s[3] == '-') { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); s += 2; while ( s < e && ! ( s[-3] == '-' && s[-2] == '-' && s[-1] == '>' ) ) s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } break; } default: { if (*s >= 'a' && *s <= 'z') { /* if (HasKeyword[*s - 'a']) { static char16 buf[64], *o = buf; char16 *e = s; while (IsSymbolChar(*e)) *o++ = *e++; *o = 0; KeyworkType type = Keywords.Find(buf); if (type != KNone) { GAutoPtr st(new GTextView3::GStyle(STYLE_IDE)); if (st) { st->View = this; st->Start = s - Text; st->Font = Bold; st->Len = e - s; st->Fore = ColourKeyword; InsertStyle(st); s = e - 1; } } } */ } break; } } } } GColour DocEditStyling::ColourFromType(DocType t) { switch (t) { default: return GColour::Black; case CodePhp: return ColourPhp; case CodeCss: return ColourStyle; case CodePre: return ColourPre; case CodeComment: return ColourComment; } } void DocEditStyling::StyleHtml(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; char *Ext = LgiGetExtension(p.FileName); DocType Type = Ext && !stricmp(Ext, "js") ? CodePhp : CodeHtml; GTextView3::GStyle *Cur = NULL; #define START_CODE() \ if (Type != CodeHtml) \ { \ Cur = &Style.New().Construct(View, STYLE_IDE); \ Cur->Start = s - Text; \ Cur->Font = View->GetFont(); \ Cur->Fore = ColourFromType(Type); \ } #define END_CODE() \ if (Cur) \ { \ Cur->Len = (s - Text) - Cur->Start; \ if (Cur->Len <= 0) \ { \ Style.Delete(Style.rbegin()); \ } \ Cur = NULL; \ } char16 *s; for (s = Text; s < e; s++) { switch (*s) { case '\'': { if (s > Text && IsAlpha(s[-1])) break; // else fall through } case '\"': { if (Type != CodeComment) { END_CODE(); p.StyleString(s, e, ColourLiteral); s++; START_CODE(); s--; } break; } case '/': { if (Type != CodeHtml && Type != CodePre) { if (s[1] == '/') { END_CODE(); char16 *nl = Strchr(s, '\n'); if (!nl) nl = s + Strlen(s); auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourComment; st.Len = nl - s; s = nl; START_CODE(); } else if (s[1] == '*') { END_CODE(); char16 *end_comment = Stristr(s, L"*/"); if (!end_comment) end_comment = s + Strlen(s); else end_comment += 2; auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourComment; st.Len = end_comment - s; s = end_comment; START_CODE(); } } break; } case '<': { #define IS_TAG(name) \ ((tmp = strlen(#name)) && \ len == tmp && \ !Strnicmp(tag, L ## #name, tmp)) #define SCAN_TAG() \ char16 *tag = s + 1; \ char16 *c = tag; \ while (c < e && strchr(WhiteSpace, *c)) c++; \ while (c < e && (IsAlpha(*c) || strchr("!_/0123456789", *c))) c++; \ size_t len = c - tag, tmp; if (Type == CodeHtml) { if (s[1] == '?' && s[2] == 'p' && s[3] == 'h' && s[4] == 'p') { // Start PHP block Type = CodePhp; START_CODE(); s += 4; } else if ( s[1] == '!' && s[2] == '-' && s[3] == '-') { // Start comment Type = CodeComment; START_CODE(); s += 3; } else { // Html element SCAN_TAG(); bool start = false; if (IS_TAG(pre)) { Type = CodePre; while (*c && *c != '>') c++; if (*c) c++; start = true; } else if (IS_TAG(style)) { Type = CodeCss; while (*c && *c != '>') c++; if (*c) c++; start = true; } auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourHtml; st.Len = c - s; s = c - 1; if (start) { s++; START_CODE(); } } } else if (Type == CodePre) { SCAN_TAG(); if (IS_TAG(/pre)) { END_CODE(); Type = CodeHtml; s--; } } else if (Type == CodeCss) { SCAN_TAG(); if (IS_TAG(/style)) { END_CODE(); Type = CodeHtml; s--; } } break; } case '>': { if (Type == CodeHtml) { auto &st = Style.New().Construct(View, STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourHtml; st.Len = 1; } else if (Type == CodeComment) { if (s - 2 >= Text && s[-1] == '-' && s[-2] == '-') { s++; END_CODE(); Type = CodeHtml; } } break; } case '?': { if (Type == CodePhp && s[1] == '>') { Type = CodeHtml; s += 2; END_CODE(); s--; } break; } } } END_CODE(); } void DocEditStyling::AddKeywords(const char **keys, bool IsType) { for (const char **k = keys; *k; k++) { Node *n = &Root; for (const char *c = *k; *c && n; c++) { int idx = n->Map(*c); LgiAssert(idx >= 0); if (!n->Next[idx]) n->Next[idx] = new Node; n = n->Next[idx]; } if (n) n->Type = IsType ? KType : KLang; } } void DocEdit::PourStyle(size_t Start, ssize_t EditSize) { if (FileType == SrcUnknown) { char *Ext = LgiGetExtension(Doc->GetFileName()); if (!Ext) FileType = SrcPlainText; else if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "cc") || !stricmp(Ext, "h") || !stricmp(Ext, "hpp") ) FileType = SrcCpp; else if (!stricmp(Ext, "py")) FileType = SrcPython; else if (!stricmp(Ext, "xml")) FileType = SrcXml; else if (!stricmp(Ext, "html") || !stricmp(Ext, "htm") || !stricmp(Ext, "php") || !stricmp(Ext, "js")) FileType = SrcHtml; else FileType = SrcPlainText; if (LangParam[FileType].Keywords) AddKeywords(LangParam[FileType].Keywords, false); if (LangParam[FileType].Types) AddKeywords(LangParam[FileType].Types, true); if (LangParam[FileType].Edges) { RefreshEdges = LangParam[FileType].Edges; RefreshSize = 0; for (const char **e = RefreshEdges; *e; e++) RefreshSize = MAX((int)strlen(*e), RefreshSize); } } // Get into the waiting mode (so the worker is not using 'StyleIn' data) if (WorkerState == KStyling) { #ifdef _DEBUG uint64 Start = LgiMicroTime(); #endif ParentState = KCancel; while (WorkerState != KWaiting) LgiSleep(1); #ifdef _DEBUG uint64 Tm = LgiMicroTime() - Start; LgiTrace("DocEdit: Styling->Waiting time: %.1g ms\n", (double) Tm / 1000.0); #endif } // Create the StyleIn array from the current document // Lock the object and give the text to the worker #if LOG_STYLE LgiTrace("DocEdit starting style...\n"); #endif ParentState = KStyling; if (DocEditStyling::Lock(_FL)) { Params.PourStart = Start; Params.PourSize = EditSize; Params.Dirty.Empty(); Params.Text.Length(Size); Params.FileName = Doc->GetFileName(); GetVisible(Params.Visible); memcpy(Params.Text.AddressOf(), Text, sizeof(*Text) * Size); DocEditStyling::Unlock(); Event.Signal(); } } int DocEdit::CountRefreshEdges(size_t At, ssize_t Len) { if (!RefreshEdges) return 0; size_t s = MAX(0, At - (RefreshSize - 1)); bool t[256] = {0}; for (const char **Edge = RefreshEdges; *Edge; Edge++) { const char *e = *Edge; t[e[0]] = true; } int Edges = 0; for (size_t i = s; i <= At; i++) { if (Text[i] < 256 && t[Text[i]]) { for (const char **Edge = RefreshEdges; *Edge; Edge++) { auto n = i; const char *e; for (e = *Edge; *e; e++) if (Text[n++] != *e) break; if (!*e) Edges++; } } } return Edges; } diff --git a/Ide/Code/IdeProject.cpp b/Ide/Code/IdeProject.cpp --- a/Ide/Code/IdeProject.cpp +++ b/Ide/Code/IdeProject.cpp @@ -1,3845 +1,3853 @@ #if defined(WIN32) #include #else #include #endif #include #include "Lgi.h" #include "LgiIde.h" #include "GDragAndDrop.h" #include "GToken.h" #include "resdefs.h" #include "GProcess.h" #include "GCombo.h" #include "INet.h" #include "LListItemCheckBox.h" #include "FtpThread.h" #include "GClipBoard.h" #include "GDropFiles.h" #include "GSubProcess.h" #include "ProjectNode.h" #include "WebFldDlg.h" #include "GCss.h" #include "GTableLayout.h" #include "GTextLabel.h" #include "GButton.h" extern const char *Untitled; const char SourcePatterns[] = "*.c;*.h;*.cpp;*.cc;*.java;*.d;*.php;*.html;*.css"; const char *AddFilesProgress::DefaultExt = "c,cpp,cc,cxx,h,hpp,hxx,html,css,json,js,jsx,txt,png,jpg,jpeg,rc,xml,mk,paths,makefile,py"; #define USE_OPEN_PROGRESS 1 -#define STOP_BUILD_TIMEOUT 3000 +#define STOP_BUILD_TIMEOUT 2000 #ifdef WINDOWS #define LGI_STATIC_LIBRARY_EXT "lib" #else #define LGI_STATIC_LIBRARY_EXT "a" #endif const char *PlatformNames[] = { "Windows", "Linux", "Mac", "Haiku", 0 }; int PlatformCtrlId[] = { IDC_WIN32, IDC_LINUX, IDC_MAC, IDC_HAIKU, 0 }; char *ToUnixPath(char *s) { if (s) { char *c; while ((c = strchr(s, '\\'))) { *c = '/'; } } return s; } const char *CastEmpty(char *s) { return s ? s : ""; } bool FindInPath(GString &Exe) { GString::Array Path = GString(getenv("PATH")).Split(LGI_PATH_SEPARATOR); for (unsigned i=0; i SubProc; GString::Array BuildConfigs; GString::Array PostBuild; enum CompilerType { DefaultCompiler, VisualStudio, MingW, Gcc, CrossCompiler, PythonScript, IAR, Nmake } Compiler; enum ArchType { DefaultArch, ArchX32, ArchX64, ArchArm6, ArchArm7, } Arch; public: BuildThread(IdeProject *proj, char *makefile, bool clean, bool release, bool all, int wordsize); ~BuildThread(); ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override; GString FindExe(); GAutoString WinToMingWPath(const char *path); int Main() override; }; class IdeProjectPrivate { public: AppWnd *App; IdeProject *Project; bool Dirty, UserFileDirty; GString FileName; IdeProject *ParentProject; IdeProjectSettings Settings; GAutoPtr Thread; LHashTbl, ProjectNode*> Nodes; int NextNodeId; // Threads GAutoPtr CreateMakefile; // User info file GString UserFile; LHashTbl,int> UserNodeFlags; IdeProjectPrivate(AppWnd *a, IdeProject *project) : Project(project), Settings(project) { App = a; Dirty = false; UserFileDirty = false; ParentProject = 0; NextNodeId = 1; } void CollectAllFiles(GTreeNode *Base, GArray &Files, bool SubProjects, int Platform); }; class MakefileThread : public LThread, public LCancel { IdeProjectPrivate *d; IdeProject *Proj; IdePlatform Platform; bool BuildAfterwards; public: MakefileThread(IdeProjectPrivate *priv, IdePlatform platform, bool Build) : LThread("MakefileThread") { d = priv; Proj = d->Project; Platform = platform; BuildAfterwards = Build; Run(); } ~MakefileThread() { Cancel(); while (!IsExited()) LgiSleep(1); } int Main() { const char *PlatformName = PlatformNames[Platform]; const char *PlatformLibraryExt = NULL; const char *PlatformStaticLibExt = NULL; const char *PlatformExeExt = ""; GString LinkerFlags; const char *TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform); const char *CompilerName = d->Settings.GetStr(ProjCompiler); GString CCompilerBinary = "gcc"; GString CppCompilerBinary = "g++"; GStream *Log = d->App->GetBuildLog(); bool IsExecutableTarget = TargetType && !stricmp(TargetType, "Executable"); bool IsDynamicLibrary = TargetType && !stricmp(TargetType, "DynamicLibrary"); LgiAssert(Log); if (!Log) return false; Log->Print("CreateMakefile for '%s'...\n", PlatformName); if (Platform == PlatformWin32) { LinkerFlags = ",--enable-auto-import"; } else { if (IsDynamicLibrary) { LinkerFlags = ",-soname,$(TargetFile)"; } LinkerFlags += ",-export-dynamic,-R."; } char Buf[256]; GAutoString MakeFile = Proj->GetMakefile(); if (!MakeFile) { MakeFile.Reset(NewStr("../Makefile")); } // LGI_LIBRARY_EXT switch (Platform) { case PlatformWin32: PlatformLibraryExt = "dll"; PlatformStaticLibExt = "lib"; PlatformExeExt = ".exe"; break; case PlatformLinux: case PlatformHaiku: PlatformLibraryExt = "so"; PlatformStaticLibExt = "a"; break; case PlatformMac: PlatformLibraryExt = "dylib"; PlatformStaticLibExt = "a"; break; default: LgiAssert(0); break; } if (CompilerName) { if (!stricmp(CompilerName, "cross")) { GString CBin = d->Settings.GetStr(ProjCCrossCompiler, NULL, Platform); if (CBin && !FileExists(CBin)) FindInPath(CBin); if (CBin && FileExists(CBin)) CCompilerBinary = CBin; else Log->Print("%s:%i - Error: C cross compiler '%s' not found.\n", _FL, CBin.Get()); GString CppBin = d->Settings.GetStr(ProjCppCrossCompiler, NULL, Platform); if (CppBin && !FileExists(CppBin)) FindInPath(CppBin); if (CppBin && FileExists(CppBin)) CppCompilerBinary = CppBin; else Log->Print("%s:%i - Error: C++ cross compiler '%s' not found.\n", _FL, CppBin.Get()); } } GFile m; if (!m.Open(MakeFile, O_WRITE)) { Log->Print("Error: Failed to open '%s' for writing.\n", MakeFile.Get()); return false; } m.SetSize(0); m.Print("#!/usr/bin/make\n" "#\n" "# This makefile generated by LgiIde\n" "# http://www.memecode.com/lgi.php\n" "#\n" "\n" ".SILENT :\n" "\n" "CC = %s\n" "CPP = %s\n", CCompilerBinary.Get(), CppCompilerBinary.Get()); // Collect all files that require building GArray Files; d->CollectAllFiles ( Proj, Files, false, 1 << Platform ); if (IsExecutableTarget) { GString Exe = Proj->GetExecutable(Platform); if (Exe) { if (LgiIsRelativePath(Exe)) m.Print("Target = %s\n", Exe.Get()); else { GAutoString Base = Proj->GetBasePath(); GAutoString RelExe = LgiMakeRelativePath(Base, Exe); if (Base && RelExe) { m.Print("Target = %s\n", RelExe.Get()); } else { Log->Print("%s:%i - Error: Missing path (%s, %s).\n", _FL, Base.Get(), RelExe.Get()); return false; } } } else { Log->Print("%s:%i - Error: No executable name specified (%s, %s).\n", _FL, TargetType, d->FileName.Get()); return false; } } else { GString Target = Proj->GetTargetName(Platform); if (Target) m.Print("Target = %s\n", Target.Get()); else { Log->Print("%s:%i - Error: No target name specified.\n", _FL); return false; } } // Output the build mode, flags and some paths int BuildMode = d->App->GetBuildMode(); char *BuildModeName = BuildMode ? (char*)"Release" : (char*)"Debug"; m.Print("ifndef Build\n" " Build = %s\n" "endif\n", BuildModeName); GString sDefines[2]; GString sLibs[2]; GString sIncludes[2]; const char *ExtraLinkFlags = NULL; const char *ExeFlags = NULL; if (Platform == PlatformWin32) { ExtraLinkFlags = ""; ExeFlags = " -mwindows"; m.Print("BuildDir = $(Build)\n" "\n" "Flags = -fPIC -w -fno-inline -fpermissive\n"); const char *DefDefs = "-DWIN32 -D_REENTRANT"; sDefines[0] = DefDefs; sDefines[1] = DefDefs; } else { char PlatformCap[32]; strcpy_s( PlatformCap, sizeof(PlatformCap), Platform == PlatformHaiku ? "BEOS" : PlatformName); strupr(PlatformCap); ExtraLinkFlags = ""; ExeFlags = ""; m.Print("BuildDir = $(Build)\n" "\n" "Flags = -fPIC -w -fno-inline -fpermissive\n" // -fexceptions ); sDefines[0].Printf("-D%s -D_REENTRANT", PlatformCap); #ifdef LINUX sDefines[0] += " -D_FILE_OFFSET_BITS=64"; // >:-( sDefines[0] += " -DPOSIX"; #endif sDefines[1] = sDefines[0]; } List Deps; Proj->GetChildProjects(Deps); const char *sConfig[] = {"Debug", "Release"}; for (int Cfg = 0; Cfg < CountOf(sConfig); Cfg++) { // Set the config d->Settings.SetCurrentConfig(sConfig[Cfg]); // Get the defines setup const char *PDefs = d->Settings.GetStr(ProjDefines, NULL, Platform); if (ValidStr(PDefs)) { GToken Defs(PDefs, " ;,\r\n"); for (int i=0; iSettings.GetStr(ProjLibraryPaths, NULL, Platform); if (ValidStr(PLibPaths)) { GString::Array LibPaths = PLibPaths.Split("\n"); for (unsigned i=0; iSettings.GetStr(ProjLibraries, NULL, Platform); if (ValidStr(PLibs)) { GToken Libs(PLibs, "\r\n"); for (int i=0; iGetTargetName(Platform); if (Target) { char t[MAX_PATH]; strcpy_s(t, sizeof(t), Target); if (!strnicmp(t, "lib", 3)) memmove(t, t + 3, strlen(t + 3) + 1); char *dot = strrchr(t, '.'); if (dot) *dot = 0; GString s; s.Printf(" \\\n\t\t-l%s$(Tag)", ToUnixPath(t)); sLibs[Cfg] += s; GAutoString Base = dep->GetBasePath(); if (Base) { s.Printf(" \\\n\t\t-L%s/$(BuildDir)", ToUnixPath(Base)); sLibs[Cfg] += s; } } } // Includes // Do include paths LHashTbl,bool> Inc; const char *ProjIncludes = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); if (ValidStr(ProjIncludes)) { // Add settings include paths. GToken Paths(ProjIncludes, "\r\n"); for (int i=0; iSettings.GetStr(ProjSystemIncludes, NULL, Platform); if (ValidStr(SysIncludes)) { // Add settings include paths. GToken Paths(SysIncludes, "\r\n"); for (int i=0; iGetFileName()) { char *e = LgiGetExtension(n->GetFileName()); if (e && stricmp(e, "h") == 0) { for (char *Dir=n->GetFileName(); *Dir; Dir++) { if (*Dir == '/' || *Dir == '\\') { *Dir = DIR_CHAR; } } char Path[256]; strcpy_s(Path, sizeof(Path), n->GetFileName()); LgiTrimDir(Path); char Rel[256]; if (!Proj->RelativePath(Rel, Path)) { strcpy(Rel, Path); } if (stricmp(Rel, ".") != 0) { GAutoString RelN = ToNativePath(Rel); if (!Inc.Find(RelN)) { Inc.Add(RelN, true); } } } } } List Incs; // char *i; // for (bool b=Inc.First(&i); b; b=Inc.Next(&i)) for (auto i : Inc) { Incs.Insert(NewStr(i.key)); } Incs.Sort(StrCmp); for (auto i = Incs.First(); i; i = Incs.Next()) { GString s; if (*i == '`') s.Printf(" \\\n\t\t%s", i); else s.Printf(" \\\n\t\t-I%s", ToUnixPath(i)); sIncludes[Cfg] += s; } } // Output the defs section for Debug and Release // Debug specific m.Print("\n" "ifeq ($(Build),Debug)\n" " Flags += -g -std=c++11\n" " Tag = d\n" " Defs = -D_DEBUG %s\n" " Libs = %s\n" " Inc = %s\n", CastEmpty(sDefines[0].Get()), CastEmpty(sLibs[0].Get()), CastEmpty(sIncludes[0].Get())); // Release specific m.Print("else\n" " Flags += -s -Os -std=c++11\n" " Defs = %s\n" " Libs = %s\n" " Inc = %s\n" "endif\n" "\n", CastEmpty(sDefines[1].Get()), CastEmpty(sLibs[1].Get()), CastEmpty(sIncludes[1].Get())); if (Files.First()) { GArray IncPaths; if (Proj->BuildIncludePaths(IncPaths, false, false, Platform)) { // Do dependencies m.Print("# Dependencies\n" "Depends =\t"); for (int c = 0; c < Files.Length() && !IsCancelled(); c++) { ProjectNode *n = Files[c]; if (n->GetType() == NodeSrc) { GAutoString f = ToNativePath(n->GetFileName()); char *d = f ? strrchr(f, DIR_CHAR) : 0; char *file = d ? d + 1 : f; d = file ? strrchr(file, '.') : 0; if (d) { if (c) m.Print(" \\\n\t\t\t"); m.Print("%.*s.o", d - file, file); } } } m.Print("\n\n"); // Write out the target stuff m.Print("# Target\n"); LHashTbl,bool> DepFiles; if (TargetType) { if (IsExecutableTarget) { m.Print("# Executable target\n" "$(Target) :"); GStringPipe Rules; IdeProject *Dep; uint64 Last = LgiCurrentTime(); int Count = 0; for (Dep=Deps.First(); Dep && !IsCancelled(); Dep=Deps.Next(), Count++) { // Get dependency to create it's own makefile... Dep->CreateMakefile(Platform, false); // Build a rule to make the dependency if any of the source changes... char t[MAX_PATH] = ""; GAutoString DepBase = Dep->GetBasePath(); GAutoString Base = Proj->GetBasePath(); if (DepBase && Base && Dep->GetTargetFile(t, sizeof(t))) { char Rel[MAX_PATH] = ""; if (!Proj->RelativePath(Rel, DepBase)) { strcpy_s(Rel, sizeof(Rel), DepBase); } ToUnixPath(Rel); // Add tag to target name GToken Parts(t, "."); if (Parts.Length() == 2) sprintf_s(t, sizeof(t), "lib%s$(Tag).%s", Parts[0], Parts[1]); else sprintf_s(t, sizeof(t), "%s", Parts[0]); sprintf(Buf, "%s/$(BuildDir)/%s", Rel, t); m.Print(" %s", Buf); GArray AllDeps; Dep->GetAllDependencies(AllDeps, Platform); LgiAssert(AllDeps.Length() > 0); AllDeps.Sort(StrSort); Rules.Print("%s : ", Buf); for (int i=0; iRelativePath(Rel, AllDeps[i]) ? Rel : AllDeps[i]; ToUnixPath(f); Rules.Print("%s", f); // Add these dependencies to this makefiles dep list if (!DepFiles.Find(f)) DepFiles.Add(f, true); } AllDeps.DeleteArrays(); Rules.Print("\n\texport Build=$(Build); \\\n" "\t$(MAKE) -C %s", Rel); GAutoString Mk = Dep->GetMakefile(); // RenameMakefileForPlatform(Mk, Platform); char *DepMakefile = strrchr(Mk, DIR_CHAR); if (DepMakefile) { Rules.Print(" -f %s", DepMakefile + 1); } Rules.Print("\n\n"); } uint64 Now = LgiCurrentTime(); if (Now - Last > 1000) { Last = Now; Log->Print("Building deps %i%%...\n", (int) (((int64)Count+1)*100/Deps.Length())); } } m.Print(" outputfolder $(Depends)\n" " @echo Linking $(Target) [$(Build)]...\n" " $(CPP)%s%s %s%s -o \\\n" " $(Target) $(addprefix $(BuildDir)/,$(Depends)) $(Libs)\n", ExtraLinkFlags, ExeFlags, ValidStr(LinkerFlags) ? "-Wl" : "", LinkerFlags.Get()); if (Platform == PlatformHaiku) { // Is there an application icon configured? const char *AppIcon = d->Settings.GetStr(ProjApplicationIcon, NULL, Platform); if (AppIcon) { m.Print(" addattr -f %s -t \"'VICN'\" \"BEOS:ICON\" $(Target)\n", AppIcon); } } GString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { GString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; i /dev/null\n" "\n"); m.Print("# Clean just this target\n" "clean :\n" " rm -f $(BuildDir)/*.o $(Target)%s\n" " @echo Cleaned $(BuildDir)\n" "\n", LGI_EXECUTABLE_EXT); m.Print("# Clean all targets\n" "cleanall :\n" " rm -f $(BuildDir)/*.o $(Target)%s\n" " @echo Cleaned $(BuildDir)\n", LGI_EXECUTABLE_EXT); for (IdeProject *d=Deps.First(); d; d=Deps.Next()) { GAutoString mk = d->GetMakefile(); if (mk) { GAutoString my_base = Proj->GetBasePath(); GAutoString dep_base = d->GetBasePath(); GAutoString rel_dir = LgiMakeRelativePath(my_base, dep_base); char *mk_leaf = strrchr(mk, DIR_CHAR); m.Print(" +make -C \"%s\" -f \"%s\" clean\n", ToUnixPath(rel_dir ? rel_dir.Get() : dep_base.Get()), ToUnixPath(mk_leaf ? mk_leaf + 1 : mk.Get())); } } m.Print("\n"); } // Shared library else if (!stricmp(TargetType, "DynamicLibrary")) { m.Print("TargetFile = lib$(Target)$(Tag).%s\n" "$(TargetFile) : outputfolder $(Depends)\n" " @echo Linking $(TargetFile) [$(Build)]...\n" " $(CPP)$s -shared \\\n" " %s%s \\\n" " -o $(BuildDir)/$(TargetFile) \\\n" " $(addprefix $(BuildDir)/,$(Depends)) \\\n" " $(Libs)\n", PlatformLibraryExt, ValidStr(ExtraLinkFlags) ? "-Wl" : "", ExtraLinkFlags, LinkerFlags.Get()); GString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { GString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; i /dev/null\n" "\n" "# Clean out targets\n" "clean :\n" " rm -f $(BuildDir)/*.o $(BuildDir)/$(TargetFile)\n" " @echo Cleaned $(BuildDir)\n" "\n", PlatformLibraryExt); } // Static library else if (!stricmp(TargetType, "StaticLibrary")) { m.Print("TargetFile = lib$(Target)$(Tag).%s\n" "$(TargetFile) : outputfolder $(Depends)\n" " @echo Linking $(TargetFile) [$(Build)]...\n" " ar rcs $(BuildDir)/$(TargetFile) $(addprefix $(BuildDir)/,$(Depends))\n", PlatformStaticLibExt); GString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { GString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; i /dev/null\n" "\n" "# Clean out targets\n" "clean :\n" " rm -f $(BuildDir)/*.o $(BuildDir)/$(TargetFile)\n" " @echo Cleaned $(BuildDir)\n" "\n", PlatformStaticLibExt); } } // Create dependency tree, starting with all the source files. for (int idx=0; idxGetType() == NodeSrc) { GString Src = n->GetFullPath(); if (Src) { char Part[256]; char *d = strrchr(Src, DIR_CHAR); d = d ? d + 1 : Src.Get(); strcpy(Part, d); char *Dot = strrchr(Part, '.'); if (Dot) *Dot = 0; char Rel[MAX_PATH]; if (Platform != PlatformHaiku) { if (!Proj->RelativePath(Rel, Src)) strcpy_s(Rel, sizeof(Rel), Src); } else { // Use full path for Haiku because the Debugger needs it to // find the source correctly. As there are duplicate filenames // for different platforms it's better to rely on full paths // rather than filename index to find the right file. strcpy_s(Rel, sizeof(Rel), Src); } m.Print("%s.o : %s ", Part, ToUnixPath(Rel)); GArray SrcDeps; if (Proj->GetDependencies(Src, IncPaths, SrcDeps, Platform)) { for (int i=0; i,bool> Processed; GAutoString Base = Proj->GetBasePath(); while (!Done) { Done = true; // char *Src; // for (bool b=DepFiles.First(&Src); b; b=DepFiles.Next(&Src)) for (auto it : DepFiles) { if (IsCancelled()) break; if (Processed.Find(it.key)) continue; Done = false; Processed.Add(it.key, true); char Full[MAX_PATH], Rel[MAX_PATH]; if (LgiIsRelativePath(it.key)) { LgiMakePath(Full, sizeof(Full), Base, it.key); strcpy_s(Rel, sizeof(Rel), it.key); } else { strcpy_s(Full, sizeof(Full), it.key); GAutoString a = LgiMakeRelativePath(Base, it.key); if (a) { strcpy_s(Rel, sizeof(Rel), a); } else { strcpy_s(Rel, sizeof(Rel), a); LgiTrace("%s:%i - Failed to make relative path '%s' '%s'\n", _FL, Base.Get(), it.key); } } char *c8 = ReadTextFile(Full); if (c8) { GArray Headers; if (BuildHeaderList(c8, Headers, IncPaths, false)) { m.Print("%s : ", Rel); for (int n=0; nRelativePath(Rel, i)) { strcpy(Rel, i); } if (stricmp(i, Full) != 0) m.Print("%s", ToUnixPath(Rel)); if (!DepFiles.Find(i)) { DepFiles.Add(i, true); } } Headers.DeleteArrays(); m.Print("\n\n"); } else LgiTrace("%s:%i - Error: BuildHeaderList failed for '%s'\n", _FL, Full); DeleteArray(c8); } else LgiTrace("%s:%i - Error: Failed to read '%s'\n", _FL, Full); break; } } // Output VPATH m.Print("VPATH=%%.cpp \\\n"); for (int i=0; iSettings.GetStr(ProjMakefileRules, NULL, Platform); if (ValidStr(OtherMakefileRules)) { m.Print("\n%s\n", OtherMakefileRules); } } } else { m.Print("# No files require building.\n"); } Log->Print("...Done: '%s'\n", MakeFile.Get()); if (BuildAfterwards) { if (!Proj->GetApp()->PostEvent(M_START_BUILD)) printf("%s:%i - PostEvent(M_START_BUILD) failed.\n", _FL); } return true; } }; ///////////////////////////////////////////////////////////////////////////////////// NodeSource::~NodeSource() { if (nView) { nView->nSrc = 0; } } NodeView::~NodeView() { if (nSrc) { nSrc->nView = 0; } } ////////////////////////////////////////////////////////////////////////////////// int NodeSort(GTreeItem *a, GTreeItem *b, NativeInt d) { ProjectNode *A = dynamic_cast(a); ProjectNode *B = dynamic_cast(b); if (A && B) { if ( (A->GetType() == NodeDir) ^ (B->GetType() == NodeDir) ) { return A->GetType() == NodeDir ? -1 : 1; } else { char *Sa = a->GetText(0); char *Sb = b->GetText(0); if (Sa && Sb) { return stricmp(Sa, Sb); } } } return 0; } int XmlSort(GXmlTag *a, GXmlTag *b, NativeInt d) { GTreeItem *A = dynamic_cast(a); GTreeItem *B = dynamic_cast(b); return NodeSort(A, B, d); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool ReadVsProjFile(GString File, GString &Ver, GString::Array &Configs) { const char *Ext = LgiGetExtension(File); if (!Ext || _stricmp(Ext, "vcxproj")) return false; GFile f; if (!f.Open(File, O_READ)) return false; GXmlTree Io; GXmlTag r; if (Io.Read(&r, &f) && r.IsTag("Project")) { Ver = r.GetAttr("ToolsVersion"); GXmlTag *ItemGroup = r.GetChildTag("ItemGroup"); if (ItemGroup) for (GXmlTag *c = ItemGroup->Children.First(); c; c = ItemGroup->Children.Next()) { if (c->IsTag("ProjectConfiguration")) { char *s = c->GetAttr("Include"); if (s) Configs.New() = s; } } } else return false; return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// BuildThread::BuildThread(IdeProject *proj, char *makefile, bool clean, bool release, bool all, int wordsize) : LThread("BuildThread") { Proj = proj; Makefile = makefile; Clean = clean; Release = release; All = all; WordSize = wordsize; Arch = DefaultArch; Compiler = DefaultCompiler; GString Cmds = proj->d->Settings.GetStr(ProjPostBuildCommands, NULL); if (ValidStr(Cmds)) PostBuild = Cmds.SplitDelimit("\r\n"); char *Ext = LgiGetExtension(Makefile); if (Ext && !_stricmp(Ext, "py")) { Compiler = PythonScript; } else if (Ext && !_stricmp(Ext, "sln")) { Compiler = VisualStudio; } else { GAutoString Comp(NewStr(Proj->GetSettings()->GetStr(ProjCompiler))); if (Comp) { // Use the specified compiler... if (!stricmp(Comp, "VisualStudio")) Compiler = VisualStudio; else if (!stricmp(Comp, "MingW")) Compiler = MingW; else if (!stricmp(Comp, "gcc")) Compiler = Gcc; else if (!stricmp(Comp, "cross")) Compiler = CrossCompiler; else if (!stricmp(Comp, "IAR")) Compiler = IAR; else LgiAssert(!"Unknown compiler."); } } if (Compiler == DefaultCompiler) { // Use default compiler for platform... #ifdef WIN32 Compiler = VisualStudio; #else Compiler = Gcc; #endif } Run(); } BuildThread::~BuildThread() { if (SubProc) - SubProc->Interrupt(); + { + bool b = SubProc->Interrupt(); + LgiTrace("%s:%i - Sub process interrupt = %i.\n", _FL, b); + } + else LgiTrace("%s:%i - No sub process to interrupt.\n", _FL); uint64 Start = LgiCurrentTime(); bool Killed = false; while (!IsExited()) { LgiSleep(10); if (LgiCurrentTime() - Start > STOP_BUILD_TIMEOUT && SubProc) { if (Killed) { // Thread is stuck as well... ok kill that too!!! Argh - kill all the things!!!! Terminate(); + LgiTrace("%s:%i - Thread killed.\n", _FL); + Proj->GetApp()->PostEvent(M_BUILD_DONE); break; } else { // Kill the sub-process... - SubProc->Kill(); + bool b = SubProc->Kill(); Killed = true; + LgiTrace("%s:%i - Sub process killed.\n", _FL, b); Start = LgiCurrentTime(); } } } } ssize_t BuildThread::Write(const void *Buffer, ssize_t Size, int Flags) { if (Proj->GetApp()) { Proj->GetApp()->PostEvent(M_APPEND_TEXT, (GMessage::Param)NewStr((char*)Buffer, Size), 0); } return Size; } #pragma comment(lib, "version.lib") struct ProjInfo { GString Guid, Name, File; LHashTbl,int> Configs; }; GString BuildThread::FindExe() { GToken p(getenv("PATH"), LGI_PATH_SEPARATOR); if (Compiler == PythonScript) { #if defined(WINDOWS) uint32_t BestVer = 0; #endif GString Best; for (int i=0; idwProductVersionMS > BestVer) { BestVer = v->dwProductVersionMS; Best = Path; } } } else if (!Best) { Best = Path; } free(Buf); #else Best = Path; break; #endif } } return Best; } else if (Compiler == VisualStudio) { // Find the version we need: double fVer = 0.0; ProjInfo *First = NULL; LHashTbl, ProjInfo*> Projects; const char *Ext = LgiGetExtension(Makefile); if (Ext && !_stricmp(Ext, "sln")) { GFile f; if (f.Open(Makefile, O_READ)) { GString VerKey = "Format Version "; GString ProjKey = "Project("; GString StartSection = "GlobalSection("; GString EndSection = "EndGlobalSection"; GString Section; ssize_t Pos; GString::Array Ln = f.Read().SplitDelimit("\r\n"); for (size_t i = 0; i < Ln.Length(); i++) { GString s = Ln[i]; if ((Pos = s.Find(VerKey)) > 0) { GString sVer = s(Pos + VerKey.Length(), -1); fVer = sVer.Float(); } else if ((Pos = s.Find(ProjKey)) >= 0) { GString::Array p = s.SplitDelimit("(),="); if (p.Length() > 5) { ProjInfo *i = new ProjInfo; i->Name = p[3].Strip(" \t\""); i->File = p[4].Strip(" \t\'\""); i->Guid = p[5].Strip(" \t\""); if (LgiIsRelativePath(i->File)) { char f[MAX_PATH]; LgiMakePath(f, sizeof(f), Makefile, ".."); LgiMakePath(f, sizeof(f), f, i->File); if (FileExists(f)) i->File = f; else LgiAssert(0); } if (!First) First = i; Projects.Add(i->Guid, i); } } else if (s.Find(StartSection) >= 0) { auto p = s.SplitDelimit("() \t"); Section = p[1]; } else if (s.Find(EndSection) >= 0) { Section.Empty(); } else if (Section == "ProjectConfigurationPlatforms") { auto p = s.SplitDelimit(". \t"); auto i = Projects.Find(p[0]); if (i) { if (!i->Configs.Find(p[1])) { int Idx = i->Configs.Length() + 1; i->Configs.Add(p[1], Idx); } } } } } } else if (Ext && !_stricmp(Ext, "vcxproj")) { // ProjFile = Makefile; } else { if (Arch == DefaultArch) { if (sizeof(size_t) == 4) Arch = ArchX32; else Arch = ArchX64; } #ifdef _MSC_VER // Nmake file.. GString NmakePath; switch (_MSC_VER) { case _MSC_VER_VS2013: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\nmake.exe"; break; } case _MSC_VER_VS2015: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\nmake.exe"; break; } default: { LgiAssert(!"Impl me."); break; } } if (FileExists(NmakePath)) { Compiler = Nmake; return NmakePath; } #endif } /* if (ProjFile && FileExists(ProjFile)) { GString sVer; if (ReadVsProjFile(ProjFile, sVer, BuildConfigs)) { fVer = sVer.Float(); } } */ if (First) { for (auto i: First->Configs) { BuildConfigs[i.value - 1] = i.key; } } if (fVer > 0.0) { GString p; p.Printf("C:\\Program Files (x86)\\Microsoft Visual Studio %.1f\\Common7\\IDE\\devenv.com", fVer); if (FileExists(p)) { return p; } } } else if (Compiler == IAR) { // const char *Def = "c:\\Program Files (x86)\\IAR Systems\\Embedded Workbench 7.0\\common\\bin\\IarBuild.exe"; GString ProgFiles = LGetSystemPath(LSP_USER_APPS, 32); char p[MAX_PATH]; if (!LgiMakePath(p, sizeof(p), ProgFiles, "IAR Systems")) return GString(); GDirectory d; double LatestVer = 0.0; GString Latest; for (int b = d.First(p); b; b = d.Next()) { if (d.IsDir()) { GString n(d.GetName()); GString::Array p = n.Split(" "); if (p.Length() == 3 && p[2].Float() > LatestVer) { LatestVer = p[2].Float(); Latest = n; } } } if (Latest && LgiMakePath(p, sizeof(p), p, Latest) && LgiMakePath(p, sizeof(p), p, "common\\bin\\IarBuild.exe")) { if (FileExists(p)) return p; } } else { if (Compiler == MingW) { // Have a look in the default spot first... const char *Def = "C:\\MinGW\\msys\\1.0\\bin\\make.exe"; if (FileExists(Def)) { return Def; } } for (int i=0; iGetApp()->GetOptions()->GetValue(OPT_Jobs, Jobs) || Jobs.CastInt32() < 1) Jobs = 2; auto Pos = InitDir.RFind(DIR_STR); if (Pos) InitDir.Length(Pos); GString TmpArgs, Include, Lib, LibPath, Path; if (Compiler == VisualStudio) { // TmpArgs.Printf("\"%s\" /make \"All - Win32 Debug\"", Makefile.Get()); GString BuildConf = "All - Win32 Debug"; if (BuildConfigs.Length()) { const char *Key = Release ? "Release" : "Debug"; for (size_t i=0; i= 0) { BuildConf = c; break; } } } TmpArgs.Printf("\"%s\" %s \"%s\"", Makefile.Get(), Clean ? "/Clean" : "/Build", BuildConf.Get()); } else if (Compiler == Nmake) { const char *DefInc[] = { "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\INCLUDE", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\ATLMFC\\INCLUDE", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt" }; GString f; #define ADD_PATHS(out, in) \ for (unsigned i=0; iGetChildTag("name") : NULL; if (c) Conf = c->GetContent(); } } TmpArgs.Printf("\"%s\" %s %s -log warnings", Makefile.Get(), Clean ? "-clean" : "-make", Conf.Get()); } else { if (Compiler == MingW) { GString a; char *Dir = strrchr(MakePath, DIR_CHAR); #if 1 TmpArgs.Printf("/C \"%s\"", Exe.Get()); /* As of MSYS v1.0.18 the support for multiple jobs causes make to hang: http://sourceforge.net/p/mingw/bugs/1950/ http://mingw-users.1079350.n2.nabble.com/MSYS-make-freezes-td7579038.html Apparently it'll be "fixed" in v1.0.19. We'll see. >:-( if (Jobs.CastInt32() > 1) a.Print(" -j %i", Jobs.CastInt32()); */ a.Printf(" -f \"%s\"", Dir ? Dir + 1 : MakePath.Get()); TmpArgs += a; #else TmpArgs.Printf("/C set"); #endif Exe = "C:\\Windows\\System32\\cmd.exe"; } else { if (Jobs.CastInt32()) TmpArgs.Printf("-j %i -f \"%s\"", Jobs.CastInt32(), MakePath.Get()); else TmpArgs.Printf("-f \"%s\"", MakePath.Get()); } if (Clean) { if (All) { TmpArgs += " cleanall"; } else { TmpArgs += " clean"; } } if (Release) TmpArgs += " Build=Release"; } GString Msg; Msg.Printf("Making: %s\n", MakePath.Get()); Proj->GetApp()->PostEvent(M_APPEND_TEXT, (GMessage::Param)NewStr(Msg), 0); LgiTrace("%s %s\n", Exe.Get(), TmpArgs.Get()); if (SubProc.Reset(new GSubProcess(Exe, TmpArgs))) { + SubProc->SetNewGroup(false); SubProc->SetInitFolder(InitDir); if (Include) SubProc->SetEnvironment("INCLUDE", Include); if (Lib) SubProc->SetEnvironment("LIB", Lib); if (LibPath) SubProc->SetEnvironment("LIBPATHS", LibPath); if (Path) { GString Cur = getenv("PATH"); GString New = Cur + LGI_PATH_SEPARATOR + Path; SubProc->SetEnvironment("PATH", New); } // SubProc->SetEnvironment("DLL", "1"); if (Compiler == MingW) SubProc->SetEnvironment("PATH", "c:\\MingW\\bin;C:\\MinGW\\msys\\1.0\\bin;%PATH%"); if ((Status = SubProc->Start(true, false))) { // Read all the output char Buf[256]; ssize_t rd; while ( (rd = SubProc->Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } uint32_t ex = SubProc->Wait(); Print("Make exited with %i (0x%x)\n", ex, ex); if (Compiler == IAR && ex == 0 && PostBuild.Length()) { for (auto Cmd : PostBuild) { auto p = Cmd.Split(" ", 1); if (p[0].Equals("cd")) { if (p.Length() > 1) FileDev->SetCurrentFolder(p[1]); else LgiAssert(!"No folder for cd?"); } else { GSubProcess PostCmd(p[0], p.Length() > 1 ? p[1] : NULL); if (PostCmd.Start(true, false)) { char Buf[256]; ssize_t rd; while ( (rd = PostCmd.Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } } } } } } else { // Create a nice error message. GAutoString ErrStr = LgiErrorCodeToString(SubProc->GetErrorCode()); if (ErrStr) { char *e = ErrStr + strlen(ErrStr); while (e > ErrStr && strchr(" \t\r\n.", e[-1])) *(--e) = 0; } sprintf_s(ErrBuf, sizeof(ErrBuf), "Running make failed with %i (%s)\n", SubProc->GetErrorCode(), ErrStr.Get()); Err = ErrBuf; } } } else { Err = "Couldn't find program to build makefile."; LgiTrace("%s,%i - %s.\n", _FL, Err); } AppWnd *w = Proj->GetApp(); if (w) { w->PostEvent(M_BUILD_DONE); if (Err) Proj->GetApp()->PostEvent(M_BUILD_ERR, 0, (GMessage::Param)NewStr(Err)); } else LgiAssert(0); return 0; } ////////////////////////////////////////////////////////////////////////////////// IdeProject::IdeProject(AppWnd *App) : IdeCommon(NULL) { Project = this; d = new IdeProjectPrivate(App, this); Tag = NewStr("Project"); } IdeProject::~IdeProject() { d->App->OnProjectDestroy(this); GXmlTag::Empty(true); DeleteObj(d); } bool IdeProject::OnNode(const char *Path, ProjectNode *Node, bool Add) { if (!Path || !Node) { LgiAssert(0); return false; } char Full[MAX_PATH]; if (LgiIsRelativePath(Path)) { GAutoString Base = GetBasePath(); if (LgiMakePath(Full, sizeof(Full), Base, Path)) { Path = Full; } } bool Status = false; if (Add) Status = d->Nodes.Add(Path, Node); else Status = d->Nodes.Delete(Path); if (Status) d->App->OnNode(Path, Node, Add ? FindSymbolSystem::FileAdd : FindSymbolSystem::FileRemove); return Status; } void IdeProject::ShowFileProperties(const char *File) { ProjectNode *Node = NULL; // char *fp = FindFullPath(File, &Node); if (Node) { Node->OnProperties(); } } const char *IdeProject::GetFileComment() { return d->Settings.GetStr(ProjCommentFile); } const char *IdeProject::GetFunctionComment() { return d->Settings.GetStr(ProjCommentFunction); } IdeProject *IdeProject::GetParentProject() { return d->ParentProject; } void IdeProject::SetParentProject(IdeProject *p) { d->ParentProject = p; } bool IdeProject::GetChildProjects(List &c) { CollectAllSubProjects(c); return c.First() != 0; } bool IdeProject::RelativePath(char *Out, const char *In, bool Debug) { if (Out && In) { GAutoString Base = GetBasePath(); if (Base) { if (Debug) LgiTrace("XmlBase='%s'\n In='%s'\n", Base.Get(), In); GToken b(Base, DIR_STR); GToken i(In, DIR_STR); if (Debug) LgiTrace("Len %i-%i\n", b.Length(), i.Length()); auto ILen = i.Length() + (DirExists(In) ? 0 : 1); auto Max = MIN(b.Length(), ILen); int Common = 0; for (; Common < Max; Common++) { #ifdef WIN32 #define StrCompare stricmp #else #define StrCompare strcmp #endif if (Debug) LgiTrace("Cmd '%s'-'%s'\n", b[Common], i[Common]); if (StrCompare(b[Common], i[Common]) != 0) { break; } } if (Debug) LgiTrace("Common=%i\n", Common); if (Common > 0) { if (Common < b.Length()) { Out[0] = 0; auto Back = b.Length() - Common; if (Debug) LgiTrace("Back=%i\n", (int)Back); for (int n=0; nSettings.GetStr(ProjExe); if (PExe) { if (LgiIsRelativePath(PExe)) { GAutoString Base = GetBasePath(); if (Base) { LgiMakePath(Path, Len, Base, PExe); } else return false; } else { strcpy_s(Path, Len, PExe); } return true; } else return false; } GAutoString IdeProject::GetMakefile() { GAutoString Path; const char *PMakefile = d->Settings.GetStr(ProjMakefile); if (PMakefile) { if (LgiIsRelativePath(PMakefile)) { GAutoString Base = GetBasePath(); if (Base) { char p[MAX_PATH]; LgiMakePath(p, sizeof(p), Base, PMakefile); Path.Reset(NewStr(p)); } } else { Path.Reset(NewStr(PMakefile)); } } return Path; } void IdeProject::Clean(bool All, bool Release) { if (!d->Thread && d->Settings.GetStr(ProjMakefile)) { GAutoString m = GetMakefile(); if (m) d->Thread.Reset(new BuildThread(this, m, true, Release, All, sizeof(ssize_t)*8)); } } char *QuoteStr(char *s) { GStringPipe p(256); while (s && *s) { if (*s == ' ') { p.Push("\\ ", 2); } else p.Push(s, 1); s++; } return p.NewStr(); } class ExecuteThread : public LThread, public GStream { IdeProject *Proj; char *Exe, *Args, *Path; int Len; ExeAction Act; public: ExecuteThread(IdeProject *proj, const char *exe, const char *args, char *path, ExeAction act) : LThread("ExecuteThread") { Len = 32 << 10; Proj = proj; Act = act; Exe = NewStr(exe); Args = NewStr(args); Path = NewStr(path); DeleteOnExit = true; Run(); } ~ExecuteThread() { DeleteArray(Exe); DeleteArray(Args); DeleteArray(Path); } int Main() override { if (Proj->GetApp()) { Proj->GetApp()->PostEvent(M_APPEND_TEXT, 0, 1); } if (Exe) { GProcess p; if (Act == ExeDebug) { char *a = QuoteStr(Exe); char *b = QuoteStr(Path); p.Run("kdbg", a, b, true, 0, this); DeleteArray(a); DeleteArray(b); } else if (Act == ExeValgrind) { #ifdef LINUX GString ExePath = Proj->GetExecutable(GetCurrentPlatform()); if (ExePath) { char Path[MAX_PATH]; char *ExeLeaf = LgiGetLeaf(Exe); strcpy_s(Path, sizeof(Path), ExeLeaf ? ExeLeaf : Exe); LgiTrimDir(Path); char *Term = 0; char *WorkDir = 0; char *Execute = 0; switch (LgiGetWindowManager()) { case WM_Kde: Term = "konsole"; WorkDir = "--workdir "; Execute = "-e"; break; case WM_Gnome: Term = "gnome-terminal"; WorkDir = "--working-directory="; Execute = "-x"; break; } if (Term && WorkDir && Execute) { char *e = QuoteStr(ExePath); char *p = QuoteStr(Path); char *a = Proj->GetExeArgs() ? Proj->GetExeArgs() : (char*)""; char Args[512]; sprintf(Args, "%s%s " "--noclose " "%s valgrind --tool=memcheck --num-callers=20 %s %s", WorkDir, p, Execute, e, a); LgiExecute(Term, Args); } } #endif } else { p.Run(Exe, Args, Path, true, 0, this); } } return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override { if (Len > 0) { if (Proj->GetApp()) { Size = MIN(Size, Len); Proj->GetApp()->PostEvent(M_APPEND_TEXT, (GMessage::Param)NewStr((char*)Buffer, Size), 1); Len -= Size; } return Size; } return 0; } }; GDebugContext *IdeProject::Execute(ExeAction Act) { GAutoString Base = GetBasePath(); if (d->Settings.GetStr(ProjExe) && Base) { char e[MAX_PATH]; if (GetExePath(e, sizeof(e))) { if (FileExists(e)) { const char *Args = d->Settings.GetStr(ProjArgs); int RunAsAdmin = d->Settings.GetInt(ProjDebugAdmin); if (Act == ExeDebug) { return new GDebugContext(d->App, this, e, Args, RunAsAdmin != 0); } else { new ExecuteThread(this, e, Args, Base, Act); } } else { LgiMsg(Tree, "Executable '%s' doesn't exist.\n", AppName, MB_OK, e); } } } return NULL; } bool IdeProject::IsMakefileUpToDate() { List Proj; if (GetChildProjects(Proj)) { Proj.Insert(this); for (IdeProject *p = Proj.First(); p; p = Proj.Next()) { // Is the project file modified after the makefile? GAutoString Proj = p->GetFullPath(); uint64 ProjModTime = 0, MakeModTime = 0; GDirectory dir; if (dir.First(Proj)) { ProjModTime = dir.GetLastWriteTime(); dir.Close(); } GAutoString m = p->GetMakefile(); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); break; } if (dir.First(m)) { MakeModTime = dir.GetLastWriteTime(); dir.Close(); } // printf("Proj=%s - Timestamps " LGI_PrintfInt64 " - " LGI_PrintfInt64 "\n", Proj.Get(), ProjModTime, MakeModTime); if (ProjModTime != 0 && MakeModTime != 0 && ProjModTime > MakeModTime) { // Need to rebuild the makefile... return false; } } } return true; } bool IdeProject::FindDuplicateSymbols() { GStream *Log = d->App->GetBuildLog(); Log->Print("FindDuplicateSymbols starting...\n"); List Proj; CollectAllSubProjects(Proj); Proj.Insert(this); int Lines = 0; LHashTbl,int64> Map(200000); int Found = 0; for (IdeProject *p = Proj.First(); p; p = Proj.Next()) { GString s = p->GetExecutable(GetCurrentPlatform()); if (s) { GString Args; Args.Printf("--print-size --defined-only -C %s", s.Get()); GSubProcess Nm("nm", Args); if (Nm.Start(true, false)) { char Buf[256]; GStringPipe q; for (ssize_t Rd = 0; (Rd = Nm.Read(Buf, sizeof(Buf))); ) q.Write(Buf, Rd); GString::Array a = q.NewGStr().SplitDelimit("\r\n"); LHashTbl,bool> Local(200000); for (GString *Ln = NULL; a.Iterate(Ln); Lines++) { GString::Array p = Ln->SplitDelimit(" \t", 3); if (!Local.Find(p.Last())) { Local.Add(p.Last(), true); // const char *Sz = p[1]; int64 Ours = p[1].Int(16); int64 Theirs = Map.Find(p.Last()); if (Theirs >= 0) { if (Ours != Theirs) { if (Found++ < 100) Log->Print(" %s (" LPrintfInt64 " -> " LPrintfInt64 ")\n", p.Last().Get(), Ours, Theirs); } } else if (Ours >= 0) { Map.Add(p.Last(), Ours); } else { printf("Bad line: %s\n", Ln->Get()); } } } } } else printf("%s:%i - GetExecutable failed.\n", _FL); } /* char *Sym; for (int Count = Map.First(&Sym); Count; Count = Map.Next(&Sym)) { if (Count > 1) Log->Print(" %i: %s\n", Count, Sym); } */ Log->Print("FindDuplicateSymbols finished (%i lines)\n", Lines); return false; } bool IdeProject::FixMissingFiles() { FixMissingFilesDlg(this); return true; } void IdeProject::Build(bool All, bool Release) { if (d->Thread) { d->App->GetBuildLog()->Print("Error: Already building (%s:%i)\n", _FL); return; } GAutoString m = GetMakefile(); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); return; } if (GetApp()) GetApp()->PostEvent(M_APPEND_TEXT, 0, 0); SetClean(); if (!IsMakefileUpToDate()) CreateMakefile(GetCurrentPlatform(), true); else // Start the build thread... d->Thread.Reset ( new BuildThread ( this, m, false, Release, All, sizeof(size_t)*8 ) ); } void IdeProject::StopBuild() { d->Thread.Reset(); } bool IdeProject::Serialize(bool Write) { return true; } AppWnd *IdeProject::GetApp() { return d->App; } const char *IdeProject::GetIncludePaths() { return d->Settings.GetStr(ProjIncludePaths); } const char *IdeProject::GetPreDefinedValues() { return d->Settings.GetStr(ProjDefines); } const char *IdeProject::GetExeArgs() { return d->Settings.GetStr(ProjArgs); } GString IdeProject::GetExecutable(IdePlatform Platform) { GString Bin = d->Settings.GetStr(ProjExe, NULL, Platform); GAutoString Base = GetBasePath(); if (Bin) { if (LgiIsRelativePath(Bin) && Base) { char p[MAX_PATH]; if (LgiMakePath(p, sizeof(p), Base, Bin)) Bin = p; } return Bin; } // Create binary name from target: GString Target = GetTargetName(Platform); if (Target) { int BuildMode = d->App->GetBuildMode(); const char *Name = BuildMode ? "Release" : "Debug"; const char *Postfix = BuildMode ? "" : "d"; switch (Platform) { case PlatformWin32: { Bin.Printf("%s%s.dll", Target.Get(), Postfix); break; } case PlatformMac: { Bin.Printf("lib%s%s.dylib", Target.Get(), Postfix); break; } case PlatformLinux: case PlatformHaiku: { Bin.Printf("lib%s%s.so", Target.Get(), Postfix); break; } default: { LgiAssert(0); printf("%s:%i - Unknown platform.\n", _FL); return GString(); } } // Find the actual file... if (!Base) { printf("%s:%i - GetBasePath failed.\n", _FL); return GString(); } char Path[MAX_PATH]; LgiMakePath(Path, sizeof(Path), Base, Name); LgiMakePath(Path, sizeof(Path), Path, Bin); if (FileExists(Path)) Bin = Path; else printf("%s:%i - '%s' doesn't exist.\n", _FL, Path); return Bin; } return GString(); } char *IdeProject::GetFileName() { return d->FileName; } int IdeProject::GetPlatforms() { return PLATFORM_ALL; } GAutoString IdeProject::GetFullPath() { GAutoString Status; if (!d->FileName) { // LgiAssert(!"No path."); return Status; } GArray sections; IdeProject *proj = this; while ( proj && proj->GetFileName() && LgiIsRelativePath(proj->GetFileName())) { sections.AddAt(0, proj->GetFileName()); proj = proj->GetParentProject(); } if (!proj) { // LgiAssert(!"All projects have a relative path?"); return Status; // No absolute path in the parent projects? } char p[MAX_PATH]; strcpy_s(p, sizeof(p), proj->GetFileName()); // Copy the base path if (sections.Length() > 0) LgiTrimDir(p); // Trim off the filename for (int i=0; iFileName.Empty(); d->UserFile.Empty(); d->App->GetTree()->Insert(this); ProjectNode *f = new ProjectNode(this); if (f) { f->SetName("Source"); f->SetType(NodeDir); InsertTag(f); } f = new ProjectNode(this); if (f) { f->SetName("Headers"); f->SetType(NodeDir); InsertTag(f); } d->Settings.Set(ProjEditorTabSize, 4); d->Settings.Set(ProjEditorIndentSize, 4); d->Settings.Set(ProjEditorUseHardTabs, true); d->Dirty = true; Expanded(true); } ProjectStatus IdeProject::OpenFile(char *FileName) { GProfile Prof("IdeProject::OpenFile"); Prof.HideResultsIfBelow(1000); Empty(); Prof.Add("Init"); d->UserNodeFlags.Empty(); if (LgiIsRelativePath(FileName)) { char p[MAX_PATH]; getcwd(p, sizeof(p)); LgiMakePath(p, sizeof(p), p, FileName); d->FileName = p; } else { d->FileName = FileName; } d->UserFile = d->FileName + "." + LCurrentUserName(); if (!d->FileName) { LgiTrace("%s:%i - No filename.\n", _FL); return OpenError; } Prof.Add("FileOpen"); GFile f; if (!f.Open(d->FileName, O_READWRITE)) { LgiTrace("%s:%i - Error: Can't open '%s'.\n", _FL, d->FileName.Get()); return OpenError; } Prof.Add("Xml"); GXmlTree x; GXmlTag r; if (!x.Read(&r, &f)) { LgiTrace("%s:%i - Error: Can't read XML: %s\n", _FL, x.GetErrorMsg()); LgiMsg(Tree, x.GetErrorMsg(), AppName); return OpenError; } Prof.Add("Progress Setup"); #if DEBUG_OPEN_PROGRESS int64 Nodes = r.CountTags(); GProgressDlg Prog(d->App, 1000); Prog.SetDescription("Loading project..."); Prog.SetLimits(0, Nodes); Prog.SetYieldTime(1000); Prog.SetAlwaysOnTop(true); #endif Prof.Add("UserFile"); if (FileExists(d->UserFile)) { GFile Uf; if (Uf.Open(d->UserFile, O_READ)) { GString::Array Ln = Uf.Read().SplitDelimit("\n"); for (unsigned i=0; iUserNodeFlags.Add((int)p[0].Int(), (int)p[1].Int(16)); } } } if (!r.IsTag("Project")) return OpenError; Prof.Add("OnOpen"); bool Ok = OnOpen( #if DEBUG_OPEN_PROGRESS &Prog, #else NULL, #endif &r); #if DEBUG_OPEN_PROGRESS if (Prog.IsCancelled()) return OpenCancel; else #endif if (!Ok) return OpenError; Prof.Add("Insert"); d->App->GetTree()->Insert(this); Expanded(true); Prof.Add("Serialize"); d->Settings.Serialize(&r, false /* read */); return OpenOk; } bool IdeProject::SaveFile() { GAutoString Full = GetFullPath(); printf("IdeProject::SaveFile %s %i\n", Full.Get(), d->Dirty); if (ValidStr(Full) && d->Dirty) { GFile f; if (f.Open(Full, O_WRITE)) { f.SetSize(0); GXmlTree x; GProgressDlg Prog(d->App, 1000); Prog.SetAlwaysOnTop(true); Prog.SetDescription("Serializing project XML..."); Prog.SetYieldTime(200); Prog.SetCanCancel(false); d->Settings.Serialize(this, true /* write */); GStringPipe Buf(4096); if (x.Write(this, &Buf, &Prog)) { GCopyStreamer Cp; Prog.SetDescription("Writing XML..."); LgiYield(); if (Cp.Copy(&Buf, &f)) d->Dirty = false; } else LgiTrace("%s:%i - Failed to write XML.\n", _FL); } else LgiTrace("%s:%i - Couldn't open '%s' for writing.\n", _FL, Full.Get()); } if (d->UserFileDirty) { GFile f; LgiAssert(d->UserFile.Get()); if (f.Open(d->UserFile, O_WRITE)) { f.SetSize(0); // Save user file details.. // int Id; // for (int Flags = d->UserNodeFlags.First(&Id); Flags >= 0; Flags = d->UserNodeFlags.Next(&Id)) for (auto i : d->UserNodeFlags) { f.Print("%i,%x\n", i.key, i.value); } d->UserFileDirty = false; } } printf("\tIdeProject::SaveFile %i %i\n", d->Dirty, d->UserFileDirty); return !d->Dirty && !d->UserFileDirty; } void IdeProject::SetDirty() { d->Dirty = true; d->App->OnProjectChange(); } void IdeProject::SetUserFileDirty() { d->UserFileDirty = true; } bool IdeProject::GetExpanded(int Id) { int f = d->UserNodeFlags.Find(Id); return f >= 0 ? f : false; } void IdeProject::SetExpanded(int Id, bool Exp) { if (d->UserNodeFlags.Find(Id) != (int)Exp) { d->UserNodeFlags.Add(Id, Exp); SetUserFileDirty(); } } int IdeProject::AllocateId() { return d->NextNodeId++; } bool IdeProject::SetClean() { // printf("IdeProject::SetClean %i %i\n", d->Dirty, d->UserFileDirty); if (d->Dirty || d->UserFileDirty) { if (!ValidStr(d->FileName)) { GFileSelect s; s.Parent(Tree); s.Name("Project.xml"); if (s.Save()) { d->FileName = s.Name(); d->UserFile = d->FileName + "." + LCurrentUserName(); d->App->OnFile(d->FileName, true); Update(); } else return false; } SaveFile(); } ForAllProjectNodes(p) { p->SetClean(); } return true; } char *IdeProject::GetText(int Col) { if (d->FileName) { char *s = strrchr(d->FileName, DIR_CHAR); return s ? s + 1 : d->FileName.Get(); } return (char*)Untitled; } int IdeProject::GetImage(int Flags) { return 0; } void IdeProject::Empty() { GXmlTag *t; while ((t = Children.First())) { ProjectNode *n = dynamic_cast(t); if (n) { n->Remove(); } DeleteObj(t); } } GXmlTag *IdeProject::Create(char *Tag) { if (!stricmp(Tag, TagSettings)) return NULL; return new ProjectNode(this); } void IdeProject::OnMouseClick(GMouse &m) { if (m.IsContextMenu()) { GSubMenu Sub; Sub.AppendItem("New Folder", IDM_NEW_FOLDER); Sub.AppendItem("New Web Folder", IDM_WEB_FOLDER); Sub.AppendSeparator(); Sub.AppendItem("Build", IDM_BUILD); Sub.AppendItem("Clean Project", IDM_CLEAN_PROJECT); Sub.AppendItem("Clean All", IDM_CLEAN_ALL); Sub.AppendItem("Rebuild Project", IDM_REBUILD_PROJECT); Sub.AppendItem("Rebuild All", IDM_REBUILD_ALL); Sub.AppendSeparator(); Sub.AppendItem("Sort Children", IDM_SORT_CHILDREN); Sub.AppendSeparator(); Sub.AppendItem("Settings", IDM_SETTINGS, true); Sub.AppendItem("Insert Dependency", IDM_INSERT_DEP); m.ToScreen(); GdcPt2 c = _ScrollPos(); m.x -= c.x; m.y -= c.y; switch (Sub.Float(Tree, m.x, m.y)) { case IDM_NEW_FOLDER: { GInput Name(Tree, "", "Name:", AppName); if (Name.DoModal()) { GetSubFolder(this, Name.GetStr(), true); } break; } case IDM_WEB_FOLDER: { WebFldDlg Dlg(Tree, 0, 0, 0); if (Dlg.DoModal()) { if (Dlg.Ftp && Dlg.Www) { IdeCommon *f = GetSubFolder(this, Dlg.Name, true); if (f) { f->SetAttr(OPT_Ftp, Dlg.Ftp); f->SetAttr(OPT_Www, Dlg.Www); } } } break; } case IDM_BUILD: { StopBuild(); Build(true, d->App->IsReleaseMode()); break; } case IDM_CLEAN_PROJECT: { Clean(false, d->App->IsReleaseMode()); break; } case IDM_CLEAN_ALL: { Clean(true, d->App->IsReleaseMode()); break; } case IDM_REBUILD_PROJECT: { StopBuild(); Clean(false, d->App->IsReleaseMode()); Build(false, d->App->IsReleaseMode()); break; } case IDM_REBUILD_ALL: { StopBuild(); Clean(true, d->App->IsReleaseMode()); Build(true, d->App->IsReleaseMode()); break; } case IDM_SORT_CHILDREN: { SortChildren(); Project->SetDirty(); break; } case IDM_SETTINGS: { if (d->Settings.Edit(Tree)) { SetDirty(); } break; } case IDM_INSERT_DEP: { GFileSelect s; s.Parent(Tree); s.Type("Project", "*.xml"); if (s.Open()) { ProjectNode *New = new ProjectNode(this); if (New) { New->SetFileName(s.Name()); New->SetType(NodeDependancy); InsertTag(New); SetDirty(); } } break; } } } } char *IdeProject::FindFullPath(const char *File, ProjectNode **Node) { char *Full = 0; ForAllProjectNodes(c) { ProjectNode *n = c->FindFile(File, &Full); if (n) { if (Node) *Node = n; break; } } return Full; } bool IdeProject::HasNode(ProjectNode *Node) { ForAllProjectNodes(c) { if (c->HasNode(Node)) return true; } return false; } bool IdeProject::GetAllNodes(GArray &Nodes) { ForAllProjectNodes(c) { c->AddNodes(Nodes); } return true; } bool IdeProject::InProject(bool FuzzyMatch, const char *Path, bool Open, IdeDoc **Doc) { if (!Path) return false; // Search complete path first... ProjectNode *n = d->Nodes.Find(Path); if (!n && FuzzyMatch) { // No match, do partial matching. const char *Leaf = LgiGetLeaf(Path); auto PathLen = strlen(Path); auto LeafLen = strlen(Leaf); uint32_t MatchingScore = 0; // Traverse all nodes and try and find the best fit. // const char *p; // for (ProjectNode *Cur = d->Nodes.First(&p); Cur; Cur = d->Nodes.Next(&p)) for (auto Cur : d->Nodes) { int CurPlatform = Cur.value->GetPlatforms(); uint32_t Score = 0; if (stristr(Cur.key, Path)) { Score += PathLen; } else if (stristr(Cur.key, Leaf)) { Score += LeafLen; } const char *pLeaf = LgiGetLeaf(Cur.key); if (pLeaf && !stricmp(pLeaf, Leaf)) { Score |= 0x80000000; } if (Score && (CurPlatform & PLATFORM_CURRENT) != 0) { Score |= 0x40000000; } if (Score > MatchingScore) { MatchingScore = Score; n = Cur.value; } } } if (n && Doc) { *Doc = n->Open(); } return n != 0; } char *GetQuotedStr(char *Str) { char *s = strchr(Str, '\"'); if (s) { s++; char *e = strchr(s, '\"'); if (e) { return NewStr(s, e - s); } } return 0; } void IdeProject::ImportDsp(char *File) { if (File && FileExists(File)) { char Base[256]; strcpy(Base, File); LgiTrimDir(Base); char *Dsp = ReadTextFile(File); if (Dsp) { GToken Lines(Dsp, "\r\n"); IdeCommon *Current = this; bool IsSource = false; for (int i=0; iGetSubFolder(this, Folder, true); if (Sub) { Current = Sub; } DeleteArray(Folder); } } else if (strnicmp(L, "# End Group", 11) == 0) { IdeCommon *Parent = dynamic_cast(Current->GetParent()); if (Parent) { Current = Parent; } } else if (strnicmp(L, "# Begin Source", 14) == 0) { IsSource = true; } else if (strnicmp(L, "# End Source", 12) == 0) { IsSource = false; } else if (Current && IsSource && strncmp(L, "SOURCE=", 7) == 0) { ProjectNode *New = new ProjectNode(this); if (New) { char *Src = 0; if (strchr(L, '\"')) { Src = GetQuotedStr(L); } else { Src = NewStr(L + 7); } if (Src) { // Make absolute path char Abs[256]; LgiMakePath(Abs, sizeof(Abs), Base, ToUnixPath(Src)); // Make relitive path New->SetFileName(Src); DeleteArray(Src); } Current->InsertTag(New); SetDirty(); } } } DeleteArray(Dsp); } } } IdeProjectSettings *IdeProject::GetSettings() { return &d->Settings; } bool IdeProject::BuildIncludePaths(GArray &Paths, bool Recurse, bool IncludeSystem, IdePlatform Platform) { List Projects; if (Recurse) { GetChildProjects(Projects); } Projects.Insert(this, 0); LHashTbl, bool> Map; for (IdeProject *p=Projects.First(); p; p=Projects.Next()) { GString ProjInclude = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); GAutoString Base = p->GetBasePath(); const char *Delim = ",;\r\n"; GString::Array In, Out; GString::Array a = ProjInclude.SplitDelimit(Delim); In = a; if (IncludeSystem) { GString SysInclude = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform); a = SysInclude.SplitDelimit(Delim); In.SetFixedLength(false); In.Add(a); } for (unsigned i=0; i 1 ? a[1].Get() : NULL, NULL, true, NULL, &Buf)) { GString result = Buf.NewGStr(); a = result.Split(" \t\r\n"); for (int i=0; i Nodes; if (p->GetAllNodes(Nodes)) { GAutoString Base = p->GetFullPath(); if (Base) { LgiTrimDir(Base); for (unsigned i=0; iGetType() == NodeHeader && // Only look at headers. (n->GetPlatforms() & (1 << Platform)) != 0) // Exclude files not on this platform. { char *f = n->GetFileName(); char p[MAX_PATH]; if (f && LgiMakePath(p, sizeof(p), Base, f)) { char *l = strrchr(p, DIR_CHAR); if (l) *l = 0; if (!Map.Find(p)) { Map.Add(p, true); } } } } } } } // char *p; // for (bool b = Map.First(&p); b; b = Map.Next(&p)) for (auto p : Map) Paths.Add(p.key); return true; } void IdeProjectPrivate::CollectAllFiles(GTreeNode *Base, GArray &Files, bool SubProjects, int Platform) { for (GTreeItem *i = Base->GetChild(); i; i = i->GetNext()) { IdeProject *Proj = dynamic_cast(i); if (Proj) { if (Proj->GetParentProject() && !SubProjects) { continue; } } else { ProjectNode *p = dynamic_cast(i); if (p) { if (p->GetType() == NodeSrc || p->GetType() == NodeHeader) { if (p->GetPlatforms() & Platform) { Files.Add(p); } } } } CollectAllFiles(i, Files, SubProjects, Platform); } } GString IdeProject::GetTargetName(IdePlatform Platform) { GString Status; const char *t = d->Settings.GetStr(ProjTargetName, NULL, Platform); if (ValidStr(t)) { // Take target name from the settings Status = t; } else { char *s = strrchr(d->FileName, DIR_CHAR); if (s) { // Generate the target executable name char Target[MAX_PATH]; strcpy_s(Target, sizeof(Target), s + 1); s = strrchr(Target, '.'); if (s) *s = 0; strlwr(Target); s = Target; for (char *i = Target; *i; i++) { if (*i != '.' && *i != ' ') { *s++ = *i; } } *s = 0; Status = Target; } } return Status; } bool IdeProject::GetTargetFile(char *Buf, int BufSize) { bool Status = false; GString Target = GetTargetName(PlatformCurrent); if (Target) { const char *TargetType = d->Settings.GetStr(ProjTargetType); if (TargetType) { if (!stricmp(TargetType, "Executable")) { strcpy_s(Buf, BufSize, Target); Status = true; } else if (!stricmp(TargetType, "DynamicLibrary")) { char t[MAX_PATH]; strcpy_s(t, sizeof(t), Target); char *ext = LgiGetExtension(t); if (!ext) sprintf(t + strlen(t), ".%s", LGI_LIBRARY_EXT); else if (stricmp(ext, LGI_LIBRARY_EXT)) strcpy(ext, LGI_LIBRARY_EXT); strcpy_s(Buf, BufSize, t); Status = true; } else if (!stricmp(TargetType, "StaticLibrary")) { snprintf(Buf, BufSize, "lib%s.%s", Target.Get(), LGI_STATIC_LIBRARY_EXT); Status = true; } } } return Status; } struct Dependency { bool Scanned; GAutoString File; Dependency(const char *f) { Scanned = false; File.Reset(NewStr(f)); } }; bool IdeProject::GetAllDependencies(GArray &Files, IdePlatform Platform) { if (!GetTree()->Lock(_FL)) return false; LHashTbl, Dependency*> Deps; GAutoString Base = GetBasePath(); // Build list of all the source files... GArray Src; CollectAllSource(Src, Platform); // Get all include paths GArray IncPaths; BuildIncludePaths(IncPaths, false, false, Platform); // Add all source to dependencies for (int i=0; i Unscanned; do { // Find all the unscanned dependencies Unscanned.Length(0); // for (Dependency *d = Deps.First(); d; d = Deps.Next()) for (auto d : Deps) { if (!d.value->Scanned) Unscanned.Add(d.value); } for (int i=0; iScanned = true; char *Src = d->File; char Full[MAX_PATH]; if (LgiIsRelativePath(d->File)) { LgiMakePath(Full, sizeof(Full), Base, d->File); Src = Full; } GArray SrcDeps; if (GetDependencies(Src, IncPaths, SrcDeps, Platform)) { for (int n=0; n 0); // for (Dependency *d = Deps.First(); d; d = Deps.Next()) for (auto d : Deps) { Files.Add(d.value->File.Release()); } Deps.DeleteObjects(); GetTree()->Unlock(); return true; } bool IdeProject::GetDependencies(const char *SourceFile, GArray &IncPaths, GArray &Files, IdePlatform Platform) { if (!FileExists(SourceFile)) { LgiTrace("%s:%i - can't read '%s'\n", _FL, SourceFile); return false; } GAutoString c8(ReadTextFile(SourceFile)); if (!c8) return false; GArray Headers; if (!BuildHeaderList(c8, Headers, IncPaths, false)) return false; for (int n=0; nCreateMakefile) { if (d->CreateMakefile->IsExited()) d->CreateMakefile.Reset(); else { d->App->GetBuildLog()->Print("%s:%i - Makefile thread still running.\n", _FL); return false; } } return d->CreateMakefile.Reset(new MakefileThread(d, Platform, BuildAfterwards)); } //////////////////////////////////////////////////////////////////////////////////////////// IdeTree::IdeTree() : GTree(IDC_PROJECT_TREE, 0, 0, 100, 100) { Hit = 0; MultiSelect(true); } void IdeTree::OnCreate() { SetWindow(this); } void IdeTree::OnDragExit() { SelectDropTarget(0); } int IdeTree::WillAccept(List &Formats, GdcPt2 p, int KeyState) { static bool First = true; for (char *f=Formats.First(); f; ) { /* if (First) LgiTrace(" WillAccept='%s'\n", f); */ if (stricmp(f, NODE_DROP_FORMAT) == 0 || stricmp(f, LGI_FileDropFormat) == 0) { f = Formats.Next(); } else { Formats.Delete(f); DeleteArray(f); f = Formats.Current(); } } First = false; if (Formats.Length() > 0) { Hit = ItemAtPoint(p.x, p.y); if (Hit) { if (!stricmp(Formats.First(), LGI_FileDropFormat)) { SelectDropTarget(Hit); return DROPEFFECT_LINK; } else { IdeCommon *Src = dynamic_cast(Selection()); IdeCommon *Dst = dynamic_cast(Hit); if (Src && Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) { return DROPEFFECT_NONE; } } } // Valid target SelectDropTarget(Hit); return DROPEFFECT_MOVE; } } } else LgiTrace("%s:%i - No valid drop formats.\n", _FL); return DROPEFFECT_NONE; } int IdeTree::OnDrop(GArray &Data, GdcPt2 p, int KeyState) { SelectDropTarget(0); if (!Hit) Hit = ItemAtPoint(p.x, p.y); if (!Hit) return DROPEFFECT_NONE; for (unsigned n=0; nType == GV_BINARY && Data->Value.Binary.Length == sizeof(ProjectNode*)) { ProjectNode *Src = ((ProjectNode**)Data->Value.Binary.Data)[0]; if (Src) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() > NodeDir) { Folder = dynamic_cast(Folder->GetParent()); } IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) { return DROPEFFECT_NONE; } } // Detach GTreeItem *i = dynamic_cast(Src); i->Detach(); if (Src->GXmlTag::Parent) { LgiAssert(Src->GXmlTag::Parent->Children.HasItem(Src)); Src->GXmlTag::Parent->Children.Delete(Src); } // Attach Src->GXmlTag::Parent = Dst; Dst->Children.Insert(Src); Dst->Insert(Src); // Dirty Src->GetProject()->SetDirty(); } return DROPEFFECT_MOVE; } } } else if (dd.IsFileDrop()) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() > NodeDir) { Folder = dynamic_cast(Folder->GetParent()); } IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { AddFilesProgress Prog(this); GDropFiles Df(dd); for (int i=0; iAddFiles(&Prog, Df[i]); } } } else { LgiTrace("%s:%i - Unknown drop format: %s.\n", _FL, dd.Format.Get()); } } return DROPEFFECT_NONE; } ///////////////////////////////////////////////////////////////////////////////////////////////// AddFilesProgress::AddFilesProgress(GViewI *par) { v = 0; Cancel = false; Msg = NULL; SetParent(par); Ts = LgiCurrentTime(); GRect r(0, 0, 140, 100); SetPos(r); MoveSameScreen(par); Name("Importing files..."); GString::Array a = GString(DefaultExt).SplitDelimit(","); for (unsigned i=0; iGetCell(0, 0); c->Add(new GTextLabel(-1, 0, 0, -1, -1, "Loaded:")); c = t->GetCell(1, 0); c->Add(Msg = new GTextLabel(-1, 0, 0, -1, -1, "...")); c = t->GetCell(0, 1, true, 2); c->TextAlign(GCss::Len(GCss::AlignRight)); c->Add(new GButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); } int64 AddFilesProgress::Value() { return v; } void AddFilesProgress::Value(int64 val) { v = val; if (Visible() && Msg) { Msg->Value(v); Msg->SendNotify(GNotifyTableLayout_Refresh); } uint64 Now = LgiCurrentTime(); if (Visible()) { if (Now - Ts > 150) LgiYield(); } else if (Now - Ts > 1000) { DoModeless(); } } int AddFilesProgress::OnNotify(GViewI *c, int f) { if (c->GetId() == IDCANCEL) Cancel = true; return 0; } diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,3865 +1,4101 @@ #include #include #include #include "Lgi.h" #include "LgiIde.h" #include "GMdi.h" #include "GToken.h" #include "GXmlTree.h" #include "GPanel.h" #include "GProcess.h" #include "GButton.h" #include "GTabView.h" #include "FtpThread.h" #include "GClipBoard.h" #include "FindSymbol.h" #include "GBox.h" #include "GTextLog.h" #include "GEdit.h" #include "GTableLayout.h" #include "GTextLabel.h" #include "GCombo.h" #include "GCheckBox.h" #include "GDebugger.h" #include "LgiRes.h" #include "ProjectNode.h" #include "GBox.h" #include "GSubProcess.h" #include "GAbout.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #define USE_HAIKU_PULSE_HACK 1 #define OPT_ENTIRE_SOLUTION "SearchSolution" #define OPT_SPLIT_PX "SplitPos" #define OPT_OUTPUT_PX "OutputPx" +#define OPT_FIX_RENAMED "FixRenamed" +#define OPT_RENAMED_SYM "RenamedSym" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public GDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); GViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, GKeyEvents, 0); } } bool OnViewKey(GView *v, GKey &k) { switch (k.vkey) { case VK_UP: case VK_DOWN: case VK_PAGEDOWN: case VK_PAGEUP: { return Lst->OnKey(k); break; } case VK_RETURN: { LListItem *i = Lst->GetSelected(); if (i) { char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); break; } case VK_ESCAPE: { EndModal(0); break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; GArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (p=All.First(); p; p=All.Next()) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s); Lst->Empty(); for (unsigned i=0; iSetText(Matches[i]->GetFileName()); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_FILES: if (f == GNotifyItem_DoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (f != GNotify_ReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// char AppName[] = "LgiIde"; char *dirchar(char *s, bool rev = false) { if (rev) { char *last = 0; while (s && *s) { if (*s == '/' || *s == '\\') last = s; s++; } return last; } else { while (s && *s) { if (*s == '/' || *s == '\\') return s; s++; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class Dependency : public GTreeItem { char *File; bool Loaded; GTreeItem *Fake; public: Dependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new GTreeItem); if (FileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~Dependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(GStringPipe &p, int Depth = 0) { { char s[1024]; ZeroObj(s); memset(s, ' ', Depth * 4); sprintf(s+(Depth*4), "[%c] %s\n", Expanded() ? '-' : '+', GetText(0)); p.Push(s); } if (Loaded) { for (GTreeItem *i=GetChild(); i; i=i->GetNext()) { ((Dependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { GToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new Dependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new GButton(IDC_COPY, 10, t->GView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new GButton(IDOK, 80, t->GView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(); } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDC_COPY: { if (Root) { GStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { GClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public GTextLog { public: DebugTextLog(int id) : GTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { GTextView3::PourText(Start, Len); for (GTextLine *l=Line.First(); l; l=Line.Next()) { char16 *t = Text + l->Start; if (l->Len > 5 && !StrnicmpW(t, L"(gdb)", 5)) { l->c.Rgb(0, 160, 0); } else if (l->Len > 1 && t[0] == '[') { l->c.Rgb(192, 192, 192); } } } }; WatchItem::WatchItem(IdeOutput *out, const char *Init) { Out = out; Expanded(false); if (Init) SetText(Init); Insert(PlaceHolder = new GTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(GVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else GTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { GTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { GViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(GNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public GTextLog { public: BuildLog(int id) : GTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = GTextView3::Line.begin(); for (GTextLine *ln = *it; ln; ln = *++it) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; char16 *Err = Strnistr(t, L"error", ln->Len); char16 *Undef = Strnistr(t, L"undefined reference", ln->Len); char16 *Warn = Strnistr(t, L"warning", ln->Len); if ( (Err && strchr(":[", Err[5])) || (Undef != NULL) ) ln->c.Rgb(222, 0, 0); else if (Warn && strchr(":[", Warn[7])) ln->c.Rgb(255, 128, 0); else ln->c.Set(LC_TEXT, 24); } } } }; class IdeOutput : public GTabView { public: AppWnd *App; GTabPage *Build; GTabPage *Output; GTabPage *Debug; GTabPage *Find; GTabPage *Ftp; LList *FtpLog; GTextLog *Txt[3]; GArray Buf[3]; GFont Small; GFont Fixed; GTabView *DebugTab; GBox *DebugBox; GBox *DebugLog; LList *Locals, *CallStack, *Threads; GTree *Watch; GTextLog *ObjectDump, *MemoryDump, *Registers; GTableLayout *MemTable; GEdit *DebugEdit; GTextLog *DebuggerLog; IdeOutput(AppWnd *app) { App = app; Build = Output = Debug = Find = Ftp = 0; FtpLog = 0; DebugBox = NULL; Locals = NULL; Watch = NULL; DebugLog = NULL; DebugEdit = NULL; DebuggerLog = NULL; CallStack = NULL; ObjectDump = NULL; MemoryDump = NULL; MemTable = NULL; Threads = NULL; Registers = NULL; Small = *SysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LgiAssert(Small.Handle()); GFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(SysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(SysFont->PointSize()-1); Fixed.Face("Courier"); Fixed.Create(); } GetCss(true)->MinHeight("60px"); Build = Append("Build"); Output = Append("Output"); Find = Append("Find"); Ftp = Append("Ftp"); Debug = Append("Debug"); SetFont(&Small); Build->SetFont(&Small); Output->SetFont(&Small); Find->SetFont(&Small); Ftp->SetFont(&Small); Debug->SetFont(&Small); if (Build) Build->Append(Txt[AppWnd::BuildTab] = new BuildLog(IDC_BUILD_LOG)); if (Output) Output->Append(Txt[AppWnd::OutputTab] = new GTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new GTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new GBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new GTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); GTabPage *Page; if ((Page = DebugTab->Append("Locals"))) { Page->SetFont(&Small); if ((Locals = new LList(IDC_LOCALS_LIST, 0, 0, 100, 100, "Locals List"))) { Locals->SetFont(&Small); Locals->AddColumn("", 30); Locals->AddColumn("Type", 50); Locals->AddColumn("Name", 50); Locals->AddColumn("Value", 1000); Locals->SetPourLargest(true); Page->Append(Locals); } } if ((Page = DebugTab->Append("Object"))) { Page->SetFont(&Small); if ((ObjectDump = new GTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new GTree(IDC_WATCH_LIST, 0, 0, 100, 100, "Watch List"))) { Watch->SetFont(&Small); Watch->ShowColumnHeader(true); Watch->AddColumn("Watch", 80); Watch->AddColumn("Type", 100); Watch->AddColumn("Value", 600); Watch->SetPourLargest(true); Page->Append(Watch); GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (GXmlTag *c = w->Children.First(); c; c = w->Children.Next()) { if (c->IsTag("watch")) { Watch->Insert(new WatchItem(this, c->GetContent())); } } App->GetOptions()->Unlock(); } } } if ((Page = DebugTab->Append("Memory"))) { Page->SetFont(&Small); if ((MemTable = new GTableLayout(IDC_MEMORY_TABLE))) { GCombo *cbo; GCheckBox *chk; GTextLabel *txt; GEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; GLayoutCell *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(cbo = new GCombo(IDC_MEM_SIZE, 0, 0, 60, 20)); cbo->SetFont(&Small); cbo->Insert("1 byte"); cbo->Insert("2 bytes"); cbo->Insert("4 bytes"); cbo->Insert("8 bytes"); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(txt = new GTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(GCss::Len("1em")); c->Add(ed = new GEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(GCss::VerticalMiddle); c->Add(chk = new GCheckBox(IDC_MEM_HEX, 0, 0, -1, -1, "Show Hex")); chk->SetFont(&Small); chk->Value(true); } int cols = x; x = 0; c = MemTable->GetCell(x++, ++y, true, cols); if ((MemoryDump = new GTextLog(IDC_MEMORY_DUMP))) { MemoryDump->SetFont(&Fixed); MemoryDump->SetPourLargest(true); c->Add(MemoryDump); } Page->Append(MemTable); } } if ((Page = DebugTab->Append("Threads"))) { Page->SetFont(&Small); if ((Threads = new LList(IDC_THREADS, 0, 0, 100, 100, "Threads"))) { Threads->SetFont(&Small); Threads->AddColumn("", 20); Threads->AddColumn("Thread", 1000); Threads->SetPourLargest(true); Threads->MultiSelect(false); Page->Append(Threads); } } if ((Page = DebugTab->Append("Call Stack"))) { Page->SetFont(&Small); if ((CallStack = new LList(IDC_CALL_STACK, 0, 0, 100, 100, "Call Stack"))) { CallStack->SetFont(&Small); CallStack->AddColumn("", 20); CallStack->AddColumn("Call Stack", 1000); CallStack->SetPourLargest(true); CallStack->MultiSelect(false); Page->Append(CallStack); } } if ((Page = DebugTab->Append("Registers"))) { Page->SetFont(&Small); if ((Registers = new GTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new GBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new GEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)(SysFont->GetHeight() + 8))); } } } if (FtpLog) { FtpLog->SetPourLargest(true); FtpLog->Sunken(true); FtpLog->AddColumn("Entry", 1000); FtpLog->ShowColumnHeader(false); } for (int n=0; nSetTabSize(8); Txt[n]->Sunken(true); } } ~IdeOutput() { } const char *GetClass() { return "IdeOutput"; } void Save() { if (Watch) { GXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (GTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { GXmlTag *t = new GXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { #if !USE_HAIKU_PULSE_HACK SetPulse(1000); #endif AttachChildren(); } void RemoveAnsi(GArray &a) { char *s = a.AddressOf(); char *e = s + a.Length(); while (s < e) { if (*s == 0x7) { a.DeleteAt(s - a.AddressOf(), true); s--; } else if ( *s == 0x1b && s[1] >= 0x40 && s[1] <= 0x5f ) { // ANSI seq char *end; if (s[1] == '[' && s[2] == '0' && s[3] == ';') end = s + 4; else { end = s + 2; while (end < e && !IsAlpha(*end)) { end++; } if (*end) end++; } auto len = end - s; memmove(s, end, e - end); a.Length(a.Length() - len); s--; } s++; } } void OnPulse() { int Changed = -1; for (int Channel = 0; Channel w(Utf8ToWide(Utf, (ssize_t)Size)); char16 *OldText = Txt[Channel]->NameW(); size_t OldLen = 0; if (OldText) OldLen = StrlenW(OldText); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) { Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); } Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } if (Changed >= 0) Value(Changed); } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { char *A = a->GetFileName(); char *B = b->GetFileName(); if (A && B) { char *Af = strrchr(A, DIR_CHAR); char *Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { GAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; GMdiParent *Mdi; GOptionsFile Options; GBox *HBox, *VBox; List Docs; List Projects; GImageList *Icons; GTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; GSubMenu *WindowsMenu; GSubMenu *CreateMakefileMenu; GAutoPtr FindSym; GArray SystemIncludePaths; GArray BreakPoints; // Debugging GDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; GArray CursorHistory; bool InHistorySeek; void SeekHistory(int Direction) { if (CursorHistory.Length()) { int Loc = HistoryLoc + Direction; if (Loc >= 0 && Loc < CursorHistory.Length()) { HistoryLoc = Loc; FileLoc &Loc = CursorHistory[HistoryLoc]; App->GotoReference(Loc.File, Loc.Line, false, false); App->DumpHistory(); } } } // Find in files GAutoPtr FindParameters; GAutoPtr Finder; int AppHnd; // Mru List RecentFiles; GSubMenu *RecentFilesMenu; List RecentProjects; GSubMenu *RecentProjectsMenu; // Object AppWndPrivate(AppWnd *a) : AppHnd(GEventSinkMap::Dispatch.AddSink(a)), Options(GOptionsFile::DesktopMode, AppName) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; RecentFilesMenu = 0; RecentProjectsMenu = 0; Icons = LgiLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { FindSym.Reset(); Finder.Reset(); Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); RecentFiles.DeleteArrays(); RecentProjects.DeleteArrays(); Docs.DeleteObjects(); Projects.DeleteObjects(); DeleteObj(Icons); } bool FindSource(GAutoString &Full, char *File, char *Context) { if (!LgiIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (IdeProject *p=Projects.First(); p && !ContextPath; p=Projects.Next()) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); } if (ContextPath) { LgiTrimDir(ContextPath); char p[300]; LgiMakePath(p, sizeof(p), ContextPath, File); if (FileExists(p)) { Full.Reset(NewStr(p)); } } else { LgiTrace("%s:%i - Context '%s' not found in project.\n", _FL, Context); } } if (!Full) { List::I Projs = Projects.begin(); for (IdeProject *p=*Projs; p; p=*++Projs) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; LgiMakePath(Path, sizeof(Path), Base, File); if (FileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (IdeProject *p=Projects.First(); p && !Full; p=Projects.Next()) { Full.Reset(p->FindFullPath(Dir?Dir+1:File)); } if (!Full) { if (FileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { GAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } void GetContext(char16 *Txt, ssize_t &i, char16 *&Context) { static char16 NMsg[] = L"In file included "; static char16 FromMsg[] = L"from "; auto NMsgLen = StrlenW(NMsg); if (Txt[i] != '\n') return; if (StrncmpW(Txt + i + 1, NMsg, NMsgLen)) return; i += NMsgLen + 1; while (Txt[i]) { // Skip whitespace while (Txt[i] && strchr(" \t\r\n", Txt[i])) i++; // Check for 'from' if (StrncmpW(FromMsg, Txt + i, 5)) break; i += 5; char16 *Start = Txt + i; // Skip to end of doc or line char16 *Colon = 0; while (Txt[i] && Txt[i] != '\n') { if (Txt[i] == ':' && Txt[i+1] != '\n') { Colon = Txt + i; } i++; } if (Colon) { DeleteArray(Context); Context = NewStrW(Start, Colon-Start); } } } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { GString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); GTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; char16 *Txt = o->NameW(); if (!Txt) return; ssize_t Cur = o->GetCaret(); char16 *Context = NULL; // Scan forward to the end of file for the next filename/line number separator. ssize_t i; for (i=Cur; Txt[i]; i++) { GetContext(Txt, i, Context); if ( PossibleLineSep(Txt[i]) && isdigit(Txt[i+1]) ) { break; } } // If not found then scan from the start of the file for the next filename/line number separator. if (!PossibleLineSep(Txt[i])) { for (i=0; i 0 && Txt[Line-1] != '\n') + while (Line > 0 && !strchr("\n>", Txt[Line-1])) { Line--; } // Store the filename GAutoString File(WideToUtf8(Txt+Line, i-Line)); if (!File) return; // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number GAutoString NumStr(WideToUtf8(Txt + i, NumIndex - i)); if (!NumStr) return; // Convert it to an integer int LineNumber = atoi(NumStr); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); GString Context8 = Context; ViewMsg(File, LineNumber, Context8); } void UpdateMenus() { static const char *None = "(none)"; if (!App->GetMenu()) return; // This happens in GTK during window destruction if (RecentFilesMenu) { RecentFilesMenu->Empty(); int n=0; char *f=RecentFiles.First(); if (f) { for (; f; f=RecentFiles.Next()) { RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); } } else { RecentFilesMenu->AppendItem(None, 0, false); } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); int n=0; char *f=RecentProjects.First(); if (f) { for (; f; f=RecentProjects.Next()) { RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); } } else { RecentProjectsMenu->AppendItem(None, 0, false); } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (IdeDoc *d=Docs.First(); d; d=Docs.Next()) { const char *File = d->GetFileName(); if (!File) File = "(untitled)"; char *Dir = strrchr((char*)File, DIR_CHAR); WindowsMenu->AppendItem(Dir?Dir+1:File, IDM_WINDOWS+n++, true); } if (!Docs.First()) { WindowsMenu->AppendItem(None, 0, false); } } } void OnFile(const char *File, bool IsProject = false) { if (File) { List *Recent = IsProject ? &RecentProjects : &RecentFiles; for (char *f=Recent->First(); f; f=Recent->Next()) { if (stricmp(f, File) == 0) { Recent->Delete(f); Recent->Insert(f, 0); UpdateMenus(); return; } } Recent->Insert(NewStr(File), 0); while (Recent->Length() > 10) { char *f = Recent->Last(); Recent->Delete(f); DeleteArray(f); } UpdateMenus(); } } void RemoveRecent(char *File) { if (File) { List *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (List **r = Recent; *r; r++) { for (char *f=(*r)->First(); f; f=(*r)->Next()) { if (stricmp(f, File) == 0) { // LgiTrace("Remove '%s'\n", f); (*r)->Delete(f); DeleteArray(f); break; } } } UpdateMenus(); } } IdeDoc *IsFileOpen(const char *File) { if (!File) { LgiTrace("%s:%i - No input File?\n", _FL); return NULL; } for (IdeDoc *Doc = Docs.First(); Doc; Doc = Docs.Next()) { if (Doc->IsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(char *File) { if (File) { for (IdeProject *p = Projects.First(); p; p = Projects.Next()) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, List *Lst, bool Write) { GVariant v; if (Write) { GMemQueue p; for (char *s = Lst->First(); s; s = Lst->Next()) { p.Write((uchar*)s, strlen(s)+1); } ssize_t Size = (ssize_t)p.GetSize(); v.SetBinary(Size, p.New(), true); Options.SetValue(Opt, v); } else { Lst->DeleteArrays(); if (Options.GetValue(Opt, v) && v.Type == GV_BINARY) { char *Data = (char*)v.Value.Binary.Data; for (char *s=Data; (NativeInt)s<(NativeInt)Data+v.Value.Binary.Length; s += strlen(s) + 1) { auto ns = NewStr(s); Lst->Insert(ns); } } } } }; #ifdef COCOA #define Chk printf("%s:%i - Cnt=%i\n", LgiGetLeaf(__FILE__), __LINE__, (int)WindowHandle().p.retainCount) #else #define Chk #endif AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif Chk; GRect r(0, 0, 1300, 900); #ifdef BEOS r.Offset(GdcD->X() - r.X() - 10, GdcD->Y() - r.Y() - 10); SetPos(r); #else SetPos(r); Chk; MoveToCenter(); #endif Chk; d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); Chk; #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("Icon64.png"); #endif Chk; if (Attach(0)) { Chk; Menu = new GMenu; if (Menu) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LgiAssert(Loaded); if (Loaded) { d->RecentFilesMenu = Menu->FindSubMenu(IDM_RECENT_FILES); d->RecentProjectsMenu = Menu->FindSubMenu(IDM_RECENT_PROJECTS); d->WindowsMenu = Menu->FindSubMenu(IDM_WINDOW_LST); d->CreateMakefileMenu = Menu->FindSubMenu(IDM_CREATE_MAKEFILE); if (d->CreateMakefileMenu) { d->CreateMakefileMenu->Empty(); for (int i=0; PlatformNames[i]; i++) { d->CreateMakefileMenu->AppendItem(PlatformNames[i], IDM_MAKEFILE_BASE + i); } } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) { Debug->Checked(true); } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } Chk; GToolBar *Tools; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Chk; Tools->AppendButton("New", IDM_NEW, TBT_PUSH, true, CMD_NEW); Tools->AppendButton("Open", IDM_OPEN, TBT_PUSH, true, CMD_OPEN); Tools->AppendButton("Save", IDM_SAVE_ALL, TBT_PUSH, true, CMD_SAVE_ALL); Tools->AppendSeparator(); Tools->AppendButton("Cut", IDM_CUT, TBT_PUSH, true, CMD_CUT); Tools->AppendButton("Copy", IDM_COPY, TBT_PUSH, true, CMD_COPY); Tools->AppendButton("Paste", IDM_PASTE, TBT_PUSH, true, CMD_PASTE); Tools->AppendSeparator(); Chk; Tools->AppendButton("Compile", IDM_COMPILE, TBT_PUSH, true, CMD_COMPILE); Tools->AppendButton("Build", IDM_BUILD, TBT_PUSH, true, CMD_BUILD); Tools->AppendButton("Stop", IDM_STOP_BUILD, TBT_PUSH, true, CMD_STOP_BUILD); // Tools->AppendButton("Execute", IDM_EXECUTE, TBT_PUSH, true, CMD_EXECUTE); Tools->AppendSeparator(); Tools->AppendButton("Debug", IDM_START_DEBUG, TBT_PUSH, true, CMD_DEBUG); Tools->AppendButton("Pause", IDM_PAUSE_DEBUG, TBT_PUSH, true, CMD_PAUSE); Tools->AppendButton("Restart", IDM_RESTART_DEBUGGING, TBT_PUSH, true, CMD_RESTART); Tools->AppendButton("Kill", IDM_STOP_DEBUG, TBT_PUSH, true, CMD_KILL); Tools->AppendButton("Step Into", IDM_STEP_INTO, TBT_PUSH, true, CMD_STEP_INTO); Tools->AppendButton("Step Over", IDM_STEP_OVER, TBT_PUSH, true, CMD_STEP_OVER); Tools->AppendButton("Step Out", IDM_STEP_OUT, TBT_PUSH, true, CMD_STEP_OUT); Tools->AppendButton("Run To", IDM_RUN_TO, TBT_PUSH, true, CMD_RUN_TO); Tools->AppendSeparator(); Tools->AppendButton("Find In Files", IDM_FIND_IN_FILES, TBT_PUSH, true, CMD_FIND_IN_FILES); Tools->GetCss(true)->Padding("4px"); Tools->Attach(this); } else LgiTrace("%s:%i - No tools obj?", _FL); Chk; GVariant v = 270, OutPx = 250; d->Options.GetValue(OPT_SPLIT_PX, v); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); AddView(d->VBox = new GBox); d->VBox->SetVertical(true); d->HBox = new GBox; d->VBox->AddView(d->HBox); d->VBox->AddView(d->Output = new IdeOutput(this)); d->HBox->AddView(d->Tree = new IdeTree); if (d->Tree) { d->Tree->SetImageList(d->Icons, false); d->Tree->Sunken(false); } d->HBox->AddView(d->Mdi = new GMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } Chk; d->HBox->Value(MAX(v.CastInt32(), 20)); GRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { GCss::Len y(GCss::LenPx, OutPx.CastDouble()); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); Chk; #ifdef LINUX GString f = LgiFindFile("lgiide.png"); if (f) { // Handle()->setIcon(f); } #endif UpdateState(); Chk; Visible(true); DropTarget(true); Chk; SetPulse(1000); } #ifdef LINUX LgiFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK d->Output->SetPulse(1000); #endif Chk; } AppWnd::~AppWnd() { if (d->HBox) { GVariant v = d->HBox->Value(); d->Options.SetValue(OPT_SPLIT_PX, v); } if (d->Output) { GVariant v = d->Output->Y(); d->Options.SetValue(OPT_OUTPUT_PX, v); } ShutdownFtpThread(); CloseAll(); LgiApp->AppWnd = 0; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); } GDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { GStream *Out; GString InFile; bool IsLib; public: DumpBinThread(GStream *out, GString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LgiGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(GString Args, GStream *Str) { char Buf[256]; ssize_t Rd; GSubProcess s("c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\dumpbin.exe", Args); if (!s.Start(true, false)) return false; while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } GString::Array Dependencies() { GString Args; GStringPipe p; Args.Printf("/dependents \"%s\"", InFile.Get()); DumpBin(Args, &p); auto Parts = p.NewGStr().Replace("\r", "").Split("\n\n"); auto Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (auto &f : Files) { f = f.Strip(); bool Found = false; for (auto s : Path) { GFile::Path c(s); c += f.Get(); if (c.IsFile()) { f = c.GetFull(); Found = true; break; } } if (!Found) f += " (not found in path)"; } return Files; } GString GetArch() { GString Args; GStringPipe p; Args.Printf("/headers \"%s\"", InFile.Get()); DumpBin(Args, &p); const char *Key = " machine "; GString Arch; auto Lines = p.NewGStr().SplitDelimit("\r\n"); int64 Machine = 0; for (auto &Ln : Lines) { if (Ln.Find(Key) >= 0) { auto p = Ln.Strip().Split(Key); if (p.Length() == 2) { Arch = p[1].Strip("()"); Machine = p[0].Int(16); } } } if (Machine == 0x14c) Arch += " 32bit"; else if (Machine == 0x200) Arch += " 64bit Itanium"; else if (Machine == 0x8664) Arch += " 64bit"; return Arch; } GString GetExports() { GString Args; GStringPipe p; if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); GString Exp; auto Sect = p.NewGStr().Replace("\r", "").Split("\n\n"); if (IsLib) { GString::Array Lines, Funcs; for (auto &s : Sect) { if (s.Find("COFF", 0, 100) == 0) { Lines = s.Split("\n"); break; } } Funcs.SetFixedLength(false); for (auto &l : Lines) { if (l.Length() < 34) continue; const char *Type = l.Get() + 33; if (!Strnicmp(Type, "External", 8)) { auto Nm = l.RSplit("|",1).Last().Strip(); if (!strchr("@$.?", Nm(0))) Funcs.New() = Nm; } } Exp = GString("\n").Join(Funcs); } else { bool Ord = false; for (auto &s : Sect) { if (s.Strip().Find("ordinal") == 0) Ord = true; else if (Ord) { Exp = s; break; } else Ord = false; } } return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(); Out->Print("Dependencies:\n\t%s\n\n", GString("\n\t").Join(Deps).Get()); } auto Arch = GetArch(); if (Arch) Out->Print("Arch: %s\n\n", Arch.Get()); auto Exp = GetExports(); if (Arch) Out->Print("Exports:\n%s\n\n", Exp.Get()); return 0; } }; void AppWnd::OnReceiveFiles(GArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); } void AppWnd::OnDebugState(bool Debugging, bool Running) { // Make sure this event is processed in the GUI thread. #if DEBUG_SESSION_LOGGING LgiTrace("AppWnd::OnDebugState(%i,%i) InThread=%i\n", Debugging, Running, InThread()); #endif PostEvent(M_DEBUG_ON_STATE, Debugging, Running); } +bool IsVarChar(GString &s, int pos) +{ + if (pos < 0) + return false; + if (pos >= s.Length()) + return false; + char i = s[pos]; + return IsAlpha(i) || IsDigit(i) || i == '_'; +} + +bool ReplaceWholeWord(GString &Ln, GString Word, GString NewWord) +{ + int Pos = 0; + bool Status = false; + + while (Pos >= 0) + { + Pos = Ln.Find(Word, Pos); + if (Pos < 0) + return Status; + + int End = Pos + Word.Length(); + if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) + { + GString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); + Ln = NewLn; + Status = true; + } + + Pos++; + } + + return Status; +} + +struct FileInfo +{ + GString Path; + GString::Array Lines; + bool Dirty; + + FileInfo() + { + Dirty = false; + } + + bool Save() + { + GFile f; + if (!f.Open(Path, O_WRITE)) + return false; + + GString NewFile = GString("\n").Join(Lines); + + f.SetSize(0); + f.Write(NewFile); + + f.Close(); + return true; + } +}; + + +void AppWnd::OnFixBuildErrors() +{ + LHashTbl, GString> Map; + GVariant v; + if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) + { + auto Lines = GString(v.Str()).Split("\n"); + for (auto Ln: Lines) + { + auto p = Ln.SplitDelimit(); + if (p.Length() == 2) + Map.Add(p[0], p[1]); + } + + if (Map.Length() == 0) + { + LgiMsg(this, "No renamed symbols defined.", AppName); + return; + } + } + else return; + + GString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); + GString::Array Lines = Raw.Split("\n"); + GProgressDlg Prog(this); + Prog.SetDescription("Parsing errors..."); + Prog.SetLimits(0, Lines.Length()); + Prog.SetYieldTime(300); + int i = 0; + int Replacements = 0; + GArray Files; + + for (auto Ln : Lines) + { + if (Ln.Find("error") >= 0) + { + GString::Array p = Ln.SplitDelimit(">()"); + if (p.Length() > 2) + { + int Base = p[0].IsNumeric() ? 1 : 0; + GString Fn = p[Base]; + if (Fn.Find("Program Files") >= 0) + continue; + + GAutoString Full; + if (d->FindSource(Full, p[Base], NULL)) + { + auto LineNo = p[Base+1].Int(); + + FileInfo *Fi = NULL; + for (auto &i: Files) + { + if (i.Path.Equals(Full)) + { + Fi = &i; + break; + } + } + if (!Fi) + { + GFile f(Full, O_READ); + if (f.IsOpen()) + { + Fi = &Files.New(); + Fi->Path = Full.Get(); + auto OldFile = f.Read(); + Fi->Lines = OldFile.SplitDelimit("\n", -1, false); + } + } + + + if (Fi) + { + if (LineNo <= Fi->Lines.Length()) + { + GString &s = Fi->Lines[LineNo-1]; + for (auto i: Map) + { + if (ReplaceWholeWord(s, i.key, i.value)) + { + Fi->Dirty = true; + Replacements++; + } + } + } + } + else + { + int asd=0; + } + } + } + } + + Prog.Value(++i); + if (Prog.IsCancelled()) + break; + } + + for (auto &Fi : Files) + { + if (Fi.Dirty) + Fi.Save(); + } + + if (Replacements) + LgiMsg(this, "%i replacements made.", AppName, MB_OK, Replacements); +} + +void AppWnd::OnBuildStateChanged(bool NewState) +{ + GVariant v; + if (!NewState && + GetOptions()->GetValue(OPT_FIX_RENAMED, v) && + v.CastInt32()) + { + OnFixBuildErrors(); + } +} + void AppWnd::UpdateState(int Debugging, int Building) { if (Debugging >= 0) d->Debugging = Debugging; - if (Building >= 0) d->Building = Building; + if (Building >= 0) + { + if (d->Building != (Building != 0)) + OnBuildStateChanged(Building); + d->Building = Building; + } SetCtrlEnabled(IDM_COMPILE, !d->Building); SetCtrlEnabled(IDM_BUILD, !d->Building); SetCtrlEnabled(IDM_STOP_BUILD, d->Building); // SetCtrlEnabled(IDM_RUN, !d->Building); // SetCtrlEnabled(IDM_TOGGLE_BREAKPOINT, !d->Building); SetCtrlEnabled(IDM_START_DEBUG, !d->Debugging && !d->Building); SetCtrlEnabled(IDM_PAUSE_DEBUG, d->Debugging); SetCtrlEnabled(IDM_RESTART_DEBUGGING, d->Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, d->Debugging); SetCtrlEnabled(IDM_STEP_INTO, d->Debugging); SetCtrlEnabled(IDM_STEP_OVER, d->Debugging); SetCtrlEnabled(IDM_STEP_OUT, d->Debugging); SetCtrlEnabled(IDM_RUN_TO, d->Debugging); } void AppWnd::AppendOutput(char *Txt, AppWnd::Channels Channel) { if (!d->Output) { LgiTrace("%s:%i - No output panel.\n", _FL); return; } if (Channel < 0 || Channel >= CountOf(d->Output->Txt)) { LgiTrace("%s:%i - Channel range: %i, %i.\n", _FL, Channel, CountOf(d->Output->Txt)); return; } if (!d->Output->Txt[Channel]) { LgiTrace("%s:%i - No log for channel %i.\n", _FL, Channel); return; } if (Txt) { d->Output->Buf[Channel].Add(Txt, strlen(Txt)); } else { d->Output->Txt[Channel]->Name(""); } } void AppWnd::SaveAll() { List::I Docs = d->Docs.begin(); for (IdeDoc *Doc = *Docs; Doc; Doc = *++Docs) { Doc->SetClean(); d->OnFile(Doc->GetFileName()); } List::I Projs = d->Projects.begin(); for (IdeProject *Proj = *Projs; Proj; Proj = *++Projs) { Proj->SetClean(); d->OnFile(Proj->GetFileName(), true); } } void AppWnd::CloseAll() { SaveAll(); while (d->Docs.First()) delete d->Docs.First(); IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects.First()) delete d->Projects.First(); DeleteObj(d->DbgContext); } bool AppWnd::OnRequestClose(bool IsClose) { SaveAll(); return GWindow::OnRequestClose(IsClose); } bool AppWnd::OnBreakPoint(GDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { char *fn = doc->GetFileName(); bool Match = !_stricmp(fn, b.File); if (Match) { doc->AddBreakPoint(b.Line, Add); } } if (d->DbgContext) { d->DbgContext->OnBreakPoint(b, Add); } return true; } bool AppWnd::LoadBreakPoints(IdeDoc *doc) { if (!doc) return false; char *fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(GDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &bp = d->BreakPoints[i]; db->SetBreakPoint(&bp); } return true; } bool AppWnd::ToggleBreakpoint(const char *File, ssize_t Line) { bool DeleteBp = false; for (int i=0; iBreakPoints.Length(); i++) { GDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(File, b.File) && b.Line == Line) { OnBreakPoint(b, false); d->BreakPoints.DeleteAt(i); DeleteBp = true; break; } } if (!DeleteBp) { GDebugger::BreakPoint &b = d->BreakPoints.New(); b.File = File; b.Line = Line; OnBreakPoint(b, true); } return true; } void AppWnd::DumpHistory() { #if 0 LgiTrace("History %i of %i\n", d->HistoryLoc, d->CursorHistory.Length()); for (int i=0; iCursorHistory.Length(); i++) { FileLoc &p = d->CursorHistory[i]; LgiTrace(" [%i] = %s, %i %s\n", i, p.File.Get(), p.Line, d->HistoryLoc == i ? "<-----":""); } #endif } /* void CheckHistory(GArray &CursorHistory) { if (CursorHistory.Length() > 0) { FileLoc *loc = &CursorHistory[0]; for (unsigned i=CursorHistory.Length(); iInHistorySeek) { if (d->CursorHistory.Length() > 0) { FileLoc &Last = d->CursorHistory.Last(); if (_stricmp(File, Last.File) == 0 && abs(Last.Line - Line) <= 1) { // Previous or next line... just update line number Last.Line = Line; DumpHistory(); return; } // Add new entry d->HistoryLoc++; FileLoc &loc = d->CursorHistory[d->HistoryLoc]; #ifdef WIN64 if ((NativeInt)loc.File.Get() == 0xcdcdcdcdcdcdcdcd) LgiAssert(0); // wtf? else #endif loc.Set(File, Line); } else { // Add new entry d->CursorHistory[0].Set(File, Line); } // Destroy any history after the current... d->CursorHistory.Length(d->HistoryLoc+1); DumpHistory(); } } void AppWnd::OnFile(char *File, bool IsProject) { d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { IdeDoc *Doc = new IdeDoc(this, Src, 0); if (Doc) { d->Docs.Insert(Doc); GRect p = d->Mdi->NewPos(); Doc->SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); Doc->Raise(); if (FileName) d->OnFile(FileName); } return Doc; } IdeDoc *AppWnd::GetCurrentDoc() { if (d->Mdi) return dynamic_cast(d->Mdi->GetTop()); return NULL; } IdeDoc *AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory) { if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) Doc->SetLine(Line, CurIp); /* else LgiTrace("%s:%i - No file '%s' found.\n", _FL, File); */ if (!WithHistory) d->InHistorySeek = false; return Doc; } IdeDoc *AppWnd::FindOpenFile(char *FileName) { List::I it = d->Docs.begin(); for (IdeDoc *i=*it; i; i=*++it) { char *f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH]; if (*f == '.') LgiMakePath(Path, sizeof(Path), Base, f); else strcpy_s(Path, sizeof(Path), f); if (stricmp(Path, FileName) == 0) return i; } } else { if (stricmp(f, FileName) == 0) return i; } } } return 0; } IdeDoc *AppWnd::OpenFile(const char *FileName, NodeSource *Src) { static bool DoingProjectFind = false; IdeDoc *Doc = 0; const char *File = Src ? Src->GetFileName() : FileName; if (!Src && !ValidStr(File)) { LgiTrace("%s:%i - No source or file?\n", _FL); return NULL; } GString FullPath; if (LgiIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto Project : Projs) { GAutoString ProjPath = Project->GetBasePath(); char p[MAX_PATH]; LgiMakePath(p, sizeof(p), ProjPath, File); if (FileExists(p)) { FullPath = p; File = FullPath; break; } } } } Doc = d->IsFileOpen(File); if (!Doc) { if (Src) { Doc = NewDocWnd(File, Src); } else if (!DoingProjectFind) { DoingProjectFind = true; List::I Proj = d->Projects.begin(); for (IdeProject *p=*Proj; p && !Doc; p=*++Proj) { p->InProject(LgiIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && FileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { #ifdef BEOS BView *h = Doc->Handle(); BWindow *w = h ? h->Window() : 0; bool att = Doc->IsAttached(); printf("%s:%i - att=%i h=%p w=%p\n", _FL, att, h, w); #endif if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { IdeProject *Root = 0; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { if (!p->GetParentProject()) { LgiAssert(Root == 0); Root = p; } } return Root; } IdeProject *AppWnd::OpenProject(char *FileName, IdeProject *ParentProj, bool Create, bool Dep) { if (!FileName) { LgiTrace("%s:%i - Error: No filename.\n", _FL); return NULL; } if (d->IsProjectOpen(FileName)) { LgiTrace("%s:%i - Warning: Project already open.\n", _FL); return NULL; } IdeProject *p = new IdeProject(this); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } GString::Array Inc; p->BuildIncludePaths(Inc, false, false, PlatformCurrent); d->FindSym->SetIncludePaths(Inc); p->SetParentProject(ParentProj); ProjectStatus Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!Dep) { char *d = strrchr(FileName, DIR_CHAR); if (d++) { char n[256]; sprintf(n, "%s [%s]", AppName, d); Name(n); } } } else { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, FileName); DeleteObj(p); if (Status == OpenError) d->RemoveRecent(FileName); } if (!GetTree()->Selection()) { GetTree()->Select(GetTree()->GetChild()); } GetTree()->Focus(true); return p; } GMessage::Result AppWnd::OnEvent(GMessage *m) { switch (MsgCode(m)) { case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, IsReleaseMode()); else printf("%s:%i - No root project.\n", _FL); break; } case M_BUILD_DONE: { UpdateState(-1, false); IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case M_BUILD_ERR: { char *Msg = (char*)MsgB(m); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { char *Text = (char*) MsgA(m); Channels Ch = (Channels) MsgB(m); AppendOutput(Text, Ch); DeleteArray(Text); break; } case M_DEBUG_ON_STATE: { bool Debugging = m->A(); bool Running = m->B(); if (d->Running != Running) { bool RunToNotRun = d->Running && !Running; d->Running = Running; if (RunToNotRun && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(GNotifyValueChanged); } } if (d->Debugging != Debugging) { d->Debugging = Debugging; if (!Debugging) { IdeDoc::ClearCurrentIp(); IdeDoc *c = GetCurrentDoc(); if (c) c->UpdateControl(); // Shutdown the debug context and free the memory DeleteObj(d->DbgContext); } } SetCtrlEnabled(IDM_START_DEBUG, !Debugging || !Running); SetCtrlEnabled(IDM_PAUSE_DEBUG, Debugging && Running); SetCtrlEnabled(IDM_RESTART_DEBUGGING, Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, Debugging); SetCtrlEnabled(IDM_STEP_INTO, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OVER, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OUT, Debugging && !Running); SetCtrlEnabled(IDM_RUN_TO, Debugging && !Running); break; } default: { if (d->DbgContext) d->DbgContext->OnEvent(m); break; } } return GWindow::OnEvent(m); } bool AppWnd::OnNode(const char *Path, ProjectNode *Node, FindSymbolSystem::SymAction Action) { // This takes care of adding/removing files from the symbol search engine. if (!Path || !Node) return false; d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } GOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public GDialog { AppWnd *App; GFontType Font; public: Options(AppWnd *a) { SetParent(App = a); if (LoadFromResource(IDD_OPTIONS)) { SetCtrlEnabled(IDC_FONT, false); MoveToCenter(); if (!Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Font.GetSystemFont("Fixed"); } char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } GVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); DoModal(); } } int OnNotify(GViewI *c, int f) { switch (c->GetId()) { case IDOK: { GVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { if (Font.DoUI(this)) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } } break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; int64 RowLen = GetCtrlValue(IDC_MEM_ROW_LEN); bool InHex = GetCtrlValue(IDC_MEM_HEX) != 0; d->DbgContext->FormatMemoryDump(iWord, (int)RowLen, InHex); } } int AppWnd::OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (Flags == GNotify_DeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (Flags == VK_RETURN && d->DbgContext) { char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (Flags == VK_RETURN) { if (d->DbgContext) { char *s = Ctrl->Name(); if (s) { char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; d->DbgContext->OnMemoryDump(s, iWord, (int)GetCtrlValue(IDC_MEM_ROW_LEN), GetCtrlValue(IDC_MEM_HEX) != 0); } else if (d->DbgContext->MemoryDump) { d->DbgContext->MemoryDump->Print("No address specified."); } else { LgiAssert(!"No MemoryDump."); } } else LgiAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (Flags == VK_RETURN) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && Flags == GNotifyValueChanged) { switch (Ctrl->Value()) { case AppWnd::LocalsTab: { d->DbgContext->UpdateLocals(); break; } case AppWnd::WatchTab: { d->DbgContext->UpdateWatches(); break; } case AppWnd::RegistersTab: { d->DbgContext->UpdateRegisters(); break; } case AppWnd::CallStackTab: { d->DbgContext->UpdateCallStack(); break; } case AppWnd::ThreadsTab: { d->DbgContext->UpdateThreads(); break; } default: break; } } break; } case IDC_LOCALS_LIST: { if (d->Output->Locals && Flags == GNotifyItem_DoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { char *Var = it->GetText(2); char *Val = it->GetText(3); if (Var) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::ObjectTab); d->DbgContext->DumpObject(Var, Val); } } } break; } case IDC_CALL_STACK: { if (Flags == M_CHANGE) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (Flags == GNotifyItem_Select) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { GAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { GAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (Flags) { case GNotify_DeleteKey: { GArray Sel; for (GTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case GNotifyItem_Click: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case GNotifyContainer_Click: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (Flags == GNotifyItem_Select) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { GString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::IsReleaseMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; return IsRelease; } bool AppWnd::Build() { SaveAll(); IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); bool IsRelease = Release ? Release->Checked() : false; p->Build(false, IsRelease); return true; } else if ((Top = TopDoc())) { return Top->Build(); } return false; } +class RenameDlg : public GDialog +{ + AppWnd *App; + +public: + RenameDlg(AppWnd *a) + { + SetParent(App = a); + MoveSameScreen(a); + + if (LoadFromResource(IDC_RENAME)) + { + GVariant v; + if (App->GetOptions()->GetValue(OPT_FIX_RENAMED, v)) + SetCtrlValue(IDC_FIX_RENAMED, v.CastInt32()); + if (App->GetOptions()->GetValue(OPT_RENAMED_SYM, v)) + SetCtrlName(IDC_SYM, v.Str()); + } + } + + int OnNotify(GViewI *c, int f) + { + switch (c->GetId()) + { + case IDOK: + { + GVariant v; + App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); + App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); + } + case IDCANCEL: + { + EndModal(c->GetId() == IDOK); + break; + } + } + return 0; + } +}; + bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (IdeProject *p=d->Projects.First(); p; p=d->Projects.Next()) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (GTreeItem *i = Node->GetParent(); i; i = i->GetParent()) { i->Expanded(true); } Node->Select(true); Node->ScrollTo(); return true; } } return false; } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_EXIT: { LgiCloseApp(); break; } case IDM_OPTIONS: { Options Dlg(this); break; } case IDM_HELP: { LgiExecute(APP_URL); break; } case IDM_ABOUT: { GAbout a(this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc) { GRect p = d->Mdi->NewPos(); Doc->SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { GFileSelect s; s.Parent(this); if (s.Open()) { OpenFile(s.Name()); } break; } case IDM_SAVE_ALL: { SaveAll(); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { GFileSelect s; s.Parent(this); if (s.Save()) { Top->SetFileName(s.Name(), true); d->OnFile(s.Name()); } } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top) { if (Top->OnRequestClose(false)) { Top->Quit(); } } DeleteObj(d->DbgContext); break; } case IDM_CLOSE_ALL: { CloseAll(); Name(AppName); break; } // // Editor // case IDM_UNDO: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { GTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(); else { GInput Inp(this, NULL, LgiLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); if (Inp.DoModal()) { GString s = Inp.GetStr(); GString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { GString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true); } else LgiMsg(this, "Error: Needs a file name as well.", AppName); } } break; } case IDM_CUT: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { GTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_PASTE); break; } case IDM_FIND_IN_FILES: { if (!d->Finder) { d->Finder.Reset(new FindInFilesThread(d->AppHnd)); } if (d->Finder) { if (!d->FindParameters && d->FindParameters.Reset(new FindParams)) { GVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } FindInFiles Dlg(this, d->FindParameters); GViewI *Focus = GetFocus(); if (Focus) { GTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { GAutoString a(Edit->GetSelection()); Dlg.Params->Text = a; } } IdeProject *p = RootProject(); if (p) { GAutoString Base = p->GetBasePath(); if (Base) Dlg.Params->Dir = Base; } if (Dlg.DoModal()) { if (p && Dlg.Params->Type == FifSearchSolution) { Dlg.Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); GArray Nodes; for (IdeProject *p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg.Params->ProjectFiles.Add(s); } } GVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (GMessage::Param) new FindParams(d->FindParameters)); } } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { FindSymResult r = d->FindSym->OpenSearchDlg(this); if (r.File) { GotoReference(r.File, r.Line, false); } } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { FindInProject Dlg(this); Dlg.DoModal(); } break; } case IDM_FIND_REFERENCES: { GViewI *f = LgiApp->GetFocus(); GDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; GString Txt = doc->Name(); char *s = Txt.Get() + c; char *e = s; while ( s > Txt.Get() && IsSymbolChar(s[-1])) s--; while (*e && IsSymbolChar(*e)) e++; if (e <= s) break; GString Word(s, e - s); if (!d->Finder) d->Finder.Reset(new FindInFilesThread(d->AppHnd)); if (!d->Finder) break; IdeProject *p = RootProject(); if (!p) break; List Projects; Projects.Insert(p); p->GetChildProjects(Projects); GArray Nodes; for (p = Projects.First(); p; p = Projects.Next()) p->GetAllNodes(Nodes); GAutoPtr Params(new FindParams); Params->Type = FifSearchSolution; Params->MatchWord = true; Params->Text = Word; for (unsigned i = 0; i < Nodes.Length(); i++) { Params->ProjectFiles.New() = Nodes[i]->GetFullPath(); } d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (GMessage::Param) Params.Release()); break; } case IDM_PREV_LOCATION: { d->SeekHistory(-1); break; } case IDM_NEXT_LOCATION: { d->SeekHistory(1); break; } // // Project // case IDM_NEW_PROJECT: { CloseAll(); IdeProject *p; d->Projects.Insert(p = new IdeProject(this)); if (p) { p->CreateProject(); } break; } case IDM_OPEN_PROJECT: { GFileSelect s; s.Parent(this); s.Type("Projects", "*.xml"); if (s.Open()) { CloseAll(); OpenProject(s.Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { GFileSelect s; s.Parent(this); s.Type("Developer Studio Project", "*.dsp"); if (s.Open()) { p->ImportDsp(s.Name()); } } break; } case IDM_RUN: { SaveAll(); IdeProject *p = RootProject(); if (p) { p->Execute(); } break; } case IDM_VALGRIND: { SaveAll(); IdeProject *p = RootProject(); if (p) { p->Execute(ExeValgrind); } break; } case IDM_FIX_MISSING_FILES: { IdeProject *p = RootProject(); if (p) p->FixMissingFiles(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_FIND_DUPE_SYM: { IdeProject *p = RootProject(); if (p) p->FindDuplicateSymbols(); else LgiMsg(this, "No project loaded.", AppName); break; } + case IDM_RENAME_SYM: + { + RenameDlg Dlg(this); + Dlg.DoModal(); + break; + } case IDM_START_DEBUG: { SaveAll(); IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); break; } if (d->DbgContext) { d->DbgContext->OnCommand(IDM_CONTINUE); } else if ((d->DbgContext = p->Execute(ExeDebug))) { d->DbgContext->DebuggerLog = d->Output->DebuggerLog; d->DbgContext->Watch = d->Output->Watch; d->DbgContext->Locals = d->Output->Locals; d->DbgContext->CallStack = d->Output->CallStack; d->DbgContext->Threads = d->Output->Threads; d->DbgContext->ObjectDump = d->Output->ObjectDump; d->DbgContext->Registers = d->Output->Registers; d->DbgContext->MemoryDump = d->Output->MemoryDump; d->DbgContext->OnCommand(IDM_START_DEBUG); d->Output->Value(AppWnd::DebugTab); d->Output->DebugEdit->Focus(true); } break; } case IDM_TOGGLE_BREAKPOINT: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) ToggleBreakpoint(Cur->GetFileName(), Cur->GetLine()); break; } case IDM_ATTACH_TO_PROCESS: case IDM_PAUSE_DEBUG: case IDM_RESTART_DEBUGGING: case IDM_RUN_TO: case IDM_STEP_INTO: case IDM_STEP_OVER: case IDM_STEP_OUT: { if (d->DbgContext) d->DbgContext->OnCommand(Cmd); break; } case IDM_STOP_DEBUG: { if (d->DbgContext && d->DbgContext->OnCommand(Cmd)) { DeleteObj(d->DbgContext); } break; } case IDM_BUILD: { Build(); break; } case IDM_STOP_BUILD: { IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case IDM_CLEAN: { SaveAll(); IdeProject *p = RootProject(); if (p) p->Clean(true, IsReleaseMode()); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { GMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { GString Exe = p->GetExecutable(GetCurrentPlatform()); if (FileExists(Exe)) { Depends Dlg(this, Exe); } else { LgiMsg(this, "Couldn't find '%s'\n", AppName, MB_OK, Exe ? Exe.Get() : ""); } } break; } case IDM_SP_TO_TAB: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(true); break; } case IDM_TAB_TO_SP: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(false); break; } case IDM_ESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(true); break; } case IDM_DESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(false); break; } case IDM_EOL_LF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(false); break; } case IDM_EOL_CRLF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(true); break; } case IDM_LOAD_MEMDUMP: { NewMemDumpViewer(this); break; } case IDM_SYS_CHAR_SUPPORT: { new SysCharSupport(this); break; } default: { char *r = d->RecentFiles[Cmd - IDM_RECENT_FILE]; if (r) { IdeDoc *f = d->IsFileOpen(r); if (f) { f->Raise(); } else { OpenFile(r); } } char *p = d->RecentProjects[Cmd - IDM_RECENT_PROJECT]; if (p) { CloseAll(); OpenProject(p, NULL, false); if (d->Tree) { d->Tree->Focus(true); } } IdeDoc *Doc = d->Docs[Cmd - IDM_WINDOWS]; if (Doc) { Doc->Raise(); } IdePlatform PlatIdx = (IdePlatform) (Cmd - IDM_MAKEFILE_BASE); const char *Platform = PlatIdx >= 0 && PlatIdx < PlatformMax ? PlatformNames[Cmd - IDM_MAKEFILE_BASE] : NULL; if (Platform) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatIdx, false); } } break; } } return 0; } GTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return dynamic_cast(d->Mdi->GetTop()); } GTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { GViewI *f = GetFocus(); LgiTrace("%s:%i - Edit doesn't have focus, f=%p %s doc.edit=%s\n", _FL, f, f ? f->GetClass() : 0, Doc->Name()); } } return 0; } void AppWnd::OnProjectDestroy(IdeProject *Proj) { d->Projects.Delete(Proj); } void AppWnd::OnProjectChange() { GArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { d->Docs.Delete(Doc); d->UpdateMenus(); } } int AppWnd::GetBuildMode() { GMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BUILD_TYPE_RELEASE; } return BUILD_TYPE_DEBUG; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } GStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms) { d->FindSym->Search(ResultsSinkHnd, Sym, AllPlatforms); } #include "GSubProcess.h" bool AppWnd::GetSystemIncludePaths(::GArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - GSubProcess sp1("echo"); GSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; GStringPipe p; while ((r = sp1.Read(Buf, sizeof(Buf))) > 0) { p.Write(Buf, r); } bool InIncludeList = false; while (p.Pop(Buf, sizeof(Buf))) { if (stristr(Buf, "#include")) { InIncludeList = true; } else if (stristr(Buf, "End of search")) { InIncludeList = false; } else if (InIncludeList) { GAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LgiMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (FileExists(p)) { GFile f; if (f.Open(p, O_READ)) { GXmlTree t; GXmlTag r; if (t.Read(&r, &f)) { GXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { GXmlTag *Projects = NULL; char *Name; for (GXmlTag *c = Opts->Children.First(); c; c = Opts->Children.Next()) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } GXmlTag *VCDirectories = NULL; for (GXmlTag *c = Projects ? Projects->Children.First() : NULL; c; c = Projects->Children.Next()) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } for (GXmlTag *prop = VCDirectories ? VCDirectories->Children.First() : NULL; prop; prop = VCDirectories->Children.Next()) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); GToken t(Bar ? Bar + 1 : prop->GetContent(), ";"); for (int i=0; iSystemIncludePaths.New().Reset(NewStr(s)); } } } } } } } #endif } for (int i=0; iSystemIncludePaths.Length(); i++) { Paths.Add(NewStr(d->SystemIncludePaths[i])); } return true; } /* #include "GSubProcess.h" void Test() { GDirectory d; for (int b = d.First("C:\\Users\\matthew\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cache"); b; b = d.Next()) { if (!d.IsDir()) { char p[MAX_PATH]; d.Path(p, sizeof(p)); GFile f; if (f.Open(p, O_READ)) { char Buf[256]; ssize_t Rd = f.Read(Buf, sizeof(Buf)); if (Rd > 3 && !strnicmp(Buf, "Ogg", 3)) { char out[MAX_PATH]; f.Close(); LgiMakePath(out, sizeof(out), "C:\\Users\\matthew\\Desktop\\new day", d.GetName()); strcat(out, ".ogg"); if (!FileDev->Copy(p, out)) { LgiTrace("%s:%i - Failed to copy '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Not an ogg '%s'\n", _FL, d.GetName()); } } else { LgiTrace("%s:%i - Can't open '%s'\n", _FL, d.GetName()); } } } } */ int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); GApp a(AppArgs, "LgiIde"); if (a.IsOk()) { /* GString mt = LGetAppForProtocol("mailto"); GString https = LGetAppForProtocol("https"); printf("%s\n%s\n", mt.Get(), https.Get()); GArray Out; if (GSocket::EnumInterfaces(Out)) { for (auto &i : Out) { printf("%s %s %s\n", i.Name.Get(), i.ToString().Get(), i.ToString(i.Netmask4).Get()); } } */ a.AppWnd = new AppWnd; a.Run(); } return 0; } diff --git a/Ide/Code/LgiIde.h b/Ide/Code/LgiIde.h --- a/Ide/Code/LgiIde.h +++ b/Ide/Code/LgiIde.h @@ -1,313 +1,315 @@ #ifndef _LGI_IDE_H_ #define _LGI_IDE_H_ // // Compiler specific macros: // // Microsoft Visual C++: _MSC_VER // // GCC: __GNUC__ // #ifdef WIN32 #include "resource.h" #endif #include "resdefs.h" #include "GDocView.h" #include "GOptionsFile.h" #include "FindSymbol.h" #include "GStringClass.h" #include "GDebugger.h" #include "GTextView3.h" #include "LList.h" #define APP_VER "1.0" #define APP_URL "http://www.memecode.com/lgi/ide" #define DEBUG_FIND_DEFN 0 enum IdeMessages { M_APPEND_TEXT = M_USER+200, M_APPEND_STR, M_START_BUILD, M_BUILD_ERR, M_BUILD_DONE, M_DEBUG_ON_STATE, /// Find symbol results message: /// GAutoPtr Req((FindSymRequest*)Msg->A()); M_FIND_SYM_REQUEST, /// Send a file to the worker thread... /// FindSymbolSystem::SymFileParams *Params = (FindSymbolSystem::SymFileParams*)Msg->A(); M_FIND_SYM_FILE, /// Send a file to the worker thread... /// GAutoPtr Paths((GString::Array*)Msg->A()); M_FIND_SYM_INC_PATHS, /// Styling is finished M_STYLING_DONE, }; #define ICON_PROJECT 0 #define ICON_DEPENDANCY 1 #define ICON_FOLDER 2 #define ICON_SOURCE 3 #define ICON_HEADER 4 #define ICON_RESOURCE 5 #define ICON_GRAPHIC 6 #define ICON_WEB 7 enum IdeIcon { CMD_NEW, CMD_OPEN, CMD_SAVE, CMD_CUT, CMD_COPY, CMD_PASTE, CMD_COMPILE, CMD_BUILD, CMD_STOP_BUILD, CMD_EXECUTE, CMD_DEBUG, CMD_PAUSE, CMD_KILL, CMD_RESTART, CMD_RUN_TO, CMD_STEP_INTO, CMD_STEP_OVER, CMD_STEP_OUT, CMD_FIND_IN_FILES, CMD_SEARCH, CMD_SAVE_ALL, }; enum IdeControls { IDC_STATIC = -1, IDC_WATCH_LIST = 700, IDC_LOCALS_LIST, IDC_DEBUG_EDIT, IDC_DEBUGGER_LOG, IDC_BUILD_LOG, IDC_OUTPUT_LOG, IDC_FIND_LOG, IDC_CALL_STACK, IDC_DEBUG_TAB, IDC_OBJECT_DUMP, IDC_MEMORY_DUMP, IDC_MEMORY_TABLE, IDC_MEM_ADDR, IDC_MEM_SIZE, IDC_MEM_ROW_LEN, IDC_MEM_HEX, IDC_REGISTERS, IDC_THREADS, IDC_EDIT, IDC_FILE_SEARCH, IDC_METHOD_SEARCH, IDC_SYMBOL_SEARCH, IDC_PROJECT_TREE, IDC_ALL_PLATFORMS, }; enum IdeMenuCmds { IDM_CONTINUE = 900 }; #define BUILD_TYPE_DEBUG 0 #define BUILD_TYPE_RELEASE 1 #define OPT_EditorFont "EdFont" #define OPT_Jobs "Jobs" ////////////////////////////////////////////////////////////////////// // Platform stuff enum IdePlatform { PlatformCurrent = -1, PlatformWin32 = 0, PlatformLinux, PlatformMac, PlatformHaiku, PlatformMax, }; #define PLATFORM_WIN32 (1 << PlatformWin32) #define PLATFORM_LINUX (1 << PlatformLinux) #define PLATFORM_MAC (1 << PlatformMac) #define PLATFORM_HAIKU (1 << PlatformHaiku) #define PLATFORM_ALL (PLATFORM_WIN32|PLATFORM_LINUX|PLATFORM_MAC|PLATFORM_HAIKU) #if defined(_WIN32) #define PLATFORM_CURRENT PLATFORM_WIN32 #elif defined(MAC) #define PLATFORM_CURRENT PLATFORM_MAC #elif defined(LINUX) #define PLATFORM_CURRENT PLATFORM_LINUX #elif defined(BEOS) #define PLATFORM_CURRENT PLATFORM_HAIKU #endif extern const char *PlatformNames[]; extern const char sCurrentPlatform[]; extern const char *Untitled; extern const char SourcePatterns[]; ////////////////////////////////////////////////////////////////////// class IdeDoc; class IdeProject; extern char AppName[]; extern char *FindHeader(char *Short, GArray &Paths); extern bool BuildHeaderList(char *Cpp, GArray &Headers, GArray &IncPaths, bool Recurse); class NodeView; class NodeSource { friend class NodeView; protected: NodeView *nView; public: NodeSource() { nView = 0; } virtual ~NodeSource(); virtual GString GetFullPath() = 0; virtual bool IsWeb() = 0; virtual char *GetFileName() = 0; virtual char *GetLocalCache() = 0; virtual bool Load(GDocView *Edit, NodeView *Callback) = 0; virtual bool Save(GDocView *Edit, NodeView *Callback) = 0; virtual IdeProject *GetProject() = 0; }; class NodeView { friend class NodeSource; protected: NodeSource *nSrc; public: NodeView(NodeSource *s) { if ((nSrc = s)) { nSrc->nView = this; } } virtual ~NodeView(); virtual void OnDelete() = 0; virtual void OnSaveComplete(bool Status) = 0; }; class AppWnd : public GWindow { class AppWndPrivate *d; friend class AppWndPrivate; void UpdateMemoryDump(); void DumpHistory(); public: enum Channels { BuildTab, OutputTab, FindTab, FtpTab, DebugTab, }; enum DebugTabs { LocalsTab, ObjectTab, WatchTab, MemoryTab, ThreadsTab, CallStackTab, RegistersTab }; AppWnd(); ~AppWnd(); void SaveAll(); void CloseAll(); IdeDoc *OpenFile(const char *FileName, NodeSource *Src = 0); IdeDoc *NewDocWnd(const char *FileName, NodeSource *Src); IdeDoc *GetCurrentDoc(); IdeProject *OpenProject(char *FileName, IdeProject *ParentProj, bool Create = false, bool Dep = false); IdeProject *RootProject(); IdeDoc *TopDoc(); IdeDoc *FocusDoc(); GTextView3 *FocusEdit(); void AppendOutput(char *Txt, Channels Channel); + void OnFixBuildErrors(); + void OnBuildStateChanged(bool NewState); void UpdateState(int Debugging = -1, int Building = -1); void OnReceiveFiles(GArray &Files) override; int GetBuildMode(); GTree *GetTree(); GOptionsFile *GetOptions(); LList *GetFtpLog(); GStream *GetBuildLog(); IdeDoc *FindOpenFile(char *FileName); IdeDoc *GotoReference(const char *File, int Line, bool CurIp, bool WithHistory = true); void FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms); bool GetSystemIncludePaths(GArray &Paths); bool IsReleaseMode(); bool ShowInProject(const char *Fn); bool Build(); // Events void OnLocationChange(const char *File, int Line); int OnCommand(int Cmd, int Event, OsView Wnd) override; void OnDocDestroy(IdeDoc *Doc); void OnProjectDestroy(IdeProject *Proj); void OnProjectChange(); void OnFile(char *File, bool IsProject = false); bool OnRequestClose(bool IsClose) override; int OnNotify(GViewI *Ctrl, int Flags) override; GMessage::Result OnEvent(GMessage *m) override; bool OnNode(const char *Path, class ProjectNode *Node, FindSymbolSystem::SymAction Action); void OnPulse() override; // Debugging support class GDebugContext *GetDebugContext(); bool ToggleBreakpoint(const char *File, ssize_t Line); bool OnBreakPoint(GDebugger::BreakPoint &b, bool Add); bool LoadBreakPoints(IdeDoc *doc); bool LoadBreakPoints(GDebugger *db); void OnDebugState(bool Debugging, bool Running); }; #include "IdeDoc.h" #include "IdeProject.h" #include "FindInFiles.h" extern void NewMemDumpViewer(AppWnd *App, char *file = 0); class SysCharSupport : public GWindow { class SysCharSupportPriv *d; public: SysCharSupport(AppWnd *app); ~SysCharSupport(); int OnNotify(GViewI *v, int f); void OnPosChange(); }; #endif diff --git a/Ide/Resources/LgiIde.lr8 b/Ide/Resources/LgiIde.lr8 --- a/Ide/Resources/LgiIde.lr8 +++ b/Ide/Resources/LgiIde.lr8 @@ -1,692 +1,726 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ide/Resources/resdefs.h b/Ide/Resources/resdefs.h --- a/Ide/Resources/resdefs.h +++ b/Ide/Resources/resdefs.h @@ -1,150 +1,155 @@ // This file generated by LgiRes #define IDD_MISSING_FILES 7 #define IDC_TABLE 8 #define IDC_MISSING 10 #define IDC_RESULTS 12 #define IDC_BROWSE 14 #define IDC_DELETE 16 #define IDC_17 17 #define IDD_FIND_IN_FILES 19 #define IDC_FILE_TYPES 23 #define IDC_DIR 25 #define IDC_SET_DIR 26 #define IDC_WHOLE_WORD 29 #define IDC_CASE 30 #define IDC_SUB_DIRS 31 #define IDC_COPY_PATH 34 #define IDC_MSG 36 #define IDC_TYPE 37 +#define IDC_RENAME 43 +#define IDC_44 44 +#define IDC_SYM 45 +#define IDC_FIX_RENAMED 48 #define IDD_OPTIONS 63 #define IDC_FONT 65 #define IDC_SET_FONT 66 #define IDC_FOLDER_HISTORY 69 #define IDC_TYPE_HISTORY 70 #define IDC_WIN32 79 #define IDC_LINUX 80 #define IDC_MAC 81 #define IDD_CHAR 82 #define IDC_CHAR 83 #define IDS_87 87 #define IDS_88 88 #define IDC_VALUE 96 #define IDC_PATH 97 #define IDD_WEB_FOLDER 98 #define IDS_99 99 #define IDC_HOST 101 #define IDC_USERNAME 104 #define IDC_PASSWORD 105 #define IDC_WWW 107 #define IDC_NAME 111 #define IDD_FTP_FILE 112 #define IDC_FILES 113 #define IDS_114 114 #define IDS_115 115 #define IDS_116 116 #define IDC_LOG 117 #define IDS_118 118 #define IDS_121 121 #define IDD_FIND_SYMBOL 179 #define IDC_182 182 #define IDC_183 183 #define IDC_STR 192 #define IDC_SHOW_CURRENT_ONLY 195 #define IDD_PROJECT_SETTINGS 199 #define IDC_HAIKU 200 #define IDC_SEARCH 202 #define IDC_CLEAR_SEARCH 203 #define IDC_SUB_TABLE 206 #define IDC_JOBS 213 #define IDC_233 233 #define IDC_ENTIRE_SOLUTION 234 #define IDC_FIND_PROJECT_FILE 239 #define IDC_240 240 #define IDC_242 242 #define IDC_TEXT 243 #define IDM_EOL_LF 500 #define IDM_MENU_501 501 #define IDM_OPEN 502 #define IDM_SAVE_ALL 503 #define IDM_NEW 504 #define IDM_CLOSE 505 #define IDM_CLOSE_ALL 506 #define IDM_OPTIONS 507 #define IDM_RECENT_FILES 508 #define IDM_RECENT_PROJECTS 509 #define IDM_EXIT 510 #define IDM_UNDO 511 #define IDM_REDO 512 #define IDM_CUT 513 #define IDM_COPY 514 #define IDM_PASTE 515 #define IDM_GOTO 516 #define IDM_FIND 517 #define IDM_FIND_NEXT 518 #define IDM_REPLACE 519 #define IDM_NEW_PROJECT 520 #define IDM_OPEN_PROJECT 521 #define IDM_IMPORT_DSP 522 #define IDM_PROJECT_SETTINGS 523 #define IDM_CLEAN 524 #define IDM_BUILD 525 #define IDM_BUILD_ALL 526 #define IDM_NEXT_MSG 527 #define IDM_DEBUG_MODE 528 #define IDM_RUN 529 #define IDM_DEPENDS 530 #define IDM_TAB_TO_SP 531 #define IDM_SP_TO_TAB 532 #define IDM_ABOUT 533 #define IDM_HELP 534 #define IDM_SAVEAS 535 #define IDM_WINDOW_LST 536 #define IDM_COMPILE 537 #define IDM_STOP_BUILD 538 #define IDM_FIND_IN_FILES 539 #define IDM_CREATE_MAKEFILE 540 #define IDM_RELEASE_MODE 541 #define IDC_SEARCH_DIR 542 #define IDM_VALGRIND 543 #define IDC_PLATFORMS 544 #define IDM_EOL_CRLF 545 #define IDM_FIX_MISSING_FILES 546 #define IDC_LOOK_FOR 547 #define IDM_FIND_DUPE_SYM 548 #define IDM_LOOKUP_SYMBOLS 549 #define IDM_MENU_550 550 #define IDD_FILE_PROPS 551 #define IDM_LOAD_MEMDUMP 552 #define IDM_SYS_CHAR_SUPPORT 553 #define IDC_NOT_MATCH 554 #define IDC_MATCH 555 #define IDM_ESCAPE 556 #define IDM_DESCAPE 557 #define IDC_75 558 #define IDM_FIND_SYMBOL 559 #define IDM_FIND_REFERENCES 560 #define IDM_MENU_561 561 #define IDM_GOTO_SYMBOL 562 #define IDC_SETTINGS 563 #define IDC_DETAIL 564 #define IDM_MAKEFILE_PLATFORMS 565 #define IDM_MENU_566 566 #define IDM_PREV_LOCATION 567 #define IDM_NEXT_LOCATION 568 #define IDM_SAVE 569 #define IDM_START_DEBUG 570 #define IDM_PAUSE_DEBUG 571 #define IDM_STOP_DEBUG 572 #define IDM_MENU_573 573 #define IDM_ATTACH_TO_PROCESS 574 #define IDM_STEP_INTO 575 #define IDM_STEP_OVER 576 #define IDM_STEP_OUT 577 #define IDM_MENU_578 578 #define IDM_TOGGLE_BREAKPOINT 579 #define IDM_MENU_580 580 #define IDM_RESTART_DEBUGGING 581 #define IDM_RUN_TO 582 #define IDM_PREV_MSG 583 #define IDM_FIND_PROJECT_FILE 584 +#define IDM_RENAME_SYM 585 diff --git a/include/common/GSubProcess.h b/include/common/GSubProcess.h --- a/include/common/GSubProcess.h +++ b/include/common/GSubProcess.h @@ -1,145 +1,148 @@ /** \file \brief Sub-process wrapper. This class runs one or more sub-processes chained together by pipes. Example: GSubProcess p1("ls", "-l"); GSubProcess p2("grep", "string"); p1.Connect(&p2); p1.Start(true, false); int r; char Buf[256]; while ((r = p1.Read(Buf, sizeof(Buf))) > 0) { // So something with 'Buf' } */ #ifndef _SUB_PROCESS_H_ #define _SUB_PROCESS_H_ #ifdef WIN32 #define USE_SIMPLE_FORK 0 #else #define USE_SIMPLE_FORK 1 #endif #if USE_SIMPLE_FORK #include #endif #if defined(MAC) #include #define GSUBPROCESS_ERROR EBADEXEC #elif defined(LINUX) #include #define GSUBPROCESS_ERROR ECHILD #elif defined(WINDOWS) #define GSUBPROCESS_ERROR ERROR_PROCESS_ABORTED #endif class GSubProcess : public GStreamI { public: #if defined(WIN32) typedef HANDLE PipeHandle; typedef DWORD ProcessId; #else typedef int PipeHandle; typedef pid_t ProcessId; #endif union Pipe { PipeHandle Handles[2]; struct { PipeHandle Read; PipeHandle Write; }; Pipe(); bool Create ( #ifdef WIN32 LPSECURITY_ATTRIBUTES pAttr #else void *UnusedParam #endif ); void Close(); }; protected: GString Exe; GArray Args; GString InitialFolder; + bool NewGroup; struct Variable { GString Var, Val; }; bool EnvironmentChanged; GArray Environment; uint32_t ErrorCode; PipeHandle ExternIn, ExternOut; Variable *GetEnvVar(const char *Var, bool Create = false); ProcessId ChildPid; #if defined(POSIX) Pipe Io; int ExitValue; // was uint32 bool Dupe(PipeHandle Old, PipeHandle New); #elif defined(WIN32) HANDLE ChildHnd; DWORD ExitValue; Pipe ChildOutput, ChildInput; bool Dupe(PipeHandle Old, PipeHandle &New); #endif GSubProcess *Parent, *Child; public: // Object GSubProcess(const char *exe, const char *args = NULL); ~GSubProcess(); - // Environment + // Environment void SetInitFolder(const char *f); const char *GetEnvironment(const char *Var); bool SetEnvironment(const char *Var, const char *Value); // Dom (support StreamReadable/StreamWritable) bool GetValue(const char *Var, GVariant &Value) override; // Handles void SetStdin(PipeHandle Hnd); void SetStdout(PipeHandle Hnd); // Process lifecycle + bool GetNewGroup() { return NewGroup; } + void SetNewGroup(bool ng) { NewGroup = ng; } ProcessId Handle() { return ChildPid; } bool IsRunning(); uint32_t GetErrorCode(); int32 GetExitValue(); void Connect(GSubProcess *child); bool Start(bool ReadAccess = true, bool WriteAccess = false, bool MapStderrToStdout = true); int Wait(); - void Interrupt(); - int Kill(); + bool Interrupt(); + bool Kill(); // IO int Peek(); GString Read(); ssize_t Read(void *Buf, ssize_t Size, int Flags = 0) override; bool Write(GString s); ssize_t Write(const void *Buf, ssize_t Size, int Flags = 0) override; }; #endif diff --git a/src/common/Lgi/GSubProcess.cpp b/src/common/Lgi/GSubProcess.cpp --- a/src/common/Lgi/GSubProcess.cpp +++ b/src/common/Lgi/GSubProcess.cpp @@ -1,965 +1,1023 @@ /** \file \brief Sub-process wrapper. This class runs one or more sub-processes chained together by pipes. Example: GSubProcess p1("ls", "-l"); GSubProcess p2("grep", "string"); p1.Connect(&p2); p1.Start(true, false); int r; char Buf[256]; while ((r = p1.Read(Buf, sizeof(Buf))) > 0) { // So something with 'Buf' } */ #if defined(MAC) || defined(POSIX) #define _GNU_SOURCE #include #include #include #include #endif #ifdef BEOS #include #endif #include "Lgi.h" #include "GSubProcess.h" #include "GToken.h" #define DEBUG_SUBPROCESS 0 #if defined(WIN32) #define NULL_PIPE NULL #define ClosePipe CloseHandle #else #define NULL_PIPE -1 #define ClosePipe close #define INVALID_PID -1 #endif GSubProcess::Pipe::Pipe() { Read = Write = NULL_PIPE; } bool GSubProcess::Pipe::Create ( #ifdef WIN32 LPSECURITY_ATTRIBUTES pAttr #else void *UnusedParam #endif ) { #if defined(WIN32) return CreatePipe(&Read, &Write, pAttr, 0) != 0; #else return pipe(Handles) != NULL_PIPE; #endif } void GSubProcess::Pipe::Close() { if (Read != NULL_PIPE) { ClosePipe(Read); Read = NULL_PIPE; } if (Write != NULL_PIPE) { ClosePipe(Write); Write = NULL_PIPE; } } GSubProcess::GSubProcess(const char *exe, const char *args) { #if defined(POSIX) ChildPid = INVALID_PID; ExitValue = -1; #elif defined(WIN32) ChildPid = NULL; ChildHnd = NULL; ExitValue = 0; #endif + NewGroup = true; ErrorCode = 0; Parent = Child = NULL; Exe = exe; Args.Add(Exe); EnvironmentChanged = false; ExternIn = NULL_PIPE; ExternOut = NULL_PIPE; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::GSubProcess('%s','%s')\n", _FL, this, exe, args); #endif char *s; while ((s = LgiTokStr(args))) { Args.Add(s); } } GSubProcess::~GSubProcess() { #if defined(POSIX) Io.Close(); #endif if (Child) { LgiAssert(Child->Parent == this); Child->Parent = NULL; } if (Parent) { LgiAssert(Parent->Child == this); Parent->Child = NULL; } } #ifndef WINDOWS extern char **environ; #endif GSubProcess::Variable *GSubProcess::GetEnvVar(const char *Var, bool Create) { if (Environment.Length() == 0) { // Read all variables in #ifdef WINDOWS LPWCH e = GetEnvironmentStringsW(); if (e) { char16 *s = e; while (*s) { char16 *eq = StrchrW(s, '='); if (!eq) break; ptrdiff_t NameChars = eq - s; if (NameChars > 0) { Variable &v = Environment.New(); v.Var.SetW(s, eq - s); eq++; v.Val.SetW(eq); } eq += StrlenW(eq); s = eq + 1; } FreeEnvironmentStringsW(e); } #else for (int i=0; environ[i]; i++) { auto p = GString(environ[i]).Split("=", 1); if (p.Length() == 2) { Variable &v = Environment.New(); v.Var = p[0]; v.Val = p[1]; } } #endif } for (unsigned i=0; iVal.Get() : NULL; } bool GSubProcess::SetEnvironment(const char *Var, const char *Value) { Variable *v = GetEnvVar(Var, true); if (!v) return false; bool IsPath = !_stricmp(Var, "PATH"); GStringPipe a; const char *s = Value; while (*s) { char *n = strchr(s, '%'); char *e = n ? strchr(n + 1, '%') : NULL; if (n && e) { a.Write(s, (int) (n-s)); n++; ptrdiff_t bytes = e - n; char Name[128]; if (bytes > sizeof(Name) - 1) bytes = sizeof(Name)-1; memcpy(Name, n, bytes); Name[bytes] = 0; const char *existing = GetEnvironment(Name); if (existing) { a.Write(existing, (int)strlen(existing)); } s = e + 1; } else { a.Write(s, (int)strlen(s)); break; } } v->Val = a.NewGStr(); if (IsPath) { // Remove missing paths from the list GToken t(v->Val, LGI_PATH_SEPARATOR); GStringPipe p; for (unsigned i=0; iVal = p.NewGStr(); } EnvironmentChanged = true; return true; } bool GSubProcess::GetValue(const char *Var, ::GVariant &Value) { switch (LgiStringToDomProp(Var)) { case StreamReadable: { #ifdef WINNATIVE char Buf[32] = ""; DWORD lpBytesRead = 0; BOOL b = PeekNamedPipe( ChildOutput.Read, Buf, sizeof(Buf), &lpBytesRead, NULL, NULL); Value = b && lpBytesRead > 0; break; #endif } /* case StreamWritable: { break; } */ default: return false; } return true; } void GSubProcess::SetStdin(OsFile Hnd) { ExternIn = Hnd; } void GSubProcess::SetStdout(OsFile Hnd) { ExternOut = Hnd; } void GSubProcess::Connect(GSubProcess *child) { Child = child; if (Child) { Child->Parent = this; } } bool GSubProcess::Start(bool ReadAccess, bool WriteAccess, bool MapStderrToStdout) { bool Status = false; #if USE_SIMPLE_FORK int in[2]; if (pipe(in) == -1) { printf("parent: Failed to create stdin pipe"); return false; } int out[2]; if (pipe(out) == -1) { printf("parent: Failed to create stdout pipe"); return false; } ChildPid = fork(); if (ChildPid == 0) { // We are in the child process. if (InitialFolder) { chdir(InitialFolder); } // Child shouldn't write to its stdin. if (close(in[1])) printf("%s:%i - close failed.\n", _FL); // Child shouldn't read from its stdout. if (close(out[0])) printf("%s:%i - close failed.\n", _FL); // Redirect stdin and stdout for the child process. if (dup2(in[0], fileno(stdin)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdin for child\n", _FL); return false; } if (close(in[0])) printf("%s:%i - close failed.\n", _FL); if (dup2(out[1], fileno(stdout)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stdout for child\n", _FL); return false; } if (dup2(out[1], fileno(stderr)) == -1) { printf("%s:%i - child[pre-exec]: Failed to redirect stderr for child\n", _FL); return false; } close(out[1]); // Execute the child Args.Add(NULL); if (Environment.Length()) { GString::Array Vars; GArray Env; Vars.SetFixedLength(false); for (auto v : Environment) { GString &s = Vars.New(); s.Printf("%s=%s", v.Var.Get(), v.Val.Get()); Env.Add(s.Get()); } Env.Add(NULL); execve(Exe, &Args[0], Env.AddressOf()); } else { execvp(Exe, &Args[0]); } // Execution will pass to here if the 'Exe' can't run or doesn't exist // So by exiting with an error the parent process can handle it. exit(GSUBPROCESS_ERROR); } else { // We are in the parent process. if (ChildPid == -1) { printf("%s:%i - parent: Failed to create child", _FL); return false; } // Parent shouldn't read from child's stdin. if (close(in[0])) printf("%s:%i - close failed.\n", _FL); // Parent shouldn't write to child's stdout. if (close(out[1])) printf("%s:%i - close failed.\n", _FL); Io.Read = out[0]; Io.Write = in[1]; // printf("USE_SIMPLE_FORK success.\n"); return true; } #else #if DEBUG_SUBPROCESS LgiTrace("%s:%i - %p::Start(%i,%i,%i)\n", _FL, this, ReadAccess, WriteAccess, MapStderrToStdout); #endif // Find the end of the process list ::GArray p; for (GSubProcess *s=this; s; s=s->Child) { LgiAssert(!s->Child || s->Child->Parent == s); p.Add(s); } size_t Kids = p.Length() + 1; #ifdef WIN32 SECURITY_ATTRIBUTES Attr; Attr.nLength = sizeof(SECURITY_ATTRIBUTES); Attr.bInheritHandle = true; Attr.lpSecurityDescriptor = NULL; #else int Attr = 0; #endif #if defined(POSIX) ::GArray Pipes; Pipes.Length(Kids); Pipes[0].Create(&Attr); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].create %i,%i\n", _FL, 0, Pipes[0].Read, Pipes[0].Write); #endif Status = true; for (int i=1; iChildPid = fork(); if (sp->ChildPid == INVALID_PID) { LgiTrace("%s:%i - fork failed with %i", _FL, errno); exit(1); } else if (sp->ChildPid == 0) { if (InitialFolder) { chdir(InitialFolder); } // Close irrelevant pipes for (int j = 0; j < i-1; j++) { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* pipe[%i].close %i,%i\n", _FL, j, Pipes[j].Read, Pipes[j].Write); #endif Pipes[j].Close(); } // Set up STDIN and STDOUT Pipe &in = Pipes[i-1]; Pipe &out = Pipes[i]; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* %i) Child init %i->'%s'->%i\n", _FL, i, in.Read, sp->Exe.Get(), out.Write); #endif #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", _FL, in.Read, STDIN_FILENO); #endif Dupe(in.Read, STDIN_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Close %i\n", _FL, in.Write); #endif close(in.Write); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", _FL, out.Write, STDOUT_FILENO); #endif Dupe(out.Write, STDOUT_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Dupe %i->%i\n", out.Write, STDERR_FILENO); #endif Dupe(out.Write, STDERR_FILENO); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *CHILD* Close %i\n", _FL, out.Read); #endif close(out.Read); fsync(STDOUT_FILENO); LgiSleep(100); // Execute the child sp->Args.Add(NULL); execvp(sp->Exe, &sp->Args[0]); LgiTrace("%s:%i - execvp('%s').\n", _FL, sp->Exe.Get()); for (int i=0; iArgs.Length(); i++) LgiTrace("%s:%i - Args[%i]='%s'\n", _FL, i, sp->Args[i]); Status = false; break; } } // Close irrelevant pipes for (int j = 1; j < Kids - 1; j++) { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].close %i,%i\n", _FL, j, Pipes[j].Read, Pipes[j].Write); #endif Pipes[j].Close(); } #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[0].close %i, pipe[%i].close %i\n", _FL, Pipes[0].Read, Pipes.Length()-1, Pipes.Last().Write); #endif close(Pipes[0].Read); close(Pipes.Last().Write); // Set the input and output pipes for this sub-process. if (WriteAccess) Io.Write = Pipes[0].Write; else { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[0].close %i\n", _FL, Pipes[0].Write); #endif close(Pipes[0].Write); } if (ReadAccess) Io.Read = Pipes.Last().Read; else { #if DEBUG_SUBPROCESS LgiTrace("%s:%i - *PARENT* pipe[%i].close %i\n", _FL, Pipes.Length()-1, Pipes.Last().Read); #endif close(Pipes.Last().Read); } // LgiTrace("Final Handles %i, %i\n", Io.Read, Io.Write); #elif defined(WIN32) GAutoWString WExe; if (FileExists(Exe)) { WExe.Reset(Utf8ToWide(Exe)); } else { char *Ext = LgiGetExtension(Exe); bool HasExt = Ext && _stricmp(Ext, "exe") == 0; #if defined(WIN32) && !defined(PLATFORM_MINGW) GToken p; char *sPath = NULL; size_t sSize; errno_t err = _dupenv_s(&sPath, &sSize, "PATH"); if (err == 0) p.Parse(sPath, LGI_PATH_SEPARATOR); free(sPath); #else GToken p(getenv("PATH"), LGI_PATH_SEPARATOR); #endif for (unsigned i=0; i 0) { WArg[Ch++] = ' '; } if (strchr(a, ' ')) Ch += swprintf_s(WArg+Ch, CountOf(WArg)-Ch, L"\"%s\"", aw.Get()); else Ch += swprintf_s(WArg+Ch, CountOf(WArg)-Ch, L"%s", aw.Get()); } #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Args='%S'\n", _FL, WArg); #endif bool HasExternIn = ExternIn != NULL_PIPE; #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Oringinal handles, out=%p, in=%p, HasExternIn=%i\n", _FL, OldStdout, OldStdin, HasExternIn); #endif if (ChildOutput.Create(&Attr) && (HasExternIn || ChildInput.Create(&Attr))) { if (!SetHandleInformation(ChildOutput.Read, HANDLE_FLAG_INHERIT, 0)) LgiTrace("%s:%i - SetHandleInformation failed.\n", _FL); if (!HasExternIn && !SetHandleInformation(ChildInput.Write, HANDLE_FLAG_INHERIT, 0)) LgiTrace("%s:%i - SetHandleInformation failed.\n", _FL); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - Output Pipe: rd=%p, wr=%p\n", _FL, ChildOutput.Read, ChildOutput.Write); if (!HasExternIn) LgiTrace("%s:%i - Input Pipe: rd=%p, wr=%p\n", _FL, ChildInput.Read, ChildInput.Write); #endif STARTUPINFOW Info; ZeroObj(Info); Info.cb = sizeof(Info); PROCESS_INFORMATION ProcInfo; ZeroObj(ProcInfo); Info.dwFlags = STARTF_USESTDHANDLES; Info.hStdOutput = ChildOutput.Write; Info.hStdInput = HasExternIn ? ExternIn : ChildInput.Read; if (MapStderrToStdout) Info.hStdError = ChildOutput.Write; GAutoWString WInitialFolder(Utf8ToWide(InitialFolder)); #if DEBUG_SUBPROCESS LgiTrace("%s:%i - WInitialFolder=%S, EnvironmentChanged=%i\n", _FL, WInitialFolder.Get(), EnvironmentChanged); #endif GAutoWString WEnv; if (EnvironmentChanged) { GMemQueue q(256); for (unsigned i=0; i 0) p.Write(Buf, Rd); else break; } return p.NewGStr(); } ssize_t GSubProcess::Read(void *Buf, ssize_t Size, int TimeoutMs) { #if defined(POSIX) bool DoRead = true; if (TimeoutMs) { OsSocket s = Io.Read; if (ValidSocket(s)) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { DoRead = true; } else { // printf("SubProc not readable..\n"); return 0; } } else LgiTrace("%s:%i - Invalid socket.\n", _FL); } return (int)read(Io.Read, Buf, Size); #else DWORD Rd = -1, Sz; if (!ReadFile(ChildOutput.Read, Buf, AssertCast(Sz, Size), &Rd, NULL)) return -1; return Rd; #endif } int GSubProcess::Peek() { #if defined(POSIX) int bytesAvailable = 0; int r = ioctl(Io.Read, FIONREAD, &bytesAvailable); return r ? -1 : bytesAvailable; #else DWORD Rd = 0, Avail = 0; char Buf[32]; if (PeekNamedPipe(ChildOutput.Read, Buf, sizeof(Buf), &Rd, &Avail, NULL)) return Rd; return 0; #endif } bool GSubProcess::Write(GString s) { auto Wr = Write(s.Get(), s.Length()); return Wr == s.Length(); } ssize_t GSubProcess::Write(const void *Buf, ssize_t Size, int Flags) { #if defined(POSIX) return (int)write(Io.Write, Buf, Size); #else DWORD Wr = -1, Sz; if (!WriteFile(ChildInput.Write, Buf, AssertCast(Sz, Size), &Wr, NULL)) return -1; return Wr; #endif } diff --git a/src/common/Text/GTextView3.cpp b/src/common/Text/GTextView3.cpp --- a/src/common/Text/GTextView3.cpp +++ b/src/common/Text/GTextView3.cpp @@ -1,5326 +1,5327 @@ #include #include #include #include "Lgi.h" #include "GTextView3.h" #include "GInput.h" #include "GScrollBar.h" #ifdef WIN32 #include #endif #include "GClipBoard.h" #include "GDisplayString.h" #include "GViewPriv.h" #include "GCssTools.h" #include "LgiRes.h" #include "Mail.h" #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 LUIS_DEBUG 0 #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 CURSOR_BLINK 1000 // ms #define ALLOC_BLOCK 64 #define IDC_VS 1000 #ifndef IDM_OPEN #define IDM_OPEN 1 #endif #ifndef IDM_NEW #define IDM_NEW 2 #endif #ifndef IDM_COPY #define IDM_COPY 3 #endif #ifndef IDM_CUT #define IDM_CUT 4 #endif #ifndef IDM_PASTE #define IDM_PASTE 5 #endif #define IDM_COPY_URL 6 #define IDM_AUTO_INDENT 7 #define IDM_UTF8 8 #define IDM_PASTE_NO_CONVERT 9 #ifndef IDM_UNDO #define IDM_UNDO 10 #endif #ifndef IDM_REDO #define IDM_REDO 11 #endif #define IDM_FIXED 12 #define IDM_SHOW_WHITE 13 #define IDM_HARD_TABS 14 #define IDM_INDENT_SIZE 15 #define IDM_TAB_SIZE 16 #define IDM_DUMP 17 #define IDM_RTL 18 #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE GColour(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.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; ////////////////////////////////////////////////////////////////////// class GDocFindReplaceParams3 : public GDocFindReplaceParams, public LMutex { public: // Find/Replace History GAutoWString LastFind; GAutoWString LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; bool SearchUpwards; GDocFindReplaceParams3() { MatchCase = false; MatchWord = false; SelectionOnly = false; SearchUpwards = false; } }; class GTextView3Private : public GCss, public LMutex { public: GTextView3 *View; GRect rPadding; int PourX; bool LayoutDirty; ssize_t DirtyStart, DirtyLen; GColour UrlColour; bool CenterCursor; ssize_t WordSelectMode; GString Eol; GString LastError; // Find/Replace Params bool OwnFindReplaceParams; GDocFindReplaceParams3 *FindReplaceParams; // Map buffer ssize_t MapLen; char16 *MapBuf; // // Thread safe Name(char*) impl GString SetName; // #ifdef _DEBUG GString PourLog; #endif GTextView3Private(GTextView3 *view) : LMutex("GTextView3Private") { View = view; WordSelectMode = -1; PourX = -1; DirtyStart = DirtyLen = 0; UrlColour.Rgb(0, 0, 255); uint32_t c24 = 0; if (_lgi_read_colour_config("colour.LC_URL", &c24)) UrlColour.c24(c24); CenterCursor = false; LayoutDirty = true; rPadding.ZOff(0, 0); MapBuf = 0; MapLen = 0; OwnFindReplaceParams = true; FindReplaceParams = new GDocFindReplaceParams3; } ~GTextView3Private() { 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 == GCss::PropPadding || Prop == GCss::PropPaddingLeft || Prop == GCss::PropPaddingRight || Prop == GCss::PropPaddingTop || Prop == GCss::PropPaddingBottom) { GCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public GRange { UndoType Type; GArray Txt; }; struct GTextView3Undo : public GUndoEvent { GTextView3 *View; GArray Changes; GTextView3Undo(GTextView3 *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); } } // GUndoEvent 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 GTextView3::GStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// GTextView3::GTextView3( int Id, int x, int y, int cx, int cy, GFontType *FontType) : ResObject(Res_Custom) { // init vars GView::d->Css.Reset(d = new GTextView3Private(this)); PourEnabled = true; PartialPour = false; 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(GCss::Len(GCss::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 { GFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new GFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour.c24()); Underline->Create(); } Bold = new GFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = SysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); GRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LgiResources::StyleElement(this); } GTextView3::~GTextView3() { Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != SysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the GView::Css auto ptr } char16 *GTextView3::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 GTextView3::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { GFontType Type; if (Type.GetSystemFont("Fixed")) { GFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } GDocView::SetFixedWidthFont(i); } } else if (FixedFont) { GFont *f = FixedFont; FixedFont = Font; Font = f; GDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void GTextView3::SetReadOnly(bool i) { GDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void GTextView3::SetCrLf(bool crlf) { CrLf = crlf; } void GTextView3::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void GTextView3::SetWrapType(LDocWrapType i) { GDocView::SetWrapType(i); CanScrollX = i != TEXTED_WRAP_REFLOW; OnPosChange(); Invalidate(); } GFont *GTextView3::GetFont() { return Font; } GFont *GTextView3::GetBold() { return Bold; } void GTextView3::SetFont(GFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != SysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == SysFont) { Font = new GFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new GFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour.c24()); } if (!Bold) Bold = new GFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void GTextView3::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; GDisplayString 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 GTextView3::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 GTextView3::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; GTextLine *Prev = NULL; for (auto i : Line) { GTextLine *l = i; if (l->Start != Pos) { LogLines(); LgiAssert(!"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(); LgiAssert(!"Incorrect length."); return false; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { LogLines(); LgiAssert(!"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(); LgiAssert(!"Last line != end of doc"); return false; } return true; } int GTextView3::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 GTextView3::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); GProfile Prof(_txt); #endif LgiAssert(InThread()); GRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; GTextLine *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(); GDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LgiCurrentTime(); 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 GStringPipe 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++) { GTextLine *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... { GDisplayString 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) { GTextLine *l = new GTextLine; 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) { GDisplayString 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; } 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 GDisplayString 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])) { GDisplayString 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 GTextLine *l = new GTextLine; 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; break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LgiCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; // LgiTrace("Pour timeout...\n"); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) PartialPour = false; SendNotify(GNotifyCursorChanged); } #ifdef _DEBUG ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { GTextLine *Last = Line.Length() ? Line.Last() : 0; if (!Last || Last->Start + Last->Len < Size) { GTextLine *l = new GTextLine; 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() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; #if PROFILE_POUR static GString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { // printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded); 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, (GMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (GTextLine *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 GTextView3::InsertStyle(GAutoPtr s) { if (!s) return false; LgiAssert(s->Start >= 0); LgiAssert(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; } GTextView3::GStyle *GTextView3::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 GArray Ver; int Os = LgiGetOs(&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 GTextView3::GStyle *GTextView3::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void GTextView3::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LgiCurrentTime(); #endif LgiAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); // 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) { GArray Links; LgiAssert((ssize_t)Start + Length <= Size); if (LgiDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new GStyle(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 = LgiCurrentTime() - StartTime; #endif } bool GTextView3::Insert(size_t At, const char16 *Data, ssize_t Len) { GProfile Prof("GTextView3::Insert"); Prof.HideResultsIfBelow(1000); LgiAssert(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) { GAutoPtr Obj(new GTextView3Undo(this)); GTextView3Undo *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; GTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Insert(Cur = new GTextLine); 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 GTextLine(); 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) { GTextLine *l = Line.Last(); 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"); PourStyle(At, Len); } SendNotify(GNotifyDocChanged); return true; } } return false; } bool GTextView3::Delete(size_t At, ssize_t Len) { bool Status = false; LgiAssert(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 GTextView3Undo(this)); GTextView3Undo *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; GTextLine *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; GTextLine *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; GTextLine *Cur = GetTextLine(At, &Index); if (Cur) { GRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(GNotifyDocChanged); Status = true; } } return Status; } void GTextView3::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); } } GTextView3::GTextLine *GTextView3::GetTextLine(ssize_t Offset, ssize_t *Index) { int i = 0; for (GTextLine *l = Line.First(); l; l = Line.Next(), i++) { if (Offset >= l->Start && Offset <= l->Start+l->Len) { if (Index) { *Index = i; } return l; } } return NULL; } int64 GTextView3::Value() { char *n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void GTextView3::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } GString GTextView3::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return GString(); GTextLine *Ln = Line[LineIdx-1]; if (!Ln) return GString(); GString s(Text + Ln->Start, Ln->Len); return s; } char *GTextView3::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool GTextView3::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LgiAssert(LgiIsUtf8(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 LgiAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } char16 *GTextView3::NameW() { return Text; } bool GTextView3::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; } GRange GTextView3::GetSelectionRange() { GRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *GTextView3::GetSelection() { GRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LgiNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool GTextView3::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void GTextView3::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void GTextView3::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t GTextView3::GetLines() { return Line.Length(); } void GTextView3::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool GTextView3::GetLineColumnAtIndex(GdcPt2 &Pt, ssize_t Index) { ssize_t FromIndex = 0; GTextLine *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 GTextView3::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t GTextView3::IndexAt(int x, int y) { GTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool GTextView3::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; GTextLine *To = GetTextLine(Off, &ToIndex); if (VScroll && To) { GRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; 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; } } } return ForceFullUpdate; } void GTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LgiCurrentTime(); 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; GTextLine *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; GTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds GRect 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 { LgiAssert(0); return; } GTextLine *SLine = GetTextLine(Start); GTextLine *ELine = GetTextLine(End); GRect 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 GRect 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 GRect 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(GNotifyCursorChanged); } //int _Time = LgiCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void GTextView3::SetBorder(int b) { } bool GTextView3::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LgiNewConvertCp(LgiAnsiToLgiCp(), Txt16, LGI_WideCharset); GClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool GTextView3::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); char16 *Txt16 = NewStrW(Text+Min, Max-Min); #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LgiNewConvertCp(LgiAnsiToLgiCp(), Txt16, LGI_WideCharset); GClipBoard Clip(this); Clip.Text(Txt8); Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool GTextView3::Paste() { GClipBoard Clip(this); GAutoWString 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 GTextView3::ClearDirty(bool Ask, char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LgiLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LgiLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { GFileSelect Select; Select.Parent(this); if (!FileName && Select.Save()) { FileName = Select.Name(); } Save(FileName); } else if (Answer == IDCANCEL) { return false; } } return true; } bool GTextView3::Open(const char *Name, const char *CharSet) { bool Status = false; GFile 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*)LgiNewConvertCp(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; } bool GTextView3::Save(const char *Name, const char *CharSet) { GFile f; GString 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) { char *c8 = (char*)LgiNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, Size * sizeof(char16)); if (c8) { int Len = (int)strlen(c8); if (CrLf) { Status = true; int BufLen = 1 << 20; GAutoString Buf(new char[BufLen]); char *b = Buf; char *e = Buf + BufLen; char *c = c8; while (*c) { if (b > e - 10) { ptrdiff_t Bytes = b - Buf; if (f.Write(Buf, (int)Bytes) != Bytes) { Status = false; break; } b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } ptrdiff_t Bytes = b - Buf; if (f.Write(Buf, (int)Bytes) != Bytes) Status = false; } else { Status = f.Write(c8, Len) == Len; } DeleteArray(c8); } Dirty = false; } } else { int Err = f.GetError(); GAutoString sErr = LgiErrorCodeToString(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 *GTextView3::GetLastError() { return d->LastError; } void GTextView3::UpdateScrollBars(bool Reset) { if (VScroll) { GRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = GetLines(); // printf("SetLimits %i, %i\n", 0, (int)Lines); VScroll->SetLimits(0, 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; } GRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } } bool GTextView3::DoCase(bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { GAutoPtr Obj(new GTextView3Undo(this)); GTextView3Undo *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(GNotifyDocChanged); } } return true; } ssize_t GTextView3::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } void GTextView3::SetLine(int i) { GTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, false); d->CenterCursor = false; } } bool GTextView3::DoGoto() { GInput Dlg(this, "", LgiLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); if (Dlg.DoModal() == IDOK && Dlg.GetStr()) { SetLine(atoi(Dlg.GetStr())); } return true; } GDocFindReplaceParams *GTextView3::CreateFindReplaceParams() { return new GDocFindReplaceParams3; } void GTextView3::SetFindReplaceParams(GDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (GDocFindReplaceParams3*) Params; } } bool GTextView3::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(GFindReplaceCommon *Dlg, bool Replace, void *User) { GTextView3 *v = (GTextView3*) 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 GTextView3::DoFind() { GString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = GString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } #ifdef BEOS GFindDlg *Dlg = new GFindDlg(this, u, Text3_FindCallback, this); if (Dlg) Dlg->DoModeless(); #else GFindDlg Dlg(this, u, Text3_FindCallback, this); Dlg.DoModal(); Focus(true); #endif return false; } bool GTextView3::DoReplace() { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { GRange 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); GReplaceDlg 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 GTextView3::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 GTextView3::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 - FindLen; 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; } } GRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) return r.Start; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool GTextView3::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) { GAutoPtr s(new GStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore.Set(LC_FOCUS_SEL_FORE, 24); s->Back = GColour(LC_FOCUS_SEL_BACK, 24).Mix(GColour(LC_WORKSPACE, 24)); 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 GTextView3::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 GTextView3::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: { LgiAssert(false); break; } } return Offset; } bool GTextView3::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; GArray 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 GTextView3::OnSetHidden(int Hidden) { } void GTextView3::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; GLayout::OnPosChange(); GRect c = GetClient(); bool ScrollYNeeded = c.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { // printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int GTextView3::WillAccept(List &Formats, GdcPt2 Pt, int KeyState) { for (char *s = Formats.First(); s; ) { if (!_stricmp(s, "text/uri-list") || !_stricmp(s, "text/html") || !_stricmp(s, "UniformResourceLocatorW")) { s = Formats.Next(); } else { // LgiTrace("Ignoring format '%s'\n", s); Formats.Delete(s); DeleteArray(s); s = Formats.Current(); } } return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int GTextView3::OnDrop(GArray &Data, GdcPt2 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++; } GAutoWString w ( (char16*)LgiNewConvertCp ( 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 for (GViewI *p = GetParent(); p; p = p->GetParent()) { GDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); break; } } } } return Status; } void GTextView3::OnCreate() { SetWindow(this); DropTarget(true); SetPulse(PULSE_TIMEOUT); } void GTextView3::OnEscape(GKey &K) { } bool GTextView3::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 GTextView3::OnFocus(bool f) { Invalidate(); } ssize_t GTextView3::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; int Y = (VScroll) ? (int)VScroll->Value() : 0; GTextLine *l = Line.ItemAt(Y); y += (l) ? l->r.y1 : 0; while (l) { if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; GDisplayString 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; } } l = (Down) ? Line.Next() : Line.Prev(); Y++; } // outside text area if (Down) { l = Line.Last(); if (l) { if (y > l->r.y2) { // end of document return Size; } } } return 0; } void GTextView3::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(GNotifyDocChanged); } } void GTextView3::Redo() { UndoQue.Redo(); } void GTextView3::DoContextMenu(GMouse &m) { GSubMenu RClick; GAutoString ClipText; { GClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } #if LUIS_DEBUG RClick.AppendItem("Dump Layout", IDM_DUMP, true); RClick.AppendSeparator(); #endif GStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LgiLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); GMenuItem *i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LgiLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LgiLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { #if LUIS_DEBUG case IDM_DUMP: { int n=0; for (GTextLine *l=Line.First(); l; l=Line.Next(), n++) { LgiTrace("[%i] %i,%i (%s)\n", n, l->Start, l->Len, l->r.Describe()); char *s = WideToUtf8(Text + l->Start, l->Len); if (s) { LgiTrace("%s\n", s); DeleteArray(s); } } break; } #endif case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(GNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); 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); GInput 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); GInput 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 GTextView3::OnStyleClick(GStyle *style, GMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { GString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool GTextView3::OnStyleMenu(GStyle *style, GSubMenu *m) { switch (style->Owner) { case STYLE_URL: { GString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LgiLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LgiLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LgiLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void GTextView3::OnStyleMenuClick(GStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { GString 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) { GClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void GTextView3::OnMouseClick(GMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (!m.IsContextMenu()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); GStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } else { DoContextMenu(m); return; } } if (!Processed) { Capture(m.Down()); } } int GTextView3::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return GView::OnHitTest(x, y); } void GTextView3::OnMouseMove(GMouse &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(); } } } LgiCursor GTextView3::GetCursor(int x, int y) { GRect c = GetClient(); c.Offset(-c.x1, -c.y1); GStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int GTextView3::GetColumn() { int x = 0; GTextLine *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.c16 == VK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.c16 == VK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.c16 == VK_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 VK_RETURN: { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case VK_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 VK_TAB: return true; case VK_RETURN: { return !GetReadOnly(); } case VK_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 VK_F3: { if (k.Down()) { DoFindNext(); } return true; break; } case VK_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 #endif if (k.Ctrl() || k.Alt()) { // 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 VK_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 #endif if (k.Ctrl() || k.Alt()) { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); GDisplayString 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 VK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView3_PageDown; #endif GTextLine *l = GetTextLine(Cursor); if (l) { GTextLine *Next = Line.Next(); if (Next) { GDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); GDisplayString 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 VK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif GTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case VK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif GTextLine *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 VK_PAGEUP: { #ifdef MAC GTextView3_PageUp: #endif if (k.Down()) { GTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); GTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case VK_PAGEDOWN: { #ifdef MAC GTextView3_PageDown: #endif if (k.Down()) { GTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); GTextLine *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 VK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case VK_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.Modifier() && !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 VK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void GTextView3::OnEnter(GKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; GTextLine *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 GTextView3::TextWidth(GFont *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++); GDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int GTextView3::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int GTextView3::ScrollYPixel() { return ScrollYLine() * LineY; } GRect GTextView3::DocToScreen(GRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void GTextView3::OnPaintLeftMargin(GSurface *pDC, GRect &r, GColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void GTextView3::OnPaint(GSurface *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); GProfile 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); } #if PROFILE_PAINT Prof.Add("Setup"); #endif GRect 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 GSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); GColour SelectedText(HasFocus ? LC_FOCUS_SEL_FORE : LC_NON_FOCUS_SEL_FORE, 24); GColour SelectedBack(HasFocus ? LC_FOCUS_SEL_BACK : LC_NON_FOCUS_SEL_BACK, 24); GCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } GColour Fore(ForeDef.Type == GCss::ColorRgb ? Rgb32To24(ForeDef.Rgb32) : LC_TEXT, 24); GColour Back ( /*!ReadOnly &&*/ BkDef.Type == GCss::ColorRgb ? Rgb32To24(BkDef.Rgb32) : Enabled() ? LC_WORKSPACE : LC_MED, 24 ); // GColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore.Set(LC_LOW, 24); Back.Set(LC_MED, 24); } #ifdef DOUBLE_BUFFER_PAINT GMemDC *pMem = new GMemDC; 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 { GRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); GTextLine *l=Line.ItemAt(k); int Dy = (l) ? -l->r.y1 : 0; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (l && 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(); GStyle *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; for (; l && l->r.y1+Dy < r.Y(); l=Line.Next()) { GRect Tr = l->r; #ifdef DOUBLE_BUFFER_PAINT Tr.Offset(-Tr.x1, -Tr.y1); #else Tr.Offset(0, y - Tr.y1); #endif //GRect 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 { GColour fore = l->c.IsValid() ? l->c : Fore; GColour 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 << GDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << GDisplayString::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; } LgiAssert(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) { GFont *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); LgiAssert(l->Start + Done >= 0); GDisplayString 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 == GCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour.c24(), 24); int x = FX >> GDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); GColour fore = l->c.IsValid() ? l->c : Fore; GColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LgiAssert(0); } else { // draw a block of normal text LgiAssert(l->Start + Done >= 0); GDisplayString 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 { GColour fore = l->c.IsValid() ? l->c : Fore; GColour 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 >> GDisplayString::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 // GColour 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; GDisplayString 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 GRect Scr = GetClient(); Scr.Offset(ScrollX, 0); GRect 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) { GRect c = CursorPos; #ifdef DOUBLE_BUFFER_PAINT c.Offset(-d->rPadding.x1, -y); #endif pOut->Colour((!ReadOnly) ? Fore : GColour(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(GSurface::LineAlternate); GColour Old = pDC->Colour(GColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); GString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); GDisplayString ds(SysFont, s); SysFont->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; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(GColour(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 = LgiCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); GMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "GTextView3::OnPaint crashed.", "Lgi"); } #endif } GMessage::Result GTextView3::OnEvent(GMessage *Msg) { switch (MsgCode(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)MsgA(Msg); char *Out = (char*)MsgB(Msg); if (Out) { char *In = (char*)LgiNewConvertCp(LgiAnsiToLgiCp(), 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*)LgiNewConvertCp(LGI_WideCharset, Buf, LgiAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return GLayout::OnEvent(Msg); } int GTextView3::OnNotify(GViewI *Ctrl, int Flags) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (Flags == GNotifyScrollBar_Create) { UpdateScrollBars(); } Invalidate(); } return 0; } void GTextView3::OnPulse() { if (!ReadOnly) { uint64 Now = LgiCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; GRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void GTextView3::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { GUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.Protocol; GString App = LGetAppForProtocol(Proto); if (App) LgiExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool GTextView3::OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } /////////////////////////////////////////////////////////////////////////////// class GTextView3_Factory : public GViewFactory { GView *NewView(const char *Class, GRect *Pos, const char *Text) { if (_stricmp(Class, "GTextView3") == 0) { return new GTextView3(-1, 0, 0, 2000, 2000); } return 0; } } TextView3_Factory;