diff --git a/ResourceEditor/mac/LgiRes.xcodeproj/project.pbxproj b/ResourceEditor/mac/LgiRes.xcodeproj/project.pbxproj --- a/ResourceEditor/mac/LgiRes.xcodeproj/project.pbxproj +++ b/ResourceEditor/mac/LgiRes.xcodeproj/project.pbxproj @@ -1,665 +1,665 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 48; objects = { /* Begin PBXBuildFile section */ 343FB8952386621600797ABC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 343FB8942386621600797ABC /* Assets.xcassets */; }; 343FB8AB238662C500797ABC /* LgiMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343FB8AA238662C500797ABC /* LgiMain.cpp */; }; 343FB8C62386638C00797ABC /* LgiCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343FB8A92386627A00797ABC /* LgiCocoa.framework */; }; 343FB8C82386639C00797ABC /* LgiCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 343FB8A92386627A00797ABC /* LgiCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 343FB8DC2386642A00797ABC /* OptionsFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343FB8DB2386642A00797ABC /* OptionsFile.cpp */; }; 347A9B582970B8C200D57A0B /* libpng16.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 347A9B552970B8BD00D57A0B /* libpng16.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34CC002823AAD2A100CCAAEA /* Search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001B23AAD2A100CCAAEA /* Search.cpp */; }; 34CC002923AAD2A100CCAAEA /* LgiRes_ControlTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */; }; 34CC002A23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */; }; 34CC002B23AAD2A100CCAAEA /* LgiRes_Menu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */; }; 34CC002C23AAD2A100CCAAEA /* LgiRes_Css.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */; }; 34CC002D23AAD2A100CCAAEA /* LgiResApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */; }; 34CC002E23AAD2A100CCAAEA /* ShowLanguages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */; }; 34CC002F23AAD2A100CCAAEA /* LgiRes_Dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */; }; 34CC003023AAD2A100CCAAEA /* LgiRes_String.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */; }; 34CC003223AAD2C100CCAAEA /* StatusBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */; }; 34CC003423AAD2E700CCAAEA /* Mru.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003323AAD2E700CCAAEA /* Mru.cpp */; }; 34CC003723AAD2FE00CCAAEA /* DocApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */; }; 34CC003823AAD2FE00CCAAEA /* About.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003623AAD2FE00CCAAEA /* About.cpp */; }; 34CC003C23AAD33500CCAAEA /* mac-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 34CC003A23AAD33500CCAAEA /* mac-icon.icns */; }; 34CC003D23AAD33500CCAAEA /* lgires.lr8 in Resources */ = {isa = PBXBuildFile; fileRef = 34CC003B23AAD33500CCAAEA /* lgires.lr8 */; }; 34CC003F23AAD34600CCAAEA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34CC003E23AAD34600CCAAEA /* Cocoa.framework */; }; 34CC004223AAD54200CCAAEA /* Png.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC004123AAD54200CCAAEA /* Png.cpp */; }; 34CC006623AAD5DB00CCAAEA /* _StringIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */; }; 34CC006723AAD5DB00CCAAEA /* _DialogIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */; }; 34CC006823AAD5DB00CCAAEA /* _MenuIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */; }; 34CC006923AAD5DB00CCAAEA /* _Icons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006523AAD5DB00CCAAEA /* _Icons.gif */; }; 34CC006C23AAD5FC00CCAAEA /* Gif.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */; }; 34CC006D23AAD5FC00CCAAEA /* Lzw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 343FB8A82386627A00797ABC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C2681CBF020F0028B84B; remoteInfo = LgiCocoa; }; 343FB8C32386638800797ABC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C2671CBF020F0028B84B; remoteInfo = LgiCocoa; }; 347A9B542970B8BD00D57A0B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 9D1B005B4C804977BD5DC316; remoteInfo = libpng16; }; 347A9B562970B8BD00D57A0B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 5BBB106F0CF040E99654EA52; remoteInfo = libpng16_static; }; 34CC005123AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = E5FD78A808A24711B59ED1F2; remoteInfo = example; }; 34CC005723AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 787EECA18CBC4D4FB4E389EA; remoteInfo = minigzip; }; 34CC005923AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 480BA9D67D004C9FB7A41391; remoteInfo = pngtest; }; 34CC005B23AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = C246F8AB721C4E3383C137C4; remoteInfo = zlib; }; 34CC005D23AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = FE658531E3604B19A40B327E; remoteInfo = zlib_static; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 343FB8C72386639100797ABC /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 347A9B582970B8C200D57A0B /* libpng16.dylib in CopyFiles */, 343FB8C82386639C00797ABC /* LgiCocoa.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 343FB88E2386621400797ABC /* LgiRes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LgiRes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 343FB8942386621600797ABC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 343FB8992386621600797ABC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 343FB89C2386621600797ABC /* LgiRes.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LgiRes.entitlements; sourceTree = ""; }; 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LgiCocoa.xcodeproj; path = ../../src/mac/cocoa/LgiCocoa.xcodeproj; sourceTree = ""; }; 343FB8AA238662C500797ABC /* LgiMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiMain.cpp; path = ../../src/common/Lgi/LgiMain.cpp; sourceTree = ""; }; 343FB8DB2386642A00797ABC /* OptionsFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptionsFile.cpp; path = ../../src/common/Lgi/OptionsFile.cpp; sourceTree = ""; }; 34CC001B23AAD2A100CCAAEA /* Search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Search.cpp; path = ../src/Search.cpp; sourceTree = ""; }; 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_ControlTree.cpp; path = ../src/LgiRes_ControlTree.cpp; sourceTree = ""; }; 34CC001D23AAD2A100CCAAEA /* LgiRes_Dialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_Dialog.h; path = ../src/LgiRes_Dialog.h; sourceTree = ""; }; 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_TableLayout.cpp; path = ../src/LgiRes_TableLayout.cpp; sourceTree = ""; }; 34CC001F23AAD2A100CCAAEA /* LgiResEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiResEdit.h; path = ../src/LgiResEdit.h; sourceTree = ""; }; 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Menu.cpp; path = ../src/LgiRes_Menu.cpp; sourceTree = ""; }; 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Css.cpp; path = ../src/LgiRes_Css.cpp; sourceTree = ""; }; 34CC002223AAD2A100CCAAEA /* LgiRes_String.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_String.h; path = ../src/LgiRes_String.h; sourceTree = ""; }; 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiResApp.cpp; path = ../src/LgiResApp.cpp; sourceTree = ""; }; 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ShowLanguages.cpp; path = ../src/ShowLanguages.cpp; sourceTree = ""; }; 34CC002523AAD2A100CCAAEA /* LgiRes_Menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_Menu.h; path = ../src/LgiRes_Menu.h; sourceTree = ""; }; 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Dialog.cpp; path = ../src/LgiRes_Dialog.cpp; sourceTree = ""; }; 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_String.cpp; path = ../src/LgiRes_String.cpp; sourceTree = ""; }; 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StatusBar.cpp; path = ../../src/common/Widgets/StatusBar.cpp; sourceTree = ""; }; 34CC003323AAD2E700CCAAEA /* Mru.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mru.cpp; path = ../../src/common/Lgi/Mru.cpp; sourceTree = ""; }; 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DocApp.cpp; path = ../../src/common/Lgi/DocApp.cpp; sourceTree = ""; }; 34CC003623AAD2FE00CCAAEA /* About.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = About.cpp; path = ../../src/common/Lgi/About.cpp; sourceTree = ""; }; 34CC003A23AAD33500CCAAEA /* mac-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = "mac-icon.icns"; path = "../Resources/mac-icon.icns"; sourceTree = ""; }; 34CC003B23AAD33500CCAAEA /* lgires.lr8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = lgires.lr8; path = ../Resources/lgires.lr8; sourceTree = ""; }; 34CC003E23AAD34600CCAAEA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 34CC004123AAD54200CCAAEA /* Png.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Png.cpp; path = ../../src/common/Gdc2/Filters/Png.cpp; sourceTree = ""; }; 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libpng.xcodeproj; path = ../../../../../CodeLib/libpng/build/libpng.xcodeproj; sourceTree = ""; }; - 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _StringIcons.gif; path = ../src/_StringIcons.gif; sourceTree = ""; }; - 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _DialogIcons.gif; path = ../src/_DialogIcons.gif; sourceTree = ""; }; - 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _MenuIcons.gif; path = ../src/_MenuIcons.gif; sourceTree = ""; }; - 34CC006523AAD5DB00CCAAEA /* _Icons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _Icons.gif; path = ../src/_Icons.gif; sourceTree = ""; }; + 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _StringIcons.gif; path = ../resources/_StringIcons.gif; sourceTree = ""; }; + 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _DialogIcons.gif; path = ../resources/_DialogIcons.gif; sourceTree = ""; }; + 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _MenuIcons.gif; path = ../resources/_MenuIcons.gif; sourceTree = ""; }; + 34CC006523AAD5DB00CCAAEA /* _Icons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _Icons.gif; path = ../resources/_Icons.gif; sourceTree = ""; }; 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Gif.cpp; path = ../../src/common/Gdc2/Filters/Gif.cpp; sourceTree = ""; }; 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Lzw.cpp; path = ../../src/common/Gdc2/Filters/Lzw.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 343FB88B2386621400797ABC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 34CC003F23AAD34600CCAAEA /* Cocoa.framework in Frameworks */, 343FB8C62386638C00797ABC /* LgiCocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 343FB8852386621400797ABC = { isa = PBXGroup; children = ( 343FB8A22386625100797ABC /* Cocoa */, 343FB8B9238662DB00797ABC /* Code */, 343FB8C52386638C00797ABC /* Frameworks */, 343FB8A32386626700797ABC /* Lgi */, 343FB88F2386621400797ABC /* Products */, 34CC003923AAD32200CCAAEA /* Resources */, ); sourceTree = ""; }; 343FB88F2386621400797ABC /* Products */ = { isa = PBXGroup; children = ( 343FB88E2386621400797ABC /* LgiRes.app */, ); name = Products; sourceTree = ""; }; 343FB8A22386625100797ABC /* Cocoa */ = { isa = PBXGroup; children = ( 343FB8942386621600797ABC /* Assets.xcassets */, 343FB8992386621600797ABC /* Info.plist */, 343FB89C2386621600797ABC /* LgiRes.entitlements */, ); name = Cocoa; sourceTree = ""; }; 343FB8A32386626700797ABC /* Lgi */ = { isa = PBXGroup; children = ( 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */, 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */, 34CC003623AAD2FE00CCAAEA /* About.cpp */, 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */, 34CC003323AAD2E700CCAAEA /* Mru.cpp */, 343FB8DB2386642A00797ABC /* OptionsFile.cpp */, 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */, 343FB8AA238662C500797ABC /* LgiMain.cpp */, 34CC004123AAD54200CCAAEA /* Png.cpp */, 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */, 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */, ); name = Lgi; sourceTree = ""; }; 343FB8A52386627A00797ABC /* Products */ = { isa = PBXGroup; children = ( 343FB8A92386627A00797ABC /* LgiCocoa.framework */, ); name = Products; sourceTree = ""; }; 343FB8B9238662DB00797ABC /* Code */ = { isa = PBXGroup; children = ( 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */, 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */, 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */, 34CC001D23AAD2A100CCAAEA /* LgiRes_Dialog.h */, 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */, 34CC002523AAD2A100CCAAEA /* LgiRes_Menu.h */, 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */, 34CC002223AAD2A100CCAAEA /* LgiRes_String.h */, 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */, 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */, 34CC001F23AAD2A100CCAAEA /* LgiResEdit.h */, 34CC001B23AAD2A100CCAAEA /* Search.cpp */, 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */, ); name = Code; sourceTree = ""; }; 343FB8C52386638C00797ABC /* Frameworks */ = { isa = PBXGroup; children = ( 34CC003E23AAD34600CCAAEA /* Cocoa.framework */, ); name = Frameworks; sourceTree = ""; }; 34CC003923AAD32200CCAAEA /* Resources */ = { isa = PBXGroup; children = ( 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */, 34CC006523AAD5DB00CCAAEA /* _Icons.gif */, 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */, 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */, 34CC003B23AAD33500CCAAEA /* lgires.lr8 */, 34CC003A23AAD33500CCAAEA /* mac-icon.icns */, ); name = Resources; sourceTree = ""; }; 34CC004423AAD57F00CCAAEA /* Products */ = { isa = PBXGroup; children = ( 34CC005223AAD57F00CCAAEA /* example */, 347A9B552970B8BD00D57A0B /* libpng16.dylib */, 347A9B572970B8BD00D57A0B /* libpng16_static.a */, 34CC005823AAD57F00CCAAEA /* minigzip */, 34CC005A23AAD57F00CCAAEA /* pngtest */, 34CC005C23AAD57F00CCAAEA /* libz_local.dylib */, 34CC005E23AAD57F00CCAAEA /* libzlib_static.a */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 343FB88D2386621400797ABC /* LgiRes */ = { isa = PBXNativeTarget; buildConfigurationList = 343FB89F2386621600797ABC /* Build configuration list for PBXNativeTarget "LgiRes" */; buildPhases = ( 343FB88A2386621400797ABC /* Sources */, 343FB88B2386621400797ABC /* Frameworks */, 343FB88C2386621400797ABC /* Resources */, 343FB8C72386639100797ABC /* CopyFiles */, ); buildRules = ( ); dependencies = ( 343FB8C42386638800797ABC /* PBXTargetDependency */, ); name = LgiRes; productName = LgiRes; productReference = 343FB88E2386621400797ABC /* LgiRes.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 343FB8862386621400797ABC /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1420; ORGANIZATIONNAME = Memecode; TargetAttributes = { 343FB88D2386621400797ABC = { CreatedOnToolsVersion = 10.1; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 343FB8892386621400797ABC /* Build configuration list for PBXProject "LgiRes" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 343FB8852386621400797ABC; productRefGroup = 343FB88F2386621400797ABC /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = 343FB8A52386627A00797ABC /* Products */; ProjectRef = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; }, { ProductGroup = 34CC004423AAD57F00CCAAEA /* Products */; ProjectRef = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; }, ); projectRoot = ""; targets = ( 343FB88D2386621400797ABC /* LgiRes */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 343FB8A92386627A00797ABC /* LgiCocoa.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LgiCocoa.framework; remoteRef = 343FB8A82386627A00797ABC /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 347A9B552970B8BD00D57A0B /* libpng16.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; name = libpng16.dylib; path = libpng16.15.29.0.dylib; remoteRef = 347A9B542970B8BD00D57A0B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 347A9B572970B8BD00D57A0B /* libpng16_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libpng16_static.a; remoteRef = 347A9B562970B8BD00D57A0B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005223AAD57F00CCAAEA /* example */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = example; remoteRef = 34CC005123AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005823AAD57F00CCAAEA /* minigzip */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = minigzip; remoteRef = 34CC005723AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005A23AAD57F00CCAAEA /* pngtest */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = pngtest; remoteRef = 34CC005923AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005C23AAD57F00CCAAEA /* libz_local.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; name = libz_local.dylib; path = libz_local.1.2.5.dylib; remoteRef = 34CC005B23AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005E23AAD57F00CCAAEA /* libzlib_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libzlib_static.a; remoteRef = 34CC005D23AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 343FB88C2386621400797ABC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 343FB8952386621600797ABC /* Assets.xcassets in Resources */, 34CC003C23AAD33500CCAAEA /* mac-icon.icns in Resources */, 34CC006623AAD5DB00CCAAEA /* _StringIcons.gif in Resources */, 34CC006723AAD5DB00CCAAEA /* _DialogIcons.gif in Resources */, 34CC006823AAD5DB00CCAAEA /* _MenuIcons.gif in Resources */, 34CC006923AAD5DB00CCAAEA /* _Icons.gif in Resources */, 34CC003D23AAD33500CCAAEA /* lgires.lr8 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 343FB88A2386621400797ABC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 34CC002B23AAD2A100CCAAEA /* LgiRes_Menu.cpp in Sources */, 34CC002C23AAD2A100CCAAEA /* LgiRes_Css.cpp in Sources */, 343FB8DC2386642A00797ABC /* OptionsFile.cpp in Sources */, 34CC002F23AAD2A100CCAAEA /* LgiRes_Dialog.cpp in Sources */, 34CC003723AAD2FE00CCAAEA /* DocApp.cpp in Sources */, 34CC003823AAD2FE00CCAAEA /* About.cpp in Sources */, 34CC002E23AAD2A100CCAAEA /* ShowLanguages.cpp in Sources */, 34CC002923AAD2A100CCAAEA /* LgiRes_ControlTree.cpp in Sources */, 34CC003023AAD2A100CCAAEA /* LgiRes_String.cpp in Sources */, 34CC006D23AAD5FC00CCAAEA /* Lzw.cpp in Sources */, 34CC002823AAD2A100CCAAEA /* Search.cpp in Sources */, 34CC006C23AAD5FC00CCAAEA /* Gif.cpp in Sources */, 34CC002D23AAD2A100CCAAEA /* LgiResApp.cpp in Sources */, 34CC003423AAD2E700CCAAEA /* Mru.cpp in Sources */, 34CC002A23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp in Sources */, 34CC004223AAD54200CCAAEA /* Png.cpp in Sources */, 343FB8AB238662C500797ABC /* LgiMain.cpp in Sources */, 34CC003223AAD2C100CCAAEA /* StatusBar.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 343FB8C42386638800797ABC /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LgiCocoa; targetProxy = 343FB8C32386638800797ABC /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 343FB89D2386621600797ABC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 343FB89E2386621600797ABC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; }; name = Release; }; 343FB8A02386621600797ABC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AV9WN2LD8; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, - ../Code, - ../Resources, + ../src, + ../resources, ../../../../../CodeLib/libpng/build, ../../../../../CodeLib/libpng, ); INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = ( "-DMAC", "-DLGI_COCOA", "-D_DEBUG", "-Wno-nullability-completeness", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.LgiRes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 343FB8A12386621600797ABC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AV9WN2LD8; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, - ../Code, - ../Resources, + ../src, + ../resources, ../../../../../CodeLib/libpng/build, ../../../../../CodeLib/libpng, ); INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_CFLAGS = ( "-DMAC", "-DLGI_COCOA", "-Wno-nullability-completeness", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.LgiRes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 343FB8892386621400797ABC /* Build configuration list for PBXProject "LgiRes" */ = { isa = XCConfigurationList; buildConfigurations = ( 343FB89D2386621600797ABC /* Debug */, 343FB89E2386621600797ABC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 343FB89F2386621600797ABC /* Build configuration list for PBXNativeTarget "LgiRes" */ = { isa = XCConfigurationList; buildConfigurations = ( 343FB8A02386621600797ABC /* Debug */, 343FB8A12386621600797ABC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 343FB8862386621400797ABC /* Project object */; } diff --git a/ResourceEditor/src/LgiResApp.cpp b/ResourceEditor/src/LgiResApp.cpp --- a/ResourceEditor/src/LgiResApp.cpp +++ b/ResourceEditor/src/LgiResApp.cpp @@ -1,4730 +1,4725 @@ /* ** 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); LAssert(f); if (f) { Images = LLoadImageList(f, 16, 16); LAssert(Images); 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) { 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) { 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 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/Search.cpp b/ResourceEditor/src/Search.cpp --- a/ResourceEditor/src/Search.cpp +++ b/ResourceEditor/src/Search.cpp @@ -1,792 +1,786 @@ #include #include "lgi/common/Lgi.h" #include "LgiResEdit.h" #include "resdefs.h" #include "lgi/common/Combo.h" #include "LgiRes_Dialog.h" #include "LgiRes_Menu.h" #define SetCtrlFocus(id) { LViewI *v = FindControl(id); if (v) v->Focus(true); } enum Msgs { M_SEARCH = M_USER + 100, }; class Result : public LListItem { public: AppWnd *App; ResStringGroup *Grp; ResMenuItem *Menu; ResDialog *Dialog; ResDialogCtrl *Ctrl; ResString *Str; LLanguageId Lang; Result(AppWnd *app, ResString *s, LLanguageId l = 0) { App = app; Grp = 0; Menu = 0; Dialog = 0; Ctrl = 0; Str = s; Lang = l; } const char *GetText(int i) { switch (i) { case 0: { if (Menu) return "MenuItem"; else if (Dialog) return "Control"; return "String"; break; } case 1: { return Str ? Str->GetDefine() : 0; break; } case 2: { if (Str) { return Str->Get(Lang); } break; } case 3: { return Lang; break; } } return 0; } void OnMouseClick(LMouse &m) { if (App && m.Left() && m.Down()) App->GotoObject(Str, Grp, Dialog, Menu, Ctrl); } }; bool SearchThread::HasContent(char *s) { while (s && *s) { if (((*s & 0x80) != 0) || isalpha(*s)) { return true; } s++; } return false; } Result *SearchThread::Test(ResString *s) { if (!s) return NULL; if (Params.LimitToDefine && !Params.LimitToText && !Params.InLang) { if (s->GetDefine()) { size_t matches = 0; for (auto &t: Params.Text) if (stristr(s->GetDefine(), t)) matches++; if (Params.Text.Length() == matches) return new Result(App, s); } } if (Params.LimitToDefine) return NULL; if (Params.InLang) { char *Txt = s->Get(Params.InLang); size_t matches = 0; for (auto &t: Params.Text) if (Txt && stristr(Txt, t)) matches++; if (Params.Text.Length() == matches) return new Result(App, s, Params.InLang); } else { for (auto item: s->Items) { size_t matches = 0; for (auto &t: Params.Text) if (item->GetStr() && stristr(item->GetStr(), t)) matches++; if (Params.Text.Length() == matches) return new Result(App, s, item->GetLang()); } } if (Params.LimitToText) return NULL; if (Params.NotInLang) { if (s->Items.Length() > 0 && !s->Get(Params.NotInLang) && HasContent(s->Get("en"))) return new Result(App, s, Params.NotInLang); } if (Params.Ref != INVALID_INT) { if (s->GetRef() == Params.Ref) return new Result(App, s); } if (Params.CtrlId != INVALID_INT) { if (s->GetId() == Params.CtrlId) return new Result(App, s); } return NULL; } Result *SearchThread::Test(ResMenuItem *mi) { Result *r = Test(mi->GetStr()); if (r) return r; if (mi->Shortcut()) { size_t matches = 0; for (auto &t: Params.Text) { if (stristr(mi->Shortcut(), t)) matches++; } if (Params.Text.Length() == matches) if ((r = new Result(App, mi->GetStr(), Params.InLang))) { r->Menu = mi; return r; } } return NULL; } SearchThread::SearchThread(AppWnd *app, LList *results) : App(app), Results(results), LEventTargetThread("SearchThread") { App->ListObjects(Res); Run(); } void SearchThread::Search(SearchParams &p) { State.Cancel(true); PostEvent(M_SEARCH, (LMessage::Param)new SearchParams(p)); } LMessage::Result SearchThread::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SEARCH: { LAutoPtr p; if (ReceiveA(p, Msg)) Params = *p; else return 0; break; } default: return 0; } State.Cancel(false); Results->Empty(); for (auto r: Res) { if (State.IsCancelled() || Results->Length() > 100) break; if (r->IsStringGroup()) { List::I it = r->IsStringGroup()->GetStrs()->begin(); for (ResString *s = *it; s && !State.IsCancelled(); s = *++it) { Result *Res = Test(s); if (Res) { Res->Grp = r->IsStringGroup(); Results->Insert(Res); } } } else if (r->IsDialog()) { List Ctrls; r->IsDialog()->EnumCtrls(Ctrls); for (auto c: Ctrls) { if (State.IsCancelled()) break; Result *Res = Test(c->GetStr()); if (Res) { Res->Ctrl = c; Res->Dialog = r->IsDialog(); Results->Insert(Res); } } } else if (r->IsMenu()) { LArray Items; r->IsMenu()->EnumItems(Items); for (auto c: Items) { if (State.IsCancelled()) break; Result *Res = Test(c); if (Res) { Res->Menu = c; Results->Insert(Res); } } } } return 1; } //////////////////////////////////////////////////////////////////////////////////// // Search window void FillLangs(LViewI *v, int id, List &l) { LCombo *c; if (!v->GetViewById(id, c)) return; for (auto li: l) c->Insert(li->Name); } Search::Search(AppWnd *app) { SetParent(App = app); List l; List Res; if (App->ListObjects(Res)) { for (auto r: Res) { ResStringGroup *g = r->IsStringGroup(); if (!g) continue; for (int i=0; iGetLanguages(); i++) { LLanguage *Lang = g->GetLanguage(i); if (!Lang) continue; bool Has = false; for (auto i: l) { if (i == Lang) { Has = true; break; } } if (!Has) l.Insert(Lang); } } } #if NEW_UI if (LoadFromResource(IDD_FIND)) #else if (LoadFromResource(IDD_SEARCH)) #endif { MoveSameScreen(App); FillLangs(this, IDC_LANG, l); FillLangs(this, IDC_NOT_LANG, l); #if NEW_UI LList *lst; if (GetViewById(IDC_RESULTS, lst)) { lst->AddColumn("Object", 60); lst->AddColumn("Define", 120); lst->AddColumn("Text", 300); lst->AddColumn("Language", 60); Thread.Reset(new SearchThread(App, lst)); } else LAssert(0); #endif OnCheck(); SetCtrlFocus(IDC_TEXT); } } void Search::OnCheck() { SetCtrlEnabled(IDC_LANG, GetCtrlValue(IDC_IN_LANG)); SetCtrlEnabled(IDC_NOT_LANG, GetCtrlValue(IDC_NOT_IN_LANG)); #if !NEW_UI SetCtrlEnabled(IDC_DEFINE, GetCtrlValue(IDC_IN_DEFINES)); SetCtrlEnabled(IDC_TEXT, GetCtrlValue(IDC_IN_TEXT)); SetCtrlEnabled(IDC_REF, GetCtrlValue(IDC_IN_REF)); SetCtrlEnabled(IDC_CTRL, GetCtrlValue(IDC_IN_CTRL)); #endif } int Search::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { #if NEW_UI case IDC_TEXT: { if (!Thread) break; Text = LString(c->Name()).SplitDelimit(); Thread->Search(*this); break; } case IDC_IN_TEXT: { if (!Thread) break; LimitToText = c->Value() != 0; Thread->Search(*this); break; } case IDC_IN_DEFINES: { if (!Thread) break; LimitToDefine = c->Value() != 0; Thread->Search(*this); break; } case IDC_IN_LANG: { SetCtrlFocus(IDC_LANG); OnCheck(); // fall thru } case IDC_LANG: { InLang = NULL; if (!Thread) break; if (GetCtrlValue(IDC_IN_LANG)) { auto FullName = GetCtrlName(IDC_LANG); LLanguage *l = LFindLang("", FullName); InLang = l ? l->Id : NULL; } Thread->Search(*this); break; } case IDC_NOT_IN_LANG: { SetCtrlFocus(IDC_NOT_LANG); OnCheck(); // fall thru } case IDC_NOT_LANG: { NotInLang = NULL; if (!Thread) break; if (GetCtrlValue(IDC_NOT_IN_LANG)) { LLanguage *l = LFindLang("", GetCtrlName(IDC_NOT_LANG)); NotInLang = l ? l->Id : NULL; } Thread->Search(*this); break; } case IDC_REF: { Ref = (int) c->Value(); Thread->Search(*this); break; } case IDC_CTRL: { Ref = (int) c->Value(); Thread->Search(*this); break; } #else case IDC_IN_TEXT: case IDC_IN_DEFINES: case IDC_IN_REF: case IDC_IN_CTRL: case IDC_IN_LANG: case IDC_NOT_IN_LANG: { OnCheck(); switch (c->GetId()) { case IDC_IN_TEXT: SetCtrlFocus(IDC_TEXT); break; case IDC_IN_DEFINES: SetCtrlFocus(IDC_DEFINE); break; case IDC_IN_REF: SetCtrlFocus(IDC_REF); break; case IDC_IN_CTRL: SetCtrlFocus(IDC_CTRL); break; case IDC_IN_LANG: SetCtrlFocus(IDC_LANG); break; case IDC_NOT_IN_LANG: SetCtrlFocus(IDC_NOT_LANG); break; } break; } case IDOK: { if (GetCtrlValue(IDC_IN_TEXT)) { char *t = GetCtrlName(IDC_TEXT); if (ValidStr(t)) Text = NewStr(t); } if (GetCtrlValue(IDC_IN_DEFINES)) { char *t = GetCtrlName(IDC_DEFINE); if (ValidStr(t)) Define = NewStr(t); } if (GetCtrlValue(IDC_IN_LANG)) { LLanguage *l = LFindLang(0, GetCtrlName(IDC_LANG)); if (l) InLang = l->Id; } if (GetCtrlValue(IDC_NOT_IN_LANG)) { LLanguage *l = LFindLang(0, GetCtrlName(IDC_NOT_LANG)); if (l) NotInLang = l->Id; } if (GetCtrlValue(IDC_IN_REF) && GetCtrlName(IDC_REF)) { Ref = GetCtrlValue(IDC_REF); } if (GetCtrlValue(IDC_IN_CTRL) && GetCtrlName(IDC_CTRL)) { CtrlId = GetCtrlValue(IDC_CTRL); } // fall thru } #endif case IDCANCEL: { EndModal(c->GetId() == IDOK); break; } } return 0; } //////////////////////////////////////////////////////////////////////////////////// // Results window class ResultsPrivate { public: AppWnd *App; char *Text; char *Define; LLanguageId InLang; LLanguageId NotInLang; int RefId; int CtrlId; bool Searching; bool Running; LList *Lst; bool HasContent(char *s) { while (s && *s) { if (((*s & 0x80) != 0) || isalpha(*s)) { return true; } s++; } return false; } Result *Test(ResString *s) { Result *r = 0; if (s) { if (ValidStr(Text)) { if (InLang) { char *Txt = s->Get(InLang); if (Txt && stristr(Txt, Text)) { r = new Result(App, s, InLang); } } else { for (auto t: s->Items) { if (t->GetStr() && stristr(t->GetStr(), Text)) { r = new Result(App, s, t->GetLang()); break; } } } } if (ValidStr(Define) && s->GetDefine()) { if (stristr(s->GetDefine(), Define)) { r = new Result(App, s); } } if (!r && NotInLang) { if (s->Items.Length() > 0 && !s->Get(NotInLang) && HasContent(s->Get("en"))) { r = new Result(App, s, NotInLang); } } if (!r && RefId) { if (s->GetRef() == RefId) { r = new Result(App, s); } } if (!r && CtrlId) { if (s->GetId() == CtrlId) { r = new Result(App, s); } } } return r; } Result *Test(ResMenuItem *mi) { Result *r = Test(mi->GetStr()); if (r) return r; if (ValidStr(Text) && mi->Shortcut()) { if (stristr(mi->Shortcut(), Text)) { if ((r = new Result(App, mi->GetStr(), InLang))) { r->Menu = mi; return r; } } } return NULL; } }; Results::Results(AppWnd *app, Search *params) { d = new ResultsPrivate; d->App = app; d->Text = LString(" ").Join(params->Text); #if !NEW_UI d->Define = NewStr(params->Define); #endif d->InLang = params->InLang; d->NotInLang = params->NotInLang; d->RefId = params->Ref; d->CtrlId = params->CtrlId; d->Searching = false; d->Running = false; d->Lst = 0; LResourceLoad r; LRect p; LAutoString n; if (r.LoadFromResource(IDD_RESULTS, this, &p, &n)) { SetPos(p); MoveToCenter(); if (n[0]) Name(n); if (Attach(0)) { AttachChildren(); Visible(true); GetViewById(IDC_LIST, d->Lst); List Res; d->App->ListObjects(Res); d->Searching = true; for (auto r: Res) { if (!d || !d->Searching) break; if (r->IsStringGroup()) { List::I it = r->IsStringGroup()->GetStrs()->begin(); for (ResString *s = *it; s && d && d->Searching; s = *++it) { Result *Res = d->Test(s); if (Res) { Res->Grp = r->IsStringGroup(); d->Lst->Insert(Res); } - - LYield(); } } else if (r->IsDialog()) { List Ctrls; r->IsDialog()->EnumCtrls(Ctrls); for (auto c: Ctrls) { if (!d || !d->Searching) break; Result *Res = d->Test(c->GetStr()); if (Res) { Res->Ctrl = c; Res->Dialog = r->IsDialog(); d->Lst->Insert(Res); } - - LYield(); } } else if (r->IsMenu()) { LArray Items; r->IsMenu()->EnumItems(Items); for (auto c: Items) { if (!d || !d->Searching) break; Result *Res = d->Test(c); if (Res) { Res->Menu = c; d->Lst->Insert(Res); } - - LYield(); } } } d->Searching = false; } } d->Running = true; } Results::~Results() { DeleteArray(d->Text); DeleteArray(d->Define); DeleteObj(d); } void Results::OnPosChange() { LRect Client = GetClient(); LViewI *v; if (GetViewById(IDC_LIST, v)) { LRect r = v->GetPos(); r.x2 = Client.x2 - 12; r.y2 = Client.y2 - 40; v->SetPos(r); v = FindControl(IDOK); if (v) { LRect p = v->GetPos(); p.Offset(Client.x2 - p.X() - 12 - p.x1, r.y2 + 10 - p.y1); v->SetPos(p); } } } int Results::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDOK: { if (d->Searching) { d->Searching = false; } else if (d->Running) { Quit(); } break; } } return 0; } diff --git a/src/common/Text/XmlTree.cpp b/src/common/Text/XmlTree.cpp --- a/src/common/Text/XmlTree.cpp +++ b/src/common/Text/XmlTree.cpp @@ -1,1663 +1,1663 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/XmlTree.h" #include "lgi/common/Variant.h" static char LXmlHeader[] = "\n"; ////////////////////////////////////////////////////////////////////////////// int _Pools = 0; int _Normals = 0; char *LXmlAlloc::Alloc(const char *s, ssize_t len) { if (!s) return NULL; if (len < 0) len = (int)strlen(s); ssize_t bytes = len + 1; char *p = (char*) Alloc(LGI_ALLOC_ALIGN(bytes)); if (!p) return 0; memcpy(p, s, len); p[len] = 0; return p; } class XmlPoolAlloc : public LXmlAlloc { struct Block { size_t Used, Len; char *Ptr; Block() { Used = Len = 0; Ptr = 0; } ~Block() { DeleteArray(Ptr); } bool Has(size_t Bytes) { return Used + Bytes <= Len; } }; LArray Mem; public: XmlPoolAlloc() { _Pools++; #ifndef LGI_STATIC // LgiTrace("%p::XmlPoolAlloc _Pools=%i, Refs=%i\n", (LXmlAlloc*)this, _Pools, _GetCount()); #endif } ~XmlPoolAlloc() { _Pools--; #ifndef LGI_STATIC // LStackTrace("%p::~XmlPoolAlloc _Pools=%i, Refs=%i\n", (LXmlAlloc*)this, _Pools, _GetCount()); #endif } void *Alloc(size_t Size) { Block *b = 0; int BlockSize = 32 << 10; if (Mem.Length() == 0 || !Mem[Mem.Length()-1].Has(Size)) { // New block b = &Mem.New(); b->Len = BlockSize; b->Used = 0; b->Ptr = new char[b->Len]; } else b = &Mem[Mem.Length()-1]; char *p = b->Ptr + b->Used; b->Used += Size; return p; } void Free(void *Ptr) { } }; class XmlNormalAlloc : public LXmlAlloc { public: XmlNormalAlloc() { _Normals++; #ifndef LGI_STATIC // LgiTrace("%p::XmlNormalAlloc _Normals=%i\n", (LXmlAlloc*)this, _Normals); #endif } ~XmlNormalAlloc() { _Normals--; #ifndef LGI_STATIC // LStackTrace("%p::~XmlNormalAlloc _Normals=%i\n", (LXmlAlloc*)this, _Normals); #endif } void *Alloc(size_t Size) { return new char[Size]; } void Free(void *Ptr) { delete [] ((char*)Ptr); } }; ////////////////////////////////////////////////////////////////////////////// class LXmlTreePrivate { public: LXmlFactory *Factory = NULL; LXmlTag *Current = NULL; LStreamI *File = NULL; LString Error; int Flags = 0; LHashTbl,char16> Entities; LHashTbl,bool> NoChildTags; LArray Buf; Progress *Prog = NULL; LString StyleFile; LString StyleType; bool NoDom() { return TestFlag(Flags, GXT_NO_DOM); } LXmlTreePrivate() { Entities.Add("lt", '<'); Entities.Add("gt", '>'); Entities.Add("amp", '&'); Entities.Add("quot", '\"'); Entities.Add("apos", '\''); } }; ////////////////////////////////////////////////////////////////////////////// const char *EncodeEntitiesAttr = "\'<>\"\n"; const char *EncodeEntitiesContent = "\'<>\""; char *LXmlTree::EncodeEntities(const char *s, ssize_t len, const char *extra_characters) { LStringPipe p; if (EncodeEntities(&p, s, len, extra_characters)) { return p.NewStr(); } return 0; } bool LXmlTree::EncodeEntities(LStreamI *to, const char *start, ssize_t len, const char *extra_characters) { if (!start || !to) return 0; int Amp = (d->Flags & GXT_NO_ENTITIES) ? 10000000 : '&'; const char *end = start + len; for (const char *s = start; s && *s;) { const char *e = s; while ( *e && *e != Amp && *e != '\r' && (!extra_characters || !strchr(extra_characters, *e)) && (len < 0 || e < end)) e++; if (e == end || *e) { to->Write(s, (int) (e - s)); if (e == end) break; switch (*e) { case '<': to->Write((char*)"<", 4); break; case '>': to->Write((char*)">", 4); break; case '&': to->Write((char*)"&", 5); break; case '\'': to->Write((char*)"'", 6); break; case '\"': to->Write((char*)""", 6); break; case '\r': // Do nothing... break; default: { char Ent[32]; sprintf_s(Ent, sizeof(Ent), "&#%i;", (uchar)*e); to->Write(Ent, strlen(Ent)); break; } } s = e + 1; } else { to->Write(s, strlen(s)); break; } } return true; } char *LXmlTree::DecodeEntities(LXmlAlloc *Alloc, char *In, ssize_t Len) { if (!In || !Alloc) { LAssert(!"Param error"); return NULL; } // char *OriginalIn = In; // Setup temporary buffer ssize_t BufSize = Len + 32; int BufR = BufSize & 0xff; if (BufR) BufSize += 256 - BufR; if (d->Buf.Length() < BufR) d->Buf.Length(BufR); char *Start = &d->Buf[0]; char *Out = Start; char *OutEnd = Out + d->Buf.Length(); char *InEnd = In + Len; while (*In && In < InEnd) { if (Out >= OutEnd - 4) { // Running out of space, extend the buffer. size_t Cur = Out - Start; d->Buf.Length(d->Buf.Length() + 256); Start = &d->Buf[0]; Out = Start + Cur; OutEnd = Start + d->Buf.Length(); } if (*In != '&') { *Out++ = *In++; continue; } In++; // Skip the '&' char16 c16 = 0; if (*In == '#') { In++; if (*In == 'x') c16 = htoi(++In); else c16 = atoi(In); In = strchr(In, ';'); if (In) { In++; } else { LAssert(0); } } else { ssize_t len; char *Col = strnchr(In, ';', 16); if (Col && (len = (Col - In)) < 16) { char tmp[16]; memcpy(tmp, In, len); tmp[len] = 0; c16 = d->Entities.Find(tmp); if (c16) In = Col + 1; } if (!c16) { // Not a real named entity, so just emit the ampersand. *Out++ = '&'; } } if (c16) { LAutoString c8(WideToUtf8(&c16, 1)); if (c8) { for (char *c = c8; *c; c++) *Out++ = *c; } } } return Alloc->Alloc(Start, Out - Start); } ////////////////////////////////////////////////////////////////////////////// LAutoRefPtr TagHeapAllocator(new XmlNormalAlloc); LXmlTag::LXmlTag(const char *tag, LXmlAlloc *alloc) { Children.SetFixedLength(true); if (alloc) Allocator = alloc; else Allocator = TagHeapAllocator; Write = false; Parent = NULL; Content = NULL; Tag = Allocator->Alloc(tag); } LXmlTag::LXmlTag(const LXmlTag &t) { Allocator = t.Allocator; LAssert(Allocator != NULL); Tag = NULL; Write = false; Parent = NULL; Content = NULL; Copy((LXmlTag&)t); } LXmlTag::~LXmlTag() { RemoveTag(); Empty(true); } void LXmlTag::EmptyAttributes() { for (auto &a: Attr) { Allocator->Free(a.Name); Allocator->Free(a.Value); } Attr.Length(0); } void LXmlTag::EmptyChildren() { LXmlTag *c; while (Children.Length() && (c = Children[0])) { LAssert(c->Parent == this); DeleteObj(c); } } void LXmlTag::Empty(bool Deep) { EmptyAttributes(); Allocator->Free(Content); SetTag(NULL); if (Deep) EmptyChildren(); } void LXmlTag::Swap(LXmlTag &t) { Allocator.Swap(t.Allocator); LSwap(Write, t.Write); LSwap(Tag, t.Tag); LSwap(Content, t.Content); // I think this is right... but IDK ssize_t AIdx = Parent ? Parent->Children.IndexOf(this) : -1; if (AIdx >= 0) Parent->Children[AIdx] = &t; ssize_t BIdx = t.Parent ? t.Parent->Children.IndexOf(&t) : -1; if (BIdx >= 0) t.Parent->Children[BIdx] = this; LSwap(Parent, t.Parent); Attr.Swap(t.Attr); Children.Swap(t.Children); for (auto c: Children) c->Parent = this; for (auto c: t.Children) c->Parent = &t; } bool LXmlTag::Copy(LXmlTag &t, bool Deep) { Empty(Deep); Allocator = t.Allocator; Content = NewStr(t.Content); Tag = Allocator->Alloc(t.Tag); Attr.Length(t.Attr.Length()); for (int i=0; iAlloc(a.Name); Attr[i].Value = Allocator->Alloc(a.Value); } if (Deep) { for (auto c: t.Children) { LXmlTag *n = new LXmlTag; if (n) { n->Copy(*c, Deep); InsertTag(n); } } } return true; } bool LXmlTag::XPath(LArray &Results, const char *Path) { return false; } LXmlTag &LXmlTag::operator =(LXmlTag &t) { Copy(t); return *this; } LXmlTag *LXmlTag::CreateTag(const char *Name, char *Content) { LXmlTag *c = GetChildTag(Name, true); if (c) c->Content = NewStr(Content); return c; } const char *LXmlTag::GetTag() { return Tag; } void LXmlTag::SetTag(const char *Str, ssize_t Len) { Allocator->Free(Tag); Tag = NULL; if (Str) { Tag = Allocator->Alloc(Str, Len); } } LXmlTag *LXmlTag::GetChildTag(const char *Name, bool Create, const char *TagSeparator) { auto p = LString(Name).SplitDelimit(TagSeparator); LXmlTag *t = this; for (int i=0; iChildren) { if (c->Tag && stricmp(c->Tag, Part) == 0) { Child = c; break; } } if (!Child) { if (Create) { t->InsertTag( Child = new LXmlTag(p[i]) ); if (!Child) { return 0; } } else return 0; } t = Child; } return t; } bool LXmlTag::GetVariant(const char *Name, LVariant &Value, const char *Array) { if (Name) { for (auto c: Children) { if (c->Tag && stricmp(c->Tag, Name) == 0) { Value = (LDom*) c; return true; } } char *v = GetAttr(Name); if (v) { if (v[0] == 'b' && v[1] == 'i' && v[2] == 'n' && v[3] == 'a' && v[4] == 'r' && v[5] == 'y' && v[6] == '(') { Value.Empty(); Value.Type = GV_BINARY; int Len = 0; char *Start = v + 7; for (char *s = Start; *s && *s != ')'; s++) Len++; Len >>= 1; Value.Value.Binary.Length = Len; Value.Value.Binary.Data = new uchar[Len]; if (Value.Value.Binary.Data) { char *s = Start; for (int i=0; i= '0' && *s <= '9') ? *s - '0' : \ ((*s >= 'a' && *s <= 'f') ? *s - 'a' + 10 : 0) int High = HexToBin(); s++; int Low = HexToBin(); s++; ((uint8_t*)Value.Value.Binary.Data)[i] = High << 4 | Low; } } } else { Value = v; } return true; } if (stricmp(Name, "text") == 0) { Value = Content; return true; } } return false; } bool LXmlTag::SetVariant(const char *Name, LVariant &Value, const char *Array) { if (Name) { switch (Value.Type) { case GV_NULL: { DelAttr(Name); break; } case GV_BOOL: { char i[32]; sprintf_s(i, sizeof(i), "%i", Value.Value.Bool); SetAttr(Name, i); break; } case GV_INT32: { char i[32]; sprintf_s(i, sizeof(i), "%i", Value.Value.Int); SetAttr(Name, i); break; } case GV_INT64: { char i[32]; sprintf_s(i, sizeof(i), LPrintfInt64, Value.Value.Int64); SetAttr(Name, i); break; } case GV_DOUBLE: { char i[32]; sprintf_s(i, sizeof(i), "%f", Value.Value.Dbl); SetAttr(Name, i); break; } case GV_STRING: { SetAttr(Name, Value.Str()); break; } case GV_BINARY: { LStringPipe p; p.Print("binary("); for (int i=0; iRemoveTag(); t->Parent = this; Children.SetFixedLength(false); Children.Add(t); Children.SetFixedLength(true); } } bool LXmlTag::RemoveTag() { if (!Parent) return true; Children.SetFixedLength(false); bool Status = Parent->Children.Delete(this); Children.SetFixedLength(true); Parent = 0; return Status; } int64 LXmlTag::CountTags() { uint64 c = 1; for (auto t: Children) c += t->CountTags(); return c; } bool LXmlTag::Dump(int Depth) { #define Tabs() { for (int i=0; iDump(Depth + 1); } LgiTrace("\n"); Depth--; #undef Tabs return true; } int LXmlTag::GetContentAsInt(int Default) { return Content ? atoi(Content) : Default; } bool LXmlTag::SetContent(int i) { char s[32]; return SetContent(s, sprintf_s(s, sizeof(s), "%i", i)); } LXmlAttr *LXmlTag::_Attr(const char *Name, bool Wr) { if (!Name) return 0; // Validate the name... for (const char *c = Name; *c; c++) { if (!IsAlpha(*c) && !IsDigit(*c) && !strchr(":-_()", *c)) { LAssert(!"Invalid attribute name."); return 0; } } if (Wr && !Allocator) Allocator = new XmlNormalAlloc; for (int i=0; iName && stricmp(a->Name, Name) == 0) { if (Wr) { Allocator->Free(a->Value); a->Value = 0; } return a; } } if (!Wr) return 0; // Create LXmlAttr &n = Attr.New(); n.Name = Allocator->Alloc(Name); return &n; } bool LXmlTag::DelAttr(const char *Name) { for (int i=0; iFree(a.Name); Allocator->Free(a.Value); Attr.DeleteAt(i); return true; } } return false; } bool LXmlTag::SetContent(const char *s, ssize_t len) { char *n = s ? Allocator->Alloc(s, len > 0 ? len : strlen(s)) : NULL; if (Content) { Allocator->Free(Content); Content = NULL; } Content = n; return s ? Content != NULL : true; } char *LXmlTag::GetAttr(const char *n) { LXmlAttr *a = _Attr(n, false); return a ? a->Value : 0; } int LXmlTag::GetAsInt(const char *n) { LXmlAttr *a = _Attr(n, false); return a ? atoi(a->Value) : -1; } bool LXmlTag::SetAttr(const char *n, const char *Value) { LXmlAttr *a = _Attr(n, true); if (a) { a->Value = Allocator->Alloc(Value); return true; } return false; } bool LXmlTag::SetAttr(const char *n, int Value) { LXmlAttr *a = _Attr(n, true); if (a) { char s[32]; sprintf_s(s, sizeof(s), "%i", Value); a->Value = Allocator->Alloc(s); return true; } return false; } bool LXmlTag::SetAttr(const char *n, int64 Value) { LXmlAttr *a = _Attr(n, true); if (a) { char s[32]; sprintf_s(s, sizeof(s), LPrintfInt64, Value); a->Value = Allocator->Alloc(s); return true; } return false; } bool LXmlTag::SerializeAttr(const char *Attr, LString &s) { if (Write) // arg -> attr { if (s) { LXmlAttr *a = _Attr(Attr, true); if (!a) return false; a->Value = Allocator->Alloc(s); return a->Value != 0; } else { DelAttr(Attr); } } else // attr -> arg { LXmlAttr *a = _Attr(Attr, false); if (a) s = a->Value; else s.Empty(); } return true; } bool LXmlTag::SerializeAttr(const char *Attr, int &Int) { LXmlAttr *a = _Attr(Attr, Write); if (a) { if (Write) { char s[256]; sprintf_s(s, sizeof(s), "%i", Int); a->Value = Allocator->Alloc(s); return a->Value != 0; } else if (a->Value) { Int = atoi(a->Value); return true; } } return false; } bool LXmlTag::SerializeAttr(const char *Name, char *&Str) { LXmlAttr *a = _Attr(Name, Write); if (a) { if (Write) { if (ValidStr(Str)) { a->Value = Allocator->Alloc(Str); } else { return DelAttr(Name); } return a->Value != 0; } else if (a->Value) { Str = NewStr(a->Value); return true; } } return false; } bool LXmlTag::SerializeAttr(const char *Attr, double &Dbl) { LXmlAttr *a = _Attr(Attr, Write); if (a) { if (Write) { char s[256]; sprintf_s(s, sizeof(s), "%f", Dbl); a->Value = Allocator->Alloc(s); return a->Value != 0; } else if (a->Value) { Dbl = atof(a->Value); return true; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////////////// LXmlTree::LXmlTree(int Flags) { d = new LXmlTreePrivate; d->Flags = Flags; } LXmlTree::~LXmlTree() { DeleteObj(d); } static const char *White = " \t\r\n"; #define SkipWhiteSpace(s) while (*s && strchr(White, *s)) s++ void LXmlTag::ParseAttribute(LXmlTree *Tree, LXmlAlloc *Alloc, char *&t, bool &NoChildren, bool &TypeDef) { while (*t && *t != '>' && *t != '?') { // Skip white SkipWhiteSpace(t); if (*t == '>') break; if (*t == '/') { t++; NoChildren = true; break; } char *AttrName = t; if (TypeDef) { if (*t == '\"') { char *Start = ++t; t = strchr(t, '\"'); if (t) { if (t > Start) { LXmlAttr &At = Attr.New(); At.Name = Alloc->Alloc(Start, t - Start); } t++; continue; } else { break; } } else if (*t == '[') { // Read all defs into children of this tag t++; while (*t && *t != ']') { bool NoChildren = false; auto c = Tree->Parse(Alloc, t, NoChildren, true); if (c) InsertTag(c.Release()); else break; } if (*t == ']') t++; continue; } } // Goto the end of the attr name while ( *t && ( IsAlpha(*t) || IsDigit(*t) || strchr("-._:()", *t) != 0 ) ) { t++; } if (t > AttrName) { LXmlAttr &At = Attr.New(); At.Name = Alloc->Alloc(AttrName, t-AttrName); // Skip white SkipWhiteSpace(t); // Look for equals if (*t == '=') { t++; // Skip white SkipWhiteSpace(t); if (strchr("\"\'", *t)) { // Delimited string char Delim = *t++; char *End = strchr(t, Delim); if (End) { if (Tree->d->Flags & GXT_NO_ENTITIES) { At.Value = Alloc->Alloc(t, End - t); } else { At.Value = Tree->DecodeEntities(Alloc, t, End - t); } /* if (At.Value) { // Strip out '\r' and '\n' in the attribute value. char *i = At.Value, *o = At.Value; do { if (*i != '\n' && *i != '\r') *o++ = *i; else *o++ = ' '; } while (*i++); } */ t = End + 1; } else { break; } } else { // Non delimited string char *End = t; while (*End && !strchr(White, *End) && *End != '>' && *End != '/') End++; At.Value = Tree->DecodeEntities(Alloc, t, End - t); t = End; } } } else { LgiTrace("%s:%i - Error tagname='%s'\n", _FL, Tag); LAssert(0); break; } // Skip white SkipWhiteSpace(t); } } LAutoPtr LXmlTree::Parse(LXmlAlloc *Alloc, char *&t, bool &NoChildren, bool InTypeDef) { bool KeepWs = TestFlag(d->Flags, GXT_KEEP_WHITESPACE); char *Start = t; LStringPipe Before; LAutoPtr Tag; // Skip white ParsingStart: while (*t) { if (*t == '<' && t[1] != ' ') { if (t[1] == '!') { if (t[2] == '-' && t[3] == '-') { // Start of comment if (KeepWs) { Before.Write(Start, t - Start); } char *End = strstr(t + 3, "-->"); if (End) { t += 3; OnParseComment(Tag ? Tag : d->Current, t, End - t); t = End + 2; if (KeepWs) { Start = t; } } else { t = 0; break; } } else { char *End = strchr(t + 1, '>'); if (End) { t = End; } } } else { break; } } t++; } if (KeepWs && t > Start) { Before.Write(Start, t - Start); if (Before.GetSize() > 0) { LAutoPtr PreContent(d->Factory ? d->Factory->Create(NULL) : new LXmlTag); if (PreContent) { PreContent->Allocator = Alloc; NoChildren = true; if (d->Flags & GXT_NO_ENTITIES) PreContent->Content = Before.NewStr(); else { LAutoString Tmp(Before.NewStr()); LAutoRefPtr LocalAlloc(new XmlNormalAlloc); PreContent->Content = DecodeEntities(Tag ? Tag->Allocator : LocalAlloc, Tmp, strlen(Tmp)); } return PreContent; } } } if (*t && *t == '<') { t++; SkipWhiteSpace(t); // Store tagname start char *TagName = t; // bool TypeDef = *t == '!'; if (*t == '/') t++; while ( *t && ( IsAlpha(*t) || *t == '!' || *t == '?' || *t == '-' || *t == '_' || *t == ':' || *t == '.' || *t == '[' || *t == ']' || (t > TagName && IsDigit(*t)) ) ) { t++; } if (t > TagName) { if (TagName[0] == '?') { LAutoString TmpStr(NewStr(TagName, t - TagName)); LXmlTag Temp(TmpStr, Alloc); TmpStr.Reset(); bool bTrue = true; while (*t) { SkipWhiteSpace(t); if (t[0] == '>' || (t[0] == '?' && t[1] == '>')) { break; } Temp.ParseAttribute(this, Alloc, t, bTrue, InTypeDef); } if (Temp.Tag) { if (stricmp(Temp.Tag, "?xml-stylesheet") == 0) { char *Type = Temp.GetAttr("type"); char *File = Temp.GetAttr("href"); if (Type && File) { SetStyleFile(File, Type); } } } Before.Empty(); goto ParsingStart; } else { // Create the tag object LString Name(TagName, t - TagName); if (Tag.Reset(d->Factory ? d->Factory->Create(Name) : new LXmlTag)) Tag->Allocator = Alloc; // Parse attributes into tag if (Tag) { Tag->Empty(false); LAssert(Tag->Tag == NULL); Tag->SetTag(TagName, t - TagName); NoChildren = Tag->Tag ? Tag->Tag[0] == '?' : false; Tag->ParseAttribute(this, Alloc, t, NoChildren, InTypeDef); // Skip white SkipWhiteSpace(t); if (*t == '>') t++; } char *ContentStart = t; bool ContentIsWhite = true; while ( *t && *t != '<' && (!InTypeDef || *t != ']') ) { if (!strchr(WhiteSpace, *t)) ContentIsWhite = false; t++; } if ( t > ContentStart && ( !ContentIsWhite || TestFlag(d->Flags, GXT_KEEP_WHITESPACE) ) ) { if (d->Flags & GXT_NO_ENTITIES) Tag->Content = NewStr(ContentStart, t - ContentStart); else Tag->Content = DecodeEntities(Tag->Allocator, ContentStart, t - ContentStart); } } } else if (*t) { printf("Xml: stopped at '%-20s'\n", t); LAssert(0); } } return Tag; } bool LXmlTree::Read(LXmlTag *Root, LStreamI *File, LXmlFactory *Factory) { if (!Root) { d->Error = "No root argument."; return false; } if (!File) { d->Error = "No input stream argument."; return false; } LString t = Root->Tag; Root->Empty(true); LAutoRefPtr Allocator(new XmlPoolAlloc); Root->Allocator = Allocator; Root->SetTag(t); int64 Len = File->GetSize(); if (Len <= 0) { d->Error.Printf("Input stream is empty: %" PRId64 " bytes.\n", Len); return false; } LString Str = File->Read(); char *Ptr = Str; d->Factory = Factory; d->Current = Root; bool First = true; bool NoDom = TestFlag(d->Flags, GXT_NO_DOM); while (d->Current && Ptr && *Ptr) { bool NoChildren = true; auto t = Parse( Allocator, Ptr, NoChildren, false); if (!t) break; // Doc type handling... if (t->Tag && t->Tag[0] == '!' && strcmp(t->Tag, "!DOCTYPE") == 0) { for (auto c: t->Children) { if (Strcmp(c->Tag, "!ENTITY") || c->Attr.Length() != 2) continue; auto &Ent = c->Attr[0]; auto &Value = c->Attr[1]; if (Ent.Name && Value.Name && !d->Entities.Find(Ent.Name)) { LVariant v(Value.Name); auto w = v.WStr(); if (w) d->Entities.Add(Ent.Name, *w); } } } if (t->Tag && t->Tag[0] == '/' && d->Current && d->Current->Tag) { // End tag if (!Stricmp(t->Tag + 1, d->Current->Tag)) { d->Current = d->Current->Parent; } else { int Lines = 1; for (char *k = Ptr; k >= Str; k--) { if (*k == '\n') Lines++; } d->Error.Printf("Mismatched '%s' tag, got '%s' instead (Line %i).\n", t->Tag, d->Current->Tag, Lines); #ifdef _DEBUG LXmlTree Dbg; LFile Out; if (Out.Open("c:\\temp\\out.xml", O_WRITE)) { Dbg.Write(Root, &Out); } #endif break; } } else { t->Serialize(t->Write = false); if (First && !NoDom) { Root->Swap(*t); } else if (d->Current) { auto NewTag = t.Release(); d->Current->InsertTag(NewTag); if (!NoDom && !NoChildren && !d->NoChildTags.Find(NewTag->Tag)) { d->Current = NewTag; } } else { LAssert(!"Invalid state"); } } First = false; } d->Factory = NULL; return true; } bool LXmlTree::Output(LXmlTag *t, int Depth) { if (!t) return false; LString endTag; bool HasContent = false; bool HasChildren = false; bool ValidTag = false; #define OutputWrite(buf, size) \ if (d->File->Write(buf, size) != size) \ goto WriteError; #define Tabs if (!TestFlag(d->Flags, GXT_NO_PRETTY_WHITESPACE)) \ { for (int i=0; iProg) - *d->Prog++; + d->Prog->Value(d->Prog->Value()+1); t->Serialize(t->Write = true); Tabs HasContent = ValidStr(t->Content); HasChildren = t->Children.Length() > 0; ValidTag = ValidStr(t->Tag) && !IsDigit(t->Tag[0]); // Test to see if the tag is valid if (ValidTag) { if (LStreamPrint(d->File, "<%s", t->Tag) <= 0) goto StreamPrintError; for (int i = 0; i < t->Attr.Length(); i++) { LXmlAttr& a = t->Attr[i]; // Write the attribute name if(LStreamPrint(d->File, " %s=\"", a.Name) <= 0) goto StreamPrintError; // Encode the value if(a.Value && !EncodeEntities(d->File, a.Value, -1, EncodeEntitiesAttr)) goto EncoderEntitiesError; // Write the delimiter OutputWrite("\"", 1); if (i < t->Attr.Length() - 1 && TestFlag(d->Flags, GXT_PRETTY_WHITESPACE)) { OutputWrite("\n", 1); for (int n = 0; n <= Depth; n++) OutputWrite("\t", 1); } } if (HasContent || HasChildren) { OutputWrite(">", 1); endTag.Printf("\n", t->Tag); } else if (d->NoDom()) endTag = ">\n"; else endTag = " />\n"; } else { // we skip over the tag and content and process the children. // It's an error to have attributes and no tag. LAssert(t->Attr.Length() == 0); } if (HasContent) { if (!EncodeEntities(d->File, t->Content, -1, EncodeEntitiesContent)) goto EncoderEntitiesError; if (HasChildren) { OutputWrite((char*)"\n", 1); Tabs } } // Write the child tags if (HasChildren) { auto It = t->Children.begin(); LXmlTag *c = t->Children.Length() ? *It : NULL; if (c) { if (!HasContent) { OutputWrite((char*)"\n", 1); } for (; It != t->Children.end(); It++) { c = *It; if (!Output(c, Depth + (d->NoDom() ? 0 : 1))) return false; } Tabs } } if (endTag) OutputWrite(endTag.Get(), endTag.Length()); #undef Tabs return true; EncoderEntitiesError: LAssert(!"EncodeEntities failed."); d->Error = "EncodeEntities failed."; return false; StreamPrintError: LAssert(!"StreamPrint failed."); d->Error = "StreamPrint failed."; return false; WriteError: LAssert(!"Write failed."); d->Error = "Write failed."; return false; } bool LXmlTree::Write(LXmlTag *Root, LStreamI *File, Progress *Prog) { if (!Root || !File) { LAssert(!"Missing param."); return false; } d->File = File; if ((d->Prog = Prog)) d->Prog->SetRange(Root->CountTags()); if (!TestFlag(d->Flags, GXT_NO_HEADER)) File->Write(LXmlHeader, strlen(LXmlHeader)); if (d->StyleFile && d->StyleType) LStreamPrint(d->File, "\n", d->StyleFile.Get(), d->StyleType.Get()); auto Status = Output(Root, 0); d->File = NULL; d->Prog = NULL; return Status; } const char *LXmlTree::GetErrorMsg() { return d->Error; } LHashTbl,bool> *LXmlTree::NoChildTags() { return &d->NoChildTags; } LHashTbl,char16> *LXmlTree::GetEntityTable() { return &d->Entities; } const char *LXmlTree::GetStyleFile(const char **StyleType) { if (StyleType) *StyleType = d->StyleType; return d->StyleFile; } void LXmlTree::SetStyleFile(const char *file, const char *type) { d->StyleFile = file; d->StyleType = type; } diff --git a/src/common/Widgets/List.cpp b/src/common/Widgets/List.cpp --- a/src/common/Widgets/List.cpp +++ b/src/common/Widgets/List.cpp @@ -1,2722 +1,2717 @@ /*hdr ** FILE: LList.cpp ** AUTHOR: Matthew Allen ** DATE: 14/2/2000 ** DESCRIPTION: Lgi self-drawn listbox ** ** Copyright (C) 2000 Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/List.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" // Debug defines #define DEBUG_EDIT_LABEL 1 // Number of pixels you have to move the mouse until a drag is initiated. #define DRAG_THRESHOLD 4 // Switches for various profiling code.. #define LList_POUR_PROFILE 1 #define LList_ONPAINT_PROFILE 0 // Options #define DOUBLE_BUFFER_PAINT 0 #define ForAllItems(Var) for (auto Var : Items) #define ForAllItemsReverse(Var) Iterator ItemIter(&Items); for (LListItem *Var = ItemIter.Last(); Var; Var = ItemIter.Prev()) #define VisibleItems() CompletelyVisible // (LastVisible - FirstVisible + 1) #define MaxScroll() MAX((int)Items.Length() - CompletelyVisible, 0) class LListPrivate { public: // Mode LListMode Mode; int Columns; int VisibleColumns; // This is a pointer to a flag, that gets set when the object // is deleted. Used to trap events deleting the window. If an // event handler deletes the current window we can't touch any // of the member variables anymore, so we need to know to quit/return // ASAP. bool *DeleteFlag; // If this is true the ctrl is selecting lots of things // and we only want to notify once. bool NoSelectEvent; // Drag'n'drop LPoint DragStart; int DragData; // Kayboard search uint64 KeyLast; char16 *KeyBuf; // Class LListPrivate() { DragData = 0; KeyBuf = 0; DeleteFlag = 0; Columns = 0; VisibleColumns = 0; Mode = LListDetails; NoSelectEvent = false; } ~LListPrivate() { if (DeleteFlag) *DeleteFlag = true; DeleteArray(KeyBuf); } }; class LListItemPrivate { public: bool Selected = false; bool Visible = true; int ListItem_Image = -1; List Cols; LArray Str; LArray Display; int16 LayoutColumn = -1; LListItemPrivate() { } ~LListItemPrivate() { Cols.DeleteObjects(); EmptyStrings(); EmptyDisplay(); } void EmptyStrings() { Str.DeleteArrays(); } void EmptyDisplay() { Display.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////////////////////////////// LListItemColumn::LListItemColumn(LListItem *item, int col) { _Column = col; _Item = item; _Value = 0; _Item->d->Cols.Insert(this); } LList *LListItemColumn::GetList() { return _Item ? _Item->Parent : 0; } LItemContainer *LListItemColumn::GetContainer() { return GetList(); } LListT *LListItemColumn::GetAllItems() { return GetList() ? &GetList()->Items : 0; } void LListItemColumn::Value(int64 i) { if (i != _Value) { _Value = i; _Item->OnColumnNotify(_Column, _Value); } } LListItemColumn *LListItemColumn::GetItemCol(LListItem *i, int Col) { if (i) { for (auto c: i->d->Cols) { if (c->_Column == Col) { return c; } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////// // List item LListItem::LListItem(const char *initStr) { d = new LListItemPrivate; Pos.ZOff(-1, -1); if (initStr) SetText(initStr); } LListItem::~LListItem() { if (Parent) Parent->Remove(this); DeleteObj(d); } void LListItem::SetImage(int i) { d->ListItem_Image = i; } int LListItem::GetImage(int Flags) { return d->ListItem_Image; } LItemContainer *LListItem::GetContainer() { return Parent; } List *LListItem::GetItemCols() { return &d->Cols; } /* Calling this to store your data is optional. Just override the "GetText" function to return your own data to avoid duplication in memory. */ bool LListItem::SetText(const char *s, int i) { if (i < 0) return false; // Delete any existing column DeleteArray((char*&)d->Str[i]); DeleteObj(d->Display[i]); // Add new string in d->Str[i] = NewStr(s); if (Parent) Parent->SendNotify(LNotifyItemChange); return true; } // User can override this if they want to use their own data const char *LListItem::GetText(int i) { return d->Str[i]; } bool LListItem::Select() { return d->Selected; } LRect *LListItem::GetPos(int Col) { static LRect r; r = Pos; if (Parent->GetMode() == LListDetails) { if (Col >= 0) { LItemColumn *Column = 0; int Cx = Parent->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Parent->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } } else { r.Offset(16, 0); } return &r; } void LListItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; Update(); if (Parent && d->Selected && !Parent->d->NoSelectEvent) { LArray Items; Items.Add(this); Parent->OnItemSelect(Items); } } } void LListItem::ScrollTo() { if (Parent) { if (Parent->GetMode() == LListDetails && Parent->VScroll) { ssize_t n = Parent->Items.IndexOf(this); if (n < Parent->FirstVisible) { Parent->VScroll->Value(n); Parent->Invalidate(&Parent->ItemsPos); } else if (n >= Parent->LastVisible) { Parent->VScroll->Value(n - (Parent->LastVisible - Parent->FirstVisible) + 1); Parent->Invalidate(&Parent->ItemsPos); } } else if (Parent->GetMode() == LListColumns && Parent->HScroll) { ssize_t n = Parent->Items.IndexOf(this); if (n < Parent->FirstVisible) { Parent->HScroll->Value(d->LayoutColumn); Parent->Invalidate(&Parent->ItemsPos); } else if (n >= Parent->LastVisible) { ssize_t Range = Parent->HScroll->Page(); Parent->HScroll->Value(d->LayoutColumn - Range); Parent->Invalidate(&Parent->ItemsPos); } } } } void LListItem::Update() { if (Parent) { if (Parent->Lock(_FL)) { d->EmptyDisplay(); LPoint Info; OnMeasure(&Info); LRect r = Pos; if (r.Valid()) { if (Info.y != r.Y()) { Pos.y2 = Pos.y1 + Info.y - 1; Parent->PourAll(); r.y1 = MIN(r.y1, Pos.y1); r.y2 = Parent->ItemsPos.y2; } Parent->Invalidate(&r); } Parent->Unlock(); } } else { d->EmptyDisplay(); } } void LListItem::OnMeasure(LPoint *Info) { if (!Info) return; if (Parent && Parent->GetMode() == LListDetails) { Info->x = 4 << 10; } else { auto s = GetDs(0); Info->x = 22 + (s ? s->X() : 0); } LFont *f = Parent ? Parent->GetFont() : LSysFont; Info->y = MAX(16, f->GetHeight() + 2); // the default height } bool LListItem::GridLines() { return (Parent) ? Parent->GridLines : false; } void LListItem::OnMouseClick(LMouse &m) { int Col = Parent ? Parent->ColumnAtX(m.x) : -1; for (auto h: d->Cols) { if (Col == h->GetColumn()) { h->OnMouseClick(m); } } } LDisplayString *LListItem::GetDs(int Col, int FitTo) { if (!d->Display[Col]) { LFont *f = GetFont(); if (!f && Parent) f = Parent->GetFont(); if (!f) f = LSysFont; const char *Text = d->Str[Col] ? d->Str[Col] : GetText(Col); LAssert((NativeInt)Text != 0xcdcdcdcd && (NativeInt)Text != 0xfdfdfdfd); d->Display[Col] = new LDisplayString(f, Text?Text:(char*)""); if (d->Display[Col] && FitTo > 0) { d->Display[Col]->TruncateWithDots(FitTo); } } return d->Display[Col]; } void LListItem::ClearDs(int Col) { if (Col >= 0) { DeleteObj(d->Display[Col]); } else { d->Display.DeleteObjects(); } } void LListItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LSurface *&pDC = Ctx.pDC; if (!pDC) return; LRect ng = Ctx; // non-grid area if (c && c->InDrag()) { pDC->Colour(DragColumnColour); pDC->Rectangle(&ng); } else { LColour Background = Ctx.Back; if (Parent->GetMode() == LListDetails && (c && c->Mark()) && !d->Selected) { Background = GdcMixColour(LColour(0, 24), Background, (double)1/32); } if (GridLines()) { ng.x2--; ng.y2--; } if (!c || c->Type() == GIC_ASK_TEXT) { LDisplayString *Ds = GetDs(i, Ctx.X()); if (Ds) { Ds->GetFont()->TabSize(0); Ds->GetFont()->Transparent(false); Ds->GetFont()->Colour(Ctx.Fore, Background); switch (Ctx.Align.Type) { case LCss::AlignCenter: Ds->Draw(pDC, ng.x1+((ng.X()-Ds->X())/2), ng.y1+1, &ng); break; case LCss::AlignRight: Ds->Draw(pDC, ng.x2-Ds->X()-1, ng.y1+1, &ng); break; default: // Left or inherit Ds->Draw(pDC, ng.x1+1, ng.y1+1, &ng); break; } } else { pDC->Colour(Background); pDC->Rectangle(&ng); } } else { pDC->Colour(Background); pDC->Rectangle(&ng); if ((c && c->Type() == GIC_ASK_IMAGE) && Parent->GetImageList()) { int Img = GetImage(); if (Img >= 0) { int CenterY = Ctx.y1 + ((Ctx.Y() - Parent->GetImageList()->TileY()) >> 1); LAssert(CenterY >= 0); Parent->GetImageList()->Draw(pDC, Ctx.x1+1, CenterY, Img, Background); } } } if (GridLines()) { pDC->Colour(L_LOW); pDC->Line(Ctx.x1, Ctx.y2, Ctx.x2, Ctx.y2); pDC->Line(Ctx.x2, Ctx.y1, Ctx.x2, Ctx.y2); } } } void LListItem::OnPaint(LItem::ItemPaintCtx &Ctx) { if (!Parent || !d->Visible) return; int x = Ctx.x1; auto CtxX = Ctx.X(); LAutoPtr Prev; if (GetCss()) { Prev.Reset(new ItemPaintCtx(Ctx)); LCss::ColorDef Fill = GetCss()->Color(); if (Fill.Type == LCss::ColorRgb) Ctx.Fore.Set(Fill.Rgb32, 32); if (!Select()) { Fill = GetCss()->BackgroundColor(); if (Fill.Type == LCss::ColorRgb) Ctx.Back.Set(Fill.Rgb32, 32); } } // Icon? if (Parent->IconCol) { LItem::ItemPaintCtx IcoCtx = Ctx; IcoCtx.Set(x, Ctx.y1, x + Parent->IconCol->Width()-1, Ctx.y2); // draw icon OnPaintColumn(IcoCtx, -1, Parent->IconCol); x = IcoCtx.x2 + 1; } // draw columns auto It = d->Cols.begin(); LListItemColumn *h = *It; LItem::ItemPaintCtx ColCtx = Ctx; if (Parent->Columns.Length()) { for (int i=0; iColumns.Length(); i++) { LItemColumn *c = Parent->Columns[i]; if (Parent->GetMode() == LListColumns) ColCtx.Set(x, Ctx.y1, Ctx.x2, Ctx.y2); else ColCtx.Set(x, Ctx.y1, x + c->Width()-1, Ctx.y2); ColCtx.Align = c->TextAlign(); OnPaintColumn(ColCtx, i, c); if (h && i == h->GetColumn()) { h->OnPaintColumn(ColCtx, i, c); h = *(++It); } x = ColCtx.x2 + 1; if (Parent->GetMode() == LListColumns) break; } } else { // One fake column for the whole control: ColCtx.Set(x, Ctx.y1, x+CtxX-1, Ctx.y2); ColCtx.Align = LCss::AlignLeft; OnPaintColumn(ColCtx, 0, NULL); x = ColCtx.x2 + 1; } // after columns if (x <= Ctx.x2) { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(x, Ctx.y1, Ctx.x2, Ctx.y2); } if (Prev) Ctx = *Prev; } ////////////////////////////////////////////////////////////////////////////// // List control LList::LList(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_ListView) { d = new LListPrivate; SetId(id); Name(name); ItemsPos.ZOff(-1, -1); Buf = 0; GridLines = false; FirstVisible = -1; LastVisible = -1; EditLabels = false; MultiSelect(true); CompletelyVisible = 0; Keyboard = -1; Sunken(true); Name("LList"); #if WINNATIVE SetStyle(GetStyle() | WS_TABSTOP); SetDlgCode(DLGC_WANTARROWS); Cursor = 0; #endif SetTabStop(true); LRect r(x, y, x+cx, y+cy); SetPos(r); LResources::StyleElement(this); } LList::~LList() { DeleteObj(Buf); Empty(); EmptyColumns(); DeleteObj(d); } LListMode LList::GetMode() { return d->Mode; } void LList::SetMode(LListMode m) { if (d->Mode ^ m) { d->Mode = m; if (IsAttached()) { PourAll(); Invalidate(); } } } void LList::OnItemClick(LListItem *Item, LMouse &m) { if (Item) Item->OnMouseClick(m); } void LList::OnItemBeginDrag(LListItem *Item, LMouse &m) { if (Item) Item->OnBeginDrag(m); } void LList::OnItemSelect(LArray &It) { if (It.Length()) { Keyboard = (int)Items.IndexOf(It[0]); LAssert(Keyboard >= 0); LHashTbl, bool> Sel; for (int n=0; nOnSelect(); if (!MultiSelect()) Sel.Add(It[n], true); } if (!MultiSelect()) { // deselect all other items ForAllItems(i) { if (!Sel.Find(i)) { if (i->d->Selected) { /* i->d->Selected = false; i->Update(); */ i->Select(false); } } } } } // Notify selection change SendNotify(LNotifyItemSelect); } bool LItemContainer::DeleteColumn(LItemColumn *Col) { bool Status = false; if (Col && Lock(_FL)) { if (Columns.HasItem(Col)) { Columns.Delete(Col); DeleteObj(Col); UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); Status = true; } Unlock(); } return Status; } LMessage::Result LList::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #ifdef WIN32 case WM_VSCROLL: { if (VScroll) return VScroll->OnEvent(Msg); break; } #endif } return LItemContainer::OnEvent(Msg); } int LList::OnNotify(LViewI *Ctrl, LNotification n) { if ( (Ctrl->GetId() == IDC_VSCROLL && VScroll) || (Ctrl->GetId() == IDC_HSCROLL && HScroll) ) { if (n.Type == LNotifyScrollBarCreate) UpdateScrollBars(); Invalidate(&ItemsPos); } return LLayout::OnNotify(Ctrl, n); } LRect &LList::GetClientRect() { static LRect r; r = GetPos(); r.Offset(-r.x1, -r.y1); return r; } LListItem *LList::HitItem(int x, int y, int *Index) { int n=0; ForAllItems(i) { if ( ( // Is list mode we consider the item to have infinite width. // This helps with multi-selection when the cursor falls outside // the window's bounds but is still receiving mouse move messages // because of mouse capture. d->Mode == LListDetails && y >= i->Pos.y1 && y <= i->Pos.y2 ) || ( i->Pos.Overlap(x, y) ) ) { if (Index) *Index = n; return i; } n++; } return NULL; } void LList::ClearDs(int Col) { ForAllItems(i) { i->ClearDs(Col); } } void LList::KeyScroll(int iTo, int iFrom, bool SelectItems) { int Start = -1, End = -1, i = 0; { ForAllItems(n) { if (n->Select()) { if (Start < 0) { Start = i; } } else if (Start >= 0 && End < 0) { End = i - 1; } i++; } if (End < 0) End = i - 1; } if (Items.Length() == 0) return; iTo = limit(iTo, 0, (int)Items.Length()-1); iFrom = limit(iFrom, 0, (int)Items.Length()-1); LListItem *To = Items.ItemAt(iTo); LListItem *From = Items.ItemAt(iFrom); // int Inc = (iTo < iFrom) ? -1 : 1; if (To && From && iTo != iFrom) { // LListItem *Item = 0; if (SelectItems) { int OtherEnd = Keyboard == End ? Start : End; int Min = MIN(OtherEnd, iTo); int Max = MAX(OtherEnd, iTo); i = 0; d->NoSelectEvent = true; LArray Sel; ForAllItems(n) { bool s = i>=Min && i<=Max; n->Select(s); if (s) Sel.Add(n); i++; } d->NoSelectEvent = false; OnItemSelect(Sel); } else { Select(To); } To->ScrollTo(); Keyboard = iTo; } } bool LList::OnMouseWheel(double Lines) { if (VScroll) { int64 Old = VScroll->Value(); VScroll->Value(Old + (int)Lines); if (Old != VScroll->Value()) { Invalidate(&ItemsPos); } } if (HScroll) { int64 Old = HScroll->Value(); HScroll->Value(Old + (int)(Lines / 3)); if (Old != HScroll->Value()) { Invalidate(&ItemsPos); } } return true; } bool LList::OnKey(LKey &k) { bool Status = false; LListItem *Item = GetSelected(); if (Item) { Status = Item->OnKey(k); } if (k.vkey != LK_UP && k.vkey != LK_DOWN && k.CtrlCmd()) { switch (k.c16) { case 'A': case 'a': { if (k.Down()) SelectAll(); Status = true; break; } } } else { switch (k.vkey) { case LK_RETURN: { #if WINNATIVE if (!k.IsChar) #endif { if (k.Down()) SendNotify(LNotification(k)); } break; } case LK_BACKSPACE: case LK_DELETE: case LK_ESCAPE: { if (k.Down()) SendNotify(LNotification(k)); break; } case LK_UP: { // int i = Value(); #ifdef MAC if (k.Ctrl()) goto LList_PageUp; else if (k.System()) goto LList_Home; #endif if (k.Down()) KeyScroll(Keyboard-1, Keyboard, k.Shift()); Status = true; break; } case LK_DOWN: { #ifdef MAC if (k.Ctrl()) goto LList_PageDown; else if (k.System()) goto LList_End; #endif if (k.Down()) KeyScroll(Keyboard+1, Keyboard, k.Shift()); Status = true; break; } case LK_LEFT: { if (GetMode() == LListColumns) { if (k.Down()) { LListItem *Hit = GetSelected(); if (Hit) { LListItem *To = 0; int ToDist = 0x7fffffff; for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { LListItem *i = *It; if (!i->Pos.Valid()) break; if (i->Pos.x2 < Hit->Pos.x1) { int Dx = i->Pos.x1 - Hit->Pos.x1; int Dy = i->Pos.y1 - Hit->Pos.y1; int IDist = Dx * Dx + Dy * Dy; if (!To || IDist < ToDist) { To = i; ToDist = IDist; } } } if (!To && HScroll) { if (Hit->d->LayoutColumn == HScroll->Value() + 1) { // Seek back to the start of the column before the // first visible column for (auto it = Items.begin(FirstVisible); it.In(); it--) { LListItem *i = *it; if (i->d->LayoutColumn < HScroll->Value()) { it++; break; } } // Now find the entry at the right height } } if (To) { Select(0); To->Select(true); To->ScrollTo(); } } } Status = true; } break; } case LK_RIGHT: { if (GetMode() == LListColumns) { if (k.Down()) { LListItem *Hit = GetSelected(); if (Hit) { LListItem *To = 0; int ToDist = 0x7fffffff; for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { LListItem *i = *It; if (!i->Pos.Valid()) break; if (i->Pos.x1 > Hit->Pos.x2) { int Dx = i->Pos.x1 - Hit->Pos.x1; int Dy = i->Pos.y1 - Hit->Pos.y1; int IDist = Dx * Dx + Dy * Dy; if (!To || IDist < ToDist) { To = i; ToDist = IDist; } } } if (To) { Select(0); To->Select(true); To->ScrollTo(); } } } Status = true; } break; } case LK_PAGEUP: { #ifdef MAC LList_PageUp: #endif if (k.Down()) { int Vis = VisibleItems(); Vis = MAX(Vis, 0); KeyScroll(Keyboard-Vis, Keyboard, k.Shift()); } Status = true; break; } case LK_PAGEDOWN: { #ifdef MAC LList_PageDown: #endif if (k.Down()) { int Vis = VisibleItems(); Vis = MAX(Vis, 0); KeyScroll(Keyboard+Vis, Keyboard, k.Shift()); } Status = true; break; } case LK_END: { #ifdef MAC LList_End: #endif if (k.Down()) KeyScroll((int)Items.Length()-1, Keyboard, k.Shift()); Status = true; break; } case LK_HOME: { #ifdef MAC LList_Home: #endif if (k.Down()) KeyScroll(0, Keyboard, k.Shift()); Status = true; break; } #ifdef VK_APPS case VK_APPS: { if (k.Down()) { LListItem *s = GetSelected(); if (s) { LRect *r = &s->Pos; if (r) { LMouse m; LListItem *FirstVisible = ItemAt((VScroll) ? (int)VScroll->Value() : 0); m.x = 32 + ItemsPos.x1; m.y = r->y1 + (r->Y() >> 1) - (FirstVisible ? FirstVisible->Pos.y1 : 0) + ItemsPos.y1; m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); OnMouseClick(m); } Status = true; } } break; } #endif default: { if ( !Status && k.IsChar && ( IsDigit(k.c16) || IsAlpha(k.c16) || strchr("_.-", k.c16) ) ) { if (k.Down()) { uint64 Now = LCurrentTime(); LStringPipe p; if (d->KeyBuf && Now < d->KeyLast + 1500) { p.Push(d->KeyBuf); } DeleteArray(d->KeyBuf); d->KeyLast = Now; p.Push(&k.c16, 1); d->KeyBuf = p.NewStrW(); if (d->KeyBuf) { char *c8 = WideToUtf8(d->KeyBuf); if (c8) { int Col = 0; bool Ascend = true; for (int i=0; iMark()) { Col = i; if (c->Mark() == GLI_MARK_UP_ARROW) { Ascend = false; } } } bool Selected = false; auto It = Ascend ? Items.begin() : Items.rbegin(); for (; It.In(); Ascend ? ++It : --It) { LListItem *i = *It; if (!Selected) { const char *t = i->GetText(Col); if (t && stricmp(t, c8) >= 0) { i->Select(true); i->ScrollTo(); Selected = true; } else { i->Select(false); } } else { i->Select(false); } } DeleteArray(c8); } } } Status = true; } break; } } } return Status; } LCursor LList::GetCursor(int x, int y) { LItemColumn *Resize, *Over; HitColumn(x, y, Resize, Over); if (Resize) return LCUR_SizeHor; return LCUR_Normal; } void LList::OnMouseClick(LMouse &m) { // m.Trace("LList::OnMouseClick"); if (Lock(_FL)) { if (m.Down()) { Focus(true); DragMode = DRAG_NONE; d->DragStart.x = m.x; d->DragStart.y = m.y; if (ColumnHeaders && ColumnHeader.Overlap(m.x, m.y)) { // Clicked on a column heading LItemColumn *Resize, *Over; int Index = HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { if (m.CtrlCmd()) { ResizeColumnsToContent(); } else { ColSizes Sizes; GetColumnSizes(Sizes); int AvailablePx = GetClient().X() - 5; if (VScroll) AvailablePx -= VScroll->X(); int ExpandPx = AvailablePx - (Sizes.FixedPx + Sizes.ResizePx); if (ExpandPx > 0) { int MaxPx = Resize->GetContentSize() + DEFAULT_COLUMN_SPACING; int AddPx = MIN(ExpandPx, MaxPx - Resize->Width()); if (AddPx > 0) { Resize->Width(Resize->Width() + AddPx); ClearDs(Index); Invalidate(); } } } } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } else { DragMode = CLICK_COLUMN; d->DragData = (int)Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } } else if (ItemsPos.Overlap(m.x, m.y)) { // Clicked in the items area bool HandlerHung = false; int ItemIndex = -1; LListItem *Item = HitItem(m.x, m.y, &ItemIndex); // LViewI *Notify = Item ? (GetNotify()) ? GetNotify() : GetParent() : 0; d->DragData = ItemIndex; if (Item && Item->Select()) { // Click on selected item if (m.CtrlCmd()) { Item->Select(false); OnItemClick(Item, m); } else { // Could be drag'n'drop operation // Or just a select int64 StartHandler = LCurrentTime(); // this will get set if 'this' is deleted. bool DeleteFlag = false; // Setup the delete flag pointer d->DeleteFlag = &DeleteFlag; // Do the event... may delete 'this' object, or hang for a long time OnItemClick(Item, m); // If the object has been deleted... exit out of here NOW! if (DeleteFlag) { return; } // Shut down the delete flag pointer... it'll point to invalid stack soon. d->DeleteFlag = 0; // Check if the handler hung for a long time... uint64 Now = LCurrentTime(); HandlerHung = Now - StartHandler > 200; if (!HandlerHung && !m.Double() && !m.IsContextMenu()) { // Start d'n'd watcher pulse... SetPulse(100); Capture(true); DragMode = CLICK_ITEM; } if (!IsCapturing()) { // If capture failed then we reset the dragmode... DragMode = DRAG_NONE; } } } else { // Selection change if (m.Shift() && MultiSelect()) { int n = 0; int a = MIN(ItemIndex, Keyboard); int b = MAX(ItemIndex, Keyboard); LArray Sel; ForAllItems(i) { bool s = n >= a && n <= b; if (i->d->Selected ^ s) { i->d->Selected = s; i->Update(); } if (s) Sel.Add(i); n++; } OnItemSelect(Sel); Item->Select(true); } else { bool PostSelect = false; bool SelectionChanged = false; // Temporaily turn off selection events... // and just send one at the end. // d->NoSelectEvent = true; ForAllItems(i) { if (Item == i) // clicked item { if (m.CtrlCmd()) { // Toggle selected state if (!i->Select()) { Keyboard = (int)Items.IndexOf(i); } i->Select(!i->Select()); SelectionChanged = true; } else { // Select this after we have delselected everything else PostSelect = true; } } else if (!m.CtrlCmd() || !MultiSelect()) { if (i->Select()) { i->Select(false); SelectionChanged = true; } } } if (PostSelect) { SelectionChanged |= Item->Select() == false; Item->Select(true); Keyboard = (int)Items.IndexOf(Item); } if (!m.CtrlCmd() && Items.Length() && !m.IsContextMenu()) { DragMode = SELECT_ITEMS; SetPulse(100); Capture(true); } if (SelectionChanged) { SendNotify(LNotifyItemSelect); } // d->NoSelectEvent = false; } OnItemClick(Item, m); } if (!HandlerHung) { if (m.IsContextMenu()) SendNotify(LNotification(m, LNotifyItemContextMenu)); else if (Item || m.Double()) SendNotify(LNotification(m)); else SendNotify(LNotification(m, LNotifyContainerClick)); } } } else // Up Click { switch (DragMode) { case CLICK_COLUMN: { if (d->DragData < 0) break; LItemColumn *c = Columns[d->DragData]; if (c) { c->Value(false); LRect cpos = c->GetPos(); Invalidate(&cpos); if (cpos.Overlap(m.x, m.y)) { OnColumnClick((int)Columns.IndexOf(c), m); } } else { OnColumnClick(-1, m); } break; } case CLICK_ITEM: { // This code allows the user to change a larger selection // down to a single item, by clicking on that item. This // can't be done on the down click because the user may also // be clicking the selected items to drag them somewhere and // if we de-selected all but the clicked item on the down // click they would never be able to drag and drop more than // one item. // // However we also do not want this to select items after the // contents of the list box have changed since the down click LListItem *Item = Items.ItemAt(d->DragData); if (Item) { bool Change = false; LArray s; ForAllItems(i) { bool Sel = Item == i; if (Sel ^ i->Select()) { Change = true; i->Select(Sel); if (Sel) { s.Add(i); } } } if (Change) OnItemSelect(s); } break; } case DRAG_COLUMN: { // End column drag if (DragCol) { LRect DragPos = DragCol->GetPos(); LPoint p(DragPos.x1 + (DragPos.X()/2), 0); PointToView(p); int OldIndex = DragCol->GetIndex(); int Best = 100000000, NewIndex = OldIndex, i=0, delta; for (i=0; iGetPos().x1); if (delta < Best) { Best = delta; NewIndex = i - (i > OldIndex ? 1 : 0); } } delta = abs(p.x - Columns.Last()->GetPos().x2); if (delta < Best) { NewIndex = i; } LItemColumn *Col = DragCol->GetColumn(); if (OldIndex != NewIndex && OnColumnReindex(Col, OldIndex, NewIndex)) { Columns.SetFixedLength(false); Columns.Delete(Col, true); Columns.AddAt(OldIndex < NewIndex ? NewIndex-1 : NewIndex, Col); Columns.SetFixedLength(true); UpdateAllItems(); } DragCol->Quit(); DragCol = NULL; } Invalidate(); break; } } LListItem *Item = HitItem(m.x, m.y); if (Item) { OnItemClick(Item, m); } if (IsCapturing()) { Capture(false); } DragMode = DRAG_NONE; } Unlock(); } } void LList::OnPulse() { if (!Lock(_FL)) return; if (IsCapturing()) { LMouse m; bool HasMs = GetMouse(m); // m.Trace("LList::OnPulse"); if (HasMs && (m.y < 0 || m.y >= Y())) { switch (DragMode) { case SELECT_ITEMS: { int OverIndex = 0; LListItem *Over = 0; if (m.y < 0) { int Space = -m.y; int n = FirstVisible - 1; for (auto It = Items.begin(n); It != Items.end(); --It, n--) { LListItem *i = *It; LPoint Info; i->OnMeasure(&Info); if (Space > Info.y) { Space -= Info.y; } else { OverIndex = n; Over = i; break; } } if (!Over) { Over = Items[0]; OverIndex = 0; } } else if (m.y >= Y()) { int Space = m.y - Y(); int n = LastVisible + 1; for (auto It = Items.begin(n); It != Items.end(); ++It, n++) { LListItem *i = *It; LPoint Info; i->OnMeasure(&Info); if (Space > Info.y) { Space -= Info.y; } else { OverIndex = n; Over = i; break; } } if (!Over) { Over = *Items.rbegin(); OverIndex = (int)Items.Length()-1; } } int Min = MIN(d->DragData, OverIndex); int Max = MAX(d->DragData, OverIndex); int n = Min; for (auto It = Items.begin(Min); It != Items.end() && n <= Max; ++It, n++) { LListItem *i = *It; if (!i->Select()) i->Select(true); } if (Over) { Over->ScrollTo(); } break; } } } } else { DragMode = DRAG_NONE; SetPulse(); } Unlock(); } void LList::OnMouseMove(LMouse &m) { if (!Lock(_FL)) return; // m.Trace("LList::OnMouseMove"); switch (DragMode) { case DRAG_COLUMN: { if (DragCol) { LPoint p; PointToScreen(p); LRect r = DragCol->GetPos(); r.Offset(-p.x, -p.y); // to view co-ord r.Offset(m.x - DragCol->GetOffset() - r.x1, 0); if (r.x1 < 0) r.Offset(-r.x1, 0); if (r.x2 > X()-1) r.Offset((X()-1)-r.x2, 0); r.Offset(p.x, p.y); // back to screen co-ord DragCol->SetPos(r, true); r = DragCol->GetPos(); } break; } case RESIZE_COLUMN: { LItemColumn *c = Columns[d->DragData]; if (c) { // int OldWidth = c->Width(); int NewWidth = m.x - c->GetPos().x1; c->Width(MAX(NewWidth, 4)); ClearDs(d->DragData); Invalidate(); } break; } case CLICK_COLUMN: { if (d->DragData < 0 || d->DragData >= Columns.Length()) break; LItemColumn *c = Columns[d->DragData]; if (c) { if (abs(m.x - d->DragStart.x) > DRAG_THRESHOLD || abs(m.y - d->DragStart.y) > DRAG_THRESHOLD) { OnColumnDrag(d->DragData, m); } else { bool Over = c->GetPos().Overlap(m.x, m.y); if (m.Down() && Over != c->Value()) { c->Value(Over); LRect r = c->GetPos(); Invalidate(&r); } } } break; } case SELECT_ITEMS: { int n=0; // bool Selected = m.y < ItemsPos.y1; if (IsCapturing()) { if (MultiSelect()) { int Over = -1; HitItem(m.x, m.y, &Over); if (m.y < ItemsPos.y1 && FirstVisible == 0) { Over = 0; } else { int n = FirstVisible; for (auto it = Items.begin(n); it != Items.end(); it++) { auto k = *it; if (!k->OnScreen()) break; if ((m.y >= k->Pos.y1) && (m.y <= k->Pos.y2)) { Over = n; break; } n++; } } if (Over >= 0) { n = 0; int Start = MIN(Over, d->DragData); int End = MAX(Over, d->DragData); ForAllItems(i) { i->Select(n >= Start && n <= End); n++; } } } else { ForAllItems(i) { i->Select(i->Pos.Overlap(m.x, m.y)); } } } break; } case CLICK_ITEM: { LListItem *Cur = Items.ItemAt(d->DragData); if (Cur) { Cur->OnMouseMove(m); if (IsCapturing() && (abs(d->DragStart.x-m.x) > DRAG_THRESHOLD || abs(d->DragStart.y-m.y) > DRAG_THRESHOLD)) { Capture(false); OnItemBeginDrag(Cur, m); DragMode = DRAG_NONE; } } break; } default: { List s; if (GetSelection(s)) { for (auto c: s) { LMouse ms = m; ms.x -= c->Pos.x1; ms.y -= c->Pos.y1; c->OnMouseMove(ms); } } break; } } Unlock(); } int64 LList::Value() { int n=0; ForAllItems(i) { if (i->Select()) { return n; } n++; } return -1; } void LList::Value(int64 Index) { int n=0; ForAllItems(i) { if (n == Index) { i->Select(true); Keyboard = n; } else { i->Select(false); } n++; } } void LList::SelectAll() { if (Lock(_FL)) { ForAllItems(i) { i->d->Selected = true; } Unlock(); Invalidate(); } } bool LList::Select(LListItem *Obj) { bool Status = false; ForAllItems(i) { i->Select(Obj == i); if (Obj == i) Status = true; } return true; } LListItem *LList::GetSelected() { LListItem *n = 0; if (Lock(_FL)) { ForAllItems(i) { if (i->Select()) { n = i; break; } } Unlock(); } return n; } bool LList::GetUpdateRegion(LListItem *i, LRegion &r) { r.Empty(); if (d->Mode == LListDetails) { if (i->Pos.Valid()) { LRect u = i->Pos; u.y2 = ItemsPos.y2; r.Union(&u); return true; } } else if (d->Mode == LListColumns) { if (i->Pos.Valid()) { LRect u = i->Pos; u.y2 = ItemsPos.y2; r.Union(&u); u.x1 = u.x2 + 1; u.y1 = ItemsPos.y1; r.Union(&u); return true; } } return false; } bool LList::Insert(LListItem *i, int Index, bool Update) { List l; l.Insert(i); return Insert(l, Index, Update); } bool LList::Insert(List &l, int Index, bool Update) { bool Status = false; if (Lock(_FL)) { bool First = Items.Length() == 0; // Insert list of items for (auto i: l) { if (i->Parent != this) { i->Parent = this; i->Select(false); Items.Insert(i, Index); i->OnInsert(); if (Index >= 0) Index++; if (First) { First = false; Keyboard = 0; i->Select(true); } } } Status = true; Unlock(); if (Update) { // Update screen PourAll(); Invalidate(); // Notify SendNotify(LNotifyItemInsert); } } return Status; } bool LList::Delete(ssize_t Index) { return Delete(Items.ItemAt(Index)); } bool LList::Delete(LListItem *i) { bool Status = false; if (Lock(_FL)) { if (Remove(i)) { // Delete DeleteObj(i); Status = true; } Unlock(); } return Status; } bool LList::Remove(LListItem *i) { bool Status = false; if (Lock(_FL)) { if (i && i->GetList() == this) { LRegion Up; bool Visible = GetUpdateRegion(i, Up); bool Selected = i->Select(); int Index = (int)Items.IndexOf(i); int64 Pos = (VScroll) ? VScroll->Value() : 0; // Remove from list Items.Delete(i); i->OnRemove(); i->Parent = 0; UpdateScrollBars(); // Update screen if ((VScroll && VScroll->Value() != Pos) || Index < FirstVisible) { Invalidate(&ItemsPos); } else if (Visible) { Up.y2 = ItemsPos.y2; Invalidate(&Up); } // Notify LViewI *Note = GetNotify() ? GetNotify() : GetParent(); if (Note) { if (Selected) { LArray s; OnItemSelect(s); } LNotification n(LNotifyItemDelete); Note->OnNotify(this, n); } Status = true; } Unlock(); } return Status; } bool LList::HasItem(LListItem *Obj) { return Items.HasItem(Obj); } int LList::IndexOf(LListItem *Obj) { return (int)Items.IndexOf(Obj); } LListItem *LList::ItemAt(size_t Index) { return Index < Items.Length() ? Items.ItemAt(Index) : NULL; } void LList::ScrollToSelection() { if (VScroll) { int n=0; int Vis = VisibleItems(); ForAllItems(i) { if (i->Select()) { if (n < FirstVisible || n > LastVisible) { int k = n - (Vis/2); VScroll->Value(MAX(k, 0)); Invalidate(&ItemsPos); break; } } n++; } } } void LList::Empty() { if (Lock(_FL)) { ForAllItems(i) { LAssert(i->Parent == this); i->Parent = 0; DeleteObj(i); } Items.Empty(); FirstVisible = LastVisible = -1; DragMode = DRAG_NONE; if (VScroll) { VScroll->Value(0); VScroll->SetRange(0); } Invalidate(); DeleteArray(d->KeyBuf); Unlock(); } } void LList::RemoveAll() { if (Lock(_FL)) { if (Items.Length()) { LArray s; OnItemSelect(s); } for (auto i: Items) { i->OnRemove(); i->Parent = 0; } Items.Empty(); FirstVisible = LastVisible = -1; DragMode = DRAG_NONE; if (VScroll) { // these have to be in this order because // "SetLimits" can cause the VScroll object to // be deleted and becoming NULL VScroll->Value(0); VScroll->SetRange(0); } Invalidate(); DeleteArray(d->KeyBuf); Unlock(); } } void LList::OnPosChange() { LLayout::OnPosChange(); } void LList::UpdateScrollBars() { static bool Processing = false; if (!Processing && InThread()) { Processing = true; if (VScroll) { int Vis = VisibleItems(); int Max = MaxScroll(); if (VScroll->Value() > MAX(Max, 0)) { VScroll->Value(Max); } VScroll->SetPage(Vis); VScroll->SetRange(Items.Length()); } if (HScroll) { HScroll->SetPage(d->VisibleColumns); HScroll->SetRange(d->Columns); } Processing = false; } } void LList::PourAll() { #if LList_POUR_PROFILE LProfile Prof("PourAll()", 100); #endif // Layout all the elements LRect Client = GetClient(); LFont *Font = GetFont(); - - if (Columns.Length() == 0) - { - int asd=0; - } if (d->Mode == LListDetails) { if (ColumnHeaders) { ColumnHeader = Client; ColumnHeader.y2 = ColumnHeader.y1 + Font->GetHeight() + 4; ItemsPos = Client; ItemsPos.y1 = ColumnHeader.y2 + 1; } else { ItemsPos = Client; ColumnHeader.ZOff(-1, -1); } int n = 0; int y = ItemsPos.y1; int Max = MaxScroll(); FirstVisible = (VScroll) ? (int)VScroll->Value() : 0; if (FirstVisible > Max) FirstVisible = Max; LastVisible = 0x7FFFFFFF; CompletelyVisible = 0; bool SomeHidden = false; // Process visible flag ForAllItems(i) { auto css = i->GetCss(); i->d->Visible = !css || css->Display() != LCss::DispNone; } #if LList_POUR_PROFILE Prof.Add("List items"); #endif ForAllItems(i) { if (!i->d->Visible) { i->Pos.Set(-1, -1, -2, -2); SomeHidden = true; continue; // Don't increment 'n' } if (n < FirstVisible || n > LastVisible) { i->Pos.Set(-1, -1, -2, -2); SomeHidden = true; } else { LPoint Info; i->OnMeasure(&Info); if (i->Pos.Valid() && Info.y != i->Pos.Y()) { // This detects changes in item height and invalidates the items below this one. LRect in(0, y+Info.y, X()-1, Y()-1); Invalidate(&in); } i->Pos.Set(ItemsPos.x1, y, ItemsPos.x2, y+Info.y-1); y = y+Info.y; if (i->Pos.y2 > ItemsPos.y2) { LastVisible = n; SomeHidden = true; } else { CompletelyVisible++; } } n++; } if (LastVisible >= Items.Length()) { LastVisible = (int)Items.Length() - 1; } SetScrollBars(false, SomeHidden); UpdateScrollBars(); } else if (d->Mode == LListColumns) { ColumnHeader.ZOff(-1, -1); ItemsPos = Client; FirstVisible = 0; int CurX = 0; int CurY = 0; int MaxX = 16; LArray Col; d->Columns = 1; d->VisibleColumns = 0; int64 ScrollX = HScroll ? HScroll->Value() : 0; int64 OffsetY = HScroll ? 0 : LScrollBar::GetScrollSize(); FirstVisible = -1; int n = 0; #if LList_POUR_PROFILE Prof.Add("List cols"); #endif ForAllItems(i) { LPoint Info; i->OnMeasure(&Info); if (d->Columns <= ScrollX || CurX > ItemsPos.X()) { i->Pos.ZOff(-1, -1); i->d->LayoutColumn = d->Columns; if (ItemsPos.y1 + CurY + Info.y > ItemsPos.y2 - OffsetY) { CurY = 0; d->Columns++; if (d->Columns > ScrollX && CurX < ItemsPos.X()) { goto FlowItem; } } } else { FlowItem: if (ItemsPos.y1 + CurY + Info.y > ItemsPos.y2 - OffsetY) { // wrap to next column for (int n=0; nPos.x2 = CurX + MaxX - 1; } Col.Length(0); CurX += MaxX; CurY = 0; d->Columns++; if (CurX < ItemsPos.X()) { d->VisibleColumns++; } } if (FirstVisible < 0) FirstVisible = n; LastVisible = n; i->d->LayoutColumn = d->Columns; i->Pos.ZOff(Info.x-1, Info.y-1); i->Pos.Offset(ItemsPos.x1 + CurX, ItemsPos.y1 + CurY); Col[Col.Length()] = i; MaxX = MAX(MaxX, Info.x); CompletelyVisible++; } CurY += Info.y; n++; } d->VisibleColumns = MAX(1, d->VisibleColumns); // pour remaining items... for (n=0; nPos.x2 = CurX + MaxX - 1; } Col.Length(0); if (CurX + MaxX < ItemsPos.X()) { d->VisibleColumns++; } // printf("%u - ScrollX=%i VisCol=%i Cols=%i\n", (uint32)LCurrentTime(), ScrollX, d->VisibleColumns, d->Columns); SetScrollBars(d->VisibleColumns < d->Columns, false); UpdateScrollBars(); } } static LColour Tint(LColour back, double amt) { bool Darken = back.GetGray() >= 128; LColour Mixer = Darken ? LColour::Black : LColour::White; return back.Mix(Mixer, (float)(1.0f - amt)); } void LList::OnPaint(LSurface *pDC) { #if LList_ONPAINT_PROFILE int Start = LCurrentTime(), t1, t2, t3, t4, t5; #endif if (!Lock(_FL)) return; LCssTools Tools(this); LColour DisabledTint(L_MED); LColour Workspace(L_WORKSPACE); LColour NonFocusBack(L_NON_FOCUS_SEL_BACK); LColour Fore = Enabled() ? Tools.GetFore() : Tools.GetFore().Mix(DisabledTint); LColour Back = Tools.GetBack(&Workspace, 0); double NonFocusBackAmt = (double)NonFocusBack.GetGray() / Workspace.GetGray(); if (!Enabled()) Back = Back.Mix(DisabledTint); LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : (Enabled() ? Tint(Back, NonFocusBackAmt) : DisabledTint)); PourAll(); // printf("ListPaint SelFore=%s SelBack=%s Back=%s %f NonFocusBack=%s\n", SelFore.GetStr(), SelBack.GetStr(), Back.GetStr(), NonFocusBackAmt, NonFocusBack.GetStr()); #if LList_ONPAINT_PROFILE t1 = LCurrentTime(); #endif // Check icon column status then draw if (AskImage() && !IconCol) { IconCol.Reset(new LItemColumn(this, 0, 18)); if (IconCol) { IconCol->Resizable(false); IconCol->Type(GIC_ASK_IMAGE); } } else if (!AskImage()) IconCol.Reset(); PaintColumnHeadings(pDC); #if LList_ONPAINT_PROFILE t2 = LCurrentTime(); #endif // Draw items if (!Buf) Buf = new LMemDC; LRect r = ItemsPos; int n = FirstVisible; int LastY = r.y1; LCss::ColorDef Fill; int LastSelected = -1; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; LRegion Rgn(ItemsPos); if (Items.Length()) { for (auto It = Items.begin(n); It != Items.end(); ++It, n++) { LListItem *i = *It; if (i->Pos.Valid()) { // Setup painting colours in the context if (LastSelected ^ (int)i->Select()) { if ((LastSelected = i->Select())) { Ctx.Fore = SelFore; Ctx.Back = SelBack; } else { Ctx.Fore = Fore; Ctx.Back = Back; } } // tell the item what colour to use #if DOUBLE_BUFFER_PAINT if (Buf->X() < i->Pos.X() || Buf->Y() < i->Pos.Y()) { Buf->Create(i->Pos.X(), i->Pos.Y(), GdcD->GetBits()); } Ctx = i->Pos; Ctx.r.Offset(-Ctx.r.x1, -Ctx.r.y1); i->OnPaint(Ctx); pDC->Blt(i->Pos.x1, i->Pos.y1, Buf, &Ctx.r); #else (LRect&)Ctx = i->Pos; i->OnPaint(Ctx); #endif Rgn.Subtract(&i->Pos); LastY = i->Pos.y2 + 1; } } } pDC->Colour(Back); for (LRect *w=Rgn.First(); w; w=Rgn.Next()) { pDC->Rectangle(w); } Unlock(); #if LList_ONPAINT_PROFILE int64 End = LCurrentTime(); printf("LList::OnPaint() pour=%i headers=%i items=%i\n", (int) (t1-Start), (int) (t2-t1), (int) (End-t2)); #endif } void LList::OnFocus(bool b) { LListItem *s = GetSelected(); if (Items.Length()) { if (!s) { s = Items[0]; if (s) s->Select(true); } for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { auto i = *It; if (i->Pos.Valid() && i->d->Selected) { Invalidate(&i->Pos); } } } LLayout::OnFocus(b); if (!b && IsCapturing()) { Capture(false); } } void LList::UpdateAllItems() { if (Lock(_FL)) { bool needsRepour = false; ForAllItems(i) { auto css = i->GetCss(); bool vis = !css || css->Display() != LCss::DispNone; if (i->d->Visible != vis) needsRepour = true; i->d->EmptyDisplay(); } Unlock(); if (needsRepour) PourAll(); Invalidate(); } } int LList::GetContentSize(int Index) { int Max = 0; for (auto It = Items.begin(); It.In(); It++) { LListItem *i = *It; LDisplayString *s = i->d->Display[Index]; LDisplayString *Mem = 0; // If no cached string, create it for the list item if (!s || s->IsTruncated()) { LFont *f = i->GetFont(); if (!f) f = GetFont(); if (!f) f = LSysFont; const char *Text = i->d->Str[Index] ? i->d->Str[Index] : i->GetText(Index); if (s && s->IsTruncated()) { s = Mem = new LDisplayString(f, Text?Text:(char*)""); } else { s = i->d->Display[Index] = new LDisplayString(f, Text?Text:(char*)""); } } // Measure it if (s) { Max = MAX(Max, s->X()); } DeleteObj(Mem); } // Measure the heading too LItemColumn *Col = Columns[Index]; LFont *f = GetFont(); LAssert(f != 0); if (f) { LDisplayString h(f, Col->Name()); int Hx = h.X() + (Col->Mark() ? 10 : 0); Max = MAX(Max, Hx); } return Max; }