diff --git a/ResourceEditor/src/LgiResApp.cpp b/ResourceEditor/src/LgiResApp.cpp --- a/ResourceEditor/src/LgiResApp.cpp +++ b/ResourceEditor/src/LgiResApp.cpp @@ -1,4736 +1,4728 @@ /* ** FILE: LgiRes.cpp ** AUTHOR: Matthew Allen ** DATE: 3/8/99 ** DESCRIPTION: Lgi Resource Editor ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "LgiRes_Menu.h" #include "lgi/common/About.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextView3.h" #include "lgi/common/Token.h" #include "lgi/common/DataDlg.h" #include "lgi/common/Button.h" #include "lgi/common/Menu.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" char AppName[] = "Lgi Resource Editor"; char HelpFile[] = "Help.html"; char OptionsFileName[] = "Options.r"; char TranslationStrMagic[] = "LgiRes.String"; #define VIEW_PULSE_RATE 100 #ifndef DIALOG_X #define DIALOG_X 1.56 #define DIALOG_Y 1.85 #define CTRL_X 1.50 #define CTRL_Y 1.64 #endif enum Ctrls { IDC_HBOX = 100, IDC_VBOX, }; const char *TypeNames[] = { "", "Css", "Dialog", "String", "Menu", 0}; ////////////////////////////////////////////////////////////////////////////// ResFileFormat GetFormat(const char *File) { ResFileFormat Format = Lr8File; char *Ext = LGetExtension(File); if (Ext) { if (stricmp(Ext, "lr") == 0) Format = CodepageFile; else if (stricmp(Ext, "xml") == 0) Format = XmlFile; } return Format; } char *EncodeXml(const char *Str, int Len) { char *Ret = 0; if (Str) { LStringPipe p; const char *s = Str; for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '<': { p.Push(s, e-s); p.Push("<"); s = ++e; break; } case '>': { p.Push(s, e-s); p.Push(">"); s = ++e; break; } case '&': { p.Push(s, e-s); p.Push("&"); s = ++e; break; } case '\\': { if (e[1] == 'n') { // Newline p.Push(s, e-s); p.Push("\n"); s = (e += 2); break; } // fall thru } case '\'': case '\"': case '/': { // Convert to entity p.Push(s, e-s); char b[32]; snprintf(b, sizeof(b), "&#%i;", *e); p.Push(b); s = ++e; break; } default: { // Regular character e++; break; } } } p.Push(s); Ret = p.NewStr(); } return Ret; } char *DecodeXml(const char *Str, int Len) { if (Str) { LStringPipe p; const char *s = Str; for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '&': { // Store string up to here p.Push(s, e-s); e++; if (*e == '#') { // Numerical e++; if (*e == 'x' || *e == 'X') { // Hex e++; char16 c = htoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else if (isdigit(*e)) { // Decimal char16 c = atoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else { LAssert(0); } while (*e && *e != ';') e++; } else if (isalpha(*e)) { // named entity const char *Name = e; while (*e && *e != ';') e++; auto Len = e - Name; if (Len == 3 && strnicmp(Name, "amp", Len) == 0) { p.Push("&"); } else if (Len == 2 && strnicmp(Name, "gt", Len) == 0) { p.Push(">"); } else if (Len == 2 && strnicmp(Name, "lt", Len) == 0) { p.Push("<"); } else { // Unsupported entity LAssert(0); } } else { LAssert(0); while (*e && *e != ';') e++; } s = ++e; break; } case '\n': { p.Push(s, e-s); p.Push("\\n"); s = ++e; break; } default: { e++; break; } } } p.Push(s); return p.NewStr(); } return 0; } ////////////////////////////////////////////////////////////////////////////// Resource::Resource(AppWnd *w, int t, bool enabled) { AppWindow = w; ResType = t; Item = 0; SysObject = false; LAssert(AppWindow); } Resource::~Resource() { AppWindow->OnResourceDelete(this); if (Item) { Item->Obj = 0; DeleteObj(Item); } } bool Resource::IsSelected() { return Item?Item->Select():false; } bool Resource::Attach(LViewI *Parent) { LView *w = Wnd(); if (w) { return w->Attach(Parent); } return false; } ////////////////////////////////////////////////////////////////////////////// ResFolder::ResFolder(AppWnd *w, int t, bool enabled) : Resource(w, t, enabled) { Wnd()->Name(""); Wnd()->Enabled(enabled); } ////////////////////////////////////////////////////////////////////////////// ObjTreeItem::ObjTreeItem(Resource *Object) { if ((Obj = Object)) { Obj->Item = this; if (dynamic_cast(Object)) SetImage(ICON_FOLDER); else { int t = Object->Type(); switch (t) { case TYPE_CSS: SetImage(ICON_CSS); break; case TYPE_DIALOG: SetImage(ICON_DIALOG); break; case TYPE_STRING: SetImage(ICON_STRING); break; case TYPE_MENU: SetImage(ICON_MENU); break; } } } } ObjTreeItem::~ObjTreeItem() { if (Obj) { Obj->Item = 0; DeleteObj(Obj); } } const char *ObjTreeItem::GetText(int i) { if (Obj) { int Type = Obj->Type(); if (Type > 0) return Obj->Wnd()->Name(); else return TypeNames[-Type]; } return "#NO_OBJ"; } void ObjTreeItem::OnSelect() { if (Obj) { Obj->App()->OnResourceSelect(Obj); } } void ObjTreeItem::OnMouseClick(LMouse &m) { if (!Obj) return; if (m.IsContextMenu()) { Tree->Select(this); LSubMenu RClick; if (Obj->Wnd()->Enabled()) { if (Obj->Type() > 0) { // Resource RClick.AppendItem("Delete", IDM_DELETE, !Obj->SystemObject()); RClick.AppendItem("Rename", IDM_RENAME, !Obj->SystemObject()); } else { // Folder RClick.AppendItem("New", IDM_NEW, true); RClick.AppendSeparator(); auto Insert = RClick.AppendSub("Import from..."); if (Insert) { Insert->AppendItem("Lgi File", IDM_IMPORT, true); Insert->AppendItem("Win32 Resource Script", IDM_IMPORT_WIN32, false); } } // Custom entries if (!Obj->SystemObject()) { Obj->OnRightClick(&RClick); } } else { RClick.AppendItem("Not implemented", 0, false); } if (Tree->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Tree, m.x, m.y)) { case IDM_NEW: { SerialiseContext Ctx; Obj->App()->NewObject(Ctx, 0, -Obj->Type()); break; } case IDM_DELETE: { Obj->App()->SetDirty(true, [this](auto ok) { if (ok) Obj->App()->DelObject(Obj); }); break; } case IDM_RENAME: { auto Dlg = new LInput(Tree, GetText(), "Enter the name for the object", "Object Name"); Dlg->DoModal([&](auto dlg, auto id) { if (id) { Obj->Wnd()->Name(Dlg->GetStr()); Update(); Obj->App()->SetDirty(true, NULL); } delete dlg; }); break; } case IDM_IMPORT: { auto Select = new LFileSelect(Obj->App()); Select->Type("Text", "*.txt"); Select->Open([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(dlg->Name(), O_READ)) { SerialiseContext Ctx; Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type()); if (Res) { // TODO // Res->Read(); } } else { LgiMsg(Obj->App(), "Couldn't open file for reading."); } } delete dlg; }); break; } case IDM_IMPORT_WIN32: { /* List l; if (ImportWin32Dialogs(l, MainWnd)) { for (ResDialog *r = l.First(); r; r = l.Next()) { Obj->App()->InsertObject(TYPE_DIALOG, r); } } */ break; } default: { Obj->OnCommand(Cmd); break; } } } } } ////////////////////////////////////////////////////////////////////////////// FieldView::FieldView(AppWnd *app) : Fields(NextId, true) { NextId = 100; App = app; Source = 0; Ignore = true; SetTabStop(true); Sunken(true); #ifdef WIN32 SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } FieldView::~FieldView() { } void FieldView::Serialize(bool Write) { if (!Source) return; Ignore = !Write; Fields.SetMode(Write ? FieldTree::UiToObj : FieldTree::ObjToUi); Fields.SetView(this); Source->Serialize(Fields); /* for (DataDlgField *f=Fields.First(); f; f=Fields.Next()) { LViewI *v; if (GetViewById(f->GetCtrl(), v)) { switch (f->GetType()) { case DATA_STR: { if (Write) // Ctrl -> Options { char *s = v->Name(); Options->Set(f->GetOption(), s); } else // Options -> Ctrl { char *s = 0; Options->Get(f->GetOption(), s); v->Name(s?s:(char*)""); } break; } case DATA_BOOL: case DATA_INT: { if (Write) // Ctrl -> Options { char *s = v->Name(); if (s && (s = strchr(s, '\''))) { s++; char *e = strchr(s, '\''); int i = 0; if (e - s == 4) { memcpy(&i, s, 4); i = LgiSwap32(i); Options->Set(f->GetOption(), i); } } else { int i = v->Value(); Options->Set(f->GetOption(), i); } } else // Options -> Ctrl { int i = 0; Options->Get(f->GetOption(), i); if (i != -1 && (i & 0xff000000) != 0) { char m[8]; i = LgiSwap32(i); sprintf(m, "'%04.4s'", &i); v->Name(m); } else { v->Value(i); } } break; } case DATA_FLOAT: case DATA_PASSWORD: case DATA_STR_SYSTEM: default: { LAssert(0); break; } } } else LAssert(0); } */ Ignore = false; } class TextViewEdit : public LTextView3 { public: bool Multiline; TextViewEdit( int Id, int x, int y, int cx, int cy, LFontType *FontInfo = 0) : LTextView3(Id, x, y, cx, cy, FontInfo) { Multiline = false; #ifdef WIN32 SetDlgCode(DLGC_WANTARROWS | DLGC_WANTCHARS); #endif } bool OnKey(LKey &k) { if (!Multiline && (k.c16 == '\t' || k.c16 == LK_RETURN)) { return false; } return LTextView3::OnKey(k); } }; class Hr : public LView { public: Hr(int x1, int y, int x2) { LRect r(x1, y, x2, y+1); SetPos(r); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); LThinBorder(pDC, c, DefaultSunkenEdge); } bool OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min) Inf.Height.Min = Inf.Height.Max = 2; else Inf.Width.Min = Inf.Width.Max = -1; return true; } }; void FieldView::OnDelete(FieldSource *s) { if (Source != NULL && Source == s) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = NULL; } } void FieldView::OnSelect(FieldSource *s) { Ignore = true; OnDelete(Source); if (Source) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = 0; } if (s) { // Add new fields Source = s; Source->_FieldView = AddDispatch(); if (Source->GetFields(Fields)) { LFontType Sys; Sys.GetSystemFont("System"); LTableLayout *t = new LTableLayout(IDC_TABLE); int Row = 0; LLayoutCell *Cell; LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++, Row++) { FieldTree::Field *c = (*b)[n]; switch (c->Type) { case DATA_STR: case DATA_FLOAT: case DATA_INT: case DATA_FILENAME: { Cell = t->GetCell(0, Row); Cell->VerticalAlign(LCss::VerticalMiddle); Cell->Add(new LTextLabel(-1, 0, 0, -1, -1, c->Label)); TextViewEdit *Tv; Cell = t->GetCell(1, Row, true, c->Type == DATA_FILENAME ? 1 : 2); Cell->Add(Tv = new TextViewEdit(c->Id, 0, 0, 100, 20, &Sys)); if (Tv) { Tv->Multiline = c->Multiline; Tv->GetCss(true)->Height(LCss::Len(LCss::LenPx, c->Multiline ? LSysFont->GetHeight() * 8 : LSysFont->GetHeight() + 8)); Tv->SetWrapType(TEXTED_WRAP_NONE); Tv->Sunken(true); } if (c->Type == DATA_FILENAME) { Cell = t->GetCell(2, Row); Cell->Add(new LButton(-c->Id, 0, 0, 21, 21, "...")); } break; } case DATA_BOOL: { Cell = t->GetCell(1, Row, true, 2); Cell->Add(new LCheckBox(c->Id, 0, 0, -1, -1, c->Label)); break; } default: LAssert(!"Impl me."); break; } } if (i < a.Length() - 1) { Cell = t->GetCell(0, Row++, true, 3); Cell->Add(new Hr(0, 0, X()-1)); } } AddView(t); OnPosChange(); AttachChildren(); Invalidate(); } Serialize(false); Ignore = false; } } void FieldView::OnPosChange() { LRect c = GetClient(); c.Inset(6, 6); LViewI *v; if (GetViewById(IDC_TABLE, v)) v->SetPos(c); } LMessage::Result FieldView::OnEvent(LMessage *m) { switch (m->Msg()) { case M_OBJECT_CHANGED: { FieldSource *Src = (FieldSource*)m->A(); if (Src == Source) { Fields.SetMode(FieldTree::ObjToUi); Fields.SetView(this); Serialize(false); } else LAssert(0); break; } } return LLayout::OnEvent(m); } int FieldView::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ignore) { LTextView3 *Tv = dynamic_cast(Ctrl); if (Tv && n.Type == LNotifyCursorChanged) { return 0; } LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++) { FieldTree::Field *c = (*b)[n]; if (c->Id == Ctrl->GetId()) { // Write the value back to the objects Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); return 0; } else if (c->Id == -Ctrl->GetId()) { auto s = new LFileSelect(this); s->Open([&](auto dlg, auto status) { if (status) { auto File = App->GetCurFile(); if (File) { LFile::Path p = File; p--; auto Rel = LMakeRelativePath(p, dlg->Name()); if (Rel) SetCtrlName(c->Id, Rel); else SetCtrlName(c->Id, dlg->Name()); } else SetCtrlName(c->Id, dlg->Name()); Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); } delete dlg; }); } } } } return 0; } void FieldView::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } ////////////////////////////////////////////////////////////////////////////// ObjContainer::ObjContainer(AppWnd *w) : LTree(100, 0, 0, 100, 100, "LgiResObjTree") { Window = w; Sunken(true); Insert(Style = new ObjTreeItem( new ResFolder(Window, -TYPE_CSS))); Insert(Dialogs = new ObjTreeItem( new ResFolder(Window, -TYPE_DIALOG))); Insert(Strings = new ObjTreeItem( new ResFolder(Window, -TYPE_STRING))); Insert(Menus = new ObjTreeItem( new ResFolder(Window, -TYPE_MENU))); const char *IconFile = "_icons.gif"; auto f = LFindFile(IconFile); if (f) { Images = LLoadImageList(f, 16, 16); if (Images) SetImageList(Images, false); else LgiTrace("%s:%i - failed to load '%s'\n", _FL, IconFile); } } ObjContainer::~ObjContainer() { DeleteObj(Images); } bool ObjContainer::AppendChildren(ObjTreeItem *Res, List &Lst) { bool Status = true; if (Res) { LTreeItem *Item = Res->GetChild(); while (Item) { ObjTreeItem *i = dynamic_cast(Item); if (i) Lst.Insert(i->GetObj()); else Status = false; Item = Item->GetNext(); } } return Status; } Resource *ObjContainer::CurrentResource() { ObjTreeItem *Item = dynamic_cast(Selection()); if (!Item) return NULL; return Item->GetObj(); } bool ObjContainer::ListObjects(List &Lst) { bool Status = AppendChildren(Style, Lst); Status &= AppendChildren(Dialogs, Lst); Status &= AppendChildren(Strings, Lst); Status &= AppendChildren(Menus, Lst); return Status; } ////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 int Icon = IDI_ICON1; #else const char *Icon = "icon64.png"; #endif AppWnd::AppWnd() : LDocApp(AppName, Icon) { - LastRes = 0; - Fields = 0; - ViewMenu = 0; - ContentView = NULL; - VBox = NULL; - HBox = NULL; - ShortCuts = 0; - CurLang = -1; ShowLanguages.Add("en", true); + SetQuitOnClose(true); if (_Create()) { LVariant Langs; if (GetOptions()->GetValue(OPT_ShowLanguages, Langs)) { ShowLanguages.Empty(); LToken L(Langs.Str(), ","); for (int i=0; iEmpty(); _Destroy(); } void AppWnd::OnCreate() { if (_LoadMenu("IDM_MENU")) { if (_FileMenu) { int n = 6; _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Import Win32 Script", IDM_IMPORT_WIN32, true, n++); _FileMenu->AppendItem("Import LgiRes Language", IDM_IMPORT_LANG, true, n++); _FileMenu->AppendItem("Compare To File...", IDM_COMPARE, true, n++); _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Properties", IDM_PROPERTIES, true, n++); } ViewMenu = Menu->FindSubMenu(IDM_VIEW); LAssert(ViewMenu); } else LgiTrace("%s:%i - _LoadMenu failed.\n", _FL); Status = 0; StatusInfo[0] = StatusInfo[1] = 0; HBox = new LBox(IDC_HBOX); if (HBox) { HBox->GetCss(true)->Padding("5px"); VBox = new LBox(IDC_VBOX, true); if (VBox) { HBox->AddView(VBox); VBox->AddView(Objs = new ObjContainer(this)); if (Objs) { Objs->AskImage(true); Objs->AskText(true); } VBox->AddView(Fields = new FieldView(this)); VBox->Value(200); } HBox->Value(240); HBox->Attach(this); } DropTarget(true); LString Open; if (LAppInst->GetOption("o", Open)) LoadLgi(Open); } void AppWnd::OnLanguagesChange(LLanguageId Lang, bool Add, bool Update) { bool Change = false; if (Lang) { // Update the list.... bool Has = false; for (int i=0; iId, Lang) == 0) { Has = true; if (!Add) { Languages.DeleteAt(i); Change = true; } break; } } if (Add && !Has) { Change = true; Languages.Add(LFindLang(Lang)); } } // Update the menu... if (ViewMenu && (Change || Update)) { // Remove existing language menu items while (ViewMenu->RemoveItem(2)); // Add new ones int n = 0; for (int i=0; iAppendItem(Lang->Name, IDM_LANG_BASE + n, true); if (Item) { if (CurLang == i) { Item->Checked(true); } } } } } } bool AppWnd::ShowLang(LLanguageId Lang) { return ShowLanguages.Find(Lang) != 0; } void AppWnd::ShowLang(LLanguageId Lang, bool Show) { // Apply change if (Show) { OnLanguagesChange(Lang, true); ShowLanguages.Add(Lang, true); } else { ShowLanguages.Delete(Lang); } // Store the setting for next time LStringPipe p; // const char *L; // for (bool i = ShowLanguages.First(&L); i; i = ShowLanguages.Next(&L)) for (auto i : ShowLanguages) { if (p.GetSize()) p.Push(","); p.Push(i.key); } char *Langs = p.NewStr(); if (Langs) { LVariant v; GetOptions()->SetValue(OPT_ShowLanguages, v = Langs); DeleteArray(Langs); } // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } } LLanguage *AppWnd::GetCurLang() { if (CurLang >= 0 && CurLang < Languages.Length()) return Languages[CurLang]; return LFindLang("en"); } void AppWnd::SetCurLang(LLanguage *L) { for (int i=0; iId == L->Id) { // Set new current CurLang = i; // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } break; } } } LArray *AppWnd::GetLanguages() { return &Languages; } class Test : public LView { COLOUR c; public: Test(COLOUR col, int x1, int y1, int x2, int y2) { c = col; LRect r(x1, y1, x2, y2); SetPos(r); _BorderSize = 1; Sunken(true); } void OnPaint(LSurface *pDC) { pDC->Colour(c, 24); pDC->Rectangle(); } }; LMessage::Result AppWnd::OnEvent(LMessage *m) { - LMru::OnEvent(m); - switch (m->Msg()) { case M_CHANGE: { LAutoPtr note((LNotification*)m->B()); return OnNotify((LViewI*) m->A(), *note); } case M_DESCRIBE: { char *Text = (char*) m->A(); if (Text) { SetStatusText(Text, STATUS_NORMAL); } break; } } - return LWindow::OnEvent(m); + + return LDocApp::OnEvent(m); } void _CountGroup(ResStringGroup *Grp, int &Words, int &Multi) { for (auto s: *Grp->GetStrs()) { if (s->Items.Length() > 1) { Multi++; char *e = s->Get("en"); if (e) { LToken t(e, " "); Words += t.Length(); } } } } int AppWnd::OnCommand(int Cmd, int Event, OsView Handle) { SerialiseContext Ctx; switch (Cmd) { case IDM_SHOW_LANG: { auto Dlg = new ShowLanguagesDlg(this); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_NEW_CSS: { NewObject(Ctx, 0, TYPE_CSS); break; } case IDM_NEW_DIALOG: { NewObject(Ctx, 0, TYPE_DIALOG); break; } case IDM_NEW_STRING_GRP: { NewObject(Ctx, 0, TYPE_STRING); break; } case IDM_NEW_MENU: { NewObject(Ctx, 0, TYPE_MENU); break; } case IDM_CLOSE: { Empty(); break; } case IDM_IMPORT_WIN32: { LoadWin32(); break; } case IDM_IMPORT_LANG: { ImportLang(); break; } case IDM_COMPARE: { Compare(); break; } case IDM_PROPERTIES: { List l; if (Objs->ListObjects(l)) { int Dialogs = 0; int Strings = 0; int Menus = 0; int Words = 0; int MultiLingual = 0; for (auto r: l) { switch (r->Type()) { case TYPE_DIALOG: { Dialogs++; break; } case TYPE_STRING: { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { Strings += Grp->GetStrs()->Length(); _CountGroup(Grp, Words, MultiLingual); } break; } case TYPE_MENU: { Menus++; ResMenu *Menu = dynamic_cast(r); if (Menu) { if (Menu->Group) { Strings += Menu->Group->GetStrs()->Length(); _CountGroup(Menu->Group, Words, MultiLingual); } } break; } } } LgiMsg( this, "This file contains:\n" "\n" " Dialogs: %i\n" " Menus: %i\n" " Strings: %i\n" " Multi-lingual: %i\n" " Words: %i", AppName, MB_OK, Dialogs, Menus, Strings, MultiLingual, Words); } break; } case IDM_EXIT: { LCloseApp(); break; } case IDM_FIND: { auto s = new Search(this); s->DoModal([&](auto dlg, auto id) { if (id) new Results(this, s); delete dlg; }); break; } case IDM_NEXT: { LgiMsg(this, "Not implemented :(", AppName); break; } case IDM_CUT: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_CUT); } else { Resource *r = Objs->CurrentResource(); if (r) r->Copy(true); } break; } case IDM_COPY: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_COPY); } else { Resource *r = Objs->CurrentResource(); if (r) r->Copy(false); } break; } case IDM_PASTE: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_PASTE); } else { Resource *r = Objs->CurrentResource(); if (r) r->Paste(); } break; } case IDM_TABLELAYOUT_TEST: { OpenTableLayoutTest(this); break; } case IDM_HELP: { char ExeName[MAX_PATH_LEN]; sprintf_s(ExeName, sizeof(ExeName), "%s", LGetExePath().Get()); while (strchr(ExeName, DIR_CHAR) && strlen(ExeName) > 3) { char p[256]; LMakePath(p, sizeof(p), ExeName, "index.html"); if (!LFileExists(p)) { LMakePath(p, sizeof(p), ExeName, "help"); LMakePath(p, sizeof(p), p, "index.html"); } if (LFileExists(p)) { LExecute(HelpFile, NULL, ExeName); break; } LTrimDir(ExeName); } break; } case IDM_SHOW_SHORTCUTS: { if (!ShortCuts) { ShortCuts = new ShortCutView(this); } if (ShortCuts) { auto res = Objs->CurrentResource(); if (res) ShortCuts->OnResource(res); } break; } case IDM_ABOUT: { LAbout Dlg( this, AppName, APP_VER, "\nLgi Resource Editor (lr8 files).", "icon64.png", "http://www.memecode.com/lgi/res", "fret@memecode.com"); break; } default: { int Idx = Cmd - IDM_LANG_BASE; if (Idx >= 0 && Idx < Languages.Length()) { // Deselect the old lang auto Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(false); } // Set the current CurLang = Idx; // Set the new lang's menu item Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(true); } // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } } break; } } return LDocApp::OnCommand(Cmd, Event, Handle); } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { default: { break; } } return 0; } void AppWnd::FindStrings(List &Strs, char *Define, int *CtrlId) { if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { StringList *s = r->GetStrs(); if (s) { for (auto Str: *s) { if (Define && ValidStr(Str->GetDefine())) { if (strcmp(Define, Str->GetDefine()) == 0) { Strs.Insert(Str); continue; } } if (CtrlId) { if (*CtrlId == Str->GetId()) { Strs.Insert(Str); continue; } } } } } } } } int AppWnd::GetUniqueCtrlId() { int Max = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { LHashTbl, int> t; for (auto r: l) { StringList *sl = r->GetStrs(); if (sl) { for (auto s: *sl) { if (s->GetId() > 0 && !t.Find(s->GetId())) { t.Add(s->GetId(), s->GetId()); } Max = MAX(s->GetId(), Max); } } } int i = 500; while (true) { if (t.Find(i)) { i++; } else { return i; } } } } return Max + 1; } int AppWnd::GetUniqueStrRef(int Start) { if (!Objs) return -1; List l; if (!Objs->ListObjects(l)) return -1; LHashTbl, ResString*> Map; LArray Dupes; for (auto r: l) { ResStringGroup *Grp = r->GetStringGroup(); if (Grp) { List::I it = Grp->GetStrs()->begin(); for (ResString *s = *it; s; s = *++it) { if (s->GetRef()) { ResString *Existing = Map.Find(s->GetRef()); if (Existing) { // These get their ref's reset to a unique value as a side // effect of this function... Dupes.Add(s); } else { Map.Add(s->GetRef(), s); } } else { // auto Idx = Grp->GetStrs()->IndexOf(s); LAssert(!"No string ref?"); } } } } for (int i=Start; true; i++) { if (!Map.Find(i)) { if (Dupes.Length()) { ResString *s = Dupes[0]; Dupes.DeleteAt(0); s->SetRef(i); SetDirty(true, NULL); } else { return i; } } } return -1; } ResString *AppWnd::GetStrFromRef(int Ref) { ResString *Str = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { if ((Str = Grp->FindRef(Ref))) break; } } } } return Str; } ResStringGroup *AppWnd::GetDialogSymbols() { if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { auto ObjName = Grp->Wnd()->Name(); if (ObjName && stricmp(ObjName, StrDialogSymbols) == 0) { return Grp; } } } } } return NULL; } void AppWnd::OnReceiveFiles(LArray &Files) { auto f = Files.Length() ? Files[0] : 0; if (f) { _OpenFile(f, false, NULL); } } void AppWnd::SetStatusText(char *Text, int Pane) { if (Pane >= 0 && Pane < STATUS_MAX && StatusInfo[Pane]) { StatusInfo[Pane]->Name(Text); } } Resource *AppWnd::NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select) { Resource *r = 0; ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { r = new ResCss(this); Dir = Objs->Style; break; } case TYPE_DIALOG: { r = new ResDialog(this); Dir = Objs->Dialogs; break; } case TYPE_STRING: { r = new ResStringGroup(this); Dir = Objs->Strings; break; } case TYPE_MENU: { r = new ResMenu(this); Dir = Objs->Menus; break; } } if (r) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { Dir->Insert(Item); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } } r->Create(load, &ctx); if (Item) { Item->Update(); } SetDirty(true, NULL); } return r; } bool AppWnd::InsertObject(int Type, Resource *r, bool Select) { bool Status = false; if (r) { ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { Dir = Objs->Style; break; } case TYPE_DIALOG: { Dir = Objs->Dialogs; break; } case TYPE_STRING: { Dir = Objs->Strings; break; } case TYPE_MENU: { Dir = Objs->Menus; break; } } if (Dir) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { const char *Name = Item->GetText(); r->Item = Item; Dir->Insert(Item, (Name && Name[0] == '_') ? 0 : -1); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } Status = true; } } } return Status; } void AppWnd::DelObject(Resource *r) { OnResourceSelect(0); DeleteObj(r); } ObjTreeItem *GetTreeItem(LTreeItem *ti, Resource *r) { for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } ObjTreeItem *GetTreeItem(LTree *ti, Resource *r) { for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } void AppWnd::GotoObject(ResString *s, ResStringGroup *g, ResDialog *d, ResMenuItem *m, ResDialogCtrl *c) { if (s) { Resource *Res = 0; if (g) { Res = g; } else if (d) { Res = d; } else if (m) { Res = m->GetMenu(); } if (Res) { ObjTreeItem *ti = GetTreeItem(Objs, Res); if (ti) { ti->Select(true); if (g) { s->GetList()->Select(0); s->ScrollTo(); LYield(); s->Select(true); } else if (d) { LYield(); d->SelectCtrl(c); } else if (m) { for (LTreeItem *i=m; i; i=i->GetParent()) { i->Expanded(true); } m->Select(true); m->ScrollTo(); } } else { printf("%s:%i - couldn't find resources tree item\n", _FL); } } } } bool AppWnd::ListObjects(List &Lst) { if (Objs) { return Objs->ListObjects(Lst); } return false; } void AppWnd::OnObjChange(FieldSource *r) { if (Fields) { Fields->Serialize(false); SetDirty(true, NULL); } } void AppWnd::OnObjSelect(FieldSource *r) { if (Fields) Fields->OnSelect(r); } void AppWnd::OnObjDelete(FieldSource *r) { if (Fields) { Fields->OnDelete(r); } } void AppWnd::OnResourceDelete(Resource *r) { auto v = GetShortCutView(); if (v) v->OnResource(NULL); } void AppWnd::OnResourceSelect(Resource *r) { if (LastRes) { OnObjSelect(NULL); if (ContentView) { ContentView->Detach(); DeleteObj(ContentView); } LastRes = NULL; } if (r) { ContentView = r->CreateUI(); if (ContentView) { if (HBox) ContentView->Attach(HBox); LastRes = r; } auto v = GetShortCutView(); if (v) v->OnResource(r); } } char *TagName(LXmlTag *t) { static char Buf[1024]; LArray Tags; for (; t; t = t->Parent) { Tags.AddAt(0, t); } Buf[0] = 0; for (int i=0; iGetTag()); } return Buf; } class ResCompare : public LWindow, public LResourceLoad { LList *Lst; public: ResCompare(const char *File1, const char *File2) { Lst = 0; LRect p; LAutoString n; if (LoadFromResource(IDD_COMPARE, this, &p, &n)) { SetPos(p); Name(n); MoveToCenter(); GetViewById(IDC_DIFFS, Lst); if (Attach(0)) { Visible(true); AttachChildren(); LXmlTag *t1 = new LXmlTag; LXmlTag *t2 = new LXmlTag; if (t1 && File1) { LFile f; if (f.Open(File1, O_READ)) { LXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t1, &f, 0)) { DeleteObj(t1); } } else { DeleteObj(t1); } } if (t2 && File2) { LFile f; if (f.Open(File2, O_READ)) { LXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t2, &f, 0)) { DeleteObj(t2); } } else { DeleteObj(t2); } } if (Lst && t1 && t2) { Lst->Enabled(false); Compare(t1, t2); Lst->Enabled(true); } DeleteObj(t1); DeleteObj(t2); } } } void Compare(LXmlTag *t1, LXmlTag *t2) { char s[1024]; if (stricmp(t1->GetTag(), t2->GetTag()) != 0) { snprintf(s, sizeof(s), "Different Tag: '%s' <-> '%s'", t1->GetTag(), t2->GetTag()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } LHashTbl,LXmlAttr*> a; for (int i=0; iAttr.Length(); i++) { LXmlAttr *a1 = &t1->Attr[i]; a.Add(a1->GetName(), a1); } for (int n=0; nAttr.Length(); n++) { LXmlAttr *a2 = &t2->Attr[n]; LXmlAttr *a1 = (LXmlAttr*) a.Find(a2->GetName()); if (a1) { if (strcmp(a1->GetValue(), a2->GetValue()) != 0) { snprintf(s, sizeof(s), "Different Attr Value: '%s' <-> '%s'", a1->GetValue(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); snprintf(s, sizeof(s), "%s.%s", TagName(t1), a1->GetName()); i->SetText(s, 1); Lst->Insert(i); } } a.Delete(a2->GetName()); } else { snprintf(s, sizeof(s), "[Right] Missing Attr: '%s' = '%s'", a2->GetName(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } } // char *Key; // for (void *v = a.First(&Key); v; v = a.Next(&Key)) for (auto v : a) { LXmlAttr *a1 = v.value; snprintf(s, sizeof(s), "[Left] Missing Attr: '%s' = '%s'", a1->GetName(), a1->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } if (t1->IsTag("string-group")) { LArray r1, r2; for (auto t: t1->Children) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r1[r] = t; } } } for (auto t: t2->Children) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r2[r] = t; } } } auto Max = MAX(r1.Length(), r2.Length()); for (int i = 0; iGetAttr("ref"), r1[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r1[i]), 1); Lst->Insert(n); } } else if (r2[i]) { snprintf(s, sizeof(s), "[Left] Missing String: Ref=%s, Def=%s", r2[i]->GetAttr("ref"), r2[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r2[i]), 1); Lst->Insert(n); } } } } else { LXmlTag *c1 = t1->Children[0]; LXmlTag *c2 = t2->Children[0]; while (c1 && c2) { Compare(c1, c2); c1 = t1->Children[0]; c2 = t2->Children[0]; } } LYield(); } void OnPosChange() { LRect c = GetClient(); if (Lst) { c.Inset(7, 7); Lst->SetPos(c); } } }; void AppWnd::Compare() { auto s = new LFileSelect(this); s->Type("Lgi Resource", "*.lr8"); s->Open([&](auto dlg, auto status) { if (status) new ResCompare(GetCurFile(), dlg->Name()); delete dlg; }); } void AppWnd::ImportLang() { // open dialog auto Select = new LFileSelect(this); Select->Type("Lgi Resources", "*.lr8;*.xml"); Select->Open([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(dlg->Name(), O_READ)) { SerialiseContext Ctx; Ctx.Format = GetFormat(dlg->Name()); // convert file to Xml objects LXmlTag *Root = new LXmlTag; if (Root) { LXmlTree Tree(GXT_NO_ENTITIES); if (Tree.Read(Root, &F, 0)) { List Menus; List Groups; for (auto t: Root->Children) { if (t->IsTag("menu")) { ResMenu *Menu = new ResMenu(this); if (Menu && Menu->Read(t, Ctx)) { Menus.Insert(Menu); } else break; } else if (t->IsTag("string-group")) { ResStringGroup *g = new ResStringGroup(this); if (g && g->Read(t, Ctx)) { Groups.Insert(g); } else break; } } Ctx.PostLoad(this); bool HasData = false; for (auto g: Groups) { g->SetLanguages(); if (g->GetStrs()->Length() > 0 && g->GetLanguages() > 0) { HasData = true; } } if (HasData) { List Langs; for (auto g: Groups) { for (int i=0; iGetLanguages(); i++) { LLanguage *Lang = g->GetLanguage(i); if (Lang) { bool Has = false; for (auto l: Langs) { if (stricmp((char*)l, (char*)Lang) == 0) { Has = true; break; } } if (!Has) { Langs.Insert(Lang); } } } } auto Dlg = new LangDlg(this, Langs); Dlg->DoModal([&](auto dlg, auto id) { if (id == IDOK && Dlg->Lang) { LStringPipe Errors; int Matches = 0; int NotFound = 0; int Imported = 0; int Different = 0; for (auto g: Groups) { List::I Strings = g->GetStrs()->begin(); for (ResString *s=*Strings; s; s=*++Strings) { ResString *d = GetStrFromRef(s->GetRef()); if (d) { Matches++; char *Str = s->Get(Dlg->Lang->Id); char *Dst = d->Get(Dlg->Lang->Id); if ( ( Str && Dst && strcmp(Dst, Str) != 0 ) || ( (Str != 0) ^ (Dst != 0) ) ) { Different++; d->Set(Str, Dlg->Lang->Id); Imported++; } } else { NotFound++; char e[256]; snprintf(e, sizeof(e), "String ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } } List Lst; if (ListObjects(Lst)) { for (auto m: Menus) { // find matching menu in our list ResMenu *Match = 0; for (auto r: Lst) { ResMenu *n = dynamic_cast(r); if (n && stricmp(n->Name(), m->Name()) == 0) { Match = n; break; } } if (Match) { // match strings List *Src = m->GetStrs(); List *Dst = Match->GetStrs(); for (auto s: *Src) { bool FoundRef = false; for (auto d: *Dst) { if (s->GetRef() == d->GetRef()) { FoundRef = true; char *Str = s->Get(Dlg->Lang->Id); if (Str) { char *Dst = d->Get(Dlg->Lang->Id); if (!Dst || strcmp(Dst, Str)) { Different++; } d->Set(Str, Dlg->Lang->Id); Imported++; } break; } } if (!FoundRef) { NotFound++; char e[256]; snprintf(e, sizeof(e), "MenuString ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } Match->SetLanguages(); } } for (auto r: Lst) { ResStringGroup *StrRes = dynamic_cast(r); if (StrRes) { StrRes->SetLanguages(); } } } char *ErrorStr = Errors.NewStr(); LgiMsg( this, "Imported: %i\n" "Matched: %i\n" "Not matched: %i\n" "Different: %i\n" "Total: %i\n" "\n" "Import complete.\n" "\n%s", AppName, MB_OK, Imported, Matches, NotFound, Different, Matches + NotFound, ErrorStr?ErrorStr:(char*)""); } delete dlg; }); } else { LgiMsg(this, "No language information to import", AppName, MB_OK); } // Groups.DeleteObjects(); // Menus.DeleteObjects(); } else { LgiMsg(this, "Failed to parse XML from file.\nError: %s", AppName, MB_OK, Tree.GetErrorMsg()); } DeleteObj(Root); } } } delete dlg; }); } bool AppWnd::Empty() { // Delete any existing objects List l; if (ListObjects(l)) { for (auto It = l.begin(); It != l.end(); ) { auto r = *It; if (r->SystemObject()) l.Delete(It); else It++; } for (auto r: l) { DelObject(r); } } return true; } bool AppWnd::OpenFile(const char *FileName, bool Ro) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { return LoadLgi(FileName); } else if (stristr(FileName, ".rc")) { LoadWin32(FileName); return true; } return false; } void AppWnd::SaveFile(const char *FileName, std::function Callback) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { auto r = SaveLgi(FileName); if (Callback) Callback(FileName, r); return; } if (Callback) Callback(FileName, false); } void AppWnd::GetFileTypes(LFileSelect *Dlg, bool Write) { Dlg->Type("Lgi Resources", "*.lr8;*.xml"); if (!Write) { Dlg->Type("All Files", LGI_ALL_FILES); } } // Lgi load/save bool AppWnd::TestLgi(bool Quite) { bool Status = true; List l; if (ListObjects(l)) { ErrorCollection Errors; for (auto r: l) { Status &= r->Test(&Errors); } if (Errors.StrErr.Length() > 0) { LStringPipe Sample; for (int i=0; iGetRef(), s->GetDefine(), Errors.StrErr[i].Msg.Get()); } char *Sam = Sample.NewStr(); LgiMsg(this, "%i strings have errors.\n\n%s", AppName, MB_OK, Errors.StrErr.Length(), Sam); DeleteArray(Sam); } else if (!Quite) { LgiMsg(this, "Object are all ok.", AppName); } } return Status; } #define PROFILE_LOAD 0 #if PROFILE_LOAD #define PROF(s) prof.Add(s) #else #define PROF(s) #endif bool AppWnd::LoadLgi(const char *FileName) { #if PROFILE_LOAD LProfile prof("LoadLgi"); #endif Empty(); if (!FileName) return false; PROF("fOpen"); LFile f; if (!f.Open(FileName, O_READ)) return false; PROF("prog"); LAutoPtr Progress(new LProgressDlg(this)); Progress->SetDescription("Initializing..."); Progress->SetType("Tags"); LAutoPtr Root(new LXmlTag); if (!Root) return false; // convert file to Xml objects LXmlTree Xml(0); Progress->SetDescription("Lexing..."); PROF("xml.read"); if (!Xml.Read(Root, &f, 0)) { LgiMsg(this, "Xml read failed: %s", AppName, MB_OK, Xml.GetErrorMsg()); return false; } Progress->SetRange(Root->Children.Length()); PROF("xml to objs"); // convert Xml list into objects SerialiseContext Ctx; for (auto t: Root->Children) { Progress->Value(Root->Children.IndexOf(t)); int RType = 0; if (t->IsTag("dialog")) RType = TYPE_DIALOG; else if (t->IsTag("string-group")) RType = TYPE_STRING; else if (t->IsTag("menu")) RType = TYPE_MENU; else if (t->IsTag("style")) RType = TYPE_CSS; else LAssert(!"Unexpected tag"); if (RType > 0) NewObject(Ctx, t, RType, false); } PROF("postload"); Ctx.PostLoad(this); PROF("sort"); SortDialogs(); PROF("test"); TestLgi(); PROF("scan langs"); // Scan for languages and update the view lang menu Languages.Length(0); LHashTbl, LLanguage*> Langs; if (!ViewMenu) return false; PROF("remove menu items"); // Remove existing language menu items while (ViewMenu->RemoveItem(1)); ViewMenu->AppendSeparator(); PROF("enum objs"); // Enumerate all languages List res; if (ListObjects(res)) { for (auto r: res) { ResStringGroup *Sg = r->IsStringGroup(); if (Sg) { for (int i=0; iGetLanguages(); i++) { LLanguage *Lang = Sg->GetLanguage(i); if (Lang) { Langs.Add(Lang->Id, Lang); } } } } } PROF("update langs"); // Update languages array int n = 0; for (auto i : Langs) { Languages.Add(i.value); auto Item = ViewMenu->AppendItem(i.value->Name, IDM_LANG_BASE + n, true); if (Item && i.value->IsEnglish()) { Item->Checked(true); CurLang = n; } n++; } PROF("none menu"); if (Languages.Length() == 0) ViewMenu->AppendItem("(none)", -1, false); return true; } void SerialiseContext::PostLoad(AppWnd *App) { for (int i=0; iGetUniqueCtrlId(); s->SetId(Id); Log.Print("Repaired CtrlId of string ref %i to %i\n", s->GetRef(), Id); } LAutoString a(Log.NewStr()); if (ValidStr(a)) { LgiMsg(App, "%s", "Load Warnings", MB_OK, a.Get()); } } int DialogNameCompare(ResDialog *a, ResDialog *b, NativeInt Data) { const char *A = (a)?a->Name():0; const char *B = (b)?b->Name():0; if (A && B) return stricmp(A, B); return -1; } void AppWnd::SortDialogs() { List Lst; if (ListObjects(Lst)) { List Dlgs; for (auto r: Lst) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) { Dlgs.Insert(Dlg); Dlg->Item->Remove(); } } Dlgs.Sort(DialogNameCompare); for (auto d: Dlgs) { Objs->Dialogs->Insert(d->Item); } } } class ResTreeNode { public: char *Str; ResTreeNode *a, *b; ResTreeNode(char *s) { a = b = 0; Str = s; } ~ResTreeNode() { DeleteArray(Str); DeleteObj(a); DeleteObj(b); } void Enum(List &l) { if (a) { a->Enum(l); } if (Str) { l.Insert(Str); } if (b) { b->Enum(l); } } bool Add(char *s) { int Comp = (Str && s) ? stricmp(Str, s) : -1; if (Comp == 0) { return false; } if (Comp < 0) { if (a) { return a->Add(s); } else { a = new ResTreeNode(s); } } if (Comp > 0) { if (b) { return b->Add(s); } else { b = new ResTreeNode(s); } } return true; } }; class ResTree { ResTreeNode *Root; public: ResTree() { Root = 0; } ~ResTree() { DeleteObj(Root); } bool Add(char *s) { if (s) { if (!Root) { Root = new ResTreeNode(NewStr(s)); return true; } else { return Root->Add(NewStr(s)); } } return false; } void Enum(List &l) { if (Root) { Root->Enum(l); } } }; const char *HeaderStr = "// This file generated by LgiRes\r\n\r\n"; struct DefinePair { char *Name; int Value; }; int PairCmp(DefinePair *a, DefinePair *b) { return a->Value - b->Value; } bool AppWnd::WriteDefines(LStream &Defs) { bool Status = false; ResTree Tree; // Empty file Defs.Write(HeaderStr, strlen(HeaderStr)); // make a unique list of #define's List Lst; if (ListObjects(Lst)) { LHashTbl,int> Def; LHashTbl,char*> Ident; for (auto r: Lst) { List *StrList = r->GetStrs(); if (StrList) { Status = true; List::I sl = StrList->begin(); for (ResString *s = *sl; s; s = *++sl) { if (ValidStr(s->GetDefine())) { if (stricmp(s->GetDefine(), "IDOK") == 0) { s->SetId(IDOK); } else if (stricmp(s->GetDefine(), "IDCANCEL") == 0) { s->SetId(IDCANCEL); } else if (stricmp(s->GetDefine(), "IDC_STATIC") == 0) { s->SetId(-1); } else if (stricmp(s->GetDefine(), "-1") == 0) { s->SetDefine(0); } else { // Remove dupe ID's char IdStr[32]; snprintf(IdStr, sizeof(IdStr), "%i", s->GetId()); char *Define; if ((Define = Ident.Find(IdStr))) { if (strcmp(Define, s->GetDefine())) { List n; FindStrings(n, s->GetDefine()); int NewId = GetUniqueCtrlId(); for (auto Ns: n) { Ns->SetId(NewId); } } } else { Ident.Add(IdStr, s->GetDefine()); } // Make all define's the same int CtrlId; if ((CtrlId = Def.Find(s->GetDefine()))) { // Already there... s->SetId(CtrlId); } else { // Add... LAssert(s->GetId()); if (s->GetId()) Def.Add(s->GetDefine(), s->GetId()); } } } } } } // write the list out LArray Pairs; // char *s = 0; // for (int i = Def.First(&s); i; i = Def.Next(&s)) for (auto i : Def) { if (ValidStr(i.key) && stricmp(i.key, "IDOK") != 0 && stricmp(i.key, "IDCANCEL") != 0 && stricmp(i.key, "IDC_STATIC") != 0 && stricmp(i.key, "-1") != 0) { DefinePair &p = Pairs.New(); p.Name = i.key; p.Value = i.value; } } Pairs.Sort(PairCmp); for (int n=0; n=' ' && (uint8_t)(c) <= 127) if (IsPrintable(s[0]) && IsPrintable(s[1]) && IsPrintable(s[2]) && IsPrintable(s[3])) { #ifndef __BIG_ENDIAN__ int32 i = LgiSwap32(p.Value); memcpy(s, &i, 4); #endif Defs.Print("#define %s%s'%04.4s'\r\n", p.Name, Tab, s); } else Defs.Print("#define %s%s%i\r\n", p.Name, Tab, p.Value); } } return Status; } bool AppWnd::SaveLgi(const char *FileName) { bool Status = false; if (!TestLgi()) { if (LgiMsg(this, "Do you want to save the file with errors?", AppName, MB_YESNO) == IDNO) return false; } // Rename the existing file to 'xxxxxx.bak' if (LFileExists(FileName)) { char Bak[MAX_PATH_LEN]; strcpy_s(Bak, sizeof(Bak), FileName); char *e = LGetExtension(Bak); if (e) { strcpy(e, "bak"); if (LFileExists(Bak)) FileDev->Delete(Bak, false); FileDev->Move(FileName, Bak); } } // Save the file to xml if (FileName) { LFile f; LFile::Path DefsName = FileName; DefsName += "../resdefs.h"; LStringPipe Defs; if (f.Open(FileName, O_WRITE)) { SerialiseContext Ctx; f.SetSize(0); Defs.SetSize(0); Defs.Print("// Generated by LgiRes\r\n\r\n"); List l; if (ListObjects(l)) { // Remove all duplicate symbol Id's from the dialogs for (auto r: l) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) Dlg->CleanSymbols(); } // write defines WriteDefines(Defs); LXmlTag Root("resources"); // Write all string lists out first so that when we load objects // back in again the strings will already be loaded and can // be referenced for (auto r: l) { if (r->Type() == TYPE_STRING) { LXmlTag *c = new LXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { LAssert(0); DeleteObj(c); } } } // now write the rest of the objects out for (auto r: l) { if (r->Type() != TYPE_STRING) { LXmlTag *c = new LXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { r->Write(c, Ctx); LAssert(0); DeleteObj(c); } } } // Set the offset type. // // Older versions of LgiRes stored the dialog's controls at a fixed // offset (3,17) from where they should've been. That was fixed, but // to differentiate between the 2 systems, we store a tag at the // root element. Root.SetAttr("Offset", 1); LXmlTree Tree(GXT_NO_ENTITIES); Status = Tree.Write(&Root, &f); if (Status) { // Also write the header... but only if it's changed... auto DefsContent = Defs.NewLStr(); LAutoString OldDefsContent(LReadTextFile(DefsName)); if (Strcmp(DefsContent.Get(), OldDefsContent.Get())) { LFile DefsFile; if (!DefsFile.Open(DefsName, O_WRITE)) goto FileErrorMsg; DefsFile.SetSize(0); DefsFile.Write(DefsContent); } } } } else { FileErrorMsg: LgiMsg(this, "Couldn't open these files for output:\n" "\t%s\n" "\t%s\n" "\n" "Maybe they are read only or locked by another application.", AppName, MB_OK, FileName, DefsName.GetFull().Get()); } } return Status; } // Win32 load/save #define ADJUST_CTRLS_X 2 #define ADJUST_CTRLS_Y 12 #define IMP_MODE_SEARCH 0 #define IMP_MODE_DIALOG 1 #define IMP_MODE_DLG_CTRLS 2 #define IMP_MODE_STRINGS 3 #define IMP_MODE_MENU 4 class ImportDefine { public: char *Name; char *Value; ImportDefine() { Name = Value = 0; } ImportDefine(char *Line) { Name = Value = 0; if (Line && *Line == '#') { Line++; if (strnicmp(Line, "define", 6) == 0) { Line += 6; Line = LSkipDelim(Line); char *Start = Line; const char *WhiteSpace = " \r\n\t"; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } Name = NewStr(Start, Line-Start); Line = LSkipDelim(Line); Start = Line; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } if (Start != Line) { Value = NewStr(Start, Line-Start); } } } } ~ImportDefine() { DeleteArray(Name); DeleteArray(Value); } }; class DefineList : public List { int NestLevel; public: bool Defined; List IncludeDirs; DefineList() { Defined = true; NestLevel = 0; } ~DefineList() { for (auto i: *this) { DeleteObj(i); } for (auto c: IncludeDirs) { DeleteArray(c); } } void DefineSymbol(const char *Name, const char *Value = 0) { ImportDefine *Def = new ImportDefine; if (Def) { Def->Name = NewStr(Name); if (Value) Def->Value = NewStr(Value); Insert(Def); } } ImportDefine *GetDefine(char *Name) { if (Name) { for (auto i: *this) { if (i->Name && stricmp(i->Name, Name) == 0) { return i; } } } return NULL; } void ProcessLine(char *Line) { if (NestLevel > 16) { return; } if (Line && *Line == '#') { Line++; LToken T(Line); if (T.Length() > 0) { if (stricmp(T[0], "define") == 0) // #define { ImportDefine *Def = new ImportDefine(Line-1); if (Def) { if (Def->Name) { Insert(Def); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "include") == 0) // #include { NestLevel++; LFile F; if (T.Length() > 1) { for (auto IncPath: IncludeDirs) { char FullPath[256]; strcpy(FullPath, IncPath); if (FullPath[strlen(FullPath)-1] != DIR_CHAR) { strcat(FullPath, DIR_STR); } strcat(FullPath, T[1]); if (F.Open(FullPath, O_READ)) { char Line[1024]; while (!F.Eof()) { F.ReadStr(Line, sizeof(Line)); char *p = LSkipDelim(Line); if (*p == '#') { ProcessLine(p); } } break; } } } NestLevel--; } else if (stricmp(T[0], "if") == 0) // #if { } else if (stricmp(T[0], "ifdef") == 0) // #if { if (T.Length() > 1) { Defined = GetDefine(T[1]) != 0; } } else if (stricmp(T[0], "endif") == 0) // #endif { Defined = true; } else if (stricmp(T[0], "pragma") == 0) { ImportDefine *Def = new ImportDefine; if (Def) { char *Str = Line + 7; char *First = strchr(Str, '('); char *Second = (First) ? strchr(First+1, ')') : 0; if (First && Second) { Insert(Def); Def->Name = NewStr(Str, First-Str); First++; Def->Value = NewStr(First, Second-First); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "undef") == 0) { } } } // else it's not for us anyway } }; void TokLine(LArray &T, char *Line) { if (Line) { // Exclude comments for (int k=0; Line[k]; k++) { if (Line[k] == '/' && Line[k+1] == '/') { Line[k] = 0; break; } } // Break into tokens for (const char *s = Line; s && *s; ) { while (*s && strchr(" \t", *s)) s++; char *t = LTokStr(s); if (t) T.Add(t); else break; } } } void AppWnd::LoadWin32(const char *FileName) { bool Status = false; LHashTbl,bool> CtrlNames; CtrlNames.Add("LTEXT", true); CtrlNames.Add("EDITTEXT", true); CtrlNames.Add("COMBOBOX", true); CtrlNames.Add("SCROLLBAR", true); CtrlNames.Add("GROUPBOX", true); CtrlNames.Add("PUSHBUTTON", true); CtrlNames.Add("DEFPUSHBUTTON", true); CtrlNames.Add("CONTROL", true); CtrlNames.Add("ICON", true); CtrlNames.Add("LISTBOX", true); Empty(); auto Load = [&](const char *FileName) { if (!FileName) return; LProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("K"); Progress.SetScale(1.0/1024.0); char *FileTxt = LReadTextFile(FileName); if (FileTxt) { LToken Lines(FileTxt, "\r\n"); DeleteArray(FileTxt); DefineList Defines; ResStringGroup *String = new ResStringGroup(this); int Mode = IMP_MODE_SEARCH; // Language char *Language = 0; LLanguageId LanguageId = 0; // Dialogs List DlLList; CtrlDlg *Dlg = 0; // Menus ResDialog *Dialog = 0; ResMenu *Menu = 0; List Menus; ResMenuItem *MenuItem[32]; int MenuLevel = 0; bool MenuNewLang = false; int MenuNextItem = 0; // Include defines char IncPath[256]; strcpy(IncPath, FileName); LTrimDir(IncPath); Defines.IncludeDirs.Insert(NewStr(IncPath)); Defines.DefineSymbol("_WIN32"); Defines.DefineSymbol("IDC_STATIC", "-1"); DoEvery Ticker(200); Progress.SetDescription("Reading resources..."); Progress.SetRange(Lines.Length()); if (String) { InsertObject(TYPE_STRING, String, false); } for (int CurLine = 0; CurLine < Lines.Length(); CurLine++) { if (Ticker.DoNow()) { Progress.Value(CurLine); LYield(); } // Skip white space char *Line = Lines[CurLine]; char *p = LSkipDelim(Line); Defines.ProcessLine(Line); // Tokenize LArray T; TokLine(T, Line); // Process line if (Defines.Defined) { switch (Mode) { case IMP_MODE_SEARCH: { DeleteObj(Dialog); Dlg = 0; if (*p != '#') { if (T.Length() > 1 && (stricmp(T[1], "DIALOG") == 0 || stricmp(T[1], "DIALOGEX") == 0)) { Mode = IMP_MODE_DIALOG; Dialog = new ResDialog(this); if (Dialog) { Dialog->Create(NULL, NULL); Dialog->Name(T[0]); auto It = Dialog->IterateViews(); Dlg = dynamic_cast(It[0]); if (Dlg) { int Pos[4] = {0, 0, 0, 0}; int i = 0; for (; iResDialogCtrl::SetPos(r); Dlg->GetStr()->SetDefine(T[0]); ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Dlg->GetStr()->SetId(atoi(Def->Value)); } } } break; } if (T.Length() > 1 && stricmp(T[1], "MENU") == 0) { ZeroObj(MenuItem); Mode = IMP_MODE_MENU; Menu = 0; // Check for preexisting menu in another language MenuNewLang = false; for (auto m: Menus) { if (stricmp(m->Name(), T[0]) == 0) { MenuNewLang = true; Menu = m; break; } } // If it doesn't preexist then create it if (!Menu) { Menu = new ResMenu(this); if (Menu) { Menus.Insert(Menu); Menu->Create(NULL, NULL); Menu->Name(T[0]); } } break; } if (T.Length() > 0 && stricmp(T[0], "STRINGTABLE") == 0) { Mode = IMP_MODE_STRINGS; if (String) { String->Name("_Win32 Imports_"); } break; } if (T.Length() > 2 && stricmp(T[0], "LANGUAGE") == 0) { LanguageId = 0; DeleteArray(Language); char *Language = NewStr(T[1]); if (Language) { LLanguage *Info = LFindLang(0, Language); if (Info) { LanguageId = Info->Id; ResDialog::AddLanguage(Info->Id); } } break; } } break; } case IMP_MODE_DIALOG: { if (T.Length() > 0 && Dlg) { if (stricmp(T[0], "CAPTION") == 0) { char *Caption = T[1]; if (Caption) { Dlg->GetStr()->Set(Caption, LanguageId); } } else if (stricmp(T[0], "BEGIN") == 0) { Mode = IMP_MODE_DLG_CTRLS; } } break; } case IMP_MODE_DLG_CTRLS: { char *Type = T[0]; if (!Type) break; if (stricmp(Type, "end") != 0) { // Add wrapped content to the token array. char *Next = Lines[CurLine+1]; if (Next) { Next = LSkipDelim(Next); char *NextTok = LTokStr((const char*&)Next); if (NextTok) { if (stricmp(NextTok, "END") != 0 && !CtrlNames.Find(NextTok)) { TokLine(T, Lines[++CurLine]); } DeleteArray(NextTok); } } } // Process controls if (stricmp(Type, "LTEXT") == 0) { if (T.Length() >= 7) { CtrlText *Ctrl = new CtrlText(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "EDITTEXT") == 0) { if (T.Length() >= 7) { CtrlEditbox *Ctrl = new CtrlEditbox(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "COMBOBOX") == 0) { if (T.Length() >= 6) { CtrlComboBox *Ctrl = new CtrlComboBox(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "SCROLLBAR") == 0) { if (T.Length() == 6) { CtrlScrollBar *Ctrl = new CtrlScrollBar(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "GROUPBOX") == 0) { if (T.Length() >= 7) { CtrlGroup *Ctrl = new CtrlGroup(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "PUSHBUTTON") == 0 || stricmp(Type, "DEFPUSHBUTTON") == 0) { if (T.Length() >= 7) { CtrlButton *Ctrl = new CtrlButton(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "CONTROL") == 0) { if (T.Length() >= 7) { char *Caption = T[1]; char *Id = T[2]; char *Type = T[3]; bool Checkbox = false; bool Radio = false; bool Done = false; // loop through styles int i; for (i=4; !Done && iSetPos(r); if (Caption) Ctrl->GetStr()->Set(Caption, LanguageId); if (Id) Ctrl->GetStr()->SetDefine(Id); ImportDefine *Def = Defines.GetDefine(Id); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } Dlg->AddView(Ctrl->View()); } } } } else if (stricmp(Type, "ICON") == 0) { if (T.Length() >= 7) { CtrlBitmap *Ctrl = new CtrlBitmap(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl->View()); } } } else if (stricmp(Type, "LISTBOX") == 0) { if (T.Length() >= 7) { CtrlList *Ctrl = new CtrlList(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "END") == 0) { // search for an existing dialog resource in // another language ResDialog *Match = 0; CtrlDlg *MatchObj = 0; for (auto d: DlLList) { auto It = d->IterateViews(); LViewI *Wnd = It[0]; if (Wnd) { CtrlDlg *Obj = dynamic_cast(Wnd); if (Obj) { if (Obj->GetStr()->GetId() == Dlg->GetStr()->GetId()) { MatchObj = Obj; Match = d; break; } } } } if (Match) { // Merge the controls from "Dlg" to "MatchObj" List Old; List New; Dlg->ListChildren(New); MatchObj->ListChildren(Old); // add the language strings for the caption // without clobbering the languages already // present for (auto s: Dlg->GetStr()->Items) { if (!MatchObj->GetStr()->Get(s->GetLang())) { MatchObj->GetStr()->Set(s->GetStr(), s->GetLang()); } } for (auto c: New) { ResDialogCtrl *MatchCtrl = 0; // try matching by Id { for (auto Mc: Old) { if (Mc->GetStr()->GetId() == c->GetStr()->GetId() && Mc->GetStr()->GetId() > 0) { MatchCtrl = Mc; break; } } } // ok no Id match, match by location and type if (!MatchCtrl) { List Overlapping; for (auto Mc: Old) { LRect a = Mc->View()->GetPos(); LRect b = c->View()->GetPos(); LRect c = a; c.Bound(&b); if (c.Valid()) { int Sa = a.X() * a.Y(); int Sb = b.X() * b.Y(); int Sc = c.X() * c.Y(); int Total = Sa + Sb - Sc; double Amount = (double) Sc / (double) Total; if (Amount > 0.5) { // mostly similar in size Overlapping.Insert(Mc); } } } if (Overlapping.Length() == 1) { MatchCtrl = Overlapping[0]; } } if (MatchCtrl) { // woohoo we are cool for (auto s: c->GetStr()->Items) { MatchCtrl->GetStr()->Set(s->GetStr(), s->GetLang()); } } } // Delete the duplicate OnObjSelect(0); DeleteObj(Dialog); } else { // Insert the dialog InsertObject(TYPE_DIALOG, Dialog, false); DlLList.Insert(Dialog); } Dialog = 0; Dlg = 0; Status = true; Mode = IMP_MODE_SEARCH; } break; } case IMP_MODE_STRINGS: { if (stricmp(T[0], "BEGIN") == 0) { } else if (stricmp(T[0], "END") == 0) { Status = true; Mode = IMP_MODE_SEARCH; } else { if (T.Length() > 1) { ResString *Str = String->FindName(T[0]); if (!Str) { Str = String->CreateStr(); if (Str) { ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Str->SetId(atoi(Def->Value)); } Str->SetDefine(T[0]); Str->UnDuplicate(); } } if (Str) { // get the language LLanguage *Lang = LFindLang(Language); LLanguageId SLang = (Lang) ? Lang->Id : (char*)"en"; StrLang *s = 0; // look for language present in string object for (auto ss: Str->Items) { if (*ss == SLang) { s = ss; break; } } // if not present then add it if (!s) { s = new StrLang; if (s) { Str->Items.Insert(s); s->SetLang(SLang); } } // set the value if (s) { s->SetStr(T[1]); } } } } break; } case IMP_MODE_MENU: { if (T.Length() >= 1) { if (stricmp(T[0], "BEGIN") == 0) { MenuLevel++; } else if (stricmp(T[0], "END") == 0) { MenuLevel--; if (MenuLevel == 0) { Status = true; Mode = IMP_MODE_SEARCH; Menu->SetLanguages(); if (!MenuNewLang) { InsertObject(TYPE_MENU, Menu, false); } Menu = 0; } } else { ResMenuItem *i = 0; char *Text = T[1]; if (Text) { if (stricmp(T[0], "POPUP") == 0) { if (MenuNewLang) { LTreeItem *Ri = 0; if (MenuItem[MenuLevel]) { Ri = MenuItem[MenuLevel]->GetNext(); } else { if (MenuLevel == 1) { Ri = Menu->ItemAt(0); } else if (MenuItem[MenuLevel-1]) { Ri = MenuItem[MenuLevel-1]->GetChild(); } } if (Ri) { // Seek up to the next submenu while (!Ri->GetChild()) { Ri = Ri->GetNext(); } i = dynamic_cast(Ri); // char *si = i->Str.Get("en"); if (i) { MenuItem[MenuLevel] = i; } } } else { MenuItem[MenuLevel] = i = new ResMenuItem(Menu); } if (i) { LLanguage *Lang = LFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } MenuNextItem = 0; } else if (stricmp(T[0], "MENUITEM") == 0) { if (MenuNewLang) { if (MenuItem[MenuLevel-1] && T.Length() > 2) { ImportDefine *id = Defines.GetDefine(T[2]); if (id) { int Id = atoi(id->Value); int n = 0; for (LTreeItem *o = MenuItem[MenuLevel-1]->GetChild(); o; o = o->GetNext(), n++) { ResMenuItem *Res = dynamic_cast(o); if (Res && Res->GetStr()->GetId() == Id) { i = Res; break; } } } } MenuNextItem++; } else { i = new ResMenuItem(Menu); } if (i) { if (stricmp(Text, "SEPARATOR") == 0) { // Set separator i->Separator(true); } else { if (!MenuNewLang) { // Set Id i->GetStr()->SetDefine(T[2]); if (i->GetStr()->GetDefine()) { ImportDefine *id = Defines.GetDefine(i->GetStr()->GetDefine()); if (id) { i->GetStr()->SetId(atoi(id->Value)); i->GetStr()->UnDuplicate(); } } } // Set Text LLanguage *Lang = LFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } } } } if (i && !MenuNewLang) { if (MenuLevel == 1) { Menu->Insert(i); } else if (MenuItem[MenuLevel-1]) { MenuItem[MenuLevel-1]->Insert(i); } } } } break; } } } T.DeleteArrays(); } DeleteObj(Dialog); if (String->Length() > 0) { String->SetLanguages(); } } Invalidate(); }; if (FileName) Load(FileName); else { auto Select = new LFileSelect(this); Select->Type("Win32 Resource Script", "*.rc"); Select->Open([&](auto dlg, auto status) { if (status) Load(dlg->Name()); delete dlg; }); } } bool AppWnd::SaveWin32() { return false; } ///////////////////////////////////////////////////////////////////////// ResFrame::ResFrame(Resource *child) { Child = child; Name("ResFrame"); } ResFrame::~ResFrame() { if (Child) { Child->App()->OnObjSelect(NULL); Child->Wnd()->Detach(); } } void ResFrame::OnFocus(bool b) { Child->Wnd()->Invalidate(); } bool ResFrame::Attach(LViewI *p) { bool Status = LLayout::Attach(p); if (Status && Child) { Child->Attach(this); Child->Wnd()->Visible(true); } return Status; } bool ResFrame::Pour(LRegion &r) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } return false; } bool ResFrame::OnKey(LKey &k) { bool Status = false; if (k.Down() && Child) { switch (k.c16) { case LK_DELETE: { if (k.Shift()) { Child->Copy(true); } else { Child->Delete(); } Status = true; break; } case 'x': case 'X': { if (k.Ctrl()) { Child->Copy(true); Status = true; } break; } case 'c': case 'C': { if (k.Ctrl()) { Child->Copy(); Status = true; } break; } case LK_INSERT: { if (k.Ctrl()) { Child->Copy(); } else if (k.Shift()) { Child->Paste(); } Status = true; break; } case 'v': case 'V': { if (k.Ctrl()) { Child->Paste(); Status = true; } break; } } } return Child->Wnd()->OnKey(k) || Status; } void ResFrame::OnPaint(LSurface *pDC) { // Draw nice frame LRect r(0, 0, X()-1, Y()-1); LThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); LFlatBorder(pDC, r, 4); LWideBorder(pDC, r, DefaultSunkenEdge); // Set the child to the client area Child->Wnd()->SetPos(r); // Draw the dialog & controls LView::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// LgiFunc char *_LgiGenLangLookup(); #include "lgi/common/AutoPtr.h" #include "lgi/common/Variant.h" #include "lgi/common/Css.h" #include "lgi/common/TableLayout.h" class Foo : public LLayoutCell { public: Foo() { TextAlign(AlignLeft); } bool Add(LView *v) { return false; } bool Remove(LView *v) { return false; } }; ////////////////////////////////////////////////////////////////////// ShortCutView::ShortCutView(AppWnd *app) { App = app; LRect r(0, 0, 500, 600); SetPos(r); MoveSameScreen(App); Name("Dialog Shortcuts"); if (Attach(0)) { Lst = new LList(100, 0, 0, 100, 100); Lst->Attach(this); Lst->SetPourLargest(true); Lst->AddColumn("Key", 50); Lst->AddColumn("Ref", 80); Lst->AddColumn("CtrlId", 80); Lst->AddColumn("Name", 150); Visible(true); } } ShortCutView::~ShortCutView() { App->OnCloseView(this); } enum ShortCutCol { ColKey, ColRefId, ColCtrlId, ColName }; void FindMenuKeys(LList *out, ResMenu *menu) { if (!out || !menu) return; LArray items; menu->EnumItems(items); for (auto i: items) { auto sc = i->Shortcut(); if (sc) { auto item = new LListItem(sc); LString ref, ctrl; ref.Printf("%i", i->GetStr()->GetRef()); ctrl.Printf("%i", i->GetStr()->GetId()); item->SetText(ref, ColRefId); item->SetText(ctrl, ColCtrlId); item->SetText(i->GetStr()->Get(), ColName); item->_UserPtr = i; out->Insert(item); } } } void FindShortCuts(LList *Out, LViewI *In) { for (auto c: In->IterateViews()) { auto rdc = dynamic_cast(c); if (!rdc || !rdc->GetStr()) continue; auto n = rdc->GetStr()->Get(); if (n) { char *a = strchr(n, '&'); if (a && a[1] != '&') { LListItem *li = new LListItem; LString s(++a, 1); LString ref, ctrl; ref.Printf("%i", rdc->GetStr()->GetRef()); ctrl.Printf("%i", rdc->GetStr()->GetId()); li->SetText(s.Upper(), ColKey); li->SetText(ref, ColRefId); li->SetText(ctrl, ColCtrlId); li->SetText(rdc->GetClass(), ColName); li->_UserPtr = rdc; Out->Insert(li); } } FindShortCuts(Out, c); } } int ShortCutView::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == Lst->GetId()) { switch (n.Type) { case LNotifyItemClick: { auto li = Lst->GetSelected(); if (!li) break; LString s = li->GetText(1); ResObject *c = (ResObject*) li->_UserPtr; if (!c) break; if (auto ctrl = dynamic_cast(c)) App->GotoObject(ctrl->GetStr(), NULL, ctrl->GetDlg(), NULL, ctrl); else if (auto mi = dynamic_cast(c)) App->GotoObject(mi->GetStr(), NULL, NULL, mi, NULL); else LAssert(!"Impl me."); break; } default: break; } } return LWindow::OnNotify(Ctrl, n); } void ShortCutView::OnResource(Resource *r) { Lst->Empty(); if (!r) return; if (auto dlg = dynamic_cast(r)) FindShortCuts(Lst, dlg); else if (auto menu = dynamic_cast(r)) FindMenuKeys(Lst, menu); Lst->Sort(0); Lst->ResizeColumnsToContent(); } ShortCutView *AppWnd::GetShortCutView() { return ShortCuts; } void AppWnd::OnCloseView(ShortCutView *v) { if (v == ShortCuts) ShortCuts = NULL; } ////////////////////////////////////////////////////////////////////// void TestFunc() { /* Foo c; uint32 *p = (uint32*)&c; p++; p = (uint32*)(*p); void *method = (void*)p[2]; for (int i=0; i<16; i++) { printf("[%i]=%p\n", i, p[i]); } c.OnChange(LCss::PropBackground); */ } int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, "LgiRes"); if (a.IsOk()) { if ((a.AppWnd = new AppWnd)) { TestFunc(); a.AppWnd->Visible(true); a.Run(); } } return 0; } diff --git a/ResourceEditor/src/LgiResEdit.h b/ResourceEditor/src/LgiResEdit.h --- a/ResourceEditor/src/LgiResEdit.h +++ b/ResourceEditor/src/LgiResEdit.h @@ -1,899 +1,899 @@ /*hdr ** FILE: LgiRes.h ** AUTHOR: Matthew Allen ** DATE: 22/10/00 ** DESCRIPTION: Resource Editor App Header ** */ #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/DocApp.h" #include "lgi/common/Properties.h" #include "lgi/common/Variant.h" #include "lgi/common/DataDlg.h" #include "lgi/common/OptionsFile.h" #include "resource.h" #include "lgi/common/Tree.h" #include "lgi/common/Box.h" #include "lgi/common/EventTargetThread.h" //////////////////////////////////////////////////////////////////////////////////////////// // Defines // version #define APP_VER "4.1" // window messages #define IDM_UNDO 201 #define IDM_REDO 202 /* #define IDM_CUT 203 #define IDM_COPY 204 #define IDM_PASTE 205 */ #define IDM_DELETE 300 #define IDM_RENAME 301 #define IDM_SETTINGS 302 #define IDM_IMPORT 303 #define IDM_IMPORT_WIN32 304 #define IDM_EXPORT 305 #define IDM_EXPORT_WIN32 306 #define IDM_NEW_LANG 307 #define IDM_DELETE_LANG 308 #define IDM_IMPORT_LANG 309 #define IDM_PROPERTIES 310 #define IDM_NEW_SUB 311 #define IDM_NEW_ITEM 312 #define IDM_DELETE_ITEM 313 #define IDM_MOVE_LEFT 314 #define IDM_MOVE_RIGHT 315 #define IDM_SET_LANG 316 #define IDM_TAB_ORDER 317 #define IDM_DUMP 318 #define IDM_COPY_TEXT 319 #define IDM_PASTE_TEXT 320 #define IDM_NEW_ID 321 #define IDM_COMPARE 322 #define IDM_MOVE_UP 324 #define IDM_MOVE_DOWN 325 #define IDM_REF_EQ_ID 326 #define IDM_ID_EQ_REF 327 #define IDM_UP 328 #define IDM_DOWN 329 #define IDM_SET_OK 330 #define IDM_SET_CANCEL 331 #define IDM_LANG_BASE 2000 #define IDC_TABLE 999 #define IDC_CHAT_MSG 1000 #define STATUS_NORMAL 0 #define STATUS_INFO 1 #define STATUS_MAX 2 #define VAL_Ref "Ref" #define VAL_Id "Id" #define VAL_Define "Define" #define VAL_Tag "Tag" #define VAL_Text "Text" #define VAL_CellClass "CellClass" #define VAL_CellStyle "CellStyle" #define VAL_Text "Text" #define VAL_Pos "Pos" #define VAL_x1 "x1" #define VAL_y1 "y1" #define VAL_x2 "x2" #define VAL_y2 "y2" #define VAL_Visible "Visible" #define VAL_Enabled "Enabled" #define VAL_Class "Class" #define VAL_Style "Style" #define VAL_VerticalAlign "valign" #define VAL_HorizontalAlign "align" #define VAL_Children "children" #define VAL_Span "span" #define VAL_Image "image" #define VAL_Toggle "toggle" // Misc class AppWnd; #define MainWnd ((AppWnd*)LApp::ObjInstance()->AppWnd) // App enum ObjectTypes { TYPE_CSS = 1, TYPE_DIALOG, TYPE_STRING, TYPE_MENU }; enum IconTypes { ICON_FOLDER, ICON_IMAGE, ICON_ICON, ICON_CURSOR, ICON_DIALOG, ICON_STRING, ICON_MENU, ICON_DISABLED, ICON_CSS }; #define OPT_ShowLanguages "ShowLang" #define StrDialogSymbols "_Dialog Symbols_" extern char TranslationStrMagic[]; extern char AppName[]; //////////////////////////////////////////////////////////////////////////////////////////// // Functions extern char *EncodeXml(const char *s, int l = -1); extern char *DecodeXml(const char *s, int l = -1); //////////////////////////////////////////////////////////////////////////////////////////// // Classes class AppWnd; class ObjTreeItem; class ResString; typedef List StringList; class ResMenu; class ResDialog; class ResStringGroup; class ResMenuItem; struct ErrorInfo { ResString *Str; LAutoString Msg; }; class ErrorCollection { public: LArray StrErr; }; struct SerialiseContext { ResFileFormat Format; LStringPipe Log; LArray FixId; SerialiseContext() : Log(512) { Format = Lr8File; } void PostLoad(AppWnd *App); }; class Resource { friend class AppWnd; friend class ObjTreeItem; protected: int ResType; AppWnd *AppWindow; ObjTreeItem *Item; bool SysObject; public: Resource(AppWnd *w, int t = 0, bool enabled = true); virtual ~Resource(); AppWnd *App() { return AppWindow; } bool SystemObject() { return SysObject; } void SystemObject(bool i) { SysObject = i; } bool IsSelected(); virtual LView *Wnd() { return NULL; } virtual bool Attach(LViewI *Parent); virtual int Type() { return ResType; } virtual void Type(int i) { ResType = i; } virtual void Create(LXmlTag *load, SerialiseContext *ctx) = 0; // called when users creates virtual ResStringGroup *GetStringGroup() { return 0; } // Sub classes virtual ResStringGroup *IsStringGroup() { return 0; } virtual ResDialog *IsDialog() { return 0; } virtual ResMenu *IsMenu() { return 0; } // Serialization virtual bool Test(ErrorCollection *e) = 0; virtual bool Read(LXmlTag *t, SerialiseContext &Ctx) = 0; virtual bool Write(LXmlTag *t, SerialiseContext &Ctx) = 0; virtual StringList *GetStrs() { return NULL; } // Clipboard virtual void Delete() {} virtual void Copy(bool Delete = false) {} virtual void Paste() {} // UI virtual LView *CreateUI() { return 0; } virtual void OnRightClick(LSubMenu *RClick) {} virtual void OnCommand(int Cmd) {} virtual void OnShowLanguages() {} }; class ResFolder : public Resource, public LView { public: ResFolder(AppWnd *w, int t, bool enabled = true); LView *Wnd() { return dynamic_cast(this); } void Create(LXmlTag *load, SerialiseContext *ctx) { LAssert(0); } bool Test(ErrorCollection *e) { return false; } bool Read(LXmlTag *t, SerialiseContext &Ctx) { return false; } bool Write(LXmlTag *t, SerialiseContext &Ctx) { return false; } }; class ResFrame : public LLayout { Resource *Child; public: ResFrame(Resource *child); ~ResFrame(); bool Pour(LRegion &r); bool OnKey(LKey &k); void OnPaint(LSurface *pDC); bool Attach(LViewI *p); void OnFocus(bool b); }; class ObjTreeItem : public LTreeItem { friend class Resource; Resource *Obj; public: ObjTreeItem(Resource *Object); ~ObjTreeItem(); Resource *GetObj() { return Obj; } const char *GetText(int i=0); void OnSelect(); void OnMouseClick(LMouse &m); }; class ObjContainer : public LTree { friend class AppWnd; - ObjTreeItem *Style; - ObjTreeItem *Dialogs; - ObjTreeItem *Strings; - ObjTreeItem *Menus; + ObjTreeItem *Style = NULL; + ObjTreeItem *Dialogs = NULL; + ObjTreeItem *Strings = NULL; + ObjTreeItem *Menus = NULL; - LImageList *Images; - AppWnd *Window; + LImageList *Images = NULL; + AppWnd *Window = NULL; bool AppendChildren(ObjTreeItem *Item, List &Lst); public: ObjContainer(AppWnd *w); ~ObjContainer(); Resource *CurrentResource(); bool ListObjects(List &Lst); }; class FieldTree { public: enum FieldMode { None, UiToObj, ObjToUi, ObjToStore, StoreToObj, }; struct Field { // Global FieldTree *Tree; LAutoString Label; LAutoString Name; int Type; int Id; bool Multiline; void *Token; Field(FieldTree *tree) { Tree = tree; Type = 0; Id = 0; Token = 0; Multiline = false; } }; typedef LArray FieldArr; protected: int &NextId; FieldMode Mode; LViewI *View; LDom *Store; bool Deep; LHashTbl, FieldArr*> f; FieldArr *Get(void *Token, bool Create = false) { FieldArr *a = f.Find(Token); if (!a) { if (Create) f.Add(Token, a = new FieldArr); else LAssert(0); } return a; } Field *GetField(void *Token, const char *FieldName) { if (!Token || !FieldName) return 0; FieldArr *a = Get(Token); if (!a) return 0; for (int i=0; iLength(); i++) { if (!stricmp((*a)[i]->Name, FieldName)) return (*a)[i]; } return 0; } public: FieldTree(int &next, bool deep) : NextId(next) { Mode = None; View = 0; Store = 0; Deep = deep; } ~FieldTree() { Empty(); } FieldMode GetMode() { return Mode; } bool GetDeep() { return Deep; } void SetMode(FieldMode m) { Mode = m; } void SetView(LViewI *v) { View = v; } void SetStore(LDom *p) { Store = p; } void Empty() { // for (FieldArr *a = f.First(); a; a = f.Next()) for (auto a : f) { a.value->DeleteObjects(); } f.DeleteObjects(); View = 0; } void Insert(void *Token, int Type, int Reserved, const char *Name, const char *Label, int Idx = -1, bool Multiline = false) { FieldArr *a = Get(Token, true); if (!a) return; Field *n = new Field(this); if (n) { n->Token = Token; n->Label.Reset(NewStr(Label)); n->Name.Reset(NewStr(Name)); n->Id = NextId++; n->Type = Type; n->Multiline = Multiline; a->Add(n); } } void Serialize(void *Token, const char *FieldName, int &i) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlValue(f->Id, i); break; case UiToObj: i = (int)View->GetCtrlValue(f->Id); break; case StoreToObj: if (Store->GetValue(FieldName, v)) i = v.CastInt32(); break; case ObjToStore: Store->SetValue(FieldName, v = i); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, bool &b, int Default = -1) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant i; switch (Mode) { case ObjToUi: View->SetCtrlValue(f->Id, b); break; case UiToObj: b = View->GetCtrlValue(f->Id); break; case StoreToObj: { if (Store->GetValue(FieldName, i)) { b = i.CastInt32() != 0; } break; } case ObjToStore: if (Default < 0 || (bool)Default != b) Store->SetValue(FieldName, i = b); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, char *&s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: { DeleteArray(s); auto t = View->GetCtrlName(f->Id); if (ValidStr(t)) s = NewStr(t); break; } case StoreToObj: { DeleteArray(s); if (Store->GetValue(FieldName, v)) s = v.ReleaseStr(); break; } case ObjToStore: Store->SetValue(FieldName, v = s); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LAutoString &s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: { auto t = View->GetCtrlName(f->Id); s.Reset(ValidStr(t) ? NewStr(t) : 0); break; } case StoreToObj: { s.Reset(Store->GetValue(FieldName, v) ? v.ReleaseStr() : 0); break; } case ObjToStore: Store->SetValue(FieldName, v = s); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LString &s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: s = View->GetCtrlName(f->Id); break; case StoreToObj: if (Store->GetValue(FieldName, v)) s = v.Str(); else s.Empty(); break; case ObjToStore: Store->SetValue(FieldName, v = s.Get()); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LRect &r) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, r.GetStr()); break; case UiToObj: { r.SetStr(View->GetCtrlName(f->Id)); break; } case StoreToObj: { if (Store->GetValue(FieldName, v)) { r.SetStr(v.Str()); } break; } case ObjToStore: { Store->SetValue(FieldName, v = r.GetStr()); break; } default: LAssert(0); } } static int FieldArrCmp(FieldArr **a, FieldArr **b) { return (**a)[0]->Id - (**b)[0]->Id; } void GetAll(LArray &Fields) { // for (FieldArr *a = f.First(0); a; a = f.Next(0)) for (auto a : f) { Fields.Add(a.value); } Fields.Sort(FieldArrCmp); } }; #define M_OBJECT_CHANGED (M_USER + 4567) class FieldSource { friend class FieldView; int _FieldView; public: FieldSource() { _FieldView = -1; } virtual ~FieldSource() {} virtual bool GetFields(FieldTree &Fields) = 0; virtual bool Serialize(FieldTree &Fields) = 0; void OnFieldChange() { if (_FieldView >= 0) PostThreadEvent(_FieldView, M_OBJECT_CHANGED, (LMessage::Param)this); } }; #include "LgiRes_Dialog.h" class FieldView : public LLayout { protected: FieldSource *Source; FieldTree Fields; int NextId; bool Ignore; AppWnd *App; public: FieldView(AppWnd *app); ~FieldView(); void Serialize(bool Write); void OnPosChange(); void OnSelect(FieldSource *s); void OnDelete(FieldSource *s); LMessage::Result OnEvent(LMessage *m); void OnPaint(LSurface *pDC); int OnNotify(LViewI *Ctrl, LNotification n); }; class ShortCutView : public LWindow { AppWnd *App; LList *Lst; public: ShortCutView(AppWnd *app); ~ShortCutView(); void OnResource(Resource *r); int OnNotify(LViewI *Ctrl, LNotification n); }; #include "LgiRes_String.h" class AppWnd : public LDocApp { protected: // UI - LBox *HBox; - LBox *VBox; - LView *ContentView; + LBox *HBox = NULL; + LBox *VBox = NULL; + LView *ContentView = NULL; - LSubMenu *Edit; - LSubMenu *Help; - LSubMenu *ViewMenu; + LSubMenu *Edit = NULL; + LSubMenu *Help = NULL; + LSubMenu *ViewMenu = NULL; - LStatusBar *Status; + LStatusBar *Status = NULL; LStatusPane *StatusInfo[STATUS_MAX]; - ShortCutView *ShortCuts; + ShortCutView *ShortCuts = NULL; // App - ObjContainer *Objs; - Resource *LastRes; - FieldView *Fields; + ObjContainer *Objs = NULL; + Resource *LastRes = NULL; + FieldView *Fields = NULL; // Languages - int CurLang; + int CurLang = -1; LArray Languages; LHashTbl, bool> ShowLanguages; void SortDialogs(); void GetFileTypes(LFileSelect *Dlg, bool Write); public: AppWnd(); ~AppWnd(); // Languages bool ShowLang(LLanguageId Lang); void ShowLang(LLanguageId Lang, bool Show); LLanguage *GetCurLang(); void SetCurLang(LLanguage *L); LArray *GetLanguages(); void OnLanguagesChange(LLanguageId Lang, bool Add, bool Update = false); // --------------------------------------------------------------------- // Application Resource *NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select = true); bool InsertObject(int Type, Resource *r, bool Select = true); void DelObject(Resource *r); bool ListObjects(List &Lst); int GetUniqueStrRef(int Start = 1); int GetUniqueCtrlId(); void FindStrings(List &Strs, char *Define = 0, int *CtrlId = 0); ResString *GetStrFromRef(int Ref); ResStringGroup *GetDialogSymbols(); bool Empty(); void OnObjChange(FieldSource *r); void OnObjSelect(FieldSource *r); void OnObjDelete(FieldSource *r); void OnResourceSelect(Resource *r); void OnResourceDelete(Resource *r); void GotoObject(class ResString *s, ResStringGroup *g, ResDialog *d, ResMenuItem *m, ResDialogCtrl *c); ShortCutView *GetShortCutView(); void OnCloseView(ShortCutView *v); // --------------------------------------------------------------------- // Methods void SetStatusText(char *Text, int Pane = 0); bool TestLgi(bool Quite = true); bool LoadLgi(const char *FileName = 0); bool SaveLgi(const char *FileName = 0); void LoadWin32(const char *FileName = 0); bool SaveWin32(); void ImportLang(); void Compare(); bool WriteDefines(LStream &Defs); bool OpenFile(const char *FileName, bool Ro); void SaveFile(const char *FileName, std::function Callback); // --------------------------------------------------------------------- // Window int OnNotify(LViewI *Ctrl, LNotification n); LMessage::Result OnEvent(LMessage *m); int OnCommand(int Cmd, int Event, OsView Handle); void OnReceiveFiles(LArray &Files); void OnCreate(); }; #define INVALID_INT -10000 #define NEW_UI 1 struct SearchParams { LString::Array Text; #if NEW_UI bool LimitToText; bool LimitToDefine; #else LString Define; #endif LLanguageId InLang; LLanguageId NotInLang; int Ref; int CtrlId; SearchParams() { InLang = NULL; NotInLang = NULL; Ref = INVALID_INT; CtrlId = INVALID_INT; #if NEW_UI LimitToText = false; LimitToDefine = false; #else #endif } }; class SearchThread : public LEventTargetThread { List Res; AppWnd *App; LList *Results; SearchParams Params; LCancel State; bool HasContent(char *s); class Result *Test(class ResString *s); class Result *Test(class ResMenuItem *mi); public: SearchThread(AppWnd *app, LList *results); void Search(SearchParams &p); LMessage::Result OnEvent(LMessage *Msg); }; class Search : public LDialog, public SearchParams { AppWnd *App; LAutoPtr Thread; void OnCheck(); public: Search(AppWnd *app); int OnNotify(LViewI *c, LNotification n); }; class Results : public LWindow { class ResultsPrivate *d; public: Results(AppWnd *app, Search *search); ~Results(); void OnPosChange(); int OnNotify(LViewI *v, LNotification n); }; class ShowLanguagesDlg : public LDialog { class ShowLanguagesDlgPriv *d; public: ShowLanguagesDlg(AppWnd *app); ~ShowLanguagesDlg(); int OnNotify(LViewI *v, LNotification n); }; class ResCss : public Resource, public LLayout { friend class ResCssUi; protected: class ResCssUi *Ui; LAutoString Style; public: ResCss(AppWnd *w, int type = TYPE_CSS); ~ResCss(); void Create(LXmlTag *Load, SerialiseContext *Ctx); LView *Wnd() { return dynamic_cast(this); } void OnShowLanguages(); // Resource LView *CreateUI(); void OnRightClick(LSubMenu *RClick); void OnCommand(int Cmd); int OnCommand(int Cmd, int Event, OsView hWnd); // Serialize bool Test(ErrorCollection *e); bool Read(LXmlTag *t, SerialiseContext &Ctx); bool Write(LXmlTag *t, SerialiseContext &Ctx); }; extern void OpenTableLayoutTest(LViewI *p); diff --git a/ResourceEditor/win/LgiRes.vcxproj.filters b/ResourceEditor/win/LgiRes.vcxproj.filters --- a/ResourceEditor/win/LgiRes.vcxproj.filters +++ b/ResourceEditor/win/LgiRes.vcxproj.filters @@ -1,114 +1,116 @@  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav {dc3eeaf5-8eff-4c5c-afd1-f05bfe27166f} {b3a7e499-c5f7-431e-b958-9ac361382974} Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Lgi Lgi Lgi Lgi - Source Files + Lgi + + + Lgi - Source Files - - - Source Files + Lgi Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - - Resource Files Resource Files Scripts + + + Lgi + + \ No newline at end of file diff --git a/include/lgi/common/Window.h b/include/lgi/common/Window.h --- a/include/lgi/common/Window.h +++ b/include/lgi/common/Window.h @@ -1,333 +1,333 @@ #ifndef _LWINDOW_H_ #define _LWINDOW_H_ #include "lgi/common/View.h" /// The available states for a top level window enum LWindowZoom { /// Minimized LZoomMin, /// Restored/Normal LZoomNormal, /// Maximized LZoomMax }; enum LWindowHookType { LNoEvents = 0, /// \sa LWindow::RegisterHook() LMouseEvents = 1, /// \sa LWindow::RegisterHook() LKeyEvents = 2, /// \sa LWindow::RegisterHook() LKeyAndMouseEvents = LMouseEvents | LKeyEvents, }; /// A top level window. class LgiClass LWindow : public LView, // This needs to be second otherwise is causes v-table problems. #ifndef LGI_SDL virtual #endif public LDragDropTarget { friend class BViewRedir; friend class LApp; friend class LView; friend class LButton; friend class LDialog; friend class LWindowPrivate; friend struct LDialogPriv; - bool _QuitOnClose; + bool _QuitOnClose = false; protected: class LWindowPrivate *d; #if WINNATIVE LWindow *_Dialog = NULL; #elif defined(HAIKU) LWindowZoom _PrevZoom = LZoomNormal; #else OsWindow Wnd; void SetDeleteOnClose(bool i); #endif #if defined __GTK_H__ friend class LMenu; friend void lgi_widget_size_allocate(Gtk::GtkWidget *widget, Gtk::GtkAllocation *allocation); Gtk::GtkWidget *_Root, *_VBox, *_MenuBar; void OnGtkDelete(); Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); #elif defined(LGI_CARBON) friend pascal OSStatus LgiWindowProc(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); void _Delete(); bool _RequestClose(bool os); #elif defined(__OBJC__) public: // This returns the root level content NSView NSView *Handle(); protected: #endif /// The default button LViewI *_Default = NULL; /// The menu on the window LMenu *Menu = NULL; void SetChildDialog(LDialog *Dlg); void SetDragHandlers(bool On); /// Haiku: This shuts down the window's thread cleanly. int WaitThread(); public: #ifdef _DEBUG LMemDC DebugDC; #endif #ifdef __GTK_H__ LWindow(Gtk::GtkWidget *w = NULL); #elif LGI_CARBON LWindow(WindowRef wr = NULL); #elif LGI_COCOA LWindow(OsWindow wnd = NULL); #else LWindow(); #endif ~LWindow(); const char *GetClass() override { return "LWindow"; } /// Lays out the child views into the client area. virtual void PourAll(); /// Returns the current menu object LMenu *GetMenu() { return Menu; } /// Set the menu object. void SetMenu(LMenu *m) { Menu = m; } /// Set the window's icon bool SetIcon(const char *FileName); /// Don't show title bar bool SetTitleBar(bool ShowTitleBar); /// Gets the "quit on close" setting. bool GetQuitOnClose() { return _QuitOnClose; } /// \brief Sets the "quit on close" setting. /// /// When this is switched on the application will quit the main message /// loop when this LWindow is closed. This is really useful for your /// main application window. Otherwise the UI will disappear but the /// application is still running. void SetQuitOnClose(bool i) { _QuitOnClose = i; } bool GetSnapToEdge(); void SetSnapToEdge(bool b); bool GetAlwaysOnTop(); void SetAlwaysOnTop(bool b); /// Gets the current zoom setting LWindowZoom GetZoom(); /// Sets the current zoom void SetZoom(LWindowZoom i); /// Raises the window to the top of the stack. void Raise(); /// Moves a top level window on screen. void MoveOnScreen(); /// Moves a top level to the center of the screen void MoveToCenter(); /// Moves a top level window to where the mouse is void MoveToMouse(); /// Moves the window to somewhere on the same screen as 'wnd' bool MoveSameScreen(LViewI *wnd); // Focus setting LViewI *GetFocus(); enum FocusType { GainFocus, LoseFocus, ViewDelete }; void SetFocus(LViewI *ctrl, FocusType type); /// This setting can turn of taking focus when the window is shown. Useful for popups that /// don't want to steal focus from an underlying window. /// The default value is 'true' bool SetWillFocus(bool f); /// Registers a watcher to receive OnView... messages before they /// are passed through to the intended recipient. bool RegisterHook ( /// The target view. LView *Target, /// Combination of: /// #LMouseEvents - Where Target->OnViewMouse(...) is called for each click. /// and /// #LKeyEvents - Where Target->OnViewKey(...) is called for each key. /// OR'd together. LWindowHookType EventType, /// Not implemented int Priority = 0 ); /// Unregisters a hook target bool UnregisterHook(LView *Target); /// Gets the default view LViewI *GetDefault(); /// Sets the default view void SetDefault(LViewI *v); /// Saves/loads the window's state, e.g. position, minimized/maximized etc bool SerializeState ( /// The data store for reading/writing LDom *Store, /// The field name to use for storing settings under const char *FieldName, /// TRUE if loading the settings into the window, FALSE if saving to the store. bool Load ); /// Builds a map of keyboard short cuts. typedef LHashTbl,LViewI*> ShortcutMap; void BuildShortcuts(ShortcutMap &Map, LViewI *v = NULL); ////////////////////// Events /////////////////////////////// /// Called when the window zoom state changes. virtual void OnZoom(LWindowZoom Action) {} /// Called when the tray icon is clicked. (if present) virtual void OnTrayClick(LMouse &m); /// Called when the tray icon menu is about to be displayed. virtual void OnTrayMenu(LSubMenu &m) {} /// Called when the tray icon menu item has been selected. virtual void OnTrayMenuResult(int MenuId) {} /// Called when files are dropped on the window. virtual void OnReceiveFiles(LArray &Files) {} /// Called when a URL is sent to the window virtual void OnUrl(const char *Url) {}; ///////////////// Implementation //////////////////////////// void OnPosChange() override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; /// Allow the window to filter mouse events: /// \returns false if the Window consumed the event. bool HandleViewMouse(LView *v, LMouse &m); /// Allow the window to filter key events: /// \returns false if the Window consumed the event. bool HandleViewKey(LView *v, LKey &k); /// Return true to accept application quit bool OnRequestClose(bool OsShuttingDown) override; bool Obscured(); bool Visible() override; void Visible(bool i) override; bool IsActive(); bool SetActive(); LRect &GetPos() override; void SetDecor(bool Visible); LPoint GetDpi(); LPointF GetDpiScale(); void ScaleSizeToDpi(); // D'n'd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; #if !WINNATIVE bool Attach(LViewI *p) override; // Props #if defined(HAIKU) OsWindow WindowHandle() override; #else OsWindow WindowHandle() override { return Wnd; } #endif bool Name(const char *n) override; const char *Name() override; bool SetPos(LRect &p, bool Repaint = false) override; LRect &GetClient(bool InClientSpace = true) override; // Events void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; void OnCreate() override; virtual void OnFrontSwitch(bool b); #else OsWindow WindowHandle() override { return _View; } #endif #if HAIKU void SetModalDialog(LWindow *dlg); #elif defined(LGI_SDL) virtual bool PushWindow(LWindow *v); virtual LWindow *PopWindow(); #elif defined __GTK_H__ void OnGtkRealize(); bool IsAttached(); void Quit(bool DontDelete = false); LRect *GetDecorSize(); bool TranslateMouse(LMouse &m); LViewI *WindowFromPoint(int x, int y, bool Debug = false); void _SetDynamic(bool b); void _OnViewDelete(); void SetParent(LViewI *p) override; #elif defined(MAC) bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) override; void Quit(bool DontDelete = false) override; int OnCommand(int Cmd, int Event, OsView Wnd) override; LViewI *WindowFromPoint(int x, int y, int DebugDebug = 0) override; #if defined(LGI_CARBON) OSErr HandlerCallback(DragTrackingMessage *tracking, DragRef theDrag); #endif #endif }; #endif diff --git a/private/win/AppPriv.h b/private/win/AppPriv.h --- a/private/win/AppPriv.h +++ b/private/win/AppPriv.h @@ -1,56 +1,50 @@ #pragma once #include "SymLookup.h" #include "lgi/common/Json.h" #include "lgi/common/FontCache.h" class LAppPrivate { public: // Common - LApp *Owner; + LApp *Owner = NULL; LAutoPtr Config; - LFileSystem *FileSystem; - GdcDevice *GdcSystem; + LFileSystem *FileSystem = NULL; + GdcDevice *GdcSystem = NULL; OsAppArguments Args; - LLibrary *SkinLib; - OsThread GuiThread; - int LinuxWine; + LLibrary *SkinLib = NULL; + OsThread GuiThread = NULL; + int LinuxWine = -1; LAutoString Mime, ProductId; - bool ThemeAware; + bool ThemeAware = true; /// Any fonts needed for styling the elements LAutoPtr FontCache; // Win32 - bool QuitReceived; + bool QuitReceived = false; LApp::ClassContainer Classes; - LSymLookup *SymLookup; + LSymLookup *SymLookup = NULL; LAppPrivate(LApp *owner) : Owner(owner) { - LinuxWine = -1; - SymLookup = 0; - QuitReceived = false; - SkinLib = 0; - GuiThread = NULL; - auto b = DuplicateHandle(GetCurrentProcess(), + auto b = DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &GuiThread, 0, false, DUPLICATE_SAME_ACCESS); - ThemeAware = true; } ~LAppPrivate() { DeleteObj(SkinLib); DeleteObj(SymLookup); } LJson *GetConfig(); bool SaveConfig(); }; diff --git a/src/common/Lgi/DocApp.cpp b/src/common/Lgi/DocApp.cpp --- a/src/common/Lgi/DocApp.cpp +++ b/src/common/Lgi/DocApp.cpp @@ -1,726 +1,726 @@ /*hdr ** FILE: LDocApp.cpp ** AUTHOR: Matthew Allen ** DATE: 20/4/01 ** DESCRIPTION: Generic Document Application Impl ** */ #include #include "lgi/common/DocApp.h" #include "lgi/common/Token.h" #include "lgi/common/OptionsFile.h" #ifdef HAS_PROPERTIES #include "GProperties.h" #endif #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" ////////////////////////////////////////////////////////////////////////////////////////// class LDocAppPrivate { public: // Data LWindow *App; LString OptionsFile; LString OptionsParam; char *AppName; LString CurFile; bool Dirty; GDocAppInstallMode Mode; LDocAppPrivate(LWindow *app, char *param) { App = app; OptionsParam = param; AppName = 0; Dirty = 0; Mode = InstallPortable; } ~LDocAppPrivate() { // Release memory DeleteArray(AppName); } LString GetOptionsFile(const char *Ext) { // Get options file LString Status; char Opt[MAX_PATH_LEN]; if (LAppInst->GetOption("o", Opt, sizeof(Opt))) { if (LFileExists(Opt)) Status = Opt; } if (!Status) { auto Exe = LGetExeFile(); char *File = strrchr(Exe, DIR_CHAR); if (File) { File++; Status = File; #ifdef WIN32 auto Dot = Status.RFind("."); if (Dot >= 0) { Status = Status(0, Dot+1) + Ext; } else #endif { // unix apps have no '.' in their name Status += LString(".") + Ext; } char p[MAX_PATH_LEN]; if (Mode == InstallPortable) { LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); } else { if (LGetSystemPath(LSP_APP_ROOT, p, sizeof(p)) && !LDirExists(p)) { FileDev->CreateFolder(p); } } LMakePath(p, sizeof(p), p, Status); Status = p; } } return Status; } /////////////////////////////////////////////////////////////////////////// // Xml Options const char *GetExtension(LOptionsFile *p) { return "xml"; } bool SetOpt(LOptionsFile *p, const char *opt, char *str) { LVariant v = str; return p ? p->SetValue(opt, v) : false; } bool GetOpt(LOptionsFile *p, const char *opt, LVariant &v) { return p ? p->GetValue(opt, v) : false; } bool SerializeOpts(LOptionsFile *p, bool Write) { if (!p) return false; p->SetFile(OptionsFile); bool Result = p->SerializeFile(Write); if (Write && !Result) { // Probably because we don't have write access to the install folder? LgiMsg( App, "Failed to write options to '%s' (mode=%s)", App->Name(), MB_OK, OptionsFile.Get(), Mode == InstallPortable ? "Portable" : "Desktop"); } return Result; } //////////////////////////////////////////////////////////////////////////// // Prop List Options #ifdef __PROP_H char *GetExtension(ObjProperties *p) { return "r"; } bool SetOpt(ObjProperties *p, char *opt, char *str) { return p ? p->Set(opt, str) : false; } bool GetOpt(ObjProperties *p, char *opt, LVariant &v) { char *str; if (p && p->Get(opt, str)) { v = str; return true; } return false; } bool SerializeOpts(ObjProperties *p, bool Write) { bool Status = false; if (p) { LFile f; if (f.Open(OptionsFile, Write?O_WRITE:O_READ)) { if (Write) f.SetSize(0); Status = p->Serialize(f, Write); } } return Status; } #endif }; ////////////////////////////////////////////////////////////////////////////////////////// template LDocApp::LDocApp(const char *appname, LIcon icon, char *optsname) { Options = 0; _LangOptsName = 0; d = new LDocAppPrivate(this, optsname); LRect r(0, 0, 800, 600); SetPos(r); MoveToCenter(); _FileMenu = 0; d->AppName = NewStr(appname?appname:(char*)"Lgi.LDocApp"); char p[MAX_PATH_LEN]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) { LMakePath(p, sizeof(p), p, "_write_test.txt"); LFile f; if (!f.Open(p, O_WRITE)) { d->Mode = InstallDesktop; } else { f.Close(); FileDev->Delete(p, false); } } SetQuitOnClose(true); // Setup class if (icon) { #if defined WIN32 LWindowsClass *c = LWindowsClass::Create(d->AppName); if (c) { if (icon < 0x10000) c->Class.hIcon = LoadIcon(LProcessInst(), MAKEINTRESOURCE(icon)); else c->Class.hIcon = LoadIcon(LProcessInst(), (TCHAR*)(size_t)icon); } #else SetIcon(icon); #endif } } template LDocApp::~LDocApp() { DeleteObj(d); DeleteObj(Options); } template GDocAppInstallMode LDocApp::GetInstallMode() { return d->Mode; } template bool LDocApp::SetLanguage(char *LangId) { if (!_LangOptsName) return false; if (LgiMsg( this, LLoadString(L_DOCAPP_RESTART_APP, "Changing the language requires restarting the application.\nDo you want to restart?"), d->AppName, MB_YESNO) != IDYES) return false; LVariant v; GetOptions()->SetValue(_LangOptsName, v = LangId); GetOptions()->SerializeFile(true); LCloseApp(); LExecute(LGetExeFile()); return true; } template char *LDocApp::GetOptionsFileName() { return d->OptionsFile; } template void LDocApp::_Close() { Empty(); SetCurFile(0); } template bool LDocApp::_DoSerialize(bool Write) { bool Status = false; if (!d->OptionsFile) { const char *Ext = d->GetExtension(Options); d->OptionsFile = d->GetOptionsFile(Ext); } if (!Options) { if (LFileExists(d->OptionsFile)) Options = new OptionsFmt(d->OptionsFile); else Options = new OptionsFmt(LOptionsFile::PortableMode, d->OptionsParam); } if (Write) { // Save window position SerializeState(Options, "Pos", false); // save misc options SerializeOptions(Options, true); LOptionsFile *Of = dynamic_cast(Options); if (Of) Of->CreateTag("Mru"); LMru::Serialize(Options, "Mru", true); } // do the work Status = _SerializeFile(Write); if (!Write) { LVariant Lang; if (_LangOptsName && Options->GetValue(_LangOptsName, Lang)) { LAppInst->SetConfig("language", Lang.Str()); } // read misc options LMru::Serialize(Options, "Mru", false); SerializeOptions(Options, false); // window pos SerializeState(Options, "Pos", true); } return Status; } template bool LDocApp::_SerializeFile(bool Write) { return d->SerializeOpts(Options, Write); } template bool LDocApp::_Create(LIcon IconResource) { // Load options _DoSerialize(false); // Create and setup the main application window #ifdef WIN32 HICON hIcon = NULL; if (IconResource) hIcon = LoadIcon(LProcessInst(), MAKEINTRESOURCE(IconResource)); CreateClassW32(d->AppName, hIcon); #endif if (Attach(0)) { Name(d->AppName); if (X() < 1) { LRect r(100, 100, 600, 500); SetPos(r); } } else { LAssert(!"Window create failed."); return false; } return true; } template bool LDocApp::_Destroy() { // Save options _DoSerialize(true); LAppInst->AppWnd = 0; return true; } template bool LDocApp::_LoadMenu(const char *Resource, const char *Tags, int FileMenuId, int RecentMenuId) { if ((Menu = new LMenu)) { Menu->Attach(this); if (Resource) { Menu->Load(this, Resource, Tags); if (FileMenuId >= 0) _FileMenu = Menu->FindSubMenu(FileMenuId); else _FileMenu = Menu->AppendSub("&File", 0); } else { _FileMenu = Menu->AppendSub("&File"); } if (_FileMenu) { int Idx = 0; if (!_FileMenu->FindItem(IDM_OPEN)) _FileMenu->AppendItem("&Open", IDM_OPEN, true, Idx++, "CtrlCmd+O"); if (!_FileMenu->FindItem(IDM_SAVE)) _FileMenu->AppendItem("&Save", IDM_SAVE, true, Idx++, "CtrlCmd+S"); if (!_FileMenu->FindItem(IDM_SAVEAS)) _FileMenu->AppendItem("Save &As", IDM_SAVEAS, true, Idx++); if (!_FileMenu->FindItem(IDM_CLOSE)) { _FileMenu->AppendItem("Close", IDM_CLOSE, true, Idx++, "CtrlCmd+W"); _FileMenu->AppendSeparator(Idx++); } LSubMenu *Recent = NULL; if (RecentMenuId >= 0) Recent = _FileMenu->FindSubMenu(RecentMenuId); else Recent = _FileMenu->AppendSub("Recent...", Idx++); if (Recent) { Set(Recent); //_FileMenu->AppendSeparator(); LMru::Serialize(Options, "Mru", false); } if (!_FileMenu->FindItem(IDM_EXIT)) _FileMenu->AppendItem("&Quit", IDM_EXIT, true, -1, "CtrlCmd+Q"); } } return Menu != 0; } template void LDocApp::_OpenFile(const char *File, bool ReadOnly, std::function Callback) { SetDirty(false, [this, Callback, ReadOnly, Fn = LString(File)](auto status) mutable { if (!status) { if (Callback) Callback(false); return; } char RealPath[MAX_PATH_LEN]; if (LResolveShortcut(Fn, RealPath, sizeof(RealPath))) Fn = RealPath; LMru::_OpenFile(Fn, ReadOnly, [this, Callback, Fn](auto ok) { if (ok) { d->Dirty = false; SetCurFile(Fn); } if (Callback) Callback(ok); }); }); } template void LDocApp::_SaveFile(const char *File, std::function Callback) { char RealPath[MAX_PATH_LEN]; if (LResolveShortcut(File, RealPath, sizeof(RealPath))) { File = RealPath; } else { strcpy_s(RealPath, sizeof(RealPath), File); } LMru::_SaveFile(RealPath, [this, Callback](auto FileName, auto Status) { if (Status) { d->Dirty = false; SetCurFile(FileName); OnDirty(d->Dirty); } if (Callback) Callback(FileName, Status); }); } template char *LDocApp::GetAppName() { return d->AppName; } template void LDocApp::SetCurFile(const char *f) { if (!d->CurFile.Equals(f)) { d->CurFile = f; if (f) AddFile(f); } LString Display; if (SerializeEntry(&Display, &d->CurFile, NULL)) { char s[MAX_PATH_LEN + 100]; if (Display) { sprintf_s(s, sizeof(s), "%s [%s%s]", d->AppName, Display.Get(), d->Dirty ? " changed" : ""); } else if (d->Dirty) { sprintf_s(s, sizeof(s), "%s [changed]", d->AppName); } else { strcpy_s(s, sizeof(s), d->AppName); } Name(s); } } template const char *LDocApp::GetCurFile() { return d->CurFile; } template void LDocApp::SetDirty(bool Dirty, std::function Callback) { if (IsAttached() && (d->Dirty ^ Dirty)) { // Changing... if (Dirty) { // Setting dirty d->Dirty = true; SetCurFile(d->CurFile); } else { // Clearing dirty int Result = LgiMsg(this, LLoadString(L_DOCAPP_SAVE_CHANGE, "Do you want to save your changes?"), d->AppName, MB_YESNOCANCEL); if (Result == IDYES) { if (!ValidStr(d->CurFile)) { LMru::OnCommand(IDM_SAVEAS, [this](auto status) { if (status) { d->Dirty = false; SetCurFile(d->CurFile); } }); if (Callback) Callback(false); return; } else { _SaveFile(d->CurFile, [this, Callback](auto fn, auto status) { if (status) { d->Dirty = false; SetCurFile(fn); OnDirty(d->Dirty); } if (Callback) Callback(status); }); return; } } else if (Result == IDCANCEL) { if (Callback) Callback(false); return; } d->Dirty = false; SetCurFile(d->CurFile); } OnDirty(d->Dirty); } if (Callback) Callback(true); } template bool LDocApp::GetDirty() { return d->Dirty; } template OptionsFmt *LDocApp::GetOptions() { return Options; } template void LDocApp::OnReceiveFiles(LArray &Files) { if (Files.Length() == 0) return; LString f = Files[0]; if (!f) return; _OpenFile(f, false, [this, f](auto ok) { if (ok) LMru::AddFile(f); }); } template bool LDocApp::OnRequestClose(bool OsShuttingDown) { if (GetDirty()) { SetDirty(false, [this](auto ok) { if (ok) LCloseApp(); }); // Wait for the SetDirty callback to exit... return false; } - return true; + return LWindow::OnRequestClose(OsShuttingDown); } template int LDocApp::OnCommand(int Cmd, int Event, OsView Window) { switch (Cmd) { case IDM_SAVE: { if (!GetCurFile()) { LMru::OnCommand(IDM_SAVEAS, NULL); return 0; } else { _SaveFile(GetCurFile(), NULL); return 0; } break; } case IDM_CLOSE: { SetDirty(false, [this](auto ok) { if (ok) _Close(); }); return 0;; } case IDM_EXIT: { LCloseApp(); break; } } LMru::OnCommand(Cmd, NULL); return 0; } template LMessage::Result LDocApp::OnEvent(LMessage *m) { LMru::OnEvent(m); /* switch (MsgCode(m)) { #ifdef WIN32 case WM_CLOSE: { if (!SetDirty(false)) { return 0; } break; } #endif } */ return LWindow::OnEvent(m); } template class LDocApp; #ifdef __PROP_H template class LDocApp; #endif diff --git a/src/common/Lgi/Mru.cpp b/src/common/Lgi/Mru.cpp --- a/src/common/Lgi/Mru.cpp +++ b/src/common/Lgi/Mru.cpp @@ -1,471 +1,472 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Mru.h" #include "lgi/common/Variant.h" #include "lgi/common/Menu.h" //////////////////////////////////////////////////////////////////// #define DEBUG_LOG 0 #define M_MRU_BASE (M_USER+0x3500) struct LMruEntry { LString Display; LString Raw; }; class LMruPrivate { public: int Size = 10; LArray Items; LSubMenu *Parent = NULL; LFileType *SelectedType = NULL; LMruPrivate() { } ~LMruPrivate() { Items.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////// LMru::LMru() { d = new LMruPrivate; } LMru::~LMru() { DeleteObj(d); } bool LMru::SerializeEntry ( /// The displayable version of the reference (this should have any passwords blanked out) LString *Display, /// The form passed to the client software to open/save. (passwords NOT blanked) LString *Raw, /// The form safe to write to disk, if a password is present it must be encrypted. LString *Stored ) { if (Raw && Raw->Get()) { if (Stored) *Stored = *Raw; if (Display) *Display = *Raw; } else if (Stored && Stored->Get()) { if (Display) *Display = *Stored; if (Raw) *Raw = *Stored; } return true; } void LMru::GetFileTypes(LFileSelect *Dlg, bool Write) { Dlg->Type("All Files", LGI_ALL_FILES); } const char *LMru::_GetCurFile() { if (d->Items.Length()) return d->Items[0]->Raw; return NULL; } LFileType *LMru::GetSelectedType() { return d->SelectedType; } void LMru::_OpenFile(const char *File, bool ReadOnly, std::function Callback) { bool Status = OpenFile(File, ReadOnly); if (Status) AddFile(File, true); else RemoveFile(File); if (Callback) Callback(Status); } void LMru::_SaveFile(const char *FileName, std::function Callback) { if (!FileName) { if (Callback) Callback(NULL, false); return; } LString File = FileName; LFileType *st; if (!LFileExists(File) && (st = GetSelectedType()) && st->Extension()) { auto Cur = LGetExtension(File); if (!Cur) { // extract extension LString::Array a = LString(st->Extension()).Split(LGI_PATH_SEPARATOR); for (auto e: a) { LString::Array p = e.RSplit(".", 1); if (!p.Last().Equals("*")) { // bung the extension from the file type if not there File += "."; File += p.Last(); break; } } } } SaveFile(File, [this, Callback](auto fileName, auto Status) { if (Status) AddFile(fileName); else RemoveFile(fileName); if (Callback) Callback(fileName, Status); }); } void LMru::_Update() { if (d->Items.Length() > d->Size) d->Items.Length(d->Size); if (d->Parent) { // remove existing items. d->Parent->Empty(); // add current items if (d->Items.Length() > 0) { for (int i=0; iItems.Length(); i++) { LMruEntry *c = d->Items[i]; d->Parent->AppendItem(c->Display ? c->Display : c->Raw, M_MRU_BASE + i, true); } } else { d->Parent->AppendItem("(none)", -1, false); } } } bool LMru::Set(LSubMenu *parent, int size) { d->Parent = parent; if (size > 0) d->Size = size; _Update(); return true; } const char *LMru::AddFile(const char *FileName, bool Update) { #if DEBUG_LOG LgiTrace("%s:%i - AddFile(%s,%i)\n", _FL, FileName, Update); #endif if (!FileName) return NULL; auto Status = FileName; LMruEntry *c = NULL; for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; #if DEBUG_LOG LgiTrace("[%i] cmp '%s' '%s'\n", i, e->Raw.Get(), FileName); #endif if (!LFileCompare(e->Raw, FileName)) { // exact string being added.. just move to the top // no need to reallocate if (strcmp(e->Raw, FileName) && e->Raw.Length() == strlen(FileName)) { e->Raw = FileName; // This fixes any changes in case... #if DEBUG_LOG LgiTrace("Updating raw case\n"); #endif } else { #if DEBUG_LOG LgiTrace("Moving to the top\n"); #endif } d->Items.DeleteAt(i, true); d->Items.AddAt(0, e); c = e; break; } } if (!c) { c = new LMruEntry; c->Raw = FileName; if (SerializeEntry(&c->Display, &c->Raw, NULL)) { #if DEBUG_LOG LgiTrace("Adding new entry %s %s\n", c->Raw.Get(), c->Display.Get()); #endif d->Items.AddAt(0, c); } else { LAssert(0); } } if (Update) { // update _Update(); } return Status; } void LMru::RemoveFile(const char *FileName, bool Update) { // remove from list if there for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; if (stricmp(e->Raw, FileName) == 0) { d->Items.DeleteAt(i); DeleteObj(e); break; } } if (Update) { _Update(); } } void LMru::DoFileDlg(LAutoPtr FileSelect, bool Open, std::function OnSelect) { if (!FileSelect) { LAssert(!"No select dialog."); return; } auto Select = FileSelect.Release(); GetFileTypes(Select, false); Select->ShowReadOnly(Open); auto Cb = [this, Open, OnSelect](auto s, bool ok) { if (ok) { d->SelectedType = s->TypeAt(s->SelectedType()); if (Open) { _OpenFile(s->Name(), s->ReadOnly(), [OnSelect](auto ok) { if (OnSelect) OnSelect(ok); }); } else { _SaveFile(s->Name(), [OnSelect](auto fn, auto ok) { if (OnSelect) OnSelect(ok); }); } } else if (OnSelect) { OnSelect(false); } delete s; }; if (Open) Select->Open(Cb); else Select->Save(Cb); } void LMru::OnCommand(int Cmd, std::function OnStatus) { LViewI *Wnd = d->Parent->GetMenu() ? d->Parent->GetMenu()->WindowHandle() : 0; if (!Wnd) return; if (Cmd >= M_MRU_BASE && Cmd < M_MRU_BASE + d->Items.Length()) { int Index = Cmd - M_MRU_BASE; auto c = d->Items[Index]; if (!c) { if (OnStatus) OnStatus(false); } else { _OpenFile(c->Raw, false, [OnStatus](auto ok) { - OnStatus(ok); + if (OnStatus) + OnStatus(ok); }); } } else if (Cmd == IDM_OPEN || Cmd == IDM_SAVEAS) { LAutoPtr Select(new LFileSelect); Select->Parent(Wnd); Select->ClearTypes(); d->SelectedType = 0; if (_GetCurFile()) { if (LFileExists(_GetCurFile())) Select->Name(_GetCurFile()); char Path[256]; strcpy_s(Path, sizeof(Path), _GetCurFile()); LTrimDir(Path); if (LDirExists(Path)) Select->InitialDir(Path); } auto ForwardStatus = [OnStatus](bool ok) { if (OnStatus) OnStatus(ok); }; if (Cmd == IDM_OPEN) DoFileDlg(Select, true, ForwardStatus); else if (Cmd == IDM_SAVEAS) DoFileDlg(Select, false, ForwardStatus); } } LMessage::Result LMru::OnEvent(LMessage *Msg) { /* if (d->Parent && MsgCode(Msg) == M_COMMAND) { #ifdef BEOS int32 Cmd = 0; int32 Event = 0; Msg->FindInt32("Cmd", &Cmd); Msg->FindInt32("Event", &Event); #else int Cmd = MsgA(Msg) & 0xffff; #endif OnCommand(Cmd); } */ return false; } bool LMru::Serialize(LDom *Store, const char *Prefix, bool Write) { bool Status = false; LVariant v; if (Store && Prefix) { if (Write) { // add our keys int Idx = 0; char Key[64]; LHashTbl, bool> Saved; for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; LAssert(e->Raw.Get() != NULL); if (!Saved.Find(e->Raw)) { Saved.Add(e->Raw, true); LString Stored; if (SerializeEntry(NULL, &e->Raw, &Stored)) // Convert Raw -> Stored { sprintf_s(Key, sizeof(Key), "%s.Item%i", Prefix, Idx++); Store->SetValue(Key, v = Stored.Get()); } else LAssert(0); } } sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); Store->SetValue(Key, v = (int)Idx); } else { // clear ourself d->Items.DeleteObjects(); // read our keys in char Key[64]; sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); LVariant i; if (Store->GetValue(Key, i)) { for (int n=0; nGetValue(Key, File)) { LString Stored = File.Str(); LAssert(Stored.Get() != NULL); LAutoPtr e(new LMruEntry); if (SerializeEntry(&e->Display, &e->Raw, &Stored)) // Convert Stored -> Raw { d->Items.Add(e.Release()); } } } } _Update(); } } return Status; } diff --git a/src/win/Lgi/Window.cpp b/src/win/Lgi/Window.cpp --- a/src/win/Lgi/Window.cpp +++ b/src/win/Lgi/Window.cpp @@ -1,1418 +1,1416 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Panel.h" #include "lgi/common/Variant.h" #include "lgi/common/Button.h" #include "lgi/common/Notifications.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" #define DEBUG_WINDOW_PLACEMENT 0 #define DEBUG_HANDLE_VIEW_KEY 0 #define DEBUG_HANDLE_VIEW_MOUSE 0 #define DEBUG_SERIALIZE_STATE 0 #define DEBUG_SETFOCUS 0 extern bool In_SetWindowPos; typedef UINT (WINAPI *ProcGetDpiForWindow)(_In_ HWND hwnd); typedef UINT (WINAPI *ProcGetDpiForSystem)(VOID); LLibrary User32("User32"); LPoint LGetDpiForWindow(HWND hwnd) { static bool init = false; static ProcGetDpiForWindow pGetDpiForWindow = NULL; static ProcGetDpiForSystem pGetDpiForSystem = NULL; if (!init) { init = true; pGetDpiForWindow = (ProcGetDpiForWindow) User32.GetAddress("GetDpiForWindow"); pGetDpiForSystem = (ProcGetDpiForSystem) User32.GetAddress("GetDpiForSystem"); } if (pGetDpiForWindow && pGetDpiForSystem) { auto Dpi = hwnd ? pGetDpiForWindow(hwnd) : pGetDpiForSystem(); return LPoint(Dpi, Dpi); } return LScreenDpi(); } /////////////////////////////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; class LWindowPrivate { public: LArray Hooks; bool SnapToEdge; bool AlwaysOnTop; LWindowZoom Show; bool InCreate; LAutoPtr Wp; LPoint Dpi; int ShowCmd = SW_NORMAL; // Focus stuff LViewI *Focus; LWindowPrivate() { Focus = NULL; InCreate = true; Show = LZoomNormal; SnapToEdge = false; AlwaysOnTop = false; } ~LWindowPrivate() { } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return (ssize_t)Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { _Window = this; d = new LWindowPrivate; SetStyle(GetStyle() | WS_TILEDWINDOW | WS_CLIPCHILDREN); SetStyle(GetStyle() & ~WS_CHILD); SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); LWindowsClass *c = LWindowsClass::Create(GetClass()); if (c) c->Register(); Visible(false); - _Default = 0; _Lock = new LMutex("LWindow"); - _QuitOnClose = false; } LWindow::~LWindow() { if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (Menu) { Menu->Detach(); DeleteObj(Menu); } DeleteObj(_Lock); DeleteObj(d); } int LWindow::WaitThread() { // No thread to wait on... return 0; } bool LWindow::SetTitleBar(bool ShowTitleBar) { if (ShowTitleBar) { SetStyle(GetStyle() | WS_TILEDWINDOW); } else { SetStyle(GetStyle() & ~WS_TILEDWINDOW); SetStyle(GetStyle() | WS_POPUP); } return true; } bool LWindow::SetIcon(const char *Icon) { return CreateClassW32(LAppInst->Name(), LoadIcon(LProcessInst(), (LPCWSTR)Icon)) != 0; } LViewI *LWindow::GetFocus() { return d->Focus; } static LAutoString DescribeView(LViewI *v) { if (!v) return LAutoString(NewStr("NULL")); char s[512]; int ch = 0; ::LArray p; for (LViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (auto n=MIN(3, (ssize_t)p.Length()-1); n>=0; n--) { v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, ">%s", v->GetClass()); } return LAutoString(NewStr(s)); } static bool HasParentPopup(LViewI *v) { for (; v; v = v->GetParent()) { if (dynamic_cast(v)) return true; } return false; } bool LWindow::SetWillFocus(bool f) { d->ShowCmd = f ? SW_NORMAL : SW_SHOWNOACTIVATE; return true; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { LViewI *This = this; if (ctrl == This && d->Focus) { // The main LWindow is getting focus. // Check if we can re-focus the previous child focus... LView *v = d->Focus->GetGView(); if (v && !HasParentPopup(v)) { // We should never return focus to a popup, or it's child. if (!(v->WndFlags & GWF_FOCUS)) { // Yes, the child view doesn't think it has focus... // So re-focus it... if (v->Handle()) { // Non-virtual window... ::SetFocus(v->Handle()); } v->WndFlags |= GWF_FOCUS; v->OnFocus(true); v->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) refocusing: %s\n", _set.Get(), TypeName, _foc.Get()); #endif return; } } } // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non LView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool b) { d->SnapToEdge = b; } bool LWindow::GetAlwaysOnTop() { return d->AlwaysOnTop; } void LWindow::SetAlwaysOnTop(bool b) { d->AlwaysOnTop = b; if (_View) SetWindowPos(_View, b ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); } void LWindow::Raise() { if (_View) { DWORD dwFGProcessId; DWORD dwFGThreadId = GetWindowThreadProcessId(_View, &dwFGProcessId); DWORD dwThisThreadId = GetCurrentThreadId(); AttachThreadInput(dwThisThreadId, dwFGThreadId, true); SetForegroundWindow(_View); BringWindowToTop(_View); if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", __FILE__, __LINE__, _View); } ::SetFocus(_View); AttachThreadInput(dwThisThreadId, dwFGThreadId, false); } } LWindowZoom LWindow::GetZoom() { if (IsZoomed(Handle())) { return LZoomMax; } else if (IsIconic(Handle())) { return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (_View && IsWindowVisible(_View)) { switch (i) { case LZoomMax: { ShowWindow(Handle(), SW_MAXIMIZE); break; } case LZoomMin: { ShowWindow(Handle(), SW_MINIMIZE); break; } case LZoomNormal: { if (!Visible()) { Visible(true); } if (IsIconic(Handle()) || IsZoomed(Handle())) { ShowWindow(Handle(), d->ShowCmd); } LYield(); RECT r; GetWindowRect(Handle(), &r); if (r.left != Pos.x1 || r.top != Pos.y1) { int Shadow = WINDOWS_SHADOW_AMOUNT; SetWindowPos(Handle(), 0, Pos.x1, Pos.y1, Pos.X() + Shadow, Pos.Y() + Shadow, SWP_NOZORDER); } break; } } } d->Show = i; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return true; } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { #if DEBUG_HANDLE_VIEW_MOUSE m.Trace("HandleViewMouse"); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { LView *t = d->Hooks[i].Target; if (!t->OnViewMouse(v, m)) { #if DEBUG_HANDLE_VIEW_MOUSE if (m.IsMove()) LgiTrace(" Hook %i of %i ate mouse event: '%s'\n", i, d->Hooks.Length(), d->Hooks[i].Target->GetClass()); #endif return false; } } } #if DEBUG_HANDLE_VIEW_MOUSE if (!m.IsMove()) LgiTrace(" Passing mouse event to '%s'\n", v->GetClass()); #endif return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { #if DEBUG_HANDLE_VIEW_KEY char msg[256]; sprintf_s(msg, sizeof(msg), "HandleViewKey, v=%s", v ? v->GetClass() : "NULL"); k.Trace(msg); #endif // Any window in a pop up always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Popup %s handling key.\n", p->GetClass()); #endif return v->OnKey(k); } } // Allow any hooks to see the key... #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Hooks.Length()=%i.\n", (int)d->Hooks.Length()); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Hook[%i] %s handling key.\n", i, d->Hooks[i].Target->GetClass()); #endif return true; } } } // Give the key to the focused window... if (d->Focus && d->Focus->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Focus %s handling key.\n", d->Focus->GetClass()); #endif return true; } // Check default controls p = 0; if (k.c16 == VK_RETURN) { if (!_Default) p = _Default = FindControl(IDOK); else p = _Default; #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using _Default ctrl (%s).\n", p ? p->GetClass() : "NULL"); #endif } else if (k.c16 == VK_ESCAPE) { p = FindControl(IDCANCEL); if (p) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using IDCANCEL ctrl (%s).\n", p->GetClass()); #endif } } if (p && p->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Default control %s handled key.\n", p->GetClass()); #endif return true; } // Menu shortcut? if (Menu && Menu->OnKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Menu handled key.\n"); #endif return true; } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" No one handled key.\n"); #endif return false; } void LWindow::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } bool LWindow::Obscured() { RECT tRect; bool isObscured = false; if (GetWindowRect(_View, &tRect)) { RECT nRect; HWND walker = _View; while (walker = ::GetNextWindow(walker, GW_HWNDPREV)) { if (IsWindowVisible(walker)) { if ((::GetWindowRect(walker, &nRect))) { RECT iRect; IntersectRect(&iRect, &tRect, &nRect); if (iRect.bottom || iRect.top || iRect.left || iRect.right) { isObscured = true; break; } } } } } return isObscured; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool v) { if (v) PourAll(); if (v) { SetStyle(GetStyle() | WS_VISIBLE); if (_View) { LWindowZoom z = d->Show; char *Cmd = 0; LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(*Wp); Wp->flags = 2; Wp->ptMaxPosition.x = -1; Wp->ptMaxPosition.y = -1; if (d->Show == LZoomMax) { Wp->showCmd = SW_MAXIMIZE; Cmd = "SW_MAXIMIZE"; } else if (d->Show == LZoomMin) { Wp->showCmd = SW_MINIMIZE; Cmd = "SW_MINIMIZE"; } else { Wp->showCmd = d->ShowCmd; Cmd = "SW_NORMAL"; } Wp->rcNormalPosition = Pos; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(_View, Wp); if (d->InCreate) d->Wp = Wp; } } } else { #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - Visible(%i)\n", __FILE__, __LINE__, v); #endif LView::Visible(v); } if (v) { OnZoom(d->Show); } } static bool IsAppWnd(HWND h) { if (!IsWindowVisible(h)) return false; auto flags = GetWindowLong(h, GWL_STYLE); if (flags & WS_POPUP) return false; return true; } bool LWindow::IsActive() { auto top = GetTopWindow(GetDesktopWindow()); while (top && !IsAppWnd(top)) top = ::GetWindow(top, GW_HWNDNEXT); return top == _View; } bool LWindow::SetActive() { if (!_View) return false; return SetWindowPos(_View, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) != 0; } void LWindow::PourAll() { LRegion Client(GetClient()); LRegion Update; bool HasTools = false; { LRegion Tools; for (auto v: Children) { LView *k = dynamic_cast(v); if (k && k->_IsToolBar) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } // LgiTrace("Client=%s\n", Client.Bound().GetStr()); for (auto v: Children) { LView *k = dynamic_cast(v); if (!(k && k->_IsToolBar)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible // v->Visible(FALSE); } } } for (int i=0; iMsg()) { case WM_DPICHANGED: { d->Dpi.x = HIWORD(Msg->A()); d->Dpi.y = LOWORD(Msg->A()); OnPosChange(); break; } case M_ASSERT_UI: { LAutoPtr Str((LString*)Msg->A()); extern void LAssertDlg(LString Msg, std::function Callback); if (Str) LAssertDlg(Str ? *Str : "Error: no msg.", NULL); break; } case M_SET_WINDOW_PLACEMENT: { /* Apparently if you use SetWindowPlacement inside the WM_CREATE handler, then the restored rect doesn't "stick", it gets stomped on by windows. So this code... RESETS it to be what we set earlier. Windows sucks. */ if (!d->Wp || !_View) break; LRect r = d->Wp->rcNormalPosition; if (!LView::Visible()) d->Wp->showCmd = SW_HIDE; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, r.GetStr(), d->Wp->showCmd); #endif SetWindowPlacement(_View, d->Wp); d->Wp.Reset(); break; } case WM_SYSCOLORCHANGE: { LColour::OnChange(); break; } case WM_WINDOWPOSCHANGING: { bool Icon = IsIconic(Handle()) != 0; bool Zoom = IsZoomed(Handle()) != 0; if (!Icon && (_Dialog || !Zoom)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (!Info) break; if (Info->flags == (SWP_NOSIZE | SWP_NOMOVE) && _Dialog) { // Info->flags |= SWP_NOZORDER; Info->hwndInsertAfter = _Dialog->Handle(); } if (GetMinimumSize().x && GetMinimumSize().x > Info->cx) { Info->cx = GetMinimumSize().x; } if (GetMinimumSize().y && GetMinimumSize().y > Info->cy) { Info->cy = GetMinimumSize().y; } /* This is broken on windows 10... windows get stuck on the edge of the desktop. RECT Rc; if (d->SnapToEdge && SystemParametersInfo(SPI_GETWORKAREA, 0, &Rc, SPIF_SENDCHANGE)) { LRect r = Rc; LRect p(Info->x, Info->y, Info->x + Info->cx - 1, Info->y + Info->cy - 1); if (r.Valid() && p.Valid()) { int Snap = 12; if (abs(p.x1 - r.x1) <= Snap) { // Snap left edge Info->x = r.x1; } else if (abs(p.x2 - r.x2) <= Snap) { // Snap right edge Info->x = r.x2 - Info->cx + 1; } if (abs(p.y1 - r.y1) <= Snap) { // Snap top edge Info->y = r.y1; } else if (abs(p.y2 - r.y2) <= Snap) { // Snap bottom edge Info->y = r.y2 - Info->cy + 1; } } } */ } break; } case WM_SIZE: { if (Visible()) { LWindowZoom z = d->Show; switch (Msg->a) { case SIZE_MINIMIZED: { z = LZoomMin; break; } case SIZE_MAXIMIZED: { z = LZoomMax; break; } case SIZE_RESTORED: { z = LZoomNormal; break; } } if (z != d->Show) { OnZoom(d->Show = z); } } Status = LView::OnEvent(Msg) != 0; break; } case WM_CREATE: { if (d->AlwaysOnTop) SetAlwaysOnTop(true); PourAll(); OnCreate(); if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } d->InCreate = false; if (d->Wp) { PostEvent(M_SET_WINDOW_PLACEMENT); } break; } case WM_WINDOWPOSCHANGED: { d->Wp.Reset(); Status = LView::OnEvent(Msg) != 0; break; } case WM_QUERYENDSESSION: case WM_CLOSE: { bool QuitApp; bool OsShuttingDown = Msg->Msg() == WM_QUERYENDSESSION; if (QuitApp = OnRequestClose(OsShuttingDown)) { Quit(); } if (Msg->Msg() == WM_CLOSE) { return 0; } else { return QuitApp; } break; } case WM_SYSCOMMAND: { if (Msg->a == SC_CLOSE) { if (OnRequestClose(false)) { Quit(); } return 0; } else { Status = LView::OnEvent(Msg) != 0; } break; } case WM_DROPFILES: { HDROP hDrop = (HDROP) Msg->a; if (hDrop) { LArray FileNames; int Count = 0; Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } OnReceiveFiles(FileNames); FileNames.DeleteArrays(); } break; } case M_HANDLEMOUSEMOVE: { // This receives events fired from the LMouseHookPrivate class so that // non-LGI windows create mouse hook events as well. LTempView v((OsView)Msg->B()); LMouse m; m.x = LOWORD(Msg->A()); m.y = HIWORD(Msg->A()); HandleViewMouse(&v, m); break; } case M_COMMAND: { HWND OurWnd = Handle(); // copy onto the stack, because // we might lose the 'this' object in the // OnCommand handler which would delete // the memory containing the handle. Status = OnCommand((int) Msg->a, 0, (OsView) Msg->b); if (!IsWindow(OurWnd)) { // The window was deleted so break out now break; } // otherwise fall thru to the LView handler } default: { Status = (int) LView::OnEvent(Msg); break; } } return Status; } LPoint LWindow::GetDpi() { if (!d->Dpi.x) d->Dpi = LGetDpiForWindow(_View); return d->Dpi; } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); LPointF r( Dpi.x / 96.0, Dpi.y / 96.0 ); return r; } LRect &LWindow::GetPos() { if (_View && IsZoomed(_View)) { static LRect r; RECT rc; GetWindowRect(_View, &rc); r = rc; return r; } return Pos; } void LWindow::OnPosChange() { PourAll(); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { auto i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { #if WINNATIVE LButton *Btn; if (Btn = dynamic_cast(_Default)) Btn->Default(false); #endif _Default = v; #if WINNATIVE if (Btn = dynamic_cast(_Default)) Btn->Default(true); #endif } bool LWindow::UnregisterHook(LView *Target) { auto i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; #if DEBUG_SERIALIZE_STATE LgiTrace("LWindow::SerializeState(%p, %s, %i)\n", Store, FieldName, Load); #endif if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i v=%s\n", __LINE__, v.Str()); #endif for (auto Var: v.LStr().SplitDelimit(";")) { auto v = Var.SplitDelimit("=", 1); if (v.Length() == 2) { if (v[0].Equals("State")) State = (LWindowZoom)v[1].Int(); else if (v[0].Equals("Pos")) { LRect r; r.SetStr(v[1]); if (r.Valid()) Position = r; } } else return false; } #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i State=%i, Pos=%s\n", __LINE__, State, Position.GetStr()); #endif // Apply any shortcut override int Show = LAppInst->GetShow(); if (Show == SW_SHOWMINIMIZED || Show == SW_SHOWMINNOACTIVE || Show == SW_MINIMIZE) { State = LZoomMin; } else if (Show == SW_SHOWMAXIMIZED || Show == SW_MAXIMIZE) { State = LZoomMax; } LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(WINDOWPLACEMENT); if (Visible()) { if (State == LZoomMax) { Wp->showCmd = SW_SHOWMAXIMIZED; } else if (State == LZoomMin) { Wp->showCmd = SW_MINIMIZE; } else { Wp->showCmd = d->ShowCmd; } } else { Wp->showCmd = SW_HIDE; d->Show = State; } LRect DefaultPos(100, 100, 900, 700); if (Position.Valid()) { LArray Displays; LRect AllDisplays; bool PosOk = true; if (LGetDisplays(Displays, &AllDisplays)) { // Check that the position is on one of the screens PosOk = false; for (unsigned i=0; ir; Int.Intersection(&Position); if (Int.Valid() && Int.X() > 20 && Int.Y() > 20) { PosOk = true; break; } } Displays.DeleteObjects(); } if (PosOk) Pos = Position; else Pos = DefaultPos; } else Pos = DefaultPos; Wp->rcNormalPosition = Pos; #if DEBUG_SERIALIZE_STATE LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", _FL, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(Handle(), Wp); if (d->InCreate) d->Wp = Wp; } } else return false; } else { char s[256]; LWindowZoom State = GetZoom(); LRect Position; if (Handle()) { WINDOWPLACEMENT Wp; ZeroObj(Wp); Wp.length = sizeof(Wp); GetWindowPlacement(Handle(), &Wp); Position = Wp.rcNormalPosition; } else { // A reasonable fall back if we don't have a window... Position = GetPos(); } sprintf_s(s, sizeof(s), "State=%i;Pos=%s", State, Position.GetStr()); #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i s='%s'\n", __LINE__, s); #endif LVariant v = s; if (!Store->SetValue(FieldName, v)) return false; } return true; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } }