diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,453 +1,459 @@ cmake_minimum_required (VERSION 3.18) if(WIN32) set(CMAKE_SYSTEM_VERSION 10.0.19041.0) endif() project (Lgi) include(build.cmake) set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_CXX_STANDARD 14) set(LGI_VERSION_MAJOR 1) set(LGI_VERSION_MINOR 0) if (UNIX AND NOT APPLE) set(LINUX TRUE) endif() if(WIN32) message("CMAKE_GENERATOR=${CMAKE_GENERATOR}") message("CMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") if (CMAKE_GENERATOR MATCHES "Win64" OR CMAKE_GENERATOR_PLATFORM MATCHES "x64") set(WORDSIZE 64) set(OPENSSL_DIR "c:/Program Files/OpenSSL-Win64") else() set(WORDSIZE 32) set(OPENSSL_DIR "c:/Program Files (x86)/OpenSSL-Win32") endif() set(OPENSSL_INCLUDE_DIR "${OPENSSL_DIR}/include") if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION MATCHES "14393") message("SDK too old: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") endif() endif() get_directory_property(hasParent PARENT_DIRECTORY) if(hasParent) set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}" PARENT_SCOPE) endif() set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}") +# External libs: get_filename_component(CODE ../.. ABSOLUTE) get_filename_component(CODELIB ${CODE}/../codelib ABSOLUTE) if (NOT EXISTS "${CODELIB}") message(FATAL_ERROR "Expecting external libraries in: ${CODELIB}") else() message("CODELIB=${CODELIB}") endif() set(ICONV_PATH "${CODELIB}/libiconv") if (NOT EXISTS "${ICONV_PATH}/include") if (APPLE) if (EXISTS "/opt/local/include/iconv.h") set(ICONV_PATH "/opt/local") else() find_package(Iconv REQUIRED) endif() elseif (LINUX) find_package(Iconv REQUIRED) else() message(FATAL_ERROR "Missing iconv at: ${ICONV_PATH}") endif() endif() +get_filename_component(PNG_LIBRARY "${CODELIB}/libpng" ABSOLUTE) +if (EXISTS ${PNG_LIBRARY} AND NOT TARGET libpng15) + add_subdirectory(${PNG_LIBRARY} libpng) +endif() + +get_filename_component(JPEG_LIBRARY "${CODELIB}/libjpeg-9a" ABSOLUTE) +if (NOT TARGET libjpeg9a) + add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) +endif() + if (APPLE) # SET(GUI_TYPE MACOSX_BUNDLE) find_library(COCOA_LIBRARY Cocoa) mark_as_advanced (COCOA_LIBRARY) set(PUBLIC_LIBS ${COCOA_LIBRARY}) elseif (LINUX) find_package(PkgConfig) pkg_check_modules(GTK3 REQUIRED gtk+-3.0) pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4) set(PUBLIC_LIBS pthread crypt z) -elseif (WIN32) - - get_filename_component(PNG_LIBRARY "${CODELIB}/libpng" ABSOLUTE) - if (EXISTS ${PNG_LIBRARY}) - add_subdirectory(${PNG_LIBRARY} libpng) - endif() - endif() +# Lgi source code: set (CommonSource src/common/Gdc2/15Bit.cpp src/common/Gdc2/16Bit.cpp src/common/Gdc2/24Bit.cpp src/common/Gdc2/32Bit.cpp src/common/Gdc2/64Bit.cpp src/common/Gdc2/8Bit.cpp src/common/Gdc2/Alpha.cpp src/common/Gdc2/Filters/Filter.cpp src/common/Gdc2/Font/DisplayString.cpp src/common/Gdc2/Font/StringLayout.cpp src/common/Gdc2/Font/Font.cpp src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/Font/FontType.cpp src/common/Gdc2/Font/TypeFace.cpp src/common/Gdc2/Font/Charset.cpp src/common/Gdc2/Colour.cpp src/common/Gdc2/GdcCommon.cpp src/common/Gdc2/Rect.cpp src/common/Gdc2/Surface.cpp src/common/Gdc2/Path/Path.cpp src/common/Gdc2/Tools/ColourReduce.cpp src/common/General/Containers.cpp src/common/General/DateTime.cpp src/common/General/ExeCheck.cpp src/common/General/FileCommon.cpp src/common/General/Growl.cpp src/common/General/Password.cpp src/common/Hash/md5/md5.c src/common/Hash/sha1/sha1.c src/common/Net/Base64.cpp src/common/Net/Uri.cpp src/common/Net/Net.cpp src/common/Net/NetTools.cpp src/common/Net/MDStringToDigest.cpp src/common/Lgi/Alert.cpp src/common/Lgi/Css.cpp src/common/Lgi/CssTools.cpp src/common/Lgi/FindReplace.cpp src/common/Lgi/FontSelect.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Input.cpp src/common/Lgi/Library.cpp src/common/Lgi/MemStream.cpp src/common/Lgi/Mru.cpp src/common/Lgi/Mutex.cpp src/common/Lgi/Object.cpp src/common/Lgi/OptionsFile.cpp src/common/Lgi/SubProcess.cpp src/common/Lgi/Stream.cpp src/common/Lgi/ThreadCommon.cpp src/common/Lgi/ThreadEvent.cpp src/common/Lgi/ToolTip.cpp src/common/Lgi/TrayIcon.cpp src/common/Lgi/Variant.cpp src/common/Lgi/AppCommon.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/WindowCommon.cpp src/common/Lgi/DragAndDropCommon.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp src/common/Lgi/DropFiles.cpp src/common/Lgi/MenuCommon.cpp src/common/Resource/LgiRes.cpp src/common/Resource/Res.cpp src/common/Skins/Gel/Gel.cpp src/common/Text/DocView.cpp src/common/Text/String.cpp src/common/Text/TextView3.cpp src/common/Text/TextView4.cpp src/common/Text/Token.cpp src/common/Text/Unicode.cpp src/common/Text/Utf8.cpp src/common/Text/XmlTree.cpp src/common/Widgets/Button.cpp src/common/Widgets/Bitmap.cpp src/common/Widgets/Box.cpp src/common/Widgets/ItemContainer.cpp src/common/Widgets/List.cpp src/common/Widgets/Panel.cpp src/common/Widgets/Popup.cpp src/common/Widgets/ProgressDlg.cpp src/common/Widgets/RadioGroup.cpp src/common/Widgets/Splitter.cpp src/common/Widgets/StatusBar.cpp src/common/Widgets/TableLayout.cpp src/common/Widgets/TabView.cpp src/common/Widgets/TextLabel.cpp src/common/Widgets/ToolBar.cpp src/common/Widgets/Tree.cpp + include/lgi/common/App.h + include/lgi/common/View.h + include/lgi/common/Thread.h ) if (APPLE) set(Source ${CommonSource} src/common/Widgets/CheckBox.cpp src/common/Widgets/Combo.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Edit.cpp src/common/Lgi/Layout.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp src/mac/cocoa/ShowFileProp_Mac.mm src/mac/common/MemDC.cpp src/mac/common/PrintDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/File.mm src/mac/cocoa/Mem.mm src/mac/cocoa/App.mm src/mac/cocoa/ClipBoard.mm src/mac/cocoa/DragAndDrop.mm src/mac/cocoa/General.mm src/mac/cocoa/FileSelect.mm src/mac/cocoa/Menu.mm src/mac/cocoa/Printer.mm src/mac/cocoa/Thread.mm src/mac/cocoa/View.mm src/mac/cocoa/Widgets.mm src/mac/cocoa/Window.mm src/mac/cocoa/CocoaView.mm src/mac/cocoa/Gdc2.mm ) elseif (LINUX) set(Source ${CommonSource} src/linux/Gtk/Gdc2.cpp src/linux/Gtk/MemDC.cpp src/linux/Gtk/PrintDC.cpp src/linux/Gtk/ScreenDC.cpp src/linux/Gtk/LgiWidget.cpp src/linux/General/File.cpp src/linux/General/Mem.cpp src/linux/General/ShowFileProp_Linux.cpp src/linux/Lgi/App.cpp src/linux/Lgi/ClipBoard.cpp src/linux/Lgi/DragAndDrop.cpp src/linux/Lgi/General.cpp src/linux/Lgi/Layout.cpp src/linux/Lgi/Menu.cpp src/linux/Lgi/Printer.cpp src/linux/Lgi/Thread.cpp src/linux/Lgi/View.cpp src/linux/Lgi/Widgets.cpp src/linux/Lgi/Window.cpp src/common/Lgi/FileSelect.cpp src/common/Widgets/CheckBox.cpp src/common/Widgets/Edit.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Combo.cpp src/common/Widgets/RadioGroup.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp ) elseif (MSVC) set(Source ${CommonSource} src/win/General/Lgi.manifest src/win/Gdc2/Gdc2.cpp src/win/Gdc2/GdiLeak.cpp src/win/Gdc2/MemDC.cpp src/win/Gdc2/PrintDC.cpp src/win/Gdc2/ScreenDC.cpp src/win/General/File.cpp src/win/General/Mem.cpp src/win/General/ShowFileProp_Win.cpp src/win/INet/MibAccess.cpp src/win/Lgi/App.cpp src/win/Lgi/ClipBoard.cpp src/win/Lgi/DragAndDrop.cpp src/win/Lgi/FileSelect.cpp src/win/Lgi/General.cpp src/win/Lgi/Layout.cpp src/win/Lgi/Menu.cpp src/win/Lgi/Printer.cpp src/win/Lgi/ScrollBar.cpp src/win/Lgi/Thread.cpp src/win/Lgi/View.cpp src/win/Lgi/Window.cpp src/win/Lgi/LgiException.cpp src/win/Widgets/Button_Win.cpp src/common/Widgets/CheckBox.cpp # Use XP check box src/win/Widgets/Combo_Win.cpp src/win/Widgets/Dialog_Win.cpp src/win/Widgets/Edit_Win.cpp src/win/Widgets/RadioGroup_Win.cpp src/win/Widgets/Slider_Win.cpp src/win/Widgets/Progress_Win.cpp) endif () add_library(lgi SHARED ${Source}) if (DEFINED LGI_LIB_POSTFIX) set_target_properties(lgi PROPERTIES DEBUG_POSTFIX ${LGI_LIB_POSTFIX}d RELEASE_POSTFIX ${LGI_LIB_POSTFIX}) endif() if (APPLE) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK "YES" ) set_target_properties(lgi PROPERTIES FRAMEWORK true) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_INSTALL_PATH @executable_path/../Frameworks/) target_include_directories(lgi PUBLIC include/lgi/mac/cocoa /opt/local/include - ${CODELIB}/libpng - ${CODELIB}/libpng/build PRIVATE private/mac ) target_link_libraries(lgi PUBLIC ${EXTRA_LIBS}) target_compile_options(lgi PUBLIC -Wno-nullability-completeness) set(OBJCPP_SOURCE src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/GdcCommon.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Process.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/TrayIcon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/SubProcess.cpp src/common/Lgi/AppCommon.cpp src/common/Lgi/DropFiles.cpp src/common/Widgets/Popup.cpp src/common/General/DateTime.cpp src/mac/common/MemDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/General.cpp ) set_source_files_properties(${OBJCPP_SOURCE} PROPERTIES COMPILE_FLAGS "-x objective-c++") target_compile_definitions(lgi PUBLIC MAC LGI_COCOA ) elseif (LINUX) target_compile_definitions(lgi PUBLIC LINUX POSIX ) target_compile_options(lgi PUBLIC -fPIC -w -fno-inline -fpermissive ) target_include_directories(lgi PUBLIC include/lgi/linux/Gtk include/lgi/linux ${GTK3_INCLUDE_DIRS} ${GST_INCLUDE_DIRS} PRIVATE private/linux ) target_link_libraries(lgi PUBLIC ${GTK3_LIBRARIES} ${GST_LIBRARIES} magic ) elseif (MSVC) target_compile_definitions(lgi PUBLIC WINDOWS WIN32 _WINDLL _UNICODE UNICODE ) target_compile_options(lgi PRIVATE /W2 ) target_include_directories(lgi PUBLIC include/lgi/win PRIVATE private/win) target_link_libraries(lgi PUBLIC ComCtl32.lib Ws2_32.lib UxTheme.lib imm32.lib) endif() target_link_libraries(lgi PUBLIC ${PUBLIC_LIBS}) target_compile_definitions(lgi PUBLIC LGI_RES PRIVATE LGI_LIBRARY ) target_include_directories(lgi PUBLIC include PRIVATE private/common ${PNGLIB_INCLUDE_DIR} ${ICONV_PATH}/include ) set_target_properties(lgi PROPERTIES LINKER_LANGUAGE CXX) add_subdirectory(Ide) add_subdirectory(Lvc) add_subdirectory(ResourceEditor) add_subdirectory(HtmlEditor) add_subdirectory(HtmlTest) add_subdirectory(ScriptingUnitTests) add_subdirectory(SlogViewer) add_subdirectory(Updater) if(WIN32) add_subdirectory(VsLaunch) endif() if(LINUX) add_subdirectory(src/linux/CrashHandler) endif() diff --git a/HtmlEditor/CMakeLists.txt b/HtmlEditor/CMakeLists.txt --- a/HtmlEditor/CMakeLists.txt +++ b/HtmlEditor/CMakeLists.txt @@ -1,66 +1,62 @@ set(APP_MANIFEST) if (WIN32) set(APP_TYPE WIN32) set(APP_ICON Rich.rc) set(APP_MANIFEST ../src/win/General/Lgi.manifest) set(SPELL_CHECK ${LGI_DIR}/src/win/Lgi/SpellCheckWindows.cpp) elseif (APPLE) set(APP_TYPE MACOSX_BUNDLE) set(SPELL_CHECK ${LGI_DIR}/src/mac/cocoa/SpellCheckMac.mm) elseif (LINUX) set(APP_ICON) set(SPELL_CHECK ${LGI_DIR}/src/common/Text/SpellCheckAspell.cpp) endif() -if (NOT TARGET libjpeg9a) - add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) -endif() - add_executable(HtmlEdit ${APP_TYPE} Rich.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Jpeg.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Png.cpp ${LGI_DIR}/src/common/Gdc2/Tools/GdcTools.cpp ${LGI_DIR}/src/common/Net/Http.cpp ${LGI_DIR}/src/common/Net/OpenSSLSocket.cpp ${LGI_DIR}/src/common/Lgi/LgiMain.cpp ${LGI_DIR}/src/common/Text/Emoji/EmojiMap.cpp ${LGI_DIR}/src/common/Text/Emoji/EmojiTools.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/Homoglyphs.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/HomoglyphsTable.cpp ${LGI_DIR}/src/common/Text/Html.cpp ${LGI_DIR}/src/common/Text/HtmlCommon.cpp ${LGI_DIR}/src/common/Text/HtmlParser.cpp ${LGI_DIR}/src/common/Widgets/Editor/BlockCursor.cpp ${LGI_DIR}/src/common/Widgets/Editor/RichTextEdit.cpp ${LGI_DIR}/src/common/Widgets/Editor/RichTextEditPriv.cpp ${LGI_DIR}/src/common/Widgets/Editor/HorzRuleBlock.cpp ${LGI_DIR}/src/common/Widgets/Editor/ImageBlock.cpp ${LGI_DIR}/src/common/Widgets/Editor/TextBlock.cpp ${SPELL_CHECK} resource.h ${APP_ICON} ${APP_MANIFEST}) target_include_directories(HtmlEdit PRIVATE Code Resources ${OPENSSL_INCLUDE_DIR} ../private/common) target_link_libraries(HtmlEdit PRIVATE lgi libpng15 libjpeg9a) if (APPLE) macBundlePostBuild(HtmlEdit) endif() set_target_properties(HtmlEdit PROPERTIES EXCLUDE_FROM_ALL TRUE) diff --git a/HtmlTest/CMakeLists.txt b/HtmlTest/CMakeLists.txt --- a/HtmlTest/CMakeLists.txt +++ b/HtmlTest/CMakeLists.txt @@ -1,78 +1,74 @@ set(APP_MANIFEST) set(APP_RESOURCE HtmlTestSuite.lr8) if (WIN32) set(APP_TYPE WIN32) set(APP_ICON) set(APP_MANIFEST ../src/win/General/Lgi.manifest) enable_language(ASM_MASM) elseif (APPLE) set(APP_TYPE MACOSX_BUNDLE) set(APP_ICON) set_source_files_properties(${APP_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") elseif (LINUX) set(APP_ICON) endif() -if (NOT TARGET libjpeg9a) - add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) -endif() - add_executable(HtmlTest ${APP_TYPE} TestSuite.cpp ${LGI_DIR}/src/common/Coding/LexCpp.cpp ${LGI_DIR}/src/common/Coding/ScriptCompiler.cpp ${LGI_DIR}/src/common/Coding/ScriptLibrary.cpp ${LGI_DIR}/src/common/Coding/ScriptVM.cpp ${LGI_DIR}/src/common/Coding/ScriptingPriv.h ${LGI_DIR}/src/common/Gdc2/Filters/Gif.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Jpeg.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Lzw.cpp ${LGI_DIR}/src/common/Gdc2/Filters/Png.cpp ${LGI_DIR}/src/common/Gdc2/Font/EmojiFont.cpp ${LGI_DIR}/src/common/Gdc2/Tools/GdcTools.cpp ${LGI_DIR}/src/common/Images/Compare/ImageComparison.cpp ${LGI_DIR}/src/common/Net/Http.cpp ${LGI_DIR}/src/common/Net/OpenSSLSocket.cpp ${LGI_DIR}/src/common/Lgi/LgiMain.cpp ${LGI_DIR}/src/common/Text/Emoji/EmojiMap.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/Homoglyphs.cpp ${LGI_DIR}/src/common/Text/Homoglyphs/HomoglyphsTable.cpp ${LGI_DIR}/src/common/Text/Html.cpp ${LGI_DIR}/src/common/Text/HtmlCommon.cpp ${LGI_DIR}/src/common/Text/HtmlParser.cpp ${LGI_DIR}/src/common/Widgets/ZoomView.cpp ${APP_MANIFEST}) if (WIN32) target_sources(HtmlTest PRIVATE ${LGI_DIR}/src/common/Coding/ExternCall.asm) endif() target_include_directories(HtmlTest PRIVATE Code Resources ${OPENSSL_INCLUDE_DIR} ../private/common) target_link_libraries(HtmlTest PRIVATE lgi libpng15 libjpeg9a) if (APPLE) macBundlePostBuild(HtmlTest) endif() set_target_properties(HtmlTest PROPERTIES EXCLUDE_FROM_ALL TRUE) \ No newline at end of file diff --git a/Ide/CMakeLists.txt b/Ide/CMakeLists.txt --- a/Ide/CMakeLists.txt +++ b/Ide/CMakeLists.txt @@ -1,152 +1,151 @@ cmake_minimum_required (VERSION 3.18) set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_CXX_STANDARD 14) project (LgiIde) if (UNIX AND NOT APPLE) set(LINUX TRUE) endif() if (NOT TARGET libpng15) - get_filename_component(LIBPNG_DIR "${CODELIB}/libpng" ABSOLUTE) - add_subdirectory(${LIBPNG_DIR} libpng) + message(FATAL_ERROR "Find the libpng target (see parent folder cmake)") endif() if (APPLE) set(GUI_TYPE MACOSX_BUNDLE) find_library(COCOA_LIBRARY Cocoa) mark_as_advanced(COCOA_LIBRARY) set(EXTRA_LIBS ${COCOA_LIBRARY}) elseif (LINUX) add_definitions("-fPIC -w -fno-inline -fpermissive") find_package(PNG REQUIRED) elseif (MSVC) set(ResourceFiles ../src/win/General/Lgi.manifest Resources/Script1.rc) if (NOT EXISTS ${OPENSSL_INCLUDE_DIR}) message(FATAL_ERROR "OpenSSL not found: '${OPENSSL_INCLUDE_DIR}'") endif() if (NOT EXISTS ${CODELIB}) message(FATAL_ERROR "Codelib not found: '${CODELIB}'") endif() if (${MSVC_VERSION} GREATER_EQUAL 1900) set(VS_VER 14) elseif (${MSVC_VERSION} EQUAL 1800) set(VS_VER 12) else() message(FATAL_ERROR "Unknown Visual Studio version: ${MSVC_VERSION}") endif() endif () set (CommonSource Code/SimpleCppParser.cpp Code/WebFldDlg.cpp Code/IdeCommon.cpp Code/AddFtpFile.cpp Code/ProjectNode.cpp Code/IdeDoc.cpp Code/IdeProject.cpp Code/IdeProjectSettings.cpp Code/LgiIde.cpp Code/LgiUtils.cpp Code/MemDumpViewer.cpp Code/SpaceTabConv.cpp Code/SysCharSupport.cpp Code/DebugContext.cpp Code/Debugger.cpp Code/History.cpp Code/FindInFiles.cpp Code/FindSymbol.cpp Code/FtpThread.cpp Code/DocEdit.cpp Code/PythonParser.cpp Code/MissingFiles.cpp Code/DocEditStyling.cpp Code/levenshtein.c Code/JavascriptParser.cpp Code/NewProjectFromTemplate.cpp ../src/common/Net/OpenSSLSocket.cpp ../src/common/Net/Http.cpp ../src/common/Net/Ftp.cpp ../src/common/Gdc2/Filters/Png.cpp ../src/common/Lgi/About.cpp ../src/common/Lgi/Mdi.cpp ../src/common/Lgi/LgiMain.cpp ../src/common/Widgets/ControlTree.cpp ../src/common/Coding/ParseCpp.cpp ../src/common/Coding/LexCpp.cpp ) list(APPEND ResourceFiles Resources/cmds-32px.png Resources/icons.png Resources/cmds-16px.png Resources/LgiIde.lr8 Resources/icon64.png ) if (APPLE) set(MacResourceFiles MacCocoa/Info.plist Resources/mac-icon.icns) set(APP_TYPE MACOSX_BUNDLE) set_source_files_properties(${MacResourceFiles} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") list(APPEND ResourceFiles ${MacResourceFiles}) elseif (MSVC) set(APP_TYPE WIN32) endif () add_executable(LgiIde ${APP_TYPE} ${CommonSource} ${ResourceFiles}) target_link_libraries(LgiIde lgi libpng15) target_include_directories(LgiIde PRIVATE Code Resources) if (APPLE) target_link_libraries(LgiIde ${EXTRA_LIBS}) set_target_properties(LgiIde PROPERTIES MACOSX_BUNDLE TRUE MACOSX_FRAMEWORK_IDENTIFIER com.memecode.LgiIde RESOURCE "${ResourceFiles}" ) message("macBundlePostBuild(LgiIde)") macBundlePostBuild(LgiIde) elseif (MSVC) target_link_libraries(LgiIde ComCtl32.lib Ws2_32.lib UxTheme.lib imm32.lib) add_custom_command(TARGET LgiIde POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" "$/$" COMMENT "Copying Lgi to output directory") if (EXISTS ${PNG_DLL}) add_custom_command(TARGET LgiIde POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${PNG_DLL}" "$/libpng${VS_VER}x${WORDSIZE}.dll" COMMENT "Copying Lgi to output directory") endif() endif () # Create a resources sub-folder and copy over the resource files (images and lr8) file(COPY ${CMAKE_CURRENT_LIST_DIR}/Resources DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories (LgiIde PUBLIC Code "Resources" ${PNG_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} "${CODELIB}/libiconv-1.14/include") set_target_properties(LgiIde PROPERTIES EXCLUDE_FROM_ALL TRUE) diff --git a/Ide/Code/FindSymbol.cpp b/Ide/Code/FindSymbol.cpp --- a/Ide/Code/FindSymbol.cpp +++ b/Ide/Code/FindSymbol.cpp @@ -1,678 +1,685 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Token.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/TextFile.h" #include "LgiIde.h" #include "FindSymbol.h" #include "ParserCommon.h" #if 1 #include "lgi/common/ParseCpp.h" #endif #include "resdefs.h" #define MSG_TIME_MS 1000 #define DEBUG_FIND_SYMBOL 0 #define DEBUG_NO_THREAD 1 -#define DEBUG_FILE "BayesianFilter.cpp" +// #define DEBUG_FILE "FileSelect.cpp" int SYM_FILE_SENT = 0; class FindSymbolDlg : public LDialog { // AppWnd *App; LList *Lst; FindSymbolSystem *Sys; public: FindSymResult Result; FindSymbolDlg(LViewI *parent, FindSymbolSystem *sys); ~FindSymbolDlg(); int OnNotify(LViewI *v, LNotification n); void OnCreate(); bool OnViewKey(LView *v, LKey &k); LMessage::Result OnEvent(LMessage *m); }; int ScoreCmp(FindSymResult **a, FindSymResult **b) { return (*b)->Score - (*a)->Score; } #define USE_HASH 1 struct FindSymbolSystemPriv : public LEventTargetThread { struct FileSyms { LString Path; int Platforms; LString::Array *Inc; LArray Defs; bool IsSource; bool IsHeader; bool IsPython; bool IsJavascript; bool Parse(LAutoWString Source, bool Debug) { IsSource = false; IsHeader = false; IsPython = false; IsJavascript = false; Defs.Length(0); bool Status = false; char *Ext = LGetExtension(Path); if (Ext) { IsSource = !_stricmp(Ext, "c") || !_stricmp(Ext, "cpp") || !_stricmp(Ext, "cxx"); IsHeader = !_stricmp(Ext, "h") || !_stricmp(Ext, "hpp") || !_stricmp(Ext, "hxx"); IsPython = !_stricmp(Ext, "py"); IsJavascript = !_stricmp(Ext, "js"); if (IsSource || IsHeader) Status = BuildCppDefnList(Path, Source, Defs, DefnNone, Debug); else if (IsJavascript) Status = BuildJsDefnList(Path, Source, Defs, DefnNone, Debug); else if (IsPython) Status = BuildPyDefnList(Path, Source, Defs, DefnNone, Debug); } return Status; } }; int hApp; int MissingFiles; LArray IncPaths; #if USE_HASH LHashTbl, FileSyms*> Files; #else LArray Files; #endif uint32_t Tasks; uint64 MsgTs; bool DoingProgress; FindSymbolSystemPriv(int appSinkHnd) : LEventTargetThread("FindSymbolSystemPriv"), hApp(appSinkHnd) { Tasks = 0; MsgTs = 0; MissingFiles = 0; DoingProgress = false; } ~FindSymbolSystemPriv() { // End the thread... EndThread(); // Clean up mem Files.DeleteObjects(); } void Log(const char *Fmt, ...) { va_list Arg; va_start(Arg, Fmt); LString s; s.Printf(Arg, Fmt); va_end(Arg); if (s.Length()) PostThreadEvent(hApp, M_APPEND_TEXT, (LMessage::Param)NewStr(s), AppWnd::BuildTab); } #if !USE_HASH int GetFileIndex(LString &Path) { for (unsigned i=0; iPath) return i; } return -1; } #endif bool AddFile(LString Path, int Platforms) { + bool Debug = false; #ifdef DEBUG_FILE - bool Debug = false; if ((Debug = Path.Find(DEBUG_FILE) >= 0)) printf("%s:%i - AddFile(%s)\n", _FL, Path.Get()); #endif // Already added? #if USE_HASH FileSyms *f = Files.Find(Path); if (f) { if (Platforms && f->Platforms == 0) f->Platforms = Platforms; return true; } #else int Idx = GetFileIndex(Path); if (Idx >= 0) { if (Platforms && Files[Idx]->Platforms == 0) Files[Idx]->Platforms = Platforms; return true; } FileSyms *f; #endif if (!LFileExists(Path)) { Log("Missing '%s'\n", Path.Get()); MissingFiles++; return false; } LAssert(!LIsRelativePath(Path)); // Setup the file sym data... f = new FileSyms; if (!f) return false; f->Path = Path; f->Platforms = Platforms; f->Inc = IncPaths.Length() ? IncPaths.Last() : NULL; #if USE_HASH Files.Add(Path, f); #else Files.Add(f); #endif // Parse for headers... LTextFile Tf; if (!Tf.Open(Path, O_READ) || Tf.GetSize() < 4) { LgiTrace("%s:%i - Error: LTextFile.Open(%s) failed.\n", _FL, Path.Get()); return false; } LAutoString Source = Tf.Read(); LArray Headers; LArray EmptyInc; if (BuildHeaderList(Source, Headers, f->Inc ? *f->Inc : EmptyInc, false)) { for (auto h: Headers) AddFile(h, 0); } Headers.DeleteArrays(); // Parse for symbols... #ifdef DEBUG_FILE if (Debug) printf("%s:%i - About to parse '%s'.\n", _FL, f->Path.Get()); #endif return f->Parse(LAutoWString(Utf8ToWide(Source)), Debug); } bool ReparseFile(LString Path) { #if USE_HASH FileSyms *f = Files.Find(Path); int Platform = f ? f->Platforms : 0; #else int Idx = GetFileIndex(Path); int Platform = Idx >= 0 ? Files[Idx]->Platforms : 0; #endif if (!RemoveFile(Path)) return false; return AddFile(Path, Platform); } bool RemoveFile(LString Path) { #if USE_HASH FileSyms *f = Files.Find(Path); if (!f) return false; Files.Delete(Path); delete f; #else int Idx = GetFileIndex(Path); if (Idx < 0) return false; delete Files[Idx]; Files.DeleteAt(Idx); #endif return true; } LMessage::Result OnEvent(LMessage *Msg) { if (IsCancelled()) return -1; switch (Msg->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Req((FindSymRequest*)Msg->A()); bool AllPlatforms = (bool)Msg->B(); if (Req && Req->SinkHnd >= 0) { LString::Array p = Req->Str.SplitDelimit(" \t"); if (p.Length() == 0) break; LArray ClassMatches; LArray HdrMatches; LArray SrcMatches; // For each file... #if USE_HASH for (auto it : Files) { FileSyms *fs = it.value; #else for (unsigned f=0; fPath.Find(DEBUG_FILE) >= 0; if (Debug) printf("%s:%i - Searching '%s' with %i syms...\n", _FL, fs->Path.Get(), (int)fs->Defs.Length()); #endif // Check platforms... if (!AllPlatforms && (fs->Platforms & PLATFORM_CURRENT) == 0) + { continue; + } // For each symbol... for (unsigned i=0; iDefs.Length(); i++) { DefnInfo &Def = fs->Defs[i]; + #ifdef DEBUG_FILE + if (Debug) + printf("%s:%i - '%s'\n", _FL, Def.Name.Get()); + #endif + // For each search term... bool Match = true; int ScoreSum = 0; for (unsigned n=0; nScore = ScoreSum; r->File = Def.File.Get(); r->Symbol = Def.Name.Get(); r->Line = Def.Line; if (Def.Type == DefnClass) ClassMatches.Add(r); else if (fs->IsHeader) HdrMatches.Add(r); else SrcMatches.Add(r); } } } } } ClassMatches.Sort(ScoreCmp); Req->Results.Add(ClassMatches); Req->Results.Add(HdrMatches); Req->Results.Add(SrcMatches); int Hnd = Req->SinkHnd; PostObject(Hnd, M_FIND_SYM_REQUEST, Req); } break; } case M_FIND_SYM_FILE: { LAutoPtr Params((FindSymbolSystem::SymFileParams*)Msg->A()); if (Params) { LString::Array Mime = LGetFileMimeType(Params->File).Split("/"); if (!Mime[0].Equals("image")) { if (Params->Action == FindSymbolSystem::FileAdd) AddFile(Params->File, Params->Platforms); else if (Params->Action == FindSymbolSystem::FileRemove) RemoveFile(Params->File); else if (Params->Action == FindSymbolSystem::FileReparse) ReparseFile(Params->File); } } SYM_FILE_SENT--; break; } case M_FIND_SYM_INC_PATHS: { LAutoPtr Paths((LString::Array*)Msg->A()); if (Paths) IncPaths.Add(Paths.Release()); break; } default: { LAssert(!"Implement handler for message."); break; } } auto Now = LCurrentTime(); // printf("Msg->Msg()=%i " LPrintfInt64 " %i\n", Msg->Msg(), MsgTs, (int)GetQueueSize()); if (Now - MsgTs > MSG_TIME_MS) { MsgTs = Now; DoingProgress = true; uint32_t Remaining = (uint32_t) (Tasks - GetQueueSize()); if (Remaining > 0) Log("FindSym: %i of %i (%.1f%%)\n", Remaining, Tasks, (double)Remaining * 100.0 / MAX(Tasks, 1)); } else if (GetQueueSize() == 0 && MsgTs) { if (DoingProgress) { Log("FindSym: Done.\n"); DoingProgress = false; } if (MissingFiles > 0) { Log("(%i files are missing)\n", MissingFiles); } MsgTs = 0; Tasks = 0; } return 0; } }; int AlphaCmp(LListItem *a, LListItem *b, NativeInt d) { return stricmp(a->GetText(0), b->GetText(0)); } FindSymbolDlg::FindSymbolDlg(LViewI *parent, FindSymbolSystem *sys) { Lst = NULL; Sys = sys; SetParent(parent); if (LoadFromResource(IDD_FIND_SYMBOL)) { MoveToCenter(); LViewI *f; if (GetViewById(IDC_STR, f)) f->Focus(true); GetViewById(IDC_RESULTS, Lst); } } FindSymbolDlg::~FindSymbolDlg() { } void FindSymbolDlg::OnCreate() { RegisterHook(this, LKeyEvents); } bool FindSymbolDlg::OnViewKey(LView *v, LKey &k) { switch (k.vkey) { case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { return Lst->OnKey(k); break; } } return false; } LMessage::Result FindSymbolDlg::OnEvent(LMessage *m) { switch (m->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Req((FindSymRequest*)m->A()); if (Req) { LString Str = GetCtrlName(IDC_STR); if (Str == Req->Str) { Lst->Empty(); List Ls; LString s; int CommonPathLen = 0; for (unsigned i=0; iResults.Length(); i++) { FindSymResult *r = Req->Results[i]; if (i) { char *a = s.Get(); char *a_end = strrchr(a, DIR_CHAR); char *b = r->File.Get(); char *b_end = strrchr(b, DIR_CHAR); int Common = 0; while ( *a && a <= a_end && *b && b <= b_end && ToLower(*a) == ToLower(*b)) { Common++; a++; b++; } if (i == 1) CommonPathLen = Common; else CommonPathLen = MIN(CommonPathLen, Common); } else s = r->File; } for (unsigned i=0; iResults.Length(); i++) { FindSymResult *r = Req->Results[i]; LListItem *it = new LListItem; if (it) { LString Ln; Ln.Printf("%i", r->Line); it->SetText(r->File.Get() + CommonPathLen, 0); it->SetText(Ln, 1); it->SetText(r->Symbol, 2); Ls.Insert(it); } } Lst->Insert(Ls); Lst->ResizeColumnsToContent(); } } break; } } return LDialog::OnEvent(m); } int FindSymbolDlg::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_STR: { if (n.Type != LNotifyReturnKey) { auto Str = v->Name(); if (Str && strlen(Str) > 2) { // Create a search Sys->Search(AddDispatch(), Str, true); } } break; } case IDC_RESULTS: { if (n.Type == LNotifyItemDoubleClick) { // Fall throu } else break; } case IDOK: { if (Lst) { LListItem *i = Lst->GetSelected(); if (i) { Result.File = i->GetText(0); Result.Line = atoi(i->GetText(1)); } } EndModal(true); break; } case IDCANCEL: { EndModal(false); break; } } return LDialog::OnNotify(v, n); } /////////////////////////////////////////////////////////////////////////// FindSymbolSystem::FindSymbolSystem(int AppHnd) { d = new FindSymbolSystemPriv(AppHnd); } FindSymbolSystem::~FindSymbolSystem() { delete d; } void FindSymbolSystem::OpenSearchDlg(LViewI *Parent, std::function Callback) { FindSymbolDlg *Dlg = new FindSymbolDlg(Parent, this); Dlg->DoModal([Dlg, Callback](auto d, auto code) { if (Callback) Callback(Dlg->Result); delete Dlg; }); } bool FindSymbolSystem::SetIncludePaths(LString::Array &Paths) { LString::Array *a = new LString::Array; if (!a) return false; *a = Paths; return d->PostEvent(M_FIND_SYM_INC_PATHS, (LMessage::Param)a); } bool FindSymbolSystem::OnFile(const char *Path, SymAction Action, int Platforms) { if (d->Tasks == 0) d->MsgTs = LCurrentTime(); d->Tasks++; LAutoPtr Params(new FindSymbolSystem::SymFileParams); Params->File = Path; Params->Action = Action; Params->Platforms = Platforms; if (d->PostObject(d->GetHandle(), M_FIND_SYM_FILE, Params)) { SYM_FILE_SENT++; } return false; } void FindSymbolSystem::Search(int ResultsSinkHnd, const char *SearchStr, bool AllPlat) { FindSymRequest *Req = new FindSymRequest(ResultsSinkHnd); if (Req) { Req->Str = SearchStr; d->PostEvent(M_FIND_SYM_REQUEST, (LMessage::Param)Req, (LMessage::Param)AllPlat); } } diff --git a/Ide/Code/IdeProject.cpp b/Ide/Code/IdeProject.cpp --- a/Ide/Code/IdeProject.cpp +++ b/Ide/Code/IdeProject.cpp @@ -1,4073 +1,4086 @@ #if defined(WIN32) #include #else #include #endif #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Combo.h" #include "lgi/common/Net.h" #include "lgi/common/ListItemCheckBox.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DropFiles.h" #include "lgi/common/SubProcess.h" #include "lgi/common/Css.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/RegKey.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "LgiIde.h" #include "resdefs.h" #include "FtpThread.h" #include "ProjectNode.h" #include "WebFldDlg.h" extern const char *Untitled; const char SourcePatterns[] = "*.c;*.h;*.cpp;*.cc;*.java;*.d;*.php;*.html;*.css;*.js"; const char *AddFilesProgress::DefaultExt = "c,cpp,cc,cxx,h,hpp,hxx,html,css,json,js,jsx,txt,png,jpg,jpeg,rc,xml,mk,paths,makefile,py,java,php"; const char *VsBinaries[] = {"devenv.com", "WDExpress.exe"}; #define USE_OPEN_PROGRESS 1 #define STOP_BUILD_TIMEOUT 2000 #ifdef WINDOWS #define LGI_STATIC_LIBRARY_EXT "lib" #else #define LGI_STATIC_LIBRARY_EXT "a" #endif const char *PlatformNames[] = { "Windows", "Linux", "Mac", "Haiku", 0 }; int PlatformCtrlId[] = { IDC_WIN32, IDC_LINUX, IDC_MAC, IDC_HAIKU, 0 }; const char *PlatformDynamicLibraryExt(IdePlatform Platform) { if (Platform == PlatformWin) return "dll"; if (Platform == PlatformMac) return "dylib"; return "so"; } const char *PlatformSharedLibraryExt(IdePlatform Platform) { if (Platform == PlatformWin) return "lib"; return "a"; } const char *PlatformExecutableExt(IdePlatform Platform) { if (Platform == PlatformWin) return ".exe"; return ""; } char *ToUnixPath(char *s) { if (s) { char *c; while ((c = strchr(s, '\\'))) *c = '/'; } return s; } const char *CastEmpty(char *s) { return s ? s : ""; } bool FindInPath(LString &Exe) { LString::Array Path = LString(getenv("PATH")).Split(LGI_PATH_SEPARATOR); for (unsigned i=0; i SubProc; LString::Array BuildConfigs; LString::Array PostBuild; int AppHnd; enum CompilerType { DefaultCompiler, VisualStudio, MingW, Gcc, CrossCompiler, PythonScript, IAR, Nmake, Cygwin, Xcode, } Compiler; enum ArchType { DefaultArch, ArchX32, ArchX64, ArchArm6, ArchArm7, } Arch; public: BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize); ~BuildThread(); ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override; LString FindExe(); LAutoString WinToMingWPath(const char *path); int Main() override; }; class IdeProjectPrivate { public: AppWnd *App; IdeProject *Project; bool Dirty, UserFileDirty; LString FileName; IdeProject *ParentProject; IdeProjectSettings Settings; LAutoPtr Thread; LHashTbl, ProjectNode*> Nodes; int NextNodeId; // Threads LAutoPtr CreateMakefile; // User info file LString UserFile; LHashTbl,int> UserNodeFlags; IdeProjectPrivate(AppWnd *a, IdeProject *project) : Project(project), Settings(project) { App = a; Dirty = false; UserFileDirty = false; ParentProject = 0; NextNodeId = 1; } void CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform); }; LString ToPlatformPath(const char *s, IdePlatform platform) { LString p = s; if (platform == PlatformWin) return p.Replace("/", "\\"); else return p.Replace("\\", "/"); } class MakefileThread : public LThread, public LCancel { IdeProjectPrivate *d; IdeProject *Proj; IdePlatform Platform; LStream *Log; bool BuildAfterwards; bool HasError; public: static int Instances; MakefileThread(IdeProjectPrivate *priv, IdePlatform platform, bool Build) : LThread("MakefileThread") { Instances++; d = priv; Proj = d->Project; Platform = platform; BuildAfterwards = Build; HasError = false; Log = d->App->GetBuildLog(); Run(); } ~MakefileThread() { Cancel(); while (!IsExited()) LSleep(1); Instances--; } void OnError(const char *Fmt, ...) { va_list Arg; va_start(Arg, Fmt); LStreamPrintf(Log, 0, Fmt, Arg); va_end(Arg); HasError = true; } int Main() { const char *PlatformName = PlatformNames[Platform]; const char *PlatformLibraryExt = NULL; const char *PlatformStaticLibExt = NULL; const char *PlatformExeExt = ""; LString LinkerFlags; const char *TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform); const char *CompilerName = d->Settings.GetStr(ProjCompiler); LString CCompilerBinary = "gcc"; LString CppCompilerBinary = "g++"; LStream *Log = d->App->GetBuildLog(); bool IsExecutableTarget = TargetType && !stricmp(TargetType, "Executable"); bool IsDynamicLibrary = TargetType && !stricmp(TargetType, "DynamicLibrary"); LAssert(Log); if (!Log) return false; Log->Print("CreateMakefile for '%s'...\n", PlatformName); if (Platform == PlatformWin) { LinkerFlags = ",--enable-auto-import"; } else { if (IsDynamicLibrary) { LinkerFlags = ",-soname,$(TargetFile)"; } LinkerFlags += ",-export-dynamic,-R."; } char Buf[256]; auto MakeFile = Proj->GetMakefile(Platform); Proj->CheckExists(MakeFile); if (!MakeFile) MakeFile = "../Makefile"; // LGI_LIBRARY_EXT switch (Platform) { case PlatformWin: PlatformLibraryExt = "dll"; PlatformStaticLibExt = "lib"; PlatformExeExt = ".exe"; break; case PlatformLinux: case PlatformHaiku: PlatformLibraryExt = "so"; PlatformStaticLibExt = "a"; break; case PlatformMac: PlatformLibraryExt = "dylib"; PlatformStaticLibExt = "a"; break; default: LAssert(0); break; } if (CompilerName) { if (!stricmp(CompilerName, "cross")) { LString CBin = d->Settings.GetStr(ProjCCrossCompiler, NULL, Platform); if (CBin && !LFileExists(CBin)) FindInPath(CBin); if (CBin && LFileExists(CBin)) CCompilerBinary = CBin; else Log->Print("%s:%i - Error: C cross compiler '%s' not found.\n", _FL, CBin.Get()); LString CppBin = d->Settings.GetStr(ProjCppCrossCompiler, NULL, Platform); if (CppBin && !LFileExists(CppBin)) FindInPath(CppBin); if (CppBin && LFileExists(CppBin)) CppCompilerBinary = CppBin; else Log->Print("%s:%i - Error: C++ cross compiler '%s' not found.\n", _FL, CppBin.Get()); } } LFile m; if (!m.Open(MakeFile, O_WRITE)) { Log->Print("Error: Failed to open '%s' for writing.\n", MakeFile.Get()); return false; } m.SetSize(0); m.Print("#!/usr/bin/make\n" "#\n" "# This makefile generated by LgiIde\n" "# http://www.memecode.com/lgi.php\n" "#\n" "\n" ".SILENT :\n" "\n" "CC = %s\n" "CPP = %s\n", CCompilerBinary.Get(), CppCompilerBinary.Get()); // Collect all files that require building LArray Files; d->CollectAllFiles ( Proj, Files, false, 1 << Platform ); - LAutoString Base = Proj->GetBasePath(); + auto Base = Proj->GetBasePath(); if (IsExecutableTarget) { LString Exe = Proj->GetExecutable(Platform); if (Exe) { if (LIsRelativePath(Exe)) m.Print("Target = %s\n", ToPlatformPath(Exe, Platform).Get()); else { auto RelExe = LMakeRelativePath(Base, Exe); if (Base && RelExe) { m.Print("Target = %s\n", ToPlatformPath(RelExe, Platform).Get()); } else { Log->Print("%s:%i - Error: Missing path (%s, %s).\n", _FL, Base.Get(), RelExe.Get()); return false; } } } else { Log->Print("%s:%i - Error: No executable name specified (%s, %s).\n", _FL, TargetType, d->FileName.Get()); return false; } } else { LString Target = Proj->GetTargetName(Platform); if (Target) m.Print("Target = %s\n", ToPlatformPath(Target, Platform).Get()); else { Log->Print("%s:%i - Error: No target name specified.\n", _FL); return false; } } // Output the build mode, flags and some paths auto BuildMode = d->App->GetBuildMode(); auto BuildModeName = toString(BuildMode); m.Print("ifndef Build\n" " Build = %s\n" "endif\n", BuildModeName); LString sDefines[BuildMax]; LString sLibs[BuildMax]; LString sIncludes[BuildMax]; const char *ExtraLinkFlags = NULL; const char *ExeFlags = NULL; if (Platform == PlatformWin) { ExtraLinkFlags = ""; ExeFlags = " -mwindows"; m.Print("BuildDir = $(Build)\n" "\n" "Flags = -fPIC -w -fno-inline -fpermissive\n"); const char *DefDefs = "-DWIN32 -D_REENTRANT"; - sDefines[0] = DefDefs; - sDefines[1] = DefDefs; + sDefines[BuildDebug] = DefDefs; + sDefines[BuildRelease] = DefDefs; } else { LString PlatformCap = PlatformName; ExtraLinkFlags = ""; ExeFlags = ""; m.Print("BuildDir = $(Build)\n" "\n" "Flags = -fPIC -w -fno-inline -fpermissive\n" // -fexceptions ); sDefines[0].Printf("-D%s -D_REENTRANT", PlatformCap.Upper().Get()); #ifdef LINUX - sDefines[0] += " -D_FILE_OFFSET_BITS=64"; // >:-( - sDefines[0] += " -DPOSIX"; + sDefines[BuildDebug] += " -D_FILE_OFFSET_BITS=64"; // >:-( + sDefines[BuildDebug] += " -DPOSIX"; #endif - sDefines[1] = sDefines[0]; + sDefines[BuildRelease] = sDefines[BuildDebug]; } List Deps; Proj->GetChildProjects(Deps); for (int Cfg = BuildDebug; Cfg < BuildMax; Cfg++) { // Set the config auto cfgName = toString((BuildConfig)Cfg); d->Settings.SetCurrentConfig(cfgName); // Get the defines setup auto PDefs = d->Settings.GetStr(ProjDefines, NULL, Platform); if (ValidStr(PDefs)) { LToken Defs(PDefs, " ;,\r\n"); for (int i=0; iSettings.GetStr(ProjLibraryPaths, NULL, Platform); if (ValidStr(PLibPaths)) { LString::Array LibPaths = PLibPaths.Split("\n"); for (auto i: LibPaths) { LString s, in = i.Strip(); if (!in.Length()) continue; - if (in(0) == '-') + if (strchr("`-", in(0))) { s.Printf(" \\\n\t\t%s", in.Get()); } else { LString Rel; if (!LIsRelativePath(in)) Rel = LMakeRelativePath(Base, in); LString Final = Rel ? Rel.Get() : in.Get(); if (!Proj->CheckExists(Final)) OnError("%s:%i - Library path '%s' doesn't exist (from %s).\n", _FL, Final.Get(), Proj->GetFileName()); s.Printf(" \\\n\t\t-L%s", ToUnixPath(Final)); } sLibs[Cfg] += s; } } const char *PLibs = d->Settings.GetStr(ProjLibraries, NULL, Platform); if (ValidStr(PLibs)) { LToken Libs(PLibs, "\r\n"); for (int i=0; iCheckExists(l); s.Printf(" \\\n\t\t-l%s", ToUnixPath(l)); } sLibs[Cfg] += s; } } for (auto dep: Deps) { LString Target = dep->GetTargetName(Platform); if (Target) { char t[MAX_PATH_LEN]; strcpy_s(t, sizeof(t), Target); if (!strnicmp(t, "lib", 3)) memmove(t, t + 3, strlen(t + 3) + 1); char *dot = strrchr(t, '.'); if (dot) *dot = 0; LString s, sTarget = t; Proj->CheckExists(sTarget); s.Printf(" \\\n\t\t-l%s$(Tag)", ToUnixPath(sTarget)); sLibs[Cfg] += s; auto DepBase = dep->GetBasePath(); if (DepBase) { LString DepPath = DepBase.Get(); auto Rel = LMakeRelativePath(Base, DepPath); LString Final = Rel ? Rel.Get() : DepPath.Get(); Proj->CheckExists(Final); s.Printf(" \\\n\t\t-L%s/$(BuildDir)", ToUnixPath(Final.RStrip("/\\"))); sLibs[Cfg] += s; } } } // Includes // Do include paths LHashTbl,bool> Inc; auto ProjIncludes = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); if (ValidStr(ProjIncludes)) { // Add settings include paths. LToken Paths(ProjIncludes, "\r\n"); for (int i=0; iCheckExists(pn)) OnError("%s:%i - Include path '%s' doesn't exist.\n", _FL, pn.Get()); else if (!Inc.Find(pn)) Inc.Add(pn, true); } } const char *SysIncludes = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform); if (ValidStr(SysIncludes)) { // Add settings include paths. LToken Paths(SysIncludes, "\r\n"); for (int i=0; iCheckExists(pn)) OnError("%s:%i - System include path '%s' doesn't exist (from %s).\n", _FL, pn.Get(), Proj->GetFileName()); else if (!Inc.Find(pn)) Inc.Add(pn, true); } } #if 0 /* Currently this code is adding extra paths that are covered by the official 'IncludePaths' in addition to relative paths in the actual #include parameter. e.g. #include "lgi/common/SomeHeader.h" Hence disabling it for the time being. */ // Add paths of headers for (int i=0; iGetFileName()) { char *e = LGetExtension(n->GetFileName()); if (e && stricmp(e, "h") == 0) { LString Fn = n->GetFileName(); for (char *Dir = Fn; *Dir; Dir++) { if (*Dir == '/' || *Dir == '\\') { *Dir = DIR_CHAR; } } char Path[MAX_PATH_LEN]; strcpy_s(Path, sizeof(Path), Fn); LTrimDir(Path); LString Rel; if (!Proj->RelativePath(Rel, Path)) Rel = Path; if (stricmp(Rel, ".") != 0) { LAutoString RelN = ToNativePath(Rel); if (!Proj->CheckExists(RelN)) OnError("Header include path '%s' doesn't exist.\n", RelN.Get()); else if (!Inc.Find(RelN)) Inc.Add(RelN, true); } } } } #endif LString::Array Incs; for (auto i: Inc) Incs.New() = i.key; Incs.Sort(); for (auto i: Incs) { LString s; if (*i == '`') s.Printf(" \\\n\t\t%s", i.Get()); else s.Printf(" \\\n\t\t-I%s", ToUnixPath(i)); sIncludes[Cfg] += s; } } // Output the defs section for Debug and Release // Debug specific m.Print("\n" "ifeq ($(Build),Debug)\n" " Flags += -g -std=c++14\n" " Tag = d\n" " Defs = -D_DEBUG %s\n" " Libs = %s\n" " Inc = %s\n", CastEmpty(sDefines[0].Get()), CastEmpty(sLibs[0].Get()), CastEmpty(sIncludes[0].Get())); // Release specific m.Print("else\n" " Flags += -s -Os -std=c++14\n" " Defs = %s\n" " Libs = %s\n" " Inc = %s\n" "endif\n" "\n", CastEmpty(sDefines[1].Get()), CastEmpty(sLibs[1].Get()), CastEmpty(sIncludes[1].Get())); if (Files.Length()) { LArray IncPaths; if (Proj->BuildIncludePaths(IncPaths, false, false, Platform)) { // Do source code list... m.Print("# Dependencies\n" "Source =\t"); LString::Array SourceFiles; for (auto &n: Files) { if (n->GetType() == NodeSrc) { auto f = n->GetFileName(); auto path = ToPlatformPath(f, Platform); if (path.Find("./") == 0) path = path(2,-1); SourceFiles.Add(path); } } SourceFiles.Sort(); int c = 0; for (auto &src: SourceFiles) { if (c++) m.Print(" \\\n\t\t\t"); m.Print("%s", src.Get()); } m.Print("\n" "\n" "SourceLst := $(patsubst %%.c,%%.o,$(patsubst %%.cpp,%%.o,$(Source)))\n" "\n" "Objects := $(addprefix $(BuildDir)/,$(SourceLst))\n" "\n"); // Write out the target stuff m.Print("# Target\n"); LHashTbl,bool> DepFiles; if (TargetType) { if (IsExecutableTarget) { m.Print("# Executable target\n" "$(Target) :"); LStringPipe Rules; IdeProject *Dep; uint64 Last = LCurrentTime(); int Count = 0; auto It = Deps.begin(); for (Dep=*It; Dep && !IsCancelled(); Dep=*(++It), Count++) { // Get dependency to create it's own makefile... Dep->CreateMakefile(Platform, false); // Build a rule to make the dependency if any of the source changes... auto DepBase = Dep->GetBasePath(); auto Base = Proj->GetBasePath(); auto TargetFile = Dep->GetTargetFile(Platform); if (DepBase && Base && TargetFile) { LString Rel; if (!Proj->RelativePath(Rel, DepBase)) Rel = DepBase; ToUnixPath(Rel); // Add tag to target name auto Parts = TargetFile.SplitDelimit("."); if (Parts.Length() == 2) TargetFile.Printf("lib%s$(Tag).%s", Parts[0].Get(), Parts[1].Get()); sprintf(Buf, "%s/$(BuildDir)/%s", Rel.Get(), TargetFile.Get()); m.Print(" %s", Buf); LArray AllDeps; Dep->GetAllDependencies(AllDeps, Platform); LAssert(AllDeps.Length() > 0); AllDeps.Sort(StrSort); Rules.Print("%s : ", Buf); for (int i=0; iRelativePath(DepRel, AllDeps[i]) ? DepRel.Get() : AllDeps[i]; ToUnixPath(f); Rules.Print("%s", f); // Add these dependencies to this makefiles dep list if (!DepFiles.Find(f)) DepFiles.Add(f, true); } AllDeps.DeleteArrays(); Rules.Print("\n\texport Build=$(Build); \\\n" "\t$(MAKE) -C %s", Rel.Get()); auto Mk = Dep->GetMakefile(Platform); // RenameMakefileForPlatform(Mk, Platform); if (Mk) { char *DepMakefile = strrchr(Mk, DIR_CHAR); if (DepMakefile) Rules.Print(" -f %s", DepMakefile + 1); } else { Mk = Dep->GetMakefile(Platform); OnError("%s:%i - No makefile for '%s'\n", _FL, Dep->GetFullPath().Get()); } Rules.Print("\n\n"); } uint64 Now = LCurrentTime(); if (Now - Last > 1000) { Last = Now; Log->Print("Building deps %i%%...\n", (int) (((int64)Count+1)*100/Deps.Length())); } } m.Print(" $(Objects)\n" " mkdir -p $(BuildDir)\n" " @echo Linking $(Target) [$(Build)]...\n" " $(CPP)%s%s %s%s -o \\\n" " $(Target) $(Objects) $(Libs)\n", ExtraLinkFlags, ExeFlags, ValidStr(LinkerFlags) ? "-Wl" : "", LinkerFlags.Get()); if (Platform == PlatformHaiku) { // Is there an application icon configured? const char *AppIcon = d->Settings.GetStr(ProjApplicationIcon, NULL, Platform); if (AppIcon) { m.Print(" addattr -f %s -t \"'VICN'\" \"BEOS:ICON\" $(Target)\n", AppIcon); } } LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iGetMakefile(Platform); if (mk) { LAutoString my_base = Proj->GetBasePath(); LAutoString dep_base = d->GetBasePath(); d->CheckExists(dep_base); auto rel_dir = LMakeRelativePath(my_base, dep_base); d->CheckExists(rel_dir); char *mk_leaf = strrchr(mk, DIR_CHAR); m.Print(" +make -C \"%s\" -f \"%s\" clean\n", ToUnixPath(rel_dir ? rel_dir.Get() : dep_base.Get()), ToUnixPath(mk_leaf ? mk_leaf + 1 : mk.Get())); } } m.Print("\n"); } // Shared library else if (!stricmp(TargetType, "DynamicLibrary")) { m.Print("TargetFile = lib$(Target)$(Tag).%s\n" "$(TargetFile) : $(Objects)\n" " mkdir -p $(BuildDir)\n" " @echo Linking $(TargetFile) [$(Build)]...\n" " $(CPP)$s -shared \\\n" " %s%s \\\n" " -o $(BuildDir)/$(TargetFile) \\\n" " $(Objects) \\\n" " $(Libs)\n", PlatformLibraryExt, ValidStr(ExtraLinkFlags) ? "-Wl" : "", ExtraLinkFlags, LinkerFlags.Get()); LString PostBuildCmds = d->Settings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iSettings.GetStr(ProjPostBuildCommands, NULL, Platform); if (ValidStr(PostBuildCmds)) { LString::Array a = PostBuildCmds.Split("\n"); for (unsigned i=0; iCheckExists(p); if (p && !strchr(p, '`')) { if (!LIsRelativePath(p)) { auto a = LMakeRelativePath(Base, p); m.Print("\t%s \\\n", ToPlatformPath(a ? a.Get() : p.Get(), Platform).Get()); } else { m.Print("\t%s \\\n", ToPlatformPath(p, Platform).Get()); } } } m.Print("\t$(BuildDir)\n\n"); const char *OtherMakefileRules = d->Settings.GetStr(ProjMakefileRules, NULL, Platform); if (ValidStr(OtherMakefileRules)) { m.Print("\n%s\n", OtherMakefileRules); } } } else { m.Print("# No files require building.\n"); } Log->Print("...Done: '%s'\n", MakeFile.Get()); if (BuildAfterwards) { if (!Proj->GetApp()->PostEvent(M_START_BUILD)) printf("%s:%i - PostEvent(M_START_BUILD) failed.\n", _FL); } return HasError; } void OnAfterMain() { Proj->GetApp()->PostEvent(M_MAKEFILES_CREATED, (LMessage::Param)Proj); } }; ///////////////////////////////////////////////////////////////////////////////////// NodeSource::~NodeSource() { if (nView) { nView->nSrc = 0; } } NodeView::~NodeView() { if (nSrc) { nSrc->nView = 0; } } ////////////////////////////////////////////////////////////////////////////////// int NodeSort(LTreeItem *a, LTreeItem *b, NativeInt d) { ProjectNode *A = dynamic_cast(a); ProjectNode *B = dynamic_cast(b); if (A && B) { if ( (A->GetType() == NodeDir) ^ (B->GetType() == NodeDir) ) { return A->GetType() == NodeDir ? -1 : 1; } else { return Stricmp(a->GetText(0), b->GetText(0)); } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool ReadVsProjFile(LString File, LString &Ver, LString::Array &Configs) { const char *Ext = LGetExtension(File); if (!Ext || _stricmp(Ext, "vcxproj")) return false; LFile f; if (!f.Open(File, O_READ)) return false; LXmlTree Io; LXmlTag r; if (Io.Read(&r, &f) && r.IsTag("Project")) { Ver = r.GetAttr("ToolsVersion"); LXmlTag *ItemGroup = r.GetChildTag("ItemGroup"); if (ItemGroup) for (auto c: ItemGroup->Children) { if (c->IsTag("ProjectConfiguration")) { char *s = c->GetAttr("Include"); if (s) Configs.New() = s; } } } else return false; return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// BuildThread::BuildThread(IdeProject *proj, char *makefile, bool clean, BuildConfig config, bool all, int wordsize) : LThread("BuildThread") { Proj = proj; Makefile = makefile; Clean = clean; Config = config; All = all; WordSize = wordsize; Arch = DefaultArch; Compiler = DefaultCompiler; AppHnd = Proj->GetApp()->AddDispatch(); LString Cmds = proj->d->Settings.GetStr(ProjPostBuildCommands, NULL); if (ValidStr(Cmds)) PostBuild = Cmds.SplitDelimit("\r\n"); auto Ext = LGetExtension(Makefile); if (Ext && !_stricmp(Ext, "py")) Compiler = PythonScript; else if (Ext && !_stricmp(Ext, "sln")) Compiler = VisualStudio; else if (Ext && !Stricmp(Ext, "xcodeproj")) Compiler = Xcode; else { auto Comp = Proj->GetSettings()->GetStr(ProjCompiler); if (Comp) { // Use the specified compiler... if (!stricmp(Comp, "VisualStudio")) Compiler = VisualStudio; else if (!stricmp(Comp, "MingW")) Compiler = MingW; else if (!stricmp(Comp, "gcc")) Compiler = Gcc; else if (!stricmp(Comp, "cross")) Compiler = CrossCompiler; else if (!stricmp(Comp, "IAR")) Compiler = IAR; else if (!stricmp(Comp, "Cygwin")) Compiler = Cygwin; else LAssert(!"Unknown compiler."); } } if (Compiler == DefaultCompiler) { // Use default compiler for platform... #ifdef WIN32 Compiler = VisualStudio; #else Compiler = Gcc; #endif } Run(); } BuildThread::~BuildThread() { if (SubProc) { bool b = SubProc->Interrupt(); LgiTrace("%s:%i - Sub process interrupt = %i.\n", _FL, b); } else LgiTrace("%s:%i - No sub process to interrupt.\n", _FL); uint64 Start = LCurrentTime(); bool Killed = false; while (!IsExited()) { LSleep(10); if (LCurrentTime() - Start > STOP_BUILD_TIMEOUT && SubProc) { if (Killed) { // Thread is stuck as well... ok kill that too!!! Argh - kill all the things!!!! Terminate(); LgiTrace("%s:%i - Thread killed.\n", _FL); Proj->GetApp()->PostEvent(M_BUILD_DONE); break; } else { // Kill the sub-process... bool b = SubProc->Kill(); Killed = true; LgiTrace("%s:%i - Sub process killed.\n", _FL, b); Start = LCurrentTime(); } } } } ssize_t BuildThread::Write(const void *Buffer, ssize_t Size, int Flags) { if (Proj->GetApp()) { Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::BuildTab); } return Size; } #pragma comment(lib, "version.lib") struct ProjInfo { LString Guid, Name, File; LHashTbl,ssize_t> Configs; }; LString BuildThread::FindExe() { LString::Array p = LGetPath(); if (Compiler == PythonScript) { #if defined(WINDOWS) uint32_t BestVer = 0; #else LString BestVer; #endif static LString Best; if (!Best) { const char *binName[] = {"python3", "python"}; for (int i=0; idwProductVersionMS > BestVer) { BestVer = v->dwProductVersionMS; Best = Path; } } } else if (!Best) { Best = Path; } free(Buf); #else LSubProcess p(Path, "--version"); LStringPipe o; if (p.Start()) p.Communicate(&o); auto Out = o.NewGStr(); auto Ver = Out.SplitDelimit().Last(); printf("Ver=%s\n", Ver.Get()); if (!BestVer || Stricmp(Ver.Get(), BestVer.Get()) > 0) { Best = Path; BestVer = Ver; } break; #endif } } } } return Best; } else if (Compiler == VisualStudio) { // Find the version we need: double fVer = 0.0; ProjInfo *First = NULL; LHashTbl, ProjInfo*> Projects; const char *Ext = LGetExtension(Makefile); if (Ext && !_stricmp(Ext, "sln")) { LFile f; if (f.Open(Makefile, O_READ)) { LString ProjKey = "Project("; LString StartSection = "GlobalSection("; LString EndSection = "EndGlobalSection"; LString Section; ssize_t Pos; LString::Array Ln = f.Read().SplitDelimit("\r\n"); for (size_t i = 0; i < Ln.Length(); i++) { LString s = Ln[i].Strip(); if ((Pos = s.Find(ProjKey)) >= 0) { LString::Array p = s.SplitDelimit("(),="); if (p.Length() > 5) { ProjInfo *i = new ProjInfo; i->Name = p[3].Strip(" \t\""); i->File = p[4].Strip(" \t\'\""); i->Guid = p[5].Strip(" \t\""); if (LIsRelativePath(i->File)) { char f[MAX_PATH_LEN]; LMakePath(f, sizeof(f), Makefile, ".."); LMakePath(f, sizeof(f), f, i->File); if (LFileExists(f)) i->File = f; /* else LAssert(0); */ } if (!First) First = i; Projects.Add(i->Guid, i); } } else if (s.Find(StartSection) >= 0) { auto p = s.SplitDelimit("() \t"); Section = p[1]; } else if (s.Find(EndSection) >= 0) { Section.Empty(); } else if (Section == "ProjectConfigurationPlatforms") { auto p = s.SplitDelimit(". \t"); auto i = Projects.Find(p[0]); if (i) { if (!i->Configs.Find(p[1])) { auto Idx = i->Configs.Length() + 1; i->Configs.Add(p[1], Idx); } } } else if (Section == "SolutionConfigurationPlatforms") { auto p = s.SplitDelimit(); auto config = p[0]; for (auto &it: Projects) { auto proj = it.value; if (!proj->Configs.Find(config)) proj->Configs.Add(config, proj->Configs.Length()+1); } } } } } else if (Ext && !_stricmp(Ext, "vcxproj")) { // ProjFile = Makefile; } else { if (Arch == DefaultArch) { if (sizeof(size_t) == 4) Arch = ArchX32; else Arch = ArchX64; } #ifdef _MSC_VER // Nmake file.. LString NmakePath; switch (_MSC_VER) { #ifdef _MSC_VER_VS2013 case _MSC_VER_VS2013: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\nmake.exe"; break; } #endif #ifdef _MSC_VER_VS2015 case _MSC_VER_VS2015: { if (Arch == ArchX32) NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\nmake.exe"; else NmakePath = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\nmake.exe"; break; } #endif default: { LAssert(!"Impl me."); break; } } if (LFileExists(NmakePath)) { Compiler = Nmake; return NmakePath; } #endif } /* if (ProjFile && LFileExists(ProjFile)) { LString sVer; if (ReadVsProjFile(ProjFile, sVer, BuildConfigs)) { fVer = sVer.Float(); } } */ if (First) { for (auto i: First->Configs) { BuildConfigs[i.value - 1] = i.key; LFile f(First->File, O_READ); LXmlTree t; LXmlTag r; if (t.Read(&r, &f)) { if (r.IsTag("Project")) { auto ToolsVersion = r.GetAttr("ToolsVersion"); if (ToolsVersion) { fVer = atof(ToolsVersion); } } } } } if (fVer > 0.0) { for (int i=0; i LatestVer) { LatestVer = p[2].Float(); Latest = n; } } } if (Latest && LMakePath(p, sizeof(p), p, Latest) && LMakePath(p, sizeof(p), p, "common\\bin\\IarBuild.exe")) { if (LFileExists(p)) return p; } } else if (Compiler == Xcode) { return "/usr/bin/xcodebuild"; } else if (Compiler == Cygwin) { #ifdef WINDOWS LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Cygwin\\Installations"); List n; k.GetValueNames(n); LString s; for (auto i:n) { s = k.GetStr(i); if (s.Find("\\??\\") == 0) s = s(4,-1); if (LDirExists(s)) { CygwinPath = s; break; } } n.DeleteArrays(); LFile::Path p(s, "bin\\make.exe"); if (p.Exists()) return p.GetFull(); #endif } else { if (Compiler == MingW) { // Have a look in the default spot first... const char *Def = "C:\\MinGW\\msys\\1.0\\bin\\make.exe"; if (LFileExists(Def)) { return Def; } } for (int i=0; iGetApp()->GetOptions()->GetValue(OPT_Jobs, Jobs) || Jobs.CastInt32() < 1) Jobs = 2; auto Pos = InitDir.RFind(DIR_STR); if (Pos) InitDir.Length(Pos); LString TmpArgs, Include, Lib, LibPath, Path; if (Compiler == VisualStudio) { // TmpArgs.Printf("\"%s\" /make \"All - Win32 Debug\"", Makefile.Get()); LString BuildConf = "All - Win32 Debug"; if (BuildConfigs.Length()) { auto Key = toString(Config); for (size_t i=0; i= 0) { if (!BuildConf || (c.Find("x64") >= 0 && BuildConf.Find("x64") < 0)) BuildConf = c; } } } TmpArgs.Printf("\"%s\" %s \"%s\"", Makefile.Get(), Clean ? "/Clean" : "/Build", BuildConf.Get()); } else if (Compiler == Nmake) { const char *DefInc[] = { "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\INCLUDE", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\ATLMFC\\INCLUDE", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um", "C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt" }; LString f; #define ADD_PATHS(out, in) \ for (unsigned i=0; iGetChildTag("name") : NULL; if (c) Conf = c->GetContent(); } } TmpArgs.Printf("\"%s\" %s %s -log warnings", Makefile.Get(), Clean ? "-clean" : "-make", Conf.Get()); } else if (Compiler == Xcode) { LString::Array Configs; Configs.SetFixedLength(false); LString a; a.Printf("-list -project \"%s\"", Makefile.Get()); LSubProcess Ls(Exe, a); if (Ls.Start()) { LStringPipe o; Ls.Communicate(&o); auto key = "Build Configurations:"; auto lines = o.NewGStr().SplitDelimit("\n"); int inKey = -1; for (auto l: lines) { int ws = 0; for (int i=0; i=0) inKey = ws; else if (inKey>=0) { if (ws>inKey) { Configs.New() = l.Strip(); } else { inKey = -1; } } // printf("%s\n", l.Get()); } } auto Config = Configs.Length() > 0 ? Configs[0] : LString("Debug"); TmpArgs.Printf("-project \"%s\" -configuration %s", Makefile.Get(), Config.Get()); } else { if (Compiler == Cygwin) { LFile::Path p(CygwinPath, "bin"); Path = p.GetFull(); } if (Compiler == MingW) { LString a; char *Dir = strrchr(MakePath, DIR_CHAR); #if 1 TmpArgs.Printf("/C \"%s\"", Exe.Get()); /* As of MSYS v1.0.18 the support for multiple jobs causes make to hang: http://sourceforge.net/p/mingw/bugs/1950/ http://mingw-users.1079350.n2.nabble.com/MSYS-make-freezes-td7579038.html Apparently it'll be "fixed" in v1.0.19. We'll see. >:-( if (Jobs.CastInt32() > 1) a.Print(" -j %i", Jobs.CastInt32()); */ a.Printf(" -f \"%s\"", Dir ? Dir + 1 : MakePath.Get()); TmpArgs += a; #else TmpArgs.Printf("/C set"); #endif Exe = "C:\\Windows\\System32\\cmd.exe"; } else { if (Jobs.CastInt32()) TmpArgs.Printf("-j %i -f \"%s\"", Jobs.CastInt32(), MakePath.Get()); else TmpArgs.Printf("-f \"%s\"", MakePath.Get()); } if (Clean) { if (All) TmpArgs += " cleanall"; else TmpArgs += " clean"; } if (Config == BuildRelease) TmpArgs += " Build=Release"; } PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::BuildTab); LString Msg; Msg.Printf("Making: %s\n", MakePath.Get()); Proj->GetApp()->PostEvent(M_APPEND_TEXT, (LMessage::Param)NewStr(Msg), AppWnd::BuildTab); LgiTrace("%s %s\n", Exe.Get(), TmpArgs.Get()); if (SubProc.Reset(new LSubProcess(Exe, TmpArgs))) { SubProc->SetNewGroup(false); SubProc->SetInitFolder(InitDir); if (Include) SubProc->SetEnvironment("INCLUDE", Include); if (Lib) SubProc->SetEnvironment("LIB", Lib); if (LibPath) SubProc->SetEnvironment("LIBPATHS", LibPath); if (Path) { LString Cur = getenv("PATH"); LString New = Cur + LGI_PATH_SEPARATOR + Path; SubProc->SetEnvironment("PATH", New); } // SubProc->SetEnvironment("DLL", "1"); if (Compiler == MingW) SubProc->SetEnvironment("PATH", "c:\\MingW\\bin;C:\\MinGW\\msys\\1.0\\bin;%PATH%"); if ((Status = SubProc->Start(true, false))) { // Read all the output char Buf[256]; ssize_t rd; while ( (rd = SubProc->Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } uint32_t ex = SubProc->Wait(); Print("Make exited with %i (0x%x)\n", ex, ex); if (Compiler == IAR && ex == 0 && PostBuild.Length()) { for (auto Cmd : PostBuild) { auto p = Cmd.Split(" ", 1); if (p[0].Equals("cd")) { if (p.Length() > 1) FileDev->SetCurrentFolder(p[1]); else LAssert(!"No folder for cd?"); } else { LSubProcess PostCmd(p[0], p.Length() > 1 ? p[1] : NULL); if (PostCmd.Start(true, false)) { char Buf[256]; ssize_t rd; while ( (rd = PostCmd.Read(Buf, sizeof(Buf))) > 0 ) { Write(Buf, rd); } } } } } } else { // Create a nice error message. LString ErrStr = LErrorCodeToString(SubProc->GetErrorCode()); if (ErrStr) { char *e = ErrStr.Get() + ErrStr.Length(); while (e > ErrStr && strchr(" \t\r\n.", e[-1])) *(--e) = 0; } sprintf_s(ErrBuf, sizeof(ErrBuf), "Running make failed with %i (%s)\n", SubProc->GetErrorCode(), ErrStr.Get()); Err = ErrBuf; } } } else { Err = "Couldn't find program to build makefile."; LgiTrace("%s,%i - %s.\n", _FL, Err); } AppWnd *w = Proj->GetApp(); if (w) { w->PostEvent(M_BUILD_DONE); if (Err) Proj->GetApp()->PostEvent(M_BUILD_ERR, 0, (LMessage::Param)NewStr(Err)); } else LAssert(0); return 0; } ////////////////////////////////////////////////////////////////////////////////// IdeProject::IdeProject(AppWnd *App) : IdeCommon(NULL) { Project = this; d = new IdeProjectPrivate(App, this); Tag = NewStr("Project"); } IdeProject::~IdeProject() { d->App->OnProjectDestroy(this); LXmlTag::Empty(true); DeleteObj(d); } bool IdeProject::OnNode(const char *Path, ProjectNode *Node, bool Add) { if (!Path || !Node) { LAssert(0); return false; } char Full[MAX_PATH_LEN]; if (LIsRelativePath(Path)) { LAutoString Base = GetBasePath(); if (LMakePath(Full, sizeof(Full), Base, Path)) { Path = Full; } } bool Status = false; if (Add) Status = d->Nodes.Add(Path, Node); else Status = d->Nodes.Delete(Path); LString p = Path; if (Status && CheckExists(p)) d->App->OnNode(p, Node, Add ? FindSymbolSystem::FileAdd : FindSymbolSystem::FileRemove); return Status; } void IdeProject::ShowFileProperties(const char *File) { ProjectNode *Node = NULL; // char *fp = FindFullPath(File, &Node); if (Node) { Node->OnProperties(); } } const char *IdeProject::GetFileComment() { return d->Settings.GetStr(ProjCommentFile); } const char *IdeProject::GetFunctionComment() { return d->Settings.GetStr(ProjCommentFunction); } IdeProject *IdeProject::GetParentProject() { return d->ParentProject; } void IdeProject::SetParentProject(IdeProject *p) { d->ParentProject = p; } bool IdeProject::GetChildProjects(List &c) { CollectAllSubProjects(c); return c.Length() > 0; } bool IdeProject::RelativePath(LString &Out, const char *In, bool Debug) { if (!In) return false; auto Base = GetBasePath(); if (!Base) return false; CheckExists(Base); if (Debug) LgiTrace("XmlBase='%s'\n In='%s'\n", Base.Get(), In); LToken b(Base, DIR_STR); LToken i(In, DIR_STR); char out[MAX_PATH_LEN] = ""; if (Debug) LgiTrace("Len %i-%i\n", b.Length(), i.Length()); auto ILen = i.Length() + (LDirExists(In) ? 0 : 1); auto Max = MIN(b.Length(), ILen); int Common = 0; for (; Common < Max; Common++) { #ifdef WIN32 #define StrCompare stricmp #else #define StrCompare strcmp #endif if (Debug) LgiTrace("Cmd '%s'-'%s'\n", b[Common], i[Common]); if (StrCompare(b[Common], i[Common]) != 0) { break; } } if (Debug) LgiTrace("Common=%i\n", Common); if (Common > 0) { if (Common < b.Length()) { out[0] = 0; auto Back = b.Length() - Common; if (Debug) LgiTrace("Back=%i\n", (int)Back); for (int n=0; nSettings.GetStr(ProjExe); - if (PExe) + auto PExe = d->Settings.GetStr(ProjExe); + LString Exe; + if (!PExe) { - if (LIsRelativePath(PExe)) + // Use the default exe name? + Exe = GetExecutable(GetCurrentPlatform()); + if (Exe) { - LAutoString Base = GetBasePath(); - if (Base) - { - LMakePath(Path, Len, Base, PExe); - } - else return false; + printf("Exe='%s'\n", Exe.Get()); + PExe = Exe; } + } + + if (!PExe) + return false; + + if (LIsRelativePath(PExe)) + { + auto Base = GetBasePath(); + if (Base) + LMakePath(Path, Len, Base, PExe); else - { - strcpy_s(Path, Len, PExe); - } - - return true; + return false; } - else return false; + else + { + strcpy_s(Path, Len, PExe); + } + + return true; } LString IdeProject::GetMakefile(IdePlatform Platform) { LString Path; const char *PMakefile = d->Settings.GetStr(ProjMakefile, NULL, Platform); if (PMakefile) { if (LIsRelativePath(PMakefile)) { LAutoString Base = GetBasePath(); if (Base) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Base, PMakefile); Path = p; } } else { Path = PMakefile; } } return Path; } void IdeProject::Clean(bool All, BuildConfig Config) { if (!d->Thread && d->Settings.GetStr(ProjMakefile)) { auto m = GetMakefile(PlatformCurrent); if (m) { CheckExists(m); d->Thread.Reset(new BuildThread(this, m, true, Config, All, sizeof(ssize_t)*8)); } } } char *QuoteStr(char *s) { LStringPipe p(256); while (s && *s) { if (*s == ' ') { p.Push("\\ ", 2); } else p.Push(s, 1); s++; } return p.NewStr(); } class ExecuteThread : public LThread, public LStream, public LCancel { IdeProject *Proj; LString Exe, Args, Path; int Len; ExeAction Act; int AppHnd; public: ExecuteThread(IdeProject *proj, const char *exe, const char *args, char *path, ExeAction act) : LThread("ExecuteThread") { Len = 32 << 10; Proj = proj; Act = act; Exe = exe; Args = args; Path = path; DeleteOnExit = true; AppHnd = proj->GetApp()->AddDispatch(); Run(); } ~ExecuteThread() { Cancel(); while (!IsExited()) LSleep(1); } int Main() override { PostThreadEvent(AppHnd, M_SELECT_TAB, AppWnd::OutputTab); PostThreadEvent(AppHnd, M_APPEND_TEXT, 0, AppWnd::OutputTab); if (Exe) { if (Act == ExeDebug) { LSubProcess sub("kdbg", Exe); if (Path) sub.SetInitFolder(Path); if (sub.Start()) sub.Communicate(this, NULL, this); } else if (Act == ExeValgrind) { #ifdef LINUX LString ExePath = Proj->GetExecutable(GetCurrentPlatform()); if (ExePath) { char Path[MAX_PATH_LEN]; char *ExeLeaf = LGetLeaf(Exe); strcpy_s(Path, sizeof(Path), ExeLeaf ? ExeLeaf : Exe.Get()); LTrimDir(Path); char *Term = 0; char *WorkDir = 0; char *Execute = 0; switch (LGetWindowManager()) { case WM_Kde: Term = "konsole"; WorkDir = "--workdir "; Execute = "-e"; break; case WM_Gnome: Term = "gnome-terminal"; WorkDir = "--working-directory="; Execute = "-x"; break; } if (Term && WorkDir && Execute) { char *e = QuoteStr(ExePath); char *p = QuoteStr(Path); char *a = Proj->GetExeArgs() ? Proj->GetExeArgs() : (char*)""; char Args[512]; sprintf(Args, "%s%s " "--noclose " "%s valgrind --tool=memcheck --num-callers=20 %s %s", WorkDir, p, Execute, e, a); LExecute(Term, Args); } } #endif } else { LSubProcess sub(Exe, Args); if (Path) sub.SetInitFolder(Path); if (sub.Start()) sub.Communicate(this, NULL, this); } } return 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override { if (Len <= 0) return 0; PostThreadEvent(AppHnd, M_APPEND_TEXT, (LMessage::Param)NewStr((char*)Buffer, Size), AppWnd::OutputTab); Len -= Size; return Size; } }; -LDebugContext *IdeProject::Execute(ExeAction Act) +LDebugContext *IdeProject::Execute(ExeAction Act, LString *ErrMsg) { - LAutoString Base = GetBasePath(); - if (d->Settings.GetStr(ProjExe) && - Base) + auto Base = GetBasePath(); + + if (!Base) { - char e[MAX_PATH_LEN]; - if (GetExePath(e, sizeof(e))) - { - if (LFileExists(e)) - { - const char *Args = d->Settings.GetStr(ProjArgs); - const char *Env = d->Settings.GetStr(ProjEnv); - LString InitDir = d->Settings.GetStr(ProjInitDir); - int RunAsAdmin = d->Settings.GetInt(ProjDebugAdmin); - if (Act == ExeDebug) - { - if (InitDir && LIsRelativePath(InitDir)) - { - LFile::Path p(Base); - p += InitDir; - InitDir = p.GetFull(); - } - - return new LDebugContext(d->App, this, e, Args, RunAsAdmin != 0, Env, InitDir); - } - else - { - new ExecuteThread(this, e, Args, Base, Act); - } - } - else - { - LgiMsg(Tree, "Executable '%s' doesn't exist.\n", AppName, MB_OK, e); - } - } + if (ErrMsg) *ErrMsg = "No base path for project."; + return NULL; + } + + char e[MAX_PATH_LEN]; + if (!GetExePath(e, sizeof(e))) + { + if (ErrMsg) *ErrMsg = "GetExePath failed."; + return NULL; } + if (!LFileExists(e)) + { + if (ErrMsg) ErrMsg->Printf("Executable '%s' doesn't exist.\n", e); + return NULL; + } + + const char *Args = d->Settings.GetStr(ProjArgs); + const char *Env = d->Settings.GetStr(ProjEnv); + LString InitDir = d->Settings.GetStr(ProjInitDir); + int RunAsAdmin = d->Settings.GetInt(ProjDebugAdmin); + if (Act == ExeDebug) + { + if (InitDir && LIsRelativePath(InitDir)) + { + LFile::Path p(Base); + p += InitDir; + InitDir = p.GetFull(); + } + + return new LDebugContext(d->App, this, e, Args, RunAsAdmin != 0, Env, InitDir); + } + + // Run without debugging... + new ExecuteThread(this, e, Args, Base, Act); return NULL; } bool IdeProject::IsMakefileAScript() { auto m = GetMakefile(PlatformCurrent); if (m) { auto Ext = LGetExtension(m); if (!Stricmp(Ext, "py")) { // Not a makefile but a build script... can't update. return true; } } return false; } bool IdeProject::IsMakefileUpToDate() { if (IsMakefileAScript()) return true; // We can't know if it's up to date... List Proj; GetChildProjects(Proj); Proj.Insert(this); for (auto p: Proj) { // Is the project file modified after the makefile? auto Proj = p->GetFullPath(); uint64 ProjModTime = 0, MakeModTime = 0; LDirectory dir; if (dir.First(Proj)) { ProjModTime = dir.GetLastWriteTime(); dir.Close(); } auto m = p->GetMakefile(PlatformCurrent); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); break; } auto Ext = LGetExtension(m); if (!Stricmp(Ext, "py")) { // Not a makefile but a build script... can't update. return true; } if (dir.First(m)) { MakeModTime = dir.GetLastWriteTime(); dir.Close(); } // printf("Proj=%s - Timestamps " LGI_PrintfInt64 " - " LGI_PrintfInt64 "\n", Proj.Get(), ProjModTime, MakeModTime); if (ProjModTime != 0 && MakeModTime != 0 && ProjModTime > MakeModTime) { // Need to rebuild the makefile... return false; } } return true; } bool IdeProject::FindDuplicateSymbols() { LStream *Log = d->App->GetBuildLog(); Log->Print("FindDuplicateSymbols starting...\n"); List Proj; CollectAllSubProjects(Proj); Proj.Insert(this); int Lines = 0; LHashTbl,int64> Map(200000); int Found = 0; for (auto p: Proj) { LString s = p->GetExecutable(GetCurrentPlatform()); if (s) { LString Args; Args.Printf("--print-size --defined-only -C %s", s.Get()); LSubProcess Nm("nm", Args); if (Nm.Start(true, false)) { char Buf[256]; LStringPipe q; for (ssize_t Rd = 0; (Rd = Nm.Read(Buf, sizeof(Buf))); ) q.Write(Buf, Rd); LString::Array a = q.NewGStr().SplitDelimit("\r\n"); LHashTbl,bool> Local(200000); for (auto &Ln: a) { LString::Array p = Ln.SplitDelimit(" \t", 3); if (!Local.Find(p.Last())) { Local.Add(p.Last(), true); // const char *Sz = p[1]; int64 Ours = p[1].Int(16); int64 Theirs = Map.Find(p.Last()); if (Theirs >= 0) { if (Ours != Theirs) { if (Found++ < 100) Log->Print(" %s (" LPrintfInt64 " -> " LPrintfInt64 ")\n", p.Last().Get(), Ours, Theirs); } } else if (Ours >= 0) { Map.Add(p.Last(), Ours); } else { printf("Bad line: %s\n", Ln.Get()); } } Lines++; } } } else printf("%s:%i - GetExecutable failed.\n", _FL); } /* char *Sym; for (int Count = Map.First(&Sym); Count; Count = Map.Next(&Sym)) { if (Count > 1) Log->Print(" %i: %s\n", Count, Sym); } */ Log->Print("FindDuplicateSymbols finished (%i lines)\n", Lines); return false; } bool IdeProject::FixMissingFiles() { FixMissingFilesDlg(this); return true; } void IdeProject::Build(bool All, BuildConfig Config) { if (d->Thread) { d->App->GetBuildLog()->Print("Error: Already building (%s:%i)\n", _FL); return; } auto m = GetMakefile(PlatformCurrent); CheckExists(m); if (!m) { d->App->GetBuildLog()->Print("Error: no makefile? (%s:%i)\n", _FL); return; } if (GetApp()) GetApp()->PostEvent(M_APPEND_TEXT, 0, 0); SetClean([&](bool ok) { if (!ok) return; if (!IsMakefileUpToDate()) CreateMakefile(GetCurrentPlatform(), true); else // Start the build thread... d->Thread.Reset ( new BuildThread ( this, m, false, Config, All, sizeof(size_t)*8 ) ); }); } void IdeProject::StopBuild() { d->Thread.Reset(); } bool IdeProject::Serialize(bool Write) { return true; } AppWnd *IdeProject::GetApp() { return d->App; } const char *IdeProject::GetIncludePaths() { return d->Settings.GetStr(ProjIncludePaths); } const char *IdeProject::GetPreDefinedValues() { return d->Settings.GetStr(ProjDefines); } const char *IdeProject::GetExeArgs() { return d->Settings.GetStr(ProjArgs); } LString IdeProject::GetExecutable(IdePlatform Platform) { LString Bin = d->Settings.GetStr(ProjExe, NULL, Platform); auto TargetType = d->Settings.GetStr(ProjTargetType, NULL, Platform); auto Base = GetBasePath(); if (Bin) { if (LIsRelativePath(Bin) && Base) { char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Base, Bin)) Bin = p; } return Bin; } // Create binary name from target: auto Target = GetTargetName(Platform); if (Target) { bool IsLibrary = Stristr(TargetType, "library"); int BuildMode = d->App->GetBuildMode(); const char *Name = BuildMode ? "Release" : "Debug"; const char *Postfix = BuildMode ? "" : "d"; switch (Platform) { case PlatformWin: { if (IsLibrary) Bin.Printf("%s%s.dll", Target.Get(), Postfix); else Bin = Target; break; } case PlatformMac: { if (IsLibrary) Bin.Printf("lib%s%s.dylib", Target.Get(), Postfix); else Bin = Target; break; } case PlatformLinux: case PlatformHaiku: { if (IsLibrary) Bin.Printf("lib%s%s.so", Target.Get(), Postfix); else Bin = Target; break; } default: { LAssert(0); printf("%s:%i - Unknown platform.\n", _FL); return LString(); } } // Find the actual file... if (!Base) { printf("%s:%i - GetBasePath failed.\n", _FL); return LString(); } char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, Name); LMakePath(Path, sizeof(Path), Path, Bin); if (LFileExists(Path)) Bin = Path; else printf("%s:%i - '%s' doesn't exist.\n", _FL, Path); return Bin; } return LString(); } const char *IdeProject::GetFileName() { return d->FileName; } int IdeProject::GetPlatforms() { return PLATFORM_ALL; } LAutoString IdeProject::GetFullPath() { LAutoString Status; if (!d->FileName) { // LAssert(!"No path."); return Status; } LArray sections; IdeProject *proj = this; while ( proj && proj->GetFileName() && LIsRelativePath(proj->GetFileName())) { sections.AddAt(0, proj->GetFileName()); proj = proj->GetParentProject(); } if (!proj) { // LAssert(!"All projects have a relative path?"); return Status; // No absolute path in the parent projects? } char p[MAX_PATH_LEN]; strcpy_s(p, sizeof(p), proj->GetFileName()); // Copy the base path if (sections.Length() > 0) LTrimDir(p); // Trim off the filename for (int i=0; iFileName.Empty(); d->UserFile.Empty(); d->App->GetTree()->Insert(this); ProjectNode *f = new ProjectNode(this); if (f) { f->SetName("Source"); f->SetType(NodeDir); InsertTag(f); } f = new ProjectNode(this); if (f) { f->SetName("Headers"); f->SetType(NodeDir); InsertTag(f); } d->Settings.Set(ProjEditorTabSize, 4); d->Settings.Set(ProjEditorIndentSize, 4); d->Settings.Set(ProjEditorUseHardTabs, true); d->Dirty = true; Expanded(true); } ProjectStatus IdeProject::OpenFile(const char *FileName) { LProfile Prof("IdeProject::OpenFile"); Prof.HideResultsIfBelow(1000); Empty(); Prof.Add("Init"); d->UserNodeFlags.Empty(); if (LIsRelativePath(FileName)) { char p[MAX_PATH_LEN]; getcwd(p, sizeof(p)); LMakePath(p, sizeof(p), p, FileName); d->FileName = p; } else { d->FileName = FileName; } d->UserFile = d->FileName + "." + LCurrentUserName(); if (!d->FileName) { LgiTrace("%s:%i - No filename.\n", _FL); return OpenError; } Prof.Add("FileOpen"); LFile f; LString FullPath = d->FileName.Get(); if (CheckExists(FullPath) && !f.Open(FullPath, O_READWRITE)) { LgiTrace("%s:%i - Error: Can't open '%s'.\n", _FL, FullPath.Get()); return OpenError; } Prof.Add("Xml"); LXmlTree x; LXmlTag r; if (!x.Read(&r, &f)) { LgiTrace("%s:%i - Error: Can't read XML: %s\n", _FL, x.GetErrorMsg()); LgiMsg(Tree, x.GetErrorMsg(), AppName); return OpenError; } Prof.Add("Progress Setup"); #if DEBUG_OPEN_PROGRESS int64 Nodes = r.CountTags(); LProgressDlg Prog(d->App, 1000); Prog.SetDescription("Loading project..."); Prog.SetLimits(0, Nodes); Prog.SetYieldTime(1000); Prog.SetAlwaysOnTop(true); #endif Prof.Add("UserFile"); if (LFileExists(d->UserFile)) { LFile Uf; if (Uf.Open(d->UserFile, O_READ)) { LString::Array Ln = Uf.Read().SplitDelimit("\n"); for (unsigned i=0; iUserNodeFlags.Add((int)p[0].Int(), (int)p[1].Int(16)); } } } if (!r.IsTag("Project")) return OpenError; Prof.Add("OnOpen"); bool Ok = OnOpen( #if DEBUG_OPEN_PROGRESS &Prog, #else NULL, #endif &r); #if DEBUG_OPEN_PROGRESS if (Prog.IsCancelled()) return OpenCancel; else #endif if (!Ok) return OpenError; Prof.Add("Insert"); d->App->GetTree()->Insert(this); Expanded(true); Prof.Add("Serialize"); d->Settings.Serialize(&r, false /* read */); return OpenOk; } bool IdeProject::SaveFile() { auto Full = GetFullPath(); if (ValidStr(Full) && d->Dirty) { LFile f; if (f.Open(Full, O_WRITE)) { f.SetSize(0); LXmlTree x; d->Settings.Serialize(this, true /* write */); if (x.Write(this, &f)) d->Dirty = false; else LgiTrace("%s:%i - Failed to write XML.\n", _FL); } else LgiTrace("%s:%i - Couldn't open '%s' for writing.\n", _FL, Full.Get()); } if (d->UserFileDirty) { LFile f; LAssert(d->UserFile.Get()); if (f.Open(d->UserFile, O_WRITE)) { f.SetSize(0); // Save user file details.. // int Id; // for (int Flags = d->UserNodeFlags.First(&Id); Flags >= 0; Flags = d->UserNodeFlags.Next(&Id)) for (auto i : d->UserNodeFlags) { f.Print("%i,%x\n", i.key, i.value); } d->UserFileDirty = false; } } printf("\tIdeProject::SaveFile %i %i\n", d->Dirty, d->UserFileDirty); return !d->Dirty && !d->UserFileDirty; } void IdeProject::SetDirty() { d->Dirty = true; d->App->OnProjectChange(); } void IdeProject::SetUserFileDirty() { d->UserFileDirty = true; } bool IdeProject::GetExpanded(int Id) { int f = d->UserNodeFlags.Find(Id); return f >= 0 ? f : false; } void IdeProject::SetExpanded(int Id, bool Exp) { if (d->UserNodeFlags.Find(Id) != (int)Exp) { d->UserNodeFlags.Add(Id, Exp); SetUserFileDirty(); } } int IdeProject::AllocateId() { return d->NextNodeId++; } template bool CheckExists(LAutoString Base, T &p, Fn Setter, bool Debug) { LFile::Path Full; bool WasRel = LIsRelativePath(p); if (WasRel) { Full = Base.Get(); Full += p.Get(); } else Full = p.Get(); bool Ret = Full.Exists(); if (!Ret) { // Is the case wrong? for (int i=1; i%s\n", i, Leaf.Get(), dir.GetName()); Leaf = dir.GetName(); Matched = true; break; } } if (!Matched) break; } } if ((Ret = Full.Exists())) { LString Old = p.Get(); if (WasRel) { auto r = LMakeRelativePath(Base, Full); Setter(p, r); } else { Setter(p, Full.GetFull()); } if (Debug) printf("%s -> %s\n", Old.Get(), p.Get()); } } if (Debug) printf("CheckExists '%s' = %i\n", Full.GetFull().Get(), Ret); return Ret; } bool IdeProject::CheckExists(LString &p, bool Debug) { return ::CheckExists(GetBasePath(), p, [](LString &o, const char *i) { o = i; }, Debug); } bool IdeProject::CheckExists(LAutoString &p, bool Debug) { return ::CheckExists(GetBasePath(), p, [](LAutoString &o, const char *i) { o.Reset(NewStr(i)); }, Debug); } bool IdeProject::GetClean() { for (auto i: *this) { ProjectNode *p = dynamic_cast(i); if (p && !p->GetClean()) return false; } return !d->Dirty && !d->UserFileDirty; } void IdeProject::SetClean(std::function OnDone) { auto CleanNodes = [&]() { for (auto i: *this) { ProjectNode *p = dynamic_cast(i); if (!p) break; p->SetClean(); } if (OnDone) OnDone(true); }; if (d->Dirty || d->UserFileDirty) { if (ValidStr(d->FileName)) SaveFile(); else { LFileSelect s; s.Parent(Tree); s.Name("Project.xml"); s.Save([&](auto s, auto ok) { if (!ok) { if (OnDone) OnDone(false); return; } d->FileName = s->Name(); d->UserFile = d->FileName + "." + LCurrentUserName(); d->App->OnFile(d->FileName, true); Update(); CleanNodes(); }); return; } } CleanNodes(); } const char *IdeProject::GetText(int Col) { if (d->FileName) { char *s = strrchr(d->FileName, DIR_CHAR); return s ? s + 1 : d->FileName.Get(); } return Untitled; } int IdeProject::GetImage(int Flags) { return 0; } void IdeProject::Empty() { LXmlTag *t; while (Children.Length() > 0 && (t = Children[0])) { ProjectNode *n = dynamic_cast(t); if (n) { n->Remove(); } DeleteObj(t); } } LXmlTag *IdeProject::Create(char *Tag) { if (!stricmp(Tag, TagSettings)) return NULL; return new ProjectNode(this); } void IdeProject::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu Sub; Sub.AppendItem("New Folder", IDM_NEW_FOLDER); Sub.AppendItem("New Web Folder", IDM_WEB_FOLDER); Sub.AppendSeparator(); Sub.AppendItem("Build", IDM_BUILD); Sub.AppendItem("Clean Project", IDM_CLEAN_PROJECT); Sub.AppendItem("Clean All", IDM_CLEAN_ALL); Sub.AppendItem("Rebuild Project", IDM_REBUILD_PROJECT); Sub.AppendItem("Rebuild All", IDM_REBUILD_ALL); Sub.AppendSeparator(); Sub.AppendItem("Sort Children", IDM_SORT_CHILDREN); Sub.AppendSeparator(); Sub.AppendItem("Settings", IDM_SETTINGS, true); Sub.AppendItem("Insert Dependency", IDM_INSERT_DEP); m.ToScreen(); auto c = _ScrollPos(); m.x -= c.x; m.y -= c.y; switch (Sub.Float(Tree, m.x, m.y)) { case IDM_NEW_FOLDER: { LInput Name(Tree, "", "Name:", AppName); Name.DoModal([&](auto d, auto code) { GetSubFolder(this, Name.GetStr(), true); }); break; } case IDM_WEB_FOLDER: { WebFldDlg *Dlg = new WebFldDlg(Tree, 0, 0, 0); Dlg->DoModal([&](auto d, auto code) { if (Dlg->Ftp && Dlg->Www) { IdeCommon *f = GetSubFolder(this, Dlg->Name, true); if (f) { f->SetAttr(OPT_Ftp, Dlg->Ftp); f->SetAttr(OPT_Www, Dlg->Www); } } delete Dlg; }); break; } case IDM_BUILD: { StopBuild(); Build(true, d->App->GetBuildMode()); break; } case IDM_CLEAN_PROJECT: { Clean(false, d->App->GetBuildMode()); break; } case IDM_CLEAN_ALL: { Clean(true, d->App->GetBuildMode()); break; } case IDM_REBUILD_PROJECT: { StopBuild(); Clean(false, d->App->GetBuildMode()); Build(false, d->App->GetBuildMode()); break; } case IDM_REBUILD_ALL: { StopBuild(); Clean(true, d->App->GetBuildMode()); Build(true, d->App->GetBuildMode()); break; } case IDM_SORT_CHILDREN: { SortChildren(); Project->SetDirty(); break; } case IDM_SETTINGS: { d->Settings.Edit(Tree, [&]() { SetDirty(); }); break; } case IDM_INSERT_DEP: { LFileSelect *s = new LFileSelect; s->Parent(Tree); s->Type("Project", "*.xml"); s->Open([&](auto s, bool ok) { if (!ok) return; ProjectNode *New = new ProjectNode(this); if (New) { New->SetFileName(s->Name()); New->SetType(NodeDependancy); InsertTag(New); SetDirty(); } delete s; }); break; } } } } char *IdeProject::FindFullPath(const char *File, ProjectNode **Node) { char *Full = 0; for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; ProjectNode *n = c->FindFile(File, &Full); if (n) { if (Node) *Node = n; break; } } return Full; } bool IdeProject::HasNode(ProjectNode *Node) { for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; if (c->HasNode(Node)) return true; } return false; } bool IdeProject::GetAllNodes(LArray &Nodes) { for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; c->AddNodes(Nodes); } return true; } bool IdeProject::InProject(bool FuzzyMatch, const char *Path, bool Open, IdeDoc **Doc) { if (!Path) return false; // Search complete path first... ProjectNode *n = d->Nodes.Find(Path); if (!n && FuzzyMatch) { // No match, do partial matching. const char *Leaf = LGetLeaf(Path); auto PathLen = strlen(Path); auto LeafLen = strlen(Leaf); uint32_t MatchingScore = 0; // Traverse all nodes and try and find the best fit. // const char *p; // for (ProjectNode *Cur = d->Nodes.First(&p); Cur; Cur = d->Nodes.Next(&p)) for (auto Cur : d->Nodes) { int CurPlatform = Cur.value->GetPlatforms(); uint32_t Score = 0; if (stristr(Cur.key, Path)) { Score += PathLen; } else if (stristr(Cur.key, Leaf)) { Score += LeafLen; } const char *pLeaf = LGetLeaf(Cur.key); if (pLeaf && !stricmp(pLeaf, Leaf)) { Score |= 0x40000000; } if (Score && (CurPlatform & PLATFORM_CURRENT) != 0) { Score |= 0x80000000; } if (Score > MatchingScore) { MatchingScore = Score; n = Cur.value; } } } if (n && Doc) { *Doc = n->Open(); } return n != 0; } char *GetQuotedStr(char *Str) { char *s = strchr(Str, '\"'); if (s) { s++; char *e = strchr(s, '\"'); if (e) { return NewStr(s, e - s); } } return 0; } void IdeProject::ImportDsp(const char *File) { if (File && LFileExists(File)) { char Base[256]; strcpy(Base, File); LTrimDir(Base); char *Dsp = LReadTextFile(File); if (Dsp) { LToken Lines(Dsp, "\r\n"); IdeCommon *Current = this; bool IsSource = false; for (int i=0; iGetSubFolder(this, Folder, true); if (Sub) { Current = Sub; } DeleteArray(Folder); } } else if (strnicmp(L, "# End Group", 11) == 0) { IdeCommon *Parent = dynamic_cast(Current->GetParent()); if (Parent) { Current = Parent; } } else if (strnicmp(L, "# Begin Source", 14) == 0) { IsSource = true; } else if (strnicmp(L, "# End Source", 12) == 0) { IsSource = false; } else if (Current && IsSource && strncmp(L, "SOURCE=", 7) == 0) { ProjectNode *New = new ProjectNode(this); if (New) { char *Src = 0; if (strchr(L, '\"')) { Src = GetQuotedStr(L); } else { Src = NewStr(L + 7); } if (Src) { // Make absolute path char Abs[256]; LMakePath(Abs, sizeof(Abs), Base, ToUnixPath(Src)); // Make relitive path New->SetFileName(Src); DeleteArray(Src); } Current->InsertTag(New); SetDirty(); } } } DeleteArray(Dsp); } } } IdeProjectSettings *IdeProject::GetSettings() { return &d->Settings; } bool IdeProject::BuildIncludePaths(LArray &Paths, bool Recurse, bool IncludeSystem, IdePlatform Platform) { List Projects; if (Recurse) GetChildProjects(Projects); Projects.Insert(this, 0); LHashTbl, bool> Map; for (auto p: Projects) { LString ProjInclude = d->Settings.GetStr(ProjIncludePaths, NULL, Platform); LAutoString Base = p->GetBasePath(); const char *Delim = ",;\r\n"; LString::Array In, Out; LString::Array a = ProjInclude.SplitDelimit(Delim); In = a; if (IncludeSystem) { LString SysInclude = d->Settings.GetStr(ProjSystemIncludes, NULL, Platform); a = SysInclude.SplitDelimit(Delim); In.SetFixedLength(false); In.Add(a); } for (unsigned i=0; i 1 ? a[1].Get() : NULL); LStringPipe Buf; if (Proc.Start()) { Proc.Communicate(&Buf); LString result = Buf.NewGStr(); a = result.Split(" \t\r\n"); for (int i=0; i Nodes; if (p->GetAllNodes(Nodes)) { auto Base = p->GetFullPath(); if (Base) { LTrimDir(Base); for (auto &n: Nodes) { if (n->GetType() == NodeHeader && // Only look at headers. (n->GetPlatforms() & (1 << Platform)) != 0) // Exclude files not on this platform. { auto f = n->GetFileName(); char p[MAX_PATH_LEN]; if (f && LMakePath(p, sizeof(p), Base, f)) { char *l = strrchr(p, DIR_CHAR); if (l) *l = 0; if (!Map.Find(p)) { Map.Add(p, true); } } } } } } } // char *p; // for (bool b = Map.First(&p); b; b = Map.Next(&p)) for (auto p : Map) Paths.Add(p.key); return true; } void IdeProjectPrivate::CollectAllFiles(LTreeNode *Base, LArray &Files, bool SubProjects, int Platform) { for (auto i:*Base) { IdeProject *Proj = dynamic_cast(i); if (Proj) { if (Proj->GetParentProject() && !SubProjects) { continue; } } else { ProjectNode *p = dynamic_cast(i); if (p) { if (p->GetType() == NodeSrc || p->GetType() == NodeHeader) { if (p->GetPlatforms() & Platform) { Files.Add(p); } } } } CollectAllFiles(i, Files, SubProjects, Platform); } } LString IdeProject::GetTargetName(IdePlatform Platform) { LString Status; const char *t = d->Settings.GetStr(ProjTargetName, NULL, Platform); if (ValidStr(t)) { // Take target name from the settings Status = t; } else { char *s = strrchr(d->FileName, DIR_CHAR); if (s) { // Generate the target executable name char Target[MAX_PATH_LEN]; strcpy_s(Target, sizeof(Target), s + 1); s = strrchr(Target, '.'); if (s) *s = 0; strlwr(Target); s = Target; for (char *i = Target; *i; i++) { if (*i != '.' && *i != ' ') { *s++ = *i; } } *s = 0; Status = Target; } } return Status; } LString IdeProject::GetTargetFile(IdePlatform Platform) { LString Target = GetTargetName(PlatformCurrent); if (!Target) { LAssert(!"No target?"); return NULL; } const char *TargetType = d->Settings.GetStr(ProjTargetType); if (!TargetType) { LAssert(!"Needs a target type."); return NULL; } if (!stricmp(TargetType, "Executable")) { return Target; } else if (!stricmp(TargetType, "DynamicLibrary")) { char t[MAX_PATH_LEN]; auto DefExt = PlatformDynamicLibraryExt(Platform); strcpy_s(t, sizeof(t), Target); char *ext = LGetExtension(t); if (!ext) sprintf(t + strlen(t), ".%s", DefExt); else if (stricmp(ext, DefExt)) strcpy(ext, DefExt); return t; } else if (!stricmp(TargetType, "StaticLibrary")) { LString Ret; Ret.Printf("%s.%s", Target.Get(), PlatformSharedLibraryExt(Platform)); return Ret; } return NULL; } struct ProjDependency { bool Scanned; LAutoString File; ProjDependency(const char *f) { Scanned = false; File.Reset(NewStr(f)); } }; bool IdeProject::GetAllDependencies(LArray &Files, IdePlatform Platform) { if (!GetTree()->Lock(_FL)) return false; LHashTbl, ProjDependency*> Deps; LAutoString Base = GetBasePath(); // Build list of all the source files... LArray Src; CollectAllSource(Src, Platform); // Get all include paths LArray IncPaths; BuildIncludePaths(IncPaths, false, false, Platform); // Add all source to dependencies for (int i=0; i Unscanned; do { // Find all the unscanned dependencies Unscanned.Length(0); // for (ProjDependency *d = Deps.First(); d; d = Deps.Next()) for (auto d : Deps) { if (!d.value->Scanned) Unscanned.Add(d.value); } for (int i=0; iScanned = true; char *Src = d->File; char Full[MAX_PATH_LEN]; if (LIsRelativePath(d->File)) { LMakePath(Full, sizeof(Full), Base, d->File); Src = Full; } LArray SrcDeps; if (GetDependencies(Src, IncPaths, SrcDeps, Platform)) { for (int n=0; n 0); // for (ProjDependency *d = Deps.First(); d; d = Deps.Next()) for (auto d : Deps) { Files.Add(d.value->File.Release()); } Deps.DeleteObjects(); GetTree()->Unlock(); return true; } bool IdeProject::GetDependencies(const char *InSourceFile, LArray &IncPaths, LArray &Files, IdePlatform Platform) { LString SourceFile = InSourceFile; if (!CheckExists(SourceFile)) { LgiTrace("%s:%i - can't read '%s'\n", _FL, SourceFile.Get()); return false; } LAutoString c8(LReadTextFile(SourceFile)); if (!c8) return false; LArray Headers; if (!BuildHeaderList(c8, Headers, IncPaths, false)) return false; for (int n=0; nCreateMakefile) { if (d->CreateMakefile->IsExited()) d->CreateMakefile.Reset(); else { d->App->GetBuildLog()->Print("%s:%i - Makefile thread still running.\n", _FL); return false; } } if (Platform == PlatformCurrent) Platform = GetCurrentPlatform(); return d->CreateMakefile.Reset(new MakefileThread(d, Platform, BuildAfterwards)); } void IdeProject::OnMakefileCreated() { if (d->CreateMakefile) { d->CreateMakefile.Reset(); if (MakefileThread::Instances == 0) GetApp()->PostEvent(M_LAST_MAKEFILE_CREATED); } } //////////////////////////////////////////////////////////////////////////////////////////// IdeTree::IdeTree() : LTree(IDC_PROJECT_TREE, 0, 0, 100, 100) { Hit = 0; MultiSelect(true); } void IdeTree::OnCreate() { SetWindow(this); } void IdeTree::OnDragExit() { SelectDropTarget(0); } int IdeTree::WillAccept(LDragFormats &Formats, LPoint p, int KeyState) { static bool First = true; Formats.SupportsFileDrops(); Formats.Supports(NODE_DROP_FORMAT); First = false; if (Formats.Length() == 0) { LgiTrace("%s:%i - No valid drop formats.\n", _FL); return DROPEFFECT_NONE; } Hit = ItemAtPoint(p.x, p.y); if (!Hit) { SelectDropTarget(NULL); return DROPEFFECT_NONE; } if (Formats.HasFormat(LGI_FileDropFormat)) { SelectDropTarget(Hit); return DROPEFFECT_LINK; } IdeCommon *Src = dynamic_cast(Selection()); IdeCommon *Dst = dynamic_cast(Hit); if (Src && Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) return DROPEFFECT_NONE; } } // Valid target SelectDropTarget(Hit); return DROPEFFECT_MOVE; } int IdeTree::OnDrop(LArray &Data, LPoint p, int KeyState) { int Ret = DROPEFFECT_NONE; SelectDropTarget(NULL); if (!(Hit = ItemAtPoint(p.x, p.y))) return Ret; for (unsigned n=0; nType == GV_BINARY && Data->Value.Binary.Length == sizeof(ProjectNode*)) { ProjectNode *Src = ((ProjectNode**)Data->Value.Binary.Data)[0]; if (Src) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() != NodeDir) Folder = dynamic_cast(Folder->GetParent()); IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { // Check this folder is not a child of the src for (IdeCommon *n=Dst; n; n=dynamic_cast(n->GetParent())) { if (n == Src) return DROPEFFECT_NONE; } // Detach LTreeItem *i = dynamic_cast(Src); i->Detach(); if (Src->LXmlTag::Parent) { LAssert(Src->LXmlTag::Parent->Children.HasItem(Src)); Src->LXmlTag::Parent->Children.Delete(Src); } // Attach Src->LXmlTag::Parent = Dst; Dst->Children.SetFixedLength(false); Dst->Children.Add(Src); Dst->Children.SetFixedLength(true); Dst->Insert(Src); // Dirty Src->GetProject()->SetDirty(); } Ret = DROPEFFECT_MOVE; } } } else if (dd.IsFileDrop()) { ProjectNode *Folder = dynamic_cast(Hit); while (Folder && Folder->GetType() > NodeDir) Folder = dynamic_cast(Folder->GetParent()); IdeCommon *Dst = dynamic_cast(Folder?Folder:Hit); if (Dst) { AddFilesProgress Prog(this); LDropFiles Df(dd); for (int i=0; iAddFiles(&Prog, Df[i])) Ret = DROPEFFECT_LINK; } } } else LgiTrace("%s:%i - Unknown drop format: %s.\n", _FL, dd.Format.Get()); } return Ret; } ///////////////////////////////////////////////////////////////////////////////////////////////// AddFilesProgress::AddFilesProgress(LViewI *par) { v = 0; Cancel = false; Msg = NULL; SetParent(par); Ts = LCurrentTime(); LRect r(0, 0, 140, 100); SetPos(r); MoveSameScreen(par); Name("Importing files..."); LString::Array a = LString(DefaultExt).SplitDelimit(","); for (unsigned i=0; iGetCell(0, 0); c->Add(new LTextLabel(-1, 0, 0, -1, -1, "Loaded:")); c = t->GetCell(1, 0); c->Add(Msg = new LTextLabel(-1, 0, 0, -1, -1, "...")); c = t->GetCell(0, 1, true, 2); c->TextAlign(LCss::Len(LCss::AlignRight)); c->Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); } int64 AddFilesProgress::Value() { return v; } void AddFilesProgress::Value(int64 val) { v = val; uint64 Now = LCurrentTime(); if (Visible()) { if (Now - Ts > 200) { if (Msg) { Msg->Value(v); Msg->SendNotify(LNotifyTableLayoutRefresh); } LYield(); Ts = Now; } } else if (Now - Ts > 1000) { DoModeless(); SetAlwaysOnTop(true); } } int AddFilesProgress::OnNotify(LViewI *c, LNotification n) { if (c->GetId() == IDCANCEL) Cancel = true; return 0; } diff --git a/Ide/Code/IdeProject.h b/Ide/Code/IdeProject.h --- a/Ide/Code/IdeProject.h +++ b/Ide/Code/IdeProject.h @@ -1,223 +1,223 @@ #ifndef _IDE_PROJECT_H_ #define _IDE_PROJECT_H_ #include "lgi/common/XmlTree.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Tree.h" #include "lgi/common/OptionsFile.h" #include "Debugger.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/List.h" #define NODE_DROP_FORMAT "com.memecode.ProjectNode" #define OPT_Ftp "ftp" #define OPT_Www "www" enum ExeAction { ExeRun, ExeDebug, ExeValgrind }; enum AppCommands { IDM_INSERT = 100, IDM_NEW_FOLDER, IDM_RENAME, IDM_DELETE, IDM_SETTINGS, IDM_INSERT_DEP, IDM_SORT_CHILDREN, IDM_PROPERTIES, IDM_BROWSE_FOLDER, IDM_OPEN_TERM, IDM_IMPORT_FOLDER, IDM_WEB_FOLDER, IDM_INSERT_FTP, IDM_SHOW_IN_PROJECT, // IDM_BUILD, IDM_CLEAN_PROJECT, IDM_CLEAN_ALL, IDM_REBUILD_PROJECT, IDM_REBUILD_ALL }; enum ProjectStatus { OpenError, OpenOk, OpenCancel, }; extern int PlatformCtrlId[]; class AddFilesProgress : public LDialog { uint64 Ts; uint64 v; LView *Msg; public: bool Cancel; static const char *DefaultExt; LHashTbl, bool> Exts; AddFilesProgress(LViewI *par); int64 Value(); void Value(int64 val); int OnNotify(LViewI *c, LNotification n); }; extern IdePlatform GetCurrentPlatform(); class AppWnd; class IdeProject; class IdeCommon : public LTreeItem, public LXmlTag { friend class IdeProject; protected: IdeProject *Project; public: IdeCommon(IdeProject *p); ~IdeCommon(); IdeProject *GetProject() { return Project; } bool OnOpen(LProgressDlg *Prog, LXmlTag *Src); void CollectAllSubProjects(List &c); void CollectAllSource(LArray &c, IdePlatform Platform); void SortChildren(); void InsertTag(LXmlTag *t) override; bool RemoveTag() override; virtual bool IsWeb() = 0; virtual int GetPlatforms() = 0; bool AddFiles(AddFilesProgress *Prog, const char *Path); IdeCommon *GetSubFolder(IdeProject *Project, char *Name, bool Create = false); }; class WatchItem : public LTreeItem { class IdeOutput *Out; LTreeItem *PlaceHolder; public: WatchItem(IdeOutput *out, const char *Init = NULL); ~WatchItem(); bool SetText(const char *s, int i = 0) override; void OnExpand(bool b) override; bool SetValue(LVariant &v); }; #include "IdeProjectSettings.h" #include "DebugContext.h" class IdeProject : public LXmlFactory, public IdeCommon { friend class ProjectNode; friend class BuildThread; class IdeProjectPrivate *d; bool OnNode(const char *Path, class ProjectNode *Node, bool Add); public: IdeProject(AppWnd *App); ~IdeProject(); bool IsWeb() { return false; } int GetPlatforms(); const char *GetFileName(); // Can be a relative path LAutoString GetFullPath(); // Always a complete path LAutoString GetBasePath(); // A non-relative path to the folder containing the project AppWnd *GetApp(); LString GetExecutable(IdePlatform Platform); const char *GetExeArgs(); const char *GetIncludePaths(); const char *GetPreDefinedValues(); LXmlTag *Create(char *Tag); void Empty(); LString GetMakefile(IdePlatform Platform); bool GetExePath(char *Path, int Len); bool RelativePath(LString &Out, const char *In, bool Debug = false); void Build(bool All, BuildConfig Config); void StopBuild(); void Clean(bool All, BuildConfig Config); - LDebugContext *Execute(ExeAction Act = ExeRun); + LDebugContext *Execute(ExeAction Act = ExeRun, LString *ErrMsg = NULL); bool FixMissingFiles(); bool FindDuplicateSymbols(); bool InProject(bool FuzzyMatch, const char *Path, bool Open, class IdeDoc **Doc = 0); const char *GetFileComment(); const char *GetFunctionComment(); bool IsMakefileUpToDate(); bool IsMakefileAScript(); bool CreateMakefile(IdePlatform Platform, bool BuildAfterwards); LString GetTargetName(IdePlatform Platform); LString GetTargetFile(IdePlatform Platform); bool BuildIncludePaths(LArray &Paths, bool Recurse, bool IncludeSystem, IdePlatform Platform); void ShowFileProperties(const char *File); bool GetExpanded(int Id); void SetExpanded(int Id, bool Exp); int AllocateId(); bool CheckExists(LString &p, bool Debug = false); bool CheckExists(LAutoString &p, bool Debug = false); void OnMakefileCreated(); // Nodes char *FindFullPath(const char *File, class ProjectNode **Node = NULL); bool GetAllNodes(LArray &Nodes); bool HasNode(ProjectNode *Node); // Project hierarchy IdeProject *GetParentProject(); bool GetChildProjects(List &c); void SetParentProject(IdeProject *p); // File void CreateProject(); ProjectStatus OpenFile(const char *FileName); bool SaveFile(); bool GetClean(); void SetClean(std::function OnDone); void SetDirty(); void SetUserFileDirty(); bool Serialize(bool Write); void ImportDsp(const char *File); // Dependency calculation bool GetAllDependencies(LArray &Files, IdePlatform Platform); bool GetDependencies(const char *SourceFile, LArray &IncPaths, LArray &Files, IdePlatform Platform); // Settings IdeProjectSettings *GetSettings(); // Impl const char *GetText(int Col); int GetImage(int Flags); void OnMouseClick(LMouse &m); }; class IdeTree : public LTree, public LDragDropTarget { LTreeItem *Hit; public: IdeTree(); void OnCreate(); void OnDragExit(); int WillAccept(LDragFormats &Formats, LPoint p, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); }; extern const char TagSettings[]; extern void FixMissingFilesDlg(IdeProject *Proj); #endif diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4651 +1,4655 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mdi.h" #include "lgi/common/Token.h" #include "lgi/common/XmlTree.h" #include "lgi/common/Panel.h" #include "lgi/common/Button.h" #include "lgi/common/TabView.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/Edit.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Combo.h" #include "lgi/common/CheckBox.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Box.h" #include "lgi/common/SubProcess.h" #include "lgi/common/About.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/FileSelect.h" #include "lgi/common/SubProcess.h" #include "LgiIde.h" #include "FtpThread.h" #include "FindSymbol.h" #include "Debugger.h" #include "ProjectNode.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #define USE_HAIKU_PULSE_HACK 0 #define OPT_ENTIRE_SOLUTION "SearchSolution" #define OPT_SPLIT_PX "SplitPos" #define OPT_OUTPUT_PX "OutputPx" #define OPT_FIX_RENAMED "FixRenamed" #define OPT_RENAMED_SYM "RenamedSym" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public LDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); LViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, LKeyEvents, 0); } } bool OnViewKey(LView *v, LKey &k) { switch (k.vkey) { case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { return Lst->OnKey(k); break; } case LK_RETURN: { if (k.Down()) { LListItem *i = Lst->GetSelected(); if (i) { const char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); return true; } break; } case LK_ESCAPE: { if (k.Down()) { EndModal(0); return true; } break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; LArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s); Lst->Empty(); for (auto m: Matches) { LListItem *li = new LListItem; LString Fn = m->GetFileName(); #ifdef WINDOWS Fn = Fn.Replace("/","\\"); #else Fn = Fn.Replace("\\","/"); #endif m->GetProject()->CheckExists(Fn); li->SetText(Fn); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_FILES: if (n.Type == LNotifyItemDoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (n.Type != LNotifyReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// char AppName[] = "LgiIde"; char *dirchar(char *s, bool rev = false) { if (rev) { char *last = 0; while (s && *s) { if (*s == '/' || *s == '\\') last = s; s++; } return last; } else { while (s && *s) { if (*s == '/' || *s == '\\') return s; s++; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class AppDependency : public LTreeItem { char *File; bool Loaded; LTreeItem *Fake; public: AppDependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new LTreeItem); if (LFileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~AppDependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(LStringPipe &p, int Depth = 0) { { char s[1024]; ZeroObj(s); memset(s, ' ', Depth * 4); sprintf(s+(Depth*4), "[%c] %s\n", Expanded() ? '-' : '+', GetText(0)); p.Push(s); } if (Loaded) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { ((AppDependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { LToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new AppDependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new LButton(IDC_COPY, 10, t->LView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new LButton(IDOK, 80, t->LView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(NULL); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_COPY: { if (Root) { LStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { LClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public LTextLog { public: DebugTextLog(int id) : LTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { auto Ts = LCurrentTime(); LTextView3::PourText(Start, Len); auto Dur = LCurrentTime() - Ts; if (Dur > 1500) { // Yo homes, too much text bro... Name(NULL); } else { for (auto l: Line) { char16 *t = Text + l->Start; if (l->Len > 5 && !StrnicmpW(t, L"(gdb)", 5)) { l->c.Rgb(0, 160, 0); } else if (l->Len > 1 && t[0] == '[') { l->c.Rgb(192, 192, 192); } } } } }; WatchItem::WatchItem(IdeOutput *out, const char *Init) { Out = out; Expanded(false); if (Init) SetText(Init); Insert(PlaceHolder = new LTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(LVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else LTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { LTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { LViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(LNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public LTextLog { public: BuildLog(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = LTextView3::Line.begin(); for (LTextLine *ln = *it; ln; ln = *++it) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; char16 *Err = Strnistr(t, L"error", ln->Len); char16 *Undef = Strnistr(t, L"undefined reference", ln->Len); char16 *Warn = Strnistr(t, L"warning", ln->Len); if ( (Err && strchr(":[", Err[5])) || (Undef != NULL) ) ln->c.Rgb(222, 0, 0); else if (Warn && strchr(":[", Warn[7])) ln->c.Rgb(255, 128, 0); else ln->c = LColour(L_TEXT); } } } }; class IdeOutput : public LTabView { public: AppWnd *App; LTabPage *Build; LTabPage *Output; LTabPage *Debug; LTabPage *Find; LTabPage *Ftp; LList *FtpLog; LTextLog *Txt[AppWnd::Channels::ChannelMax]; LArray Buf[AppWnd::Channels::ChannelMax]; LFont Small; LFont Fixed; LTabView *DebugTab; LBox *DebugBox; LBox *DebugLog; LList *Locals, *CallStack, *Threads; LTree *Watch; LTextLog *ObjectDump, *MemoryDump, *Registers; LTableLayout *MemTable; LEdit *DebugEdit; LTextLog *DebuggerLog; IdeOutput(AppWnd *app) { ZeroObj(Txt); App = app; Build = Output = Debug = Find = Ftp = 0; FtpLog = 0; DebugBox = NULL; Locals = NULL; Watch = NULL; DebugLog = NULL; DebugEdit = NULL; DebuggerLog = NULL; CallStack = NULL; ObjectDump = NULL; MemoryDump = NULL; MemTable = NULL; Threads = NULL; Registers = NULL; Small = *LSysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LAssert(Small.Handle()); LFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(LSysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(LSysFont->PointSize()-1); Fixed.Face("Courier"); Fixed.Create(); } GetCss(true)->MinHeight("60px"); Build = Append("Build"); Output = Append("Output"); Find = Append("Find"); Ftp = Append("Ftp"); Debug = Append("Debug"); SetFont(&Small); Build->SetFont(&Small); Output->SetFont(&Small); Find->SetFont(&Small); Ftp->SetFont(&Small); Debug->SetFont(&Small); if (Build) Build->Append(Txt[AppWnd::BuildTab] = new BuildLog(IDC_BUILD_LOG)); if (Output) Output->Append(Txt[AppWnd::OutputTab] = new LTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new LTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new LBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new LTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); LTabPage *Page; if ((Page = DebugTab->Append("Locals"))) { Page->SetFont(&Small); if ((Locals = new LList(IDC_LOCALS_LIST, 0, 0, 100, 100, "Locals List"))) { Locals->SetFont(&Small); Locals->AddColumn("", 30); Locals->AddColumn("Type", 50); Locals->AddColumn("Name", 50); Locals->AddColumn("Value", 1000); Locals->SetPourLargest(true); Page->Append(Locals); } } if ((Page = DebugTab->Append("Object"))) { Page->SetFont(&Small); if ((ObjectDump = new LTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new LTree(IDC_WATCH_LIST, 0, 0, 100, 100, "Watch List"))) { Watch->SetFont(&Small); Watch->ShowColumnHeader(true); Watch->AddColumn("Watch", 80); Watch->AddColumn("Type", 100); Watch->AddColumn("Value", 600); Watch->SetPourLargest(true); Page->Append(Watch); LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (auto c: w->Children) { if (c->IsTag("watch")) { Watch->Insert(new WatchItem(this, c->GetContent())); } } App->GetOptions()->Unlock(); } } } if ((Page = DebugTab->Append("Memory"))) { Page->SetFont(&Small); if ((MemTable = new LTableLayout(IDC_MEMORY_TABLE))) { LCombo *cbo; LCheckBox *chk; LTextLabel *txt; LEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; auto *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(cbo = new LCombo(IDC_MEM_SIZE, 0, 0, 60, 20)); cbo->SetFont(&Small); cbo->Insert("1 byte"); cbo->Insert("2 bytes"); cbo->Insert("4 bytes"); cbo->Insert("8 bytes"); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(chk = new LCheckBox(IDC_MEM_HEX, 0, 0, -1, -1, "Show Hex")); chk->SetFont(&Small); chk->Value(true); } int cols = x; x = 0; c = MemTable->GetCell(x++, ++y, true, cols); if ((MemoryDump = new LTextLog(IDC_MEMORY_DUMP))) { MemoryDump->SetFont(&Fixed); MemoryDump->SetPourLargest(true); c->Add(MemoryDump); } Page->Append(MemTable); } } if ((Page = DebugTab->Append("Threads"))) { Page->SetFont(&Small); if ((Threads = new LList(IDC_THREADS, 0, 0, 100, 100, "Threads"))) { Threads->SetFont(&Small); Threads->AddColumn("", 20); Threads->AddColumn("Thread", 1000); Threads->SetPourLargest(true); Threads->MultiSelect(false); Page->Append(Threads); } } if ((Page = DebugTab->Append("Call Stack"))) { Page->SetFont(&Small); if ((CallStack = new LList(IDC_CALL_STACK, 0, 0, 100, 100, "Call Stack"))) { CallStack->SetFont(&Small); CallStack->AddColumn("", 20); CallStack->AddColumn("Call Stack", 1000); CallStack->SetPourLargest(true); CallStack->MultiSelect(false); Page->Append(CallStack); } } if ((Page = DebugTab->Append("Registers"))) { Page->SetFont(&Small); if ((Registers = new LTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new LBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new LEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)(LSysFont->GetHeight() + 8))); } } } if (FtpLog) { FtpLog->SetPourLargest(true); FtpLog->Sunken(true); FtpLog->AddColumn("Entry", 1000); FtpLog->ShowColumnHeader(false); } for (int n=0; nSetTabSize(8); Txt[n]->Sunken(true); } } ~IdeOutput() { } const char *GetClass() { return "IdeOutput"; } void Save() { if (Watch) { LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (LTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { LXmlTag *t = new LXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { SetPulse(1000); AttachChildren(); } void RemoveAnsi(LArray &a) { char *s = a.AddressOf(); char *e = s + a.Length(); while (s < e) { if (*s == 0x7) { a.DeleteAt(s - a.AddressOf(), true); s--; } else if ( *s == 0x1b && s[1] >= 0x40 && s[1] <= 0x5f ) { // ANSI seq char *end; if (s[1] == '[' && s[2] == '0' && s[3] == ';') end = s + 4; else { end = s + 2; while (end < e && !IsAlpha(*end)) { end++; } if (*end) end++; } auto len = end - s; memmove(s, end, e - end); a.Length(a.Length() - len); s--; } s++; } } void OnPulse() { int Changed = -1; for (int Channel = 0; Channel w; if (!LIsUtf8(Utf, (ssize_t)Size)) { LgiTrace("Ch %i not utf len=" LPrintfInt64 "\n", Channel, Size); // Clear out the invalid UTF? uint8_t *u = (uint8_t*) Utf, *e = u + Size; ssize_t len = Size; LArray out; while (u < e) { int32 u32 = LgiUtf8To32(u, len); if (u32) { out.Add(u32); } else { out.Add(0xFFFD); u++; } } out.Add(0); w.Reset(out.Release()); } else { RemoveAnsi(Buf[Channel]); w.Reset(Utf8ToWide(Utf, (ssize_t)Size)); } // auto OldText = Txt[Channel]->NameW(); ssize_t OldLen = Txt[Channel]->Length(); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); else printf("Caret move: %i, %i = %i\n", (int)Cur, (int)OldLen, Cur > OldLen - 1); Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } /* if (Changed >= 0) Value(Changed); */ } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { auto A = a->GetFileName(); auto B = b->GetFileName(); if (A && B) { auto Af = strrchr(A, DIR_CHAR); auto Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { LAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; LMdiParent *Mdi; LOptionsFile Options; LBox *HBox, *VBox; List Docs; List Projects; LImageList *Icons; LTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; bool FixBuildWait = false; int RebuildWait = 0; LSubMenu *WindowsMenu; LSubMenu *CreateMakefileMenu; LAutoPtr FindSym; LArray SystemIncludePaths; LArray BreakPoints; // Debugging LDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; LArray CursorHistory; bool InHistorySeek; void SeekHistory(int Direction) { if (CursorHistory.Length()) { int Loc = HistoryLoc + Direction; if (Loc >= 0 && Loc < CursorHistory.Length()) { HistoryLoc = Loc; FileLoc &Loc = CursorHistory[HistoryLoc]; App->GotoReference(Loc.File, Loc.Line, false, false); App->DumpHistory(); } } } // Find in files LAutoPtr FindParameters; LAutoPtr Finder; int AppHnd; // Mru LString::Array RecentFiles; LSubMenu *RecentFilesMenu = NULL; LString::Array RecentProjects; LSubMenu *RecentProjectsMenu = NULL; // Object AppWndPrivate(AppWnd *a) : Options(LOptionsFile::DesktopMode, AppName), AppHnd(LEventSinkMap::Dispatch.AddSink(a)) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; Mdi = NULL; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; Icons = LLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { FindSym.Reset(); Finder.Reset(); if (Output) Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); while (Docs.Length()) { auto len = Docs.Length(); delete Docs[0]; LAssert(Docs.Length() != len); // doc must delete itself... } auto root = App->RootProject(); if (root) { // printf("Deleting proj %s\n", root->GetFileName()); delete root; } LAssert(!Projects.Length()); DeleteObj(Icons); } bool FindSource(LAutoString &Full, char *File, char *Context) { if (!LIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (auto p: Projects) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); if (ContextPath) break; } if (ContextPath) { LTrimDir(ContextPath); char p[300]; LMakePath(p, sizeof(p), ContextPath, File); if (LFileExists(p)) { Full.Reset(NewStr(p)); } } else { LgiTrace("%s:%i - Context '%s' not found in project.\n", _FL, Context); } } if (!Full) { List::I Projs = Projects.begin(); for (IdeProject *p=*Projs; p; p=*++Projs) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, File); if (LFileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (auto p: Projects) { if (Full.Reset(p->FindFullPath(Dir?Dir+1:File))) break; } if (!Full) { if (LFileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { LAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } #if 1 #define LOG_SEEK_MSG(...) printf(__VA_ARGS__) #else #define LOG_SEEK_MSG(...) #endif void GetContext(const char16 *Txt, ssize_t &i, char16 *&Context) { static char16 NMsg[] = L"In file included "; static char16 FromMsg[] = L"from "; auto NMsgLen = StrlenW(NMsg); if (Txt[i] != '\n') return; if (StrncmpW(Txt + i + 1, NMsg, NMsgLen)) return; i += NMsgLen + 1; while (Txt[i]) { // Skip whitespace while (Txt[i] && strchr(" \t\r\n", Txt[i])) i++; // Check for 'from' if (StrncmpW(FromMsg, Txt + i, 5)) break; i += 5; auto Start = Txt + i; // Skip to end of doc or line const char16 *Colon = 0; while (Txt[i] && Txt[i] != '\n') { if (Txt[i] == ':' && Txt[i+1] != '\n') { Colon = Txt + i; } i++; } if (Colon) { DeleteArray(Context); Context = NewStrW(Start, Colon-Start); } } } template bool IsTimeStamp(T *s, ssize_t i) { while (i > 0 && s[i-1] != '\n') i--; auto start = i; while (s[i] && (IsDigit(s[i]) || strchr(" :-", s[i]))) i++; LString txt(s + start, i - start); auto parts = txt.SplitDelimit(" :-"); return parts.Length() == 6 && parts[0].Length() == 4; } template bool IsContext(T *s, ssize_t i) { auto key = L"In file included"; auto end = i; while (i > 0 && s[i-1] != '\n') i--; if (Strnistr(s + i, key, end - i)) return true; return false; } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { LString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); LTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; auto Txt = o->NameW(); if (!Txt) return; ssize_t Cur = o->GetCaret(); char16 *Context = NULL; // Scan forward to the end of file for the next filename/line number separator. ssize_t i; for (i=Cur; Txt[i]; i++) { GetContext(Txt, i, Context); if ( PossibleLineSep(Txt[i]) && isdigit(Txt[i+1]) && !IsTimeStamp(Txt, i) && !IsContext(Txt, i) ) { break; } } // If not found then scan from the start of the file for the next filename/line number separator. if (!PossibleLineSep(Txt[i])) { for (i=0; i 0 && !strchr("\n>", Txt[Line-1])) { Line--; } // Store the filename LString File(Txt+Line, i-Line); if (!File) return; #if DIR_CHAR == '\\' File = File.Replace("/", "\\"); #else File = File.Replace("\\", "/"); #endif // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number LString NumStr(Txt + i, NumIndex - i); if (!NumStr) return; // Convert it to an integer auto LineNumber = (int)NumStr.Int(); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); LString Context8 = Context; ViewMsg(File, LineNumber, Context8); } void UpdateMenus() { static const char *None = "(none)"; if (!App->GetMenu()) return; // This happens in GTK during window destruction if (RecentFilesMenu) { RecentFilesMenu->Empty(); if (RecentFiles.Length() == 0) RecentFilesMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentFiles.begin(); (f = *It); f=*(++It)) { for (; f; f=*(++It)) { if (LIsUtf8(f)) RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); else RecentFiles.Delete(It); } } } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); if (RecentProjects.Length() == 0) RecentProjectsMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentProjects.begin(); (f = *It); f=*(++It)) { if (LIsUtf8(f)) RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); else RecentProjects.Delete(It); } } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (auto d: Docs) { const char *File = d->GetFileName(); if (!File) File = "(untitled)"; char *Dir = strrchr((char*)File, DIR_CHAR); WindowsMenu->AppendItem(Dir?Dir+1:File, IDM_WINDOWS+n++, true); } if (!Docs.Length()) { WindowsMenu->AppendItem(None, 0, false); } } } void Dump(LString::Array &a) { for (auto i: a) printf(" %s\n", i.Get()); } void OnFile(const char *File, bool IsProject = false) { if (!File) return; auto *Recent = IsProject ? &RecentProjects : &RecentFiles; for (auto &f: *Recent) { if (f && LFileCompare(f, File) == 0) { f = File; UpdateMenus(); return; } } Recent->AddAt(0, File); if (Recent->Length() > 10) Recent->Length(10); UpdateMenus(); } void RemoveRecent(const char *File) { if (File) { LString::Array *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (int i=0; Recent[i]; i++) { auto &a = *Recent[i]; for (size_t n=0; nIsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(const char *File) { if (File) { for (auto p: Projects) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, LString::Array *Lst, bool Write) { LVariant v; LString Sep = OptFileSeparator; if (Write) { if (Lst->Length() > 0) { auto s = Sep.Join(*Lst); Options.SetValue(Opt, v = s.Get()); // printf("Saving '%s' to %s\n", s.Get(), Opt); } else Options.DeleteValue(Opt); } else if (Options.GetValue(Opt, v)) { auto files = LString(v.Str()).Split(Sep); Lst->Length(0); for (auto f: files) if (f.Length() > 0) Lst->Add(f); // printf("Reading '%s' to %s, file.len=%i %s\n", v.Str(), Opt, (int)files.Length(), v.Str()); } // else printf("%s:%i - No option '%s' to read.\n", _FL, Opt); } }; AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif LRect r(0, 0, 1000, 760); SetPos(r); MoveToCenter(); d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("icon64.png"); #endif if (!Attach(0)) { LgiTrace("%s:%i - Attach failed.\n", _FL); return; } if ((Menu = new LMenu)) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LAssert(Loaded); if (Loaded) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); d->RecentFilesMenu = Menu->FindSubMenu(IDM_RECENT_FILES); d->RecentProjectsMenu = Menu->FindSubMenu(IDM_RECENT_PROJECTS); d->WindowsMenu = Menu->FindSubMenu(IDM_WINDOW_LST); d->CreateMakefileMenu = Menu->FindSubMenu(IDM_CREATE_MAKEFILE); if (d->CreateMakefileMenu) { d->CreateMakefileMenu->Empty(); for (int i=0; PlatformNames[i]; i++) { d->CreateMakefileMenu->AppendItem(PlatformNames[i], IDM_MAKEFILE_BASE + i); } } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) Debug->Checked(true); else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } LToolBar *Tools = NULL; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Tools->AppendButton("New", IDM_NEW, TBT_PUSH, true, CMD_NEW); Tools->AppendButton("Open", IDM_OPEN, TBT_PUSH, true, CMD_OPEN); Tools->AppendButton("Save", IDM_SAVE_ALL, TBT_PUSH, true, CMD_SAVE_ALL); Tools->AppendSeparator(); Tools->AppendButton("Cut", IDM_CUT, TBT_PUSH, true, CMD_CUT); Tools->AppendButton("Copy", IDM_COPY, TBT_PUSH, true, CMD_COPY); Tools->AppendButton("Paste", IDM_PASTE, TBT_PUSH, true, CMD_PASTE); Tools->AppendSeparator(); Tools->AppendButton("Compile", IDM_COMPILE, TBT_PUSH, true, CMD_COMPILE); Tools->AppendButton("Build", IDM_BUILD, TBT_PUSH, true, CMD_BUILD); Tools->AppendButton("Stop", IDM_STOP_BUILD, TBT_PUSH, true, CMD_STOP_BUILD); // Tools->AppendButton("Execute", IDM_EXECUTE, TBT_PUSH, true, CMD_EXECUTE); Tools->AppendSeparator(); Tools->AppendButton("Debug", IDM_START_DEBUG, TBT_PUSH, true, CMD_DEBUG); Tools->AppendButton("Pause", IDM_PAUSE_DEBUG, TBT_PUSH, true, CMD_PAUSE); Tools->AppendButton("Restart", IDM_RESTART_DEBUGGING, TBT_PUSH, true, CMD_RESTART); Tools->AppendButton("Kill", IDM_STOP_DEBUG, TBT_PUSH, true, CMD_KILL); Tools->AppendButton("Step Into", IDM_STEP_INTO, TBT_PUSH, true, CMD_STEP_INTO); Tools->AppendButton("Step Over", IDM_STEP_OVER, TBT_PUSH, true, CMD_STEP_OVER); Tools->AppendButton("Step Out", IDM_STEP_OUT, TBT_PUSH, true, CMD_STEP_OUT); Tools->AppendButton("Run To", IDM_RUN_TO, TBT_PUSH, true, CMD_RUN_TO); Tools->AppendSeparator(); Tools->AppendButton("Find In Files", IDM_FIND_IN_FILES, TBT_PUSH, true, CMD_FIND_IN_FILES); Tools->GetCss(true)->Padding("4px"); Tools->Attach(this); } else LgiTrace("%s:%i - No tools obj?", _FL); LVariant v = 270, OutPx = 250; d->Options.GetValue(OPT_SPLIT_PX, v); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); AddView(d->VBox = new LBox); d->VBox->SetVertical(true); d->HBox = new LBox; d->VBox->AddView(d->HBox); d->VBox->AddView(d->Output = new IdeOutput(this)); d->HBox->AddView(d->Tree = new IdeTree); if (d->Tree) { d->Tree->SetImageList(d->Icons, false); d->Tree->Sunken(false); } d->HBox->AddView(d->Mdi = new LMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } d->HBox->Value(MAX(v.CastInt32(), 20)); LRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { auto Px = OutPx.CastInt32(); LCss::Len y(LCss::LenPx, (float)MAX(Px, 120)); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); UpdateState(); Visible(true); DropTarget(true); SetPulse(1000); #ifdef LINUX LFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK if (d->Output) d->Output->SetPulse(1000); #endif OnCommand(IDM_NEW, 0, NULL); } AppWnd::~AppWnd() { LAssert(IsClean()); WaitThread(); if (d->HBox) { LVariant v = d->HBox->Value(); d->Options.SetValue(OPT_SPLIT_PX, v); } if (d->Output) { LVariant v = d->Output->Y(); d->Options.SetValue(OPT_OUTPUT_PX, v); } ShutdownFtpThread(); LAppInst->AppWnd = NULL; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); if (d->FixBuildWait) { d->FixBuildWait = false; if (OnFixBuildErrors() > 0) d->RebuildWait = 3; } else if (d->RebuildWait > 0) { if (--d->RebuildWait == 0) Build(); } } LDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { LStream *Out; LString InFile; bool IsLib; public: DumpBinThread(LStream *out, LString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(LString Args, LStream *Str) { char Buf[256]; ssize_t Rd; const char *Prog = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\dumpbin.exe"; LSubProcess s(Prog, Args); if (!s.Start(true, false)) { Out->Print("%s:%i - '%s' doesn't exist.\n", _FL, Prog); return false; } while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } LString::Array Dependencies(const char *Executable, int Depth = 0) { LString Args; LStringPipe p; Args.Printf("/dependents \"%s\"", Executable); DumpBin(Args, &p); char Spaces[256]; int Len = Depth * 2; memset(Spaces, ' ', Len); Spaces[Len] = 0; LString::Array Files; auto Parts = p.NewGStr().Replace("\r", "").Split("\n\n"); if (Parts.Length() > 0) { Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (size_t i=0; i= 0) { auto p = Ln.Strip().Split(Key); if (p.Length() == 2) { Arch = p[1].Strip("()"); Machine = p[0].Int(16); } } } if (Machine == 0x14c) Arch += " 32bit"; else if (Machine == 0x200) Arch += " 64bit Itanium"; else if (Machine == 0x8664) Arch += " 64bit"; return Arch; } LString GetExports() { LString Args; LStringPipe p; if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); LString Exp; auto Sect = p.NewGStr().Replace("\r", "").Split("\n\n"); if (IsLib) { LString::Array Lines, Funcs; for (auto &s : Sect) { if (s.Find("COFF", 0, 100) == 0) { Lines = s.Split("\n"); break; } } Funcs.SetFixedLength(false); for (auto &l : Lines) { if (l.Length() < 34) continue; const char *Type = l.Get() + 33; if (!Strnicmp(Type, "External", 8)) { auto Nm = l.RSplit("|",1).Last().Strip(); if (!strchr("@$.?", Nm(0))) Funcs.New() = Nm; } } Exp = LString("\n").Join(Funcs); } else { bool Ord = false; for (auto &s : Sect) { if (s.Strip().Find("ordinal") == 0) Ord = true; else if (Ord) { Exp = s; break; } else Ord = false; } } return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(InFile); if (Deps.Length()) Out->Print("Dependencies:\n\t%s\n\n", LString("\n\t").Join(Deps).Get()); } auto Arch = GetArch(); if (Arch) Out->Print("Arch: %s\n\n", Arch.Get()); auto Exp = GetExports(); if (Arch) Out->Print("Exports:\n%s\n\n", Exp.Get()); return 0; } }; void AppWnd::OnReceiveFiles(LArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); if (LAppInst->GetOption("createMakeFiles")) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatformCurrent, false); } } } void AppWnd::OnDebugState(bool Debugging, bool Running) { // Make sure this event is processed in the GUI thread. #if DEBUG_SESSION_LOGGING LgiTrace("AppWnd::OnDebugState(%i,%i) InThread=%i\n", Debugging, Running, InThread()); #endif PostEvent(M_DEBUG_ON_STATE, Debugging, Running); } bool IsVarChar(LString &s, ssize_t pos) { if (pos < 0) return false; if (pos >= s.Length()) return false; char i = s[pos]; return IsAlpha(i) || IsDigit(i) || i == '_'; } bool ReplaceWholeWord(LString &Ln, LString Word, LString NewWord) { ssize_t Pos = 0; bool Status = false; while (Pos >= 0) { Pos = Ln.Find(Word, Pos); if (Pos < 0) return Status; ssize_t End = Pos + Word.Length(); if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) { LString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); Ln = NewLn; Status = true; } Pos++; } return Status; } struct LFileInfo { LString Path; LString::Array Lines; bool Dirty; LFileInfo() { Dirty = false; } bool Save() { LFile f; if (!f.Open(Path, O_WRITE)) return false; LString NewFile = LString("\n").Join(Lines); f.SetSize(0); f.Write(NewFile); f.Close(); return true; } }; int AppWnd::OnFixBuildErrors() { LHashTbl, LString> Map; LVariant v; if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) { auto Lines = LString(v.Str()).Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(); if (p.Length() == 2) Map.Add(p[0], p[1]); } } LString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); LString::Array Lines = Raw.Split("\n"); auto *Log = d->Output->Txt[AppWnd::OutputTab]; Log->Name(NULL); Log->Print("Parsing errors...\n"); int Replacements = 0; LArray Files; LHashTbl,bool> FixHistory; for (int Idx=0; Idx= 0) { #ifdef WINDOWS LString::Array p = Ln.SplitDelimit(">()"); #else LString::Array p = Ln(0, ErrPos).Strip().SplitDelimit(":"); #endif if (p.Length() <= 2) { Log->Print("Error: Only %i parts? '%s'\n", (int)p.Length(), Ln.Get()); } else { #ifdef WINDOWS int Base = p[0].IsNumeric() ? 1 : 0; LString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) { Log->Print("Is prog file\n"); continue; } auto LineNo = p[Base+1].Int(); bool FileNotFound = Ln.Find("Cannot open include file:") > 0; #else LString Fn = p[0]; auto LineNo = p[1].Int(); bool FileNotFound = false; // fixme #endif LAutoString Full; if (!d->FindSource(Full, Fn, NULL)) { Log->Print("Error: Can't find Fn='%s' Line=%i\n", Fn.Get(), (int)LineNo); continue; } LFileInfo *Fi = NULL; for (auto &i: Files) { if (i.Path.Equals(Full)) { Fi = &i; break; } } if (!Fi) { LFile f(Full, O_READ); if (f.IsOpen()) { Fi = &Files.New(); Fi->Path = Full.Get(); auto OldFile = f.Read(); Fi->Lines = OldFile.SplitDelimit("\n", -1, false); } else { Log->Print("Error: Can't open '%s'\n", Full.Get()); } } if (Fi) { LString Loc; Loc.Printf("%s:%i", Full.Get(), (int)LineNo); if (FixHistory.Find(Loc)) { // Log->Print("Already fixed %s\n", Loc.Get()); } else if (LineNo <= Fi->Lines.Length()) { FixHistory.Add(Loc, true); if (FileNotFound) { auto n = p.Last().SplitDelimit("\'"); auto wrongName = n[1]; LFile f(Full, O_READ); auto Lines = f.Read().SplitDelimit("\n", -1, false); f.Close(); if (LineNo <= Lines.Length()) { auto &errLine = Lines[LineNo-1]; auto Pos = errLine.Find(wrongName); /* if (Pos < 0) { for (int i=0; iPrint("[%i]=%s\n", i, Lines[i].Get()); } */ if (Pos > 0) { // Find where it went... LString newPath; for (auto p: d->Projects) { const char *SubStr[] = { ".", "lgi/common" }; LArray IncPaths; if (p->BuildIncludePaths(IncPaths, true, false, PlatformCurrent)) { for (auto &inc: IncPaths) { for (int sub=0; !newPath && subPrint("Already changed '%s'.\n", wrongName.Get()); } else { LString backup = LString(Full.Get()) + ".orig"; if (LFileExists(backup)) FileDev->Delete(backup); LError Err; if (FileDev->Move(Full, backup, &Err)) { errLine = newLine; LString newLines = LString("\n").Join(Lines); LFile out(Full, O_WRITE); out.Write(newLines); Log->Print("Fixed '%s'->'%s' on ln %i in %s\n", wrongName.Get(), newPath.Get(), (int)LineNo, Full.Get()); Replacements++; } else Log->Print("Error: moving '%s' to backup (%s).\n", Full.Get(), Err.GetMsg().Get()); } } else Log->Print("Error: Missing header '%s'.\n", wrongName.Get()); } else { Log->Print("Error: '%s' not found in line %i of '%s' -> '%s'\n", wrongName.Get(), (int)LineNo, Fn.Get(), Full.Get()); // return; } } else Log->Print("Error: Line %i is beyond file lines: %i\n", (int)LineNo, (int)Lines.Length()); } else { auto OldReplacements = Replacements; for (auto i: Map) { for (int Offset = 0; (LineNo + Offset >= 1) && Offset >= -1; Offset--) { LString &s = Fi->Lines[LineNo+Offset-1]; if (ReplaceWholeWord(s, i.key, i.value)) { Log->Print("Renamed '%s' -> '%s' at %s:%i\n", i.key, i.value.Get(), Full.Get(), LineNo+Offset); Fi->Dirty = true; Replacements++; Offset = -2; } } } if (OldReplacements == Replacements && Ln.Find("syntax error: id") > 0) { Log->Print("Unhandled: %s\n", Ln.Get()); } } } else { Log->Print("Error: Invalid line %i\n", (int)LineNo); } } else { Log->Print("Error: Fi is NULL\n"); } } } } for (auto &Fi : Files) { if (Fi.Dirty) Fi.Save(); } Log->Print("%i replacements made.\n", Replacements); if (Replacements > 0) d->Output->Value(AppWnd::OutputTab); return Replacements; } void AppWnd::OnBuildStateChanged(bool NewState) { LVariant v; if (!NewState && GetOptions()->GetValue(OPT_FIX_RENAMED, v) && v.CastInt32()) { d->FixBuildWait = true; } } void AppWnd::UpdateState(int Debugging, int Building) { // printf("UpdateState %i %i\n", Debugging, Building); if (Debugging >= 0) d->Debugging = Debugging; if (Building >= 0) { if (d->Building != (Building != 0)) OnBuildStateChanged(Building); d->Building = Building; } SetCtrlEnabled(IDM_COMPILE, !d->Building); SetCtrlEnabled(IDM_BUILD, !d->Building); SetCtrlEnabled(IDM_STOP_BUILD, d->Building); // SetCtrlEnabled(IDM_RUN, !d->Building); // SetCtrlEnabled(IDM_TOGGLE_BREAKPOINT, !d->Building); SetCtrlEnabled(IDM_START_DEBUG, !d->Debugging && !d->Building); SetCtrlEnabled(IDM_PAUSE_DEBUG, d->Debugging); SetCtrlEnabled(IDM_RESTART_DEBUGGING, d->Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, d->Debugging); SetCtrlEnabled(IDM_STEP_INTO, d->Debugging); SetCtrlEnabled(IDM_STEP_OVER, d->Debugging); SetCtrlEnabled(IDM_STEP_OUT, d->Debugging); SetCtrlEnabled(IDM_RUN_TO, d->Debugging); } void AppWnd::AppendOutput(char *Txt, AppWnd::Channels Channel) { if (!d->Output) { LgiTrace("%s:%i - No output panel.\n", _FL); return; } if (Channel < 0 || Channel >= CountOf(d->Output->Txt)) { LgiTrace("%s:%i - Channel range: %i, %i.\n", _FL, Channel, CountOf(d->Output->Txt)); return; } if (!d->Output->Txt[Channel]) { LgiTrace("%s:%i - No log for channel %i.\n", _FL, Channel); return; } if (Txt) { d->Output->Buf[Channel].Add(Txt, strlen(Txt)); } else { auto Ctrl = d->Output->Txt[Channel]; Ctrl->UnSelectAll(); Ctrl->Name(""); } } bool AppWnd::IsClean() { for (auto Doc: d->Docs) { if (!Doc->GetClean()) return false; } for (auto Proj: d->Projects) { if (!Proj->GetClean()) return false; } return true; } struct SaveState { AppWndPrivate *d = NULL; LArray Docs; LArray Projects; std::function Callback; bool Status = true; bool CloseDirty = false; void Iterate() { if (Docs.Length()) { auto doc = Docs[0]; Docs.DeleteAt(0); // printf("Saving doc...\n"); doc->SetClean([this, doc](bool ok) { // printf("SetClean cb ok=%i\n", ok); if (ok) d->OnFile(doc->GetFileName()); else { if (CloseDirty) delete doc; Status = false; } // printf("SetClean cb iter\n", ok); Iterate(); }); } else if (Projects.Length()) { auto proj = Projects[0]; Projects.DeleteAt(0); // printf("Saving proj...\n"); proj->SetClean([this, proj](bool ok) { if (ok) d->OnFile(proj->GetFileName(), true); else { if (CloseDirty) delete proj; Status = false; } Iterate(); }); } else { // printf("Doing callback...\n"); if (Callback) Callback(Status); // printf("Deleting...\n"); delete this; } } }; void AppWnd::SaveAll(std::function Callback, bool CloseDirty) { auto ss = new SaveState; ss->d = d; ss->Callback = Callback; ss->CloseDirty = CloseDirty; for (auto Doc: d->Docs) { if (!Doc->GetClean()) ss->Docs.Add(Doc); } for (auto Proj: d->Projects) { if (!Proj->GetClean()) ss->Projects.Add(Proj); } ss->Iterate(); } void AppWnd::CloseAll() { SaveAll([&](auto status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } while (d->Docs[0]) delete d->Docs[0]; IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects[0]) delete d->Projects[0]; DeleteObj(d->DbgContext); }); } bool AppWnd::OnRequestClose(bool IsOsQuit) { if (!IsClean()) { SaveAll([](bool status) { LCloseApp(); }, true); return false; } else { return LWindow::OnRequestClose(IsOsQuit); } } bool AppWnd::OnBreakPoint(LDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { auto fn = doc->GetFileName(); bool Match = !Stricmp(fn, b.File.Get()); if (Match) doc->AddBreakPoint(b.Line, Add); } if (d->DbgContext) d->DbgContext->OnBreakPoint(b, Add); return true; } bool AppWnd::LoadBreakPoints(IdeDoc *doc) { if (!doc) return false; auto fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(LDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &bp = d->BreakPoints[i]; db->SetBreakPoint(&bp); } return true; } bool AppWnd::ToggleBreakpoint(const char *File, ssize_t Line) { bool DeleteBp = false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(File, b.File) && b.Line == Line) { OnBreakPoint(b, false); d->BreakPoints.DeleteAt(i); DeleteBp = true; break; } } if (!DeleteBp) { LDebugger::BreakPoint &b = d->BreakPoints.New(); b.File = File; b.Line = Line; OnBreakPoint(b, true); } return true; } void AppWnd::DumpHistory() { #if 0 LgiTrace("History %i of %i\n", d->HistoryLoc, d->CursorHistory.Length()); for (int i=0; iCursorHistory.Length(); i++) { FileLoc &p = d->CursorHistory[i]; LgiTrace(" [%i] = %s, %i %s\n", i, p.File.Get(), p.Line, d->HistoryLoc == i ? "<-----":""); } #endif } /* void CheckHistory(LArray &CursorHistory) { if (CursorHistory.Length() > 0) { FileLoc *loc = &CursorHistory[0]; for (unsigned i=CursorHistory.Length(); iInHistorySeek) { if (d->CursorHistory.Length() > 0) { FileLoc &Last = d->CursorHistory.Last(); if (_stricmp(File, Last.File) == 0 && abs(Last.Line - Line) <= 1) { // Previous or next line... just update line number Last.Line = Line; DumpHistory(); return; } // Add new entry d->HistoryLoc++; FileLoc &loc = d->CursorHistory[d->HistoryLoc]; #ifdef WIN64 if ((NativeInt)loc.File.Get() == 0xcdcdcdcdcdcdcdcd) LAssert(0); // wtf? else #endif loc.Set(File, Line); } else { // Add new entry d->CursorHistory[0].Set(File, Line); } // Destroy any history after the current... d->CursorHistory.Length(d->HistoryLoc+1); DumpHistory(); } } void AppWnd::OnFile(char *File, bool IsProject) { d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { IdeDoc *Doc = new IdeDoc(this, Src, 0); if (Doc) { d->Docs.Insert(Doc); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); Doc->Raise(); if (FileName) d->OnFile(FileName); } return Doc; } IdeDoc *AppWnd::GetCurrentDoc() { if (d->Mdi) return dynamic_cast(d->Mdi->GetTop()); return NULL; } IdeDoc *AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory) { if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) { Doc->SetLine(Line, CurIp); Doc->Focus(true); } if (!WithHistory) d->InHistorySeek = false; return Doc; } IdeDoc *AppWnd::FindOpenFile(char *FileName) { List::I it = d->Docs.begin(); for (IdeDoc *i=*it; i; i=*++it) { auto f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; if (*f == '.') LMakePath(Path, sizeof(Path), Base, f); else strcpy_s(Path, sizeof(Path), f); if (stricmp(Path, FileName) == 0) return i; } } else { if (stricmp(f, FileName) == 0) return i; } } } return 0; } IdeDoc *AppWnd::OpenFile(const char *FileName, NodeSource *Src) { static bool DoingProjectFind = false; IdeDoc *Doc = 0; const char *File = Src ? Src->GetFileName() : FileName; if (!Src && !ValidStr(File)) { LgiTrace("%s:%i - No source or file?\n", _FL); return NULL; } LString FullPath; if (LIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto p: Projs) { auto ProjPath = p->GetBasePath(); char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), ProjPath, File); LString Path = s; if (p->CheckExists(Path)) { FullPath = Path; File = FullPath; break; } } } } // Sniff type... - bool isLgiProj = false; + bool probablyLgiProj = false; if (!Stricmp(LGetExtension(File), "xml")) { LFile f(File, O_READ); if (f) { char buf[256]; auto rd = f.Read(buf, sizeof(buf)); if (rd > 0) - isLgiProj = Strnistr(buf, "IsFileOpen(File); if (!Doc) { if (Src) { Doc = NewDocWnd(File, Src); } else if (!DoingProjectFind) { DoingProjectFind = true; List::I Proj = d->Projects.begin(); for (IdeProject *p=*Proj; p && !Doc; p=*++Proj) { p->InProject(LIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && LFileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { Doc->OpenFile(File); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { Doc->SetEditorParams(4, 4, true, false); if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { for (auto p: d->Projects) if (!p->GetParentProject()) return p; return NULL; } IdeProject *AppWnd::OpenProject(const char *FileName, IdeProject *ParentProj, bool Create, bool Dep) { if (!FileName) { LgiTrace("%s:%i - Error: No filename.\n", _FL); return NULL; } if (d->IsProjectOpen(FileName)) { LgiTrace("%s:%i - Warning: Project already open.\n", _FL); return NULL; } IdeProject *p = new IdeProject(this); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } LString::Array Inc; p->BuildIncludePaths(Inc, false, false, PlatformCurrent); d->FindSym->SetIncludePaths(Inc); p->SetParentProject(ParentProj); ProjectStatus Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!Dep) { auto d = strrchr(FileName, DIR_CHAR); if (d++) { char n[256]; sprintf(n, "%s [%s]", AppName, d); Name(n); } } } else { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, FileName); DeleteObj(p); if (Status == OpenError) d->RemoveRecent(FileName); } if (!GetTree()->Selection()) { GetTree()->Select(GetTree()->GetChild()); } GetTree()->Focus(true); return p; } LMessage::Result AppWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_MAKEFILES_CREATED: { IdeProject *p = (IdeProject*)m->A(); if (p) p->OnMakefileCreated(); break; } case M_LAST_MAKEFILE_CREATED: { if (LAppInst->GetOption("exit")) LCloseApp(); break; } case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, GetBuildMode()); else printf("%s:%i - No root project.\n", _FL); break; } case M_BUILD_DONE: { UpdateState(-1, false); IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case M_BUILD_ERR: { char *Msg = (char*)m->B(); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { LAutoString Text((char*) m->A()); Channels Ch = (Channels) m->B(); AppendOutput(Text, Ch); break; } case M_SELECT_TAB: { if (!d->Output) break; d->Output->Value(m->A()); break; } case M_DEBUG_ON_STATE: { bool Debugging = m->A(); bool Running = m->B(); if (d->Running != Running) { bool RunToNotRun = d->Running && !Running; d->Running = Running; if (RunToNotRun && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(LNotifyValueChanged); } } if (d->Debugging != Debugging) { d->Debugging = Debugging; if (!Debugging) { IdeDoc::ClearCurrentIp(); IdeDoc *c = GetCurrentDoc(); if (c) c->UpdateControl(); // Shutdown the debug context and free the memory DeleteObj(d->DbgContext); } } SetCtrlEnabled(IDM_START_DEBUG, !Debugging || !Running); SetCtrlEnabled(IDM_PAUSE_DEBUG, Debugging && Running); SetCtrlEnabled(IDM_RESTART_DEBUGGING, Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, Debugging); SetCtrlEnabled(IDM_STEP_INTO, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OVER, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OUT, Debugging && !Running); SetCtrlEnabled(IDM_RUN_TO, Debugging && !Running); break; } default: { if (d->DbgContext) d->DbgContext->OnEvent(m); break; } } return LWindow::OnEvent(m); } bool AppWnd::OnNode(const char *Path, ProjectNode *Node, FindSymbolSystem::SymAction Action) { // This takes care of adding/removing files from the symbol search engine. if (!Path || !Node) return false; if (d->FindSym) d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } LOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public LDialog { AppWnd *App; LFontType Font; public: Options(AppWnd *a) { SetParent(App = a); if (LoadFromResource(IDD_OPTIONS)) { SetCtrlEnabled(IDC_FONT, false); MoveToCenter(); if (!Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Font.GetSystemFont("Fixed"); } char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } LVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { LVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { Font.DoUI(this, [&](auto ui) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } }); break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { const char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; int64 RowLen = GetCtrlValue(IDC_MEM_ROW_LEN); bool InHex = GetCtrlValue(IDC_MEM_HEX) != 0; d->DbgContext->FormatMemoryDump(iWord, (int)RowLen, InHex); } } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (n.Type == LNotifyDeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (n.Type == LNotifyReturnKey && d->DbgContext) { const char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (n.Type == LNotifyReturnKey) { if (d->DbgContext) { const char *s = Ctrl->Name(); if (s) { auto sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; d->DbgContext->OnMemoryDump(s, iWord, (int)GetCtrlValue(IDC_MEM_ROW_LEN), GetCtrlValue(IDC_MEM_HEX) != 0); } else if (d->DbgContext->MemoryDump) { d->DbgContext->MemoryDump->Print("No address specified."); } else { LAssert(!"No MemoryDump."); } } else LAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (n.Type == LNotifyReturnKey) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && n.Type == LNotifyValueChanged) { switch (Ctrl->Value()) { case AppWnd::LocalsTab: { d->DbgContext->UpdateLocals(); break; } case AppWnd::WatchTab: { d->DbgContext->UpdateWatches(); break; } case AppWnd::RegistersTab: { d->DbgContext->UpdateRegisters(); break; } case AppWnd::CallStackTab: { d->DbgContext->UpdateCallStack(); break; } case AppWnd::ThreadsTab: { d->DbgContext->UpdateThreads(); break; } default: break; } } break; } case IDC_LOCALS_LIST: { if (d->Output->Locals && n.Type == LNotifyItemDoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { const char *Var = it->GetText(2); const char *Val = it->GetText(3); if (Var) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::ObjectTab); d->DbgContext->DumpObject(Var, Val); } } } break; } case IDC_CALL_STACK: { if (n.Type == LNotifyValueChanged) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (n.Type == LNotifyItemSelect) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { LAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { LAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); const char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (n.Type) { case LNotifyDeleteKey: { LArray Sel; for (LTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case LNotifyItemClick: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case LNotifyContainerClick: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } default: break; } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (n.Type == LNotifyItemSelect) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { LString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::Build() { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL); return; } IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); p->Build(false, GetBuildMode()); } else if ((Top = TopDoc())) { Top->Build(); } }); return false; } class RenameDlg : public LDialog { AppWnd *App; public: static RenameDlg *Inst; RenameDlg(AppWnd *a) { Inst = this; SetParent(App = a); MoveSameScreen(a); if (LoadFromResource(IDC_RENAME)) { LVariant v; if (App->GetOptions()->GetValue(OPT_FIX_RENAMED, v)) SetCtrlValue(IDC_FIX_RENAMED, v.CastInt32()); if (App->GetOptions()->GetValue(OPT_RENAMED_SYM, v)) SetCtrlName(IDC_SYM, v.Str()); SetAlwaysOnTop(true); DoModeless(); } } ~RenameDlg() { Inst = NULL; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_APPLY: { LVariant v; App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); App->GetOptions()->SerializeFile(true); break; } case IDC_CLOSE: { EndModeless(); break; } } return 0; } }; void AppWnd::SeekHistory(int Offset) { d->SeekHistory(Offset); } RenameDlg *RenameDlg::Inst = NULL; bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (auto p: d->Projects) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (LTreeItem *i = Node->GetParent(); i; i = i->GetParent()) { i->Expanded(true); } Node->Select(true); Node->ScrollTo(); return true; } } return false; } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_EXIT: { LCloseApp(); break; } case IDM_OPTIONS: { auto dlg = new Options(this); dlg->DoModal(NULL); break; } case IDM_HELP: { LExecute(APP_URL); break; } case IDM_ABOUT: { LAbout a(this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc && d->Mdi) { LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { LFileSelect *s = new LFileSelect; s->Parent(this); // printf("File open dlg from thread=%u\n", GetCurrentThreadId()); s->Open([&](auto s, auto ok) { // printf("open handler start... ok=%i thread=%u\n", ok, GetCurrentThreadId()); if (ok) OpenFile(s->Name()); // printf("open handler deleting...\n"); delete s; // printf("open handler deleted...\n"); }); break; } case IDM_SAVE_ALL: { SaveAll(NULL); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(NULL); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Save([&](auto s, auto ok) { Top->SetFileName(s->Name(), true); d->OnFile(s->Name()); delete s; }); } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top) { if (Top->OnRequestClose(false)) { Top->Quit(); } } DeleteObj(d->DbgContext); break; } case IDM_CLOSE_ALL: { CloseAll(); Name(AppName); break; } // // Editor // case IDM_UNDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(NULL); else { LInput *Inp = new LInput(this, NULL, LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); Inp->DoModal([&](auto dlg, auto code) { LString s = Inp->GetStr(); LString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { LString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true); } else LgiMsg(this, "Error: Needs a file name as well.", AppName); delete Inp; }); } break; } case IDM_CUT: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_PASTE); break; } case IDM_FIND_IN_FILES: { if (!d->Finder) { d->Finder.Reset(new FindInFilesThread(d->AppHnd)); } if (d->Finder) { if (!d->FindParameters && d->FindParameters.Reset(new FindParams)) { LVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } FindInFiles Dlg(this, d->FindParameters); LViewI *Focus = GetFocus(); if (Focus) { LTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { LAutoString a(Edit->GetSelection()); Dlg.Params->Text = a; } } IdeProject *p = RootProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) Dlg.Params->Dir = Base; } Dlg.DoModal([&](auto dlg, auto code) { if (p && Dlg.Params->Type == FifSearchSolution) { Dlg.Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg.Params->ProjectFiles.Add(s); } } LVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) new FindParams(d->FindParameters)); }); } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { d->FindSym->OpenSearchDlg(this, [&](auto r) { if (r.File) GotoReference(r.File, r.Line, false); }); } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { auto d = new FindInProject(this); d->DoModal([](auto dlg, auto ctrlId){ delete dlg; }); } break; } case IDM_FIND_REFERENCES: { LViewI *f = LAppInst->GetFocus(); LDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; LString Txt = doc->Name(); char *s = Txt.Get() + c; char *e = s; while ( s > Txt.Get() && IsSymbolChar(s[-1])) s--; while (*e && IsSymbolChar(*e)) e++; if (e <= s) break; LString Word(s, e - s); if (!d->Finder) d->Finder.Reset(new FindInFilesThread(d->AppHnd)); if (!d->Finder) break; IdeProject *p = RootProject(); if (!p) break; List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); LAutoPtr Params(new FindParams); Params->Type = FifSearchSolution; Params->MatchWord = true; Params->Text = Word; for (unsigned i = 0; i < Nodes.Length(); i++) { Params->ProjectFiles.New() = Nodes[i]->GetFullPath(); } d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) Params.Release()); break; } case IDM_PREV_LOCATION: { d->SeekHistory(-1); break; } case IDM_NEXT_LOCATION: { d->SeekHistory(1); break; } // // Project // case IDM_NEW_PROJECT: { CloseAll(); IdeProject *p; d->Projects.Insert(p = new IdeProject(this)); if (p) { p->CreateProject(); } break; } case IDM_NEW_PROJECT_TEMPLATE: { NewProjectFromTemplate(this); break; } case IDM_OPEN_PROJECT: { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Projects", "*.xml"); s->Open([&](auto s, auto ok) { if (ok) { CloseAll(); OpenProject(s->Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } delete s; }); break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Developer Studio Project", "*.dsp"); s->Open([&](auto s, auto ok) { if (ok) p->ImportDsp(s->Name()); delete s; }); } break; } case IDM_RUN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(); }); break; } case IDM_VALGRIND: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(ExeValgrind); }); break; } case IDM_FIX_MISSING_FILES: { IdeProject *p = RootProject(); if (p) p->FixMissingFiles(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_FIND_DUPE_SYM: { IdeProject *p = RootProject(); if (p) p->FindDuplicateSymbols(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_RENAME_SYM: { if (!RenameDlg::Inst) new RenameDlg(this); break; } case IDM_START_DEBUG: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); return; } - if (d->DbgContext) - { - d->DbgContext->OnCommand(IDM_CONTINUE); - } - else if ((d->DbgContext = p->Execute(ExeDebug))) - { - d->DbgContext->DebuggerLog = d->Output->DebuggerLog; - d->DbgContext->Watch = d->Output->Watch; - d->DbgContext->Locals = d->Output->Locals; - d->DbgContext->CallStack = d->Output->CallStack; - d->DbgContext->Threads = d->Output->Threads; - d->DbgContext->ObjectDump = d->Output->ObjectDump; - d->DbgContext->Registers = d->Output->Registers; - d->DbgContext->MemoryDump = d->Output->MemoryDump; - - d->DbgContext->OnCommand(IDM_START_DEBUG); - - d->Output->Value(AppWnd::DebugTab); - d->Output->DebugEdit->Focus(true); - } - }); + LString ErrMsg; + if (d->DbgContext) + { + d->DbgContext->OnCommand(IDM_CONTINUE); + } + else if ((d->DbgContext = p->Execute(ExeDebug, &ErrMsg))) + { + d->DbgContext->DebuggerLog = d->Output->DebuggerLog; + d->DbgContext->Watch = d->Output->Watch; + d->DbgContext->Locals = d->Output->Locals; + d->DbgContext->CallStack = d->Output->CallStack; + d->DbgContext->Threads = d->Output->Threads; + d->DbgContext->ObjectDump = d->Output->ObjectDump; + d->DbgContext->Registers = d->Output->Registers; + d->DbgContext->MemoryDump = d->Output->MemoryDump; + + d->DbgContext->OnCommand(IDM_START_DEBUG); + + d->Output->Value(AppWnd::DebugTab); + d->Output->DebugEdit->Focus(true); + } + else if (ErrMsg) + { + LgiMsg(this, "Error: %s", AppName, MB_OK, ErrMsg.Get()); + } break; } case IDM_TOGGLE_BREAKPOINT: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) ToggleBreakpoint(Cur->GetFileName(), Cur->GetLine()); break; } case IDM_ATTACH_TO_PROCESS: case IDM_PAUSE_DEBUG: case IDM_RESTART_DEBUGGING: case IDM_RUN_TO: case IDM_STEP_INTO: case IDM_STEP_OVER: case IDM_STEP_OUT: { if (d->DbgContext) d->DbgContext->OnCommand(Cmd); break; } case IDM_STOP_DEBUG: { if (d->DbgContext && d->DbgContext->OnCommand(Cmd)) { DeleteObj(d->DbgContext); } break; } case IDM_BUILD: { Build(); break; } case IDM_STOP_BUILD: { IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case IDM_CLEAN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Clean(true, GetBuildMode()); }); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { LString Exe = p->GetExecutable(GetCurrentPlatform()); if (LFileExists(Exe)) { Depends Dlg(this, Exe); } else { LgiMsg(this, "Couldn't find '%s'\n", AppName, MB_OK, Exe ? Exe.Get() : ""); } } break; } case IDM_SP_TO_TAB: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(true); break; } case IDM_TAB_TO_SP: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(false); break; } case IDM_ESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(true); break; } case IDM_DESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(false); break; } case IDM_SPLIT: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->SplitSelection(i->GetStr()); delete i; }); break; } case IDM_JOIN: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->JoinSelection(i->GetStr()); delete i; }); break; } case IDM_EOL_LF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(false); break; } case IDM_EOL_CRLF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(true); break; } case IDM_LOAD_MEMDUMP: { NewMemDumpViewer(this); break; } case IDM_SYS_CHAR_SUPPORT: { new SysCharSupport(this); break; } default: { int index = Cmd - IDM_RECENT_FILE; auto r = d->RecentFiles.IdxCheck(index)? d->RecentFiles[index] : NULL; if (r) { IdeDoc *f = d->IsFileOpen(r); if (f) { f->Raise(); } else { OpenFile(r); } } index = Cmd - IDM_RECENT_PROJECT; auto p = d->RecentProjects.IdxCheck(index) ? d->RecentProjects[index] : NULL; if (p) { CloseAll(); OpenProject(p, NULL, false); if (d->Tree) { d->Tree->Focus(true); } } IdeDoc *Doc = d->Docs[Cmd - IDM_WINDOWS]; if (Doc) { Doc->Raise(); } IdePlatform PlatIdx = (IdePlatform) (Cmd - IDM_MAKEFILE_BASE); const char *Platform = PlatIdx >= 0 && PlatIdx < PlatformMax ? PlatformNames[Cmd - IDM_MAKEFILE_BASE] : NULL; if (Platform) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatIdx, false); } } break; } } return 0; } LTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return d->Mdi ? dynamic_cast(d->Mdi->GetTop()) : NULL; } LTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { LViewI *f = GetFocus(); LgiTrace("%s:%i - Edit doesn't have focus, f=%p %s doc.edit=%s\n", _FL, f, f ? f->GetClass() : 0, Doc->Name()); } } return 0; } void AppWnd::OnProjectDestroy(IdeProject *Proj) { if (d) { auto locked = Lock(_FL); // printf("OnProjectDestroy(%s) %i\n", Proj->GetFileName(), locked); d->Projects.Delete(Proj); if (locked) Unlock(); } else LAssert(!"No priv"); } void AppWnd::OnProjectChange() { LArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { auto locked = Lock(_FL); d->Docs.Delete(Doc); d->UpdateMenus(); if (locked) Unlock(); } else { LAssert(!"OnDocDestroy no priv...\n"); } } BuildConfig AppWnd::GetBuildMode() { LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BuildRelease; } return BuildDebug; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } LStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } LStream *AppWnd::GetDebugLog() { return d->Output->Txt[AppWnd::DebugTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms) { d->FindSym->Search(ResultsSinkHnd, Sym, AllPlatforms); } bool AppWnd::GetSystemIncludePaths(::LArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - LSubProcess sp1("echo"); LSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; LStringPipe p; while ((r = sp1.Read(Buf, sizeof(Buf))) > 0) { p.Write(Buf, r); } bool InIncludeList = false; while (p.Pop(Buf, sizeof(Buf))) { if (stristr(Buf, "#include")) { InIncludeList = true; } else if (stristr(Buf, "End of search")) { InIncludeList = false; } else if (InIncludeList) { LAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH_LEN]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (LFileExists(p)) { LFile f; if (f.Open(p, O_READ)) { LXmlTree t; LXmlTag r; if (t.Read(&r, &f)) { LXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { LXmlTag *Projects = NULL; char *Name; for (auto c: Opts->Children) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } LXmlTag *VCDirectories = NULL; if (Projects) for (auto c: Projects->Children) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } if (VCDirectories) for (auto prop: VCDirectories->Children) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); LToken t(Bar ? Bar + 1 : prop->GetContent(), ";"); for (int i=0; iSystemIncludePaths.New().Reset(NewStr(s)); } } } } } } } #endif } for (int i=0; iSystemIncludePaths.Length(); i++) { Paths.Add(NewStr(d->SystemIncludePaths[i])); } return true; } /* class SocketTest : public LWindow, public LThread { LTextLog *Log; public: SocketTest() : LThread("SocketTest") { Log = new LTextLog(100); SetPos(LRect(200, 200, 900, 800)); Attach(0); Visible(true); Log->Attach(this); Run(); } int Main() { LSocket s; s.SetTimeout(15000); Log->Print("Starting...\n"); auto r = s.Open("192.168.1.30", 7000); Log->Print("Open =%i\n", r); return 0; } }; */ class TestView : public LView { public: TestView() { LRect r(10, 10, 110, 210); SetPos(r); Sunken(true); printf("_BorderSize=%i\n", _BorderSize); } void OnPaint(LSurface *pdc) { auto c = GetClient(); pdc->Colour(LColour::Red); pdc->Line(c.x1, c.y1, c.x2, c.y2); pdc->Ellipse(c.x1+(c.X()/2)+10, c.y1+(c.Y()/2), c.X()/2, c.Y()/2); } }; #include "lgi/common/Tree.h" #include "lgi/common/List.h" class Test : public LWindow { public: Test() { LRect r(100, 100, 800, 700); SetPos(r); Name("Test"); SetQuitOnClose(true); if (Attach(0)) { // AddView(new TestView); // auto t = new LTree(10, 10, 10, 100, 200); auto t = new LTextLabel(10, 10, 10, 100, 35, "Text"); AddView(t); AttachChildren(); Visible(true); } } }; int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); LApp a(AppArgs, "LgiIde"); if (a.IsOk()) { - #if 0 - a.AppWnd = new Test; - #else a.AppWnd = new AppWnd; // a.AppWnd->_Dump(); - #endif a.Run(); } return 0; } diff --git a/Ide/Code/SimpleCppParser.cpp b/Ide/Code/SimpleCppParser.cpp --- a/Ide/Code/SimpleCppParser.cpp +++ b/Ide/Code/SimpleCppParser.cpp @@ -1,951 +1,952 @@ /* Known bugs: 1) Duplicate brackets in #defines, e.g: #if SOME_DEF if (expr) { #else if (different_expr) { #endif Breaks the bracket counting. Causing the depth to get out of sync with capture... Workaround: Move the brackets outside of the #define'd area if possible. 2) This syntax is wrongly assumed to be a structure: struct netconn* netconn_alloc(enum netconn_type t, netconn_callback callback) { ... } Can't find these symbols: do_recv (in api_msg.c) process_apcp_msg (in ape-apcp.c) recv_udp (in api_msg.c) Wrong position: lwip_select (out by 4 lines?) nad_apcp_send tcpip_callback_with_block appears 3 times? */ #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" #include "ParserCommon.h" #if 0 -// #define DEBUG_FILE "\\ape-apcp.c" -#define DEBUG_FILE "gen_probe.c" +#define DEBUG_FILE "FileSelect.cpp" #endif -#define DEBUG_LINE 593 +#define DEBUG_LINE 400 const char *TypeToStr(DefnType t) { switch (t) { default: case DefnNone: return "DefnNone"; case DefnDefine: return "DefnDefine"; case DefnFunc: return "DefnFunc"; case DefnClass: return "DefnClass"; case DefnEnum: return "DefnEnum"; case DefnEnumValue: return "DefnEnumValue"; case DefnTypedef: return "DefnTypedef"; case DefnVariable: return "DefnVariable"; } } bool IsFirst(LArray &a, int depth) { if (depth == 0) return true; for (int i=0; i 1) return false; return true; } bool IsFuncNameChar(char c) { return strchr("_:=~[]<>-+", c) || IsAlpha(c) || IsDigit(c); } bool ParseFunction(LRange &Return, LRange &Name, LRange &Args, const char *Defn) { if (!Defn) return false; int Depth = 0; const char *c, *Last = NULL; for (c = Defn; *c; c++) { if (*c == '(') { if (Depth == 0) Last = c; Depth++; } else if (*c == ')') { if (Depth > 0) Depth--; else { LgiTrace("%s:%i - Fn parse error '%s' (%i)\n", _FL, Defn, (int)(c - Defn)); return false; } } } if (!Last) return false; Args.Start = Last - Defn; Args.Len = c - Last; while (Last > Defn && IsWhiteSpace(Last[-1])) Last--; while (Last > Defn && IsFuncNameChar(Last[-1])) Last--; Return.Start = 0; Return.Len = Last - Defn; Name.Start = Last - Defn; Name.Len = Args.Start - Name.Start; if (Name.Len == 0 || Args.Len == 0) { LgiTrace("%s:%i - Fn parse empty section '%s' (%i,%i,%i)\n", _FL, Defn, (int)Return.Len, (int)Name.Len, (int)Args.Len); return false; } return true; } bool SeekPtr(char16 *&s, char16 *end, int &Line) { if (s > end) { LAssert(0); return false; } while (s < end) { if (*s == '\n') Line++; s++; } return true; } DefnType GuessDefnType(char16 *def, bool debug) { // In the context of a class member, this could be a variable defn: // // bool myVar = true; // bool myVar = someFn(); // // or a function defn: // // bool myFunction(int someArg = 10); // // Try to guess which it is: int roundDepth = 0; int equalsAtZero = 0; for (auto s = def; *s; s++) { if (*s == '(') roundDepth++; else if (*s == ')') roundDepth--; else if (*s == '=') { if (roundDepth == 0) equalsAtZero++; } } /* if (equalsAtZero && debug) printf("equalsAtZero: %S\n", def); */ return equalsAtZero ? DefnVariable : DefnFunc; } bool BuildCppDefnList(const char *FileName, char16 *Cpp, LArray &Defns, int LimitTo, bool Debug) { if (!Cpp) return false; static char16 StrClass[] = {'c', 'l', 'a', 's', 's', 0}; static char16 StrStruct[] = {'s', 't', 'r', 'u', 'c', 't', 0}; static char16 StrEnum[] = {'e', 'n', 'u', 'm', 0}; static char16 StrOpenBracket[] = {'{', 0}; static char16 StrColon[] = {':', 0}; static char16 StrSemiColon[] = {';', 0}; static char16 StrDefine[] = {'d', 'e', 'f', 'i', 'n', 'e', 0}; static char16 StrExtern[] = {'e', 'x', 't', 'e', 'r', 'n', 0}; static char16 StrTypedef[] = {'t', 'y', 'p', 'e', 'd', 'e', 'f', 0}; static char16 StrC[] = {'\"', 'C', '\"', 0}; char WhiteSpace[] = " \t\r\n"; char16 *s = Cpp; char16 *LastDecl = s; int Depth = 0; int Line = 0; #ifdef DEBUG_LINE int PrevLine = 0; #endif int CaptureLevel = 0; int InClass = false; // true if we're in a class definition char16 *CurClassDecl = 0; bool IsEnum = 0, IsClass = false, IsStruct = false; bool FnEmit = false; // don't emit functions between a f(n) and the next '{' // they are only parent class initializers LArray ConditionalIndex; int ConditionalDepth = 0; bool ConditionalFirst = true; bool ConditionParsingErr = false; #ifdef DEBUG_FILE Debug |= FileName && stristr(FileName, DEBUG_FILE) != NULL; #endif while (s && *s) { // skip ws while (*s && strchr(" \t\r", *s)) s++; #ifdef DEBUG_LINE if (Debug) { if (Line >= DEBUG_LINE - 1) + { int asd=0; + } else if (PrevLine != Line) { PrevLine = Line; // LgiTrace("%s:%i '%.10S'\n", FileName, Line + 1, s); } } #endif // tackle decl switch (*s) { case 0: break; case '\n': { Line ++; s++; break; } case '#': { const char16 *Hash = s; s++; skipws(s) const char16 *End = s; while (*End && IsAlpha(*End)) End++; if ( ( LimitTo == DefnNone || (LimitTo & DefnDefine) != 0 ) && (End - s) == 6 && StrncmpW(StrDefine, s, 6) == 0 ) { s += 6; defnskipws(s); LexCpp(s, LexNoReturn); char16 r = *s; *s = 0; Defns.New().Set(DefnDefine, FileName, Hash, Line + 1); *s = r; } char16 *Eol = Strchr(s, '\n'); if (!Eol) Eol = s + Strlen(s); // bool IsIf = false, IsElse = false, IsElseIf = false; if ( ((End - s) == 2 && !Strncmp(L"if", s, End - s)) || ((End - s) == 5 && !Strncmp(L"ifdef", s, End - s)) || ((End - s) == 6 && !Strncmp(L"ifndef", s, End - s)) ) { ConditionalIndex[ConditionalDepth] = 1; ConditionalDepth++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth++=%i Line=%i\n%.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } else if ( ((End - s) == 4 && !Strncmp(L"else", s, End - s)) || ((End - s) == 7 && !Strncmp(L"else if", s, 7)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) { ConditionalIndex[ConditionalDepth-1]++; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth=%i Idx++ Line=%i\n%.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } } else if ( ((End - s) == 5 && !Strncmp(L"endif", s, End - s)) ) { if (ConditionalDepth <= 0 && !ConditionParsingErr) { ConditionParsingErr = true; LgiTrace("%s:%i - Error parsing pre-processor conditions: %s:%i\n", _FL, FileName, Line+1); } if (ConditionalDepth > 0) ConditionalDepth--; ConditionalFirst = IsFirst(ConditionalIndex, ConditionalDepth); if (Debug) LgiTrace("%s:%i - ConditionalDepth--=%i Line=%i\n%.*S\n", _FL, ConditionalDepth, Line+1, Eol - s + 1, s - 1); } while (*s) { if (*s == '\n') { // could be end of # command char Last = (s[-1] == '\r') ? s[-2] : s[-1]; if (Last != '\\') break; Line++; } s++; LastDecl = s; } break; } case '\"': case '\'': { char16 Delim = *s; s++; while (*s) { if (*s == Delim) { s++; break; } if (*s == '\\') s++; if (*s == '\n') Line++; s++; } break; } case '{': { s++; if (ConditionalFirst) Depth++; FnEmit = false; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif break; } case '}': { s++; if (ConditionalFirst) { if (Depth > 0) { Depth--; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif } else { #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - ERROR Depth already=%i @ line %i\n", _FL, Depth, Line+1); #endif } } defnskipws(s); LastDecl = s; if (IsEnum) { if (IsAlpha(*s)) // typedef'd enum? { LAutoWString t(LexCpp(s, LexStrdup)); if (t) Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } IsEnum = false; } break; } case ';': { s++; LastDecl = s; if (Depth == 0 && InClass) { // Check for typedef struct name char16 *Start = s - 1; while (Start > Cpp && Start[-1] != '}') Start--; LString TypeDef = LString(Start, s - Start - 1).Strip(); if (TypeDef.Length() > 0) { if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { Defns.New().Set(DefnClass, FileName, TypeDef, Line + 1); } } // End the class def InClass = false; CaptureLevel = 0; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif DeleteArray(CurClassDecl); } break; } case '/': { s++; if (*s == '/') { // one line comment while (*s && *s != '\n') s++; LastDecl = s; } else if (*s == '*') { // multi line comment s++; while (*s) { if (s[0] == '*' && s[1] == '/') { s += 2; break; } if (*s == '\n') Line++; s++; } LastDecl = s; } break; } case '(': { s++; if (Depth == CaptureLevel && !FnEmit && LastDecl && ConditionalFirst) { // function? // find start: char16 *Start = LastDecl; skipws(Start); if (Strnstr(Start, L"__attribute__", s - Start)) break; // find end (matching closing bracket) int RoundDepth = 1; char16 *End = s; while (*End) { if (*End == '/' && End[1] == '/') { End = Strchr(End, '\n'); if (!End) break; } if (*End == '(') { RoundDepth++; } else if (*End == ')') { if (--RoundDepth == 0) break; } End++; } if (End && *End) { End++; auto Buf = NewStrW(Start, End-Start); if (Buf) { // remove new-lines auto Out = Buf; for (auto In = Buf; *In; In++) { if (*In == '\r' || *In == '\n' || *In == '\t' || *In == ' ') { *Out++ = ' '; skipws(In); In--; } else if (In[0] == '/' && In[1] == '/') { In = StrchrW(In, '\n'); if (!In) break; } else { *Out++ = *In; } } *Out++ = 0; if (CurClassDecl) { char16 Str[1024]; ZeroObj(Str); StrncpyW(Str, Buf, CountOf(Str)); char16 *b = StrchrW(Str, '('); if (b) { // Skip whitespace between name and '(' while ( b > Str && strchr(WhiteSpace, b[-1]) ) { b--; } // Skip over to the start of the name.. while ( b > Str && b[-1] != '*' && b[-1] != '&' && !strchr(WhiteSpace, b[-1]) ) { b--; } auto ClsLen = StrlenW(CurClassDecl); auto BLen = StrlenW(b); if (ClsLen + BLen + 1 > CountOf(Str)) { LgiTrace("%s:%i - Defn too long: %i\n", _FL, ClsLen + BLen + 1); } else { memmove(b + ClsLen + 2, b, sizeof(*b) * (BLen+1)); memcpy(b, CurClassDecl, sizeof(*b) * ClsLen); b += ClsLen; *b++ = ':'; *b++ = ':'; DeleteArray(Buf); Buf = NewStrW(Str); } } } // cache f(n) def auto Type = GuessDefnType(Buf, Debug); if ( ( LimitTo == DefnNone || (LimitTo & Type) != 0 ) && *Buf != ')' ) Defns.New().Set(Type, FileName, Buf, Line + 1); DeleteArray(Buf); while (*End && !strchr(";:{#", *End)) End++; SeekPtr(s, End, Line); FnEmit = *End != ';'; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - FnEmit=%i Depth=%i @ line %i\n", _FL, FnEmit, Depth, Line+1); #endif } } } break; } default: { bool InTypedef = false; if (IsAlpha(*s) || IsDigit(*s) || *s == '_') { char16 *Start = s; s++; defnskipsym(s); auto TokLen = s - Start; if (TokLen == 6 && StrncmpW(StrExtern, Start, 6) == 0) { // extern "C" block char16 *t = LexCpp(s, LexStrdup); // "C" if (t && StrcmpW(t, StrC) == 0) { defnskipws(s); if (*s == '{') { Depth--; } } DeleteArray(t); } else if (TokLen == 7 && StrncmpW(StrTypedef, Start, 7) == 0) { // Typedef skipws(s); IsStruct = !Strnicmp(StrStruct, s, StrlenW(StrStruct)); IsClass = !Strnicmp(StrClass, s, StrlenW(StrClass)); if (IsStruct || IsClass) { Start = s; InTypedef = true; goto DefineStructClass; } IsEnum = !Strnicmp(StrEnum, s, StrlenW(StrEnum)); if (IsEnum) { Start = s; s += 4; goto DefineEnum; } LStringPipe p; char16 *i; for (i = Start; i && *i;) { switch (*i) { case ' ': case '\t': { p.Push(Start, i - Start); defnskipws(i); char16 sp[] = {' ', 0}; p.Push(sp); Start = i; break; } case '\n': Line++; // fall thru case '\r': { p.Push(Start, i - Start); i++; Start = i; break; } case '{': { p.Push(Start, i - Start); int Depth = 1; i++; while (*i && Depth > 0) { switch (*i) { case '{': Depth++; break; case '}': Depth--; break; case '\n': Line++; break; } i++; } Start = i; break; } case ';': { p.Push(Start, i - Start); s = i; i = 0; break; } default: { i++; break; } } } char16 *Typedef = p.NewStrW(); if (Typedef) { if (LimitTo == DefnNone || (LimitTo & DefnTypedef) != 0) { Defns.New().Set(DefnTypedef, FileName, Typedef, Line + 1); } DeleteArray(Typedef); } } else if ( ( TokLen == 5 && (IsClass = !StrncmpW(StrClass, Start, 5)) ) || ( TokLen == 6 && (IsStruct = !StrncmpW(StrStruct, Start, 6)) ) ) { DefineStructClass: // Class / Struct if (Depth == 0) { // Check if this is really a class/struct definition or just a reference char16 *next = s; while (*next && !strchr(";(){", *next)) next++; if (*next == '{') { // Full definition InClass = true; CaptureLevel = 1; #if 0 // def DEBUG_FILE if (Debug) LgiTrace("%s:%i - CaptureLevel=%i Depth=%i @ line %i\n", _FL, CaptureLevel, Depth, Line+1); #endif char16 *n = Start + (IsClass ? StrlenW(StrClass) : StrlenW(StrStruct)), *t; List Tok; while (n && *n) { char16 *Last = n; if ((t = LexCpp(n, LexStrdup))) { if (StrcmpW(t, StrSemiColon) == 0) { DeleteArray(t); break; } else if (StrcmpW(t, StrOpenBracket) == 0 || StrcmpW(t, StrColon) == 0) { DeleteArray(CurClassDecl); CurClassDecl = *Tok.rbegin(); Tok.Delete(CurClassDecl); if (LimitTo == DefnNone || (LimitTo & DefnClass) != 0) { char16 r = *Last; *Last = 0; Defns.New().Set(DefnClass, FileName, Start, Line + 1); *Last = r; SeekPtr(s, next, Line); } DeleteArray(t); break; } else { Tok.Insert(t); } } else { LgiTrace("%s:%i - LexCpp failed at %s:%i.\n", _FL, FileName, Line+1); break; } } Tok.DeleteArrays(); } else if (InTypedef) { // Typedef'ing some other structure... // char16 *Start = s; LexCpp(s, LexNoReturn); defnskipws(s); LArray a; char16 *t; ssize_t StartRd = -1, EndRd = -1; while ((t = LexCpp(s, LexStrdup))) { if (StartRd < 0 && !StrcmpW(t, L"(")) StartRd = a.Length(); else if (EndRd < 0 && !StrcmpW(t, L")")) EndRd = a.Length(); if (!StrcmpW(t, StrSemiColon)) break; a.Add(t); } if (a.Length()) { auto iName = StartRd > 0 && EndRd > StartRd ? StartRd - 1 : a.Length() - 1; auto sName = a[iName]; Defns.New().Set(DefnTypedef, FileName, sName, Line + 1); a.DeleteArrays(); } } } } else if ( TokLen == 4 && StrncmpW(StrEnum, Start, 4) == 0 ) { DefineEnum: IsEnum = true; defnskipws(s); LAutoWString t(LexCpp(s, LexStrdup)); if (t && isalpha(*t)) { Defns.New().Set(DefnEnum, FileName, t.Get(), Line + 1); } } else if (IsEnum) { char16 r = *s; *s = 0; Defns.New().Set(DefnEnumValue, FileName, Start, Line + 1); *s = r; defnskipws(s); if (*s == '=') { s++; while (true) { defnskipws(s); defnskipsym(s); defnskipws(s); if (*s == 0 || *s == ',' || *s == '}') break; LAssert(*s != '\n'); s++; } } } if (s[-1] == ':') { LastDecl = s; } } else { s++; } break; } } } DeleteArray(CurClassDecl); if (Debug) { for (unsigned i=0; iType), def->File.Get(), def->Line, def->Name.Get()); } } return Defns.Length() > 0; } diff --git a/Ide/Code/SysCharSupport.cpp b/Ide/Code/SysCharSupport.cpp --- a/Ide/Code/SysCharSupport.cpp +++ b/Ide/Code/SysCharSupport.cpp @@ -1,161 +1,161 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/DisplayString.h" #include "LgiIde.h" class CharItem : public LListItem { LSurface *pDC; public: CharItem(LSurface *pdc) { pDC = pdc; } ~CharItem() { DeleteObj(pDC); } void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LListItem::OnPaintColumn(Ctx, i, c); if (i == 1) { Ctx.pDC->Blt(Ctx.x1, Ctx.y1, pDC); } } void OnMeasure(LPoint *Info) { LListItem::OnMeasure(Info); if (pDC) Info->y = MAX(Info->y, pDC->Y()); } }; class SysCharSupportPriv : public LResourceLoad { public: LList *Match, *NonMatch; SysCharSupportPriv() { Match = NonMatch = 0; } void Search(const char *ch) { if (ch && Match && NonMatch) { Match->Empty(); NonMatch->Empty(); - GUtf8Str Utf((char*)ch); + LUtf8Str Utf((char*)ch); uint32_t c = Utf; char msg[256]; sprintf(msg, "%i 0x%x", c, c); Match->GetWindow()->SetCtrlName(IDC_VALUE, msg); LFontSystem *s = LFontSystem::Inst(); if (s) { LString::Array Fonts; if (s->EnumerateFonts(Fonts)) { for (auto &f : Fonts) { LFont *Fnt = new LFont; if (Fnt) { Fnt->SubGlyphs(false); if (Fnt->Create(f, "16pt")) { LList *m = _HasUnicodeGlyph(Fnt->GetGlyphMap(), c) ? Match : NonMatch; LMemDC *pDC = new LMemDC; if (pDC) { char16 Str[] = { (char16)c, 0 }; LDisplayString d(Fnt, Str); if (pDC->Create(d.X(), d.Y(), System32BitColourSpace)) { pDC->Colour(L_WORKSPACE); pDC->Colour(L_TEXT); d.Draw(pDC, 0, 0); } } LListItem *i; m->Insert(i = new CharItem(pDC)); if (i) { i->SetText(f); } } DeleteObj(Fnt); } } } } } } }; SysCharSupport::SysCharSupport(AppWnd *app) { d = new SysCharSupportPriv; Name("System Character Support"); if (Attach(0)) { d->LoadFromResource(IDD_CHAR, this, &GetPos()); MoveToCenter(); AttachChildren(); GetViewById(IDC_MATCH, d->Match); GetViewById(IDC_NOT_MATCH, d->NonMatch); OnPosChange(); Visible(true); } } SysCharSupport::~SysCharSupport() { DeleteObj(d); } void SysCharSupport::OnPosChange() { /* if (d->Match) { LRect c = GetClient(); LRect r = d->Lst->GetPos(); r.x2 = c.x2 - r.x1; r.y2 = c.y2 - r.x1; d->Lst->SetPos(r); } */ } int SysCharSupport::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDOK: { d->Search(GetCtrlName(IDC_CHAR)); break; } } return 0; } diff --git a/Lgi.xml b/Lgi.xml --- a/Lgi.xml +++ b/Lgi.xml @@ -1,559 +1,559 @@ - + - - + + - - - - - - + + + + + + Makefile.linux Makefile.win64 Makefile.macosx gcc 0 ./include ./private/common ./include ./private/common ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux include/lgi/win include/lgi/win /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` lgi-gtk3 lgi-gtk3 DynamicLibrary diff --git a/Lvc/CMakeLists.txt b/Lvc/CMakeLists.txt --- a/Lvc/CMakeLists.txt +++ b/Lvc/CMakeLists.txt @@ -1,69 +1,65 @@ set(APP_RESOURCES Resources/Lvc.lr8 Resources/image-list.png) set(APP_MANIFEST) if (WIN32) set(APP_TYPE WIN32) set(APP_ICON resources/Lvc.rc) set(APP_MANIFEST ../src/win/General/Lgi.manifest) elseif (APPLE) set(APP_TYPE MACOSX_BUNDLE) set(APP_ICON Resources/mac-icon.icns) set_source_files_properties( ${APP_ICON} ${APP_RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") elseif (LINUX) set(APP_ICON Resources/icon128.png) endif() if (NOT TARGET ssh_shared) get_filename_component(LIBSSH_DIR "${CODELIB}/libssh" ABSOLUTE) if (EXISTS ${LIBSSH_DIR}) add_subdirectory(${LIBSSH_DIR} libssh) else() find_path(LIBSSH_INCLUDE_DIR libssh.h PATH /usr/include/libssh) if (NOT LIBSSH_INCLUDE_DIR) message(FATAL_ERROR "libssh not found.") endif() add_library(ssh_shared INTERFACE) set_target_properties(ssh_shared PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBSSH_INCLUDE_DIR} INTERFACE_LINK_LIBRARIES "ssh") endif() endif() -if (NOT TARGET libpng15) - get_filename_component(LIBPNG_DIR "${CODELIB}/libpng" ABSOLUTE) - add_subdirectory(${LIBPNG_DIR} libpng) -endif() add_executable(Lvc ${APP_TYPE} ../src/common/Gdc2/Filters/Png.cpp ../src/common/Lgi/LgiMain.cpp ../src/common/Text/DeEscape.cpp ../src/common/Text/XmlTreeUi.cpp ../src/common/Widgets/ControlTree.cpp Src/BlameUi.cpp Src/DropDownBtn.cpp Src/Main.cpp Src/SshConnection.cpp Src/VcCommit.cpp Src/VcFile.cpp Src/VcFolder.cpp ${APP_ICON} ${APP_RESOURCES} ${APP_MANIFEST} ) target_link_libraries(Lvc lgi libpng15 ssh_shared) if (APPLE) macBundlePostBuild(Lvc) else() file(COPY ${CMAKE_CURRENT_LIST_DIR}/Resources DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) endif() set_target_properties(Lvc PROPERTIES EXCLUDE_FROM_ALL TRUE) \ No newline at end of file diff --git a/Lvc/Resources/Lvc.lr8 b/Lvc/Resources/Lvc.lr8 --- a/Lvc/Resources/Lvc.lr8 +++ b/Lvc/Resources/Lvc.lr8 @@ -1,332 +1,322 @@ - - + diff --git a/Lvc/Resources/resdefs.h b/Lvc/Resources/resdefs.h --- a/Lvc/Resources/resdefs.h +++ b/Lvc/Resources/resdefs.h @@ -1,66 +1,67 @@ +// Generated by LgiRes + // This file generated by LgiRes #define IDC_COMMIT_AND_PUSH 3 #define IDC_BRANCH 11 #define IDC_BRANCH_DROPDOWN 14 #define IDC_MSG 16 #define IDD_OPTIONS 22 #define IDC_OPTIONS_TABLE 23 #define IDC_SVN 25 #define IDC_SVN_BROWSE 26 #define IDC_GIT 28 #define IDC_GIT_BROWSE 29 #define IDC_HG 32 #define IDC_CVS 33 #define IDC_HG_BROWSE 36 #define IDC_CVS_BROWSE 37 #define IDC_UNTRACKED 38 #define IDC_PULL_ALL 39 #define IDC_STATUS 40 #define IDC_UPDATE 41 #define IDC_HEADS 45 #define IDC_BUILD_FIX 47 #define IDD_REMOTE_FOLDER 61 #define IDC_62 62 #define IDC_HOSTS 64 #define IDC_65 65 #define IDC_HOSTNAME 67 #define IDC_USER 68 #define IDC_PASS 69 #define IDC_DELETE 70 #define IDC_REMOTE_PATH 74 #define IDC_SVN_LIMIT 79 #define IDC_GIT_LIMIT 80 #define IDC_HG_LIMIT 81 #define IDC_CVS_LIMIT 82 #define IDC_TABLE 500 #define IDC_COMMIT 501 #define IDC_PULL 502 #define IDC_PUSH 503 #define IDC_COMMIT_TABLE 504 -#define IDC_FILTER 505 #define IDC_CLEAR_FILTER 506 #define IDD_COMMIT 507 #define IDD_TOOLBAR 508 #define IDC_OPEN 509 #define IDM_FILE 510 #define IDM_OPTIONS 511 #define IDM_HELP 512 #define IDM_ABOUT 513 #define IDM_VIEW_MENU 514 #define IDM_EDIT_MENU 515 #define IDM_FIND 516 #define IDM_REFRESH 517 #define IDM_UNTRACKED 518 #define IDM_MENU_519 519 #define IDM_EXIT 520 #define IDM_REPO 521 #define IDM_PULL 522 #define IDM_PUSH 523 #define IDM_STATUS 524 #define IDM_MENU_525 525 #define IDM_UPDATE_SUBS 526 #define IDM_OPEN_LOCAL 527 #define IDM_OPEN_REMOTE 528 #define IDM_MENU_529 529 #define IDM_OPEN_DIFF 530 diff --git a/Lvc/Src/Lvc.h b/Lvc/Src/Lvc.h --- a/Lvc/Src/Lvc.h +++ b/Lvc/Src/Lvc.h @@ -1,222 +1,228 @@ #ifndef _Lvc_h_ #define _Lvc_h_ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Box.h" #include "lgi/common/Tree.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/TextView3.h" #include "lgi/common/TextLog.h" #include "lgi/common/TabView.h" #include "lgi/common/Edit.h" #include "lgi/common/Menu.h" #include "lgi/common/Ssh.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/StructuredLog.h" #define OPT_Folders "Folders" #define OPT_Folder "Folder" #define METHOD_GetContext "GetContext" #define APP_VERSION "0.8" extern const char *AppName; enum LvcIcon { IcoFolder, IcoCleanFolder, IcoDirtyFolder, IcoFile, IcoCleanFile, IcoDirtyFile, }; enum LvcNotify { LvcBase = GNotifyUserApp, LvcCommandStart, LvcCommandEnd }; enum FileColumns { COL_CHECKBOX, COL_STATE, COL_FILENAME, }; enum AppIds { - IDC_LIST = 100, - IDC_TOOLS_BOX, + IDC_TOOLS_BOX = 100, IDC_FOLDERS_BOX, IDC_COMMITS_BOX, - IDC_TREE, - IDC_FILES, IDC_FILES_BOX, IDC_TXT, IDC_LOG, IDC_MSG_BOX, IDC_TAB_VIEW, + IDC_FOLDER_TBL, // Contains the following controls: + IDC_TREE, + IDC_FILTER_FOLDERS, + IDC_CLEAR_FILTER_FOLDERS, + IDC_COMMITS_TBL, // Contains the following controls: + IDC_LIST, + IDC_FILTER_COMMITS, + IDC_CLEAR_FILTER_COMMITS, + IDC_FILES_TBL, // Contains the following controls: + IDC_FILES, + IDC_FILTER_FILES, + IDC_CLEAR_FILTER_FILES, IDM_ADD_LOCAL = 200, IDM_ADD_REMOTE, IDM_UPDATE, IDM_COPY_REV, IDM_COPY_INDEX, IDM_RENAME_BRANCH, IDM_REMOVE, IDM_CLEAN, IDM_REVERT, IDM_REVERT_TO_REV, IDM_BLAME, IDM_SAVE_AS, IDM_BROWSE, IDM_LOG, IDM_EOL_LF, IDM_EOL_CRLF, IDM_EOL_AUTO, IDM_ADD_FILE, IDM_ADD_BINARY_FILE, IDM_BROWSE_FOLDER, IDM_TERMINAL, IDM_RESOLVE, IDM_MERGE, IDM_LOG_FILE, IDM_COPY_PATH, IDM_COPY_LEAF, IDM_EDIT, }; enum AppMessages { M_DETECT_VCS = M_USER + 100, M_RUN_CMD, M_RESPONSE, M_HANDLE_CALLBACK, }; enum VersionCtrl { VcNone, VcCvs, VcSvn, VcGit, VcHg, VcPending, VcError, VcMax, }; class VcFolder; struct ParseParams { LString Str; LString AltInitPath; class VcLeaf *Leaf = NULL; bool IsWorking = false;; bool Debug = false; ParseParams(const char *str = NULL) { Str = str; } }; typedef bool (VcFolder::*ParseFn)(int, LString, ParseParams*); class SshConnection; struct AppPriv { VcFolder *CurFolder = NULL; LTree *Tree = NULL; LList *Commits = NULL; LList *Files = NULL; LEdit *Msg = NULL; LTextLog *Diff = NULL; LTextLog *Log = NULL; LTabView *Tabs = NULL; VersionCtrl PrevType = VcNone; LOptionsFile Opts; LStructuredLog sLog; int Resort = -1; - LArray Filtered; + + // Filtering + LString FolderFilter, CommitFilter, FileFilter; LHashTbl,SshConnection*> Connections; AppPriv() : Opts(LOptionsFile::DesktopMode, AppName), sLog("Lvc.slog") { } - ~AppPriv() - { - Filtered.DeleteObjects(); - } - SshConnection *GetConnection(const char *Uri, bool Create = true); auto Wnd() { return Commits ? Commits->GetWindow() : LAppInst->AppWnd; } void ClearFiles() { if (Files) Files->Empty(); if (Diff) Diff->Name(NULL); } LArray GetRevs(LString::Array &Revs); LString::Array GetCommitRange(); bool IsMenuChecked(int Item) { LMenu *m = Tree->GetWindow()->GetMenu(); LMenuItem *i = m ? m->FindItem(Item) : NULL; return i ? i->Checked() : false; } VersionCtrl DetectVcs(VcFolder *Fld); class VcFile *FindFile(const char *Path); }; #include "SshConnection.h" class BlameUi : public LWindow { struct BlameUiPriv *d; public: BlameUi(AppPriv *priv, VersionCtrl Vc, LString Output); ~BlameUi(); }; class DropDownBtn : public LDropDown, public ResObject { struct DropDownBtnPriv *d; class DropLst *Pu; public: DropDownBtn(); ~DropDownBtn(); LString::Array GetList(); bool SetList(int EditCtrl, LString::Array a); bool OnLayout(LViewLayoutInfo &Inf); }; extern bool ConvertEol(const char *Path, bool Cr); extern int GetEol(const char *Path); extern LString::Array GetProgramsInPath(const char *Program); extern LColour GetPaletteColour(int i); #include "VcFile.h" #include "VcCommit.h" #include "VcFolder.h" #endif diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp --- a/Lvc/Src/Main.cpp +++ b/Lvc/Src/Main.cpp @@ -1,1544 +1,1622 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLog.h" #include "lgi/common/Button.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Tree.h" #include "lgi/common/FileSelect.h" #include "lgi/common/StructuredLog.h" #include "Lvc.h" #include "../Resources/resdefs.h" #ifdef WINDOWS #include "../Resources/resource.h" #endif ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; #define DEFAULT_BUILD_FIX_MSG "Build fix." #define OPT_Hosts "Hosts" #define OPT_Host "Host" SshConnection *AppPriv::GetConnection(const char *Uri, bool Create) { LUri u(Uri); u.sPath.Empty(); auto s = u.ToString(); auto Conn = Connections.Find(s); if (!Conn && Create) Connections.Add(s, Conn = new SshConnection(Log, s, "matthew@*$ ")); return Conn; } VersionCtrl AppPriv::DetectVcs(VcFolder *Fld) { char p[MAX_PATH_LEN]; LUri u = Fld->GetUri(); if (!u.IsFile() || !u.sPath) { auto c = GetConnection(u.ToString()); if (!c) return VcNone; auto type = c->Types.Find(u.sPath); if (type) return type; c->DetectVcs(Fld); Fld->GetCss(true)->Color(LColour::Blue); Fld->Update(); return VcPending; } auto Path = u.sPath.Get(); #ifdef WINDOWS if (*Path == '/') Path++; #endif if (LMakePath(p, sizeof(p), Path, ".git") && LDirExists(p)) return VcGit; if (LMakePath(p, sizeof(p), Path, ".svn") && LDirExists(p)) return VcSvn; if (LMakePath(p, sizeof(p), Path, ".hg") && LDirExists(p)) return VcHg; if (LMakePath(p, sizeof(p), Path, "CVS") && LDirExists(p)) return VcCvs; return VcNone; } class DiffView : public LTextLog { public: DiffView(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto ln : LTextView3::Line) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; if (*t == '+') { ln->c = LColour::Green; ln->Back.Rgb(245, 255, 245); } else if (*t == '-') { ln->c = LColour::Red; ln->Back.Rgb(255, 245, 245); } else if (*t == '@') { ln->c.Rgb(128, 128, 128); ln->Back.Rgb(235, 235, 235); } else ln->c = LColour(L_TEXT); } } } }; class ToolBar : public LLayout, public LResourceLoad { public: ToolBar() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name)) { OnPosChange(); } else LAssert(!"Missing toolbar resource"); } void OnCreate() { AttachChildren(); } void OnPosChange() { LRect Cli = GetClient(); LTableLayout *v; if (GetViewById(IDC_TABLE, v)) { v->SetPos(Cli); v->OnPosChange(); auto r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); // printf("Used = %s\n", r.GetStr()); LCss::Len NewSz(LCss::LenPx, (float)r.Y()+3); auto OldSz = GetCss(true)->Height(); if (OldSz != NewSz) { GetCss(true)->Height(NewSz); SendNotify(LNotifyTableLayoutRefresh); } } else LAssert(!"Missing table ctrl"); } void OnPaint(LSurface *pDC) { pDC->Colour(LColour(L_MED)); pDC->Rectangle(); } }; class CommitCtrls : public LLayout, public LResourceLoad { public: CommitCtrls() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); LRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)r.Y())); } else LAssert(!"Missing table ctrl"); } else LAssert(!"Missing toolbar resource"); } void OnPosChange() { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; LString::Array GetProgramsInPath(const char *Program) { LString::Array Bin; LString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif LString::Array a = LGetPath(); for (auto p : a) { LFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public LDialog, public LXmlTreeUi { LOptionsFile &Opts; public: OptionsDlg(LViewI *Parent, LOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("svn-limit", IDC_SVN_LIMIT); Map("git-path", IDC_GIT, GV_STRING); Map("git-limit", IDC_GIT_LIMIT); Map("hg-path", IDC_HG, GV_STRING); Map("hg-limit", IDC_HG_LIMIT); Map("cvs-path", IDC_CVS, GV_STRING); Map("cvs-limit", IDC_CVS_LIMIT); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { auto s = new LFileSelect; s->Parent(this); s->Open([&](auto dlg, auto status) { if (status) SetCtrlName(EditId, s->Name()); delete dlg; }); } void BrowseFiles(LViewI *Ctrl, const char *Bin, int EditId) { LRect Pos = Ctrl->GetPos(); LPoint Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); LSubMenu s; LString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { LString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return LDialog::OnNotify(Ctrl, n); } }; int CommitDataCmp(VcCommit **_a, VcCommit **_b) { auto a = *_a; auto b = *_b; return a->GetTs().Compare(&b->GetTs()); } LString::Array AppPriv::GetCommitRange() { LString::Array r; if (Commits) { LArray Sel; Commits->GetSelection(Sel); if (Sel.Length() > 1) { Sel.Sort(CommitDataCmp); r.Add(Sel[0]->GetRev()); r.Add(Sel.Last()->GetRev()); } else { r.Add(Sel[0]->GetRev()); } } else LAssert(!"No commit list ptr"); return r; } LArray AppPriv::GetRevs(LString::Array &Revs) { LArray a; for (auto i = Commits->begin(); i != Commits->end(); i++) { VcCommit *c = dynamic_cast(*i); if (c) { for (auto r: Revs) { if (r.Equals(c->GetRev())) { a.Add(c); break; } } } } return a; } class CommitList : public LList { public: CommitList(int id) : LList(id, 0, 0, 200, 200) { } void SelectRevisions(LString::Array &Revs, const char *BranchHint = NULL) { VcCommit *Scroll = NULL; LArray Matches; for (auto i: *this) { VcCommit *item = dynamic_cast(i); if (!item) continue; bool IsMatch = false; for (auto r: Revs) { if (item->IsRev(r)) { IsMatch = true; break; } } if (IsMatch) Matches.Add(item); else if (i->Select()) i->Select(false); } for (auto item: Matches) { auto b = item->GetBranch(); if (BranchHint) { if (!b || Stricmp(b, BranchHint)) continue; } else if (b) { continue; } if (!Scroll) Scroll = item; item->Select(true); } if (!Scroll && Matches.Length() > 0) { Scroll = Matches[0]; Scroll->Select(true); } if (Scroll) Scroll->ScrollTo(); } bool OnKey(LKey &k) { switch (k.c16) { case 'p': case 'P': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { auto first = Sel[0]; auto branch = first->GetBranch(); auto p = first->GetParents(); if (p->Length() == 0) break; for (auto c:Sel) c->Select(false); SelectRevisions(*p, branch); } } return true; } case 'c': case 'C': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { LHashTbl,VcCommit*> Map; for (auto s:Sel) Map.Add(s->GetRev(), s); LString::Array n; for (auto it = begin(); it != end(); it++) { VcCommit *c = dynamic_cast(*it); if (c) { for (auto r:*c->GetParents()) { if (Map.Find(r)) { n.Add(c->GetRev()); break; } } } } for (auto c:Sel) c->Select(false); SelectRevisions(n, Sel[0]->GetBranch()); } } return true; } } return LList::OnKey(k); } }; int LstCmp(LListItem *a, LListItem *b, int Col) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if (A == NULL || B == NULL) { return (A ? 1 : -1) - (B ? 1 : -1); } auto f = A->GetFolder(); auto flds = f->GetFields(); if (!flds.Length()) { LgiTrace("%s:%i - No fields?\n", _FL); return 0; } auto fld = flds[Col]; switch (fld) { case LGraph: case LIndex: case LParents: case LRevision: default: return (int) (B->GetIndex() - A->GetIndex()); case LBranch: case LAuthor: case LMessageTxt: return Stricmp(A->GetFieldText(fld), B->GetFieldText(fld)); case LTimeStamp: return B->GetTs().Compare(&A->GetTs()); } return 0; } struct TestThread : public LThread { public: TestThread() : LThread("test") { Run(); } int Main() { auto Path = LGetPath(); LSubProcess p("python", "/Users/matthew/CodeLib/test.py"); auto t = LString(LGI_PATH_SEPARATOR).Join(Path); for (auto s: Path) printf("s: %s\n", s.Get()); p.SetEnvironment("PATH", t); if (p.Start()) { LStringPipe s; p.Communicate(&s); printf("Test: %s\n", s.NewGStr().Get()); } return 0; } }; class RemoteFolderDlg : public LDialog { class App *app; LTree *tree; struct SshHost *root, *newhost; LXmlTreeUi Ui; public: LString Uri; RemoteFolderDlg(App *application); ~RemoteFolderDlg(); int OnNotify(LViewI *Ctrl, LNotification n); }; class VcDiffFile : public LTreeItem { AppPriv *d; LString File; public: VcDiffFile(AppPriv *priv, LString file) : d(priv), File(file) { } const char *GetText(int i = 0) override { return i ? NULL : File; } void Select(bool s) override { LTreeItem::Select(s); if (s) { d->Files->Empty(); d->Diff->Name(NULL); LFile in(File, O_READ); LString s = in.Read(); if (!s) return; LString::Array a = s.Replace("\r").Split("\n"); LString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; LString Fn = a[i].Split(":", 1).Last().Strip(); f = d->FindFile(Fn); if (!f) f = new VcFile(d, NULL, NULL, false); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } } } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; LBox *FoldersBox; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *ReturnValue = (AppPriv*)this; return true; } return false; } public: App() { FoldersBox = NULL; LString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); LRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { if ((Menu = new LMenu)) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } LBox *ToolsBox = new LBox(IDC_TOOLS_BOX, true, "ToolsBox"); FoldersBox = new LBox(IDC_FOLDERS_BOX, false, "FoldersBox"); LBox *CommitsBox = new LBox(IDC_COMMITS_BOX, true, "CommitsBox"); ToolBar *Tools = new ToolBar; ToolsBox->Attach(this); Tools->Attach(ToolsBox); FoldersBox->Attach(ToolsBox); - Tree = new LTree(IDC_TREE, 0, 0, 320, 200); - // Tree->GetCss(true)->Width(LCss::Len("320px")); - Tree->ShowColumnHeader(true); - Tree->AddColumn("Folder", 250); - Tree->AddColumn("Counts", 50); - Tree->Attach(FoldersBox); - Tree->SetImageList(ImgLst, false); + auto FolderLayout = new LTableLayout(IDC_FOLDER_TBL); + auto c = FolderLayout->GetCell(0, 0, true, 2); + Tree = new LTree(IDC_TREE, 0, 0, 320, 200); + Tree->SetImageList(ImgLst, false); + Tree->ShowColumnHeader(true); + Tree->AddColumn("Folder", 250); + Tree->AddColumn("Counts", 50); + c->Add(Tree); + c = FolderLayout->GetCell(0, 1); + c->Add(new LEdit(IDC_FILTER_FOLDERS, 0, 0, -1, -1)); + c = FolderLayout->GetCell(1, 1); + c->Add(new LButton(IDC_CLEAR_FILTER_FOLDERS, 0, 0, -1, -1, "x")); + FolderLayout->Attach(FoldersBox); CommitsBox->Attach(FoldersBox); - Commits = new CommitList(IDC_LIST); - Commits->Attach(CommitsBox); - Commits->GetCss(true)->Height("40%"); + auto CommitsLayout = new LTableLayout(IDC_COMMITS_TBL); + c = CommitsLayout->GetCell(0, 0, true, 2); + Commits = new CommitList(IDC_LIST); + c->Add(Commits); + c = CommitsLayout->GetCell(0, 1); + c->Add(new LEdit(IDC_FILTER_COMMITS, 0, 0, -1, -1)); + c = CommitsLayout->GetCell(1, 1); + c->Add(new LButton(IDC_CLEAR_FILTER_COMMITS, 0, 0, -1, -1, "x")); + CommitsLayout->Attach(CommitsBox); + CommitsLayout->GetCss(true)->Height("40%"); LBox *FilesBox = new LBox(IDC_FILES_BOX, false); FilesBox->Attach(CommitsBox); - Files = new LList(IDC_FILES, 0, 0, 200, 200); - Files->GetCss(true)->Width("35%"); - Files->Attach(FilesBox); - Files->AddColumn("[ ]", 30); - Files->AddColumn("State", 100); - Files->AddColumn("Name", 400); + auto FilesLayout = new LTableLayout(IDC_FILES_TBL); + c = FilesLayout->GetCell(0, 0, true, 2); + Files = new LList(IDC_FILES, 0, 0, 200, 200); + Files->AddColumn("[ ]", 30); + Files->AddColumn("State", 100); + Files->AddColumn("Name", 400); + c->Add(Files); + c = FilesLayout->GetCell(0, 1); + c->Add(new LEdit(IDC_FILTER_FILES, 0, 0, -1, -1)); + c = FilesLayout->GetCell(1, 1); + c->Add(new LButton(IDC_CLEAR_FILTER_FILES, 0, 0, -1, -1, "x")); + FilesLayout->GetCss(true)->Width("35%"); + FilesLayout->Attach(FilesBox); LBox *MsgBox = new LBox(IDC_MSG_BOX, true); MsgBox->Attach(FilesBox); CommitCtrls *Commit = new CommitCtrls; Commit->Attach(MsgBox); Commit->GetCss(true)->Height("25%"); if (Commit->GetViewById(IDC_MSG, Msg)) { LTextView3 *Tv = dynamic_cast(Msg); if (Tv) { Tv->Sunken(true); Tv->SetWrapType(TEXTED_WRAP_NONE); } } else LAssert(!"No ctrl?"); Tabs = new LTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); LTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); Diff->SetWrapType(TEXTED_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new LTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(TEXTED_WRAP_NONE); SetCtrlValue(IDC_UPDATE, true); AttachChildren(); Visible(true); } LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { bool Req[VcMax] = {0}; for (auto c: f->Children) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); if (!Req[f->GetType()]) { Req[f->GetType()] = true; f->GetVersion(); } } } Opts.Unlock(); LRect Large(0, 0, 2000, 200); Tree->SetPos(Large); Tree->ResizeColumnsToContent(); LItemColumn *c; int i = 0, px = 0; while ((c = Tree->ColumnAt(i++))) { px += c->Width(); } FoldersBox->Value(MAX(320, px + 20)); // new TestThread(); } SetPulse(200); DropTarget(true); } ~App() { SerializeState(&Opts, "WndPos", false); LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (f) { f->EmptyChildren(); for (auto i:*Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) f->InsertTag(vcf->Save()); } Opts.Unlock(); } Opts.SerializeFile(true); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESPONSE: { SshConnection::HandleMsg(Msg); break; } case M_HANDLE_CALLBACK: { LAutoPtr Pc((ProcessCallback*)Msg->A()); if (Pc) Pc->OnComplete(); break; } } return LWindow::OnEvent(Msg); } void OnReceiveFiles(LArray &Files) { for (auto f : Files) { if (LDirExists(f)) OpenLocalFolder(f); } } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_OPEN_LOCAL: { OpenLocalFolder(); break; } case IDM_OPEN_REMOTE: { OpenRemoteFolder(); break; } case IDM_OPEN_DIFF: { auto s = new LFileSelect; s->Parent(this); s->Open([&](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } case IDM_OPTIONS: { auto Dlg = new OptionsDlg(this, Opts); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_FIND: { auto i = new LInput(this, "", "Search string:"); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId == IDOK) { LString::Array Revs; Revs.Add(i->GetStr()); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } delete dlg; }); break; } case IDM_UNTRACKED: { auto mi = GetMenu()->FindItem(IDM_UNTRACKED); if (!mi) break; mi->Checked(!mi->Checked()); LArray Flds; Tree->GetSelection(Flds); for (auto f : Flds) { f->Refresh(); } break; } case IDM_REFRESH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Refresh(); break; } case IDM_PULL: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Pull(); break; } case IDM_PUSH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Push(); break; } case IDM_STATUS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->FolderStatus(); break; } case IDM_UPDATE_SUBS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->UpdateSubs(); break; break; } case IDM_EXIT: { LCloseApp(); break; } } return 0; } void OnPulse() { for (auto i:*Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) vcf->OnPulse(); } } void OpenLocalFolder(const char *Fld = NULL) { auto Load = [&](const char *Fld) { // Check the folder isn't already loaded... bool Has = false; LArray Folders; Tree->GetAll(Folders); for (auto f: Folders) { if (f->GetUri().IsFile() && !Stricmp(f->LocalPath(), Fld)) { Has = true; break; } } if (!Has) { LUri u; u.SetFile(Fld); Tree->Insert(new VcFolder(this, u.ToString())); } }; if (!Fld) { auto s = new LFileSelect; s->Parent(this); s->OpenFolder([&](auto dlg, auto status) { if (status) Load(dlg->Name()); delete dlg; }); } else Load(Fld); } void OpenRemoteFolder() { auto Dlg = new RemoteFolderDlg(this); Dlg->DoModal([this, Dlg](auto dlg, auto status) { if (status) Tree->Insert(new VcFolder(this, Dlg->Uri)); delete dlg; }); } void OpenDiff(const char *File) { Tree->Insert(new VcDiffFile(this, File)); } + void OnFilterFolders() + { + if (!Tree) + return; + + for (auto i = Tree->GetChild(); i; i = i->GetNext()) + { + auto n = i->GetText(); + bool vis = !FolderFilter || Stristr(n, FolderFilter.Get()) != NULL; + i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); + } + + Tree->UpdateAllItems(); + Tree->Invalidate(); + } + + void OnFilterCommits() + { + } + + void OnFilterFiles() + { + VcFolder *f = dynamic_cast(Tree->Selection()); + if (f) + f->FilterCurrentFiles(); + } + int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { + case IDC_CLEAR_FILTER_FOLDERS: + { + SetCtrlName(IDC_FILTER_FOLDERS, NULL); + break; + } + case IDC_FILTER_FOLDERS: + { + LString n = c->Name(); + if (n != FolderFilter) + { + FolderFilter = n; + OnFilterFolders(); + } + break; + } + case IDC_CLEAR_FILTER_COMMITS: + { + SetCtrlName(IDC_FILTER_COMMITS, NULL); + break; + } + case IDC_FILTER_COMMITS: + { + LString n = c->Name(); + if (n != CommitFilter) + { + CommitFilter = n; + OnFilterCommits(); + } + break; + } + case IDC_CLEAR_FILTER_FILES: + { + SetCtrlName(IDC_FILTER_FILES, NULL); + break; + } + case IDC_FILTER_FILES: + { + LString n = c->Name(); + if (n != FileFilter) + { + FileFilter = n; + OnFilterFiles(); + } + break; + } case IDC_FILES: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { - // Select / deselect all checkboxes.. + // Select / deselect all check boxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (auto f: n) Checked |= f->Checked() > 0; for (auto f: n) f->Checked(Checked ? 0 : 1); } } } break; } default: break; } break; } case IDC_OPEN: { OpenLocalFolder(); break; } case IDC_TREE: { switch (n.Type) { case LNotifyContainerClick: { LMouse m; c->GetMouse(m); if (m.Right()) { LSubMenu s; s.AppendItem("Add Local", IDM_ADD_LOCAL); s.AppendItem("Add Remote", IDM_ADD_REMOTE); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD_LOCAL: { OpenLocalFolder(); break; } case IDM_ADD_REMOTE: { OpenRemoteFolder(); break; } } } break; } case LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } default: break; } break; } - case IDC_CLEAR_FILTER: - { - SetCtrlName(IDC_FILTER, NULL); - // Fall through - } - case IDC_FILTER: - { - auto f = dynamic_cast(Tree->Selection()); - if (!f) - break; - f->Select(true); - f->FilterCurrentFiles(); - break; - } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { auto BuildFix = GetCtrlValue(IDC_BUILD_FIX); const char *Msg = GetCtrlName(IDC_MSG); if (BuildFix || ValidStr(Msg)) { auto Sel = Tree->Selection(); if (Sel) { VcFolder *f = dynamic_cast(Sel); if (!f) { for (auto p = Sel->GetParent(); p; p = p->GetParent()) { f = dynamic_cast(p); if (f) break; } } if (f) { auto Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(BuildFix ? DEFAULT_BUILD_FIX_MSG : Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { LArray Folders; Tree->GetAll(Folders); bool AndUpdate = GetCtrlValue(IDC_UPDATE) != 0; for (auto f : Folders) { f->Pull(AndUpdate, LogSilo); } break; } case IDC_STATUS: { LArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } case IDC_HEADS: { if (n.Type == LNotifyValueChanged) { auto Revs = LString(c->Name()).SplitDelimit(); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } break; } case IDC_LIST: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse Ms; Commits->GetColumnClickInfo(Col, Ms); Commits->Sort(LstCmp, Col); break; } case LNotifyItemDoubleClick: { VcFolder *f = dynamic_cast(Tree->Selection()); if (!f) break; LArray s; if (Commits->GetSelection(s) && s.Length() == 1) f->OnUpdate(s[0]->GetRev()); break; } default: break; } break; } } return 0; } }; struct SshHost : public LTreeItem { LXmlTag *t; LString Host, User, Pass; SshHost(LXmlTag *tag = NULL) { t = tag; if (t) { Serialize(false); SetText(Host); } } void Serialize(bool WriteToTag) { if (WriteToTag) { LUri u; u.sProtocol = "ssh"; u.sHost = Host; u.sUser = User; u.sPass = Pass; t->SetContent(u.ToString()); } else { LUri u(t->GetContent()); if (!Stricmp(u.sProtocol.Get(), "ssh")) { Host = u.sHost; User = u.sUser; Pass = u.sPass; } } } }; RemoteFolderDlg::RemoteFolderDlg(App *application) : app(application), root(NULL), newhost(NULL), tree(NULL) { SetParent(app); LoadFromResource(IDD_REMOTE_FOLDER); if (GetViewById(IDC_HOSTS, tree)) { printf("tree=%p\n", tree); tree->Insert(root = new SshHost()); root->SetText("Ssh Hosts"); } else return; LViewI *v; if (GetViewById(IDC_HOSTNAME, v)) v->Focus(true); Ui.Map("Host", IDC_HOSTNAME); Ui.Map("User", IDC_USER); Ui.Map("Password", IDC_PASS); LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (hosts) { SshHost *h; for (auto c: hosts->Children) if (c->IsTag(OPT_Host) && (h = new SshHost(c))) root->Insert(h); app->Opts.Unlock(); } root->Insert(newhost = new SshHost()); newhost->SetText("New Host"); root->Expanded(true); newhost->Select(true); } RemoteFolderDlg::~RemoteFolderDlg() { } int RemoteFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { SshHost *cur = tree ? dynamic_cast(tree->Selection()) : NULL; #define CHECK_SPECIAL() \ if (cur == newhost) \ { \ root->Insert(cur = new SshHost()); \ cur->Select(true); \ } \ if (cur == root) \ break; switch (Ctrl->GetId()) { case IDC_HOSTS: { switch (n.Type) { case LNotifyItemSelect: { bool isRoot = cur == root; SetCtrlEnabled(IDC_HOSTNAME, !isRoot); SetCtrlEnabled(IDC_USER, !isRoot); SetCtrlEnabled(IDC_PASS, !isRoot); SetCtrlEnabled(IDC_DELETE, !isRoot && !(cur == newhost)); SetCtrlName(IDC_HOSTNAME, cur ? cur->Host : NULL); SetCtrlName(IDC_USER, cur ? cur->User : NULL); SetCtrlName(IDC_PASS, cur ? cur->Pass : NULL); break; } default: break; } break; } case IDC_HOSTNAME: { CHECK_SPECIAL() if (cur) { cur->Host = Ctrl->Name(); cur->SetText(cur->Host ? cur->Host : ""); } break; } case IDC_DELETE: { auto sel = tree ? dynamic_cast(tree->Selection()) : NULL; if (!sel) break; LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (!hosts) { LAssert(!"Couldn't lock tag."); break; } if (hosts->Children.HasItem(sel->t)) { sel->t->RemoveTag(); DeleteObj(sel->t); delete sel; } app->Opts.Unlock(); break; } case IDC_USER: { CHECK_SPECIAL() if (cur) cur->User = Ctrl->Name(); break; } case IDC_PASS: { CHECK_SPECIAL() if (cur) cur->Pass = Ctrl->Name(); break; } case IDOK: { LXmlTag *hosts; if (!(hosts = app->Opts.LockTag(OPT_Hosts, _FL))) { if (!(app->Opts.CreateTag(OPT_Hosts) && (hosts = app->Opts.LockTag(OPT_Hosts, _FL)))) break; } LAssert(hosts != NULL); for (auto i = root->GetChild(); i; i = i->GetNext()) { SshHost *h = dynamic_cast(i); if (!h || h == newhost) continue; if (h->t) ; else if ((h->t = new LXmlTag(OPT_Host))) hosts->InsertTag(cur->t); else return false; h->Serialize(true); } app->Opts.Unlock(); LUri u; u.sProtocol = "ssh"; u.sHost = GetCtrlName(IDC_HOSTNAME); u.sUser = GetCtrlName(IDC_USER); u.sPass = GetCtrlName(IDC_PASS); u.sPath = GetCtrlName(IDC_REMOTE_PATH); Uri = u.ToString(); // Fall through } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { LStructuredLog::UnitTest(); a.AppWnd = new App; a.Run(); } LAssert(VcCommit::Instances == 0); return 0; } diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,4435 +1,4413 @@ #include "Lvc.h" #include "../Resources/resdefs.h" #include "lgi/common/Combo.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Json.h" #include "lgi/common/ProgressDlg.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif #define MAX_AUTO_RESIZE_ITEMS 2000 #define PROFILE_FN 0 #if PROFILE_FN #define PROF(s) Prof.Add(s) #else #define PROF(s) #endif class TmpFile : public LFile { int Status; LString Hint; public: TmpFile(const char *hint = NULL) { Status = 0; if (hint) Hint = hint; else Hint = "_lvc"; } LFile &Create() { LFile::Path p(LSP_TEMP); p += Hint; do { char s[256]; sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand()); p += s; } while (p.Exists()); Status = LFile::Open(p.GetFull(), O_READWRITE); return *this; } }; bool TerminalAt(LString Path) { #if defined(MAC) return LExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path); #elif defined(WINDOWS) TCHAR w[MAX_PATH_LEN]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { LFile::Path p = LString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); return LExecute(p); } #elif defined(LINUX) LExecute("gnome-terminal", NULL, Path); #endif return false; } int Ver2Int(LString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; i |= (uint8_t)Int; } else { LAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; #define DEBUG_READER_THREAD 0 #if DEBUG_READER_THREAD #define LOG_READER(...) printf(__VA_ARGS__) #else #define LOG_READER(...) #endif ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread") { Vcs = vcs; Process = p; Out = out; Result = -1; FilterCount = 0; // We don't start this thread immediately... because the number of threads is scaled to the system // resources, particularly CPU cores. } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LSleep(1); } const char *HgFilter = "We\'re removing Mercurial support"; const char *CvsKill = "No such file or directory"; int ReaderThread::OnLine(char *s, ssize_t len) { switch (Vcs) { case VcHg: { if (strnistr(s, HgFilter, len)) FilterCount = 4; if (FilterCount > 0) { FilterCount--; return 0; } else if (LString(s, len).Strip().Equals("remote:")) { return 0; } break; } case VcCvs: { if (strnistr(s, CvsKill, len)) return -1; break; } default: break; } return 1; } bool ReaderThread::OnData(char *Buf, ssize_t &r) { LOG_READER("OnData %i\n", (int)r); #if 1 char *Start = Buf; for (char *c = Buf; c < Buf + r;) { bool nl = *c == '\n'; c++; if (nl) { int Result = OnLine(Start, c - Start); if (Result < 0) { // Kill process and exit thread. Process->Kill(); return false; } if (Result == 0) { ssize_t LineLen = c - Start; ssize_t NextLine = c - Buf; ssize_t Remain = r - NextLine; if (Remain > 0) memmove(Start, Buf + NextLine, Remain); r -= LineLen; c = Start; } else Start = c; } } #endif Out->Write(Buf, r); return true; } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { LString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } char Buf[1024]; ssize_t r; LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle()); while (Process->IsRunning()) { if (Out) { LOG_READER("%s:%i - starting read.\n", _FL); r = Process->Read(Buf, sizeof(Buf)); LOG_READER("%s:%i - read=%i.\n", _FL, (int)r); if (r > 0) { if (!OnData(Buf, r)) return -1; } } else { Process->Kill(); return -1; break; } } LOG_READER("%s:%i - process loop done.\n", _FL); if (Out) { while ((r = Process->Read(Buf, sizeof(Buf))) > 0) OnData(Buf, r); } LOG_READER("%s:%i - loop done.\n", _FL); Result = (int) Process->GetExitValue(); #if _DEBUG if (Result) printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result); #endif return Result; } ///////////////////////////////////////////////////////////////////////////////////////////// int VcFolder::CmdMaxThreads = 0; int VcFolder::CmdActiveThreads = 0; void VcFolder::Init(AppPriv *priv) { if (!CmdMaxThreads) CmdMaxThreads = LAppInst->GetCpuCount(); d = priv; IsCommit = false; IsLogging = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; IsBranches = StatusNone; IsIdent = StatusNone; Unpushed = Unpulled = -1; Type = VcNone; CmdErrors = 0; CurrentCommitIdx = -1; Expanded(false); Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); LAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *uri) { Init(priv); Uri.Set(uri); GetType(); } VcFolder::VcFolder(AppPriv *priv, LXmlTag *t) { Init(priv); Serialize(t, false); } VcFolder::~VcFolder() { Log.DeleteObjects(); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = d->DetectVcs(this); return Type; } const char *VcFolder::LocalPath() { if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty()) { LAssert(!"Shouldn't call this if not a file path."); return NULL; } auto c = Uri.sPath.Get(); #ifdef WINDOWS if (*c == '/') c++; #endif return c; } const char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Uri.IsFile()) Cache = LocalPath(); else Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get()); if (Cmds.Length()) Cache += " (...)"; return Cache; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(LXmlTag *t, bool Write) { if (Write) t->SetContent(Uri.ToString()); else { LString s = t->GetContent(); bool isUri = s.Find("://") >= 0; if (isUri) Uri.Set(s); else Uri.SetFile(s); } return true; } LXmlTag *VcFolder::Save() { LXmlTag *t = new LXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { const char *Def = NULL; switch (GetType()) { case VcGit: Def = "git"; break; case VcSvn: Def = "svn"; break; case VcHg: Def = "hg"; break; case VcCvs: Def = "cvs"; break; default: break; } if (!VcCmd) { LString Opt; Opt.Printf("%s-path", Def); LVariant v; if (d->Opts.GetValue(Opt, v)) VcCmd = v.Str(); } if (!VcCmd) VcCmd = Def; return VcCmd; } bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback) { Result Ret; Ret.Code = -1; const char *Exe = GetVcName(); if (!Exe || CmdErrors > 2) return false; if (Uri.IsFile()) { new ProcessCallback(Exe, Args, LocalPath(), Logging == LogNone ? d->Log : NULL, GetTree()->GetWindow(), Callback); } else { LAssert(!"Impl me."); return false; } return true; } bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (Uri.IsFile()) { if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); LAutoPtr Process(new LSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath()); #ifdef MAC // Mac GUI apps don't share the terminal path, so this overrides that and make it work auto Path = LGetPath(); if (Path.Length()) { LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path); Process->SetEnvironment("PATH", Tmp); } #endif LString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(LocalPath()); Ctx.Add(Exe); Ctx.Add(Args); LAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(GetType(), Process, c)); Cmds.Add(c.Release()); } else { auto c = d->GetConnection(Uri.ToString()); if (!c) return false; if (!c->Command(this, Exe, Args, Parser, Params)) return false; } Update(); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } void VcFolder::AddGitName(LString Hash, LString Name) { LString Existing = GitNames.Find(Hash); if (Existing) GitNames.Add(Hash, Existing + "," + Name); else GitNames.Add(Hash, Name); } LString VcFolder::GetGitNames(LString Hash) { LString Short = Hash(0, 11); return GitNames.Find(Short); } bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { LString::Array a = s.SplitDelimit("\r\n"); for (auto &l: a) { LString::Array c = l.SplitDelimit(" \t"); if (c[0].Equals("*")) { CurrentBranch = c[1]; AddGitName(c[2], CurrentBranch); Branches.Add(CurrentBranch, new VcBranch(CurrentBranch, c[2])); } else { AddGitName(c[1], c[0]); Branches.Add(c[0], new VcBranch(c[0], c[1])); } } break; } case VcHg: { auto a = s.SplitDelimit("\r\n"); Branches.DeleteObjects(); for (auto b: a) { if (!CurrentBranch) CurrentBranch = b; Branches.Add(b, new VcBranch(b)); } if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } default: { break; } } IsBranches = Result ? StatusError : StatusNone; OnBranchesChange(); return false; } void VcFolder::OnBranchesChange() { auto *w = d->Tree->GetWindow(); if (!w || !LTreeItem::Select()) return; if (Branches.Length()) { // Set the colours up LString Default; for (auto b: Branches) { if (!stricmp(b.key, "default") || !stricmp(b.key, "trunk")) Default = b.key; /* else printf("Other=%s\n", b.key); */ } int Idx = 1; for (auto b: Branches) { if (!b.value->Colour.IsValid()) { if (Default && !stricmp(b.key, Default)) b.value->Colour = GetPaletteColour(0); else b.value->Colour = GetPaletteColour(Idx++); } } } DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { LString::Array a; for (auto b: Branches) a.Add(b.key); dd->SetList(IDC_BRANCH, a); } LViewI *b; if (Branches.Length() > 0 && w->GetViewById(IDC_BRANCH, b)) { if (CurrentBranch) { b->Name(CurrentBranch); } else { auto it = Branches.begin(); if (it != Branches.end()) b->Name((*it).key); } } } void VcFolder::DefaultFields() { if (Fields.Length() == 0) { switch (GetType()) { case VcHg: { Fields.Add(LGraph); Fields.Add(LIndex); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } case VcGit: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } default: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } } } } void VcFolder::UpdateColumns() { d->Commits->EmptyColumns(); for (auto c: Fields) { switch (c) { case LGraph: d->Commits->AddColumn("---", 60); break; case LIndex: d->Commits->AddColumn("Index", 60); break; case LBranch: d->Commits->AddColumn("Branch", 60); break; case LRevision: d->Commits->AddColumn("Revision", 60); break; case LAuthor: d->Commits->AddColumn("Author", 240); break; case LTimeStamp: d->Commits->AddColumn("Date", 130); break; case LMessageTxt: d->Commits->AddColumn("Message", 400); break; default: LAssert(0); break; } } } -LString VcFolder::GetFilter() -{ - auto Ctrl = d->Wnd()->GetCtrlName(IDC_FILTER); - return ValidStr(Ctrl) ? Ctrl : NULL; -} - void VcFolder::FilterCurrentFiles() { - auto Filter = GetFilter(); - LArray All, AddFiltered; + LArray All; d->Files->GetAll(All); - // Remove any current items that no longer match... + // Update the display property for (auto i: All) { auto fn = i->GetText(COL_FILENAME); - if (Filter && !Stristr(fn, Filter.Get())) - { - d->Files->Remove(i); - AddFiltered.Add(i); - } + bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get()); + i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); + + LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis); } - // Add any filtered items that now match... - for (unsigned n=0; nFiltered.Length();) - { - auto i = d->Filtered[n]; - auto fn = i->GetText(COL_FILENAME); - if (!Filter || Stristr(fn, Filter.Get())) - { - d->Files->Insert(i); - d->Filtered.DeleteAt(n); - } - else n++; - } - - d->Filtered += AddFiltered; d->Files->Sort(0); + d->Files->UpdateAllItems(); d->Files->ResizeColumnsToContent(); } void VcFolder::Select(bool b) { #if PROFILE_FN LProfile Prof("Select"); #endif if (!b) { auto *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } PROF("Parent.Select"); LTreeItem::Select(b); if (b) { if (Uri.IsFile() && !LDirExists(LocalPath())) return; PROF("DefaultFields"); DefaultFields(); PROF("Type Change"); if (GetType() != d->PrevType) { d->PrevType = GetType(); UpdateColumns(); } PROF("UpdateCommitList"); if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { switch (GetType()) { case VcGit: { LVariant Limit; d->Opts.GetValue("git-limit", Limit); LString cmd = "rev-list --all --header --timestamp --author-date-order", s; if (Limit.CastInt32() > 0) { s.Printf(" -n %i", Limit.CastInt32()); cmd += s; } IsLogging = StartCmd(cmd, &VcFolder::ParseRevList); break; } case VcSvn: { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); if (CommitListDirty) { IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); break; } LString s; if (Limit.CastInt32() > 0) s.Printf("log --limit %i", Limit.CastInt32()); else s = "log"; IsLogging = StartCmd(s, &VcFolder::ParseLog); break; } case VcPending: { break; } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); break; } } CommitListDirty = false; } PROF("GetBranches"); if (GetBranches()) OnBranchesChange(); - LString Filter = GetFilter(); if (d->CurFolder != this) { PROF("RemoveAll"); d->CurFolder = this; d->Commits->RemoveAll(); } PROF("Uncommit"); if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Commits->Insert(Uncommit, 0); PROF("Log Loop"); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (auto l: Log) { if (CurrentCommit && l->GetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(l->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; l->SetCurrent(true); } else { l->SetCurrent(false); } break; } default: l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev())); break; } } - bool Add = !Filter; - if (Filter) + bool Add = !d->CommitFilter; + if (d->CommitFilter) { const char *s = l->GetRev(); - if (s && strstr(s, Filter) != NULL) + if (s && strstr(s, d->CommitFilter) != NULL) Add = true; s = l->GetAuthor(); - if (s && stristr(s, Filter) != NULL) + if (s && stristr(s, d->CommitFilter) != NULL) Add = true; s = l->GetMsg(); - if (s && stristr(s, Filter) != NULL) + if (s && stristr(s, d->CommitFilter) != NULL) Add = true; } LList *CurOwner = l->GetList(); if (Add ^ (CurOwner != NULL)) { if (Add) Ls.Insert(l); else d->Commits->Remove(l); } } PROF("Ls Ins"); d->Commits->Insert(Ls); if (d->Resort >= 0) { PROF("Resort"); d->Commits->Sort(LstCmp, d->Resort); d->Resort = -1; } PROF("ColSizing"); if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS) { int i = 0; if (GetType() == VcHg && d->Commits->GetColumns() >= 7) { d->Commits->ColumnAt(i++)->Width(60); // LGraph d->Commits->ColumnAt(i++)->Width(40); // LIndex d->Commits->ColumnAt(i++)->Width(100); // LRevision d->Commits->ColumnAt(i++)->Width(60); // LBranch d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } else if (d->Commits->GetColumns() >= 5) { d->Commits->ColumnAt(i++)->Width(40); // LGraph d->Commits->ColumnAt(i++)->Width(270); // LRevision d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } } else d->Commits->ResizeColumnsToContent(); PROF("UpdateAll"); d->Commits->UpdateAllItems(); PROF("GetCur"); GetCurrentRevision(); } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitIndexCmp(VcCommit **a, VcCommit **b) { auto ai = (*a)->GetIndex(); auto bi = (*b)->GetIndex(); auto diff = (int64)bi - ai; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { uint64 ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts - ats; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } void VcFolder::GetCurrentRevision(ParseParams *Params) { if (CurrentCommit || IsIdent != StatusNone) return; switch (GetType()) { case VcGit: if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcSvn: if (StartCmd("info", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcHg: if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcCvs: break; default: break; } } bool VcFolder::GetBranches(ParseParams *Params) { if (Branches.Length() > 0 || IsBranches != StatusNone) return true; switch (GetType()) { case VcGit: if (StartCmd("-P branch -a -v", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; break; case VcSvn: Branches.Add("trunk", new VcBranch("trunk")); OnBranchesChange(); break; case VcHg: if (StartCmd("branch", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; break; case VcCvs: break; default: break; } return false; } bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params) { Log.DeleteObjects(); int Errors = 0; switch (GetType()) { case VcGit: { LString::Array Commits; Commits.SetFixedLength(false); // Split on the NULL chars... char *c = s.Get(); char *e = c + s.Length(); while (c < e) { char *nul = c; while (nul < e && *nul) nul++; if (nul <= c) break; Commits.New().Set(c, nul-c); if (nul >= e) break; c = nul + 1; } for (auto Commit: Commits) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->GitParse(Commit, true)) { Log.Add(Rev.Release()); } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); Errors++; } } LinkParents(); break; } default: LAssert(!"Impl me."); break; } IsLogging = false; return Errors == 0; } LString VcFolder::GetFilePart(const char *uri) { LUri u(uri); LString File = u.IsFile() ? u.DecodeStr(u.LocalPath()) : u.sPath(Uri.sPath.Length(), -1).LStrip("/"); return File; } void VcFolder::LogFile(const char *uri) { LString Args; switch (GetType()) { case VcSvn: case VcHg: { LString File = GetFilePart(uri); ParseParams *Params = new ParseParams(uri); Args.Printf("log \"%s\"", File.Get()); IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal); break; } /* case VcGit: break; case VcCvs: break; */ default: LAssert(!"Impl me."); break; } } VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree) { VcLeaf *r = NULL; if (OpenTree) DoExpand(); for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (auto pc: Log) Map.Add(pc->GetRev(), pc); int Skipped = 0, Errors = 0; VcLeaf *File = Params ? FindLeaf(Params->Str, true) : NULL; LArray *Out = File ? &File->Log : &Log; if (File) { for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent())) Leaf->OnExpand(true); File->Select(true); File->ScrollTo(); } switch (GetType()) { case VcGit: { LString::Array c; c.SetFixedLength(false); char *prev = s.Get(); for (char *i = s.Get(); *i; ) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } for (unsigned i=0; i Rev(new VcCommit(d, this)); if (Rev->GitParse(c[i], false)) { if (!Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get()); Errors++; } } Out->Sort(CommitDateCmp); break; } case VcSvn: { LString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d, this)); LString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (File || !Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else if (Raw) { OnCmdError(Raw, "ParseLog Failed"); Errors++; } } Out->Sort(CommitRevCmp); break; } case VcHg: { LString::Array c = s.Split("\n\n"); LHashTbl, VcCommit*> Idx; for (auto &Commit: c) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->HgParse(Commit)) { auto Existing = File ? NULL : Map.Find(Rev->GetRev()); if (!Existing) Out->Add(Existing = Rev.Release()); if (Existing->GetIndex() >= 0) Idx.Add(Existing->GetIndex(), Existing); } } if (!File) { // Patch all the trivial parents... for (auto c: Log) { if (c->GetParents()->Length() > 0) continue; auto CIdx = c->GetIndex(); if (CIdx <= 0) continue; auto Par = Idx.Find(CIdx - 1); if (Par) c->GetParents()->Add(Par->GetRev()); } } Out->Sort(CommitIndexCmp); if (!File) LinkParents(); d->Resort = 1; break; } case VcCvs: { if (Result) { OnCmdError(s, "Cvs command failed."); break; } LHashTbl, VcCommit*> Map; LString::Array c = s.Split("============================================================================="); for (auto &Commit: c) { if (Commit.Strip().Length()) { LString Head, File; LString::Array Versions = Commit.Split("----------------------------"); LString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (auto &Line: Lines) { LString::Array p = Line.Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); LString Var = p[0].Strip().Lower(); LString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val, new VcBranch(Val)); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { LString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { LString Ver = Lines[0].Split(" ").Last(); LString::Array a = Lines[1].SplitDelimit(";"); LString Date = a[0].Split(":", 1).Last().Strip(); LString Author = a[1].Split(":", 1).Last().Strip(); LString Id = a[2].Split(":", 1).Last().Strip(); LString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { uint64 Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts); if (!Cc) { Map.Add(Ts, Cc = new VcCommit(d, this)); Out->Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LAssert(!"NO ts for date."); } else LAssert(!"Date parsing failed."); } } } } break; } default: LAssert(!"Impl me."); break; } if (File) File->ShowLog(); // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return !Result; } void VcFolder::LinkParents() { #if PROFILE_FN LProfile Prof("LinkParents"); #endif LHashTbl,VcCommit*> Map; // Index all the commits int i = 0; for (auto c:Log) { c->Idx = i++; c->NodeIdx = -1; Map.Add(c->GetRev(), c); } // Create all the edges... PROF("Create edges."); for (auto c:Log) { auto *Par = c->GetParents(); for (auto &pRev : *Par) { auto *p = Map.Find(pRev); if (p) new VcEdge(p, c); #if 0 else return; #endif } } // Map the edges to positions PROF("Map edges."); typedef LArray EdgeArr; LArray Active; for (auto c:Log) { for (unsigned i=0; c->NodeIdx<0 && iParent == c) { c->NodeIdx = i; break; } } } // Add starting edges to active set for (auto e:c->Edges) { if (e->Child == c) { if (c->NodeIdx < 0) c->NodeIdx = (int)Active.Length(); e->Idx = c->NodeIdx; c->Pos.Add(e, e->Idx); Active[e->Idx].Add(e); } } // Now for all active edges... assign positions for (unsigned i=0; iLength(); n++) { LAssert(Active.PtrCheck(Edges)); VcEdge *e = (*Edges)[n]; if (c == e->Child || c == e->Parent) { LAssert(c->NodeIdx >= 0); c->Pos.Add(e, c->NodeIdx); } else { // May need to untangle edges with different parents here bool Diff = false; for (auto edge: *Edges) { if (edge != e && edge->Child != c && edge->Parent != e->Parent) { Diff = true; break; } } if (Diff) { int NewIndex = -1; // Look through existing indexes for a parent match for (unsigned ii=0; iiParent? bool Match = true; for (auto ee: Active[ii]) { if (ee->Parent != e->Parent) { Match = false; break; } } if (Match) NewIndex = ii; } if (NewIndex < 0) // Create new index for this parent NewIndex = (int)Active.Length(); Edges->Delete(e); auto &NewEdges = Active[NewIndex]; NewEdges.Add(e); Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to e->Idx = NewIndex; c->Pos.Add(e, NewIndex); n--; } else { LAssert(e->Idx == i); c->Pos.Add(e, i); } } } } // Process terminating edges for (auto e: c->Edges) { if (e->Parent == c) { if (e->Idx < 0) { // This happens with out of order commits..? continue; } int i = e->Idx; if (c->NodeIdx < 0) c->NodeIdx = i; if (Active[i].HasItem(e)) Active[i].Delete(e); else LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL); } } // Collapse any empty active columns for (unsigned i=0; iIdx > 0); edge->Idx--; c->Pos.Add(edge, edge->Idx); } } i--; } } } // Find all the "heads", i.e. a commit without any children PROF("Find heads."); LCombo *Heads; if (d->Wnd()->GetViewById(IDC_HEADS, Heads)) { Heads->Empty(); for (auto c:Log) { bool Has = false; for (auto e:c->Edges) { if (e->Parent == c) { Has = true; break; } } if (!Has) Heads->Insert(c->GetRev()); } Heads->SendNotify(LNotifyTableLayoutRefresh); } } VcFile *AppPriv::FindFile(const char *Path) { if (!Path) return NULL; LArray files; if (Files->GetAll(files)) { LString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) return f; } } return NULL; } VcFile *VcFolder::FindFile(const char *Path) { return d->FindFile(Path); } void VcFolder::OnCmdError(LString Output, const char *Msg) { if (!CmdErrors) { if (Output.Length()) d->Log->Write(Output, Output.Length()); auto vc_name = GetVcName(); if (vc_name) { LString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } else if (Msg) { d->Log->Print("%s\n", Msg); } } CmdErrors++; d->Tabs->Value(1); GetCss(true)->Color(LColour::Red); } void VcFolder::ClearError() { GetCss(true)->Color(LCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { auto p = s.Strip().SplitDelimit(); CurrentCommit = p[0].Strip(" \t\r\n+"); if (p.Length() > 1) CurrentCommitIdx = p[1].Int(); else CurrentCommitIdx = -1; if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } LString::Array c = s.Split("\n"); for (unsigned i=0; iIsWorking = true; ParseStatus(Result, s, Params); break; } case VcCvs: { bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); if (Untracked) { auto Lines = s.SplitDelimit("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(" \t", 1); if (p.Length() > 1) { auto f = new VcFile(d, this, NULL, true); f->SetText(p[0], COL_STATE); f->SetText(p[1], COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } } } // else fall thru } default: { ParseDiffs(s, NULL, true); break; } } IsWorkingFld = false; FilterCurrentFiles(); d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } void VcFolder::DiffRange(const char *FromRev, const char *ToRev) { if (!FromRev || !ToRev) return; switch (GetType()) { case VcSvn: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("diff -r%s:%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcGit: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("-P diff %s..%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcCvs: case VcHg: default: LAssert(!"Impl me."); break; } } bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params) { if (Params) ParseDiffs(s, Params->Str, Params->IsWorking); else ParseDiffs(s, NULL, true); return false; } void VcFolder::Diff(VcFile *file) { auto Fn = file->GetFileName(); if (!Fn || !Stricmp(Fn, ".") || !Stricmp(Fn, "..")) return; const char *Prefix = ""; switch (GetType()) { case VcGit: Prefix = "-P "; // fall through case VcHg: { LString a; auto rev = file->GetRevision(); if (rev) a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn); else a.Printf("%sdiff \"%s\"", Prefix, Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcSvn: { LString a; if (file->GetRevision()) a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn); else a.Printf("diff \"%s\"", Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcCvs: break; default: LAssert(!"Impl me."); break; } } bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking) { LAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { LString::Array a = s.Split("\n"); LString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); LString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); // LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcHg: { LString Sep("\n"); LString::Array a = s.Split(Sep); LString::Array Diffs; VcFile *f = NULL; List Files; LProgressDlg Prog(GetTree(), 1000); Prog.SetDescription("Reading diff lines..."); Prog.SetRange(a.Length()); // Prog.SetYieldTime(300); for (unsigned i=0; iSetDiff(Sep.Join(Diffs)); Diffs.Empty(); auto MainParts = a[i].Split(" -r "); auto FileParts = MainParts.Last().Split(" ",1); LString Fn = FileParts.Last(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); // f->SetText(Status, COL_STATE); Files.Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { Diffs.Add(a[i]); } Prog.Value(i); if (Prog.IsCancelled()) break; } if (f && Diffs.Length()) { f->SetDiff(Sep.Join(Diffs)); Diffs.Empty(); } d->Files->Insert(Files); break; } case VcSvn: { LString::Array a = s.Replace("\r").Split("\n"); LString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; LString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->SetText("M", COL_STATE); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LAssert(!"Impl me."); break; } } FilterCurrentFiles(); return true; } bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; FilterCurrentFiles(); return false; } void VcFolder::OnSshCmd(SshParams *p) { if (!p || !p->f) { LAssert(!"Param error."); return; } LString s = p->Output; int Result = p->ExitCode; if (Result == ErrSubProcessFailed) { CmdErrors++; } else if (p->Parser) { bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params); if (Reselect) { if (LTreeItem::Select()) Select(true); } } } void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. // printf("Cmds.Len=%i\n", (int)Cmds.Length()); for (unsigned i=0; iRd->GetState()=%i\n", c->Rd->GetState()); if (c->Rd->GetState() == LThread::THREAD_INIT) { if (CmdActiveThreads < CmdMaxThreads) { c->Rd->Run(); CmdActiveThreads++; // LgiTrace("CmdActiveThreads++ = %i\n", CmdActiveThreads); } // else printf("Too many active threads."); } else if (c->Rd->IsExited()) { CmdActiveThreads--; // LgiTrace("CmdActiveThreads-- = %i\n", CmdActiveThreads); LString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } else if (c->PostOp) { if (s.Length() == 18 && s.Equals("GSUBPROCESS_ERROR\n")) { OnCmdError(s, "Sub process failed."); } else { Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); } } Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } // else printf("Not exited.\n"); } Processing = false; } if (Reselect) { if (LTreeItem::Select()) Select(true); } if (CmdsChanged) { Update(); } if (CmdErrors) { d->Tabs->Value(1); CmdErrors = false; } } void VcFolder::OnRemove() { LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL); if (t) { Uncommit.Reset(); if (LTreeItem::Select()) { d->Files->Empty(); d->Commits->RemoveAll(); } bool Found = false; auto u = Uri.ToString(); for (auto c: t->Children) { if (c->IsTag(OPT_Folder) && c->GetContent()) { auto Content = c->GetContent(); if (!_stricmp(Content, u)) { c->RemoveTag(); delete c; Found = true; break; } } } LAssert(Found); d->Opts.Unlock(); } } void VcFolder::Empty() { Type = VcNone; IsCommit = false; IsLogging = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; IsBranches = StatusNone; IsIdent = StatusNone; Unpushed = Unpulled = -1; CmdErrors = 0; CurrentCommitIdx = -1; CurrentCommit.Empty(); RepoUrl.Empty(); VcCmd.Empty(); Uncommit.Reset(); Log.DeleteObjects(); d->Commits->Empty(); d->Files->Empty(); if (!Uri.IsFile()) GetCss(true)->Color(LColour::Blue); } void VcFolder::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile()); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL, Uri.IsFile()); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Pull", IDM_PULL); s.AppendItem("Status", IDM_STATUS); s.AppendItem("Push", IDM_PUSH); s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); if (!Uri.IsFile()) { s.AppendSeparator(); s.AppendItem("Edit Location", IDM_EDIT); } int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LBrowseToFile(LocalPath()); break; } case IDM_TERMINAL: { TerminalAt(LocalPath()); break; } case IDM_CLEAN: { Clean(); break; } case IDM_PULL: { Pull(); break; } case IDM_STATUS: { FolderStatus(); break; } case IDM_PUSH: { Push(); break; } case IDM_UPDATE_SUBS: { UpdateSubs(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } case IDM_EDIT: { auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location"); Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { if (ctrlId) { Uri.Set(Dlg->GetStr()); Empty(); Select(true); } delete dlg; }); break; } default: break; } } } void VcFolder::OnUpdate(const char *Rev) { if (!Rev) return; if (!IsUpdate) { LString Args; NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcHg: Args.Printf("update -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; default: { LAssert(!"Impl me."); break; } } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } struct SshFindEntry { LString Flags, Name, User, Group; uint64_t Size; LDateTime Modified, Access; SshFindEntry &operator =(const LString &s) { auto p = s.SplitDelimit("/"); if (p.Length() == 7) { Flags = p[0]; Group = p[1]; User = p[2]; Access.Set((uint64_t) p[3].Int()); Modified.Set((uint64_t) p[4].Int()); Size = p[5].Int(); Name = p[6]; } return *this; } bool IsDir() { return Flags(0) == 'd'; } bool IsHidden() { return Name(0) == '.'; } const char *GetName() { return Name; } static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); } }; bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params) { if (!Params || !s) return false; auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this); LUri u(Params->Str); auto Lines = s.SplitDelimit("\r\n"); LArray Entries; for (size_t i=1; iStr, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { LUri Path = u; Path += Dir.GetName(); new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false); } } } return false; } void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri) { LUri u(ReadUri); if (u.IsFile()) { // Read child items LDirectory Dir; for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next()) { auto name = Dir.GetName(); if (Dir.IsHidden()) continue; LUri Path = u; Path += name; new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir()); } } else { auto c = d->GetConnection(ReadUri); if (!c) return; LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/"); LString Args; Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : "."); auto *Params = new ParseParams(ReadUri); Params->Leaf = dynamic_cast(Parent); c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params); return; } Parent->Sort(FolderCompare); } void VcFolder::OnVcsType(LString errorMsg) { if (!d) { LAssert(!"No priv instance"); return; } auto c = d->GetConnection(Uri.ToString(), false); if (c) { auto NewType = c->Types.Find(Uri.sPath); if (NewType && NewType != Type) { if (NewType == VcError) { OnCmdError(NULL, errorMsg); } else { Type = NewType; ClearError(); Update(); if (LTreeItem::Select()) Select(true); } } } } void VcFolder::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Uri.ToString()); } } void VcFolder::OnExpand(bool b) { if (b) DoExpand(); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { LString Args; switch (GetType()) { case VcGit: Args.Printf("-P show %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } FilterCurrentFiles(); break; } case VcHg: { Args.Printf("diff --change %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; } default: LAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } LString ConvertUPlus(LString s) { LArray c; LUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return LString((char16*)c.AddressOf()); #else return LString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params) { bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { LHashTbl,VcFile*> Map; for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) Map.Add(f->GetText(COL_FILENAME), f); } #if 0 LFile Tmp("C:\\tmp\\output.txt", O_WRITE); Tmp.Write(s); Tmp.Close(); #endif LString::Array a = s.Split("==================================================================="); for (auto i : a) { LString::Array Lines = i.SplitDelimit("\r\n"); if (Lines.Length() == 0) continue; LString f = Lines[0].Strip(); if (f.Find("File:") == 0) { LString::Array Parts = f.SplitDelimit("\t"); LString File = Parts[0].Split(": ").Last().Strip(); LString Status = Parts[1].Split(": ").Last(); LString WorkingRev; for (auto l : Lines) { LString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, WorkingRev, IsWorking))) Ins.Insert(f); } if (f) { f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } else if (f(0) == '?' && ShowUntracked) { LString File = f(2, -1); VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, NULL, IsWorking))) Ins.Insert(f); } if (f) { f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } } for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) { if (f->GetStatus() == VcFile::SUnknown) f->SetStatus(VcFile::SUntracked); } } break; } case VcGit: { LString::Array Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { LString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { f = new VcFile(d, this, p[6], IsWorking); f->SetText(p[1].Strip("."), COL_STATE); f->SetText(p.Last(), COL_FILENAME); } } else if (Fmt == 1) { LString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { if (s.Find("failed to import") >= 0) { OnCmdError(s, "Tool error."); return false; } LString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { LString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { LString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); if (GetType() == VcSvn && File.Find("+ ") == 0) { File = File(5, -1); } VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LAssert(!"Impl me."); break; } } if ((Unpushed = Ins.Length() > 0)) { if (CmdErrors == 0) GetCss(true)->Color(LColour(255, 128, 0)); } else if (Unpulled == 0) { GetCss(true)->Color(LCss::ColorInherit); } Update(); if (LTreeItem::Select()) { d->Files->Insert(Ins); FilterCurrentFiles(); } else { Ins.DeleteObjects(); } if (Params && Params->Leaf) Params->Leaf->AfterBrowse(); return false; // Don't refresh list } // Clone/checkout any sub-repositries. bool VcFolder::UpdateSubs() { LString Arg; switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: Arg = "submodule update --init"; break; } return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal); } bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params) { switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: break; } return false; } void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify) { LUri Uri(uri); if (Uri.IsFile() && Uri.sPath) { LFile::Path p(Uri.sPath(1,-1)); if (!p.IsFolder()) { LAssert(!"Needs to be a folder."); return; } } if (LTreeItem::Select()) d->ClearFiles(); LString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LAssert(!"Where is the version?"); // What version did =2 become available? It's definitely not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "-P status --porcelain=2"; else Arg = "-P status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (uri && Notify) { p->AltInitPath = uri; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); switch (GetType()) { case VcHg: CountToTip(); break; default: break; } } void VcFolder::CountToTip() { // if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk")) { // LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx); if (!CurrentBranch) GetBranches(new ParseParams("CountToTip")); else if (CurrentCommitIdx < 0) GetCurrentRevision(new ParseParams("CountToTip")); else { LString Arg; Arg.Printf("id -n -r %s", CurrentBranch.Get()); StartCmd(Arg, &VcFolder::ParseCountToTip); } } } bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: if (CurrentCommitIdx >= 0) { auto p = s.Strip(); auto idx = p.Int(); if (idx >= CurrentCommitIdx) { Unpulled = (int) (idx - CurrentCommitIdx); Update(); } } break; default: break; } return false; } void VcFolder::ListWorkingFolder() { if (IsWorkingFld) return; d->ClearFiles(); bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); LString Arg; switch (GetType()) { case VcCvs: if (Untracked) Arg = "-qn update"; else Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: Arg = "-P diff --diff-filter=ACDMRTU"; break; case VcHg: Arg = "status -mard"; break; default: return; } IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking); } void VcFolder::GitAdd() { if (!PostAdd) return; LString Args; if (PostAdd->Files.Length() == 0) { LString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { LString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params) { GitAdd(); return false; } bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params) { if (LTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { auto i = new LInput(GetTree(), "", "Git user name:", AppName); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.name \"%s\"", i->GetStr().Get()); StartCmd(Args); auto inp = new LInput(GetTree(), "", "Git user email:", AppName); i->DoModal([this, inp](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get()); StartCmd(Args); } delete dlg; }); } delete dlg; }); } break; } default: break; } return false; } if (Result == 0 && LTreeItem::Select()) { d->ClearFiles(); auto *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; CommitListDirty = true; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } case VcHg: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); else GetCss(true)->Color(LColour::Green); } break; } case VcCvs: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } default: { LAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { LArray Add; bool Partial = false; for (auto fp: *d->Files) { VcFile *f = dynamic_cast(fp); if (f) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; LJson j; j.Set("Command", "commit"); j.Set("Msg", Msg); j.Set("AndPush", (int64_t)AndPush); StartBranch(Branch, j.GetJson()); return; } if (!IsCommit) { LString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { LString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { LString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcHg: { LString::Array a; LString CommitMsg = Msg; TmpFile Tmp; if (CommitMsg.Find("\n") >= 0) { Tmp.Create().Write(Msg); a.New().Printf("commit -l \"%s\"", Tmp.GetName()); } else { a.New().Printf("commit -m \"%s\"", Msg); } if (Partial) { for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcCvs: { LString a; a.Printf("commit -m \"%s\"", Msg); IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal); break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } } } bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { if (Result == 0 && Params && Params->Str) { LJson j(Params->Str); auto cmd = j.Get("Command"); if (cmd.Equals("commit")) { auto Msg = j.Get("Msg"); auto AndPush = j.Get("AndPush").Int(); if (Msg) { Commit(Msg, NULL, AndPush > 0); } } } break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } return true; } void VcFolder::StartBranch(const char *BranchName, const char *OnCreated) { if (!BranchName) return; switch (GetType()) { case VcHg: { LString a; a.Printf("branch \"%s\"", BranchName); StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL); break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } } void VcFolder::Push(bool NewBranchOk) { LString Args; bool Working = false; switch (GetType()) { case VcHg: { auto args = NewBranchOk ? "push --new-branch" : "push"; Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal); break; } case VcGit: { Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal); break; } case VcSvn: { // Nothing to do here.. the commit pushed the data already break; } default: { OnCmdError(NULL, "No push impl for type."); break; } } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params) { bool Status = false; if (Result) { if (GetType() == VcHg) { if (s.Find("push creates new remote branches") > 0) { if (LgiMsg(GetTree(), "Push will create a new remote branch. Is that ok?", AppName, MB_YESNO) == IDYES) { Push(true); return false; } } } OnCmdError(s, "Push failed."); } else { switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; GetCss(true)->Color(LColour::Green); Update(); Status = true; } GetTree()->SendNotify((LNotifyType)LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(int AndUpdate, LoggingType Logging) { bool Status = false; if (AndUpdate < 0) AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0; switch (GetType()) { case VcNone: return; case VcHg: Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging); break; case VcGit: Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; default: OnCmdError(NULL, "No pull impl for type."); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params) { GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcHg: { CurrentCommit.Empty(); auto Lines = s.SplitDelimit("\n"); bool HasUpdates = false; for (auto Ln: Lines) { if (Ln.Find("files updated") < 0) continue; auto Parts = Ln.Split(","); for (auto p: Parts) { auto n = p.Strip().Split(" ", 1); if (n.Length() == 2) { if (n[0].Int() > 0) HasUpdates = true; } } } if (HasUpdates) GetCss(true)->Color(LColour::Green); else GetCss(true)->Color(LCss::ColorInherit); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); LString::Array a = s.SplitDelimit("\r\n"); for (auto &Ln: a) { if (Ln.Find("At revision") >= 0) { LString::Array p = Ln.SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln.Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); LString Args; if (Limit.CastInt32() > 0) Args.Printf("log --limit %i", Limit.CastInt32()); else Args = "log"; IsLogging = StartCmd(Args, &VcFolder::ParseLog); return false; } break; } default: break; } CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::MergeToLocal(LString Rev) { switch (GetType()) { case VcGit: { LString Args; Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } case VcHg: { LString Args; Args.Printf("merge -r %s", Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: if (Result == 0) CommitListDirty = true; else OnCmdError(s, "Merge failed."); break; default: LAssert(!"Impl me."); break; } return true; } void VcFolder::Refresh() { CommitListDirty = true; CurrentCommit.Empty(); GitNames.Empty(); Branches.DeleteObjects(); if (Uncommit && Uncommit->LListItem::Select()) Uncommit->Select(true); Select(true); } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) GetCss(true)->Color(LCss::ColorInherit); break; default: LAssert(!"Impl me."); break; } return false; } LColour VcFolder::BranchColour(const char *Name) { if (!Name) return GetPaletteColour(0); auto b = Branches.Find(Name); if (!b) // Must be a new one? { int i = 1; for (auto b: Branches) { auto &v = b.value; if (!v->Colour.IsValid()) { if (v->Default) v->Colour = GetPaletteColour(0); else v->Colour = GetPaletteColour(i++); } } Branches.Add(Name, b = new VcBranch(Name)); b->Colour = GetPaletteColour((int)Branches.Length()); } return b ? b->Colour : GetPaletteColour(0); } void VcFolder::CurrentRev(std::function Callback) { LString Cmd; Cmd.Printf("id -i"); RunCmd(Cmd, LogNormal, [Callback](auto r) { if (r.Code == 0) Callback(r.Out.Strip()); }); } bool VcFolder::RenameBranch(LString NewName, LArray &Revs) { switch (GetType()) { case VcHg: { // Update to the ancestor of the commits LHashTbl,int> Refs(0, -1); for (auto c: Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) < 0) Refs.Add(p, 0); if (Refs.Find(c->GetRev()) >= 0) Refs.Add(c->GetRev(), 1); } LString::Array Ans; for (auto i:Refs) { if (i.value == 0) Ans.Add(i.key); } LArray Ancestors = d->GetRevs(Ans); if (Ans.Length() != 1) { // We should only have one ancestor LString s, m; s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length()); for (auto i: Ancestors) { m.Printf("\t%s\n", i->GetRev()); s += m; } LgiMsg(GetTree(), s, AppName, MB_OK); break; } LArray Top; for (auto c:Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) == 0) Top.Add(c); } if (Top.Length() != 1) { d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL); return false; } // Create the new branch... auto First = Ancestors.First(); LString Cmd; Cmd.Printf("update -r " LPrintfInt64, First->GetIndex()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } Cmd.Printf("branch \"%s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } // Commit it to get a revision point to rebase to Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode) { // Rebase the old tree to this point Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CommitListDirty = true; d->Log->Print("Finished rename.\n", _FL); }); }); }); }); }); break; } default: { LgiMsg(GetTree(), "Not impl for this VCS.", AppName); break; } } return true; } void VcFolder::GetVersion() { auto t = GetType(); switch (t) { case VcGit: case VcSvn: case VcHg: case VcCvs: StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal); break; case VcPending: break; default: OnCmdError(NULL, "No version control found."); break; } } bool VcFolder::ParseVersion(int Result, LString s, ParseParams *Params) { if (Result) return false; auto p = s.SplitDelimit(); switch (GetType()) { case VcGit: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Git version: %s\n", p[2].Get()); } else LAssert(0); break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Svn version: %s\n", p[2].Get()); } else LAssert(0); break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[GetType()] = Ver2Int(Ver); printf("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { #ifdef _DEBUG for (auto i : p) printf("i='%s'\n", i.Get()); #endif if (p.Length() > 1) { auto Ver = p[2]; ToolVersion[GetType()] = Ver2Int(Ver); printf("Cvs version: %s\n", Ver.Get()); } break; } default: break; } return false; } bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { if (Result) { d->Tabs->Value(1); OnCmdError(s, "Add file failed"); } else ClearError(); break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { auto p = LString(Path).RSplit(DIR_STR, 1); ParseParams *params = NULL; if (p.Length() >= 2) { if ((params = new ParseParams)) params->AltInitPath = p[0]; } LString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path); return StartCmd(a, &VcFolder::ParseAddFile, params); break; } default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params) { if (GetType() == VcSvn) { if (s.Find("Skipped ") >= 0) Result = 1; // Stupid svn... *sigh* } if (Result) { OnCmdError(s, "Error reverting changes."); } ListWorkingFolder(); return false; } bool VcFolder::Revert(LString::Array &Uris, const char *Revision) { if (Uris.Length() == 0) return false; switch (GetType()) { case VcGit: { LStringPipe p; p.Print("checkout"); for (auto u: Uris) { auto Path = GetFilePart(u); p.Print(" \"%s\"", Path.Get()); } auto a = p.NewGStr(); return StartCmd(a, &VcFolder::ParseRevert); break; } case VcHg: case VcSvn: { LStringPipe p; if (Revision) p.Print("up -r %s", Revision); else p.Print("revert"); for (auto u: Uris) { auto Path = GetFilePart(u); p.Print(" \"%s\"", Path.Get()); } auto a = p.NewGStr(); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcSvn: case VcHg: case VcCvs: default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path) { if (!Path) return false; switch (GetType()) { case VcGit: { LString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcSvn: case VcHg: case VcCvs: default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params) { new BlameUi(d, GetType(), s); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; LUri u(Path); switch (GetType()) { case VcGit: { LString a; a.Printf("-P blame \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcHg: { LString a; a.Printf("annotate -un \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcSvn: { LString a; a.Printf("blame \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; LString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { LTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); auto *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx) { LFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); LDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); LAssert(uri.Find("://") >= 0); // Is URI Uri.Set(uri); LAssert(Uri); Leaf = leaf; Folder = folder; Tmp = NULL; Item->Insert(this); if (Folder) { Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); } } VcLeaf::~VcLeaf() { Log.DeleteObjects(); } LString VcLeaf::Full() { LUri u = Uri; u += Leaf; return u.ToString(); } void VcLeaf::OnBrowse() { auto full = Full(); LList *Files = d->Files; Files->Empty(); LDirectory Dir; for (int b = Dir.First(full); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, NULL, true); if (f) { f->SetUri(LString("file://") + full); f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); if (Folder) Parent->FolderStatus(full, this); } void VcLeaf::AfterBrowse() { } VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree) { if (!Stricmp(Path, Full().Get())) return this; if (OpenTree) DoExpand(); VcLeaf *r = NULL; for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } void VcLeaf::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); Parent->ReadDir(this, Full()); } } void VcLeaf::OnExpand(bool b) { if (b) DoExpand(); } const char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return LTreeItem::Select(); } void VcLeaf::Select(bool b) { LTreeItem::Select(b); if (b) { d->Commits->RemoveAll(); OnBrowse(); ShowLog(); } } void VcLeaf::ShowLog() { if (Log.Length()) { d->Commits->RemoveAll(); Parent->DefaultFields(); Parent->UpdateColumns(); for (auto i: Log) d->Commits->Insert(i); } } void VcLeaf::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); s.AppendSeparator(); s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem("Terminal At", IDM_TERMINAL); int Cmd = s.Float(GetTree(), m - _ScrollPos()); switch (Cmd) { case IDM_LOG: { Parent->LogFile(Full()); break; } case IDM_BLAME: { Parent->Blame(Full()); break; } case IDM_BROWSE_FOLDER: { LBrowseToFile(Full()); break; } case IDM_TERMINAL: { TerminalAt(Full()); break; } } } } ///////////////////////////////////////////////////////////////////////////////////////// ProcessCallback::ProcessCallback(LString exe, LString args, LString localPath, LTextLog *log, LView *view, std::function callback) : Log(log), View(view), Callback(callback), LThread("ProcessCallback.Thread"), LSubProcess(exe, args) { SetInitFolder(localPath); if (Log) Log->Print("%s %s\n", exe.Get(), args.Get()); Run(); } int ProcessCallback::Main() { if (!Start()) { Ret.Out.Printf("Process failed with %i", GetErrorCode()); Callback(Ret); } else { while (IsRunning()) { auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } } auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } Ret.Code = GetExitValue(); } View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this); return 0; } void ProcessCallback::OnComplete() // Called in the GUI thread... { Callback(Ret); } diff --git a/Lvc/Src/VcFolder.h b/Lvc/Src/VcFolder.h --- a/Lvc/Src/VcFolder.h +++ b/Lvc/Src/VcFolder.h @@ -1,361 +1,360 @@ #ifndef _VcFolder_h_ #define _VcFolder_h_ #include "lgi/common/SubProcess.h" #include class VcLeaf; enum LoggingType { LogNone, // No output from cmd LogNormal, // Output appears as it's available LogSilo, // Output appears after cmd finished (keeps it non-interleaved with other log msgs) }; enum LvcError { ErrNone, ErrSubProcessFailed = GSUBPROCESS_ERROR, }; enum LvcStatus { StatusNone, StatusActive, StatusError, }; class ReaderThread : public LThread { VersionCtrl Vcs; LStream *Out; LAutoPtr Process; int FilterCount; int OnLine(char *s, ssize_t len); bool OnData(char *Buf, ssize_t &r); public: int Result; ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out); ~ReaderThread(); int Main(); }; extern int Ver2Int(LString v); extern int ToolVersion[VcMax]; extern int LstCmp(LListItem *a, LListItem *b, int Col); struct Result { int Code; LString Out; }; struct ProcessCallback : public LThread, public LSubProcess { Result Ret; LView *View = NULL; LTextLog *Log = NULL; std::function Callback; public: ProcessCallback(LString exe, LString args, LString localPath, LTextLog *log, LView *view, std::function callback); int Main(); void OnComplete(); }; struct VcBranch : public LString { bool Default; LColour Colour; LString Hash; VcBranch(LString name, LString hash = NULL) { Default = name.Equals("default") || name.Equals("trunk") || name.Equals("main"); Set(name); if (hash) Hash = hash; } }; struct SshParams { SshConnection *c; VcFolder *f; LString Exe, Args, Path, Output; VersionCtrl Vcs; ParseFn Parser; ParseParams *Params; int ExitCode; SshParams(SshConnection *con) : c(con) { f = NULL; Parser = NULL; Params = NULL; Vcs = VcNone; ExitCode = -1; } }; class VcFolder : public LTreeItem { friend class VcCommit; class Cmd : public LStream { LString::Array Context; LStringPipe Buf; public: LoggingType Logging; LStream *Log; LAutoPtr Rd; ParseFn PostOp; LAutoPtr Params; LvcError Err; Cmd(LString::Array &context, LoggingType logging, LStream *log) { Context = context; Logging = logging; Log = log; Err = ErrNone; } ~Cmd() { } LString GetBuf() { LString s = Buf.NewGStr(); if (Log && Logging == LogSilo) { LString m; m.Printf("=== %s ===\n\t%s %s\n", Context[0].Get(), Context[1].Get(), Context[2].Get()); Log->Write(m.Get(), m.Length()); auto Lines = s.Split("\n"); for (auto Ln : Lines) Log->Print("\t%s\n", Ln.Get()); } return s; } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { ssize_t Wr = Buf.Write(Ptr, Size, Flags); if (Log && Logging == LogNormal) Log->Write(Ptr, Size, Flags); if (Flags) Err = (LvcError) Flags; return Wr; } }; class UncommitedItem : public LListItem { AppPriv *d; public: UncommitedItem(AppPriv *priv) { d = priv; } void OnPaint(LItem::ItemPaintCtx &Ctx); void Select(bool b); }; AppPriv *d; VersionCtrl Type; LUri Uri; LString CurrentCommit, RepoUrl, VcCmd; int64 CurrentCommitIdx; LArray Log; LString CurrentBranch; LHashTbl,VcBranch*> Branches; LAutoPtr Uncommit; LString Cache, NewRev; bool CommitListDirty = 0; int Unpushed = 0, Unpulled = 0; LString CountCache; LTreeItem *Tmp = NULL; int CmdErrors = 0; LArray Fields; // Git specific LHashTbl,LString> GitNames; void AddGitName(LString Hash, LString Name); LString GetGitNames(LString Hash); static int CmdMaxThreads; static int CmdActiveThreads; struct GitCommit { LString::Array Files; LString Msg, Branch; ParseParams *Param; GitCommit() { Param = NULL; } }; LAutoPtr PostAdd; void GitAdd(); LArray Cmds; bool IsLogging, IsUpdate, IsFilesCmd, IsWorkingFld, IsCommit, IsUpdatingCounts; LvcStatus IsBranches, IsIdent; void Init(AppPriv *priv); const char *GetVcName(); bool StartCmd(const char *Args, ParseFn Parser = NULL, ParseParams *Params = NULL, LoggingType Logging = LogNone); bool RunCmd(const char *Args, LoggingType Logging, std::function Callback); void OnBranchesChange(); void OnCmdError(LString Output, const char *Msg); void ClearError(); VcFile *FindFile(const char *Path); void LinkParents(); void CurrentRev(std::function Callback); LColour BranchColour(const char *Name); void Empty(); bool ParseDiffs(LString s, LString Rev, bool IsWorking); bool ParseRevList(int Result, LString s, ParseParams *Params); bool ParseLog(int Result, LString s, ParseParams *Params); bool ParseInfo(int Result, LString s, ParseParams *Params); bool ParseFiles(int Result, LString s, ParseParams *Params); bool ParseWorking(int Result, LString s, ParseParams *Params); bool ParseUpdate(int Result, LString s, ParseParams *Params); bool ParseCommit(int Result, LString s, ParseParams *Params); bool ParseGitAdd(int Result, LString s, ParseParams *Params); bool ParsePush(int Result, LString s, ParseParams *Params); bool ParsePull(int Result, LString s, ParseParams *Params); bool ParseCounts(int Result, LString s, ParseParams *Params); bool ParseRevert(int Result, LString s, ParseParams *Params); bool ParseResolve(int Result, LString s, ParseParams *Params); bool ParseBlame(int Result, LString s, ParseParams *Params); bool ParseSaveAs(int Result, LString s, ParseParams *Params); bool ParseBranches(int Result, LString s, ParseParams *Params); bool ParseStatus(int Result, LString s, ParseParams *Params); bool ParseAddFile(int Result, LString s, ParseParams *Params); bool ParseVersion(int Result, LString s, ParseParams *Params); bool ParseClean(int Result, LString s, ParseParams *Params); bool ParseDiff(int Result, LString s, ParseParams *Params); bool ParseMerge(int Result, LString s, ParseParams *Params); bool ParseCountToTip(int Result, LString s, ParseParams *Params); bool ParseUpdateSubs(int Result, LString s, ParseParams *Params); bool ParseRemoteFind(int Result, LString s, ParseParams *Params); bool ParseStartBranch(int Result, LString s, ParseParams *Params); void DoExpand(); public: VcFolder(AppPriv *priv, const char *uri); VcFolder(AppPriv *priv, LXmlTag *t); ~VcFolder(); VersionCtrl GetType(); AppPriv *GetPriv() { return d; } const char *LocalPath(); LUri GetUri() { return Uri; } VcLeaf *FindLeaf(const char *Path, bool OpenTree); void DefaultFields(); void UpdateColumns(); const char *GetText(int Col); LArray &GetFields() { return Fields; } bool Serialize(LXmlTag *t, bool Write); LXmlTag *Save(); void Select(bool b); void ListCommit(VcCommit *c); void ListWorkingFolder(); void FolderStatus(const char *Path = NULL, VcLeaf *Notify = NULL); void Commit(const char *Msg, const char *Branch, bool AndPush); void StartBranch(const char *BranchName, const char *OnCreated = NULL); void Push(bool NewBranchOk = false); void Pull(int AndUpdate = -1, LoggingType Logging = LogNormal); void Clean(); bool Revert(LString::Array &uris, const char *Revision = NULL); bool Resolve(const char *Path); bool AddFile(const char *Path, bool AsBinary = true); bool Blame(const char *Path); bool SaveFileAs(const char *Path, const char *Revision); void ReadDir(LTreeItem *Parent, const char *Uri); void SetEol(const char *Path, int Type); void GetVersion(); void Diff(VcFile *file); void DiffRange(const char *FromRev, const char *ToRev); void MergeToLocal(LString Rev); bool RenameBranch(LString NewName, LArray &Revs); void Refresh(); bool GetBranches(ParseParams *Params = NULL); void GetCurrentRevision(ParseParams *Params = NULL); void CountToTip(); bool UpdateSubs(); // Clone/checkout any sub-repositries. void LogFile(const char *Path); LString GetFilePart(const char *uri); - LString GetFilter(); void FilterCurrentFiles(); void OnPulse(); void OnUpdate(const char *Rev); void OnMouseClick(LMouse &m); void OnRemove(); void OnExpand(bool b); void OnVcsType(LString errorMsg); void OnSshCmd(SshParams *p); }; class VcLeaf : public LTreeItem { AppPriv *d; VcFolder *Parent; bool Folder; LUri Uri; LString Leaf; LTreeItem *Tmp; void DoExpand(); public: LArray Log; VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder); ~VcLeaf(); LString Full(); VcLeaf *FindLeaf(const char *Path, bool OpenTree); void OnBrowse(); void AfterBrowse(); void OnExpand(bool b); const char *GetText(int Col); int GetImage(int Flags); int Compare(VcLeaf *b); bool Select(); void Select(bool b); void OnMouseClick(LMouse &m); void ShowLog(); }; #endif diff --git a/ResourceEditor/Code/LgiResApp.cpp b/ResourceEditor/Code/LgiResApp.cpp --- a/ResourceEditor/Code/LgiResApp.cpp +++ b/ResourceEditor/Code/LgiResApp.cpp @@ -1,4656 +1,4656 @@ /* ** 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 "resdefs.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" 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]; sprintf(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); 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); } delete dlg; }); break; } case IDM_IMPORT: { auto Select = new LFileSelect(Obj->App()); Select->Type("Text", "*.txt"); Select->Open([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(dlg->Name(), O_READ)) { SerialiseContext Ctx; Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type()); if (Res) { // TODO // Res->Read(); } } else { LgiMsg(Obj->App(), "Couldn't open file for reading."); } } delete dlg; }); break; } case IDM_IMPORT_WIN32: { /* List l; if (ImportWin32Dialogs(l, MainWnd)) { for (ResDialog *r = l.First(); r; r = l.Next()) { Obj->App()->InsertObject(TYPE_DIALOG, r); } } */ break; } default: { Obj->OnCommand(Cmd); break; } } } } } ////////////////////////////////////////////////////////////////////////////// FieldView::FieldView(AppWnd *app) : Fields(NextId, true) { NextId = 100; App = app; Source = 0; Ignore = true; SetTabStop(true); Sunken(true); #ifdef WIN32 SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } FieldView::~FieldView() { } void FieldView::Serialize(bool Write) { if (!Source) return; Ignore = !Write; Fields.SetMode(Write ? FieldTree::UiToObj : FieldTree::ObjToUi); Fields.SetView(this); Source->Serialize(Fields); /* for (DataDlgField *f=Fields.First(); f; f=Fields.Next()) { LViewI *v; if (GetViewById(f->GetCtrl(), v)) { switch (f->GetType()) { case DATA_STR: { if (Write) // Ctrl -> Options { char *s = v->Name(); Options->Set(f->GetOption(), s); } else // Options -> Ctrl { char *s = 0; Options->Get(f->GetOption(), s); v->Name(s?s:(char*)""); } break; } case DATA_BOOL: case DATA_INT: { if (Write) // Ctrl -> Options { char *s = v->Name(); if (s && (s = strchr(s, '\''))) { s++; char *e = strchr(s, '\''); int i = 0; if (e - s == 4) { memcpy(&i, s, 4); i = LgiSwap32(i); Options->Set(f->GetOption(), i); } } else { int i = v->Value(); Options->Set(f->GetOption(), i); } } else // Options -> Ctrl { int i = 0; Options->Get(f->GetOption(), i); if (i != -1 && (i & 0xff000000) != 0) { char m[8]; i = LgiSwap32(i); sprintf(m, "'%04.4s'", &i); v->Name(m); } else { v->Value(i); } } break; } case DATA_FLOAT: case DATA_PASSWORD: case DATA_STR_SYSTEM: default: { LAssert(0); break; } } } else LAssert(0); } */ Ignore = false; } class TextViewEdit : public LTextView3 { public: bool Multiline; TextViewEdit( int Id, int x, int y, int cx, int cy, LFontType *FontInfo = 0) : LTextView3(Id, x, y, cx, cy, FontInfo) { Multiline = false; #ifdef WIN32 SetDlgCode(DLGC_WANTARROWS | DLGC_WANTCHARS); #endif } bool OnKey(LKey &k) { if (!Multiline && (k.c16 == '\t' || k.c16 == LK_RETURN)) { return false; } return LTextView3::OnKey(k); } }; class Hr : public LView { public: Hr(int x1, int y, int x2) { LRect r(x1, y, x2, y+1); SetPos(r); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); LThinBorder(pDC, c, DefaultSunkenEdge); } bool OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min) Inf.Height.Min = Inf.Height.Max = 2; else Inf.Width.Min = Inf.Width.Max = -1; return true; } }; void FieldView::OnDelete(FieldSource *s) { if (Source != NULL && Source == s) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = NULL; } } void FieldView::OnSelect(FieldSource *s) { Ignore = true; OnDelete(Source); if (Source) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = 0; } if (s) { // Add new fields Source = s; Source->_FieldView = AddDispatch(); if (Source->GetFields(Fields)) { LFontType Sys; Sys.GetSystemFont("System"); LTableLayout *t = new LTableLayout(IDC_TABLE); int Row = 0; LLayoutCell *Cell; LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++, Row++) { FieldTree::Field *c = (*b)[n]; switch (c->Type) { case DATA_STR: case DATA_FLOAT: case DATA_INT: case DATA_FILENAME: { Cell = t->GetCell(0, Row); Cell->VerticalAlign(LCss::VerticalMiddle); Cell->Add(new LTextLabel(-1, 0, 0, -1, -1, c->Label)); TextViewEdit *Tv; Cell = t->GetCell(1, Row, true, c->Type == DATA_FILENAME ? 1 : 2); Cell->Add(Tv = new TextViewEdit(c->Id, 0, 0, 100, 20, &Sys)); if (Tv) { Tv->Multiline = c->Multiline; Tv->GetCss(true)->Height(LCss::Len(LCss::LenPx, c->Multiline ? LSysFont->GetHeight() * 8 : LSysFont->GetHeight() + 8)); Tv->SetWrapType(TEXTED_WRAP_NONE); Tv->Sunken(true); } if (c->Type == DATA_FILENAME) { Cell = t->GetCell(2, Row); Cell->Add(new LButton(-c->Id, 0, 0, 21, 21, "...")); } break; } case DATA_BOOL: { Cell = t->GetCell(1, Row, true, 2); Cell->Add(new LCheckBox(c->Id, 0, 0, -1, -1, c->Label)); break; } default: LAssert(!"Impl me."); break; } } if (i < a.Length() - 1) { Cell = t->GetCell(0, Row++, true, 3); Cell->Add(new Hr(0, 0, X()-1)); } } AddView(t); OnPosChange(); AttachChildren(); Invalidate(); } Serialize(false); Ignore = false; } } void FieldView::OnPosChange() { LRect c = GetClient(); c.Inset(6, 6); LViewI *v; if (GetViewById(IDC_TABLE, v)) v->SetPos(c); } LMessage::Result FieldView::OnEvent(LMessage *m) { switch (m->Msg()) { case M_OBJECT_CHANGED: { FieldSource *Src = (FieldSource*)m->A(); if (Src == Source) { Fields.SetMode(FieldTree::ObjToUi); Fields.SetView(this); Serialize(false); } else LAssert(0); break; } } return LLayout::OnEvent(m); } int FieldView::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ignore) { LTextView3 *Tv = dynamic_cast(Ctrl); if (Tv && n.Type == LNotifyCursorChanged) { return 0; } LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++) { FieldTree::Field *c = (*b)[n]; if (c->Id == Ctrl->GetId()) { // Write the value back to the objects Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); return 0; } else if (c->Id == -Ctrl->GetId()) { auto s = new LFileSelect(this); s->Open([&](auto dlg, auto status) { if (status) { auto File = App->GetCurFile(); if (File) { LFile::Path p = File; p--; auto Rel = LMakeRelativePath(p, dlg->Name()); if (Rel) SetCtrlName(c->Id, Rel); else SetCtrlName(c->Id, dlg->Name()); } else SetCtrlName(c->Id, dlg->Name()); Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); } delete dlg; }); } } } } return 0; } void FieldView::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } ////////////////////////////////////////////////////////////////////////////// ObjContainer::ObjContainer(AppWnd *w) : LTree(100, 0, 0, 100, 100, "LgiResObjTree") { Window = w; Sunken(true); Insert(Style = new ObjTreeItem( new ResFolder(Window, -TYPE_CSS))); Insert(Dialogs = new ObjTreeItem( new ResFolder(Window, -TYPE_DIALOG))); Insert(Strings = new ObjTreeItem( new ResFolder(Window, -TYPE_STRING))); Insert(Menus = new ObjTreeItem( new ResFolder(Window, -TYPE_MENU))); const char *IconFile = "_icons.gif"; auto f = LFindFile(IconFile); if (f) { Images = LLoadImageList(f, 16, 16); if (Images) SetImageList(Images, false); else LgiTrace("%s:%i - failed to load '%s'\n", _FL, IconFile); } } ObjContainer::~ObjContainer() { DeleteObj(Images); } bool ObjContainer::AppendChildren(ObjTreeItem *Res, List &Lst) { bool Status = true; if (Res) { LTreeItem *Item = Res->GetChild(); while (Item) { ObjTreeItem *i = dynamic_cast(Item); if (i) Lst.Insert(i->GetObj()); else Status = false; Item = Item->GetNext(); } } return Status; } Resource *ObjContainer::CurrentResource() { ObjTreeItem *Item = dynamic_cast(Selection()); if (!Item) return NULL; return Item->GetObj(); } bool ObjContainer::ListObjects(List &Lst) { bool Status = AppendChildren(Style, Lst); Status &= AppendChildren(Dialogs, Lst); Status &= AppendChildren(Strings, Lst); Status &= AppendChildren(Menus, Lst); return Status; } ////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 int Icon = IDI_ICON1; #else const char *Icon = "icon64.png"; #endif AppWnd::AppWnd() : LDocApp(AppName, Icon) { LastRes = 0; Fields = 0; ViewMenu = 0; ContentView = NULL; VBox = NULL; HBox = NULL; ShortCuts = 0; CurLang = -1; ShowLanguages.Add("en", true); if (_Create()) { LVariant Langs; if (GetOptions()->GetValue(OPT_ShowLanguages, Langs)) { ShowLanguages.Empty(); LToken L(Langs.Str(), ","); for (int i=0; iEmpty(); _Destroy(); } void AppWnd::OnCreate() { if (_LoadMenu("IDM_MENU")) { if (_FileMenu) { int n = 6; _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Import Win32 Script", IDM_IMPORT_WIN32, true, n++); _FileMenu->AppendItem("Import LgiRes Language", IDM_IMPORT_LANG, true, n++); _FileMenu->AppendItem("Compare To File...", IDM_COMPARE, true, n++); _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Properties", IDM_PROPERTIES, true, n++); } ViewMenu = Menu->FindSubMenu(IDM_VIEW); LAssert(ViewMenu); } else LgiTrace("%s:%i - _LoadMenu failed.\n", _FL); Status = 0; StatusInfo[0] = StatusInfo[1] = 0; HBox = new LBox(IDC_HBOX); if (HBox) { HBox->GetCss(true)->Padding("5px"); VBox = new LBox(IDC_VBOX, true); if (VBox) { HBox->AddView(VBox); VBox->AddView(Objs = new ObjContainer(this)); if (Objs) { Objs->AskImage(true); Objs->AskText(true); } VBox->AddView(Fields = new FieldView(this)); VBox->Value(200); } HBox->Value(240); HBox->Attach(this); } DropTarget(true); LString Open; if (LAppInst->GetOption("o", Open)) LoadLgi(Open); } void AppWnd::OnLanguagesChange(LLanguageId Lang, bool Add, bool Update) { bool Change = false; if (Lang) { // Update the list.... bool Has = false; for (int i=0; iId, Lang) == 0) { Has = true; if (!Add) { Languages.DeleteAt(i); Change = true; } break; } } if (Add && !Has) { Change = true; Languages.Add(LFindLang(Lang)); } } // Update the menu... if (ViewMenu && (Change || Update)) { // Remove existing language menu items while (ViewMenu->RemoveItem(2)); // Add new ones int n = 0; for (int i=0; iAppendItem(Lang->Name, IDM_LANG_BASE + n, true); if (Item) { if (CurLang == i) { Item->Checked(true); } } } } } } bool AppWnd::ShowLang(LLanguageId Lang) { return ShowLanguages.Find(Lang) != 0; } void AppWnd::ShowLang(LLanguageId Lang, bool Show) { // Apply change if (Show) { OnLanguagesChange(Lang, true); ShowLanguages.Add(Lang, true); } else { ShowLanguages.Delete(Lang); } // Store the setting for next time LStringPipe p; // const char *L; // for (bool i = ShowLanguages.First(&L); i; i = ShowLanguages.Next(&L)) for (auto i : ShowLanguages) { if (p.GetSize()) p.Push(","); p.Push(i.key); } char *Langs = p.NewStr(); if (Langs) { LVariant v; GetOptions()->SetValue(OPT_ShowLanguages, v = Langs); DeleteArray(Langs); } // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } } LLanguage *AppWnd::GetCurLang() { if (CurLang >= 0 && CurLang < Languages.Length()) return Languages[CurLang]; return LFindLang("en"); } void AppWnd::SetCurLang(LLanguage *L) { for (int i=0; iId == L->Id) { // Set new current CurLang = i; // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } break; } } } LArray *AppWnd::GetLanguages() { return &Languages; } class Test : public LView { COLOUR c; public: Test(COLOUR col, int x1, int y1, int x2, int y2) { c = col; LRect r(x1, y1, x2, y2); SetPos(r); _BorderSize = 1; Sunken(true); } void OnPaint(LSurface *pDC) { pDC->Colour(c, 24); pDC->Rectangle(); } }; LMessage::Result AppWnd::OnEvent(LMessage *m) { LMru::OnEvent(m); switch (m->Msg()) { case M_CHANGE: { LAutoPtr note((LNotification*)m->B()); return OnNotify((LViewI*) m->A(), *note); } case M_DESCRIBE: { char *Text = (char*) m->A(); if (Text) { SetStatusText(Text, STATUS_NORMAL); } break; } } return LWindow::OnEvent(m); } #include "lgi/common/Token.h" 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); 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); } 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); } } 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); } 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); } } 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) { } 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; } } } 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) { sprintf(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) { sprintf(s, "Different Attr Value: '%s' <-> '%s'", a1->GetValue(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); sprintf(s, "%s.%s", TagName(t1), a1->GetName()); i->SetText(s, 1); Lst->Insert(i); } } a.Delete(a2->GetName()); } else { sprintf(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; sprintf(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]) { sprintf(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]; sprintf(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]; sprintf(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; } bool AppWnd::SaveFile(const char *FileName) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { return SaveLgi(FileName); } else if (stristr(FileName, ".rc")) { } return 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; } bool AppWnd::LoadLgi(const char *FileName) { bool Status = false; Empty(); if (FileName) { // ResFileFormat Format = GetFormat(FileName); LFile f; if (f.Open(FileName, O_READ)) { LProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("Tags"); LXmlTag *Root = new LXmlTag; if (Root) { // convert file to Xml objects LXmlTree Xml(0); Progress.SetDescription("Lexing..."); if (Xml.Read(Root, &f, 0)) { Progress.SetRange(Root->Children.Length()); // convert Xml list into objects int i=0; DoEvery Timer(500); SerialiseContext Ctx; for (auto t: Root->Children) { if (Timer.DoNow()) { Progress.Value(Root->Children.IndexOf(t)); LYield(); } 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); } i++; } Ctx.PostLoad(this); SortDialogs(); TestLgi(); // Scan for languages and update the view lang menu Languages.Length(0); LHashTbl, LLanguage*> Langs; if (ViewMenu) { // Remove existing language menu items while (ViewMenu->RemoveItem(1)); ViewMenu->AppendSeparator(); // 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); } } } } } // 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++; } if (Languages.Length() == 0) { ViewMenu->AppendItem("(none)", -1, false); } } Status = true; } else { LgiMsg(this, "Xml read failed: %s", AppName, MB_OK, Xml.GetErrorMsg()); } DeleteObj(Root); } } } return Status; } 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]; sprintf(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.NewGStr(); LAutoString OldDefsContent(LReadTextFile(DefsName)); - if (!Stricmp(DefsContent.Get(), OldDefsContent.Get())) + 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 #include "lgi/common/Token.h" 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, 300, 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("Control", 150); Visible(true); } } ShortCutView::~ShortCutView() { App->OnCloseView(this); } void FindShortCuts(LList *Out, LViewI *In) { for (LViewI *c: In->IterateViews()) { ResDialogCtrl *rdc = dynamic_cast(c); if (!rdc || !rdc->GetStr()) continue; char *n = rdc->GetStr()->Get(); if (n) { char *a = strchr(n, '&'); if (a && a[1] != '&') { LListItem *li = new LListItem; LString s(++a, 1); LString id; id.Printf("%i", rdc->GetStr()->GetRef()); li->SetText(s.Upper(), 0); li->SetText(id, 1); li->SetText(rdc->GetClass(), 2); 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: { LListItem *li = Lst->GetSelected(); if (li) { LString s = li->GetText(1); ResDialogCtrl *c = (ResDialogCtrl*) li->_UserPtr; if (c) App->GotoObject(c->GetStr(), NULL, c->GetDlg(), NULL, c); } break; } } } return LWindow::OnNotify(Ctrl, n); } void ShortCutView::OnDialogChange(ResDialog *Dlg) { Lst->Empty(); if (!Dlg) return; FindShortCuts(Lst, Dlg); Lst->Sort(NULL); } 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/docs/scripting/index.html b/docs/scripting/index.html --- a/docs/scripting/index.html +++ b/docs/scripting/index.html @@ -1,55 +1,43 @@ Lgi Scripting

Lgi Scripting Language

Lgi's scripting language is loosely based on the "C" syntax, but without a number of the more difficult to implement parts of the language. Support for the language is broken into 2 parts; the compiler, which converts source code into binary byte code, and the virtual machine, which executes the byte code in the context of the application.

None of the parts of the compiler / VM system are particularly fast or modern, and if I my choices again I would try harder to intergrate Python than write some weird little language. The compiler is recursive, and the VM is based around a big switch statement. Both quite old concepts.

That said it does work ok, and it acheives some flexibility in the various host applications. It's also very small, added very little overhead to the host and also comes with direct support for using native Lgi types, including support for DOM tree's using the '.' operator.

There is a default library of methods that are normally included with the VM in the host application which provide some support for string manipulation and other utilities.

The main applications that host Lgi's scripting language are i.Mage (for drawing procedural images) and - i.Scribe (for filters, custom menu commands and + Scribe (for filters, custom menu commands and utils/tools). - -

Source Documentation

-
\ No newline at end of file diff --git a/docs/scripting/library.html b/docs/scripting/library.html --- a/docs/scripting/library.html +++ b/docs/scripting/library.html @@ -1,1335 +1,1357 @@ Lgi Scripting Library

Lgi Scripting Library

These are the built in methods for the Lgi scripting language:
Strings Containers File System Dates and Time Bitmaps User Interface General
Global functions: LoadString
FormatSize
Sprintf
Print
ToString
New
Delete
ReadTextFile
WriteTextFile
SelectFiles
SelectFolder
ListFiles
DeleteFile

PathExists
PathJoin
PathSep
Sleep
ClockTick
Now
CreateSurface
+ ColourSpaceToString
+ StringToColourSpace
LoadDialog
MessageDlg
GetInputDlg
GetViewById
Execute
System
OsName
OsVersion
CurrentScript
Assert
Throw
DebuggerEnabled
Object Members: obj.Find
obj.Split
obj.Join
obj.Upper
obj.Lower
obj.Sub
obj.Strip
obj.Add
obj.HasKey
obj.Delete
obj.Sort
obj.Open
obj.Read
obj.Write
obj.Pos
obj.Close
obj.Year
obj.Month
obj.Day
obj.Hour
obj.Minute
obj.Second
obj.Milli

obj.Date
obj.Time
obj.DateTime
obj.Timestamp
obj.x
obj.y
obj.Bits
obj.ColourSpace
obj.Palette
obj.Load
obj.Save
obj.Handle
obj.Id
obj.Name
obj.Value
obj.Enabled
obj.Foreground
obj.Background
obj.Type
obj.Length

string LoadString(id);

Loads a string from the resource file. The strings have to be stored in a Lgi resource file and loaded at runtime by the current application. There is a graphical editor for the resource files.

Arguments:
  • id - the string's id as an integer.

Returns:
  • A string or NULL on failure.

Example:
c = LoadString(186);
 Print(c, "\n");
Enter offset:

string FormatSize(bytes);

Formats a number of bytes in KB, MB or GB as appropriate.

Arguments:
  • bytes - the size to format.

Returns:
  • A string containing the size description.

Example:
c = FormatSize(345674211);
 Print(c, "\n");
329.66 M

string Sprintf(format[, args, ...]);

Formats a string. Not available on the mac platform.

Arguments:
  • format - the format of the output string, same as C's printf.
  • [optional]args - any arguments that are required to be formatted.

Returns:
  • A formatted string.

Example:
c = Sprintf("Format: %i, %i, %s, 0x%x\n", 345, 11, "StringArg", 0x1234fa);
 Print(c);
Format: 345, 11, StringArg, 0x1234af

void Print(item1[, item2, ...]);

Prints variables on the console, if available. No new line is explicitly printed, you have to add "\n" to your arguments to move to the next line.

Arguments:
  • itemN - The object to print. Non string objects will be converted to string if possible.

Returns:
  • Nothing.

String ToString([items, ...]);

Converts all the arguments to strings.

Arguments:
  • itemN - And object to print.

Returns:
  • String.

int obj.Find(sub_string[, start[, end]]);

Finds a the position of a sub-string.

Arguments:
  • sub_string - The string to find. The search is case sensitive.
  • [optional] start - The start index of the area to search.
  • [optional] end - The end index of the area to search.

Returns:
  • The location of the sub-string or -1 if not found.

Example:
c = "A string to search";
 Print(c.Find("to"), "\n");
 Print(c.Find("missing"), "\n");
9
 -1

list obj.Split(separator[, max_split]);

Splits a string into parts based on a separator.

Arguments:
  • separator - The string to use as a separator between the parts.
  • [optional] max_splits - The maximum number of splits to make.

Returns:
  • A list of strings. Could be empty if the input is also empty.

Example:
c = "123, 456, 879";
 p = c.split(",");
 for (i=0; i<p.Length; i++)
 {
     Print("[", i, "]='", p[i].Strip(), "'\n");
 }
[0]='123'
 [1]='456'
 [2]='879'

string obj.Join(list);

Joins a list of objects into a single string separated by the 'obj' string.

Arguments:
  • list - The objects to be joined.

Returns:
  • A string concatenation of all the source objects.

Example:
c = New("list");
 c.Add(123);
 c.Add(456);
 c.Add(789);
 p = ", ";
 r = p.Join(c);
 Print(r, "\n");
123, 456, 789

string obj.Upper();

Makes an upper case version of 'obj'.

Returns:
  • An upper case string.

Example:
c = "A string";
 Print(c.Upper(), "\n");
A STRING

string obj.Lower();

Makes a lower case version of 'obj'.

Returns:
  • An lower case string.

Example:
c = "A string";
 Print(c.Lower(), "\n");
a string

string obj.Sub(start[, end]);

Returns part of the string 'obj'.

Arguments:
  • start - The start of the sub-string.
  • [optional] end - The end of the sub-string. If not supplied the end of the source string is used.

Returns:
  • A string.

Example:
c = "A string";
 Print(c.Sub(2, 5), "\n");
 Print(c.Sub(4), "\n");
str
 ring

string obj.Strip([delimiters]);

Strips the specified characters off the start and end of the string.

Arguments:
  • [optional] delimiters - The characters to strip, defaults to whitespace (' \t\r\n').

Returns:
  • A string.

Example:
c = "[A string]";
 Print(c.Strip("[]"), "\n");
A String

object New(object);

Creates a new object.

Arguments:
  • object - the name of the object. Can be a built in type of a custom defined type.
    • ### - A number create a binary block of the specified number of bytes.
    • "List" - creates a list.
    • "HashTable" - creates a hash table.
    • "Surface" - creates a memory surface (bitmap) to draw on.
    • "File" - creates a file object.
    • "DateTime" - creates a date/time object.
    • <custom_type_name> - Some custom type defined in script.

Returns:
  • The new object or NULL on failure.

Example:
c = New("DateTime");
 c.Date = "1/4/2014";
 Print(c.Type(), ": ", c, "\n");
DateTime: 1/04/2014 12:00:00a

void Delete(object);

Will set any object to NULL. Must be used with custom types before they go out of scope otherwise they will leak memory. But also works on any other type of object, like lists, hashtables or basic strings and ints.

Arguments:
  • object - the object to delete.

Returns:
  • Nothing.

void obj.Add(object[, key]);

Adds 'object1' to a container. Depending on the object type:
  • List: the key is an optional integer index. If not supplied the object is appended to the end.
  • HashTable: the key is NOT optional and specifies the key under which to store the object.

Arguments:
  • object - the object to add.
  • key - the location to store the object.

Returns:
  • Nothing.

Example:
c = New("list");
 c.Add("first");
 c.Add("second");
 Print(c, "\n");
 Print(c.Length, "\n");
{first, second}
 2

bool obj.HasKey(key);

Returns true of the container has something stored at 'key':
Arguments:
  • key - the location to store the object.
    • List: the key is an integer index.
    • HashTable: the key is a string.

Returns:
  • TRUE if the object exists.

Example:
c = New("hashtable");
 c.Add(23, "first");
 c.Add(345, "second");
 Print(c.HasKey("asd"), "\n");
 Print(c.HasKey("first"), "\n");
0
 1

void obj.Delete(key);

Removes and deletes an object from the container

Arguments:
  • key - the location of the object:
    • List: the key is an integer index. If it's out of range, nothing happens.
    • HashTable: the key is string. If the key isn't present nothing happens.

Returns:
  • Nothing.

Example:
c = New("hashtable");
 c.Add(123, "first");
 c.Add(456, "second");
 Print(c.Length, "\n");
 c.Delete("first");
 Print(c.Length, "\n");
 
2
 1

void obj.Sort(key);

Sorts a list. Not relevant for a HashTable.

Arguments:
    • If the list contains strings or integers, not parameter is needed.
    • If the list contains object references, then a string defining which field of the object to sort on needs to be provided.

Returns:
  • Nothing.

Example:
c = New("list");
 c.Add("first");
 c.Add("second");
 c.Sort();
 

string ReadTextFile(filename);

Returns the contents of a text file.

Arguments:
  • filename - the name of the file

Returns:
  • the contents of the file as a string.

bool WriteTextFile(filename, data);

Writes the contents of a variable to a text file.

Arguments:
  • filename - the name of the file
  • data - the date to write, can either be a string or a binary object

Returns:
  • non-zero on succes, zero on failure.

list SelectFiles(parent_wnd[, file_types[, initial_dir[, multi_select[, save_as]]]]);

Shows a file select dialog.

Arguments:
  • parent_wnd - the handle of the parent window.
  • [optional] file_types - the types of files to select, e.g. "*.gif;*.png;*.jpg".
  • [optional] initial_dir - the initial folder to select from.
  • [optional] multi_select - TRUE if multiple file can be selected.
  • [optional] save_as - TRUE if dialog should be for saving a file.

Returns:
  • list of filenames.

Example:
files = SelectFiles(Parent, "*.png", "C:\\Users\\matthew\\Desktop", 1);
 if (files)
 {
     for (i=0; i<files.Length; i++)
     {
         Print(files[i], "\n");
     }
 }
C:\Users\matthew\Desktop\2channel.png
 C:\Users\matthew\Desktop\19.Poster_1.png
 C:\Users\matthew\Desktop\payment.png

list SelectFolder(parent_wnd[, initial_dir]);

Shows a folder select dialog.

Arguments:
  • parent_wnd - the handle of the parent window.
  • [optional] initial_dir - the initial folder to select from.

Returns:
  • String FolderPath.

list ListFiles(folder_path[, pattern]);

Lists files in a folder.

Arguments:
  • folder_path - the folder to list.
  • [optional] pattern - the files to list, e.g. "*.gif".

Returns:
  • list of file descriptors. You can access relavant meta-data using the fields:
    • Name: the file's name.
    • Length: the size in bytes.
    • Modified: the date modified.
    • Folder: True if the entry is a folder.

  • Example:
    files = ListFiles(".");
     if (files)
     {
         for (i=0; i<files.Length; i++)
         {
             Print(files[i].Name, " is ", files[i].Length, " bytes\n");
         }
     }
    FileSystem.asm is 1080 bytes
     FileSystem.script is 936 bytes
     Object.script is 1320 bytes
     Strings.script is 1440 bytes
     System.script is 837 bytes

bool DeleteFile(path);

Deletes a file.

Arguments:
  • path - The path to the file to delete.

Returns:
  • True if the file is deleted.

string CurrentScript();

Returns:
  • the current script file and it's path.

bool Assert(value[, msg]);

Checks that 'value' is non-zero, if it is zero, it throws an exception with 'msg' if supplied.

Arguments:
  • value - The value to check.
  • msg - [Optional] the message to pass to Throw.

Returns:
  • Bool - true if value is non-zero.

void Throw([msg]);

Throws an exception, optionally with 'msg' as the message.

Arguments:
  • msg - [Optional] the message to display in the debugger.

Returns:
  • None.

void DebuggerEnabled(enabled);

Turns the debugger on or off.

Arguments:
  • enabled - True if you want the debugger to appear on an exception. If false the script will just exit at the point the exception happened, returning "false" to the caller.

Returns:
  • None.

int PathExists(path);

Queries the file system to see if a path exists as a file or folder.

Arguments:
  • path - The path to the query.

Returns:
  • 0 - If the path doesn't exist.
  • 1 - If the path is a file.
  • 2 - If the path is a folder.

string PathJoin(path1[, path2[, ...]]);

Joins a whole lot of path fragments together, evaluating relative parts as needed.

Arguments:
  • path1 - The base path.
  • path2 - A relative path segment.

Returns:
  • The complete normalised path.

string PathSep();

Returns:
  • The path separator character for the current platform. ("\" on windows, "/" otherwise)

bool obj.Open(path[, mode]);

- Opens a file. + Opens a file. Typically this follows creating a file object with New and once the file + operations are done, use Delete to clean up the memory.
+
+ Also of note: to get or change the size of the file use the Length member.

Arguments:
  • path - The path to the file to open.
  • [optional] mode - Type of access:
    • "r" - Read access (the default).
    • "w" - Write access.
    • "rw" - Read and write access.

Returns:
  • True if the file is openned.

object obj.Read(length[, type]);

Reads content from a file at the current location.

Arguments:
  • length - The bytes to read.
  • type - The type of the data:
    • 0: string data (the default).
    • 1: integer data.

Returns:
  • The data read as the indicated type, or NULL if the read failed.

int obj.Write(object[, length]);

Writes the object to the file.

Arguments:
  • object - the object to write.
  • length - [optional] number of bytes to write.
    If specified and the object is:
    • an integer: valid values are 1, 2, 4, or 8 up to the maximum size of the actual variable.
    • a string: any value from 1 through to the length of the string including the NULL in bytes (not characters).
    If not specified and the object is:
    • an integer: all the integer's bytes are written in the native order of the CPU (little-endian for supported platforms). For GV_INT32 4 bytes are written, for GV_INT64 8 bytes are written.
    • a string: all the string's bytes up to, but not including, the NULL.

Returns:
  • The bytes written.

int obj.Pos([new_position]);

Gets or sets the current file position.

Arguments:
  • [optional] new_position - A new file position.

Returns:
  • The current file position.

void obj.Close();

Closes a file.

Returns:
  • Nothing.

void Sleep(ms);

Pauses the current thread.

Arguments:
  • ms - The number of milliseconds to pause for.

Returns:
  • Nothing.

int64 ClockTick();

Returns the current clock tick.

Arguments:
  • None.

Returns:
  • A 64bit clock time from an unknown epoch, in milliseconds. Useful for timing things.

datetime Now();

Returns the current date time.

Arguments:
  • None.

Returns:
  • A GDateTime variant set to the current date and time.

int obj.Year;

Sets or gets the year part of a date/time object.

int obj.Month;

Sets or gets the month part of a date/time object.

int obj.Day;

Sets or gets the day part of a date/time object.

int obj.Hour;

Sets or gets the hour part of a date/time object.

int obj.Minute;

Sets or gets the minute part of a date/time object.

int obj.Second;

Sets or gets the second part of a date/time object.

int obj.Milli;

Sets or gets the millisecond part of a date/time object.

int64 obj.Timestamp;

Sets or gets the int64 version of a date/time object. This is compatible with a range of operating system APIs.

string obj.Date;

Sets or gets the date part of the object. Typically this is in the form 'day/month/year' in the current locale of the operating system.

string obj.Time;

Sets or gets the time part of the object. Typically this is in the form 'hour:minute:second' in the current locale of the operating system.

string obj.DateTime;

Sets or gets both the date and time parts of the object as a string.

object CreateSurface(x, y[, pixel_type]);

Creates a bitmap in memory with the specified dimensions and bit depth or colour space.

Arguments:
  • x - The width in pixels.
  • y - The height in pixels.
  • [optional] pixel_type - The type of pixel.
    The default will be the same as the screen if you don't specify it at all. Otherwise you can use:
    • an integer: which specifies the bits per pixel.
      The exact colour space will be determined to be one of the System##BitColourSpace values.
    • a string: which specifies the exact colour space.
      - Possible values are taken from the GColourSpace enum in Lgi but in string form, ie: + Possible values are taken from the LColourSpace enum in Lgi but in string form, ie:
      • "CsIndex8" - Creates a 8 bit indexed image with a palette.
      • "CsRgb16" - An rgb image with 5 red, 6 green and 5 blue bits per pixel.
      • "CsRgb24" - An rgb image with 8 bits per component.
      • "CsRgba32" - An rgba image with 8 bits per component.
      Some special values are available for the supported system colour spaces:
      • "System15BitColourSpace" - The system supported 15 bits/pixel colour space.
      • "System16BitColourSpace" - The system supported 16 bits/pixel colour space.
      • "System24BitColourSpace" - The system supported 24 bits/pixel colour space.
      • "System32BitColourSpace" - The system supported 32 bits/pixel colour space.
      Not every system will support all colour spaces. Unsupported colour space images can't be placed on the screen directly. They have to be converted to a compatible colour space first.

Returns:
  • A memory bitmap object or NULL on failure.

Example:
c = CreateBitmap(320, 240);
 Print(c.Type(), ": ", c.x, ", ", c.y, ", ", c.Bits, ", ", c.ColourSpace, "\n");
Surface: 320, 240, 32, 1211639896
The colourspace '1211639896' decimal is '0x48382858' in hex. Each byte is a component description, the high nibble is the colour type (defined in Lgi as the GComponentType enum), and the lower nibble is the number of bits (and zero = 16 bits).
+

string ColourSpaceToString(intColourSpace);

+
+ Converts a LColourSpace + integer to a string. +
+ +

int StringToColourSpace(stringColourSpace);

+
+ Converts a string colour space representation to a + LColourSpace.
+
+ Returns: +
    +
  • Non zero LColourSpace entry on success, or CsNone on error (value 0). +
+
+

int obj.X;

The width of the image in pixels (read only).

int obj.Y;

The height of the image in pixels (read only).

int obj.Bits;

The number of bits per pixel (read only).

colour_space obj.ColourSpace;

- The colour space of the image (read only). This is a integer value that maps to a GColourSpace enum. + The colour space of the image (read only). This is a integer value that maps to a LColourSpace enum.

list obj.Palette;

The palette of the image. Use array indexing to set or get the individual palette entries.

bool obj.Load(filename);

Loads an image from a file. Only filters that are compiled into the executable are supported.

Arguments:
  • filename - The path to the image file.

Returns:
  • TRUE if the file was loaded.

bool obj.Save(filename);

Soads an image to a file. Only filters that are compiled into the executable are supported.

Arguments:
  • filename - The path to the image file.

Returns:
  • TRUE if the file was saved.

view LoadDialog(parent_wnd, dialog_id);

Loads a dialog from a resource file.

Arguments:
  • parent_wnd - The handle of the parent window.
  • dialog_id - The id of the dialog from "resdefs.h".

Returns:
  • A view object.

string MessageDlg(parent_wnd, msg, title, btns);

Displays a model alert message.

Arguments:
  • parent_wnd - The handle of the parent window.
  • msg - A message above the editbox.
  • [optional] title - The title of the dialog.
  • [optional] btns - Integer mask of buttons:
    • MB_OK = 0
    • MB_OKCANCEL = 1
    • MB_ABORTRETRYIGNORE = 2
    • MB_YESNOCANCEL = 3
    • MB_YESNO = 4
    • MB_RETRYCANCEL = 5
    • MB_CANCELTRYCONTINUE = 6

Returns:
  • Integer representing the button pressed:
    • IDOK = 1
    • IDCANCEL = 2
    • IDABORT = 3
    • IDRETRY = 4
    • IDIGNORE = 5
    • IDYES = 6
    • IDNO = 7
    • IDCLOSE = 8
    • IDHELP = 9
    • IDTRYAGAIN = 10
    • IDCONTINUE = 11
    • IDTIMEOUT = 32000

string GetInputDlg(parent_wnd, initial_value, msg, title, is_password);

Asks the user for input using a dialog.

Arguments:
  • parent_wnd - The handle of the parent window.
  • initial_value - The initial value in the editbox field.
  • msg - A message above the editbox.
  • title - The title of the dialog.
  • is_password - True if you want the editbox content obsured by dot characters.

Returns:
  • A string containing what the user entered.

object GetViewById(parent, id);

Gets the child view by it's id.

Arguments:
  • parent - The parent window.
  • id - The id of the child.

Returns:
  • A view reference, or NULL if not found.

int obj.Handle;

The operating system handle for the view (read only).

int obj.Id;

The control ID for the view.

string obj.Name;

The textual name assocaited with the view.

int64 obj.Value;

The numerical value assocaited with the view.

bool obj.Enabled;

This sets or gets the enabled/disable state of the view.

colour obj.Foreground;

Gets or sets the CSS foreground colour for the view.

colour obj.Background;

Gets or sets the CSS background colour for the view.

string Execute(executable[, arguments]);

Executes a process and waits for it to finish.

Arguments:
  • executable - The path to the executable.
  • [optional] arguments - Arguments to pass to the executable.

Returns:
  • A string containing what the process wrote to stdout.

bool System(executable[, arguments]);

Executes a process and doesn't wait for it to finish.

Arguments:
  • executable - The path to the executable.
  • [optional] arguments - Arguments to pass to the executable.

Returns:
  • TRUE if the process was started.

string OsName();

Returns the current OS name:
  • Win32
  • Win64
  • MacOSX
  • Linux
  • Haiku

Example:
Print(OsName(), "\n");
Win64

list OsVersion();

Returns the current OS version as 3 integers in a list.

Returns: The OS version:
Windows XP {5, 1, 0}
Windows Vista {6, 0, 0}
Windows 7 {6, 1, 0}
Windows 8 {6, 2, 0}
Mac OS X 10.8.2 {10, 8, 2}
Ubuntu 13.04 {3, 8, 8} (i.e. the kernel version)

Example:
Print(OsVersion(), "\n");
{6, 1, 0}

string obj.Type;

Gets the type of the object as a string.

Returns:
  • A string.

Example:
c = New("list");
 Print(c.Type(), "\n");
List

int obj.Length;

Gets the length of an object. The meaning of that depends on the context of what type it is:
  • List or HashTable: number of objects in the container.
  • String: The number of bytes. -
  • File: The length of the file in bytes. +
  • File: Reading the value gives the length of the file in bytes. Writing to the value sets the file's size.
  • Bitmap: Number of pixels.
  • View: Number of child views.

Returns:
  • A integer or NULL if not applicable.

Example:
c = "This is a string.";
 Print(c.Length, "\n");
 
 c = New("list");
 c.Add(123);
 c.Add(456);
 Print(c.Length, "\n");
 
17
 2
\ No newline at end of file diff --git a/docs/scripting/syntax.html b/docs/scripting/syntax.html --- a/docs/scripting/syntax.html +++ b/docs/scripting/syntax.html @@ -1,256 +1,284 @@ Lgi Scripting Syntax

Lgi Scripting Syntax

Variables

Variables are created on demand and don't need to be explicitly declared. Sometimes the host will insert known global variables before the script is run, but that usage pattern is declining as "Main" functions are now used, that allow parameters from outside the script to be passed in via explicit method arguments.

The variables themselves are based on a variant data type, that can contain integers, strings and various pointer types. There is no dereference operator needed to use a pointer type variable. Pointer arithmetic is not supported, as generally the pointers are to single class objects, not arrays of objects. That said there are List and HashTable sub-types to the variables, but no array as such.

Scope

There are 3 fixed scopes: global, local, register. Although in practice when writting code you only ever deal with global and local. Unlike C, there are no extra scope levels inside { } blocks inside a function. Variables defined in that function (including it's arguments) are visible throughout that function. Global variables are defined outside of a function scope and are visible everywhere.

Constructs

Scripts are constructed from these primitives:
if (expression) { statements; }
 [else if (expression) { statements; }]
 [else { statements; }]
while (expression)
 {
     statements;
 }
for (initial_expression; condition_expression; iterate_expression)
 {
     statements;
 }
function method_name([arg1, arg2, ...])
 {
     statements;
+    [return expression;]
 }
struct structure_name
 {
     type member1;
     [type member2; ... ]
 };
extern "dynamic_library" return_type method_name([type arg1, type arg2, ...]);
return expression;
expression;

Operators

These operators can be used in expressions:
=Assign
+Plus
-Minus
*Multiply
/Divide
%Mod
<LessThan
<=LessThanEqual
>GreaterThan
>=GreaterThanEqual
==Equals
!=Not Equals
+=Plus Equals
-=Minus Equals
*=Multiply Equals
/=Divide Equals
++Post Increment
--Post Decrement
++Pre Increment
--Pre Decrement
&&And
||Or
!Not
.Object member reference

Objects

Some variant types allow the referencing of object members or methods via the '.' operator. The available members depends on the variant type:
  • String:
      -
    • Length - the length of the string in bytes (strings are utf-8). -
    • Int - the string as an int (atoi). -
    • Float - the string as a double (atof). -
    • Split(<separator>[, <max_split>]) - Splits the string into a list. -
    • Join(<list>) - Joins all the elements in 'list' using the string as a separator. -
    • Find(<sub-string>[, <start>[, <end>]]) - Finds a sub-string. -
    • Strip() - Removes all leading and trailing whitespace. -
    • Upper() - Converts all alpha characters to upper case. -
    • Lower() - Converts all alpha characters to lower case. -
    +
  • Length - the length of the string in bytes (strings are utf-8).
  • +
  • Int - the string as an int (atoi).
  • +
  • Float - the string as a double (atof).
  • +
  • Split(<separator>[, <max_split>]) - Splits the string into a list.
  • +
  • Join(<list>) - Joins all the elements in 'list' using the string as a separator.
  • +
  • Find(<sub-string>[, <start>[, <end>]]) - Finds a sub-string.
  • +
  • Strip() - Removes all leading and trailing whitespace.
  • +
  • Upper() - Converts all alpha characters to upper case.
  • +
  • Lower() - Converts all alpha characters to lower case.
  • + +
  • Int: +
      +
    • String - the int as an string.
    • +
    • Float - the int converted to floating point.
    • +
  • +
  • Double: +
      +
    • String - the double as an string.
    • +
    • Int - the double converted to integer.
    • +
  • List:
      -
    • Length - the number of objects in the container. -
    • Add(<element>) - Add a new element to the end of the list. -
    • Delete(<index>) - Deletes the entry at 'index'. -
    +
  • Length - the number of objects in the container.
  • +
  • Add(<element>) - Add a new element to the end of the list.
  • +
  • Delete(<index>) - Deletes the entry at 'index'.
  • +
  • HashTable:
      -
    • Length - the number of objects in the container. -
    • Add(<key>, <element>) - Add a new element using the string 'key'. -
    • Delete(<key>) - Deletes the element at using the string 'key'. -
    +
  • Length - the number of objects in the container.
  • +
  • Add(<key>, <element>) - Add a new element using the string 'key'.
  • +
  • Delete(<key>) - Deletes the element at using the string 'key'.
  • +
  • DateTime:
      -
    • Year - just the year as an int. -
    • Month - just the month as an int. -
    • Day - just the day as an int. -
    • Hour - just the hour as an int. -
    • Minute - just the minute as an int. -
    • Second - just the second as an int. -
    • Date - the date part as a string. -
    • Time - the time part as a string. -
    • DateTime - the date and time as a string. -
    +
  • Year - just the year as an int.
  • +
  • Month - just the month as an int.
  • +
  • Day - just the day as an int.
  • +
  • Hour - just the hour as an int.
  • +
  • Minute - just the minute as an int.
  • +
  • Second - just the second as an int.
  • +
  • Date - the date part as a string.
  • +
  • Time - the time part as a string.
  • +
  • DateTime - the date and time as a string (also responds to the name "string").
  • +
  • TimeStamp - a 64bit int timestamp of the date (also responds to the name "int").
  • +
  • Second64Bit - the size of a second when comparing timestamps.
  • +
  • Image (GSurface):
      -
    • x - The width in pixels. -
    • y - The height in pixels. -
    • Bits - The bit depth. -
    • ColourSpace - The surface's colour space (a GColourSpace variable as a uint32) -
    +
  • x - The width in pixels.
  • +
  • y - The height in pixels.
  • +
  • Bits - The bit depth.
  • +
  • ColourSpace - The surface's colour space (a LColourSpace variable as a uint32)
  • +

    List

    The list object type allows you to store a bunch of objects in a variable (of the same variant type). To get the length of the list, use var_name.length, and to access the various members use var_name[index_expression] where the exp evaluates to an integer. If you don't specify an integer then you'll get back a NULL variable as the result.

    HashTable

    HashTable's are very similar to lists, in that they allow you to store a objects in a variable. To get the number of objects in the hash table, use var_name.length, and to access the various members use var_name[index_expression] where the exp evaluates to an string. If you don't specify a string then you'll get back a NULL variable as the result.

    Built In Types

    These types can be used in defining structures and parameters to external functions:

    • int
    • short
    • long
    • int8
    • int16
    • int32
    • int64
    • uint8
    • uint16
    • uint32
    • uint64
    • -
    • bool/boolean
    • +
    • bool / boolean
    • double
    • float
    • char*
    • -
    • GDom
    • void*
    • -
    • GDateTime
    • -
    • GHashTable
    • -
    • GOperator
    • -
    • GView
    • -
    • GMouse
    • -
    • GKey
    • -
    • GVariant
    • +
    • LDom
    • +
    • LDateTime
    • +
    • LHashTable
    • +
    • LOperator
    • +
    • LView
    • +
    • LMouse
    • +
    • LKey
    • +
    • LVariant

    Some of these types don't have any implementation or testing done. Integers and strings should work fine though.

    Custom Types

    You can define structures in a similar fashion to C code using the struct keyword. For example:
    struct PxRgb24 {
         uint8 r;
         uint8 g;
         uint8 b;
     };
     struct Image
     {
         uint32 Width;
         uint32 Height;
         PxRgb24 Pixels[16 * 16];
     };
    Packing of structures is currently on 1 byte boundaries, but it would be possible to implement other settings later. The typeid(...) keyword is supported for custom types. e.g.
    Obj = New("Image");
     if (Obj)
     {
         Type = typeid(Obj);
         Print("TypeName=" + Type.Name + ", sizeof=" + Type.Length + "\n");
         Delete(Obj);
     }
    TypeName=Image, sizeof=772

    Calling extern functions

    To call methods in dynamic libraries you first have to declare the method using the extern keyword:
    extern "dynamic_library" type method([type arg1, type arg2, ...]);
    The types for extern functions are somewhat limited to these keywords:
    • char*
    • int, int32, uint32, int64, uint64
    • double
    • void*
    • _out_ (specifies that the argument is an output, rather than an input)
    More type support will be added as needed. All non-char pointers will become "void*". The dynamic_library will be passed to "dlopen" or "LoadLibrary" pretty much unchanged.

    Windows based example:
    extern "Kernel32.dll" uint32 GetWindowsDirectoryA(_out_ char *buffer, int size);
     buf = New(256); // Allocate a buffer for the return value
     r = GetWindowsDirectoryA(buf, buf.Length);
     Print("Dir='"+buf+"'");
    Dir='c:\Windows'
    +

    Casting between types

    + + There is limited support for casting between these types: int, string, date and double. The syntax is just: +
    my_str.int;
    +my_str.double;
    +
    +my_int.string;
    +my_int.double;
    +
    +my_dbl.string;
    +my_dbl.int;
    +
    +my_date.int;
    +my_date.string;
    +
    \ No newline at end of file diff --git a/include/lgi/common/ClipBoard.h b/include/lgi/common/ClipBoard.h --- a/include/lgi/common/ClipBoard.h +++ b/include/lgi/common/ClipBoard.h @@ -1,95 +1,98 @@ /// \file #ifndef _GCLIPBOARD_H #define _GCLIPBOARD_H #include /// Clipboard API class LgiClass LClipBoard { class LClipBoardPriv *d; LView *Owner; bool Open; LAutoPtr pDC; #if LGI_COCOA LString Txt; #else LAutoString Txt; #endif LAutoWString wTxt; public: /// On windows, this equates to a CF_TEXT, CF_BITMAP, CF_DIB type #define typedef uint32_t FormatType; typedef std::function, LString)> BitmapCb; static LString FmtToStr(FormatType Fmt); static FormatType StrToFmt(LString Fmt); /// Creates the clipboard access object. LClipBoard(LView *o); ~LClipBoard(); bool IsOpen() { return Open; } LClipBoard &operator =(LClipBoard &c); /// Empties the clipboard of it's current content. bool Empty(); bool EnumFormats(LArray &Formats); // Text bool Text(const char *Str, bool AutoEmpty = true); char *Text(); // ptr returned is still owned by this object bool TextW(const char16 *Str, bool AutoEmpty = true); char16 *TextW(); // ptr returned is still owned by this object // HTML bool Html(const char *doc, bool AutoEmpty = true); LString Html(); // Bitmap bool Bitmap(LSurface *pDC, bool AutoEmpty = true); bool Bitmap(BitmapCb Callback); - [[deprecated]] LAutoPtr Bitmap(); - #if WINNATIVE - LAutoPtr ConvertFromPtr(void *Ptr); - #endif - // Files LString::Array Files(); bool Files(LString::Array &Paths, bool AutoEmpty = true); // Binary bool Binary(FormatType Format, uint8_t *Ptr, ssize_t Len, bool AutoEmpty); // Set bool Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len); // Get + + + [[deprecated]] LAutoPtr Bitmap(); + #if WINNATIVE + LAutoPtr ConvertFromPtr(void *Ptr); + #elif defined(LINUX) + void FreeImage(unsigned char *pixels); + #endif }; #ifdef __OBJC__ extern NSString *const LBinaryDataPBoardType; @interface LBinaryData : NSObject { } @property(assign) NSData *data; - (id)init:(LString)format ptr:(uchar*)ptr len:(ssize_t)Len; - (id)init:(NSData*)data; - (bool)getData:(LString*)format data:(LAutoPtr*)Ptr len:(ssize_t*)Len var:(LVariant*)var; // Writer - (nullable id)pasteboardPropertyListForType:(NSString *)type; - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard; // Reader + (NSPasteboardReadingOptions)readingOptionsForType:(NSString *)type pasteboard:(NSPasteboard *)pasteboard; + (NSArray *)readableTypesForPasteboard:(NSPasteboard *)pasteboard; @end #endif #endif diff --git a/include/lgi/common/ItemContainer.h b/include/lgi/common/ItemContainer.h --- a/include/lgi/common/ItemContainer.h +++ b/include/lgi/common/ItemContainer.h @@ -1,426 +1,426 @@ #ifndef _ITEM_CONTAINER_H_ #define _ITEM_CONTAINER_H_ #include "lgi/common/Popup.h" #include "lgi/common/Notifications.h" #include "lgi/common/Css.h" #include "lgi/common/ImageList.h" class LItemContainer; #define DragColumnColour LColour(L_LOW) /// Base class for items in widget containers class LgiClass LItem : virtual public LEventsI { protected: LAutoPtr Css; int SelectionStart, SelectionEnd; public: /// Painting context struct ItemPaintCtx : public LRect { /// The surface to draw on LSurface *pDC; /// Current foreground colour LColour Fore; /// Current background colour LColour TxtBack, Back; // Horizontal alignment of content LCss::Len Align; /// Number of columns int Columns; /// Width of each column int *ColPx; }; LItem(); ~LItem(); LItem &operator =(LItem &i) { if (i.GetCss()) { LCss *c = GetCss(true); if (c) { *c = *i.GetCss(); } } return *this; } virtual const char *GetClass() { return "LItem"; } virtual LItemContainer *GetContainer() = 0; // Events /// Called when the item is selected virtual void OnSelect() {} /// Called when the item is clicked virtual void OnMouseClick(LMouse &m) {} /// Called when the item needs painting virtual void OnPaint(ItemPaintCtx &Ctx) = 0; /// Called when the item is dragged virtual bool OnBeginDrag(LMouse &m) { return false; } /// Called when the owning container needs to know the size of the item virtual void OnMeasure(LPoint *Info) {} /// Called when the item is inserted into a new container virtual void OnInsert() {} /// Called when the item is removed from it's container virtual void OnRemove() {} // Methods /// Call to tell the container that the data displayed by the item has changed virtual void Update() {} - /// Moves the item onscreen + /// Moves the item on screen virtual void ScrollTo() {} /// Shows a editable label above the item allowing the user to change the value associated with the column 'Col' virtual LView *EditLabel(int Col = -1); /// Event called when the edit label ends virtual void OnEditLabelEnd(); /// Sets the default selection of text when editing a label void SetEditLabelSelection(int SelStart, int SelEnd); // call before 'EditLabel' // Data /// True if the item is selected virtual bool Select() { return false; } /// Select/Deselect the item virtual void Select(bool b) {} /// Gets the text associated with the column 'Col' virtual const char *GetText(int Col = 0) { return 0; } /// Sets the text associated with the column 'Col' virtual bool SetText(const char *s, int Col=0) { return false; } /// Gets the icon index virtual int GetImage(int Flags = 0) { return -1; } /// Sets the icon index virtual void SetImage(int Col) {} /// Gets the position virtual LRect *GetPos(int Col = -1) { return 0; } /// Gets the font for the item virtual LFont *GetFont() { return 0; } /// Reads / writes list item to XML virtual bool XmlIo(class LXmlTag *Tag, bool Write) { return false; } bool OnScriptEvent(LViewI *Ctrl) { return false; } LMessage::Result OnEvent(LMessage *Msg) { return 0; } void OnMouseEnter(LMouse &m) {} void OnMouseExit(LMouse &m) {} void OnMouseMove(LMouse &m) {} bool OnMouseWheel(double Lines) { return false; } bool OnKey(LKey &k) { return false; } void OnAttach() {} void OnCreate() {} void OnDestroy() {} void OnFocus(bool f) {} void OnPulse() {} void OnPosChange() {} bool OnRequestClose(bool OsShuttingDown) { return false; } int OnHitTest(int x, int y) { return 0; } void OnChildrenChanged(LViewI *Wnd, bool Attaching) {} int OnNotify(LViewI *Ctrl, LNotification n) { return 0; } int OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void OnPaint(LSurface *pDC) { LAssert(0); } // Style access LCss *GetCss(bool Create = false) { if (!Css && Create) Css.Reset(new LCss); return Css; } }; /// The popup label for LItem's class LItemEdit : public LPopup { class LItemEditPrivate *d; public: LItemEdit(LView *parent, LItem *item, int index, int selstart, int selend); ~LItemEdit(); LItem *GetItem(); void OnPaint(LSurface *pDC); int OnNotify(LViewI *v, LNotification n); void Visible(bool i); LMessage::Result OnEvent(LMessage *Msg); bool OnKey(LKey &k); void OnFocus(bool f); }; /// No marking /// \sa LItemColumn::Mark #define GLI_MARK_NONE 0 /// Up arrow mark /// \sa LItemColumn::Mark #define GLI_MARK_UP_ARROW 1 /// Down arrow mark /// \sa LItemColumn::Mark #define GLI_MARK_DOWN_ARROW 2 /// Item container column class LgiClass LItemColumn : public ResObject, public LCss { class LItemColumnPrivate *d; friend class LDragColumn; friend class LListItem; friend class LItemContainer; public: LItemColumn(LItemContainer *parent, const char *name, int width); virtual ~LItemColumn(); // properties bool InDrag(); LRect GetPos(); void SetPos(LRect &r); /// Sets the text void Name(const char *n); /// Gets the text char *Name(); /// Sets the width void Width(int i); /// Gets the width int Width(); /// Sets the type of content in the header. Use one of #GIC_ASK_TEXT, #GIC_OWNER_DRAW, #GIC_ASK_IMAGE. /// /// Is this used?? void Type(int i); /// Gets the type of content. int Type(); /// Sets the marking, one of #GLI_MARK_NONE, #GLI_MARK_UP_ARROW or #GLI_MARK_DOWN_ARROW void Mark(int i); /// Gets the marking, one of #GLI_MARK_NONE, #GLI_MARK_UP_ARROW or #GLI_MARK_DOWN_ARROW int Mark(); /// Sets the icon to display void Icon(LSurface *i, bool Own = true); /// Gets the icon displayed LSurface *Icon(); /// True if clicked bool Value(); /// Set clicked void Value(bool i); /// Sets the index into the parent containers LImageList void Image(int i); /// Gets the index into the parent containers LImageList int Image(); /// true if resizable bool Resizable(); /// Sets whether the user can resize the column void Resizable(bool i); /// Returns the index of the column if attached to a list int GetIndex(); /// Returns the size of the content in this column int GetContentSize(); /// Returns the list LItemContainer *GetList(); /// Paint the column header. void OnPaint(LSurface *pDC, LRect &r); /// Draws the just the icon, text and mark. void OnPaint_Content(LSurface *pDC, LRect &r, bool FillBackground); }; /// Client draws the content. #define GIC_OWNER_DRAW 0x01 /// Column header is text. #define GIC_ASK_TEXT 0x02 /// Column header is an image. #define GIC_ASK_IMAGE 0x04 /// Not used. #define GIC_OWN_LIST 0x08 /// Drag is over the control #define GIC_IN_DRAG_OP 0x10 /// Multiple item select allowed #define GIC_MULTI_SELECT 0x20 #define DRAG_NONE 0 #define SELECT_ITEMS 1 #define RESIZE_COLUMN 2 #define DRAG_COLUMN 3 #define CLICK_COLUMN 4 #define TOGGLE_ITEMS 5 #define CLICK_ITEM 6 #define DEFAULT_COLUMN_SPACING 12 class LgiClass LItemContainer : public LLayout, public LImageListOwner { friend class LItemColumn; friend class LItem; friend class LItemEdit; public: struct ColInfo { int Idx, ContentPx, WidthPx; LItemColumn *Col; int GrowPx() { return WidthPx < ContentPx ? ContentPx - WidthPx : 0; } }; struct ColSizes { LArray Info; int FixedPx; int ResizePx; }; protected: int Flags; int DragMode; bool ColumnHeaders; LRect ColumnHeader; int ColClick; LMouse ColMouse; LItemEdit *ItemEdit; LArray Columns; LAutoPtr IconCol; class LDragColumn *DragCol; /// Returns size information for columns void GetColumnSizes(ColSizes &cs); /// Paints all the column headings void PaintColumnHeadings(LSurface *pDC); /// This clears all the display string cache for a given column virtual void ClearDs(int Col) = 0; public: LItemContainer(); virtual ~LItemContainer(); // Props bool OwnerDraw() { return TestFlag(Flags, GIC_OWNER_DRAW); } void OwnerDraw(bool b) { if (b) SetFlag(Flags, GIC_OWNER_DRAW); else ClearFlag(Flags, GIC_OWNER_DRAW); } bool AskText() { return TestFlag(Flags, GIC_ASK_TEXT); } void AskText(bool b) { if (b) SetFlag(Flags, GIC_ASK_TEXT); else ClearFlag(Flags, GIC_ASK_TEXT); } bool AskImage() { return TestFlag(Flags, GIC_ASK_IMAGE); } void AskImage(bool b) { if (b) SetFlag(Flags, GIC_ASK_IMAGE); else ClearFlag(Flags, GIC_ASK_IMAGE); } bool InsideDragOp() { return TestFlag(Flags, GIC_IN_DRAG_OP); } void InsideDragOp(bool b) { if (b) SetFlag(Flags, GIC_IN_DRAG_OP); else ClearFlag(Flags, GIC_IN_DRAG_OP); } /// Returns whether the user can select multiple items at the same time bool MultiSelect() { return TestFlag(Flags, GIC_MULTI_SELECT); } /// Sets whether the user can select multiple items at the same time void MultiSelect(bool b) { if (b) SetFlag(Flags, GIC_MULTI_SELECT); else ClearFlag(Flags, GIC_MULTI_SELECT); } /// Returns whether display of column headers is switched on bool ShowColumnHeader() { return ColumnHeaders; } /// Turns on display of column headers void ShowColumnHeader(bool Show) { ColumnHeaders = Show; } /// Adds a column to the list LItemColumn *AddColumn ( /// The text for the column or NULL for no text const char *Name, /// The width of the column int Width = 50, /// The index to insert at, or -1 to append to the end int Where = -1 ); /// Adds a preexisting column to the control bool AddColumn ( /// The column object. The object once added is owned by the LList LItemColumn *Col, /// The location to insert or -1 to append int Where = -1 ); /// Deletes a column from the LList bool DeleteColumn(LItemColumn *Col); /// Deletes all the columns of the LList void EmptyColumns(); /// Returns the column at index 'Index' LItemColumn *ColumnAt(unsigned i) { return i < Columns.Length() ? Columns[i] : NULL; } /// Returns the column at horizontal offset 'x', or -1 if none matches. int ColumnAtX(int X, LItemColumn **Col = 0, int *Offset = 0); /// Returns the number of columns int GetColumns() { return (int)Columns.Length(); } /// Starts a column d'n'd operation with the column at index 'Index' /// \sa OnColumnReindex is called when the user drops the column void DragColumn(int Index); /// Returns the last column click info bool GetColumnClickInfo(int &Col, LMouse &m); int HitColumn(int x, int y, LItemColumn *&Resize, LItemColumn *&Over); /// Called when a column is clicked virtual void OnColumnClick ( /// The index of the column int Col, /// The mouse parameters at the time LMouse &m ); virtual void UpdateAllItems() = 0; virtual int GetContentSize(int ColumnIdx) = 0; /// Resizes all the columns to their content, allowing a little extra space for visual effect virtual void ResizeColumnsToContent(int Border = DEFAULT_COLUMN_SPACING); LMessage::Result OnEvent(LMessage *Msg); LItemContainer &operator =(const LItemContainer &i) { LAssert(!"Not impl.."); return *this; } }; //////////////////////////////////////////////////////////////////////////////////////////// #define DRAG_COL_ALPHA 0xc0 #define LINUX_TRANS_COL 0 class LDragColumn : public LWindow { LItemContainer *List; LItemColumn *Col; int Index; int Offset; LPoint ListScrPos; #ifdef LINUX LSurface *Back; #endif public: int GetOffset() { return Offset; } int GetIndex() { return Index; } LItemColumn *GetColumn() { return Col; } LDragColumn(LItemContainer *list, int col); ~LDragColumn(); void OnPaint(LSurface *pScreen); }; #endif diff --git a/include/lgi/common/LgiInterfaces.h b/include/lgi/common/LgiInterfaces.h --- a/include/lgi/common/LgiInterfaces.h +++ b/include/lgi/common/LgiInterfaces.h @@ -1,567 +1,567 @@ // \file /// \author Matthew Allen #ifndef _LGI_INTERFACES_H_ #define _LGI_INTERFACES_H_ // Includes #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/Colour.h" #include "lgi/common/Cancel.h" #include "lgi/common/StringClass.h" #include "lgi/common/LgiUiBase.h" #include "lgi/common/Notifications.h" // Fwd defs class LXmlTag; class LMouseHook; class LFont; class LRect; class LPoint; class LRegion; class LSurface; class LMouse; class LKey; class LWindow; class LVariant; class LCss; class LViewI; class LView; #ifdef Yield #undef Yield #endif // Classes class LDomI { public: virtual ~LDomI() {} virtual bool GetValue(const char *Var, LVariant &Value) { return false; } virtual bool SetValue(const char *Var, LVariant &Value) { return false; } virtual bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { return false; } }; /// Stream interface class /// /// Defines the API /// for all the streaming data classes. Allows applications to plug /// different types of date streams into functions that take a LStream. /// Typically this means being able to swap files with sockets or data /// buffers etc. /// class LgiClass LStreamI : virtual public LDomI { public: /// Open a connection /// \returns > zero on success virtual int Open ( /// A string connection parameter const char *Str = 0, /// An integer connection parameter int Int = 0 ) { return false; } /// Returns true is the stream is still open virtual bool IsOpen() { return false; } /// Closes the connection /// \returns > zero on success virtual int Close() { return 0; } /// \brief Gets the size of the stream /// \return The size or -1 on error (e.g. the information is not available) virtual int64 GetSize() { return -1; } /// \brief Sets the size of the stream /// \return The new size or -1 on error (e.g. the size is not set-able) virtual int64 SetSize(int64 Size) { return -1; } /// \brief Gets the current position of the stream /// \return Current position or -1 on error (e.g. the position is not known) virtual int64 GetPos() { return -1; } /// \brief Sets the current position of the stream /// \return The new current position or -1 on error (e.g. the position can't be set) virtual int64 SetPos(int64 Pos) { return -1; } /// \brief Read bytes out of the stream /// \return > 0 on succes, which indicates the number of bytes read virtual ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) = 0; /// \brief Write bytes to the stream /// \return > 0 on succes, which indicates the number of bytes written virtual ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) = 0; /// \brief Creates a dynamically allocated copy of the same type of stream. /// This new stream is not connected to anything. /// \return The new stream or NULL on error. virtual LStreamI *Clone() { return 0; } virtual void ChangeThread() {} /// Utility: Read as LString. LString Read(size_t bufLen = 256) { LString s; s.Length(bufLen); auto rd = Read(s.Get(), s.Length()); if (rd < (ssize_t) s.Length()) s.Length(rd); return s; } /// Utility: Write a LString size_t Write(const LString s) { return Write(s.Get(), s.Length()); } }; /// Socket logging types.. enum LSocketLogTypes { /// Do no logging NET_LOG_NONE = 0, /// Log a hex dump of everything NET_LOG_HEX_DUMP = 1, /// Log just the bytes NET_LOG_ALL_BYTES = 2 }; /// Virtual base class for a socket. See the documentation for LSocket for a more /// through treatment of this object's API. class LSocketI : virtual public LStreamI { public: enum SocketMsgType { SocketMsgNone, SocketMsgInfo, SocketMsgSend, SocketMsgReceive, SocketMsgWarning, SocketMsgError, }; virtual ~LSocketI() {} /// Returns the actual socket (as defined by the OS) virtual OsSocket Handle(OsSocket Set = INVALID_SOCKET) { return INVALID_SOCKET; } // Cancel virtual LCancel *GetCancel() { return NULL; } virtual void SetCancel(LCancel *c) { } // Logging and utility virtual class LStreamI *GetLog() { return NULL; } // Host/Port meta data /// Returns the IP at this end of the socket virtual bool GetLocalIp ( /// Ptr to a buffer of at least 16 bytes char *IpAddr ) { return false; } /// Return the port at this end of the connection virtual int GetLocalPort() { return 0; } /// Gets the remote IP virtual bool GetRemoteIp(char *IpAddr) { return false; } /// Return the port at this end of the connection virtual int GetRemotePort() { return 0; } // Timeout /// Gets the current timeout for operations in ms virtual int GetTimeout() { return -1; } /// Sets the current timeout for operations in ms virtual void SetTimeout(int ms) {} /// Sets the continue token // virtual void SetContinue(bool *Token) {} // State /// True if there is data available to read. virtual bool IsReadable(int TimeoutMs = 0) { return false; } /// True if the socket can be written to. virtual bool IsWritable(int TimeoutMs = 0) { return false; } /// True if the socket can be accept. virtual bool CanAccept(int TimeoutMs = 0) { return false; } /// Returns whether the socket is set to blocking or not virtual bool IsBlocking() { return true; } /// Set whether the socket should block or not virtual void IsBlocking(bool block) {} /// Get the send delay setting virtual bool IsDelayed() { return true; } /// Set the send delay setting virtual void IsDelayed(bool Delay) {} // UDP /// Get UPD mode virtual bool GetUdp() { return false; } /// Set UPD mode virtual void SetUdp(bool b) {} /// Read UPD packet virtual int ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip = 0, uint16_t *Port = 0) { return 0; } /// Write UPD packet virtual int WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { return 0; } // Server /// Listens on a given port for an incoming connection. virtual bool Listen(int Port = 0) { return false; } /// Accepts an incoming connection and connects the socket you pass in to the remote host. virtual bool Accept(LSocketI *c) { return false; } // Event call backs /// Called when the connection is dropped virtual void OnDisconnect() {} /// Called when data is read virtual void OnRead(char *Data, ssize_t Len) {} /// Called when data is written virtual void OnWrite(const char *Data, ssize_t Len) {} /// Called when an error occurs virtual void OnError(int ErrorCode, const char *ErrorDescription) {} /// Called when some events happens virtual void OnInformation(const char *Str) {} /// Process an error virtual int Error(void *Param) { return 0; } virtual const char *GetErrorString() { return NULL; } LString LocalIp() { char Ip[32]; return GetLocalIp(Ip) ? Ip : NULL; } }; class LAppI { public: /// The idle function should return false to wait for more /// messages, otherwise it will be called continuously when no /// messages are available. typedef bool (*OnIdleProc)(void *Param); /// Destroys the object virtual ~LAppI() {} virtual bool IsOk() = 0; /// Returns this processes ID virtual OsProcessId GetProcessId() = 0; /// Returns the thread currently running the active message loop virtual OsThreadId GetGuiThreadId() = 0; virtual bool InThread() = 0; /// Resets the arguments virtual void SetAppArgs(OsAppArguments &AppArgs) = 0; /// Returns the arguemnts virtual OsAppArguments *GetAppArgs() = 0; /// Returns the n'th argument as a heap string. Free with DeleteArray(...). virtual const char *GetArgumentAt(int n) = 0; /// Enters the message loop. virtual bool Run ( /// [Optional] Idle callback OnIdleProc IdleCallback = 0, /// [Optional] User param for IdleCallback void *IdleParam = 0 ) = 0; /// Processed queued events and then return virtual bool Yield() = 0; /// Event called to process the command line virtual void OnCommandLine() = 0; /// Event called to process files dropped on the application virtual void OnReceiveFiles(LArray &Files) = 0; /// Event called to process URLs given to the application virtual void OnUrl(const char *Url) = 0; /// Exits the event loop with the code specified virtual void Exit ( /// The application exit code. int Code = 0 ) = 0; /// \brief Parses the command line for a switch /// \return true if the option exists. virtual bool GetOption ( /// The option to look for. const char *Option, /// String to receive the value (if any) of the option LString &Value ) = 0; /// \brief Parses the command line for a switch /// \return true if the option exists. virtual bool GetOption ( /// The option to look for. const char *Option, /// The buffer to receive the value of the command line parameter or NULL if you don't care. char *Dst = 0, /// The buffer size in bytes int DstSize = 0 ) = 0; /// Gets the application conf stored in lgi.conf virtual LString GetConfig(const char *Tag) = 0; /// Sets a single tag in the config. (Not written to disk) virtual void SetConfig(const char *Var, const char *Val) = 0; /// Gets the control with the keyboard focus virtual LViewI *GetFocus() = 0; /// Gets the MIME type of a file virtual LString GetFileMimeType ( /// The file to identify const char *File ) = 0; /// Get a system metric virtual int32 GetMetric ( /// One of #LGI_MET_DECOR_X, #LGI_MET_DECOR_Y LSystemMetric Metric ) = 0; /// Get the mouse hook instance virtual LMouseHook *GetMouseHook() = 0; /// Returns the number of cpu cores or -1 if unknown. virtual int GetCpuCount() { return -1; } /// Gets the font cache virtual class LFontCache *GetFontCache() = 0; }; class LEventSinkI { public: virtual ~LEventSinkI() {} virtual bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) = 0; }; class LEventTargetI { public: virtual ~LEventTargetI() {} virtual LMessage::Result OnEvent(LMessage *Msg) = 0; }; class LEventsI : public LEventTargetI { public: virtual ~LEventsI() {} // Events virtual void OnMouseClick(LMouse &m) = 0; virtual void OnMouseEnter(LMouse &m) = 0; virtual void OnMouseExit(LMouse &m) = 0; virtual void OnMouseMove(LMouse &m) = 0; virtual bool OnMouseWheel(double Lines) = 0; virtual bool OnKey(LKey &k) = 0; virtual void OnAttach() = 0; virtual void OnCreate() = 0; virtual void OnDestroy() = 0; virtual void OnFocus(bool f) = 0; virtual void OnPulse() = 0; virtual void OnPosChange() = 0; virtual bool OnRequestClose(bool OsShuttingDown) = 0; virtual int OnHitTest(int x, int y) = 0; virtual void OnChildrenChanged(LViewI *Wnd, bool Attaching) = 0; virtual void OnPaint(LSurface *pDC) = 0; virtual int OnCommand(int Cmd, int Event, OsView Wnd) = 0; virtual int OnNotify(LViewI *Ctrl, LNotification Data) = 0; }; class LViewLayoutInfo { public: struct Range { // 0 if unknown, -1 for "all available" int32 Min, Max; Range() { Min = Max = 0; } }; - Range Width; + Range Width; Range Height; }; class LgiClass LViewI : public LEventsI, public LEventSinkI, public virtual LDomI { friend class LView; public: // Handles #if LGI_VIEW_HANDLE virtual OsView Handle() const = 0; #endif virtual int AddDispatch() = 0; virtual OsWindow WindowHandle() { printf("LViewI::WindowHandle()\n"); return NULL; } virtual LView *GetGView() { return NULL; } // Heirarchy virtual bool Attach(LViewI *p) = 0; virtual bool AttachChildren() = 0; virtual bool Detach() = 0; virtual bool IsAttached() = 0; virtual LWindow *GetWindow() = 0; virtual LViewI *GetParent() = 0; virtual void SetParent(LViewI *p) = 0; virtual void Quit(bool DontDelete = false) = 0; virtual bool AddView(LViewI *v, int Where = -1) = 0; virtual bool DelView(LViewI *v) = 0; virtual bool HasView(LViewI *v) = 0; virtual LArray IterateViews() = 0; // Threading virtual bool Lock(const char *file, int line, int TimeOut = -1) = 0; virtual void Unlock() = 0; virtual bool InThread() = 0; // Properties virtual bool Enabled() = 0; virtual void Enabled(bool e) = 0; virtual bool Visible() = 0; virtual void Visible(bool v) = 0; virtual bool Focus() = 0; virtual void Focus(bool f) = 0; virtual class LDragDropSource *DropSource(LDragDropSource *Set = NULL) = 0; virtual class LDragDropTarget *DropTarget(LDragDropTarget *Set = NULL) = 0; virtual bool DropTarget(bool t) = 0; virtual bool Sunken() = 0; virtual void Sunken(bool i) = 0; virtual bool Flat() = 0; virtual void Flat(bool i) = 0; virtual bool Raised() = 0; virtual void Raised(bool i) = 0; virtual bool GetTabStop() = 0; virtual void SetTabStop(bool b) = 0; // Style virtual LCss *GetCss(bool Create = false) = 0; virtual void SetCss(LCss *css) = 0; virtual bool SetColour(LColour &c, bool Fore) = 0; virtual LString CssStyles(const char *Set = NULL) { return LString(); } virtual LString::Array *CssClasses() { return NULL; } virtual LFont *GetFont() = 0; virtual void SetFont(LFont *Fnt, bool OwnIt = false) = 0; // Name and value virtual bool Name(const char *n) = 0; virtual bool NameW(const char16 *n) = 0; virtual const char *Name() = 0; virtual const char16 *NameW() = 0; virtual int64 Value() = 0; virtual void Value(int64 i) = 0; virtual const char *GetClass() { return "LViewI"; } // mainly for debugging // Size and position virtual LRect &GetPos() = 0; virtual LRect &GetClient(bool InClientSpace = true) = 0; virtual bool SetPos(LRect &p, bool Repaint = false) = 0; virtual int X() = 0; virtual int Y() = 0; virtual LPoint GetMinimumSize() = 0; virtual void SetMinimumSize(LPoint Size) = 0; // Id virtual int GetId() = 0; virtual void SetId(int i) = 0; // Events and notification virtual void SendNotify(LNotification note) = 0; virtual LViewI *GetNotify() = 0; virtual void SetNotify(LViewI *n) = 0; // Mouse virtual LCursor GetCursor(int x, int y) = 0; virtual bool Capture(bool c) = 0; virtual bool IsCapturing() = 0; virtual bool GetMouse(LMouse &m, bool ScreenCoords = false) = 0; // Helper #if LGI_VIEW_HANDLE virtual LViewI *FindControl(OsView hnd) = 0; #endif virtual LViewI *FindControl(int Id) = 0; virtual int64 GetCtrlValue(int Id) = 0; virtual void SetCtrlValue(int Id, int64 i) = 0; virtual const char *GetCtrlName(int Id) = 0; virtual void SetCtrlName(int Id, const char *s) = 0; virtual bool GetCtrlEnabled(int Id) = 0; virtual void SetCtrlEnabled(int Id, bool Enabled) = 0; virtual bool GetCtrlVisible(int Id) = 0; virtual void SetCtrlVisible(int Id, bool Visible) = 0; virtual bool Pour(LRegion &r) = 0; template bool GetViewById(int Id, T *&Ptr) { LViewI *Ctrl = FindControl(Id); Ptr = dynamic_cast(Ctrl); #ifdef _DEBUG if (Ctrl != NULL && Ptr == NULL) LgiTrace("%s:%i - Can't cast '%s' to target type.\n", _FL, Ctrl->GetClass()); #endif return Ptr != NULL; } // Points virtual bool PointToScreen(LPoint &p) = 0; virtual bool PointToView(LPoint &p) = 0; virtual bool WindowVirtualOffset(LPoint *Offset) = 0; virtual LViewI *WindowFromPoint(int x, int y, int DebugDepth = 0) = 0; virtual LPoint &GetWindowBorderSize() = 0; virtual bool IsOver(LMouse &m) = 0; // Misc virtual bool Invalidate(LRect *r = 0, bool Repaint = false, bool NonClient = false) = 0; virtual bool Invalidate(LRegion *r, bool Repaint = false, bool NonClient = false) = 0; virtual void SetPulse(int Ms = -1) = 0; virtual bool OnLayout(LViewLayoutInfo &Inf) = 0; protected: virtual bool OnViewMouse(LView *v, LMouse &m) = 0; virtual bool OnViewKey(LView *v, LKey &k) = 0; }; class LMemoryPoolI { public: virtual ~LMemoryPoolI() {} virtual void *Alloc(size_t Size) = 0; virtual void Free(void *Ptr) = 0; virtual void Empty() = 0; }; #endif diff --git a/include/lgi/common/ParseCpp.h b/include/lgi/common/ParseCpp.h --- a/include/lgi/common/ParseCpp.h +++ b/include/lgi/common/ParseCpp.h @@ -1,85 +1,85 @@ #ifndef _GPARSECPP_H_ #define _GPARSECPP_H_ /// C++ symbol types -enum GSymbolType +enum LSymbolType { SymNone, SymDefineValue, SymDefineFunction, SymTypedef, SymUnion, SymClass, SymStruct, SymEnum, SymFunction, SymVariable, SymExternC }; /// A reference to a matched symbol -struct GSymbolResult +struct LSymbolResult { /// The type of the symbol - GSymbolType Type; + LSymbolType Type; /// Which file the symbol is in const char *File; /// The line number in that file int Line; /// The quality of the match (higher is better). int MatchQuality; }; /// A parser library for C++ code. class LCppParser { - struct GCppParserPriv *d; + struct LCppParserPriv *d; public: - typedef void (*SearchResultsCb)(void *CallbackData, LArray &Syms); + typedef void (*SearchResultsCb)(void *CallbackData, LArray &Syms); struct ValuePair { LAutoString Var, Val; ValuePair(const char *var = NULL, const char *val = NULL) { Var.Reset(NewStr(var)); Val.Reset(NewStr(val)); } ValuePair(const ValuePair &p) { Var.Reset(NewStr(p.Var)); Val.Reset(NewStr(p.Val)); } }; LCppParser(); virtual ~LCppParser(); /// Parse a group of source code files. void ParseCode ( /// All the include paths for this group of source code files LArray &IncludePaths, /// Any pre-defined values LArray &PreDefines, /// All the source code files to parse LArray &Source ); /// Do a search for a symbol void Search ( /// The search string, can be multiple parts broken by spaces. Sub-strings will /// match longer idenifiers and searches are case sensitive const char *Str, /// A callback to receive results. SearchResultsCb Callback, /// A user defined value to pass back to the Callback. void *CallbackData ); }; #endif \ No newline at end of file diff --git a/include/lgi/common/Progress.h b/include/lgi/common/Progress.h --- a/include/lgi/common/Progress.h +++ b/include/lgi/common/Progress.h @@ -1,47 +1,47 @@ /// \file /// \author Matthew Allen #ifndef __PROGRESS_H #define __PROGRESS_H #include "lgi/common/Mutex.h" #include "lgi/common/Cancel.h" #include "lgi/common/StringClass.h" #include "lgi/common/Dom.h" #include "lgi/common/DateTime.h" /// Generic progress class, keeps track of how far through a task you are. class LgiClass Progress : public LMutex, public LCancel, virtual public LDom { protected: LString Description, Type; uint64_t Start = 0; LDateTime StartDt; int64_t Val = 0; int64_t Low = 0, High = 0; double Scale = 1.0; public: uint64 UserData = 0; Progress(); Progress(char *desc, int64 l, int64 h, char *type = NULL, double scale = 1.0); - virtual ~Progress() {} + virtual ~Progress(); virtual bool SetRange(const LRange &r) { Low = r.Start; High = r.End(); return true; } virtual LRange GetRange() { return LRange((ssize_t)Low, (ssize_t)(High - Low)); } virtual LString GetDescription(); virtual void SetDescription(const char *d = 0); virtual int64 Value() { return Val; } virtual void Value(int64 v) { Val = v; } virtual double GetScale() { return Scale; } virtual void SetScale(double s) { Scale = s; } virtual LString GetType(); virtual void SetType(const char *t); Progress &operator =(Progress &p); }; #endif diff --git a/include/lgi/common/Unicode.h b/include/lgi/common/Unicode.h --- a/include/lgi/common/Unicode.h +++ b/include/lgi/common/Unicode.h @@ -1,885 +1,885 @@ // // GUnicode.h // // Created by Matthew Allen on 1/08/15. // #ifndef _GUnicode_h #define _GUnicode_h #include "LgiInc.h" #ifdef MAC #define REG #else #define REG register #endif typedef unsigned char uint8_t; typedef signed char int8; typedef signed short int16; typedef unsigned short uint16; typedef signed int int32; typedef unsigned int uint32_t; #ifdef _MSC_VER typedef signed __int64 int64; typedef unsigned __int64 uint64; #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) typedef int64_t int64; typedef uint64_t uint64; #elif !defined(HAIKU) typedef signed long long int64; typedef unsigned long long uint64; #endif // Various unicode code points of interest #define UNICODE_ZERO_WIDTH_SPACE 0x200B #define UNICODE_ZERO_WIDTH_JOINER 0x200d // Defines for decoding UTF8 #define IsUtf8_1Byte(c) ( ((uint8_t)(c) & 0x80) == 0x00 ) #define IsUtf8_2Byte(c) ( ((uint8_t)(c) & 0xe0) == 0xc0 ) #define IsUtf8_3Byte(c) ( ((uint8_t)(c) & 0xf0) == 0xe0 ) #define IsUtf8_4Byte(c) ( ((uint8_t)(c) & 0xf8) == 0xf0 ) #define IsUtf8_Lead(c) ( ((uint8_t)(c) & 0xc0) == 0xc0 ) #define IsUtf8_Trail(c) ( ((uint8_t)(c) & 0xc0) == 0x80 ) // Stand-alone functions /// Convert a single utf-8 char to utf-32 or returns -1 on error. inline int32 LgiUtf8To32(uint8_t *&i, ssize_t &Len) { int32 Out = 0; #define InvalidUtf() { Len--; i++; return -1; } if (Len > 0) { if (!*i) { Len = 0; return 0; } if (IsUtf8_1Byte(*i)) { // 1 byte UTF-8 Len--; return *i++; } else if (IsUtf8_2Byte(*i)) { // 2 byte UTF-8 if (Len > 1) { Out = ((int)(*i++ & 0x1f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } } else if (IsUtf8_3Byte(*i)) { // 3 byte UTF-8 if (Len > 2) { Out = ((int)(*i++ & 0x0f)) << 12; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } else InvalidUtf() } } else if (IsUtf8_4Byte(*i)) { // 4 byte UTF-8 if (Len > 3) { Out = ((int)(*i++ & 0x07)) << 18; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 12; Len--; if (IsUtf8_Trail(*i)) { Out |= ((int)(*i++ & 0x3f)) << 6; Len--; if (IsUtf8_Trail(*i)) { Out |= *i++ & 0x3f; Len--; } else InvalidUtf() } else InvalidUtf() } else InvalidUtf() } } else InvalidUtf() } return Out; } /// Convert a single utf-32 char to utf-8 inline bool LgiUtf32To8(uint32_t c, uint8_t *&i, ssize_t &Len) { if ((c & ~0x7f) == 0) { if (Len > 0) { *i++ = c; Len--; return true; } } else if ((c & ~0x7ff) == 0) { if (Len > 1) { *i++ = 0xc0 | (c >> 6); *i++ = 0x80 | (c & 0x3f); Len -= 2; return true; } } else if ((c & 0xffff0000) == 0) { if (Len > 2) { *i++ = 0xe0 | (c >> 12); *i++ = 0x80 | ((c & 0x0fc0) >> 6); *i++ = 0x80 | (c & 0x3f); Len -= 3; return true; } } else { if (Len > 3) { *i++ = 0xf0 | (c >> 18); *i++ = 0x80 | ((c&0x3f000) >> 12); *i++ = 0x80 | ((c&0xfc0) >> 6); *i++ = 0x80 | (c&0x3f); Len -= 4; return true; } } return false; } // Defined for decoding UTF16 #define IsUtf16_Lead(c) ( ((uint16)(c) & 0xfc00) == 0xD800 ) #define IsUtf16_Trail(c) ( ((uint16)(c) & 0xfc00) == 0xDc00 ) /// Convert a single utf-16 char to utf-32 inline uint32_t LgiUtf16To32(const uint16_t *&i, ssize_t &Bytes) { if (Bytes > 1) { if (!*i) { Bytes = 0; return 0; } int n = *i & 0xfc00; if (n == 0xd800 || n == 0xdc00) { // 2 word UTF if (Bytes > 3) { Bytes -= sizeof(uint16)<<1; int w = (*i & 0x3c0) >> 6; int zy = *i++ & 0x3f; return ((w + 1) << 16) | (zy << 10) | (*i++ & 0x3ff); } } // 1 word UTF Bytes -= sizeof(uint16); return *i++; } return 0; } /// Convert a single utf-32 char to utf-16 inline bool LgiUtf32To16(uint32_t c, uint16_t *&i, ssize_t &Len) { if (c >= 0x10000) { // 2 word UTF if (Len < 4) return false; int w = c - 0x10000; *i++ = 0xd800 + (w >> 10); *i++ = 0xdc00 + (c & 0x3ff); Len -= sizeof(*i) << 1; } else { if (Len < 2) return false; if (c > 0xD7FF && c < 0xE000) return false; // 1 word UTF *i++ = c; Len -= sizeof(*i); return true; } return false; } /// Seeks the pointer 'Ptr' to the next utf-8 character template inline bool LgiNextUtf8(T *&p) { T *old = p; if (IsUtf8_Lead(*p)) { p++; while (IsUtf8_Trail(*p)) p++; } else p++; return p > old; } /// Seeks the pointer 'Ptr' to the previous utf-8 character template inline void LgiPrevUtf8(T *&p) { p--; while (IsUtf8_Trail(*p)) p--; } /// Pointer to utf-8 string class LgiClass LUtf8Ptr { protected: uint8_t *Ptr; public: - static bool Warn; + static bool Warn; LUtf8Ptr(const void *p = NULL); /// Assign a new pointer to the string LUtf8Ptr &operator =(char *s) { Ptr = (uint8_t*)s; return *this; } /// Assign a new pointer to the string LUtf8Ptr &operator =(uint8_t *s) { Ptr = s; return *this; } /// \returns the current character in the string or -1 on error. operator int32(); /// Change the character at the point, the pointer will advance to the end /// of the character written. LUtf8Ptr &operator =(uint32_t ch); /// Seeks forward LUtf8Ptr &operator ++(); LUtf8Ptr &operator ++(const int i); LUtf8Ptr &operator +=(const ssize_t n); /// Seeks 1 character backward LUtf8Ptr &operator --(); LUtf8Ptr &operator --(const int i); LUtf8Ptr &operator -=(const ssize_t n); // Comparison bool operator <(const LUtf8Ptr &p) { return Ptr < p.Ptr; } bool operator <=(const LUtf8Ptr &p) { return Ptr <= p.Ptr; } bool operator >(const LUtf8Ptr &p) { return Ptr > p.Ptr; } bool operator >=(const LUtf8Ptr &p) { return Ptr >= p.Ptr; } bool operator ==(const LUtf8Ptr &p) { return Ptr == p.Ptr; } bool operator !=(const LUtf8Ptr &p) { return Ptr != p.Ptr; } ptrdiff_t operator -(const LUtf8Ptr &p) { return Ptr - p.Ptr; } /// Gets the bytes between the cur pointer and the end of the buffer or string. int GetBytes(); /// Gets the characters between the cur pointer and the end of the buffer or string. int GetChars(); /// Encodes a utf-8 char at the current location and moves the pointer along void Add(wchar_t c); /// Returns the current pointer. uint8_t *GetPtr() { return Ptr; } }; /// Unicode string class. Allows traversing a utf-8 strings. -class LgiClass GUtf8Str : public LUtf8Ptr +class LgiClass LUtf8Str : public LUtf8Ptr { // Complete buffer uint8_t *Start; uint8_t *End; LUtf8Ptr Cur; bool Own; void Empty(); public: /// Constructor - GUtf8Str + LUtf8Str ( /// The string pointer to start with char *utf, /// The number of bytes containing characters, or -1 if NULL terminated. int bytes = -1, /// Copy the string first bool Copy = false ); /// Constructor - GUtf8Str + LUtf8Str ( /// The string pointer to start with. A utf-8 copy of the string will be created. wchar_t *wide, /// The number of wide chars, or -1 if NULL terminated. int chars = -1 ); - ~GUtf8Str(); + ~LUtf8Str(); /// Assign a new pointer to the string - GUtf8Str &operator =(char *s); + LUtf8Str &operator =(char *s); /// Allocates a block of memory containing the wide representation of the string. wchar_t *ToWide(); /// \returns true if the class seems to be valid. bool Valid(); /// \returns true if at the start bool IsStart(); /// \returns true if at the end bool IsEnd(); }; // Converts character to lower case template T Tolower(T ch) { if (ch >= 'A' && ch <= 'Z') return ch - 'A' + 'a'; return ch; } template T *Strlwr(T *Str) { if (!Str) return NULL; for (T *s = Str; *s; s++) { if (*s >= 'A' && *s <= 'Z') *s = *s - 'A' + 'a'; } return Str; } // Converts character to upper case template T Toupper(T ch) { if (ch >= 'a' && ch <= 'z') return ch - 'a' + 'A'; return ch; } template T *Strupr(T *Str) { if (!Str) return NULL; for (T *s = Str; *s; s++) { if (*s >= 'a' && *s <= 'z') *s = *s - 'a' + 'A'; } return Str; } // Finds the length of the string in characters template ssize_t Strlen(const T *str) { if (!str) return 0; REG const T *s = str; while (*s) s++; return s - str; } // Templated version of NewStr/NewStrW // Duplicates a string in heap memory. template T *Strdup(const T *s, ssize_t len = -1) { if (!s) return NULL; if (len < 0) len = Strlen(s); T *n = new T[len+1]; if (!n) return NULL; memcpy(n, s, sizeof(T) * len); n[len] = 0; return n; } // Compares two strings, case sensitive template int Strcmp(const T *str_a, const T *str_b) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; while (true) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; } return 0; } // Compares the first 'len' chars of two strings, case sensitive template int Strncmp(const T *str_a, const T *str_b, ssize_t len) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; REG const T *end = a + len; while (a < end) { if (!*a || !*b || *a != *b) return *a - *b; a++; b++; } return 0; } // Compares two strings, case insensitive template int Stricmp(const T *str_a, const T *str_b) { if (!str_a || !str_b) return -1; REG const T *a = str_a; REG const T *b = str_b; REG T ach, bch; while (true) { ach = Tolower(*a); bch = Tolower(*b); if (!ach || !bch || ach != bch) return ach - bch; a++; b++; } return 0; } // Compares the first 'len' chars of two strings, case insensitive template int Strnicmp(const T *str_a, const T *str_b, ssize_t len) { if (!str_a || !str_b || len == 0) return -1; REG const T *a = str_a; REG const T *b = str_b; REG const T *end = a + len; REG T ach, bch; while (a < end) { ach = Tolower(*a); bch = Tolower(*b); if (!ach || !bch || ach != bch) return ach - bch; a++; b++; } return 0; } /// Copies a string template T *Strcpy(T *dst, ssize_t dst_len, const I *src) { if (!dst || !src || dst_len == 0) return NULL; REG T *d = dst; REG T *end = d + dst_len - 1; // leave 1 char for NULL terminator REG const I *s = src; while (d < end && *s) { *d++ = *s++; } *d = 0; // NULL terminate return dst; } /// Finds the first instance of a character in the string template T *Strchr(T *str, int ch) { if (!str) return NULL; for (REG T *s = str; *s; s++) { if (*s == ch) return s; } return NULL; } /// Finds the first instance of a character in the string template T *Strnchr(T *str, int ch, size_t len) { if (!str || len == 0) return NULL; REG T *e = str + len; for (REG T *s = str; s < e; s++) { if (*s == ch) return s; } return NULL; } /// Finds the last instance of a character in the string template T *Strrchr(T *str, int ch) { if (!str) return NULL; T *last = NULL; for (REG T *s = str; *s; s++) { if (*s == ch) last = s; } return last; } /// Appends a string to another template T *Strcat(T *dst, int dst_len, const T *postfix) { if (!dst || !postfix || dst_len < 1) return NULL; // Find the end of the string to append to while (*dst) { dst++; dst_len--; } // Reuse string copy at this point Strcpy(dst, dst_len, postfix); // Return the start of the complete string return dst; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Stristr(const T *Data, const T *Value) { if (!Data || !Value) return NULL; const T v = Tolower(*Value); while (*Data) { if (Tolower(*Data) == v) { int i; for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++) ; if (Value[i] == 0) return (T*)Data; } Data++; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strstr(const T *Data, const T *Value) { if (!Data || !Value) return NULL; const T v = *Value; while (*Data) { if (*Data == v) { int i; for (i=1; Data[i] && Data[i] == Value[i]; i++) ; if (Value[i] == 0) return (T*)Data; } Data++; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strnstr(const T *Data, const T *Value, ssize_t DataLen) { if (!Data || !Value) return NULL; const T v = *Value; ptrdiff_t ValLen = Strlen(Value); if (ValLen > DataLen) return NULL; while (*Data && DataLen >= ValLen) { if (*Data == v) { int i; for (i=1; Data[i] && Data[i] == Value[i]; i++) ; if (Value[i] == 0) return (T*)Data; } Data++; DataLen--; } return NULL; } /// Searches the string 'Data' for the 'Value' in a case insensitive manner template T *Strnistr(const T *Data, const T *Value, ptrdiff_t DataLen) { if (!Data || !Value) return NULL; const T v = Tolower(*Value); ptrdiff_t ValLen = Strlen(Value); if (ValLen > DataLen) return NULL; while (*Data && DataLen >= ValLen) { if (Tolower(*Data) == v) { int i; for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++) ; if (Value[i] == 0) return (T*)Data; } Data++; DataLen--; } return NULL; } /// Converts a string to int64 (base 10) template int64 Atoi(const T *s, int Base = 10, int64 DefaultValue = -1) { if (!s) return DefaultValue; bool Minus = false; if (*s == '-') { Minus = true; s++; } else if (*s == '+') s++; int64 v = 0; const T *Start = s; if (Base <= 10) { while (*s >= '0' && *s <= '9') { int d = *s - '0'; v *= Base; v += d; s++; } } else { if (*s == '0' && Tolower(s[1]) == 'x') s += 2; int ValidChars = Base > 10 ? Base - 10 : 0; while (*s) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'a' + ValidChars) d = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'A' + ValidChars) d = *s - 'A' + 10; else break; v *= Base; v += d; s++; } } if (s == Start) return DefaultValue; return Minus ? -v : v; } /// Works out the UTF8 length of a wide char string inline size_t WideToUtf8Len(const wchar_t *s, ssize_t wchars = -1) { if (!s) return 0; size_t Out = 0; uint8_t Buf[6]; #ifdef _MSC_VER const uint16 *i = (const uint16*) s; ssize_t Len = wchars >= 0 ? wchars << 1 : 0x7fffffff; for (uint32_t ch; ch = LgiUtf16To32(i, Len); ) { uint8_t *b = Buf; ssize_t len = sizeof(Buf); if (!LgiUtf32To8(ch, b, len)) break; Out += sizeof(Buf) - len; } #else const wchar_t *end = wchars < 0 ? NULL : s + wchars; for (uint32_t ch = 0; ( wchars < 0 || s < end ) && (ch = *s); s++) { uint8_t *b = Buf; ssize_t len = sizeof(Buf); if (!LgiUtf32To8(ch, b, len)) break; Out += sizeof(Buf) - len; } #endif return Out; } /// Converts a utf-8 string into a wide character string /// \ingroup Text LgiFunc wchar_t *Utf8ToWide ( /// Input string const char *In, /// [Optional] Size of 'In' in 'chars' or -1 for NULL terminated ssize_t InLen = -1 ); /// Converts a wide character string into a utf-8 string /// \ingroup Text LgiFunc char *WideToUtf8 ( /// Input string const wchar_t *In, /// [Optional] Number of wchar_t's in the input or -1 for NULL terminated ptrdiff_t InLen = -1 ); #endif diff --git a/include/lgi/common/Window.h b/include/lgi/common/Window.h --- a/include/lgi/common/Window.h +++ b/include/lgi/common/Window.h @@ -1,313 +1,314 @@ #ifndef _LWINDOW_H_ #define _LWINDOW_H_ #include "lgi/common/View.h" /// The available states for a top level window enum LWindowZoom { /// Minimized LZoomMin, /// Restored/Normal LZoomNormal, /// Maximized LZoomMax }; enum LWindowHookType { LNoEvents = 0, /// \sa LWindow::RegisterHook() LMouseEvents = 1, /// \sa LWindow::RegisterHook() LKeyEvents = 2, /// \sa LWindow::RegisterHook() LKeyAndMouseEvents = LMouseEvents | LKeyEvents, }; /// A top level window. class LgiClass LWindow : public LView, // This needs to be second otherwise is causes v-table problems. #ifndef LGI_SDL virtual #endif public LDragDropTarget { friend class BViewRedir; friend class LApp; friend class LView; friend class LButton; friend class LDialog; friend class LWindowPrivate; friend struct LDialogPriv; bool _QuitOnClose; protected: class LWindowPrivate *d; #if WINNATIVE LRect OldPos; LWindow *_Dialog; #elif defined(HAIKU) LWindowZoom _PrevZoom = LZoomNormal; #else OsWindow Wnd; void SetDeleteOnClose(bool i); #endif #if defined __GTK_H__ friend class LMenu; friend void lgi_widget_size_allocate(Gtk::GtkWidget *widget, Gtk::GtkAllocation *allocation); Gtk::GtkWidget *_Root, *_VBox, *_MenuBar; void OnGtkDelete(); Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); #elif defined(LGI_CARBON) friend pascal OSStatus LgiWindowProc(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); void _Delete(); bool _RequestClose(bool os); #elif defined(__OBJC__) public: // This returns the root level content NSView NSView *Handle(); protected: #endif /// The default button LViewI *_Default; /// The menu on the window LMenu *Menu; void SetChildDialog(LDialog *Dlg); void SetDragHandlers(bool On); /// Haiku: This shuts down the window's thread cleanly. int WaitThread(); public: #ifdef _DEBUG LMemDC DebugDC; #endif #ifdef __GTK_H__ LWindow(Gtk::GtkWidget *w = NULL); #elif LGI_CARBON LWindow(WindowRef wr = NULL); #elif LGI_COCOA LWindow(OsWindow wnd = NULL); #else LWindow(); #endif ~LWindow(); const char *GetClass() override { return "LWindow"; } /// Lays out the child views into the client area. virtual void PourAll(); /// Returns the current menu object LMenu *GetMenu() { return Menu; } /// Set the menu object. void SetMenu(LMenu *m) { Menu = m; } /// Set the window's icon bool SetIcon(const char *FileName); /// Gets the "quit on close" setting. bool GetQuitOnClose() { return _QuitOnClose; } /// \brief Sets the "quit on close" setting. /// /// When this is switched on the application will quit the main message /// loop when this LWindow is closed. This is really useful for your /// main application window. Otherwise the UI will disappear but the /// application is still running. void SetQuitOnClose(bool i) { _QuitOnClose = i; } bool GetSnapToEdge(); void SetSnapToEdge(bool b); bool GetAlwaysOnTop(); void SetAlwaysOnTop(bool b); /// Gets the current zoom setting LWindowZoom GetZoom(); /// Sets the current zoom void SetZoom(LWindowZoom i); /// Raises the window to the top of the stack. void Raise(); /// Moves a top level window on screen. void MoveOnScreen(); /// Moves a top level to the center of the screen void MoveToCenter(); /// Moves a top level window to where the mouse is void MoveToMouse(); /// Moves the window to somewhere on the same screen as 'wnd' bool MoveSameScreen(LViewI *wnd); // Focus setting LViewI *GetFocus(); enum FocusType { GainFocus, LoseFocus, ViewDelete }; void SetFocus(LViewI *ctrl, FocusType type); /// Registers a watcher to receive OnView... messages before they /// are passed through to the intended recipient. bool RegisterHook ( /// The target view. LView *Target, /// Combination of: /// #LMouseEvents - Where Target->OnViewMouse(...) is called for each click. /// and /// #LKeyEvents - Where Target->OnViewKey(...) is called for each key. /// OR'd together. LWindowHookType EventType, /// Not implemented int Priority = 0 ); /// Unregisters a hook target bool UnregisterHook(LView *Target); /// Gets the default view LViewI *GetDefault(); /// Sets the default view void SetDefault(LViewI *v); /// Saves/loads the window's state, e.g. position, minimized/maximized etc bool SerializeState ( /// The data store for reading/writing LDom *Store, /// The field name to use for storing settings under const char *FieldName, /// TRUE if loading the settings into the window, FALSE if saving to the store. bool Load ); /// Builds a map of keyboard short cuts. typedef LHashTbl,LViewI*> ShortcutMap; void BuildShortcuts(ShortcutMap &Map, LViewI *v = NULL); ////////////////////// Events /////////////////////////////// /// Called when the window zoom state changes. virtual void OnZoom(LWindowZoom Action) {} /// Called when the tray icon is clicked. (if present) virtual void OnTrayClick(LMouse &m); /// Called when the tray icon menu is about to be displayed. virtual void OnTrayMenu(LSubMenu &m) {} /// Called when the tray icon menu item has been selected. virtual void OnTrayMenuResult(int MenuId) {} /// Called when files are dropped on the window. virtual void OnReceiveFiles(LArray &Files) {} /// Called when a URL is sent to the window virtual void OnUrl(const char *Url) {}; ///////////////// Implementation //////////////////////////// void OnPosChange() override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; bool HandleViewMouse(LView *v, LMouse &m); bool HandleViewKey(LView *v, LKey &k); /// Return true to accept application quit bool OnRequestClose(bool OsShuttingDown) override; bool Obscured(); bool Visible() override; void Visible(bool i) override; bool IsActive(); bool SetActive(); LRect &GetPos() override; void SetDecor(bool Visible); LPoint GetDpi(); LPointF GetDpiScale(); + void ScaleSizeToDpi(); // D'n'd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; #if !WINNATIVE bool Attach(LViewI *p) override; // Props #if defined(HAIKU) OsWindow WindowHandle() override; #else OsWindow WindowHandle() override { return Wnd; } #endif bool Name(const char *n) override; const char *Name() override; bool SetPos(LRect &p, bool Repaint = false) override; LRect &GetClient(bool InClientSpace = true) override; // Events void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; void OnCreate() override; virtual void OnFrontSwitch(bool b); #else OsWindow WindowHandle() override { return _View; } #endif #if defined(LGI_SDL) virtual bool PushWindow(LWindow *v); virtual LWindow *PopWindow(); #elif defined __GTK_H__ void OnGtkRealize(); bool IsAttached(); void Quit(bool DontDelete = false); LRect *GetDecorSize(); bool TranslateMouse(LMouse &m); LViewI *WindowFromPoint(int x, int y, bool Debug = false); void _SetDynamic(bool b); void _OnViewDelete(); #elif defined(MAC) bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) override; void Quit(bool DontDelete = false) override; int OnCommand(int Cmd, int Event, OsView Wnd) override; LViewI *WindowFromPoint(int x, int y, int DebugDebug = 0) override; #if defined(LGI_CARBON) OSErr HandlerCallback(DragTrackingMessage *tracking, DragRef theDrag); #endif #endif }; #endif diff --git a/src/common/Coding/Instructions.h b/src/common/Coding/Instructions.h --- a/src/common/Coding/Instructions.h +++ b/src/common/Coding/Instructions.h @@ -1,1929 +1,2037 @@ /* This file is included in both the LVirtualMachinePriv::Execute and LVirtualMachinePriv::Decompile That way the "parsing" of instructions is the same. During decompile the define VM_DECOMP is active. During execution the define VM_EXECUTE is active. */ #ifdef VM_EXECUTE #define Resolve() &Scope[c.r->Scope][c.r->Index]; c.r++ #define GResolveRef(nm) LVariant *nm = #else #define Resolve() c.r++ #define GResolveRef(nm) -// GVarRef * +// LVarRef * #endif default: { #if VM_DECOMP if (Log) Log->Print("\t%p Unknown instruction %i (0x%x)\n", CurrentScriptAddress - 1, c.u8[-1], c.u8[-1]); #endif OnException(_FL, CurrentScriptAddress, "Unknown instruction"); SetScriptError; break; } case INop: { #if VM_DECOMP if (Log) Log->Print("%p Nop\n", CurrentScriptAddress - 1); #endif break; } case ICast: { #if VM_DECOMP if (Log) Log->Print("%p Cast %s", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); uint8_t Type = *c.u8++; #if VM_DECOMP if (Log) Log->Print(" to %s\n", LVariant::TypeToString((LVariantType)Type)); #endif #if VM_EXECUTE switch (Type) { case GV_INT32: { *Var = Var->CastInt32(); break; } case GV_STRING: { *Var = Var->CastString(); break; } case GV_DOM: { *Var = Var->CastDom(); break; } case GV_DOUBLE: { *Var = Var->CastDouble(); break; } case GV_INT64: { *Var = Var->CastInt32(); break; } case GV_BOOL: { *Var = Var->CastBool(); break; } default: { LString s; s.Printf("%s ICast warning: unknown type %i/%s\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type, LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAssign: { #if VM_DECOMP if (Log) Log->Print("%p Assign %s <- %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE CheckParam(Dst != Src); *Dst = *Src; #endif break; } case IJump: { #if VM_DECOMP if (Log) Log->Print("%p Jump by %i (to 0x%x)\n", CurrentScriptAddress - 1, c.i32[0], CurrentScriptAddress + 4 + c.i32[0]); #endif #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); c.u8 += Jmp; #endif break; } case IJumpZero: { #if VM_DECOMP if (Log) Log->Print("%p JumpZ(%s) by 0x%x\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.i32[1]); #endif GResolveRef(Exp) Resolve(); #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); if (!Exp->CastInt32()) c.u8 += Jmp; #endif break; } // case IUnaryPlus: case IUnaryMinus: { #if VM_DECOMP if (Log) Log->Print("%p UnaryMinus %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_DOUBLE: *Var = -Var->CastDouble(); break; case GV_INT64: *Var = -Var->CastInt64(); break; default: *Var = -Var->CastInt32(); break; } #endif break; } case IPlus: case IPlusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Plus %s += %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE if (Dst->Str()) { size_t dlen = strlen(Dst->Str()); char *ss; LVariant SrcTmp; switch (Src->Type) { case GV_NULL: ss = (char*)"(null)"; break; case GV_STRING: ss = Src->Str(); break; default: SrcTmp = *Src; ss = SrcTmp.CastString(); break; } if (ss) { size_t slen = strlen(ss); char *s = new char[slen + dlen + 1]; if (s) { memcpy(s, Dst->Value.String, dlen); memcpy(s + dlen, ss, slen); s[dlen + slen] = 0; DeleteArray(Dst->Value.String); Dst->Value.String = s; } } } else switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() + Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() + Src->CastInt64(); break; default: *Dst = Dst->CastInt32() + Src->CastInt32(); break; } #endif break; } case IMinus: case IMinusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Minus %s -= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() - Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() - Src->CastInt64(); break; default: *Dst = Dst->CastInt32() - Src->CastInt32(); break; } #endif break; } case IMul: case IMulEquals: { #if VM_DECOMP if (Log) Log->Print("%p Mul %s *= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() * Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() * Src->CastInt64(); break; default: *Dst = Dst->CastInt32() * Src->CastInt32(); break; } #endif break; } case IDiv: case IDivEquals: { #if VM_DECOMP if (Log) Log->Print("%p Div %s /= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() / Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() / Src->CastInt64(); break; default: *Dst = Dst->CastInt32() / Src->CastInt32(); break; } #endif break; } case IMod: { #if VM_DECOMP if (Log) Log->Print("%p Mod %s %%= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = fmod(Dst->CastDouble(), Src->CastDouble()); break; case GV_INT64: *Dst = Dst->CastInt64() % Src->CastInt64(); break; default: *Dst = Dst->CastInt32() % Src->CastInt32(); break; } #endif break; } case IPostInc: case IPreInc: { #if VM_DECOMP if (Log) Log->Print("%p PostInc %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl + 1; break; case GV_INT64: *v = v->Value.Int64 + 1; break; default: *v = v->CastInt32() + 1; break; } #endif break; } case IPostDec: case IPreDec: { #if VM_DECOMP if (Log) Log->Print("%p PostDec %sn", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl - 1; break; case GV_INT64: *v = v->Value.Int64 - 1; break; default: *v = v->CastInt32() - 1; break; } #endif break; } case IEquals: { #if VM_DECOMP if (Log) Log->Print("%p %s == %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) == 0; #endif break; } case INotEquals: { #if VM_DECOMP if (Log) Log->Print( "%p %s != %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) != 0; #endif break; } case ILessThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) < 0; #endif break; } case ILessThanEqual: { #if VM_DECOMP if (Log) Log->Print( "%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) <= 0; #endif break; } case IGreaterThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) > 0; #endif break; } case IGreaterThanEqual: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) >= 0; #endif break; } case ICallMethod: { LFunc *Meth = *c.fn++; if (!Meth) { Log->Print( "%s ICallMethod error: No method struct.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(Meth))); SetScriptError; break; } #ifdef VM_DECOMP if (Log) { Log->Print("%p Call: %s = %s(", CurrentScriptAddress - sizeof(Meth) - 1, c.r[0].GetStr(), Meth->Method.Get()); } #endif GResolveRef(Ret) Resolve(); uint16 Args = *c.u16++; #ifdef VM_EXECUTE LScriptArguments Arg(Vm, Ret); #endif for (int i=0; iPrint("%s%s", i?", ":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Arg[i] = Resolve(); CheckParam(Arg[i] != NULL); #else c.r++; #endif } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #if VM_EXECUTE GHostFunc *Hf = dynamic_cast(Meth); if (Hf) { if (!(Hf->Context->*(Hf->Func))(Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } else { // Fixme if (!Meth->Call(NULL, Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } #endif break; } case ICallScript: { int32 FuncAddr = *c.i32++; if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length()) { Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)), FuncAddr); SetScriptError; break; } uint16 Frame = *c.u16++; #if VM_DECOMP if (Log) Log->Print("%p CallScript: %s = %p(frame=%i)(", CurrentScriptAddress - 5, c.r[0].GetStr(), FuncAddr, Frame); #endif #ifdef VM_EXECUTE // Set up stack for function call int CurFrameSize = Frames.Last().CurrentFrameSize; StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = Frame; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = *c.r++; if (Sf.ReturnValue.Scope == SCOPE_LOCAL) Sf.ReturnValue.Index -= CurFrameSize; uint16 Args = *c.u16++; // Increase the local stack size size_t LocalsBase = Locals.Length(); size_t LocalsPos = Scope[SCOPE_LOCAL] - Locals.AddressOf(); Locals.SetFixedLength(false); Locals.Length(LocalsBase + Frame); Locals.SetFixedLength(); Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsPos); // Put the arguments of the function call into the local array LArray Arg; #else GResolveRef(Ret) Resolve(); int Args = *c.u16++; #endif for (int i=0; iPrint("%s%s", i?",":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Locals[LocalsBase+i] = *Resolve(); #else c.r++; #endif } #if VM_EXECUTE // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + FuncAddr; Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsBase); // This can evaluation to NULL when there is NO locals. #endif #if VM_DECOMP if (Log) Log->Print(")\n"); #endif break; } case IRet: { #if VM_DECOMP if (Log) Log->Print("%p Ret %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(ReturnValue) Resolve(); #ifdef VM_EXECUTE if (Frames.Length() > 0) { StackFrame Sf = Frames[Frames.Length()-1]; - GVarRef &Ret = Sf.ReturnValue; + LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; // LgiTrace("IRet to %i:%i\n", Ret.Scope, Ret.Index); if (Ret.Scope == SCOPE_LOCAL) LAssert(Locals.PtrCheck(RetVar)); *RetVar = *ReturnValue; CheckParam(RetVar->Type == ReturnValue->Type); Frames.Length(Frames.Length()-1); Locals.SetFixedLength(false); if (Locals.Length() >= Sf.CurrentFrameSize) { ssize_t Base = Locals.Length() - Sf.CurrentFrameSize; if (ArgsOutput) { if (Frames.Length() == 0) { for (unsigned i=0; iLength(); i++) { *(*ArgsOutput)[i] = Locals[Base+i]; } } } // LgiTrace("%s:%i Locals %i -> %i\n", _FL, Locals.Length(), Base); Locals.Length(Base); Scope[SCOPE_LOCAL] = &Locals[Sf.PrevFrameStart]; } else { // LgiTrace("%s:%i - Locals %i -> %i\n", _FL, Locals.Length(), 0); Locals.Length(0); Scope[SCOPE_LOCAL] = NULL; } Locals.SetFixedLength(); c.u8 = Base + Sf.ReturnIp; } else { ExitScriptExecution; } #endif break; } case IArrayGet: { #if VM_DECOMP if (Log) Log->Print( "%p ArrayGet %s = %s[%s]\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); LVariant *t = Var->Value.Lst->ItemAt(Idx->CastInt32()); if (t) { if (Var == Dst) { if (Var->Value.Lst->Delete(t)) { *Var = *t; DeleteObj(t); } else CheckParam(!"List delete failed."); } else *Dst = *t; } else Dst->Empty(); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *t = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); if (t) *Dst = *t; else Dst->Empty(); break; } case GV_CUSTOM: { LCustomType *T = Var->Value.Custom.Dom; size_t Sz = T->Sizeof(); int Index = Idx->CastInt32(); Dst->Type = GV_CUSTOM; Dst->Value.Custom.Dom = T; Dst->Value.Custom.Data = Var->Value.Custom.Data + (Sz * Index); break; } case GV_STRING: { auto c = Var->Str(); auto i = Idx->CastInt64(); if (!c || i < 0) break; LUtf8Ptr p(c); uint32_t ch; do { ch = p; if (i-- == 0) { *Dst = ch; break; } p++; } while (ch); break; } default: { LString s; s.Printf("%s IArrayGet warning: Can't array deref variant type %i\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IArraySet: { #if VM_DECOMP if (Log) Log->Print( "%p ArraySet %s[%s] = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); GResolveRef(Val) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); (*Var->Value.Lst).Insert(new LVariant(*Val), Idx->CastInt32()); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *Old = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); DeleteObj(Old); Var->Value.Hash->Add(Idx->CastString(), new LVariant(*Val)); break; } default: { LString s; s.Printf("%s IArraySet warning: Can't dereference type '%s'\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAnd: { #if VM_DECOMP if (Log) Log->Print("%p %s && %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) && (Src->CastInt32() != 0); #endif break; } case IOr: { #if VM_DECOMP if (Log) Log->Print("%p %s || %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) || (Src->CastInt32() != 0); #endif break; } case INot: { #if VM_DECOMP if (Log) Log->Print("%p %s = !%s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[0].GetStr()); #endif GResolveRef(Dst) Resolve(); #ifdef VM_EXECUTE *Dst = !Dst->CastBool(); #endif break; } case IDomGet: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomGet(%s, %s)\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); #ifdef VM_EXECUTE // Return "NULL" in Dst on error if (Dst != Dom) Dst->Empty(); switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = dom->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = Dom->Value.Date->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (Name->Type == GV_INT32) Fld = Name->Value.Int; else Fld = Type->IndexOf(Name->Str()); int Index = Arr ? Arr->CastInt32() : 0; Type->Get(Fld, *Dst, Dom->Value.Custom.Data, Index); } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Lst->Length(); break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Hash->Length(); break; } case GV_BINARY: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = Dom->Value.Binary.Length; break; } + case GV_INT32: + { + char *sName = Name->Str(); + CheckParam(sName); + LDomProperty p = LStringToDomProp(sName); + switch (p) + { + case TypeString: + { + char s[32]; + sprintf_s(s, sizeof(s), "%i", Dom->Value.Int); + *Dst = s; + break; + } + case TypeDouble: + { + *Dst = (double)Dom->Value.Int; + break; + } + default: + { + Dst->Empty(); + LString s; + s.Printf("%s IDomGet warning: Unexpected int32 member '%s'.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + sName); + if (Log) + Log->Write(s, s.Length()); + if (LVirtualMachine::BreakOnWarning) + OnException(NULL, -1, CurrentScriptAddress, s); + Status = ScriptWarning; + break; + } + } + break; + } + case GV_INT64: + { + char *sName = Name->Str(); + CheckParam(sName); + LDomProperty p = LStringToDomProp(sName); + switch (p) + { + case TypeString: + { + char s[32]; + sprintf_s(s, sizeof(s), LPrintfInt64, Dom->Value.Int64); + *Dst = s; + break; + } + case TypeDouble: + { + *Dst = (double)Dom->Value.Int64; + break; + } + default: + { + Dst->Empty(); + LString s; + s.Printf("%s IDomGet warning: Unexpected int64 member '%s'.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + sName); + if (Log) + Log->Write(s, s.Length()); + if (LVirtualMachine::BreakOnWarning) + OnException(NULL, -1, CurrentScriptAddress, s); + Status = ScriptWarning; + break; + } + } + break; + } + case GV_DOUBLE: + { + char *sName = Name->Str(); + CheckParam(sName); + LDomProperty p = LStringToDomProp(sName); + switch (p) + { + case TypeString: + { + char s[32]; + sprintf_s(s, sizeof(s), "%g", Dom->Value.Dbl); + *Dst = s; + break; + } + case TypeInt: + { + *Dst = (int64)Dom->Value.Dbl; + break; + } + default: + { + Dst->Empty(); + LString s; + s.Printf("%s IDomGet warning: Unexpected double member '%s'.\n", + Code->AddrToSourceRef(CurrentScriptAddress), + sName); + if (Log) + Log->Write(s, s.Length()); + if (LVirtualMachine::BreakOnWarning) + OnException(NULL, -1, CurrentScriptAddress, s); + Status = ScriptWarning; + break; + } + } + break; + } case GV_STRING: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { (*Dst) = (int)strlen(Dom->Str()); break; } case TypeInt: { (*Dst) = Dom->CastInt32(); break; } case TypeDouble: { (*Dst) = Dom->CastDouble(); break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected string member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_NULL: { LString s; s.Printf("%s IDomGet warning: Can't deref NULL object.\n", Code->AddrToSourceRef(CurrentScriptAddress)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } default: { LString s; s.Printf("%s IDomGet warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), _FL, CurrentScriptAddress); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IDomSet: { #if VM_DECOMP if (Log) Log->Print("%p %s->DomSet(%s, %s) = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); GResolveRef(Value) Resolve(); #ifdef VM_EXECUTE char *sName = Name->Str(); if (!sName) { if (Log) Log->Print("%s IDomSet error: No name string.\n", Code->AddrToSourceRef(CurrentScriptAddress)); SetScriptError; break; } switch (Dom->Type) { case GV_DOM: // case GV_GFILE: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); bool Ret = dom->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); bool Ret = Dom->Value.Date->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (IsDigit(*sName)) Fld = atoi(sName); else Fld = Type->IndexOf(sName); int Index = Arr ? Arr->CastInt32() : 0; if (!Type->Set(Fld, *Value, Dom->Value.Custom.Data, Index) && Log) { Log->Print("%s IDomSet warning: Couldn't set '%s' on custom type.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); } } break; } case GV_STRING: { LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { char *s; int DLen = Value->CastInt32(); if (DLen && (s = new char[DLen+1])) { size_t SLen = Dom->Str() ? strlen(Dom->Str()) : 0; if (SLen) memcpy(s, Dom->Str(), SLen); memset(s+SLen, ' ', DLen-SLen); s[DLen] = 0; DeleteArray(Dom->Value.String); Dom->Value.String = s; } else Dom->Empty(); break; } case TypeInt: { *Dom = Value->CastInt32(); Dom->Str(); break; } case TypeDouble: { *Dom = Value->CastDouble(); Dom->Str(); break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected string member %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected type %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type)); Status = ScriptWarning; break; } } #endif break; } case IDomCall: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomCall(%s, ", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #else - GVarRef DstRef = *c.r; + LVarRef DstRef = *c.r; #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); #ifdef VM_EXECUTE GResolveRef(Args) Resolve(); int ArgCount = Args->CastInt32(); char *sName = Name->Str(); CheckParam(sName) if (Dom->Type == GV_CUSTOM) { #define DEBUG_CUSTOM_METHOD_CALL 1 LCustomType *t = Dom->Value.Custom.Dom; CheckParam(t); LCustomType::Method *m = t->GetMethod(sName); CheckParam(m); CheckParam(m->Params.Length() == ArgCount); // Set up new stack frame... StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = m->FrameSize; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = DstRef; // Increase the local stack size AddLocalSize(m->FrameSize + 1); #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ", sName, ArgCount, m->FrameSize, m->Address, LocalsBase); #endif size_t i = LocalsBase; Locals[i++] = *Dom; // this pointer... #if DEBUG_CUSTOM_METHOD_CALL LString s = Locals[i-1].ToString(); LgiTrace("This=%s, ", s.Get()); #endif size_t end = i + ArgCount; while (i < end) { Locals[i++] = *Resolve(); #if DEBUG_CUSTOM_METHOD_CALL s = Locals[i-1].ToString(); LgiTrace("[%i]=%s, ", i-1, s.Get()); #endif } // Now adjust the local stack to point to the locals for the function Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL; // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + m->Address; #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("\n"); #endif break; } LArray Arg; Arg.Length(ArgCount); for (int i=0; iType); } else switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom); bool Ret = dom->CallMethod(sName, Dst, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date); bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); switch (p) { case ObjLength: { *Dst = (int64)Dom->Value.Lst->Length(); break; } case ContainerAdd: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Lst->Insert(v, Index); } break; } case ContainerDelete: { for (unsigned i=0; iCastInt32(); LVariant *Elem = Dom->Value.Lst->ItemAt(n); if (Elem) { Dom->Value.Lst->Delete(Elem); DeleteObj(Elem); } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg[0]->CastInt32(); *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); } else { *Dst = false; } break; } case ContainerSort: { LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL; Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param); break; } default: { Dst->Empty(); if (Log) Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); switch (p) { case ObjLength: { *Dst = Dom->Value.Hash->Length(); break; } case ContainerAdd: { if (Arg.Length() == 2 && Arg[0] && Arg[1]) { char *Key = Arg[1]->Str(); if (Key) { LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Hash->Add(Key, v); } } break; } case ContainerDelete: { if (Arg.Length() == 1 && Arg[0]) { char *Key = Arg[0]->Str(); if (Key) { LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key); if (v) { Dom->Value.Hash->Delete(Key); delete v; } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { char *Key = Arg[0]->Str(); *Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL); } else { *Dst = false; } break; } default: { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_BINARY: { switch (p) { default: break; case ObjLength: *Dst = Dom->Value.Binary.Length; break; } break; } case GV_STRING: { if (Arg.Length() > 0 && !Arg[0]) { Dst->Empty(); break; } switch (p) { case ObjLength: { char *s = Dom->Str(); *Dst = (int) (s ? strlen(s) : 0); break; } case StrJoin: { switch (Arg[0]->Type) { case GV_LIST: { LStringPipe p(256); List *Lst = Arg[0]->Value.Lst; const char *Sep = Dom->CastString(); auto It = Lst->begin(); LVariant *v = *It; if (v) { LVariant Tmp = *v; p.Print("%s", Tmp.CastString()); while ((v = *(++It))) { Tmp = *v; p.Print("%s%s", Sep, Tmp.CastString()); } } Dst->OwnStr(p.NewStr()); break; } default: { *Dst = *Arg[0]; Dst->CastString(); break; } } break; } case StrSplit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); size_t SepLen = strlen(Sep); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = strstr(c, Sep); if (!next) break; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Value.Lst->Insert(v); c = next + SepLen; } if (c && *c) { LVariant *v = new LVariant; v->OwnStr(NewStr(c)); Dst->Value.Lst->Insert(v); } break; } case StrSplitDelimit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = c; while (*next && !strchr(Sep, *next)) next++; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Add(v); for (c = next; *c && strchr(Sep, *c); c++) ; } if (c && *c) Dst->Add(new LVariant(c)); break; } case StrFind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = strlen(s); const char *sub = Arg[0]->Str(); int start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; int end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start >= sLen) { *Dst = -1; break; } char *sStart = (char*)s + start; char *pos; if (end >= 0) pos = Strnstr(sStart, sub, end); else pos = strstr(sStart, sub); if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrRfind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = strlen(s); const char *sub = Arg[0]->Str(); int start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; int end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start_idx >= sLen) { *Dst = -1; break; } size_t sublen = strlen(sub); char *cur = (char*)s + start_idx; char *end = end_idx >= 0 ? cur + end_idx : NULL; char *pos = NULL; while (true) { cur = (end) ? Strnstr(cur, sub, end - cur) : strstr(cur, sub); if (cur) { pos = cur; cur += sublen; } else break; } if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrLower: { if (Dst != Dom) *Dst = Dom->CastString(); StrLwr(Dst->Str()); break; } case StrUpper: { if (Dst != Dom) *Dst = Dom->CastString(); StrUpr(Dst->Str()); break; } case StrStrip: { char *s = Dom->Str(); if (s) { const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL; if (!Delimit) Delimit = WhiteSpace; char *start = s; char *end = s + strlen(s); while (start < end && strchr(Delimit, *start)) start++; while (end > start && strchr(Delimit, end[-1])) end--; Dst->OwnStr(NewStr(start, end - start)); } else Dst->Empty(); break; } case StrSub: { char *s = Dom->Str(); if (s) { ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0; ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; ssize_t Len = strlen(s); if (End < 0 || End > Len) End = Len; if (Start < 0) Start = 0; if (Start <= End) Dst->OwnStr(NewStr(s + Start, End - Start)); else Dst->Empty(); } else Dst->Empty(); break; } default: { Dst->Empty(); if (Log) Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", CurrentScriptAddress, sName, _FL); Status = ScriptWarning; break; } } break; } default: { const char *Type = LVariant::TypeToString(Dom->Type); char t[32]; if (!Type) { sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type); Type = t; } Dst->Empty(); if (Log) { Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), Type, _FL, CurrentScriptAddress); } Status = ScriptWarning; break; } } #else LVariant *Count = NULL; switch (c.r->Scope) { case SCOPE_GLOBAL: Count = &Code->Globals[c.r->Index]; c.r++; break; default: OnException(_FL, CurrentScriptAddress, "Unsupported scope."); return ScriptError; } int Args = Count->CastInt32(); for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr()); #endif c.r++; } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #endif break; } case IBreakPoint: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE OnException(_FL, CurrentScriptAddress-1, "ShowDebugger"); return ScriptWarning; #endif break; } case IDebug: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE #ifdef WINDOWS __debugbreak(); #elif defined MAC __builtin_trap(); #elif defined LINUX Gtk::raise(SIGINT); #else #warning "Not impl." #endif #endif break; } #undef Resolve #undef GResolveRef \ No newline at end of file diff --git a/src/common/Coding/ParseCpp.cpp b/src/common/Coding/ParseCpp.cpp --- a/src/common/Coding/ParseCpp.cpp +++ b/src/common/Coding/ParseCpp.cpp @@ -1,2338 +1,2338 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/ParseCpp.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/LexCpp.h" #include "lgi/common/Variant.h" #include "lgi/common/XmlTree.h" #include "lgi/common/Token.h" #include "ScriptingPriv.h" #define CheckToken(t) if (!t) { Error = true; break; } #if defined(WINDOWS) && defined(_MSC_VER) && !defined(_WIN64) #define Debug __asm int 3 #else #define Debug assert(0) #endif static const char16 sRoundBracketStart[] = { '(', 0 }; static const char16 sRoundBracketEnd[] = { ')', 0 }; static const char16 sDefined[] = {'d','e','f','i','n','e','d',0}; static const char16 sCpp[] = {'_','_','c','p','l','u','s','p','l','u','s',0}; enum LoopState { LsLooping, LsFinished, LsError }; struct PreprocessState { bool ParentIsActive; bool IfBranchClaimed; }; enum PreprocessSymbol { SourceBlock, HashIf, HashElif, HashIfndef, HashIfdef, HashElse, HashEndif, HashPragma, HashError, HashWarning, HashDefine, HashUndef, HashInclude, }; const char *PreprocessSymbolNames[] = { "SourceBlock", "HashIf", "HashElif", "HashIfndef", "HashIfdef", "HashElse", "HashEndif", "HashPragma", "HashError", "HashWarning", "HashDefine", "HashUndef", "HashInclude", }; /// A file is parsed into a stream of these preprocessed blocks. The content /// of the block may be present in unlexed form, or parsed form. This saves /// on having to re-lex files over and over. class PreprocessBlock { - friend struct GSourceFile; + friend struct LSourceFile; LArray Lines; public: /// The type of block this is. PreprocessSymbol Type; /// The start line in the original source code of this block. int BlockLine; // Unparsed string data char16 *Start; ssize_t Len; // -or- // Lexed data int CurToken; int Line; LArray Tokens; PreprocessBlock() { Type = SourceBlock; Start = NULL; Len = 0; CurToken = 0; BlockLine = 0; // Invalid line.. } char16 *NextToken() { if (Start != NULL) - LAssert(0); // Call GSourceFile::Lex first + LAssert(0); // Call LSourceFile::Lex first if (CurToken >= Tokens.Length()) return NULL; Line = Lines[CurToken]; return Tokens[CurToken++]; } int GetLine() { if (CurToken >= Tokens.Length()) Lines.Last(); return Lines[CurToken]; } }; bool CmpToken(char16 *a, const char *b) { if (!a || !b) return false; while (*a && *b) { if (ToLower(*a) != ToLower(*b)) return false; a++; b++; } return !*a && !*b; } bool HasToken(LArray &a, const char *b) { if (!a.Length() || !b) return false; for (int i=0; i IncludePaths; LArray PreDefines; LArray Source; LAutoString SearchTerm; WorkUnit ( LArray &IncPaths, LArray &PreDefs, LArray &Src ) { int i; for (i=0; i Tokens; - GSymbol *Parent; - LArray Children; + LSymbol *Parent; + LArray Children; const char *DefineFile; int DefineLine; bool FriendDecl; bool ForwardDecl; - GSymbol(const char *def_file, int def_line) + LSymbol(const char *def_file, int def_line) { Parent = 0; Type = SymNone; File = NULL; Line = 0; FriendDecl = false; ForwardDecl = false; DefineFile = def_file; DefineLine = def_line; } - ~GSymbol() + ~LSymbol() { Children.DeleteObjects(); } - void Add(GSymbol *s) + void Add(LSymbol *s) { if (!Children.HasItem(s)) { Children.Add(s); s->Parent = this; } } }; -struct GCppStringPool +struct LCppStringPool { int Size; int Used; LArray Mem; - GCppStringPool(int sz) + LCppStringPool(int sz) { Used = Size = sz; } - ~GCppStringPool() + ~LCppStringPool() { Mem.DeleteArrays(); } char16 *Alloc(const char16 *s, ssize_t len) { if (len < 0) len = StrlenW(s); int remaining = Size - Used; char16 *n; if (len + 1 > remaining) { n = new char16[Size]; Mem.Add(n); Used = 0; } else { LAssert(Mem.Length() > 0); n = Mem.Last() + Used; } memcpy(n, s, sizeof(*s) * len); n[len] = 0; Used += len + 1; return n; } }; char16 *LexPoolAlloc(void *Context, const char16 *s, ssize_t len) { - return ((GCppStringPool*)Context)->Alloc(s, len); + return ((LCppStringPool*)Context)->Alloc(s, len); } -struct GSourceFile +struct LSourceFile { uint32_t Id; LAutoString Path; - GCppStringPool Pool; + LCppStringPool Pool; LAutoWString Raw; bool Active; LArray Stack; int CurBlock; LArray Blocks; PreprocessBlock *Block; - GSourceFile() : Pool(64 << 10) + LSourceFile() : Pool(64 << 10) { Id = 0; CurBlock = 0; Block = NULL; Active = true; } PreprocessBlock *Current() { if (!Block && CurBlock == 0) Block = &Blocks[CurBlock]; return Block; } PreprocessBlock *Next() { CurBlock++; if (CurBlock >= Blocks.Length()) { Block = NULL; return NULL; } Block = &Blocks[CurBlock]; return Block; } bool Read(const char *path) { if (!Path.Reset(NewStr(path))) return false; LAutoString f(LReadTextFile(Path)); if (!f) return false; Raw.Reset(Utf8ToWide(f)); return true; } bool Lex(PreprocessBlock &blk) { if (blk.Start) { char16 *Cur = blk.Start; char16 *End = blk.Start + blk.Len; int Line = blk.BlockLine; char16 *t; // Temporarily NULL terminate this block char16 OldEndChar = *End; *End = 0; if (blk.Type == HashInclude) { // Special include parsing... while (*Cur && (t = LexCpp(Cur, LexPoolAlloc, &Pool, &Line))) { if (CmpToken(t, "<")) { char16 delim = '>'; char16 *End = StrchrW(t = Cur, delim); *End = 0; } else if (*t == '\"' || *t == '\'') { // Trim the string markers char16 delim = *t++; char16 *End = StrchrW(t, delim); *End = 0; } blk.Tokens.Add(t); blk.Lines.Add(Line); break; } } else if (blk.Type == HashDefine) { // Special #define parsing to remove the line continuation characters... while (*Cur && (t = LexCpp(Cur, LexPoolAlloc, &Pool, &Line))) { if (CmpToken(t, "\\")) { // Is this a line continuation char? char16 *End = Cur; bool AllWhiteSpace = true; while (*End && *End != '\r' && *End != '\n') { if (*End != ' ' && *End != '\t') { AllWhiteSpace = false; break; } End++; } if (AllWhiteSpace) { continue; } } blk.Tokens.Add(t); blk.Lines.Add(Line); } } else { // General code parsing while (*Cur && (t = LexCpp(Cur, LexPoolAlloc, &Pool, &Line))) { blk.Tokens.Add(t); blk.Lines.Add(Line); } } // Restore the string to normal *End = OldEndChar; // Clear the unparsed data ptr blk.Start = NULL; } return NULL; } void DumpBlocks() { #if 1 LXmlTree t; LFile f; char *Leaf = strrchr(Path, DIR_CHAR); char Out[MAX_PATH_LEN]; LMakePath(Out, sizeof(Out), "c:\\temp", Leaf + 1); char *Ext = LGetExtension(Out); strcpy(Ext, "xml"); if (f.Open(Out, O_WRITE)) { f.SetSize(0); LXmlTag r("Dump"); r.SetAttr("Path", Path); for (int i=0; iSetAttr("Line", b.BlockLine); LAutoString a(WideToUtf8(b.Start, b.Len)); c->SetContent(a); } t.Write(&r, &f); f.Close(); } else LAssert(0); #endif } /* bool At(const char *file, int line) { char *d = Path ? strrchr(Path, DIR_CHAR) : NULL; if (!d) return false; return stricmp(d+1, file) == 0 && Line == line; } */ }; -typedef LHashTbl, GSymbol*> SourceScopeMap; -struct GSourceScope : public SourceScopeMap +typedef LHashTbl, LSymbol*> SourceScopeMap; +struct LSourceScope : public SourceScopeMap { - GSourceScope() : SourceScopeMap(5000) + LSourceScope() : SourceScopeMap(5000) { } - ~GSourceScope() + ~LSourceScope() { DeleteObjects(); } - bool Add(char16 *k, GSymbol *v) + bool Add(char16 *k, LSymbol *v) { if (Find(k)) { return false; } return SourceScopeMap::Add(k, v); } - GSymbol *Define(char16 *name, GSymbolType Type, const char *def_file, int def_line) + LSymbol *Define(char16 *name, LSymbolType Type, const char *def_file, int def_line) { - GSymbol *s = Find(name); - if (!s) Add(name, s = new GSymbol(def_file, def_line)); + LSymbol *s = Find(name); + if (!s) Add(name, s = new LSymbol(def_file, def_line)); s->Type = Type; s->File = NULL; s->Line = 0; return s; } }; enum KeywordType { KwNone, KwSpecifier, KwType, KwQualifier, KwModifier, KwUserType, KwVisibility, KwPreprocessor }; enum MsgType { MsgError, MsgWarning, MsgInfo, }; template void AddHash(T tbl, const char *name, V type) { char16 s[256]; Strcpy(s, CountOf(s), name); tbl.Add(s, type); } -struct GCppParserWorker : public LCompileTools +struct LCppParserWorker : public LCompileTools { - GCppParserPriv *d; + LCppParserPriv *d; LAutoPtr w; - GCppStringPool GeneralPool; + LCppStringPool GeneralPool; - LArray Srcs; - LArray Scopes; // from global to local + LArray Srcs; + LArray Scopes; // from global to local LHashTbl, KeywordType> Keywords; LHashTbl, PreprocessSymbol> PreprocessSyms; LHashTbl, char*> IncludePathFiles; char *FindInclude(char *file); - bool Preprocess(GSourceFile *sf); + bool Preprocess(LSourceFile *sf); void InitScopes(); - GSourceScope *CurrentScope() { return Scopes[Scopes.Length()-1]; } + LSourceScope *CurrentScope() { return Scopes[Scopes.Length()-1]; } LAutoWString GetSymbolName(LArray &a, bool IsEnum = false); - GSymbol *ParseDecl(GSourceFile *sf, char16 *t); - GSymbol *ParseUserType(GSourceFile *sf, char16 *t); - GSymbol *ParseTypedef(GSourceFile *sf); - bool ParsePreprocessor(GSourceFile *sf); + LSymbol *ParseDecl(LSourceFile *sf, char16 *t); + LSymbol *ParseUserType(LSourceFile *sf, char16 *t); + LSymbol *ParseTypedef(LSourceFile *sf); + bool ParsePreprocessor(LSourceFile *sf); public: - GCppParserWorker(GCppParserPriv *priv); - ~GCppParserWorker(); + LCppParserWorker(LCppParserPriv *priv); + ~LCppParserWorker(); - GSourceFile *ParseCpp(const char *Path); - GSymbol *Resolve(char16 *Symbol); + LSourceFile *ParseCpp(const char *Path); + LSymbol *Resolve(char16 *Symbol); int Evaluate(LArray &Exp); void DoWork(WorkUnit *wk); int Main(); void Msg(MsgType Type, const char *Fmt, ...); }; -struct GCppParserPriv +struct LCppParserPriv { LArray IncPaths; - LAutoPtr Worker; + LAutoPtr Worker; }; -GCppParserWorker::GCppParserWorker(GCppParserPriv *priv) : +LCppParserWorker::LCppParserWorker(LCppParserPriv *priv) : GeneralPool(4 << 10) { d = priv; AddHash(Keywords, "auto", KwSpecifier); AddHash(Keywords, "extern", KwSpecifier); AddHash(Keywords, "static", KwSpecifier); AddHash(Keywords, "register", KwSpecifier); AddHash(Keywords, "mutable", KwSpecifier); AddHash(Keywords, "bool", KwType); AddHash(Keywords, "char", KwType); AddHash(Keywords, "wchar_t", KwType); AddHash(Keywords, "short", KwType); AddHash(Keywords, "int", KwType); AddHash(Keywords, "long", KwType); AddHash(Keywords, "float", KwType); AddHash(Keywords, "double", KwType); AddHash(Keywords, "void", KwType); #ifdef WIN32 AddHash(Keywords, "__int64", KwType); AddHash(Keywords, "_W64", KwModifier); #endif AddHash(Keywords, "unsigned", KwModifier); AddHash(Keywords, "signed", KwModifier); AddHash(Keywords, "__declspec", KwModifier); AddHash(Keywords, "template", KwModifier); AddHash(Keywords, "__stdcall", KwModifier); AddHash(Keywords, "friend", KwModifier); AddHash(Keywords, "operator", KwModifier); AddHash(Keywords, "class", KwUserType); AddHash(Keywords, "struct", KwUserType); AddHash(Keywords, "union", KwUserType); AddHash(Keywords, "enum", KwUserType); AddHash(Keywords, "public:", KwVisibility); AddHash(Keywords, "private:", KwVisibility); AddHash(Keywords, "protected:", KwVisibility); AddHash(Keywords, "#if", KwPreprocessor); AddHash(Keywords, "#ifdef", KwPreprocessor); AddHash(Keywords, "#ifndef", KwPreprocessor); AddHash(Keywords, "#elif", KwPreprocessor); AddHash(Keywords, "#else", KwPreprocessor); AddHash(Keywords, "#endif", KwPreprocessor); AddHash(Keywords, "#include", KwPreprocessor); AddHash(Keywords, "#define", KwPreprocessor); AddHash(Keywords, "#undef", KwPreprocessor); AddHash(Keywords, "#error", KwPreprocessor); AddHash(Keywords, "#warning", KwPreprocessor); AddHash(Keywords, "#pragma", KwPreprocessor); AddHash(PreprocessSyms, "#if", HashIf); AddHash(PreprocessSyms, "#elif", HashElif); AddHash(PreprocessSyms, "#ifndef", HashIfndef); AddHash(PreprocessSyms, "#ifdef", HashIfdef); AddHash(PreprocessSyms, "#else", HashElse); AddHash(PreprocessSyms, "#endif", HashEndif); AddHash(PreprocessSyms, "#pragma", HashPragma); AddHash(PreprocessSyms, "#error", HashError); AddHash(PreprocessSyms, "#warning", HashWarning); AddHash(PreprocessSyms, "#define", HashDefine); AddHash(PreprocessSyms, "#undef", HashUndef); AddHash(PreprocessSyms, "#include", HashInclude); } -GCppParserWorker::~GCppParserWorker() +LCppParserWorker::~LCppParserWorker() { Srcs.DeleteObjects(); Scopes.DeleteObjects(); IncludePathFiles.DeleteArrays(); } -void GCppParserWorker::Msg(MsgType Type, const char *Fmt, ...) +void LCppParserWorker::Msg(MsgType Type, const char *Fmt, ...) { va_list Arg; va_start(Arg, Fmt); #ifdef WIN32 char Buf[256]; #ifdef _MSC_VER vsnprintf_s(Buf, sizeof(Buf), _TRUNCATE, Fmt, Arg); #else vsnprintf(Buf, sizeof(Buf), Fmt, Arg); #endif OutputDebugStringA(Buf); #else vprintf(Fmt, Arg); #endif va_end(Arg); if (Type == MsgError) Debug; } -char *GCppParserWorker::FindInclude(char *File) +char *LCppParserWorker::FindInclude(char *File) { if (IncludePathFiles.Length() == 0) { for (int i=0; iIncludePaths.Length(); i++) { LDirectory dir; char p[MAX_PATH_LEN]; for (bool b=dir.First(w->IncludePaths[i]); b; b=dir.Next()) { char *Leaf = dir.GetName(); if (!dir.IsDir()) { char *ext = LGetExtension(Leaf); if (ext && ext[0] == 'h' && !ext[1]) { if (dir.Path(p, sizeof(p))) { if (!IncludePathFiles.Find(Leaf)) IncludePathFiles.Add(Leaf, NewStr(p)); } } } } } } char *p = IncludePathFiles.Find(File); return p; } -void GCppParserWorker::InitScopes() +void LCppParserWorker::InitScopes() { Scopes.DeleteObjects(); - Scopes.Add(new GSourceScope); + Scopes.Add(new LSourceScope); #ifdef WIN32 char16 buf[256]; - GSymbol *s; + LSymbol *s; #ifdef _MSC_VER s = Scopes[0]->Define(L"_MSC_VER", SymDefineValue, _FL); int ch = swprintf_s(buf, CountOf(buf), L"%i", _MSC_VER); s->Tokens.Add(GeneralPool.Alloc(buf, ch)); s->File = "none"; #endif s = Scopes[0]->Define(L"WIN32", SymDefineValue, _FL); s->File = "none"; #endif } struct RoundBracket { int Depth; int Sections; bool HasPtr; RoundBracket() { Depth = 0; Sections = 0; HasPtr = false; } void Parse(LArray &a) { for (int i=0; i= 0); } else if (CmpToken(t, "*") && Sections == 1) { HasPtr = true; } } LAssert(Depth == 0); } }; -LAutoWString GCppParserWorker::GetSymbolName(LArray &in, bool IsEnum) +LAutoWString LCppParserWorker::GetSymbolName(LArray &in, bool IsEnum) { LArray a = in; LAutoWString ret; LStringPipe p(64); RoundBracket Rb; int LastName = -1; bool FuncPtr = false; int Operator = -1; char16 *t, *prev = NULL; Rb.Parse(a); if (Rb.Sections == 2 && Rb.HasPtr) FuncPtr = true; Rb.Sections = 0; for (int i=0; iType == SymDefineValue || sym->Type == SymDefineFunction) { if (sym->Type == SymDefineFunction) { // Resolve the arguments... t = a[++i]; if (!CmpToken(t, "(")) { Msg(MsgError, "Missing '(' in call to '%S' define.\n", t); break; } int Depth = 1; while ((t = a[++i])) { if (CmpToken(t, "(")) { Depth++; } else if (CmpToken(t, ")")) { if (--Depth <= 0) break; } } Msg(MsgWarning, "NotImpl: Parsing of #define arguments.\n"); } a.DeleteAt(i, true); for (int n=0; nTokens.Length(); n++) { char16 *s = sym->Tokens[n]; a.AddAt(i+n, s); } i--; } else if (sym->Type == SymTypedef) { a.DeleteAt(i, true); if (i < a.Length()) { char16 *Next = a[i]; a.DeleteAt(i, true); for (int n=0; nTokens.Length(); n++) { char16 *s = sym->Tokens[n]; if (StricmpW(s, t)) a.AddAt(i+n, s); else a.AddAt(i+n, Next); } i--; RoundBracket rb; rb.Parse(a); if (rb.Sections == 2 && rb.HasPtr) FuncPtr = true; } else { // This can happen when you define a typedef to twice LAutoWString Empty; return Empty; } } else { goto IsAlpha; } } else if (CmpToken(t, "(")) { if (Rb.Depth == 0) Rb.Sections++; Rb.Depth++; } else if (CmpToken(t, ")")) { Rb.Depth--; LAssert(Rb.Depth >= 0); } else if (IsAlpha(*t) || *t == '_') { IsAlpha: if (FuncPtr) { if (Rb.Depth == 1 && Rb.Sections == 1) { LastName = i; } } else if (Rb.Depth == 0) { LastName = i; } } prev = t; } if (Operator >= 0) { for (int i=Operator; i Operator) p.Write(L"_", sizeof(char16)); p.Write(t, StrlenW(t) * sizeof(char16) ); } } else { if (IsEnum && LastName < 0) return LAutoWString(); LAssert(LastName >= 0); t = a[LastName]; p.Write(t, StrlenW(t) * sizeof(char16) ); } ret.Reset(p.NewStrW()); LAssert(ret); return ret; } -GSymbol *GCppParserWorker::Resolve(char16 *Symbol) +LSymbol *LCppParserWorker::Resolve(char16 *Symbol) { for (auto i = Scopes.Length()-1; i >= 0; i--) { - GSourceScope *scope = Scopes[i]; - GSymbol *sym = scope->Find(Symbol); + LSourceScope *scope = Scopes[i]; + LSymbol *sym = scope->Find(Symbol); if (sym) return sym; } return NULL; } LVariant *GetArg(LArray &Values, int i) { if (i < 0 || i >= Values.Length()) return NULL; if (Values[i].Type == GV_OPERATOR) return NULL; return &Values[i]; } int FindMatchingBracket(LArray &Exp, int Start) { int Depth = 1; for (int i = Start + 1; i &Exp) +int LCppParserWorker::Evaluate(LArray &Exp) { bool Error = false; LArray Values; for (int i=0; i Sub; for (int n=i+1; nTokens); Values.New() = Val; } else { Values.New() = 0; } } } } } if (Error) return false; // Evaluate the contents of Values while (Values.Length() > 1) { auto StartLength = Values.Length(); int OpIdx = -1; int Prec = -1; for (int i=0; iCastBool(); break; } default: { Msg(MsgError, "Impl this op.\n"); break; } } Values.DeleteAt(OpIdx + 1, true); break; } case OpInfix: { LVariant *a = GetArg(Values, OpIdx - 1); LVariant *b = GetArg(Values, OpIdx + 1); if (!a || !b) { Msg(MsgError, "Missing op args.\n"); break; } switch (OpVar.Value.Op) { #define CombineUsingOp(op, type, cpp_op) \ case op: \ { \ *a = a->Cast##type() cpp_op b->Cast##type(); \ break; \ } CombineUsingOp(OpOr, Bool, ||); CombineUsingOp(OpAnd, Bool, &&); CombineUsingOp(OpGreaterThan, Int32, >); CombineUsingOp(OpGreaterThanEqual, Int32, >=); CombineUsingOp(OpLessThan, Int32, <); CombineUsingOp(OpLessThanEqual, Int32, <=); CombineUsingOp(OpEquals, Int32, ==); CombineUsingOp(OpNotEquals, Int32, !=); CombineUsingOp(OpPlus, Int32, +); CombineUsingOp(OpMinus, Int32, -); CombineUsingOp(OpMul, Int32, *); CombineUsingOp(OpDiv, Int32, /); CombineUsingOp(OpMod, Int32, %); default: { Msg(MsgError, "Impl this op.\n"); break; } } Values.DeleteAt(OpIdx, true); Values.DeleteAt(OpIdx, true); break; } default: { Msg(MsgError, "Impl this op type.\n"); break; } } LAssert(StartLength > Values.Length()); if (Values.Length() >= StartLength) { Msg(MsgError, "Nothing happened.\n"); break; } } return Values.Length() == 1 ? Values[0].CastInt32() : 0; } #define IsWhite(c) ( (c) == ' ' || (c) == '\t' ) #define SkipNewLine(Cur) \ if (Cur[0] == '\r' && Cur[1] == '\n') \ Cur += 2; \ else if (Cur[0] == '\r') \ Cur++; \ else if (Cur[0] == '\n') \ Cur++; \ Line++; -bool GCppParserWorker::Preprocess(GSourceFile *sf) +bool LCppParserWorker::Preprocess(LSourceFile *sf) { char16 *Cur = sf->Raw; int Line = 1; char16 Cmd[256]; PreprocessBlock *Blk = NULL; while (*Cur) { // Find Start of line while (IsWhite(*Cur)) Cur++; if (*Cur == '#') { // Emit a source code block if (Blk) { Blk->Len = Cur - Blk->Start; if (Blk->Len <= 8) { bool IsWhiteSpace = true; for (int i=0; iLen; i++) { if (Blk->Start[i] != ' ' && Blk->Start[i] != '\t' && Blk->Start[i] != '\r' && Blk->Start[i] != '\n') { IsWhiteSpace = false; } } if (IsWhiteSpace) { Blk->Start = NULL; Blk->Len = 0; } else { Blk = NULL; } } else { Blk = NULL; } } // Read over the preprocessor command and save the name char16 *o = Cmd; char16 *e = Cmd + 255; *o++ = *Cur++; while (IsWhite(*Cur)) Cur++; while ( *Cur && !IsWhite(*Cur) && *Cur != '\r' && *Cur != '\n' && o < e) { *o++ = *Cur++; } *o++ = 0; while (IsWhite(*Cur)) Cur++; // Create a block for the statement if (Blk == NULL) Blk = &sf->Blocks.New(); Blk->BlockLine = Line; Blk->Type = PreprocessSyms.Find(Cmd); LAssert(Blk->Type != SourceBlock); Blk->Start = Cur; // Scan till the end of line... char16 *LastNonWhite = NULL; do { while (*Cur && *Cur != '\r' && *Cur != '\n') { if (!IsWhite(*Cur)) LastNonWhite = Cur; Cur++; } if (LastNonWhite && *LastNonWhite == '\\' && Blk->Type == HashDefine) { // Multi-line #define... SkipNewLine(Cur); } else { SkipNewLine(Cur); break; } } while (*Cur); Blk->Len = Cur - Blk->Start; while (Blk->Len > 0 && strchr(" \r\t\n", Blk->Start[Blk->Len - 1])) Blk->Len--; Blk = NULL; } else { if (!Blk) { Blk = &sf->Blocks.New(); Blk->BlockLine = Line; Blk->Start = Cur; } // Scan till the end of line... while (*Cur && *Cur != '\r' && *Cur != '\n') { if (Cur[0] == '/' && Cur[1] == '*') { // Process multi-line comment... for (Cur += 2; *Cur; ) { if (*Cur == '\r' || *Cur == '\n') { SkipNewLine(Cur); } else if (Cur[0] == '*' && Cur[1] == '/') { Cur += 2; break; } else Cur++; } } else { Cur++; } } // Skip over new line... SkipNewLine(Cur); } } if (Blk) { Blk->Len = Cur - Blk->Start; } // sf->DumpBlocks(); return true; } -GSourceFile *GCppParserWorker::ParseCpp(const char *Path) +LSourceFile *LCppParserWorker::ParseCpp(const char *Path) { - GSourceFile *sf = NULL; + LSourceFile *sf = NULL; for (int i=0; iPath && !strcmp(s->Path, Path)) { sf = s; break; } } if (!sf) { - sf = new GSourceFile; + sf = new LSourceFile; if (sf) { if (!sf->Read(Path)) { delete sf; return NULL; } Srcs.Add(sf); Preprocess(sf); } else return NULL; } bool IsExternC = false; char16 *t; PreprocessBlock *blk; while ((blk = sf->Current())) { if (blk->Type != SourceBlock) { if (!ParsePreprocessor(sf)) break; } else if (sf->Active) { bool Error = false; sf->Lex(*blk); while (!Error) { t = blk->NextToken(); if (!t) break; KeywordType kt = Keywords.Find(t); switch (kt) { case KwPreprocessor: { LAssert(!"We shouldn't see these here at all."); Error = true; break; } case KwUserType: { - GSymbol *sym = ParseUserType(sf, t); + LSymbol *sym = ParseUserType(sf, t); if (sym) { if (sf->Active) { LAutoWString SymName = GetSymbolName(sym->Tokens, sym->Type == SymEnum); if (SymName) { if (CurrentScope()->Find(SymName)) { DeleteObj(sym); } else { CurrentScope()->Add(SymName, sym); } } else if (sym->Type == SymEnum) { // Unnamed enum static int Idx = 1; #ifdef _MSC_VER char16 Buf[256]; swprintf_s(Buf, CountOf(Buf), L"UnnamedEnum%i", Idx++); #else char Tmp[256]; sprintf_s(Tmp, CountOf(Tmp), "UnnamedEnum%i", Idx++); LAutoWString Buf(Utf8ToWide(Tmp)); #endif if (CurrentScope()->Find(SymName)) { DeleteObj(sym); } else { CurrentScope()->Add(Buf, sym); } } else { Msg(MsgError, "%s:%i - Failed to get symbol name.\n", _FL); DeleteObj(sym); } } else { DeleteObj(sym); } } if (sf->Current()) blk = sf->Current(); break; } default: { if (IsExternC && CmpToken(t, "}")) { if (sf->Active) IsExternC = false; } else if (CmpToken(t, "typedef")) { - GSymbol *sym = ParseTypedef(sf); + LSymbol *sym = ParseTypedef(sf); if (sym) { LAutoWString Name = GetSymbolName(sym->Tokens); if (Name) { - GSymbol *existing = CurrentScope()->Find(Name); + LSymbol *existing = CurrentScope()->Find(Name); if (existing) { Msg(MsgError, "Symbol '%S' already exists.\n", Name.Get()); } else { CurrentScope()->Add(Name, sym); sym->File = sf->Path; sym->Line = blk->GetLine(); sym = NULL; } } DeleteObj(sym); } } else if (CmpToken(t, ";")) { } else { - GSymbol *sym = ParseDecl(sf, t); + LSymbol *sym = ParseDecl(sf, t); if (sym) { if (sf->Active) { if (sym->Type == SymExternC) { IsExternC = true; delete sym; } else { LAutoWString Name = GetSymbolName(sym->Tokens); if (Name) { - GSymbol *e = Scopes[0]->Find(Name); + LSymbol *e = Scopes[0]->Find(Name); if (e) { DeleteObj(sym); } else { Scopes[0]->Add(Name, sym); } } else { Msg(MsgError, "%s:%i - Failed to get name of decl.\n", _FL); DeleteObj(sym); } } } else { DeleteObj(sym); } } if (sf->Current()) blk = sf->Current(); } break; } } } sf->Next(); } else { sf->Next(); } } return sf; } -GSymbol *GCppParserWorker::ParseDecl(GSourceFile *sf, char16 *t) +LSymbol *LCppParserWorker::ParseDecl(LSourceFile *sf, char16 *t) { PreprocessBlock *blk = sf->Current(); int ExternC = CmpToken(t, "extern") ? 1 : 0; - GSymbol *sym = new GSymbol(_FL); + LSymbol *sym = new LSymbol(_FL); sym->Type = SymVariable; sym->File = sf->Path; sym->Line = blk->GetLine(); if (CmpToken(t, "virtual")) sym->Type = SymFunction; if (CmpToken(t, "friend")) sym->FriendDecl = true; else sym->Tokens.Add(t); LArray Temp; if (CmpToken(t, "template")) { Temp.Add(t); while ((t = blk->NextToken())) { Temp.Add(t); if (CmpToken(t, ">")) break; } if (!t) blk = sf->Next(); } LoopState Loop = LsLooping; int ScopeDepth = 0; while (Loop == LsLooping && blk) { if (blk->Type != SourceBlock) { // if (stristr(sf->Path, "GDragAndDrop.cpp") && blk->BlockLine == if (!ParsePreprocessor(sf)) { Loop = LsError; break; } blk = sf->Current(); } else if (!sf->Active) { blk = sf->Next(); } else { sf->Lex(*blk); while (Loop == LsLooping) { t = blk->NextToken(); if (!t) { blk = sf->Next(); break; } if (CmpToken(t, "{")) { if (ExternC == 2 && CmpToken(t, "{")) { sym->Type = SymExternC; Loop = LsFinished; break; } ScopeDepth++; } else if (CmpToken(t, "}")) { if (ScopeDepth <= 0) { Loop = LsError; break; } if (--ScopeDepth <= 0) { Loop = LsFinished; break; } } else if (ScopeDepth == 0) { KeywordType kt = Keywords.Find(t); if (kt == KwUserType && sym->Type != SymFunction) { - GSymbol *ut = ParseUserType(sf, t); + LSymbol *ut = ParseUserType(sf, t); if (ut) ut->FriendDecl = sym->FriendDecl; DeleteObj(sym); sym = ut; Loop = LsFinished; if (sf->Current()) blk = sf->Current(); break; } if (ExternC > 0) { if (ExternC == 1 && CmpToken(t, "\"C\"")) { ExternC++; } else if (ExternC == 2 && CmpToken(t, "{")) { sym->Type = SymExternC; Loop = LsFinished; break; } else { ExternC = 0; } } if (CmpToken(t, ";")) { Loop = LsFinished; break; } if (CmpToken(t, "(")) { sym->Type = SymFunction; } sym->Tokens.Add(t); } } } } return sym; } -GSymbol *GCppParserWorker::ParseTypedef(GSourceFile *sf) +LSymbol *LCppParserWorker::ParseTypedef(LSourceFile *sf) { - GSymbol *sym = new GSymbol(_FL); + LSymbol *sym = new LSymbol(_FL); sym->Type = SymTypedef; PreprocessBlock *blk = sf->Current(); char16 *t = blk->NextToken(); if (t == NULL) { delete sym; return NULL; } KeywordType kt = Keywords.Find(t); if (kt == KwUserType) { DeleteObj(sym); return ParseUserType(sf, t); } sym->Tokens.Add(t); while ((t = blk->NextToken())) { if (CmpToken(t, ";")) break; sym->Tokens.Add(t); } return sym; } -GSymbol *GCppParserWorker::ParseUserType(GSourceFile *sf, char16 *t) +LSymbol *LCppParserWorker::ParseUserType(LSourceFile *sf, char16 *t) { if (!sf || !t) return NULL; - GSymbol *sym = new GSymbol(_FL); + LSymbol *sym = new LSymbol(_FL); int InScope = 0; bool InParentList = false; bool Finished = false; if (CmpToken(t, "union")) sym->Type = SymUnion; else if (CmpToken(t, "class")) sym->Type = SymClass; else if (CmpToken(t, "struct")) sym->Type = SymStruct; else if (CmpToken(t, "enum")) sym->Type = SymEnum; else { delete sym; return NULL; } PreprocessBlock *blk = sf->Current(); sym->Tokens.Add(t); sym->File = sf->Path; sym->Line = blk->GetLine(); while (blk && !Finished) { if (blk->Type != SourceBlock) { if (!ParsePreprocessor(sf)) Finished = true; blk = sf->Current(); } else if (!sf->Active) { blk = sf->Next(); } else { sf->Lex(*blk); while ((t = blk->NextToken())) { if (CmpToken(t, "{")) { InScope++; } else if (CmpToken(t, "}")) { ProcessEndBracket: if (InScope > 0) { InScope--; } else { LAssert(!"End scope mis-match."); Finished = true; break; } } else if (CmpToken(t, ":")) { if (InScope == 0) { InParentList = true; } } else if (CmpToken(t, ";")) { if (!InScope) { Finished = true; break; } } else { KeywordType kt = Keywords.Find(t); if (kt == KwVisibility) { } else if (kt == KwPreprocessor) { LAssert(0); } else if (kt == KwUserType) { - GSymbol *cls = ParseUserType(sf, t); + LSymbol *cls = ParseUserType(sf, t); if (cls) { if (sf->Active) { sym->Add(cls); } else { DeleteObj(cls); } } if (sf->Current()) blk = sf->Current(); } else if (InScope > 0) { if (sym->Type == SymEnum) { - GSymbol *def = new GSymbol(_FL); + LSymbol *def = new LSymbol(_FL); if (def) { bool GotEquals = false; def->File = sf->Path; def->Line = blk->GetLine(); while ((t = blk->NextToken())) { if (CmpToken(t, ",")) break; if (CmpToken(t, "}")) { sym->Add(def); goto ProcessEndBracket; } if (CmpToken(t, "=")) GotEquals = true; else if (GotEquals) def->Tokens.Add(t); } sym->Add(def); } else { Msg(MsgError, "%s:%i - Failed to create defn.\n", _FL); } } else { - GSymbol *def = ParseDecl(sf, t); + LSymbol *def = ParseDecl(sf, t); if (def) { if (sf->Active && !def->FriendDecl) { sym->Add(def); } else { DeleteObj(def); } } else { Msg(MsgError, "%s:%i - Failed to parse defn.\n", _FL); } if (sf->Current()) blk = sf->Current(); } } else if (InParentList) { } else { sym->Tokens.Add(t); } } } if (!Finished) blk = sf->Next(); } } return sym; } char16 *FindEol(char16 *s) { char16 *LastNonWhite = NULL; while (*s) { if (!strchr(WhiteSpace, *s)) LastNonWhite = s; else if (*s == '\n') { if (!LastNonWhite || *LastNonWhite != '\\') break; } s++; } return s; } bool HasNonWhiteSpace(char16 *Start, char16 *End) { while (*Start && Start < End) { if (Start[0] == '/' && Start[1] == '/') { // Single line comments don't contribute to non-whitespace for (Start += 2; Start= End) return false; else continue; } else if (Start[0] == '/' && Start[1] == '*') { // Start of multi-line comment that doesn't contribute to non-whitespace for (Start += 2; Start= End) return false; else continue; } if (!strchr(" \t\r", *Start)) { return true; } Start++; } return false; } -bool GCppParserWorker::ParsePreprocessor(GSourceFile *sf) +bool LCppParserWorker::ParsePreprocessor(LSourceFile *sf) { char16 *t; PreprocessBlock *blk = sf->Current(); if (blk->Type == SourceBlock) { LAssert(0); return false; } bool Status = true; sf->Lex(*blk); /* LgiTrace("%s:%i - %s, stack=%i\n", sf->Path.Get(), blk->BlockLine, PreprocessSymbolNames[blk->Type], sf->Stack.Length()); */ switch (blk->Type) { case HashIf: { PreprocessState &ps = sf->Stack.New(); ps.ParentIsActive = sf->Active; ps.IfBranchClaimed = Evaluate(blk->Tokens) != 0; if (sf->Active) sf->Active = ps.IfBranchClaimed; break; } case HashElif: { PreprocessState &ps = sf->Stack.Last(); if (!ps.IfBranchClaimed) { ps.IfBranchClaimed = Evaluate(blk->Tokens) != 0; if (sf->Active) sf->Active = ps.IfBranchClaimed; } else { sf->Active = false; } break; } case HashIfndef: { t = blk->NextToken(); if (!t) { Status = false; break; } - GSymbol *def = Resolve(t); + LSymbol *def = Resolve(t); PreprocessState &ps = sf->Stack.New(); ps.ParentIsActive = sf->Active; ps.IfBranchClaimed = def == NULL || ( def->Type != SymDefineValue && def->Type != SymDefineFunction ); if (sf->Active) sf->Active = ps.IfBranchClaimed; break; } case HashIfdef: { t = blk->NextToken(); if (!t) { Status = false; break; } - GSymbol *def = Resolve(t); + LSymbol *def = Resolve(t); PreprocessState &ps = sf->Stack.New(); ps.ParentIsActive = sf->Active; ps.IfBranchClaimed = def != NULL && ( def->Type == SymDefineValue || def->Type == SymDefineFunction ); if (sf->Active) sf->Active = ps.IfBranchClaimed; break; } case HashElse: { PreprocessState &ps = sf->Stack.Last(); if (!ps.IfBranchClaimed) { ps.IfBranchClaimed = true; if (ps.ParentIsActive) sf->Active = true; } else { sf->Active = false; } break; } case HashEndif: { LAssert(sf->Stack.Length() > 0); PreprocessState &ps = sf->Stack.Last(); sf->Active = ps.ParentIsActive; sf->Stack.Length(sf->Stack.Length() - 1); break; } case HashPragma: case HashError: case HashWarning: { break; } case HashDefine: { t = blk->NextToken(); if (!t) { Status = false; break; } - GSymbol *sym = NULL; + LSymbol *sym = NULL; if (sf->Active) { sym = Scopes[0]->Define(t, SymDefineValue, _FL); sym->File = sf->Path; sym->Line = blk->BlockLine; if (blk->Tokens.Length() > 1) { sym->Tokens = blk->Tokens; sym->Tokens.DeleteAt(0, true); } else { sym->Tokens.Length(0); } if (sym->Tokens.Length() > 0 && CmpToken(sym->Tokens[0], "(")) sym->Type = SymDefineFunction; } break; } case HashUndef: { t = blk->NextToken(); if (!t) { Status = false; break; } - GSymbol *s = Scopes[0]->Find(t); + LSymbol *s = Scopes[0]->Find(t); if (s) { Scopes[0]->Delete(t); delete s; } break; } case HashInclude: { // Include a file t = blk->NextToken(); if (!t) { Status = false; break; } LAutoString FileName8(WideToUtf8(t)); if (!FileName8) { Status = false; break; } // Resolve filename char p[MAX_PATH_LEN]; bool Exists = false; char *IncPath = NULL; if (LIsRelativePath(FileName8)) { LMakePath(p, sizeof(p), sf->Path, ".."); LMakePath(p, sizeof(p), p, FileName8); if ((Exists = LFileExists(p))) IncPath = p; } if (!Exists) { if ((IncPath = FindInclude(FileName8))) Exists = true; } if (Exists) { ParseCpp(IncPath); } break; } default: { LAssert(!"Impl me"); Status = false; break; } } sf->Next(); return Status; } -void GCppParserWorker::DoWork(WorkUnit *wk) +void LCppParserWorker::DoWork(WorkUnit *wk) { w.Reset(wk); if (w->SearchTerm) { // Do search... } else if (w->Source.Length()) { // Parse source code... for (int i=0; iSource.Length(); i++) { char *ext = LGetExtension(w->Source[i]); if (!ext) continue; if (!stricmp(ext, "c") || !stricmp(ext, "cpp")) { InitScopes(); uint64 Start = LCurrentTime(); if (!stricmp(ext, "cpp")) { Scopes[0]->Define((char16*)sCpp, SymDefineValue, _FL); } ParseCpp(w->Source[i]); uint64 Time = LCurrentTime() - Start; #if 0 char16 *k; - for (GSymbol *s = Scopes[0]->First(&k); s; s = Scopes[0]->Next(&k)) + for (LSymbol *s = Scopes[0]->First(&k); s; s = Scopes[0]->Next(&k)) { LgiTrace(" %S = %p\n", k, s); } #endif LgiTrace("%i of %i: %s, %i syms, %i ms\n", i, w->Source.Length(), w->Source[i].Get(), Scopes[0]->Length(), (int)Time); } else { // No parser impl yet } Srcs.DeleteObjects(); } } else LAssert(!"Unknown work unit type."); } LCppParser::LCppParser() { - d = new GCppParserPriv; + d = new LCppParserPriv; } LCppParser::~LCppParser() { delete d; } void LCppParser::ParseCode ( LArray &IncludePaths, LArray &PreDefines, LArray &Source ) { WorkUnit *w = new WorkUnit(IncludePaths, PreDefines, Source); if (w) { if (!d->Worker) - d->Worker.Reset(new GCppParserWorker(d)); + d->Worker.Reset(new LCppParserWorker(d)); d->Worker->DoWork(w); } } void LCppParser::Search(const char *Str, SearchResultsCb Callback, void *CallbackData) { WorkUnit *w = new WorkUnit(Str); if (w) { if (!d->Worker) - d->Worker.Reset(new GCppParserWorker(d)); + d->Worker.Reset(new LCppParserWorker(d)); d->Worker->DoWork(w); } } diff --git a/src/common/Coding/ScriptCompiler.cpp b/src/common/Coding/ScriptCompiler.cpp --- a/src/common/Coding/ScriptCompiler.cpp +++ b/src/common/Coding/ScriptCompiler.cpp @@ -1,3863 +1,3863 @@ /// \file #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/LexCpp.h" #include "lgi/common/LgiString.h" #include "ScriptingPriv.h" // #define DEBUG_SCRIPT_FILE "Mail Filters Menu.script" #define GetTok(c) ((c) < Tokens.Length() ? Tokens[c] : NULL) #define GetTokType(c) ((c) < Tokens.Length() ? ExpTok.Find(Tokens[c]) : TNone) #define GV_VARIANT GV_MAX const char *sDebugger = "Debugger"; int LFunctionInfo::_Infos = 0; -enum GTokenType +enum LTokenType { #define _(type, str) T##type, #include "TokenType.h" #undef _ }; struct LinkFixup { int Tok; size_t Offset; int Args; LFunctionInfo *Func; }; struct Node { typedef LArray NodeExp; struct VariablePart { LVariant Name; NodeExp Array; bool Call; LArray Args; VariablePart() { Call = false; } ~VariablePart() { Args.DeleteObjects(); } }; // Hierarchy NodeExp Child; // One of the following are valid: LOperator Op = OpNull; // -or- bool Constant = false; int Tok = -1; LArray Lst; - GTokenType ConstTok = TNone; + LTokenType ConstTok = TNone; // -or- LFunc *ContextFunc = NULL; LArray Args; // -or- LFunctionInfo *ScriptFunc = NULL; // -or- LArray Variable; // Used during building - GVarRef Reg; - GVarRef ArrayIdx; + LVarRef Reg; + LVarRef ArrayIdx; void Init() { Reg.Empty(); ArrayIdx.Empty(); } void SetOp(LOperator o, int t) { Init(); Op = o; Tok = t; } - void SetConst(int t, GTokenType e) + void SetConst(int t, LTokenType e) { Init(); Constant = true; Tok = t; ConstTok = e; } - void SetConst(LArray &list_tokens, GTokenType e) + void SetConst(LArray &list_tokens, LTokenType e) { Init(); Constant = true; Lst = list_tokens; ConstTok = e; } void SetContextFunction(LFunc *m, int tok) { Init(); ContextFunc = m; Tok = tok; } void SetScriptFunction(LFunctionInfo *m, int tok) { Init(); ScriptFunc = m; Tok = tok; } void SetVar(char16 *Var, int t) { Init(); Variable[0].Name = Var; Tok = t; } bool IsVar() { return Variable.Length() > 0; } bool IsContextFunc() { return ContextFunc != 0; } bool IsScriptFunc() { return ScriptFunc != 0; } bool IsConst() { return Constant; } }; LCompiledCode::LCompiledCode() : Globals(SCOPE_GLOBAL), Debug(0, -1) { SysContext = NULL; UserContext = NULL; } LCompiledCode::LCompiledCode(LCompiledCode ©) : Globals(SCOPE_GLOBAL), Debug(0, -1) { *this = copy; } LCompiledCode::~LCompiledCode() { for (auto e: Externs) e->InUse = false; Externs.DeleteObjects(); } LCompiledCode &LCompiledCode::operator =(const LCompiledCode &c) { Globals = c.Globals; ByteCode = c.ByteCode; Types = c.Types; Debug = c.Debug; Methods = c.Methods; FileName = c.FileName; Source = c.Source; return *this; } LFunctionInfo *LCompiledCode::GetMethod(const char *Name, bool Create) { for (auto &m: Methods) { const char *Fn = m->GetName(); if (!strcmp(Fn, Name)) { return m; } } if (Create) { LAutoRefPtr n(new LFunctionInfo(Name)); if (n) { Methods.Add(n); return n; } } return 0; } int LCompiledCode::ObjectToSourceAddress(size_t ObjAddr) { auto Idx = Debug.Find(ObjAddr); return Idx < 0 ? 1 : Idx; } const char *LCompiledCode::AddrToSourceRef(size_t ObjAddr) { static char Status[256]; size_t Addr = ObjAddr; int LineNum = ObjectToSourceAddress(Addr); // Search for the start of the instruction... while (Addr > 0 && LineNum < 0) LineNum = ObjectToSourceAddress(--Addr); char *Dir = FileName ? strrchr(FileName, DIR_CHAR) : NULL; size_t PathLen = Dir ? Dir - FileName : 0; LString FileRef = FileName ? (PathLen > 24 ? Dir + 1 : FileName.Get()) : "(untitled)"; if (LineNum >= 0) sprintf_s( Status, sizeof(Status), "%s:%i", FileRef.Get(), LineNum); else sprintf_s(Status, sizeof(Status), "%s:0x%x", FileRef.Get(), (int)ObjAddr); return Status; } LVariant *LCompiledCode::Set(const char *Name, LVariant &v) { int i = Globals.Var(Name, true); if (i >= 0) { Globals[i] = v; return &Globals[i]; } return 0; } template void UnEscape(T *t) { T *i = t, *o = t; while (*i) { if (*i == '\\') { i++; switch (*i) { case '\\': *o++ = '\\'; i++; break; case 'n': *o++ = '\n'; i++; break; case 'r': *o++ = '\r'; i++; break; case 't': *o++ = '\t'; i++; break; case 'b': *o++ = '\b'; i++; break; } } else *o++ = *i++; } *o++ = 0; } class TokenRanges { LArray FileNames; struct Range { int Start, End; ssize_t File; int Line; }; LArray Ranges; char fl[MAX_PATH_LEN + 32]; public: TokenRanges() { Empty(); } void Empty() { Ranges.Length(0); } ssize_t Length() { return Ranges.Length(); } /// Gets the file/line at a given token const char *operator [](ssize_t Tok) { Range *r = NULL; for (unsigned i=0; i= rng->Start && Tok <= rng->End) { r = rng; break; } } if (!r) r = &Ranges.Last(); if (r->File >= (ssize_t)FileNames.Length()) { LAssert(!"Invalid file index."); return "#err: invalid file index"; } else { sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r->File].Get(), r->Line); } return fl; } const char *GetLine(int Line) { if (Ranges.Length() > 0) { Range &r = Ranges.Last(); if (r.File < (ssize_t)FileNames.Length()) sprintf_s(fl, sizeof(fl), "%s:%i", FileNames[r.File].Get(), Line); else return "#err: invalid file index."; } else { sprintf_s(fl, sizeof(fl), "$unknown:%i", Line); } return fl; } ssize_t GetFileIndex(const char *FileName) { for (unsigned i=0; iFile != FileId || r->Line != Line) { // Start a new range... r = &Ranges.New(); r->Start = r->End = TokIndex; r->File = FileId; r->Line = Line; } else { r->End = TokIndex; } } }; /// Scripting language compiler implementation class LCompilerPriv : public LCompileTools, public LScriptUtils { LHashTbl, LVariantType> Types; size_t JumpLoc; LArray> FuncMem; public: LScriptContext *SysCtx; LScriptContext *UserCtx; LCompiledCode *Code; LStream *Log; LArray Tokens; TokenRanges Lines; char16 *Script; LHashTbl, LFunc*> Methods; uint64_t Regs; LArray Scopes; LArray Fixups; LHashTbl, char16*> Defines; - LHashTbl, GTokenType> ExpTok; + LHashTbl, LTokenType> ExpTok; LDom *ScriptArgs; - GVarRef ScriptArgsRef; + LVarRef ScriptArgsRef; bool ErrShowFirstOnly; LArray ErrLog; bool Debug; #ifdef _DEBUG LString::Array RegAllocators; #endif LCompilerPriv() { Debug = false; ErrShowFirstOnly = true; SysCtx = NULL; UserCtx = NULL; Code = 0; Log = 0; Script = 0; Regs = 0; ScriptArgs = NULL; ScriptArgsRef.Empty(); #define LNULL NULL #define _(type, str) if (str) ExpTok.Add(L##str, T##type); #include "TokenType.h" #undef _ #undef LNULL Types.Add("int", GV_INT32); Types.Add("short", GV_INT32); Types.Add("long", GV_INT32); Types.Add("int8", GV_INT32); Types.Add("int16", GV_INT32); Types.Add("int32", GV_INT32); Types.Add("int64", GV_INT64); Types.Add("uint8", GV_INT32); Types.Add("uint16", GV_INT32); Types.Add("uint32", GV_INT32); Types.Add("uint64", GV_INT64); Types.Add("bool", GV_BOOL); Types.Add("boolean", GV_BOOL); Types.Add("double", GV_DOUBLE); Types.Add("float", GV_DOUBLE); Types.Add("char", GV_STRING); Types.Add("LDom", GV_DOM); Types.Add("void", GV_VOID_PTR); Types.Add("LDateTime", GV_DATETIME); Types.Add("GHashTable", GV_HASHTABLE); Types.Add("LOperator", GV_OPERATOR); Types.Add("LView", GV_GVIEW); Types.Add("LMouse", GV_LMOUSE); Types.Add("LKey", GV_LKEY); Types.Add("LVariant", GV_VARIANT); // Types.Add("binary", GV_BINARY); // Types.Add("List", GV_LIST); // Types.Add("LDom&", GV_DOMREF); } ~LCompilerPriv() { Empty(); } void Empty() { SysCtx = NULL; UserCtx = NULL; DeleteObj(Code); Log = 0; Tokens.DeleteArrays(); Lines.Empty(); DeleteArray(Script); Defines.DeleteArrays(); Methods.Empty(); } /// Prints the error message bool OnError ( /// The line number (less than 0) or Token index (greater than 0) int LineOrTok, /// The format for the string (printf) const char *Msg, /// Variable argument list ... ) { if (!ErrShowFirstOnly || ErrLog.Length() == 0) { char Buf[512]; va_list Arg; va_start(Arg, Msg); #ifndef WIN32 #define _vsnprintf vsnprintf #endif vsprintf_s(Buf, sizeof(Buf), Msg, Arg); Log->Print("%s - Error: %s\n", LineOrTok < 0 ? Lines.GetLine(-LineOrTok) : Lines[LineOrTok], Buf); va_end(Arg); ErrLog.New() = Buf; } return false; // Always return false to previous caller } void DebugInfo(int Tok) { const char *Line = Lines[Tok]; if (Line) { const char *c = strrchr(Line, ':'); if (c) { Code->Debug.Add(Code->ByteCode.Length(), ::atoi(c+1)); } } } /// Assemble a zero argument instruction bool Asm0(int Tok, uint8_t Op) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 1)) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; } else return false; return true; } /// Assemble one arg instruction - bool Asm1(int Tok, uint8_t Op, GVarRef a) + bool Asm1(int Tok, uint8_t Op, LVarRef a) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; } else return false; return true; } /// Assemble two arg instruction - bool Asm2(int Tok, uint8_t Op, GVarRef a, GVarRef b) + bool Asm2(int Tok, uint8_t Op, LVarRef a, LVarRef b) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 9)) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; } else return false; return true; } /// Assemble three arg instruction - bool Asm3(int Tok, uint8_t Op, GVarRef a, GVarRef b, GVarRef c) + bool Asm3(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); - if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * 3) )) + if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 3) )) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; *p.r++ = a; *p.r++ = b; *p.r++ = c; } else return false; return true; } /// Assemble four arg instruction - bool Asm4(int Tok, uint8_t Op, GVarRef a, GVarRef b, GVarRef c, GVarRef d) + bool Asm4(int Tok, uint8_t Op, LVarRef a, LVarRef b, LVarRef c, LVarRef d) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); - if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * 4) )) + if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * 4) )) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; if (!a.Valid()) AllocNull(a); *p.r++ = a; if (!b.Valid()) AllocNull(b); *p.r++ = b; if (!c.Valid()) AllocNull(c); *p.r++ = c; if (!d.Valid()) AllocNull(d); *p.r++ = d; } else return false; return true; } /// Assemble 'n' length arg instruction - bool AsmN(int Tok, uint8_t Op, LArray &Args) + bool AsmN(int Tok, uint8_t Op, LArray &Args) { DebugInfo(Tok); ssize_t Len = Code->ByteCode.Length(); - if (Code->ByteCode.Length(Len + 1 + (sizeof(GVarRef) * Args.Length()) )) + if (Code->ByteCode.Length(Len + 1 + (sizeof(LVarRef) * Args.Length()) )) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = Op; for (unsigned i=0; iDefines.Find(wName); if (v) { // Is it a number? Cause we should really convert to an int if possible... #define INVALID_CONVERSION -11111 int64 i = INVALID_CONVERSION; if (!Strnicmp(v, L"0x", 2)) i = Atoi(v, 16, INVALID_CONVERSION); else if (IsDigit(*v) || strchr("-.", *v)) i = Atoi(v); if (i != INVALID_CONVERSION) Value = i; else Value = v; return true; } return false; } }; /// This will look at resolving the expression to something simpler before storing it... void SimplifyDefine(LScriptEngine &Eng, char16 *Name, LAutoWString &Value) { LString Exp = Value.Get(); if (Exp.Find("(") >= 0) // Filter out simple expressions... { LVariant Result; CompilerDefineSource Src(this); if (Eng.EvaluateExpression(&Result, &Src, Exp)) { auto Val = Result.CastString(); if (Val) { // LgiTrace("Simplify: %S -> %s\n", Value.Get(), Val); Value.Reset(Utf8ToWide(Val)); } } } } /// Convert the source from one big string into an array of tokens bool Lex(char *Source, const char *FileName) { char16 *w = Utf8ToWide(Source); if (!w) return OnError(0, "Couldn't convert source to wide chars."); LScriptEngine Eng(NULL, NULL, NULL); ssize_t FileIndex = Lines.GetFileIndex(FileName); int Line = 1; char16 *s = w, *t; while ((t = LexCpp(s, LexStrdup, NULL, &Line))) { if (*t == '#') { ssize_t Len; if (!StrnicmpW(t + 1, sInclude, Len = StrlenW(sInclude))) { LAutoWString Raw(LexCpp(s, LexStrdup)); LAutoWString File(TrimStrW(Raw, (char16*)L"\"\'")); if (File) { LVariant v; LAutoString IncCode; v.OwnStr(File.Release()); if (UserCtx) { IncCode.Reset(UserCtx->GetIncludeFile(v.Str())); if (IncCode) { Lex(IncCode, v.Str()); } else { DeleteArray(t); return OnError(-Line, "Ctx failed to include '%s'", v.Str()); } } else { if (LIsRelativePath(v.Str())) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), FileName, ".."); LMakePath(p, sizeof(p), p, v.Str()); v = p; } if (LFileExists(v.Str())) { IncCode.Reset(LReadTextFile(v.Str())); if (IncCode) Lex(IncCode, v.Str()); else { DeleteArray(t); return OnError(-Line, "Couldn't read '%s'", v.Str()); } } else { DeleteArray(t); return OnError(-Line, "Couldn't include '%s'", v.Str()); } } } else { OnError(-Line, "No file for #include."); } } else if (!StrnicmpW(t + 1, sDefine, Len = StrlenW(sDefine))) { LAutoWString Name(LexCpp(s, LexStrdup)); if (Name && IsAlpha(*Name)) { char16 *Start = s; while (*Start && strchr(WhiteSpace, *Start)) Start++; char16 *Eol = StrchrW(Start, '\n'); if (!Eol) Eol = Start + StrlenW(Start); while (Eol > Start && strchr(WhiteSpace, Eol[-1])) Eol--; LAutoWString Value(NewStrW(Start, Eol - Start)); SimplifyDefine(Eng, Name, Value); Defines.Add(Name, Value.Release()); s = Eol; } } DeleteArray(t); continue; } if (!ResolveDefine(t, FileIndex, Line)) { Lines.Add((int)Tokens.Length(), FileIndex, Line); Tokens.Add(t); } } // end of "while (t = LexCpp)" loop if (!Script) { Script = w; } else { DeleteArray(w); } return true; } /// Create a null var ref - void AllocNull(GVarRef &r) + void AllocNull(LVarRef &r) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.NullIndex < 0) Code->Globals.NullIndex = (int)Code->Globals.Length(); r.Index = Code->Globals.NullIndex; Code->Globals[r.Index].Type = GV_NULL; } /// Allocate a variant and ref - LVariant *PreAllocVariant(GVarRef &r) + LVariant *PreAllocVariant(LVarRef &r) { r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); return &Code->Globals[r.Index]; } /// Allocate a constant double - void AllocConst(GVarRef &r, double d) + void AllocConst(LVarRef &r, double d) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_DOUBLE && p->Value.Dbl == d) { r.Index = (int) (p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = d; } /// Allocate a constant bool - void AllocConst(GVarRef &r, bool b) + void AllocConst(LVarRef &r, bool b) { r.Scope = SCOPE_GLOBAL; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == GV_BOOL && p->Value.Bool == b) { r.Index = (int)(p - &Code->Globals[0]); return; } p++; } } r.Index = (int)Code->Globals.Length(); Code->Globals[r.Index] = b; } /// Allocate a constant int - void AllocConst(GVarRef &r, int64 i) + void AllocConst(LVarRef &r, int64 i) { r.Scope = SCOPE_GLOBAL; LVariantType Type = i <= INT_MAX && i >= INT_MIN ? GV_INT32 : GV_INT64; if (Code->Globals.Length()) { // Check for existing int LVariant *p = &Code->Globals[0]; LVariant *e = p + Code->Globals.Length(); while (p < e) { if (p->Type == Type) { if (Type == GV_INT32 && p->Value.Int == i) { r.Index = (int) (p - &Code->Globals[0]); return; } else if (Type == GV_INT64 && p->Value.Int64 == i) { r.Index = (int) (p - &Code->Globals[0]); return; } } p++; } } // Allocate new global r.Index = (int)Code->Globals.Length(); if (Type == GV_INT32) Code->Globals[r.Index] = (int32)i; else Code->Globals[r.Index] = i; } - void AllocConst(GVarRef &r, int i) + void AllocConst(LVarRef &r, int i) { AllocConst(r, (int64)i); } /// Allocate a constant string - void AllocConst(GVarRef &r, char *s, ssize_t len = -1) + void AllocConst(LVarRef &r, char *s, ssize_t len = -1) { LAssert(s != 0); if (len < 0) len = strlen(s); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*s == *c && strcmp(s, c) == 0) { r.Index = i; return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = NewStr(s, len))) { UnEscape(v.Value.String); } } /// Allocate a constant wide string - void AllocConst(GVarRef &r, char16 *s, ssize_t len) + void AllocConst(LVarRef &r, char16 *s, ssize_t len) { LAssert(s != 0); char *utf = WideToUtf8(s, len); if (!utf) utf = NewStr(""); r.Scope = SCOPE_GLOBAL; r.Index = (int)Code->Globals.Length(); for (unsigned i=0; iGlobals.Length(); i++) { if (Code->Globals[i].Type == GV_STRING) { char *c = Code->Globals[i].Str(); if (*utf == *c && strcmp(utf, c) == 0) { r.Index = i; DeleteArray(utf); return; } } } LVariant &v = Code->Globals[r.Index]; v.Type = GV_STRING; if ((v.Value.String = utf)) { UnEscape(v.Value.String); } } /// Find a variable by name, creating it if needed - GVarRef FindVariable(LVariant &Name, bool Create) + LVarRef FindVariable(LVariant &Name, bool Create) { - GVarRef r = {0, -1}; + LVarRef r = {0, -1}; // Look for existing variable... ssize_t i; for (i=Scopes.Length()-1; i>=0; i--) { r.Index = Scopes[i]->Var(Name.Str(), false); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; return r; } } // Create new variable on most recent scope i = Scopes.Length() - 1; r.Index = Scopes[i]->Var(Name.Str(), Create); if (r.Index >= 0) { r.Scope = Scopes[i]->Scope; } return r; } /// Build asm to assign a var ref - bool AssignVarRef(Node &n, GVarRef &Value) + bool AssignVarRef(Node &n, LVarRef &Value) { /* Examples and what their assembly should look like: a = Value Assign a <- Value a[b] = Value R0 = AsmExpression(b); ArraySet a[R0] <- Value a[b].c = Value R0 = AsmExpression(b); R0 = ArrayGet a[R0] DomSet(R0, "c", Null, Value) a[b].c[d] R0 = AsmExpression(b); R0 = ArrayGet a[R0]; R1 = AsmExpression(d); R0 = DomGet(R0, "c", R1); ArraySet R0[R1] = Value a.b[c].d = Value R1 = AsmExpression(c); R0 = DomGet(a, "b", R1); DomSet(R1, "d", Null, Value) // resolve initial array if (parts > 1 && part[0].array) { // cur = ArrayGet(part[0].var, part[0].array) } else { cur = part[0] } // dom loop over loop (p = 0 to parts - 2) { if (part[p+1].array) arr = exp(part[p+1].array) else arr = null cur = DomGet(cur, part[p+1].var, arr) } // final part if (part[parts-1].arr) { arr = exp(part[parts-1].arr) ArraySet(cur, arr) } else { DomSet(cur, part[parts-1].var, null, value) } */ if (!Value.Valid()) { LAssert(!"Invalid value to assign.\n"); return false; } if (!n.IsVar()) { LAssert(!"Target must be a variable."); return false; } // Gets the first part of the variable. - GVarRef Cur = FindVariable(n.Variable[0].Name, true); + LVarRef Cur = FindVariable(n.Variable[0].Name, true); if (!Cur.Valid()) return false; if (n.Variable.Length() > 1) { // Do any initial array dereference if (n.Variable[0].Array.Length()) { // Assemble the array index's expression into 'Idx' - GVarRef Idx; + LVarRef Idx; Idx.Empty(); if (!AsmExpression(&Idx, n.Variable[0].Array)) return OnError(n.Tok, "Error creating bytecode for array index."); - GVarRef Dst = Cur; + LVarRef Dst = Cur; if (!Dst.IsReg()) { AllocReg(Dst, n.Tok, _FL); } // Assemble array load instruction Asm3(n.Tok, IArrayGet, Dst, Cur, Idx); Cur = Dst; // Cleanup DeallocReg(Idx); } // Do all the DOM "get" instructions unsigned p; for (p=0; pOwnStr(NewStrW(t + 1, Len - 2)); } else if (StrchrW(t, '.')) { // double *v = atof(t); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer *v = htoi(t + 2); } else { // decimal integer int64 i = Atoi(t); if (i <= INT_MAX && i >= INT_MIN) *v = (int32)i; else *v = i; } return true; } /// Convert a token stream to a var ref - bool TokenToVarRef(Node &n, GVarRef *&LValue) + bool TokenToVarRef(Node &n, LVarRef *&LValue) { if (n.Reg.Valid()) { if (LValue && n.Reg != *LValue) { // Need to assign to LValue LAssert(*LValue != n.Reg); Asm2(n.Tok, IAssign, *LValue, n.Reg); } } else { if (n.IsVar()) { // Variable unsigned p = 0; bool ArrayDeindexed = false; bool HasScriptArgs = Scopes.Length() <= 1 && ScriptArgs != NULL; - GVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL); + LVarRef v = FindVariable(n.Variable[p].Name, /*!HasScriptArgs ||*/ LValue != NULL); if (v.Index < 0) { if (HasScriptArgs) { if (AllocReg(v, n.Tok, _FL)) { char16 *VarName = GetTok((unsigned)n.Tok); if (!ScriptArgsRef.Valid()) { // Setup the global variable to address the script argument variable ScriptArgsRef.Scope = SCOPE_GLOBAL; ScriptArgsRef.Index = (int)Code->Globals.Length(); LVariant &v = Code->Globals[ScriptArgsRef.Index]; v.Type = GV_DOM; v.Value.Dom = ScriptArgs; } - GVarRef Name, Array; + LVarRef Name, Array; AllocConst(Name, VarName, -1); if (n.Variable[p].Array.Length()) { if (!AsmExpression(&Array, n.Variable[p].Array)) return OnError(n.Tok, "Can't assemble array expression."); ArrayDeindexed = true; } else AllocNull(Array); Asm4(n.Tok, IDomGet, v, ScriptArgsRef, Name, Array); } else return false; } else { Node::VariablePart &vp = n.Variable[p]; return OnError(n.Tok, "Undefined variable: %s", vp.Name.Str()); } } else { if (v.Scope == SCOPE_OBJECT) { // We have to load the variable into a register - GVarRef Reg, ThisPtr, MemberIndex, Null; + LVarRef Reg, ThisPtr, MemberIndex, Null; if (!AllocReg(Reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); ThisPtr.Scope = SCOPE_LOCAL; ThisPtr.Index = 0; AllocConst(MemberIndex, v.Index); AllocNull(Null); Asm4(n.Tok, IDomGet, Reg, ThisPtr, MemberIndex, Null); v = Reg; // Object variable now in 'Reg' } if (n.ConstTok == TTypeId) { if (!v.IsReg()) { // Because we are casting to it's DOM ptr, // make sure it's a register first so we don't lose the // actual variable. - GVarRef reg; + LVarRef reg; if (!AllocReg(reg, n.Tok, _FL)) return OnError(n.Tok, "Couldn't alloc register."); LAssert(reg != v); Asm2(n.Tok, IAssign, reg, v); v = reg; } // Casting to DOM will give as access to the type info for a LCustomType. // This currently doesn't work with other types :( Asm1(n.Tok, ICast, v); Code->ByteCode.Add(GV_DOM); } } n.Reg = v; LValue = NULL; LAssert(v.Scope != SCOPE_OBJECT); // Does it have an array deref? if (!ArrayDeindexed && n.Variable[p].Array.Length()) { // Evaluate the array indexing expression if (!AsmExpression(&n.ArrayIdx, n.Variable[p].Array)) { return OnError(n.Tok, "Error creating byte code for array index."); } // Do we need to create code to load the value from the array? - GVarRef Val; + LVarRef Val; if (AllocReg(Val, n.Tok, _FL)) { Asm3(n.Tok, IArrayGet, Val, n.Reg, n.ArrayIdx); n.Reg = Val; } else return OnError(n.Tok, "Error allocating register."); } // Load DOM parts... for (++p; p Args; + LVarRef Name, Arr; + LArray Args; Node::VariablePart &Part = n.Variable[p]; Name.Empty(); Arr.Empty(); char *nm = Part.Name.Str(); AllocConst(Name, nm, strlen(nm)); if (Part.Array.Length()) { if (!AsmExpression(&Arr, Part.Array)) { return OnError(n.Tok, "Can't assemble array expression."); } } else if (Part.Call) { for (unsigned i=0; i Call; + LArray Call; Call[0] = Dst; Call[1] = n.Reg; Call[2] = Name; // This must always be a global, the decompiler requires access // to the constant to know how many arguments to print. And it // can only see the globals. AllocConst(Call[3], (int)Args.Length()); Call.Add(Args); AsmN(n.Tok, IDomCall, Call); } else { Asm4(n.Tok, IDomGet, Dst, n.Reg, Name, Arr); } n.Reg = Dst; } } else if (n.IsConst()) { // Constant switch (n.ConstTok) { case TTrue: { AllocConst(n.Reg, true); break; } case TFalse: { AllocConst(n.Reg, false); break; } case TNull: { AllocNull(n.Reg); break; } default: { if (n.Lst.Length()) { // List/array constant LVariant *v = PreAllocVariant(n.Reg); if (v) { v->SetList(); for (unsigned i=0; i a(new LVariant); if (!ConvertStringToVariant(a, t)) break; v->Value.Lst->Insert(a.Release()); } } } else { char16 *t = Tokens[n.Tok]; if (*t == '\"' || *t == '\'') { // string ssize_t Len = StrlenW(t); AllocConst(n.Reg, t + 1, Len - 2); } else if (StrchrW(t, '.')) { // double AllocConst(n.Reg, atof(t)); } else if (t[0] == '0' && tolower(t[1]) == 'x') { // hex integer AllocConst(n.Reg, htoi(t + 2)); } else { // decimal integer AllocConst(n.Reg, Atoi(t)); } } break; } } LValue = NULL; } else if (n.IsContextFunc()) { // Method call, create byte codes to put func value into n.Reg - GVarRef *OutRef; + LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); - LArray a; + LArray a; for (unsigned i=0; iByteCode.Length(); - ssize_t Size = 1 + sizeof(LFunc*) + sizeof(GVarRef) + 2 + (a.Length() * sizeof(GVarRef)); + ssize_t Size = 1 + sizeof(LFunc*) + sizeof(LVarRef) + 2 + (a.Length() * sizeof(LVarRef)); Code->ByteCode.Length(Len + Size); GPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallMethod; *p.fn++ = n.ContextFunc; n.ContextFunc->InUse = true; *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; iGetName(); if (*FnName == 'D' && !_stricmp(FnName, sDebugger)) { Asm0(n.Tok, IDebug); return true; } if (n.ScriptFunc->GetParams().Length() != n.Args.Length()) { if (n.ScriptFunc->ValidStartAddr()) { // Function is already defined and doesn't have the right arg count... return OnError( n.Tok, "Wrong number of parameters: %i (%s expects %i)", n.Args.Length(), FnName, n.ScriptFunc->GetParams().Length()); } else { // Maybe it's defined later or in a separate file? // Or maybe it's just not defined at all? // Can't know until later... flag it as a unresolved external... // // Allow the assembly for the call to occur. } } // Call to a script function, create byte code to call function - GVarRef *OutRef; + LVarRef *OutRef; if (LValue) { OutRef = LValue; } else { if (!AllocReg(n.Reg, n.Tok, _FL)) { return OnError(n.Tok, "Can't allocate register for method return value."); } OutRef = &n.Reg; } DebugInfo(n.Tok); - LArray a; + LArray a; for (unsigned i=0; iByteCode.Length(); size_t Size = 1 + // instruction sizeof(uint32_t) + // address of function sizeof(uint16) + // size of frame - sizeof(GVarRef) + // return value + sizeof(LVarRef) + // return value 2 + // number of args - (a.Length() * sizeof(GVarRef)); // args + (a.Length() * sizeof(LVarRef)); // args Code->ByteCode.Length(Len + Size); GPtr p; uint8_t *Start = &Code->ByteCode[Len]; p.u8 = Start; *p.u8++ = ICallScript; if (n.ScriptFunc->ValidStartAddr() && n.ScriptFunc->ValidFrameSize()) { // Compile func address straight into code... *p.u32++ = n.ScriptFunc->GetStartAddr(); *p.u16++ = n.ScriptFunc->GetFrameSize(); } else { // Add link time fix up LinkFixup &Fix = Fixups.New(); Fix.Tok = n.Tok; Fix.Args = (int)n.Args.Length(); Fix.Offset = p.u8 - &Code->ByteCode[0]; Fix.Func = n.ScriptFunc; *p.u32++ = 0; *p.u16++ = 0; } *p.r++ = *OutRef; *p.u16++ = (uint16) n.Args.Length(); for (unsigned i=0; i e(new Node::NodeExp); if (!e) return OnError(Cur, "Mem alloc error."); if (!Expression(Cur, *e)) return OnError(Cur, "Couldn't parse func call argument expression."); vp.Args.Add(e.Release()); t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected end of file."); if (!StricmpW(t, sComma)) Cur++; else if (!StricmpW(t, sEndRdBracket)) break; else return OnError(Cur, "Expecting ',', didn't get it."); } } t = GetTok(Cur+1); } // Check for DOM operator... if (StricmpW(t, sPeriod) == 0) { // Got Dom operator Cur += 2; t = GetTok(Cur); if (!t) return OnError(Cur, "Unexpected eof."); Var.Variable.New().Name = t; } else break; } return true; } /// Parse expression into a node tree bool Expression(uint32_t &Cur, LArray &n, int Depth = 0) { if (Cur >= Tokens.Length()) return OnError(Cur, "Unexpected end of file."); char16 *t; bool PrevIsOp = true; while ((t = Tokens[Cur])) { - GTokenType Tok = ExpTok.Find(t); + LTokenType Tok = ExpTok.Find(t); if (Tok == TTypeId) { char16 *v; if (!DoTypeId(Cur, v)) return false; Node &Var = n.New(); t = v; Cur--; DoVariableNode(Cur, Var, t); Var.ConstTok = TTypeId; Cur++; } else if (Tok == TStartRdBracket) { Cur++; auto &Next = n.New(); if (!Expression(Cur, Next.Child, Depth + 1)) return false; PrevIsOp = false; if (Next.Child.Length() > 0) Next.Tok = Next.Child[0].Tok; } else if (Tok == TEndRdBracket) { if (Depth > 0) Cur++; break; } else if (Tok == TComma || Tok == TSemiColon) { break; } else if (Depth == 0 && Tok == TEndSqBracket) { break; } else if (Tok == TTrue || Tok == TFalse || Tok == TNull) { n.New().SetConst(Cur++, Tok); } else { LOperator o = IsOp(t, PrevIsOp); if (o != OpNull) { // Operator PrevIsOp = 1; n.New().SetOp(o, Cur); } else { PrevIsOp = 0; LVariant m; m = t; LFunc *f = Methods.Find(m.Str()); LFunctionInfo *sf = 0; char16 *Next = GetTok(Cur+1); bool IsFunctionCall = !StricmpW(Next, sStartRdBracket); if (f && IsFunctionCall) { Node &Call = n.New(); Call.SetContextFunction(f, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { - GTokenType Tok = ExpTok.Find(t); + LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { // Do nothing... Cur++; } else if (Tok == TEndRdBracket) { break; } else if (Tok == TSemiColon) { return OnError(Cur, "Unexpected ';'"); } else if (!Expression(Cur, Call.Args.New())) { return OnError(Cur, "Can't parse function argument."); } } } else if ( (sf = Code->GetMethod(m.Str(), IsFunctionCall)) ) { Node &Call = n.New(); Call.SetScriptFunction(sf, Cur++); // Now parse arguments // Get the start bracket if ((t = GetTok(Cur))) { if (StricmpW(t, sStartRdBracket) == 0) Cur++; else return OnError(Cur, "Function missing '('"); } else return OnError(Cur, "No token."); // Parse the args as expressions while ((t = GetTok(Cur))) { if (StricmpW(t, sComma) == 0) { // Do nothing... Cur++; } else if (StricmpW(t, sEndRdBracket) == 0) { break; } else if (!Expression(Cur, Call.Args[Call.Args.Length()])) { return OnError(Cur, "Can't parse function argument."); } } } else if (IsAlpha(*t)) { // Variable... Node &Var = n.New(); if (!DoVariableNode(Cur, Var, t)) return false; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { // Constant string or number n.New().SetConst(Cur, TLiteral); } else if (Tok == TStartCurlyBracket) { // List definition LArray Values; Cur++; int Index = 0; while ((t = Tokens[Cur])) { - GTokenType Tok = ExpTok.Find(t); + LTokenType Tok = ExpTok.Find(t); if (Tok == TComma) { Index++; } else if (Tok == TEndCurlyBracket) { break; } else if (*t == '\'' || *t == '\"' || LIsNumber(t)) { Values[Index] = Cur; } else { return OnError(Cur, "Unexpected token '%S' in list definition", t); } Cur++; } n.New().SetConst(Values, TLiteral); } else { // Unknown return OnError(Cur, "Unknown token '%S'", t); } } Cur++; } } return true; } /// Allocate a register (must be mirrored with DeallocReg) - bool AllocReg(GVarRef &r, int LineOrTok, const char *file, int line) + bool AllocReg(LVarRef &r, int LineOrTok, const char *file, int line) { for (int i=0; iPrint("CompileError:Register[%i] allocated by %s\n", n, a); } } #endif return false; } /// Deallocate a register - bool DeallocReg(GVarRef &r) + bool DeallocReg(LVarRef &r) { if (r.Scope == SCOPE_REGISTER && r.Index >= 0) { int Bit = 1 << r.Index; // LAssert((Bit & Regs) != 0); Regs &= ~Bit; #ifdef _DEBUG RegAllocators[r.Index].Empty(); #endif } return true; } /// Count allocated registers int RegAllocCount() { int c = 0; for (int i=0; i &n) { LStringPipe e; for (unsigned i=0; iMethod.Get()); } else if (n[i].ScriptFunc) { e.Print("%s(...)", n[i].ScriptFunc->GetName()); } else { e.Print("#err#"); } } return e.NewStr(); } /// Creates byte code to evaluate an expression bool AsmExpression ( /// Where the result got stored - GVarRef *Result, + LVarRef *Result, /// The nodes to create code for LArray &n, /// The depth of recursion int Depth = 0 ) { // Resolve any sub-expressions and store their values for (unsigned i = 0; i < n.Length(); i++) { auto &k = n[i]; if (!k.IsVar() && k.Child.Length()) { AllocReg(k.Reg, k.Tok, _FL); AsmExpression(&k.Reg, k.Child, Depth + 1); } } while (n.Length() > 1) { // Find which operator to handle first #ifdef _DEBUG size_t StartLength = n.Length(); #endif int OpIdx = -1; int Prec = -1; int Ops = 0; for (unsigned i=0; i Jmp; if (!TokenToVarRef(a, NullRef)) return OnError(a.Tok, "Can't convert left token to var ref."); if (Op == OpAnd) { // Jump over 'b' if 'a' is FALSE Jmp.Reset(new LJumpZero(this, a.Tok, a.Reg, false)); } else if (Op == OpOr) { // Jump over 'b' if 'a' is TRUE } if (!TokenToVarRef(b, LValue)) return OnError(b.Tok, "Can't convert right token to var ref."); - GVarRef Reg; + LVarRef Reg; Reg.Empty(); if (a.Reg.Scope != SCOPE_REGISTER) { if (AllocReg(Reg, a.Tok, _FL)) { LAssert(Reg != a.Reg); Asm2(a.Tok, IAssign, Reg, a.Reg); a.Reg = Reg; } else return OnError(a.Tok, "Can't alloc register, Regs=0x%x", Regs); } Asm2(a.Tok, Op, a.Reg, b.Reg); if (Op == OpPlusEquals || Op == OpMinusEquals || Op == OpMulEquals || Op == OpDivEquals) { AssignVarRef(a, a.Reg); } } if (a.Reg != b.Reg) DeallocReg(b.Reg); n.DeleteAt(OpIdx+1, true); n.DeleteAt(OpIdx, true); // Result is in n[OpIdx-1] (ie the 'a' node) } else { LAssert(!"Not a valid type"); return OnError(n[0].Tok, "Not a valid type."); } #ifdef _DEBUG if (StartLength == n.Length()) { // No nodes removed... infinite loop! LAssert(!"No nodes removed."); return false; } #endif } if (n.Length() == 1) { auto &k = n[0]; if (!k.Reg.Valid()) { - GVarRef *NullRef = NULL; + LVarRef *NullRef = NULL; if (!TokenToVarRef(k, NullRef)) { return false; } } if (Result) { if (Result->Valid() && *Result != k.Reg) DeallocReg(*Result); *Result = k.Reg; } else { DeallocReg(k.Reg); } return true; } return false; } /// Parses and assembles an expression - bool DoExpression(uint32_t &Cur, GVarRef *Result) + bool DoExpression(uint32_t &Cur, LVarRef *Result) { LArray n; if (Expression(Cur, n)) { bool Status = AsmExpression(Result, n); return Status; } return false; } /// Converts a variable to it's type information bool DoTypeId(uint32_t &Cur, char16 *&Var) { if (GetTokType(Cur) != TTypeId) return OnError(Cur, "Expecting 'typeid'."); Cur++; if (GetTokType(Cur) != TStartRdBracket) return OnError(Cur, "Expecting '('."); Cur++; char16 *t = GetTok(Cur); if (!t || !IsAlpha(*t)) return OnError(Cur, "Expecting variable name."); Cur++; if (GetTokType(Cur) != TEndRdBracket) return OnError(Cur, "Expecting ')'."); Cur++; Var = t; return true; } /// Parses statements bool DoStatements(uint32_t &Cur, bool *LastWasReturn, bool MoreThanOne = true) { while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; - GTokenType Tok = ExpTok.Find(t); + LTokenType Tok = ExpTok.Find(t); switch (Tok) { case TTypeId: { return OnError(Cur, "typeif only valid in an expression."); } case TSemiColon: { Cur++; break; } case TDebug: { Asm0(Cur++, IDebug); break; } case TBreakPoint: { Asm0(Cur++, IBreakPoint); break; } case TReturn: { Cur++; if (!DoReturn(Cur)) return false; if (LastWasReturn) *LastWasReturn = true; break; } case TEndCurlyBracket: case TFunction: { return true; } case TIf: { if (!DoIf(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TFor: { if (!DoFor(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TWhile: { if (!DoWhile(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } case TExtern: { if (!DoExtern(Cur)) return false; if (LastWasReturn) *LastWasReturn = false; break; } default: { if (!DoExpression(Cur, 0)) return false; - GTokenType Tok = ExpTok.Find(GetTok(Cur)); + LTokenType Tok = ExpTok.Find(GetTok(Cur)); if (Tok == TSemiColon) Cur++; if (LastWasReturn) *LastWasReturn = false; break; } } if (!MoreThanOne) break; } return true; } /// Parses if/else if/else construct bool DoIf(uint32_t &Cur) { Cur++; char16 *t; if (GetTokType(Cur) == TStartRdBracket) { Cur++; // Compile and asm code to evaluate the expression - GVarRef Result; + LVarRef Result; int ExpressionTok = Cur; Result.Empty(); if (DoExpression(Cur, &Result)) { t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "if missing ')'."); Cur++; t = GetTok(Cur); if (!t) return OnError(Cur, "if missing body statement."); // Output the jump instruction LAutoPtr Jmp(new LJumpZero(this, ExpressionTok, Result)); if (!StricmpW(t, sStartCurlyBracket)) { // Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Check for else... if ((t = GetTok(Cur)) && StricmpW(t, sElse) == 0) { // Add a jump for the "true" part of the expression to // jump over the "else" part. Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); if (Code->ByteCode.Length(JOffset + 4)) { // Initialize the ptr to zero int32 *Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = 0; // Resolve jz to here... Jmp.Reset(); // Compile the else block Cur++; if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { // 'Else' Statement block Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement if (!DoStatements(Cur, NULL, false)) return false; } // Resolve the "JOffset" jump that takes execution of // the 'true' part over the 'else' part Ptr = (int32*)&Code->ByteCode[JOffset]; *Ptr = (int32) (Code->ByteCode.Length() - JOffset - sizeof(int32)); if (*Ptr == 0) { // Empty statement... so delete the Jump instruction Code->ByteCode.Length(JOffset - 1); } } else OnError(Cur, "Mem alloc"); } return true; } } else return OnError(Cur, "if missing '('"); return false; } LArray &GetByteCode() { return Code->ByteCode; } class LJumpZero { LCompilerPriv *Comp; int JzOffset; public: - LJumpZero(LCompilerPriv *d, int Tok, GVarRef &r, bool DeallocReg = true) + LJumpZero(LCompilerPriv *d, int Tok, LVarRef &r, bool DeallocReg = true) { // Create jump instruction to jump over the body if the expression evaluates to false Comp = d; Comp->Asm1(Tok, IJumpZero, r); if (DeallocReg) Comp->DeallocReg(r); JzOffset = (int) Comp->GetByteCode().Length(); Comp->GetByteCode().Length(JzOffset + sizeof(int32)); } ~LJumpZero() { // Resolve jump int32 *Ptr = (int32*) &Comp->GetByteCode()[JzOffset]; *Ptr = (int32) (Comp->GetByteCode().Length() - (JzOffset + sizeof(int32))); } }; /// Parses while construct bool DoWhile(uint32_t &Cur) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'while'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation - GVarRef r; + LVarRef r; r.Empty(); if (!DoExpression(Cur, &r)) return false; // Create jump instruction to jump over the body if the expression evaluates to false { LJumpZero Jump(this, Cur, r); if (!(t = GetTok(Cur)) || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile the body of the loop if (!(t = GetTok(Cur))) return OnError(Cur, "Unexpected eof."); Cur++; if (StricmpW(t, sStartCurlyBracket) == 0) { // Block while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } else { // Single statement DoStatements(Cur, NULL, false); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Parses for construct bool DoFor(uint32_t &Cur) { /* For loop asm structure: +---------------------------+ | Pre-condition expression | +---------------------------+ | Eval loop contdition exp. |<--+ | | | | JUMP ZERO (jumpz) |---+-+ +---------------------------+ | | | Body of loop... | | | | | | | | | | | | | | | | | | | | | | | +---------------------------+ | | | Post-cond. expression | | | | | | | | JUMP |---+ | +---------------------------+ | | Following code... |<----+ . . */ Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' after 'for'"); Cur++; // Compile initial statement - GVarRef r; + LVarRef r; r.Empty(); t = GetTok(Cur); if (!t) return false; if (StricmpW(t, sSemiColon) && !DoExpression(Cur, 0)) return false; t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Store start of condition code size_t ConditionStart = Code->ByteCode.Length(); // Compile condition evalulation if (!DoExpression(Cur, &r)) return false; { LJumpZero Jmp(this, Cur, r); t = GetTok(Cur); // Look for ';' if (!t || StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; // Compile the post expression code size_t PostCodeStart = Code->ByteCode.Length(); t = GetTok(Cur); if (StricmpW(t, sEndRdBracket) && !DoExpression(Cur, 0)) return false; // Store post expression code in temp variable LArray PostCode; size_t PostCodeLen = Code->ByteCode.Length() - PostCodeStart; if (PostCodeLen) { PostCode.Length(PostCodeLen); memcpy(&PostCode[0], &Code->ByteCode[PostCodeStart], PostCodeLen); // Delete the post expression off the byte code, we are putting it after the block code Code->ByteCode.Length(PostCodeStart); } // Look for ')' t = GetTok(Cur); if (!t || StricmpW(t, sEndRdBracket)) return OnError(Cur, "Expecting ')'"); Cur++; // Compile body of loop if ((t = GetTok(Cur)) && StricmpW(t, sStartCurlyBracket) == 0) { Cur++; while ((t = GetTok(Cur))) { if (!StricmpW(t, sSemiColon)) { Cur++; } else if (!StricmpW(t, sEndCurlyBracket)) { Cur++; break; } else if (!DoStatements(Cur, NULL)) { return false; } } } // Add post expression code if (PostCodeLen) { size_t Len = Code->ByteCode.Length(); Code->ByteCode.Length(Len + PostCodeLen); memcpy(&Code->ByteCode[Len], &PostCode[0], PostCodeLen); } // Jump to condition evaluation at 'ConditionStart' Asm0(Cur, IJump); size_t JOffset = Code->ByteCode.Length(); Code->ByteCode.Length(JOffset + sizeof(int32)); int32 *Ptr = (int32*) &Code->ByteCode[JOffset]; *Ptr = (int32) (ConditionStart - Code->ByteCode.Length()); } return true; } /// Compiles return construct bool DoReturn(uint32_t &Cur) { - GVarRef ReturnValue; + LVarRef ReturnValue; char16 *t; int StartTok = Cur; ReturnValue.Empty(); LArray Exp; if (!Expression(Cur, Exp)) { return OnError(Cur, "Failed to compile return expression."); } else if (!AsmExpression(&ReturnValue, Exp)) { return OnError(Cur, "Failed to assemble return expression."); } if (!(t = GetTok(Cur)) || StricmpW(t, sSemiColon)) { return OnError(Cur, "Expecting ';' after return expression."); } Asm1(StartTok, IRet, ReturnValue); DeallocReg(ReturnValue); Cur++; return true; } // Compile a method definition bool DoFunction ( /// Cursor token index uint32_t &Cur, /// [Optional] Current struct / class. /// If not NULL this is a method definition for said class. LCustomType *Struct = NULL ) { bool Status = false; bool LastInstIsReturn = false; if (!JumpLoc) { size_t Len = Code->ByteCode.Length(); if (Code->ByteCode.Length(Len + 5)) { GPtr p; p.u8 = &Code->ByteCode[Len]; *p.u8++ = IJump; *p.i32++ = 0; JumpLoc = Len + 1; } else OnError(Cur, "Mem alloc failed."); } LString FunctionName; LCustomType::Method *StructMethod = NULL; // Member function of script struct/class LFunctionInfo *ScriptMethod = NULL; // Standalone scripting function char16 *Name = GetTok(Cur); if (Name) { Cur++; char16 *t = GetTok(Cur); if (!t || StricmpW(t, sStartRdBracket)) return OnError(Cur, "Expecting '(' in function."); FunctionName = Name; // Parse parameters LArray Params; Cur++; while ((t = GetTok(Cur))) { if (IsAlpha(*t)) { Params.New() = t; Cur++; if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; } if (!StricmpW(t, sComma)) ; else if (!StricmpW(t, sEndRdBracket)) { Cur++; break; } else goto UnexpectedFuncEof; Cur++; } if (Struct) { StructMethod = Struct->DefineMethod(FunctionName, Params, Code->ByteCode.Length()); } else { ScriptMethod = Code->GetMethod(FunctionName, true); if (!ScriptMethod) return OnError(Cur, "Can't define method '%s'.", FunctionName.Get()); ScriptMethod->GetParams() = Params; ScriptMethod->SetStartAddr((int32_t)Code->ByteCode.Length()); } // Parse start of body if (!(t = GetTok(Cur))) goto UnexpectedFuncEof; if (StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'."); // Setup new scope(s) LAutoPtr ObjectScope; if (Struct) { // The object scope has to be first so that local variables take // precedence over object member variables. if (ObjectScope.Reset(new LVariables(Struct))) Scopes.Add(ObjectScope); } LVariables LocalScope(SCOPE_LOCAL); if (Struct) LocalScope.Var("This", true); for (unsigned i=0; iFrameSize = (uint16)LocalScope.Length(); else if (ScriptMethod) ScriptMethod->SetFrameSize(LocalScope.Length()); else LAssert(!"What are you defining exactly?"); Status = true; Cur++; // LgiTrace("Added method %s @ %i, stack=%i, args=%i\n", f->Name.Str(), f->StartAddr, f->FrameSize, f->Params.Length()); break; } // Parse statement in the body if (!DoStatements(Cur, &LastInstIsReturn)) return OnError(Cur, "Can't compile function body."); } // Remove any scopes we created Scopes.PopLast(); if (Struct) Scopes.PopLast(); } if (!LastInstIsReturn) { - GVarRef RetVal; + LVarRef RetVal; AllocNull(RetVal); Asm1(Cur, IRet, RetVal); } return Status; UnexpectedFuncEof: return OnError(Cur, "Unexpected EOF in function."); } LExternFunc::ExternType ParseExternType(LArray &Strs) { LExternFunc::ExternType Type; Type.Out = false; Type.Ptr = 0; Type.ArrayLen = 1; Type.Base = GV_NULL; Type.Unsigned = false; bool InArray = false; for (unsigned i=0; i Tok; const char16 *t; while ((t = GetTok(Cur))) { if (!StricmpW(t, sStartRdBracket)) { Cur++; break; } else Tok.Add(t); Cur++; } if (Tok.Length() < 3) { return OnError(Cur, "Not enough tokens in extern decl."); } // First token is library name LExternFunc *e = new LExternFunc; if (!e) return OnError(Cur, "Alloc error."); Code->Externs.Add(e); e->Type = ExternFunc; char16 sQuotes[] = {'\'','\"',0}; LAutoWString LibName(TrimStrW(Tok[0], sQuotes)); e->Lib.Reset(WideToUtf8(LibName)); Tok.DeleteAt(0, true); e->Method = Tok.Last(); Tok.DeleteAt(Tok.Length()-1, true); if (!ValidStr(e->Method)) { Code->Externs.Length(Code->Externs.Length()-1); return OnError(Cur, "Failed to get extern method name."); } // Parse return type e->ReturnType = ParseExternType(Tok); // Parse argument types Tok.Length(0); while ((t = GetTok(Cur))) { if (!StricmpW(t, sEndRdBracket)) { e->ArgType.New() = ParseExternType(Tok); Cur++; break; } else if (!StricmpW(t, sComma)) { e->ArgType.New() = ParseExternType(Tok); } else Tok.Add(t); Cur++; } if ((t = GetTok(Cur))) { if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';' in extern decl."); Cur++; } Methods.Add(e->Method, e); return true; } struct ExpPart { LOperator Op; int Value; ExpPart() { Op = OpNull; Value = 0; } }; int Evaluate(LArray &Exp, size_t Start, size_t End) { LArray p; // Find outer brackets ssize_t e; for (e = End; e >= (ssize_t)Start; e--) { if (Exp[e][0] == ')') break; } for (size_t i = Start; i <= End; i++) { char16 *t = Exp[i]; if (*t == '(') { p.New().Value = Evaluate(Exp, i + 1, e - 1); i = e; } else { LOperator op = IsOp(t, 0); if (op) { p.New().Op = op; } else if (IsDigit(*t)) { p.New().Value = AtoiW(t); } else { LAssert(0); break; } } } while (p.Length() > 1) { int HighPrec = 32; int Idx = -1; for (unsigned i=0; i 0 && Idx < (int)p.Length() - 1) { switch (p[Idx].Op) { case OpPlus: p[Idx-1].Value += p[Idx+1].Value; break; case OpMinus: p[Idx-1].Value -= p[Idx+1].Value; break; case OpMul: p[Idx-1].Value *= p[Idx+1].Value; break; case OpDiv: if (p[Idx+1].Value) p[Idx-1].Value /= p[Idx+1].Value; else LAssert(!"Div 0"); break; default: LAssert(!"Impl me."); break; } p.DeleteAt(Idx, true); p.DeleteAt(Idx, true); } else { LAssert(0); break; } } else { LAssert(!"Impl me."); } } if (p.Length() == 1) { LAssert(p[0].Op == OpNull); return p[0].Value; } LAssert(0); return 0; } int ByteSizeFromType(char *s, LVariantType t) { switch (t) { case GV_INT32: { char n[16], *o = n; for (char *c = s; *c; c++) { if (IsDigit(*c) && o < n + sizeof(n) - 1) *o++ = *c; } *o++ = 0; int bits = ::atoi(n); switch (bits) { case 8: return 1; case 16: return 2; } return 4; break; } case GV_INT64: { return 8; } case GV_WSTRING: { return sizeof(char16); } default: break; } return 1; } /// Compiles struct construct bool DoStruct(uint32_t &Cur) { bool Status = false; // Parse struct name and setup a type char16 *t; LCustomType *Def = Code->Types.Find(t = GetTok(Cur)); if (!Def) Code->Types.Add(t, Def = new LCustomType(t)); Cur++; t = GetTok(Cur); if (!t || StricmpW(t, sStartCurlyBracket)) return OnError(Cur, "Expecting '{'"); Cur++; // Parse members while ((t = GetTok(Cur))) { // End of type def? - GTokenType tt = ExpTok.Find(t); + LTokenType tt = ExpTok.Find(t); if (tt == TEndCurlyBracket) { Cur++; tt = GetTokType(Cur); if (!tt || tt != TSemiColon) return OnError(Cur, "Expecting ';' after '}'"); Status = true; break; } if (tt == TFunction) { Cur++; if (!DoFunction(Cur, Def)) return false; } else { // Parse member field LVariant TypeName = t; LCustomType *NestedType = 0; LVariantType Type = Types.Find(TypeName.Str()); if (!Type) { // Check other custom types NestedType = Code->GetType(t); if (!NestedType) return OnError(Cur, "Unknown type '%S' in struct definition.", t); // Ok, nested type. Type = GV_CUSTOM; } Cur++; if (!(t = GetTok(Cur))) goto EofError; bool Pointer = false; if (t[0] == '*' && t[1] == 0) { Pointer = true; Cur++; if (!(t = GetTok(Cur))) goto EofError; } LVariant Name = t; Cur++; if (!(t = GetTok(Cur))) goto EofError; int Array = 1; if (!StricmpW(t, sStartSqBracket)) { // Array Cur++; LArray Exp; while ((t = GetTok(Cur))) { Cur++; if (!StricmpW(t, sEndSqBracket)) break; Exp.Add(t); } Array = Evaluate(Exp, 0, Exp.Length()-1); } int MemberAddr = Def->AddressOf(Name.Str()); if (MemberAddr >= 0) return OnError(Cur, "Member '%s' can't be defined twice.", Name.Str()); if (NestedType) { if (!Def->DefineField(Name.Str(), NestedType, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } else { int Bytes = ByteSizeFromType(TypeName.Str(), Type); if (!Def->DefineField(Name.Str(), Type, Bytes, Array)) return OnError(Cur, "Failed to define field '%s'.", Name.Str()); } t = GetTok(Cur); if (StricmpW(t, sSemiColon)) return OnError(Cur, "Expecting ';'"); Cur++; } } return Status; EofError: return OnError(Cur, "Unexpected EOF."); } /// Compiler entry point bool Compile() { uint32_t Cur = 0; JumpLoc = 0; // Setup the global scope Scopes.Length(0); Scopes.Add(&Code->Globals); // Compile the code... while (Cur < Tokens.Length()) { char16 *t = GetTok(Cur); if (!t) break; if (*t == '#' || StricmpW(t, sSemiColon) == 0) { Cur++; } else if (!StricmpW(t, sFunction)) { if (!DoFunction(++Cur)) return false; } else if (!StricmpW(t, sStruct)) { if (!DoStruct(++Cur)) return false; } else if (!StricmpW(t, sEndCurlyBracket)) { return OnError(Cur, "Not expecting '}'."); } else if (!StricmpW(t, sExtern)) { if (!DoExtern(++Cur)) return false; } else { if (JumpLoc) { GPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } if (!DoStatements(Cur, NULL)) { return OnError(Cur, "Statement compilation failed."); } } } if (JumpLoc) { GPtr p; p.u8 = &Code->ByteCode[JumpLoc]; *p.u32 = (uint32_t) (Code->ByteCode.Length() - (JumpLoc + 4)); JumpLoc = 0; } // Do link time fix ups... for (unsigned i=0; iValidStartAddr()) { if (f.Args != f.Func->GetParams().Length()) { return OnError(f.Tok, "Function call '%s' has wrong arg count (caller=%i, method=%i).", f.Func->GetName(), f.Args, f.Func->GetParams().Length()); } else if (!f.Func->ValidFrameSize()) { return OnError(f.Tok, "Function call '%s' has no frame size.", f.Func->GetName()); } else { GPtr p; p.u8 = &Code->ByteCode[f.Offset]; LAssert(*p.u32 == 0); *p.u32++ = f.Func->GetStartAddr(); *p.u16++ = f.Func->GetFrameSize(); } } else { return OnError(f.Tok, "Function '%s' not defined.", f.Func->GetName()); } } Fixups.Length(0); return true; } }; LCompiler::LCompiler() { d = new LCompilerPriv; } LCompiler::~LCompiler() { DeleteObj(d); } bool LCompiler::Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ) { if (!Script) return false; LStringPipe p; #ifdef DEBUG_SCRIPT_FILE d->Debug = Stristr(FileName, DEBUG_SCRIPT_FILE) != NULL; #endif if (SysContext && SysContext->GetLog()) d->Log = SysContext->GetLog(); else if (UserContext && UserContext->GetLog()) d->Log = UserContext->GetLog(); else d->Log = &p; d->Methods.Empty(); if (SysContext) { GHostFunc *f = SysContext->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = SysContext; d->Methods.Add(f[i].Method, &f[i]); } } d->SysCtx = SysContext; d->UserCtx = UserContext; if (d->UserCtx) { GHostFunc *f = d->UserCtx->GetCommands(); for (int i=0; f[i].Method; i++) { f[i].Context = d->UserCtx; if (!d->Methods.Find(f[i].Method)) d->Methods.Add(f[i].Method, f+i); else { LgiTrace("%s:%i - Conflicting name of method in application's context: '%s'\n", _FL, f[i].Method.Get()); LAssert(!"Conflicting name of method in application's context."); } } } if (!Code) Code.Reset(new LCompiledCode); bool Status = false; d->Code = dynamic_cast(Code.Get()); if (d->Code) { d->Code->UserContext = UserContext; d->Code->SysContext = SysContext; d->Code->SetSource(FileName, Script); bool LexResult = d->Lex((char*)Script, FileName); if (LexResult) { d->ScriptArgs = Args; Status = d->Compile(); } else { d->OnError(0, "Failed to lex script.\n"); } } else { d->OnError(0, "Allocation failed.\n"); } d->Code = NULL; return Status; } ////////////////////////////////////////////////////////////////////// class LScriptEnginePrivate { public: LViewI *Parent; SystemFunctions SysContext; LScriptContext *UserContext; LCompiledCode *Code; LVmDebuggerCallback *Callback; LVariant ReturnValue; LScriptEnginePrivate() { UserContext = NULL; Parent = NULL; Code = NULL; Callback = NULL; } }; LScriptEngine::LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmDebuggerCallback *Callback) { d = new LScriptEnginePrivate; d->Parent = parent; d->UserContext = UserContext; d->Callback = Callback; d->SysContext.SetEngine(this); } LScriptEngine::~LScriptEngine() { DeleteObj(d); } LCompiledCode *LScriptEngine::GetCurrentCode() { return d->Code; } bool LScriptEngine::Compile(LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName, LDom *Args) { if (!Script) { LAssert(!"Param error"); return NULL; } LCompiler Comp; return Comp.Compile(Obj, &d->SysContext, UserContext ? UserContext : d->UserContext, FileName, Script, Args); } LExecutionStatus LScriptEngine::Run(LCompiledCode *Obj, LVariant *Ret, const char *TempPath) { LExecutionStatus Status = ScriptError; d->Code = Obj; if (d->Code) { LVirtualMachine Vm(d->Callback); if (TempPath) Vm.SetTempPath(TempPath); Status = Vm.Execute(d->Code, 0, NULL, true, Ret ? Ret : &d->ReturnValue); d->Code = NULL; } return Status; } LExecutionStatus LScriptEngine::RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret) { LExecutionStatus Status = ScriptError; LCompiledCode *Code = dynamic_cast(Obj); if (Script && Code) { LAutoPtr Temp(new LCompiledCode(*Code)); uint32_t TempLen = (uint32_t) Temp->Length(); d->Code = Temp; LCompiler Comp; if (Comp.Compile(Temp, &d->SysContext, d->UserContext, Temp->GetFileName(), Script, NULL)) { LVirtualMachine Vm(d->Callback); Status = Vm.Execute(dynamic_cast(Temp.Get()), TempLen, NULL, true, Ret ? Ret : &d->ReturnValue); } d->Code = NULL; } return Status; } bool LScriptEngine::EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression) { if (!Result || !VariableSource || !Expression) { LAssert(!"Param error"); return false; } // Create trivial script to evaluate the expression LString a; a.Printf("return %s;", Expression); // Compile the script LCompiler Comp; LAutoPtr Obj; if (!Comp.Compile(Obj, NULL, NULL, NULL, a, VariableSource)) { LAssert(0); return false; } // Execute the script LVirtualMachine Vm(d->Callback); LCompiledCode *Code = dynamic_cast(Obj.Get()); auto ReturnVal = Result ? Result : &d->ReturnValue; LExecutionStatus s = Vm.Execute(Code, 0, NULL, true, ReturnVal); if (s != ScriptSuccess) { return false; } if (ReturnVal->Type == GV_INT64 && !(ReturnVal->Value.Int64 >> 32)) { *ReturnVal = ReturnVal->CastInt32(); } return true; } LStream *LScriptEngine::GetConsole() { if (d->SysContext.GetLog()) return d->SysContext.GetLog(); if (d->UserContext && d->UserContext->GetLog()) return d->UserContext->GetLog(); return NULL; } bool LScriptEngine::SetConsole(LStream *t) { d->SysContext.SetLog(t); if (d->UserContext) d->UserContext->SetLog(t); return true; } bool LScriptEngine::CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args) { LCompiledCode *Code = dynamic_cast(Obj); if (!Code || !Method) return false; LFunctionInfo *i = Code->GetMethod(Method); if (!i) return false; LVirtualMachine Vm(d->Callback); Args.Vm = &Vm; LExecutionStatus Status = Vm.ExecuteFunction(Code, i, Args, NULL); Args.Vm = NULL; return Status != ScriptError; } LScriptContext *LScriptEngine::GetSystemContext() { return &d->SysContext; } diff --git a/src/common/Coding/ScriptLibrary.cpp b/src/common/Coding/ScriptLibrary.cpp --- a/src/common/Coding/ScriptLibrary.cpp +++ b/src/common/Coding/ScriptLibrary.cpp @@ -1,1161 +1,1189 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/SubProcess.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScriptingPriv.h" ////////////////////////////////////////////////////////////////////////////////////// char16 sChar[] = L"char"; char16 sInt[] = { 'i','n','t', 0 }; char16 sUInt[] = { 'u','i','n','t', 0 }; char16 sInt32[] = { 'i','n','t','3','2', 0 }; char16 sUInt32[] = { 'u','i','n','t','3','2', 0 }; char16 sInt64[] = { 'i','n','t','6','4', 0 }; char16 sHWND[] = { 'H','W','N','D', 0 }; char16 sDWORD[] = { 'D','W','O','R','D', 0 }; char16 sLPTSTR[] = { 's','L','P','T','S','T','R', 0 }; char16 sLPCTSTR[] = { 's','L','P','C','T','S','T','R', 0 }; char16 sElse[] = { 'e','l','s','e', 0 }; char16 sIf[] = { 'i','f',0 }; char16 sFunction[] = { 'f','u','n','c','t','i','o','n',0 }; char16 sExtern[] = { 'e','x','t','e','r','n',0 }; char16 sFor[] = { 'f','o','r',0 }; char16 sWhile[] = { 'w','h','i','l','e',0 }; char16 sReturn[] = { 'r','e','t','u','r','n',0 }; char16 sInclude[] = { 'i','n','c','l','u','d','e',0 }; char16 sDefine[] = { 'd','e','f','i','n','e',0 }; char16 sStruct[] = { 's','t','r','u','c','t',0 }; char16 sTrue[] = { 't','r','u','e',0 }; char16 sFalse[] = { 'f','a','l','s','e',0 }; char16 sNull[] = { 'n','u','l','l',0 }; char16 sOutParam[] = { '_','o','u','t','_',0}; char16 sHash[] = { '#', 0 }; char16 sPeriod[] = { '.', 0 }; char16 sComma[] = { ',', 0 }; char16 sSemiColon[] = { ';', 0 }; char16 sStartRdBracket[] = { '(', 0 }; char16 sEndRdBracket[] = { ')', 0 }; char16 sStartSqBracket[] = { '[', 0 }; char16 sEndSqBracket[] = { ']', 0 }; char16 sStartCurlyBracket[] = { '{', 0 }; char16 sEndCurlyBracket[] = { '}', 0 }; ////////////////////////////////////////////////////////////////////////////////////// LExecutionStatus GHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { return (Ctx->*(Func))(Args) ? ScriptSuccess : ScriptError; } const char *InstToString(GInstruction i) { #undef _i #define _i(name, opcode, desc) \ case name: return desc; switch (i) { AllInstructions } return "#err"; } LStream LScriptArguments::NullConsole; ////////////////////////////////////////////////////////////////////////////////////// int LScriptUtils::atoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atoi(b); } return i; } int64 LScriptUtils::atoi64(char16 *s) { int64 i = 0; if (s) { #ifdef _MSC_VER i = _wtoi64(s); #else char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = strtoll(b, 0, 10); #endif } return i; } double LScriptUtils::atof(char16 *s) { double i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atof(b); } return i; } int LScriptUtils::htoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::htoi(b); } return i; } ////////////////////////////////////////////////////////////////////////////////////// SystemFunctions::SystemFunctions() { Engine = NULL; Log = NULL; #ifdef WINNATIVE Brk = NULL; #endif } SystemFunctions::~SystemFunctions() { } LStream *SystemFunctions::GetLog() { return Log; } bool SystemFunctions::SetLog(LStream *log) { LAssert(Log == NULL); Log = log; return true; } void SystemFunctions::SetEngine(LScriptEngine *Eng) { Engine = Eng; } bool SystemFunctions::Assert(LScriptArguments &Args) { *Args.GetReturn() = true; if (Args.Length() == 0) return true; auto v = Args[0]->CastInt32(); if (!v) { const char *Msg = Args.Length() > 1 ? Args[1]->CastString() : NULL; *Args.GetReturn() = false; Args.Throw(NULL, -1, Msg); } return true; } bool SystemFunctions::DebuggerEnabled(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } Args.GetVm()->SetDebuggerEnabled(Args[0]->CastInt32() != 0); return true; } bool SystemFunctions::Throw(LScriptArguments &Args) { const char *Msg = Args.Length() > 0 ? Args[0]->CastString() : NULL; Args.Throw(NULL, -1, Msg); return true; } bool SystemFunctions::LoadString(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LLoadString(Args[0]->CastInt32()); return true; } bool SystemFunctions::Sprintf(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } char *Fmt = Args[0]->Str(); if (!Fmt) return false; #if defined(LINUX) || defined(MAC) // No support for sprintf with generated args... hack a string up // Formatting widths etc not supported. LArray s; int i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { f++; // Skip '%' // char *Fmt = f; while (*f && !IsAlpha(*f)) f++; // Skip formatting.. if (i >= Args.Length()) break; // No more arguments... switch (*f) { case 's': { // String... char *v = Args[i]->CastString(); if (v) s.Add(v, strlen(v)); else s.Add((char*)"(null)", 4); break; } case 'c': { char *Str = Args[i]->Str(); s.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { break; } case 'u': case 'd': case 'i': { // Int... LString v; v.Printf("%i", Args[i]->CastInt32()); s.Add(v.Get(), v.Length()); break; } } - f++; + i++; } else s.Add(*f); } s.Add(0); // NULL terminate *Args.GetReturn() = s.AddressOf(); #else LArray Params; va_list a; unsigned i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { char *t = f + 1; while (*t && !IsAlpha(*t)) t++; if (i >= Args.Length()) { LAssert(!"Not enough args."); break; } switch (*t) { case 's': { Params.Add((UNativeInt)Args[i++]->Str()); break; } case 'c': { char *Str = Args[i++]->Str(); Params.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { union tmp { double Dbl; struct { uint32_t High; uint32_t Low; }; } Tmp; Tmp.Dbl = Args[i++]->CastDouble(); Params.Add(Tmp.High); Params.Add(Tmp.Low); break; } default: { Params.Add(Args[i++]->CastInt32()); break; } } f = *t ? t + 1 : t; } } a = (va_list) &Params[0]; #ifndef WIN32 #define _vsnprintf vsnprintf #endif char Buf[1024]; vsprintf_s(Buf, sizeof(Buf), Fmt, a); *Args.GetReturn() = Buf; #endif return true; } bool SystemFunctions::ReadTextFile(LScriptArguments &Args) { if (Args.Length() == 1 && LFileExists(Args[0]->CastString())) { if (Args.GetReturn()->OwnStr(::LReadTextFile(Args[0]->CastString()))) return true; } return false; } bool SystemFunctions::WriteTextFile(LScriptArguments &Args) { if (Args.Length() == 2) { LFile f; if (f.Open(Args[0]->CastString(), O_WRITE)) { f.SetSize(0); LVariant *v = Args[1]; if (v) { switch (v->Type) { default: break; case GV_STRING: { size_t Len = strlen(v->Value.String); *Args.GetReturn() = f.Write(v->Value.String, Len) == Len; return true; break; } case GV_BINARY: { *Args.GetReturn() = f.Write(v->Value.Binary.Data, v->Value.Binary.Length) == v->Value.Binary.Length; return true; break; } } } } } return false; } LView *SystemFunctions::CastLView(LVariant &v) { switch (v.Type) { default: break; case GV_DOM: return dynamic_cast(v.Value.Dom); case GV_GVIEW: return v.Value.View; } return 0; } bool SystemFunctions::WaitForReturn(LScriptArguments &Args) { while (Args.GetReturn()->Type != GV_NULL) { // This should only ever loop on Haiku... LYield(); LSleep(10); } return true; } bool SystemFunctions::SelectFiles(LScriptArguments &Args) { LFileSelect *s = new LFileSelect; Args.GetReturn()->Empty(); if (Args.Length() > 0) s->Parent(CastLView(*Args[0])); auto t = LString(Args.Length() > 1 ? Args[1]->CastString() : NULL).SplitDelimit(",;:"); for (auto c: t) { char *sp = strrchr(c, ' '); if (sp) { *sp++ = 0; s->Type(sp, c); } else { char *dot = strrchr(c, '.'); if (dot) { char Type[256]; sprintf_s(Type, sizeof(Type), "%s files", dot + 1); s->Type(Type, c); } } } s->Type("All Files", LGI_ALL_FILES); s->InitialDir(Args.Length() > 2 ? Args[2]->CastString() : 0); s->MultiSelect(Args.Length() > 3 ? Args[3]->CastInt32() != 0 : true); bool SaveAs = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; auto Process = [&Args](LFileSelect *s, bool ok) { if (ok) { Args.GetReturn()->SetList(); auto Lst = Args.GetReturn()->Value.Lst; for (unsigned i=0; iLength(); i++) Lst->Insert(new LVariant((*s)[i])); } else *Args.GetReturn() = false; delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); return WaitForReturn(Args); } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { LFileSelect *s = new LFileSelect; if (Args.Length() > 0) s->Parent(CastLView(*Args[0])); s->InitialDir(Args.Length() > 1 ? Args[1]->CastString() : 0); Args.GetReturn()->Empty(); s->OpenFolder([&Args](LFileSelect *s, bool ok) { if (ok) *Args.GetReturn() = s->Name(); else *Args.GetReturn() = false; delete s; }); return WaitForReturn(Args); } bool SystemFunctions::Sleep(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LSleep(Args[0]->CastInt32()); return true; } bool SystemFunctions::ToString(LScriptArguments &Args) { LStringPipe p; const char *Sep = ", "; for (unsigned i=0; iToString(); p.Print("%s%s", i?Sep:"", s.Get()); } Args.GetReturn()->OwnStr(p.NewStr()); return true; } bool SystemFunctions::Print(LScriptArguments &Args) { LStream *Out = Log ? Log : (Engine ? Engine->GetConsole() : NULL); for (unsigned n=0; Out && nWrite("NULL", 4); continue; } #if 1 size_t Len = strlen(f); Out->Write(f, Len); #else char *i = f, *o = f; for (; *i; i++) { if (*i == '\\') { i++; switch (*i) { case 'n': *o++ = '\n'; break; case 'r': *o++ = '\r'; break; case 't': *o++ = '\t'; break; case '\\': *o++ = '\\'; break; case '0': *o++ = 0; break; } } else { *o++ = *i; } } *o = 0; Out->Write(f, o - f); #endif } return true; } bool SystemFunctions::FormatSize(LScriptArguments &Args) { if (Args.Length() != 1) return false; char s[64]; LFormatSize(s, sizeof(s), Args[0]->CastInt64()); *Args.GetReturn() = s; return true; } bool SystemFunctions::ClockTick(LScriptArguments &Args) { *Args.GetReturn() = (int64)LCurrentTime(); return true; } bool SystemFunctions::Now(LScriptArguments &Args) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_DATETIME; Args.GetReturn()->Value.Date = new LDateTime; Args.GetReturn()->Value.Date->SetNow(); return true; } bool SystemFunctions::New(LScriptArguments &Args) { if (Args.Length() < 1 || !Args[0]) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->Empty(); char *sType = Args[0]->CastString(); if (!sType) return false; if (IsDigit(*sType)) { // Binary block int Bytes = ::atoi(sType); if (!Bytes) return false; return Args.GetReturn()->SetBinary(Bytes, new char[Bytes], true); } LVariant *Ret = Args.GetReturn(); LDomProperty Type = LStringToDomProp(sType); switch (Type) { case TypeList: { Ret->SetList(); break; } case TypeHashTable: { Ret->SetHashTable(); break; } case TypeSurface: { Ret->Empty(); Ret->Type = GV_LSURFACE; if ((Ret->Value.Surface.Ptr = new LMemDC)) { Ret->Value.Surface.Ptr->AddRef(); Ret->Value.Surface.Own = true; } break; } case TypeFile: { Ret->Empty(); #if 1 Ret->Type = GV_STREAM; Ret->Value.Stream.Ptr = new LFile; if (Ret->Value.Stream.Ptr) Ret->Value.Stream.Own = true; #else Ret->Type = GV_GFILE; if ((Ret->Value.File.Ptr = new LFile)) { Ret->Value.File.Ptr->AddRef(); Ret->Value.File.Own = true; } #endif break; } case TypeDateTime: { Ret->Empty(); Ret->Type = GV_DATETIME; Ret->Value.Date = new LDateTime; break; } default: { Ret->Empty(); LCompiledCode *c = Engine ? Engine->GetCurrentCode() : NULL; if (!c) return false; LAutoWString o(Utf8ToWide(sType)); LCustomType *t = c->GetType(o); if (t) { int ArrayLength = Args.Length() > 1 ? Args[1]->CastInt32() : 1; if (ArrayLength > 0) { Ret->Type = GV_CUSTOM; Ret->Value.Custom.Dom = t; Ret->Value.Custom.Data = new uint8_t[t->Sizeof() * ArrayLength]; } } } } return true; } bool SystemFunctions::Len(LScriptArguments &Args) { size_t i = 0; for (LVariant *v: Args) { switch (v->Type) { case GV_LIST: i += v->Value.Lst->Length(); break; case GV_HASHTABLE: i += v->Value.Hash->Length(); break; case GV_BINARY: i += v->Value.Binary.Length; break; case GV_STRING: i += Strlen(v->Value.String); break; case GV_WSTRING: i += Strlen(v->Value.WString); break; default: i += 1; break; } } *Args.GetReturn() = i; return true; } bool SystemFunctions::Delete(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LVariant *v = Args[0]; if (v->Type == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } else { v->Empty(); } *Args.GetReturn() = true; return true; } class LFileListEntry : public LDom { bool Folder; LVariant Name; int64 Size; LDateTime Modified; public: LFileListEntry(LDirectory *d) { Folder = d->IsDir(); Name = d->GetName(); Size = d->GetSize(); Modified.Set(d->GetLastWriteTime()); } bool GetVariant(const char *Var, LVariant &Value, const char *Arr = NULL) override { LDomProperty p = LStringToDomProp(Var); switch (p) { case ObjName: Value = Name; break; case ObjLength: Value = Size; break; case FileFolder: Value = Folder; break; case FileModified: Value = &Modified; break; default: return false; } return true; } }; bool SystemFunctions::DeleteFile(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } char *f = Args[0]->CastString(); if (f) *Args.GetReturn() = FileDev->Delete(Args[0]->CastString()); else *Args.GetReturn() = false; return true; } bool SystemFunctions::CurrentScript(LScriptArguments &Args) { LCompiledCode *Code; if (Engine && (Code = Engine->GetCurrentCode())) { *Args.GetReturn() = Code->GetFileName(); return true; } return false; } bool SystemFunctions::PathExists(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } LDirectory d; if (d.First(Args[0]->CastString(), NULL)) { if (d.IsDir()) *Args.GetReturn() = 2; else *Args.GetReturn() = 1; } else { *Args.GetReturn() = 0; } return true; } bool SystemFunctions::PathJoin(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; for (unsigned i=0; iCastString(); if (i) LMakePath(p, sizeof(p), p, s); else strcpy_s(p, sizeof(p), s); } if (*p) *Args.GetReturn() = p; else Args.GetReturn()->Empty(); return true; } bool SystemFunctions::PathSep(LScriptArguments &Args) { *Args.GetReturn() = DIR_STR; return true; } bool SystemFunctions::ListFiles(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->SetList(); LDirectory d; char *Pattern = Args.Length() > 1 ? Args[1]->CastString() : 0; char *Folder = Args[0]->CastString(); for (int b=d.First(Folder); b; b=d.Next()) { if (!Pattern || MatchStr(Pattern, d.GetName())) { Args.GetReturn()->Value.Lst->Insert(new LVariant(new LFileListEntry(&d))); } } return true; } bool SystemFunctions::CreateSurface(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } int x = Args[0]->CastInt32(); int y = Args[1]->CastInt32(); LColourSpace Cs = CsNone; if (Args.Length() > 2) { LVariant *Type = Args[2]; const char *c; if (Type->IsInt()) { // Bit depth... convert to default Colour Space. Cs = LBitsToColourSpace(Type->CastInt32()); } else if ((c = Type->Str())) { // Parse string colour space def Cs = LStringToColourSpace(Type->Str()); } } if (!Cs) // Catch all error cases and make it the default screen depth. Cs = GdcD->GetColourSpace(); if ((Args.GetReturn()->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { Args.GetReturn()->Type = GV_LSURFACE; Args.GetReturn()->Value.Surface.Own = true; Args.GetReturn()->Value.Surface.Ptr->AddRef(); } return true; } +bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) +{ + Args.GetReturn()->Empty(); + if (Args.Length() != 1) + { + LAssert(!"Wrong args."); + return false; + } + + *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); + return true; +} + +bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) +{ + Args.GetReturn()->Empty(); + if (Args.Length() != 1) + { + LAssert(!"Wrong args."); + return false; + } + + *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); + return true; +} + bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); auto *Msg = Args[1]->Str(); auto *Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); uint32_t Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; auto Btn = LgiMsg(Parent, Msg, Title, Btns); *Args.GetReturn() = Btn; return true; } bool SystemFunctions::GetInputDlg(LScriptArguments &Args) { if (Args.Length() < 4) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); char *InitVal = Args[1]->Str(); char *Msg = Args[2]->Str(); char *Title = Args[3]->Str(); bool Pass = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; LInput *Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); Dlg->DoModal([Dlg, &Args](auto d, auto code) { *Args.GetReturn() = code ? Dlg->GetStr() : -1; delete Dlg; }); return WaitForReturn(Args); } bool SystemFunctions::GetViewById(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); int Id = Args[1]->CastInt32(); if (!Parent || Id <= 0) return false; if (Parent->GetViewById(Id, Args.GetReturn()->Value.View)) { Args.GetReturn()->Type = GV_GVIEW; } return true; } bool SystemFunctions::Execute(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LStringPipe p; char *Exe = Args[0]->CastString(); char *Arguments = Args[1]->CastString(); LSubProcess e(Exe, Arguments); bool Status = e.Start(); if (Status) { e.Communicate(&p); LAutoString o(p.NewStr()); *Args.GetReturn() = o; } else if (Log) { uint32_t ErrCode = e.GetErrorCode(); LString ErrMsg = LErrorCodeToString(ErrCode); if (ErrMsg) Log->Print("Error: Execute(\"%s\",\"%s\") failed with '%s'\n", Exe, Arguments, ErrMsg.Get()); else Log->Print("Error: Execute(\"%s\",\"%s\") failed with '0x%x'\n", Exe, Arguments, ErrCode); } return Status; } bool SystemFunctions::System(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } char *Exe = Args[0]->Str(); char *Arg = Args[1]->Str(); *Args.GetReturn() = LExecute(Exe, Arg); return true; } bool SystemFunctions::OsName(LScriptArguments &Args) { *Args.GetReturn() = LGetOsName(); return true; } bool SystemFunctions::OsVersion(LScriptArguments &Args) { LArray Ver; LGetOs(&Ver); Args.GetReturn()->SetList(); for (int i=0; i<3; i++) Args.GetReturn()->Value.Lst->Insert(new LVariant(Ver[i])); return true; } #define DefFn(Name) \ GHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) GHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), // Containers/objects DefFn(New), DefFn(Delete), DefFn(Len), // Files DefFn(ReadTextFile), DefFn(WriteTextFile), DefFn(SelectFiles), DefFn(SelectFolder), DefFn(ListFiles), DefFn(DeleteFile), DefFn(CurrentScript), DefFn(PathJoin), DefFn(PathExists), DefFn(PathSep), // Time DefFn(ClockTick), DefFn(Sleep), DefFn(Now), // Images DefFn(CreateSurface), + DefFn(ColourSpaceToString), + DefFn(StringToColourSpace), // UI DefFn(MessageDlg), DefFn(GetInputDlg), DefFn(GetViewById), // System DefFn(Execute), DefFn(System), DefFn(OsName), DefFn(OsVersion), // End of list marker GHostFunc(0, 0, 0), }; GHostFunc *SystemFunctions::GetCommands() { return SystemLibrary; } diff --git a/src/common/Coding/ScriptVM.cpp b/src/common/Coding/ScriptVM.cpp --- a/src/common/Coding/ScriptVM.cpp +++ b/src/common/Coding/ScriptVM.cpp @@ -1,2169 +1,2169 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/TabView.h" #include "lgi/common/TextLog.h" #include "lgi/common/List.h" #include "lgi/common/ToolBar.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Matrix.h" #include "lgi/common/Menu.h" #include "ScriptingPriv.h" #define TIME_INSTRUCTIONS 0 #define POST_EXECUTE_STATE 0 // #define BREAK_POINT 0x0000009F #define ExitScriptExecution c.u8 = e #define SetScriptError c.u8 = e; Status = ScriptError #define CurrentScriptAddress (c.u8 - Base) #define CheckParam(ptr) if (!(ptr)) \ { \ OnException(_FL, CurrentScriptAddress-1, #ptr); \ c.u8 = e; \ Status = ScriptError; \ break; \ } #define AddLocalSize(NewSize) \ size_t LocalsBase = Locals.Length(); \ Locals.SetFixedLength(false); \ /* LgiTrace("%s:%i - Locals %i -> %i\n", _FL, LocalsBase, LocalsBase + NewSize); */ \ Locals.Length(LocalsBase + NewSize); \ Scope[SCOPE_LOCAL] = &Locals[LocalsBase]; \ Locals.SetFixedLength(); #ifdef WIN32 extern "C" uint64 __cdecl CallExtern64(void *FuncAddr, NativeInt *Ret, uint32_t Args, void *Arg); #elif defined(LINUX) #include #endif int LVariantCmp(LVariant *a, LVariant *b, NativeInt Data) { LVariant *Param = (LVariant*) Data; if (!a || !b) return 0; if (a->Type == GV_STRING && b->Type == GV_STRING) { const char *Empty = ""; const char *as = a->Str(); const char *bs = b->Str(); return _stricmp(as?as:Empty, bs?bs:Empty); } else if (a->Type == GV_DOM && b->Type == GV_DOM && Param) { const char *Fld = Param->Str(); int Dir = 1; if (Fld && *Fld == '-') { Fld++; Dir = -1; } LVariant av, bv; if (a->Value.Dom->GetValue(Fld, av) && b->Value.Dom->GetValue(Fld, bv)) { return LVariantCmp(&av, &bv, 0) * Dir; } } else if (a->Type == GV_INT32 && b->Type == GV_INT32) { return a->CastInt32() - b->CastInt32(); } else if (a->Type == GV_DATETIME && b->Type == GV_DATETIME) { return a->Value.Date->Compare(b->Value.Date); } else { LAssert(!"Impl a handler for this type."); } return 0; } inline LVariantType DecidePrecision(LVariantType a, LVariantType b) { if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline LVariantType ComparePrecision(LVariantType a, LVariantType b) { if (a == GV_NULL || b == GV_NULL) return GV_NULL; if (a == GV_DATETIME && b == GV_DATETIME) return GV_DATETIME; if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_STRING || b == GV_STRING) return GV_STRING; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline char *CastString(LVariant *v, LVariant &cache) { if (v->Type == GV_STRING) return v->Str(); cache = *v; return cache.CastString(); } inline int CompareVariants(LVariant *a, LVariant *b) { // Calculates "a - b" switch (ComparePrecision(a->Type, b->Type)) { case GV_DATETIME: return a->Value.Date->Compare(b->Value.Date); break; case GV_DOUBLE: { double d = a->CastDouble() - b->CastDouble(); if (d < -MATRIX_DOUBLE_EPSILON) return -1; return d > MATRIX_DOUBLE_EPSILON; } case GV_STRING: { LVariant as, bs; char *A = CastString(a, as); char *B = CastString(b, bs); if (!A || !B) return -1; else return strcmp(A, B); break; } case GV_INT64: { int64 d = a->CastInt64() - b->CastInt64(); if (d < 0) return -1; return d > 0; } case GV_NULL: { // One or more values is NULL if (a->IsNull() && b->IsNull()) return 0; // The same.. LVariant *Val = a->IsNull() ? b : a; if (Val->IsNull()) { LAssert(0); return 0; } switch (Val->Type) { case GV_INT32: case GV_INT64: return Val->CastInt64() != 0; case GV_STRING: return Val->Str() != NULL; default: return Val->CastVoidPtr() != NULL; } break; } default: return a->CastInt32() - b->CastInt32(); break; } } LExecutionStatus LExternFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { if (!Lib || !Method) return ScriptError; LStream *Log = Ctx ? Ctx->GetLog() : NULL; if (Args.Length() != ArgType.Length()) { if (Log) Log->Print("Error: Extern '%s.%s' expecting %i arguments, not %i given.\n", Lib.Get(), Method.Get(), ArgType.Length(), Args.Length()); return ScriptError; } LArray Val; LArray Mem; bool UnsupportedArg = false; Val.Length(Args.Length() << 1); LPointer Ptr; Ptr.ni = &Val[0]; for (unsigned i=0; !UnsupportedArg && iCastVoidPtr(); *Ptr.vp++ = cp; } else { char *s = NewStr(v->CastString()); if (!s) { UnsupportedArg = true; break; } Mem.Add(s); *Ptr.vp++ = s; } break; } case GV_VOID_PTR: { *Ptr.vp++ = v->CastVoidPtr(); break; } default: { UnsupportedArg = true; break; } } } else { // Plain type switch (t.Base) { case GV_INT32: { #if defined(_WIN64) *Ptr.s64++ = v->CastInt32(); #else *Ptr.s32++ = v->CastInt32(); #endif break; } case GV_INT64: { *Ptr.s64++ = v->CastInt64(); break; } default: { UnsupportedArg = true; break; } } } } LLibrary Library(Lib); if (!Library.IsLoaded()) { if (Log) Log->Print("Error: Extern library '%s' missing.\n", Lib.Get()); return ScriptError; } void *c = Library.GetAddress(Method); if (!c) { if (Log) Log->Print("Error: Extern library '%s' has no method '%s'.\n", Lib.Get(), Method.Get()); return ScriptError; } #if defined(_MSC_VER) || (defined(MAC) && defined(LGI_32BIT) && !LGI_COCOA) ssize_t a = Ptr.ni - &Val[0]; #endif NativeInt r = 0; #if defined(_MSC_VER) #if defined(_WIN64) // 64bit... boooo no inline asm! void *b = &Val[0]; r = CallExtern64(c, &r, (uint32_t)a, b); #else // 32bit... yay inline asm! void *b = Ptr.ni - 1; _asm { mov ecx, a mov ebx, b } label1: _asm { push [ebx] sub ebx, 4 loop label1 mov ebx, c call ebx mov r, eax } #endif #elif defined(MAC) #if LGI_COCOA #warning FIXME #elif LGI_32BIT // 32bit only void *b = Ptr.ni - 1; asm ( "movl %2, %%ecx;" "movl %3, %%ebx;" "label1:" "pushl (%%ebx);" "subl %%ebx, 4;" "loop label1;" "call *%1;" :"=a"(r) /* output */ :"r"(c), "r"(a), "r"(b) /* input */ :/*"%eax",*/ "%ecx", "%ebx" /* clobbered register */ ); #endif #else // Not implemented, gcc??? LAssert(0); #endif *Args.GetReturn() = (int) r; for (unsigned i=0; iType == GV_BINARY && v->Value.Binary.Data != NULL && t.Base == GV_STRING) { // Cast the type back to t.Base char *p = (char*)v->Value.Binary.Data; v->Type = t.Base; v->Value.String = p; } } } Mem.DeleteArrays(); return ScriptSuccess; } struct CodeBlock { unsigned SrcLine; LArray AsmAddr; unsigned ViewLine; LAutoString Source; int SrcLines; LAutoString Asm; int AsmLines; }; class LVirtualMachinePriv : public LRefCount { LVariant ArrayTemp; char *CastArrayIndex(LVariant *Idx) { if (Idx == NULL || Idx->Type == GV_NULL) return NULL; if (Idx->Type == GV_STRING) return Idx->Str(); ArrayTemp = *Idx; return ArrayTemp.CastString(); } public: struct StackFrame { uint32_t CurrentFrameSize; ssize_t PrevFrameStart; size_t ReturnIp; - GVarRef ReturnValue; + LVarRef ReturnValue; }; enum RunType { RunContinue, RunStepInstruction, RunStepLine, RunStepOut }; LStream *Log; LCompiledCode *Code; LExecutionStatus Status; GPtr c; LVariant Reg[MAX_REGISTER]; LArray Locals; LVariant *Scope[SCOPE_MAX]; LArray Frames; RunType StepType; LVmDebuggerCallback *DbgCallback; bool DebuggerEnabled; LVmDebugger *Debugger; LVirtualMachine *Vm; LScriptArguments *ArgsOutput; bool BreakCpp; LArray BreakPts; LString TempPath; LVirtualMachinePriv(LVirtualMachine *vm, LVmDebuggerCallback *Callback) { Vm = vm; DebuggerEnabled = true; BreakCpp = false; ArgsOutput = NULL; Log = NULL; Code = NULL; Debugger = NULL; DbgCallback = Callback; ZeroObj(Scope); } ~LVirtualMachinePriv() { } void DumpVariant(LStream *Log, LVariant &v) { if (!Log) return; switch (v.Type) { case GV_INT32: Log->Print("(int) %i", v.Value.Int); break; case GV_INT64: Log->Print("(int64) %I64i", v.Value.Int64); break; case GV_STRING: { char *nl = strchr(v.Value.String, '\n'); if (nl) Log->Print("(string) '%.*s...' (%i bytes)", nl - v.Value.String, v.Value.String, strlen(v.Value.String)); else Log->Print("(string) '%s'", v.Value.String); break; } case GV_DOUBLE: Log->Print("(double) %g", v.Value.Dbl); break; case GV_BOOL: Log->Print("(bool) %s", v.Value.Bool ? "true" : "false"); break; case GV_DOM: Log->Print("(LDom*) %p", v.Value.Dom); break; case GV_HASHTABLE: { Log->Print("(GHashTable*) %p {", v.Value.Hash); int n = 0; // const char *k; // for (LVariant *p = v.Value.Hash->First(&k); p; p = v.Value.Hash->Next(&k), n++) for (auto it : *v.Value.Hash) { Log->Print("%s\"%s\"=", n?",":"", it.key); DumpVariant(Log, *it.value); } Log->Print("}"); break; } case GV_LIST: { Log->Print("(LList*) %p {", v.Value.Lst); int n=0; for (auto i: *v.Value.Lst) { Log->Print("%s%i=", n?",":"", n); DumpVariant(Log, *i); n++; } Log->Print("}"); break; } case GV_NULL: { Log->Print("null"); break; } case GV_BINARY: { Log->Print("(Binary[%i])", v.Value.Binary.Length); if (v.Value.Binary.Data) { int i; for (i=0; i<16 && i < v.Value.Binary.Length; i++) Log->Print(" %.2x", ((uint8_t*)v.Value.Binary.Data)[i]); if (i < v.Value.Binary.Length) Log->Print("..."); } break; } default: Log->Print("(Type-%i) ????", v.Type); break; } } void DumpVariables(LVariant *v, int len) { if (!Log) return; for (int i=0; iPrint("[%i] = ", i); DumpVariant(Log, v[i]); Log->Print("\n"); } } } void OnException(const char *File, int Line, ssize_t Address, const char *Msg) { if (Address < 0) { uint8_t *Base = &Code->ByteCode[0]; Address = c.u8 - Base; } if (!File || Line < 0) { // Extract the file / line from the current script location File = Code->GetFileName(); Line = Code->ObjectToSourceAddress(Address); } if (Log) { char *Last = strrchr((char*)File, DIR_CHAR); Log->Print("%s Exception: %s (%s:%i)\n", Code->AddrToSourceRef(Address), Msg, Last?Last+1:File, Line); } else { LgiTrace("%s:%i - Exception @ %i: %s\n", File, Line, Address, Msg); } if (Vm && Vm->OpenDebugger(Code)) { if (!Debugger->GetCode()) { LAutoPtr Cp(new LCompiledCode(*Code)); Debugger->OwnCompiledCode(Cp); LStringPipe AsmBuf; Decompile(Code->UserContext, Code, &AsmBuf); LAutoString Asm(AsmBuf.NewStr()); Debugger->SetSource(Asm); } Debugger->OnAddress(Address); LString m; m.Printf("%s (%s:%i)", Msg, LGetLeaf(File), Line); Debugger->OnError(m); Debugger->Run(); } else { // Set the script return value to FALSE if (Frames.Length()) { StackFrame Sf = Frames[0]; - GVarRef &Ret = Sf.ReturnValue; + LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; *RetVar = false; } // Exit the script c.u8 = Code->ByteCode.AddressOf() + Code->ByteCode.Length(); // Set the script status... Status = ScriptError; } } LExecutionStatus Decompile(LScriptContext *Context, LCompiledCode *Code, LStream *log) { LExecutionStatus Status = ScriptSuccess; - LAssert(sizeof(GVarRef) == 4); + LAssert(sizeof(LVarRef) == 4); GPtr c; uint8_t *Base = &Code->ByteCode[0]; c.u8 = Base; uint8_t *e = c.u8 + Code->ByteCode.Length(); LStream *OldLog = Log; if (log) Log = log; for (unsigned k=0; kGlobals.Length(); k++) { Log->Print("G%i = ", k); DumpVariant(Log, Code->Globals[k]); Log->Print("\n"); } Log->Print("\n"); LHashTbl, char*> Fn; for (unsigned m=0; mMethods.Length(); m++) { LFunctionInfo *Info = Code->Methods[m]; if (Info->ValidStartAddr()) Fn.Add(Info->GetStartAddr(), Info->GetName()); else LAssert(!"Method not defined."); } int OldLineNum = 0; while (c.u8 < e) { char *Meth = Fn.Find(CurrentScriptAddress); if (Meth) { Log->Print("%s:\n", Meth); } int LineNum = Code->ObjectToSourceAddress(CurrentScriptAddress); if (LineNum >= 0 && LineNum != OldLineNum) { Log->Print(" %i:\n", OldLineNum = LineNum); } switch (*c.u8++) { #define VM_DECOMP 1 #include "Instructions.h" #undef VM_DECOMP } } if (log) Log = OldLog; return Status; } LExecutionStatus Setup(LCompiledCode *code, uint32_t StartOffset, LStream *log, LFunctionInfo *Func, LScriptArguments *Args) { Status = ScriptSuccess; Code = code; if (!Code) return ScriptError; if (log) Log = log; else if (Code->SysContext && Code->SysContext->GetLog()) Log = Code->SysContext->GetLog(); else if (Code->UserContext && Code->UserContext->GetLog()) Log = Code->UserContext->GetLog(); // else LgiTrace("%s:%i - Execution without a log?\n", _FL); - LAssert(sizeof(GVarRef) == 4); + LAssert(sizeof(LVarRef) == 4); uint8_t *Base = c.u8 = &Code->ByteCode[0]; uint8_t *e = c.u8 + Code->ByteCode.Length(); Scope[SCOPE_REGISTER] = Reg; Scope[SCOPE_LOCAL] = NULL; Scope[SCOPE_GLOBAL] = &Code->Globals[0]; Scope[SCOPE_OBJECT] = NULL; Scope[SCOPE_RETURN] = Args->GetReturn(); #ifdef _DEBUG const char *SourceFileName = Code->GetFileName(); char Obj[MAX_PATH_LEN]; if (SourceFileName) { if (strchr(SourceFileName, DIR_CHAR)) strcpy_s(Obj, sizeof(Obj), SourceFileName); else if (TempPath != NULL) LMakePath(Obj, sizeof(Obj), TempPath, SourceFileName); else { LGetSystemPath(LSP_TEMP, Obj, sizeof(Obj)); LMakePath(Obj, sizeof(Obj), Obj, SourceFileName); } char *Ext = LGetExtension(Obj); if (Ext) strcpy_s(Ext, sizeof(Obj)-(Ext-Obj), "asm"); else strcat_s(Obj, sizeof(Obj), ".asm"); } else { LAutoString DataPath; if (Code->UserContext) DataPath = Code->UserContext->GetDataFolder(); if (!DataPath) { char p[256]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) DataPath.Reset(NewStr(p)); } LMakePath(Obj, sizeof(Obj), DataPath, "Script.asm"); } { LDirectory SrcD, ObjD; bool OutOfDate = true; if (LFileExists(SourceFileName) && SrcD.First(SourceFileName, NULL) != 0 && ObjD.First(Obj, NULL) != 0) { OutOfDate = ObjD.GetLastWriteTime() < SrcD.GetLastWriteTime(); } if (OutOfDate || Debugger) { LFile f; LStringPipe p; LStream *Out = NULL; if (Debugger) { Out = &p; } else if (f.Open(Obj, O_WRITE)) { f.SetSize(0); Out = &f; } if (Out) { LExecutionStatus Decomp = Decompile(Code->UserContext, Code, Out); f.Close(); if (Decomp != ScriptSuccess) { LAssert(!"Decompilation failed."); return ScriptError; } if (Debugger) { LAutoString a(p.NewStr()); Debugger->OnAddress(CurrentScriptAddress); Debugger->SetSource(a); } } } } #endif #if TIME_INSTRUCTIONS LARGE_INTEGER freq = {0}, start, end; QueryPerformanceFrequency(&freq); LHashTbl, int64> Timings; LHashTbl, int> TimingFreq; #endif // Calling a function only, not the whole script StackFrame &Sf = Frames.New(); Sf.ReturnIp = e - c.u8; Sf.PrevFrameStart = 0; Sf.ReturnValue.Scope = SCOPE_RETURN; Sf.ReturnValue.Index = 0; // array is only one item long anyway if (Func) { // Set up stack for function call if (!Func->ValidFrameSize()) { Log->Print("%s:%i - Function '%s' has an invalid frame size. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } Sf.CurrentFrameSize = Func->GetFrameSize(); AddLocalSize(Sf.CurrentFrameSize); if (Args) { // Check the local frame size is at least big enough for the args... if (Args->Length() > Sf.CurrentFrameSize) { Log->Print("%s:%i - Arg count mismatch, Supplied: %i, FrameSize: %i (Script: %s).\n", _FL, (int)Args->Length(), (int)Sf.CurrentFrameSize, Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Put the arguments of the function call into the local array for (unsigned i=0; iLength(); i++) { Locals[LocalsBase+i] = *(*Args)[i]; } } if (!Func->ValidStartAddr()) { Log->Print("%s:%i - Function '%s' is not defined. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Set IP to start of function c.u8 = Base + Func->GetStartAddr(); } else { // Executing body of script Sf.CurrentFrameSize = 0; if (StartOffset > 0) c.u8 = Base + StartOffset; } return Status; } int NearestLine(size_t Addr) { int l = Code->Debug.Find(Addr); if (l >= 0) return l; for (int Off = 1; Off < 20; Off++) { int l = Code->Debug.Find(Addr + Off); if (l >= 0) { return l; } l = Code->Debug.Find(Addr - Off); if (l >= 0) { return l; } } return -1; } LExecutionStatus Run(RunType Type) { LAssert(Code != NULL); uint8_t *Base = &Code->ByteCode[0]; uint8_t *e = Base + Code->ByteCode.Length(); if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #endif #ifdef BREAK_POINT if (c.u8 - Base == BREAK_POINT) { int asd=0; } #endif switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } #if TIME_INSTRUCTIONS QueryPerformanceCounter(&end); int Ticks = end.QuadPart - start.QuadPart; int64 i = Timings.Find(TimedOpCode); Timings.Add(TimedOpCode, i + Ticks); i = TimingFreq.Find(TimedOpCode); TimingFreq.Add(TimedOpCode, i + 1); #endif } if (Log) { #if TIME_INSTRUCTIONS Log->Print("\nTimings:\n"); Log->Print("%-20s%-10s%-10s%-10s\n", "Instr", "Total", "Freq", "Ave"); int Op; for (int64 t=Timings.First(&Op); t; t=Timings.Next(&Op)) { int Frq = TimingFreq.Find(Op); int MilliSec = t * 1000000 / freq.QuadPart; Log->Print("%-20s%-10i%-10i%-10i\n", InstToString((GInstruction)Op), MilliSec, Frq, MilliSec / Frq); } Log->Print("\n"); #endif #if POST_EXECUTE_STATE Log->Print("Stack:\n"); char *v; for (void *i=Code->Globals.Lut.First(&v); i; i=Code->Globals.Lut.Next(&v)) { int Idx = (int)i - 1; if (Idx >= 0 && Idx < Code->Globals.Length()) { Log->Print("%s = ", v); DumpVariant(Log, Code->Globals[Idx]); Log->Print("\n"); } } Log->Print("\nRegisters:\n"); DumpVariables(Reg, MAX_REGISTER); #endif } } else { // Stepping through code // LHashTbl, int> &Debug = Code->Debug; int Param = 0; switch (Type) { case RunStepLine: Param = NearestLine(CurrentScriptAddress); break; case RunStepOut: Param = (int)Frames.Length(); break; default: break; } if (BreakCpp) #if defined(WIN32) && !defined(_WIN64) _asm int 3 #else assert(!"BreakPoint"); #endif while (c.u8 < e) { if (Type == RunContinue && BreakPts.HasItem(c.u8 - Base)) break; switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } if (Type == RunStepLine) { int CurLine = NearestLine(CurrentScriptAddress); if (CurLine && CurLine != Param) break; } else if (Type == RunStepOut) { if ((int)Frames.Length() < Param) break; } else if (Type == RunStepInstruction) break; } } if (Debugger && Status != ScriptError) Debugger->OnAddress(CurrentScriptAddress); return Status; } }; bool LVirtualMachine::BreakOnWarning = false; LVirtualMachine::LVirtualMachine(LVmDebuggerCallback *callback) { d = new LVirtualMachinePriv(this, callback); d->AddRef(); } LVirtualMachine::LVirtualMachine(LVirtualMachine *vm) { d = vm->d; d->AddRef(); } LVirtualMachine::~LVirtualMachine() { if (d->Vm == this) d->Vm = NULL; d->DecRef(); } LExecutionStatus LVirtualMachine::Execute(LCompiledCode *Code, uint32_t StartOffset, LStream *Log, bool StartImmediately, LVariant *Return) { if (!Code) return ScriptError; LScriptArguments Args(this, Return); LExecutionStatus s = d->Setup(Code, StartOffset, Log, NULL, &Args); if (s != ScriptSuccess || !StartImmediately) return s; return d->Run(LVirtualMachinePriv::RunContinue); } LExecutionStatus LVirtualMachine::ExecuteFunction(LCompiledCode *Code, LFunctionInfo *Func, LScriptArguments &Args, LStream *Log, LScriptArguments *ArgsOut) { LCompiledCode *Cc = dynamic_cast(Code); if (!Cc || !Func) return ScriptError; LExecutionStatus s = d->Setup(Cc, 0, Log, Func, &Args); if (s != ScriptSuccess) return s; d->ArgsOutput = ArgsOut; Args.Vm = this; LExecutionStatus r = d->Run(LVirtualMachinePriv::RunContinue); Args.Vm = NULL; return r; } void LVirtualMachine::SetDebuggerEnabled(bool b) { d->DebuggerEnabled = b; } LVmDebugger *LVirtualMachine::OpenDebugger(LCompiledCode *Code, const char *Assembly) { if (d->DebuggerEnabled && !d->Debugger) { if (!d->DbgCallback) return NULL; d->Debugger = d->DbgCallback->AttachVm(this, Code, Assembly); } return d->Debugger; } bool LVirtualMachine::StepInstruction() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepInstruction); return s != ScriptError; } bool LVirtualMachine::StepLine() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepLine); return s != ScriptError; } bool LVirtualMachine::StepOut() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepOut); return s != ScriptError; } bool LVirtualMachine::BreakExecution() { return false; } bool LVirtualMachine::Continue() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunContinue); return s != ScriptError; } bool LVirtualMachine::BreakPoint(const char *File, int Line, bool Add) { return false; } bool LVirtualMachine::BreakPoint(int Addr, bool Add) { if (Add) d->BreakPts.Add(Addr); else d->BreakPts.Delete(Addr); return true; } void LVirtualMachine::SetBreakCpp(bool Brk) { d->BreakCpp = Brk; } void LVirtualMachine::SetTempPath(const char *Path) { d->TempPath = Path; } //////////////////////////////////////////////////////////////////// /* bool GTypeDef::GetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { Value = *p.i32; break; } case GV_DOUBLE: { Value = *p.dbl; break; } case GV_STRING: { Value = p.i8; break; } case GV_CUSTOM: { Value.Empty(); Value.Type = GV_CUSTOM; Value.Value.Custom.Dom = m->Nest; Value.Value.Custom.Data = p.i8; break; } default: { return false; } } return true; } bool GTypeDef::SetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { *p.i32 = Value.CastInt32(); break; } case GV_DOUBLE: { *p.dbl = Value.CastDouble(); break; } case GV_STRING: { char *s = Value.CastString(); if (!s) return false; int i; for (i = 0; *s && i < m->Size - 1; i++) { *p.i8++ = *s++; } if (i < m->Size - 1) *p.i8 = 0; break; } case GV_CUSTOM: { GTypeDef *t = dynamic_cast(Value.Value.Custom.Dom); if (m->Nest == t) { memcpy(p.i8, Value.Value.Custom.Data, t->Sizeof()); } break; } default: { return false; } } return true; } */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////// uint32_t IconsData[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D9CCEBE, 0x3B166419, 0x74594357, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x543CF81F, 0xCEDE647C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7C998D1B, 0xF81FB61C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBFF81F, 0x43DB4C1C, 0xDF1E955B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8C0CF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D5CF81F, 0x43595C1A, 0x3AF74338, 0x8CFA4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69D6C39, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x647BADFD, 0x543C53FB, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F64CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0xF81F83AB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CBB8D5C, 0xF81FB63D, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5BD8, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x959D647C, 0xCEBDF81F, 0x32913AD3, 0xB61B5353, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3BA564CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x74DC8D9F, 0x8D9FF81F, 0x74DC8D9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0xAE1EB65E, 0xD71FA5FE, 0x853D8D7D, 0x6CBD7CFD, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0x7329FF98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE9E5C1A, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F6C7C, 0x5BD6F81F, 0xD6DD5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB6D45CAA, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x2AB5D71F, 0x8D9FF81F, 0x2AB5D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F8D9F, 0xC6DFD71F, 0xB65FBE7F, 0x9DDEAE3F, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x9DDEAE1E, 0xFFFFDF3F, 0x74FD853D, 0x647C6CBC, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x9C6CF81F, 0x944D944C, 0x944D8C0C, 0xFF54FF76, 0xF81F62A8, 0xF81FF81F, 0xF81FF81F, 0xBE5D4B99, 0x543CF81F, 0xC6BE5C7C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F53DB, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED5489, 0x3BA5B6D4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x2274DF3F, 0x857EF81F, 0x2274DF3F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xDF3F857E, 0xB65FCEDF, 0x9DBEAE1F, 0x851B959E, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0xE75F9DDE, 0xFFFFFFFF, 0x6CBC74DD, 0x543C647C, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x944BF81F, 0xFFD9F756, 0xFF53FF97, 0xFEEEFF31, 0x5226F66A, 0xF81FF81F, 0xF81FF81F, 0x84DA6C3A, 0xBE7EADDC, 0x43DB4C1C, 0xF81F953B, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4B77, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0x9D7C5C3B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED4C48, 0x5D0A75ED, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0x1A33D71F, 0x855EF81F, 0x1A33D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F855E, 0xA5FFBE7F, 0x855E959E, 0x6C7A7CFD, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0xFFFFDF1E, 0xFFFFFFFF, 0xFFFFFFFF, 0x4BFCC69D, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x940AF81F, 0xFF75FF97, 0xFEEEFF31, 0xF6ABFECD, 0xBCA7E5E8, 0xF81F49C5, 0xF81FF81F, 0x7CBAB61C, 0x541C53FA, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C9CB63D, 0x43584BBA, 0x3AD53B16, 0x743742F4, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6D8B4407, 0x3C055D0A, 0x1B212B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0x11F2CEBF, 0x7D1DF81F, 0x11F2CEBF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBF7D1D, 0x9DBEAE3F, 0x7CFD8D5E, 0x53B76CBC, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0xCEBD853D, 0xFFFFFFFF, 0x541C5C5C, 0x43BBFFFF, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x83A9F81F, 0xFF31FF74, 0xF6ACF6CF, 0xDDEAF68B, 0x41A4B467, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69DF81F, 0x32913AD3, 0xB5FB4B53, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5D0A33E5, 0x2B843C05, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0x09B1BE9F, 0x7CFDF81F, 0x09B1BE9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xBE9F7CFD, 0x959EA5FF, 0x6C9C7CFD, 0x4B565C3B, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x6CBC74FD, 0xFFFFBE3B, 0x43DC541C, 0x337BFFFF, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x8389F81F, 0x6AA472E7, 0x72C56264, 0xB487DDA9, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5BD6F81F, 0xF81F5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3C052BA4, 0x0B002B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x0990AE5F, 0x74DCF81F, 0x0990AE5F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xAE5F74DC, 0x7D1D95BE, 0x5C3B6CBC, 0x43354BD9, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x647C6CBC, 0x9D7A543C, 0x3B9B43DB, 0x2B5BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5A45F81F, 0x41A4AC26, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5377, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x2B842363, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x0170A5FE, 0x74BCF81F, 0x0170A5FE, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xA5FE74BC, 0x6C79851A, 0x4B555BF8, 0x32D34314, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x543C5C7C, 0x43BB4BFC, 0x335B3B9B, 0x231BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x49E4F81F, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D7A53B7, 0x4BBAF81F, 0xB61C6459, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0B001B42, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x01700170, 0x74DCF81F, 0x01700170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3B1674DC, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x3B163B16, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x41A4F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x747863F7, 0x953BB61C, 0x4BB843B9, 0xB61B7C98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F1301, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C18ADBA, 0x53D953B7, 0x3B144B98, 0x32503291, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB61BF81F, 0x32503291, 0xA5794B12, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5B95F81F, 0xB61A5373, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, }; LInlineBmp DbgIcons = {128, 16, 16, IconsData}; enum DbgCtrls { IDC_STATIC = -1, IDC_TABS = 300, IDC_BOX, IDC_BOX2, IDC_TEXT, IDC_LOCALS, IDC_GLOBALS, IDC_REGISTERS, IDC_STACK, IDC_LOG, IDC_RUN, IDC_PAUSE, IDC_STOP, IDC_RESTART, IDC_GOTO, IDC_STEP_INSTR, IDC_STEP_LINE, IDC_STEP_OUT, IDC_SOURCE_LST, IDC_BREAK_POINT, IDC_BREAK_CPP, IDC_VARS_TBL }; struct LScriptVmDebuggerPriv; class LDebugView : public LTextView3 { LScriptVmDebuggerPriv *d; int CurLine; int ErrorLine; LString Error; LArray BreakPts; public: LDebugView(LScriptVmDebuggerPriv *priv); ~LDebugView(); void SetError(const char *Err); int GetCurLine() { return CurLine; } int GetAddr(); void ScrollToCurLine(); void PourText(size_t Start, ssize_t Length) override; void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) override; void OnPaint(LSurface *pDC) override; bool Breakpoint(int Addr); }; struct LScriptVmDebuggerPriv { // Current script bool OwnVm; LAutoPtr Vm; LVmDebuggerCallback *Callback; LString Script, Assembly; LArray Blocks; size_t CurrentAddr; LArray LineIsAsm; LAutoPtr Obj; LVariant Return; bool AcceptNotify; // Ui bool RunLoop; LView *Parent; LBox *Main; LBox *Sub; LList *SourceLst; LTabView *Tabs; LDebugView *Text; LList *Locals, *Globals, *Registers, *Stack; LTextLog *Log; LToolBar *Tools; LTableLayout *VarsTbl; LScriptVmDebuggerPriv() { RunLoop = false; OwnVm = false; CurrentAddr = -1; Main = NULL; Tabs = NULL; Log = NULL; Text = NULL; Locals = NULL; Globals = NULL; Registers = NULL; Stack = NULL; Tools = NULL; SourceLst = NULL; Callback = NULL; VarsTbl = NULL; } }; LDebugView::LDebugView(LScriptVmDebuggerPriv *priv) : LTextView3(IDC_TEXT, 0, 0, 100, 100) { d = priv; ErrorLine = -1; SetWrapType(TEXTED_WRAP_NONE); GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, 18)); } LDebugView::~LDebugView() { } void LDebugView::SetError(const char *Err) { ErrorLine = CurLine; Error = Err; } #define IsHexChar(c) \ ( \ IsDigit(c) \ || \ ((c) >= 'a' && (c) <= 'f') \ || \ ((c) >= 'A' && (c) <= 'F') \ ) int IsAddr(char16 *Ln) { int Addr = 0; for (char16 *s = Ln; *s && *s != '\n' && s < Ln + 8; s++) { Addr += IsHexChar(*s); } if (Addr != 8) return -1; return HtoiW(Ln); } int LDebugView::GetAddr() { ssize_t Index; LTextLine *t = GetTextLine(Cursor, &Index); if (!t) return -1; int Addr = IsAddr(Text + t->Start); return Addr; } void LDebugView::ScrollToCurLine() { SetLine(CurLine); } bool LDebugView::Breakpoint(int Addr) { if (BreakPts.HasItem(Addr)) { BreakPts.Delete(Addr); Invalidate(); return false; } else { BreakPts.Add(Addr); Invalidate(); return true; } } void LDebugView::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LTextView3::OnPaintLeftMargin(pDC, r, colour); pDC->Colour(LColour(192, 0, 0)); LFont *f = GetFont(); f->Colour(L_LOW, L_WORKSPACE); f->Transparent(true); int Fy = f->GetHeight(); int Start = VScroll ? (int)VScroll->Value() : 0; int Page = (r.Y() + Fy - 1) / Fy; int Ln = Start; int Rad = (Fy >> 1) - 1; int PadY = GetCss(true)->PaddingTop().ToPx(Y(), f) + ((Fy - Rad) >> 1); auto It = Line.begin(Start); for (auto i = *It; i && Ln <= Start + Page; i = *(++It), Ln++) { int OffY = (Ln - Start) * f->GetHeight(); /* LString Num; Num.Printf("%i", Ln); LDisplayString Ds(f, Num); Ds.Draw(pDC, 0, r.y1+OffY); */ char16 *s = Text + i->Start; int Addr = IsAddr(s); if (BreakPts.HasItem(Addr)) { pDC->FilledCircle(r.x1 + Rad + 2, OffY + PadY + Rad, Rad); } } f->Transparent(false); f->Colour(L_TEXT, L_WORKSPACE); } void LDebugView::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); if (Error) { LTextLine *Ln = Line[ErrorLine]; LFont *f = GetFont(); LRect c = GetClient(); int Pad = 3; LDisplayString Ds(f, Error); LRect r(0, 0, Ds.X()-1, Ds.Y()-1); r.Inset(-Pad, -Pad); r.Offset(c.X()-r.X(), Ln ? Ln->r.y1 - ScrollYPixel(): 0); f->Transparent(false); f->Colour(LColour::White, LColour::Red); Ds.Draw(pDC, r.x1 + Pad, r.y1 + Pad, &r); } } void LDebugView::PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); CurLine = -1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; for (unsigned n=0; nCurrentAddr >= b.AsmAddr[n]) { CurLine = b.ViewLine + b.SrcLines + n - 1; } } } unsigned Idx = 0; for (auto l: Line) { // char16 *t = Text + l->Start; // char16 *e = t + l->Len; if (CurLine == Idx) { l->c.Rgb(0, 0, 0); l->Back = LColour(L_DEBUG_CURRENT_LINE); } else { bool IsAsm = Idx < d->LineIsAsm.Length() ? d->LineIsAsm[Idx] : false; if (IsAsm) { l->c.Rgb(0, 0, 255); l->Back.Rgb(0xf0, 0xf0, 0xf0); } } Idx++; } } LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmDebuggerCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; d->AcceptNotify = false; if (Vm) d->Vm.Reset(new LVirtualMachine(Vm)); d->Callback = Callback; if (Code) d->Script = Code->GetSource(); d->Assembly = Assembly; LRect r(0, 0, 1000, 900); SetPos(r); if (Parent) MoveSameScreen(Parent); else MoveToCenter(); Name("Script Debugger"); if (Attach(NULL)) { if ((Menu = new LMenu)) { Menu->Attach(this); LSubMenu *s = Menu->AppendSub("Debug"); s->AppendItem("Run", IDC_RUN, true, -1, "F5"); s->AppendItem("Pause", IDC_PAUSE, true, -1, NULL); s->AppendItem("Stop", IDC_STOP, true, -1, "Ctrl+Break"); s->AppendItem("Restart", IDC_RESTART, true, -1, NULL); s->AppendItem("Goto", IDC_GOTO, true, -1, NULL); s->AppendSeparator(); s->AppendItem("Step Instruction", IDC_STEP_INSTR, true, -1, "F11"); s->AppendItem("Step Line", IDC_STEP_LINE, true, -1, "F10"); s->AppendItem("Step Out", IDC_STEP_OUT, true, -1, "Shift+F11"); s->AppendSeparator(); s->AppendItem("Breakpoint", IDC_BREAK_POINT, true, -1, "F9"); s->AppendItem("Break Into C++", IDC_BREAK_CPP, true, -1, "Ctrl+F9"); } AddView(d->Tools = new LToolBar); uint16 *Px = (uint16*) DbgIcons.Data; LImageList *il = new LImageList(16, 16, DbgIcons.Create(*Px)); if (il) d->Tools->SetImageList(il, 16, 16, true); d->Tools->AppendButton("Run", IDC_RUN); d->Tools->AppendButton("Pause", IDC_PAUSE); d->Tools->AppendButton("Stop", IDC_STOP); d->Tools->AppendButton("Restart", IDC_RESTART); d->Tools->AppendButton("Goto", IDC_GOTO); d->Tools->AppendSeparator(); d->Tools->AppendButton("Step Instruction", IDC_STEP_INSTR); d->Tools->AppendButton("Step Line", IDC_STEP_LINE); d->Tools->AppendButton("Step Out", IDC_STEP_OUT); AddView(d->Main = new LBox(IDC_BOX)); d->Main->SetVertical(true); d->Main->AddView(d->Sub = new LBox(IDC_BOX2)); d->Sub->SetVertical(false); d->Sub->AddView(d->SourceLst = new LList(IDC_SOURCE_LST, 0, 0, 100, 100)); d->SourceLst->GetCss(true)->Width(LCss::Len("200px")); d->SourceLst->AddColumn("Source", 200); d->Sub->AddView(d->Text = new LDebugView(d)); d->Main->AddView(d->Tabs = new LTabView(IDC_TABS)); d->Tabs->GetCss(true)->Height(LCss::Len("250px")); LTabPage *p = d->Tabs->Append("Variables"); p->Append(d->VarsTbl = new LTableLayout(IDC_VARS_TBL)); int x = 0, y = 0; auto *c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Globals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Locals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Registers:")); x = 0; y++; c = d->VarsTbl->GetCell(x++, y); c->Add(d->Globals = new LList(IDC_GLOBALS, 0, 0, 100, 100)); d->Globals->AddColumn("Name",100); d->Globals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Locals = new LList(IDC_LOCALS, 0, 0, 100, 100)); d->Locals->AddColumn("Name",100); d->Locals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Registers = new LList(IDC_REGISTERS, 0, 0, 100, 100)); d->Registers->AddColumn("Name",100); d->Registers->AddColumn("Value",400); p = d->Tabs->Append("Stack"); p->Append(d->Stack = new LList(IDC_STACK, 0, 0, 100, 100)); d->Stack->SetPourLargest(true); d->Stack->AddColumn("Address", 100); d->Stack->AddColumn("Function", 300); p = d->Tabs->Append("Log"); p->Append(d->Log = new LTextLog(IDC_LOG)); AttachChildren(); Visible(true); { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExePath(), "../Scripts"); LDirectory dir; LListItem *Match = NULL; d->SourceLst->MultiSelect(false); for (int b = dir.First(p); b; b = dir.Next()) { if (!dir.IsDir()) { char *n = dir.GetName(); if (stristr(n, ".script") && dir.Path(p, sizeof(p))) { LListItem *it = new LListItem; it->SetText(dir.GetName(), 0); it->SetText(p, 1); if (Code && Code->GetFileName()) { if (_stricmp(p, Code->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } if (!Match && Code) { LListItem *it = new LListItem; if (it) { it->SetText(LGetLeaf(Code->GetFileName()), 0); it->SetText(Code->GetFileName(), 1); d->SourceLst->Insert(it); it->Select(true); } } } } d->AcceptNotify = true; } LVmDebuggerWnd::~LVmDebuggerWnd() { LAssert(d->RunLoop == false); } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { if (!d->RunLoop) return LWindow::OnRequestClose(OsShuttingDown); d->RunLoop = false; return false; // Wait for Run() to exit in it's own time. } void LVmDebuggerWnd::Run() { // This is to allow objects on the application's stack to // still be valid while the debugger UI is shown. d->RunLoop = true; while (d->RunLoop && Visible()) { LYield(); LSleep(20); } Quit(); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } void LVmDebuggerWnd::OwnVm(bool Own) { d->OwnVm = Own; } void LVmDebuggerWnd::OwnCompiledCode(LAutoPtr Cc) { d->Obj = Cc; } LCompiledCode *LVmDebuggerWnd::GetCode() { return d->Obj; } void LVmDebuggerWnd::SetSource(const char *Mixed) { #if 1 LStringPipe Glob(256); LStringPipe Tmp(256); d->Blocks.Length(0); CodeBlock *Cur = &d->Blocks.New(); // Parse the mixed source auto t = LString(Mixed).SplitDelimit("\n", -1, false); bool InGlobals = true; int InAsm = -1; for (unsigned i=0; i Code Cur->Asm.Reset(Tmp.NewStr()); Cur = &d->Blocks.New(); } else { // Code -> Asm Tmp.Empty(); } InAsm = IsAsm; } Tmp.Print("%s\n", l); if (InAsm) { Cur->AsmLines++; Cur->AsmAddr.Add(htoi(l)); } else if (!Cur->SrcLine) { while (*l == ' ') l++; if (IsDigit(*l)) Cur->SrcLine = atoi(l); } } if (InAsm) Cur->Asm.Reset(Tmp.NewStr()); Tmp.Empty(); LStringPipe Txt; auto Src = d->Script.SplitDelimit("\n", -1, false); unsigned SrcLine = 1; unsigned ViewLine = 1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; if (b.SrcLine > 0) { while (SrcLine <= b.SrcLine) { char *s = Src[SrcLine-1]; Tmp.Print("%i: %s\n", SrcLine, s ? s : ""); b.SrcLines++; SrcLine++; } b.Source.Reset(Tmp.NewStr()); } if (b.Source && b.Asm) { b.ViewLine = ViewLine; ViewLine += b.SrcLines + b.AsmLines; Txt.Print("%s%s", b.Source.Get(), b.Asm.Get()); } else if (b.Source) { b.ViewLine = ViewLine; ViewLine += b.SrcLines; Txt.Print("%s", b.Source.Get()); } else if (b.Asm) { b.ViewLine = ViewLine; ViewLine += b.AsmLines; Txt.Print("%s", b.Asm.Get()); } } while (SrcLine <= Src.Length()) { Txt.Print("%i: %s\n", SrcLine, Src[SrcLine-1].Get()); SrcLine++; } for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; int Base = b.ViewLine + b.SrcLines; for (int n = Base; nLineIsAsm[n-1] = true; } LAutoString a(Txt.NewStr()); d->Text->Name(a); #else d->Text->Name(Mixed); #endif } void LVmDebuggerWnd::UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix) { if (!d->Vm || !Lst || !Arr) return; List all; Lst->GetAll(all); LListItem *it; for (ssize_t i=0; iVm->d->DumpVariant(&p, *v); LAutoString a(p.NewStr()); char nm[32]; sprintf_s(nm, sizeof(nm), "%c" LPrintfSSizeT, Prefix, i); if (i >= (ssize_t)all.Length()) { it = new LListItem; all.Insert(it); Lst->Insert(it); } it = i < (ssize_t)all.Length() ? all[i] : NULL; if (it) { it->SetText(nm, 0); it->SetText(a, 1); } } Lst->ResizeColumnsToContent(); } void LVmDebuggerWnd::OnAddress(size_t Addr) { d->CurrentAddr = Addr; if (d->Text) { ssize_t Sz = d->Text->Length(); d->Text->PourText(0, Sz); d->Text->ScrollToCurLine(); d->Text->Invalidate(); } OnNotify(d->Tabs, LNotifyValueChanged); } void LVmDebuggerWnd::OnError(const char *Msg) { if (Msg) d->Text->SetError(Msg); } void LVmDebuggerWnd::OnRun(bool Running) { } void LVmDebuggerWnd::LoadFile(const char *File) { if (!d->Vm || !d->Callback) { LAssert(0); return; } LFile f; if (f.Open(File, O_READ)) d->Script = f.Read(); else d->Script.Empty(); d->Obj.Reset(); if (d->Callback->CompileScript(d->Obj, File, d->Script)) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) { d->Return.Empty(); d->Vm->d->Frames.Length(0); LScriptArguments Args(d->Vm, &d->Return); d->Vm->d->Setup(Code, 0, d->Log, NULL, &Args); } } } int LVmDebuggerWnd::OnCommand(int Cmd, int Event, OsView Wnd) { if (d->Vm && d->Vm->d->Vm == NULL) { // This happens when the original VM decides to go away and leave // our copy of the VM as the only one left. This means we have to // update the pointer in the VM's private data to point to us. d->Vm->d->Vm = d->Vm; } switch (Cmd) { case IDC_RUN: { if (d->Vm) d->Vm->Continue(); break; } case IDC_PAUSE: { if (d->Vm) d->Vm->BreakExecution(); break; } case IDC_STOP: { d->Vm.Reset(); if (d->RunLoop) d->RunLoop = false; else Quit(); break; } case IDC_RESTART: { if (d->Vm && d->Obj) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) d->Vm->Execute(Code, 0, d->Log, false); } break; } case IDC_GOTO: { break; } case IDC_STEP_INSTR: { if (d->Vm) d->Vm->StepInstruction(); break; } case IDC_STEP_LINE: { if (d->Vm) d->Vm->StepLine(); break; } case IDC_STEP_OUT: { if (d->Vm) d->Vm->StepOut(); break; } case IDC_BREAK_POINT: { int Addr = d->Text->GetAddr(); if (Addr >= 0) d->Vm->BreakPoint(Addr, d->Text->Breakpoint(Addr)); break; } case IDC_BREAK_CPP: { if (!d->Vm) { LAssert(0); break; } LMenuItem *i = Menu->FindItem(IDC_BREAK_CPP); if (!i) { LAssert(0); break; } bool b = !i->Checked(); i->Checked(b); d->Vm->SetBreakCpp(b); break; } } return LWindow::OnCommand(Cmd, Event, Wnd); } int LVmDebuggerWnd::OnNotify(LViewI *Ctrl, LNotification n) { if (!d->AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_TABS: { switch (Ctrl->Value()) { case 0: // Variables { if (d->Obj) { UpdateVariables(d->Globals, d->Vm->d->Scope[SCOPE_GLOBAL], d->Obj->Globals.Length(), 'G'); } if (d->Vm->d->Frames.Length()) { LVirtualMachinePriv::StackFrame &frm = d->Vm->d->Frames.Last(); UpdateVariables(d->Locals, d->Vm->d->Scope[SCOPE_LOCAL], frm.CurrentFrameSize, 'L'); } else d->Locals->Empty(); UpdateVariables(d->Registers, d->Vm->d->Scope[SCOPE_REGISTER], MAX_REGISTER, 'R'); break; } case 1: // Call stack { d->Stack->Empty(); LArray &Frames = d->Vm->d->Frames; for (int i=(int)Frames.Length()-1; i>=0; i--) { LVirtualMachinePriv::StackFrame &Sf = Frames[i]; LListItem *li = new LListItem; LString s; s.Printf("%p/%i", Sf.ReturnIp, Sf.ReturnIp); li->SetText(s, 0); const char *Src = d->Vm->d->Code->AddrToSourceRef(Sf.ReturnIp); li->SetText(Src, 1); d->Stack->Insert(li); } break; } case 2: // Log { break; } } break; } case IDC_SOURCE_LST: { if (n.Type == LNotifyItemSelect) { LListItem *it = d->SourceLst->GetSelected(); if (!it) break; const char *full = it->GetText(1); if (!LFileExists(full)) break; LoadFile(full); } break; } } return LWindow::OnNotify(Ctrl, n); } LMessage::Param LVmDebuggerWnd::OnEvent(LMessage *Msg) { return LWindow::OnEvent(Msg); } ///////////////////////////////////////////////////////////////////////////// bool LScriptArguments::Throw(const char *File, int Line, const char *Msg, ...) { if (!Vm || !Vm->d) return false; va_list Arg; va_start(Arg, Msg); LString s; s.Printf(Arg, Msg); va_end(Arg); Vm->d->OnException(File, Line, -1, s); return true; } diff --git a/src/common/Coding/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,560 +1,562 @@ #ifndef _GSCRIPTING_PRIV_H_ #define _GSCRIPTING_PRIV_H_ #include #include "lgi/common/Scripting.h" #include "lgi/common/RefCount.h" // Instructions #define _i(name, opcode, desc) \ name = opcode, #define AllInstructions \ _i(INop, 0, "Nop") \ _i(IAssign, OpAssign, "OpAssign") \ _i(IPlus, OpPlus, "OpPlus") \ _i(IUnaryPlus, OpUnaryPlus, "OpUnaryPlus") \ _i(IMinus, OpMinus, "OpMinus") \ _i(IUnaryMinus, OpUnaryMinus, "OpUnaryMinus") \ _i(IMul, OpMul, "OpMul") \ _i(IDiv, OpDiv, "OpDiv") \ _i(IMod, OpMod, "OpMod") \ _i(ILessThan, OpLessThan, "OpLessThan") \ _i(ILessThanEqual, OpLessThanEqual, "OpLessThanEqual") \ _i(IGreaterThan, OpGreaterThan, "OpGreaterThan") \ _i(IGreaterThanEqual, OpGreaterThanEqual, "OpGreaterThanEqual") \ _i(IEquals, OpEquals, "OpEquals") \ _i(INotEquals, OpNotEquals, "OpNotEquals") \ _i(IPlusEquals, OpPlusEquals, "OpPlusEquals") \ _i(IMinusEquals, OpMinusEquals, "OpMinusEquals") \ _i(IMulEquals, OpMulEquals, "OpMulEquals") \ _i(IDivEquals, OpDivEquals, "OpDivEquals") \ _i(IPostInc, OpPostInc, "OpPostInc") \ _i(IPostDec, OpPostDec, "OpPostDec") \ _i(IPreInc, OpPreInc, "OpPreInc") \ _i(IPreDec, OpPreDec, "OpPreDec") \ _i(IAnd, OpAnd, "OpAnd") \ _i(IOr, OpOr, "OpOr") \ _i(INot, OpNot, "OpNot") \ \ /** Calls a another part of the script */ \ _i(ICallScript, 64, "CallScript") \ /** Calls a method defined by the script context */ \ _i(ICallMethod, 65, "CallMethod") \ _i(IDomGet, 67, "DomGet") \ _i(IDomSet, 68, "DomSet") \ _i(IPush, 69, "Push") \ _i(IPop, 70, "Pop") \ _i(IJump, 71, "Jump") \ _i(IJumpZero, 72, "JumpZ") \ _i(IArrayGet, 73, "ArrayGet") \ _i(IArraySet, 74, "ArraySet") \ _i(IRet, 75, "Return") \ _i(IDomCall, 76, "DomCall") \ /* Stop in the VM at instruction */ \ _i(IBreakPoint, 77, "BreakPoint") \ _i(ICast, 78, "Cast") \ /* Open the debugger */ \ _i(IDebug, 79, "Debug") \ enum GInstruction { AllInstructions }; enum OperatorType { OpPrefix, OpInfix, OpPostfix, }; extern char16 sChar[]; extern char16 sInt[]; extern char16 sUInt[]; extern char16 sInt32[]; extern char16 sUInt32[]; extern char16 sInt64[]; extern char16 sHWND[]; extern char16 sDWORD[]; extern char16 sLPTSTR[]; extern char16 sLPCTSTR[]; extern char16 sElse[]; extern char16 sIf[]; extern char16 sFunction[]; extern char16 sExtern[]; extern char16 sFor[]; extern char16 sWhile[]; extern char16 sReturn[]; extern char16 sInclude[]; extern char16 sDefine[]; extern char16 sStruct[]; extern char16 sTrue[]; extern char16 sFalse[]; extern char16 sNull[]; extern char16 sOutParam[]; extern char16 sHash[]; extern char16 sPeriod[]; extern char16 sComma[]; extern char16 sSemiColon[]; extern char16 sStartRdBracket[]; extern char16 sEndRdBracket[]; extern char16 sStartSqBracket[]; extern char16 sEndSqBracket[]; extern char16 sStartCurlyBracket[]; extern char16 sEndCurlyBracket[]; extern const char *InstToString(GInstruction i); /* Variable Reference: Can either be a: - stack variable - global variable - register (0 .. MAX_REGISTER - 1) Thus a variable reference encodes 2 pieces of information, first the scope of the reference (as above) and secondly the index into that scope. Scopes are stored as arrays of variables. The scope is one byte, the index is 3 bytes following, totally 4 bytes per ref. */ #define MAX_REGISTER 16 /// 32bit variable reference, used to track where a variable is during compilation. -struct GVarRef +struct LVarRef { /// \sa #SCOPE_REGISTER, #SCOPE_LOCAL or #SCOPE_GLOBAL unsigned Scope : 8; /// Index into scope int Index : 24; bool Valid() { return Index >= 0; } void Empty() { Scope = 0; Index = -1; } bool IsReg() { return Scope == SCOPE_REGISTER && Index >= 0 && Index < MAX_REGISTER; } void SetReg(int i) { Scope = SCOPE_REGISTER; Index = i; } - bool operator ==(GVarRef &r) + bool operator ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } - bool operator !=(GVarRef &r) + bool operator !=(LVarRef &r) { return r.Scope != Scope || r.Index != Index; } const char *GetStr() { if (Index < 0) { LAssert(!"Invalid reference"); return "NoRef"; } #define GETSTR_BUF_SIZE 16 static char Buf[8][GETSTR_BUF_SIZE]; static int Cur = 0; static char Names[] = {'R', 'L', 'G'}; char *b = Buf[Cur++]; if (Cur >= 8) Cur = 0; LAssert(Scope <= SCOPE_GLOBAL); sprintf_s(b, GETSTR_BUF_SIZE, "%c%i", Names[Scope], Index); return b; } }; union GPtr { uint8_t *u8; uint16 *u16; uint32_t *u32; int8 *i8; int16 *i16; int32 *i32; double *dbl; float *flt; - GVarRef *r; + LVarRef *r; LFunc **fn; }; class SystemFunctions; class LCompileTools { protected: OperatorType OpType(LOperator o) { switch (o) { case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return OpPrefix; case OpPostInc: case OpPostDec: return OpPostfix; default: return OpInfix; } } int GetPrecedence(LOperator o) { // Taken from: // http://www.cppreference.com/operator_precedence.html switch (o) { case OpAssign: case OpMinusEquals: case OpPlusEquals: case OpMulEquals: case OpDivEquals: return 16; case OpAnd: return 13; case OpOr: return 14; case OpEquals: case OpNotEquals: return 9; case OpLessThan: case OpLessThanEqual: case OpGreaterThan: case OpGreaterThanEqual: return 8; case OpPlus: case OpMinus: return 6; case OpMul: case OpDiv: case OpMod: return 5; case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return 3; case OpPostInc: case OpPostDec: return 2; case OpNull: return 0; default: LAssert(!"Really?"); break; } return -1; } LOperator IsOp(char16 *s, int PrevIsOp) { if (!s) return OpNull; if (s[0] != 0 && !s[1]) { // One character operator switch (*s) { case '=': return OpAssign; case '*': return OpMul; case '/': return OpDiv; case '<': return OpLessThan; case '>': return OpGreaterThan; case '%': return OpMod; case '!': return OpNot; case '+': { if (PrevIsOp == 0) return OpPlus; return OpUnaryPlus; } case '-': { if (PrevIsOp == 0) return OpMinus; return OpUnaryMinus; } } } else if (s[0] != 0 && s[1] == '=' && !s[2]) { // 2 chars, "something" equals operator switch (*s) { case '!': return OpNotEquals; case '=': return OpEquals; case '<': return OpLessThanEqual; case '>': return OpGreaterThanEqual; case '+': return OpPlusEquals; case '-': return OpMinusEquals; case '*': return OpMulEquals; case '/': return OpDivEquals; } } else if (s[0] == '+' && s[1] == '+' && !s[2]) { if (PrevIsOp == 0) return OpPostInc; return OpPreInc; } else if (s[0] == '-' && s[1] == '-' && !s[2]) { if (PrevIsOp == 0) return OpPostDec; return OpPreDec; } else if (s[0] == '&' && s[1] == '&' && !s[2]) { return OpAnd; } else if (s[0] == '|' && s[1] == '|' && !s[2]) { return OpOr; } return OpNull; } }; /// This class compiles the source down to byte code class LCompiler : public LScriptUtils { class LCompilerPriv *d; public: /// Constructor LCompiler(); ~LCompiler(); /// Compile the source into byte code. bool Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ); }; /// This class is the VM for the byte language class LVirtualMachine : public LScriptUtils { friend class LVmDebuggerWnd; friend class LScriptArguments; class LVirtualMachinePriv *d; public: static bool BreakOnWarning; LVirtualMachine(LVmDebuggerCallback *callback = NULL); LVirtualMachine(LVirtualMachine *vm); ~LVirtualMachine(); /// Executes the whole script starting at the top LExecutionStatus Execute ( /// [In] The code to execute LCompiledCode *Code, /// [In] The instruction to start at... [defaults to the start of script) uint32_t StartOffset = 0, /// [Optional] Log file for execution LStream *Log = NULL, /// Start the script execution straight away? bool StartImmediately = true, /// Optional return value LVariant *Return = NULL ); /// Execute just one method and return LExecutionStatus ExecuteFunction ( /// [In] The code to execute LCompiledCode *Code, /// [In] The function to execute LFunctionInfo *Func, /// [In/Out] The function's arguments LScriptArguments &Args, /// [Optional] Log file for execution LStream *Log = NULL, /// [Optional] Copy arguments back to this array LScriptArguments *ArgsOut = NULL ); // Debugging commands LVmDebugger *OpenDebugger(LCompiledCode *Code = NULL, const char *Assembly = NULL); bool StepInstruction(); bool StepLine(); bool StepOut(); bool BreakExecution(); bool Continue(); bool BreakPoint(const char *File, int Line, bool Add); bool BreakPoint(int Addr, bool Add); void SetBreakCpp(bool Brk); void SetDebuggerEnabled(bool b); // Properties void SetTempPath(const char *Path); }; /// Scripting engine system functions class SystemFunctions : public LScriptContext { LScriptEngine *Engine; LStream *Log; #ifdef WINNATIVE HANDLE Brk; #endif LView *CastLView(LVariant &v); bool WaitForReturn(LScriptArguments &Args); public: SystemFunctions(); ~SystemFunctions(); LStream *GetLog(); bool SetLog(LStream *log); void SetEngine(LScriptEngine *Eng); char *GetIncludeFile(char *FileName) { return NULL; } GHostFunc *GetCommands(); // Debug bool Assert(LScriptArguments &Args); bool Throw(LScriptArguments &Args); bool DebuggerEnabled(LScriptArguments &Args); // String bool LoadString(LScriptArguments &Args); /// Formats a string bool Sprintf(LScriptArguments &Args); /// Formats a file size bool FormatSize(LScriptArguments &Args); /// Prints items to the console bool Print(LScriptArguments &Args); /// Converts args to string bool ToString(LScriptArguments &Args); // Object creation/deletion bool New(LScriptArguments &Args); bool Delete(LScriptArguments &Args); bool Len(LScriptArguments &Args); // File /// Reads a text file into a variable bool ReadTextFile(LScriptArguments &Args); /// Writes a text file from a variable bool WriteTextFile(LScriptArguments &Args); /// \brief Opens a file open dialog to select files. /// /// Args: LView *Parent, char *Patterns, /// char *InitFolder, bool Multiselect bool SelectFiles(LScriptArguments &Args); /// Open a folder select dialog /// /// Args: LView *Parent, char *InitFolder bool SelectFolder(LScriptArguments &Args); /// Lists file in folder /// /// Args; char *Path, [optional] char *Pattern /// Returns: List of DOM objects with the following fields: /// Name - File/dir name /// Size - Size of entry /// Folder - bool, true if folder /// Modified - LDateTime, modified time bool ListFiles(LScriptArguments &Args); /// Deletes a file bool DeleteFile(LScriptArguments &Args); /// Gets the current script path. bool CurrentScript(LScriptArguments &Args); /// Finds out if a path exists. bool PathExists(LScriptArguments &Args); /// Joins path segments together. bool PathJoin(LScriptArguments &Args); /// Returns the current OS path separator. bool PathSep(LScriptArguments &Args); // Time /// Sleeps a number of milliseconds bool Sleep(LScriptArguments &Args); /// Get the current tick count bool ClockTick(LScriptArguments &Args); /// Get the date time bool Now(LScriptArguments &Args); // Bitmaps /// Creates a memory context bool CreateSurface(LScriptArguments &Args); + bool ColourSpaceToString(LScriptArguments &Args); + bool StringToColourSpace(LScriptArguments &Args); // User interface /// Standard alert message box bool MessageDlg(LScriptArguments &Args); /// Gets an input string from the user /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title[, bool IsPassword]); bool GetInputDlg(LScriptArguments &Args); /// Gets a view by id bool GetViewById(LScriptArguments &Args); // System /// Executes a command, waits for it to finish, then returns it's output: /// String Execute(String Application, String CmdLine); bool Execute(LScriptArguments &Args); /// Executes a command and doesn't wait for it to return: /// Bool System(String Application, String CmdLine); bool System(LScriptArguments &Args); /// Gets the operating system name. bool OsName(LScriptArguments &Args); /// Gets the operating system version. bool OsVersion(LScriptArguments &Args); }; #endif diff --git a/src/common/Gdc2/Font/DisplayString.cpp b/src/common/Gdc2/Font/DisplayString.cpp --- a/src/common/Gdc2/Font/DisplayString.cpp +++ b/src/common/Gdc2/Font/DisplayString.cpp @@ -1,2275 +1,2275 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/FontSelect.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/DisplayString.h" #include "lgi/common/PixelRops.h" #include "lgi/common/UnicodeString.h" #ifdef FontChange #undef FontChange #endif #ifdef LGI_SDL #include "ftsynth.h" #endif #if WINNATIVE static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #endif //345678123456781234567812345678 // 2nd #define DEBUG_CHAR_AT 0 #define DEBUG_BOUNDS 0 #if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL)) #define DISPLAY_STRING_FRACTIONAL_NATIVE 1 #else #define DISPLAY_STRING_FRACTIONAL_NATIVE 0 #endif #if defined(__GTK_H__) struct Block : public LRect { /// This points to somewhere in Ds->Str OsChar *Str = NULL; /// Bytes in this block int Bytes = 0; /// Utf-8 characters in this block int Chars = 0; /// Alternative font to get characters from (NULL if using the display string's font) LFont *Fnt = NULL; /// Layout for this block. Shouldn't ever be NULL. But shouldn't crash otherwise. Gtk::PangoLayout *Hnd = NULL; ~Block() { if (Hnd) g_object_unref(Hnd); } }; struct GDisplayStringPriv { LDisplayString *Ds; LArray Blocks; bool Debug; int LastTabOffset; GDisplayStringPriv(LDisplayString *str) : Ds(str) { #if 0 Debug = Stristr(Ds->Str, "(Jumping).wma") != 0; #else Debug = false; #endif LastTabOffset = -1; } ~GDisplayStringPriv() { } void Create(Gtk::GtkPrintContext *PrintCtx) { auto *Fs = LFontSystem::Inst(); auto *Fnt = Ds->Font; auto Tbl = Fnt->GetGlyphMap(); int Chars = 0; LUtf8Ptr p(Ds->Str); auto *Start = p.GetPtr(); if (Tbl) { int32 w; Block *b = NULL; auto DisplayCtx = LFontSystem::Inst()->GetContext(); while ((w = (int32)p)) { LFont *f; if (w >= 0x80 && !_HasUnicodeGlyph(Tbl, w)) f = Fs->GetGlyph(w, Ds->Font); else f = Ds->Font; if (!b || (f != NULL && f != Fnt)) { // Finish old block if (b) { b->Bytes = p.GetPtr() - Start; b->Chars = Chars; Chars = 0; } Start = p.GetPtr(); if (f) { // Start new block... b = &Blocks.New(); b->Str = (char*)Start; b->Bytes = -1; // unknown at this point if (f != Ds->Font) // External font b->Fnt = f; // Create a pango layout if (PrintCtx) b->Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b->Hnd = Gtk::pango_layout_new(DisplayCtx); } // else no font supports glyph Fnt = f; } // else no change in font p++; Chars++; } if (b) { // Finish the last block b->Bytes = p.GetPtr() - Start; b->Chars = Chars; } if (Debug) { // Print the block array for (size_t i=0; iStrWords) { p++; Chars++; } auto &b = Blocks.New(); b.Str = (char*)Start; b.Bytes = p.GetPtr() - Start; b.Chars = Chars; if (PrintCtx) b.Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b.Hnd = Gtk::pango_layout_new(LFontSystem::Inst()->GetContext()); } /* This could get stuck in an infinite loop. Leaving out for the moment. for (auto &b: Blocks) { if (b.Hnd == NULL && b.Fnt == NULL) { Blocks.Length(0); goto Start; } } */ } void UpdateTabs(int Offset, int Size, bool Debug = false) { if (Ds->Font && Ds->Font->TabSize()) { int Len = 16; LastTabOffset = Offset; Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true); if (t) { for (int i=0; i bool StringConvert(Out *&out, ssize_t &OutWords, const In *in, ssize_t InLen) { if (!in) { out = 0; OutWords = 0; return false; } auto InSz = sizeof(In); auto OutSz = sizeof(Out); // Work out input size ssize_t InWords; if (InLen >= 0) InWords = InLen; else for (InWords = 0; in[InWords]; InWords++) ; if (InSz == OutSz) { // No conversion optimization out = (Out*)Strdup(in, InWords); OutWords = out ? InWords : 0; return out != 0; } else { // Convert the string to new word size static const char *Cp[] = { NULL, "utf-8", "utf-16", NULL, "utf-32"}; LAssert(OutSz <= 4 && InSz <= 4 && Cp[OutSz] && Cp[InSz]); out = (Out*) LNewConvertCp(Cp[OutSz], in, Cp[InSz], InWords*sizeof(In)); OutWords = Strlen(out); return out != NULL; } return false; } ////////////////////////////////////////////////////////////////////// #define SubtractPtr(a, b) ( ((a)-(b)) / sizeof(*a) ) #define VisibleTabChar 0x2192 #define IsTabChar(c) (c == '\t') // || (c == VisibleTabChar && VisibleTab)) #if USE_CORETEXT #include void LDisplayString::CreateAttrStr() { if (!Wide) return; if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } wchar_t *w = Wide; CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); if (string) { CFDictionaryRef attributes = Font->GetAttributes(); if (attributes) AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); // else LAssert(0); CFRelease(string); } } #endif LDisplayString::LDisplayString(LFont *f, const char *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str) { LAssert(StrWords >= 0); if (StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); } #elif defined MAC && !defined(LGI_SDL) Hnd = 0; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else ATSUCreateTextLayout(&Hnd); #endif } #endif } LDisplayString::LDisplayString(LFont *f, const char16 *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #elif defined MAC && !defined(LGI_SDL) Hnd = NULL; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else OSStatus e = ATSUCreateTextLayout(&Hnd); if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e); #endif } #endif } #ifdef _MSC_VER LDisplayString::LDisplayString(LFont *f, const uint32_t *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #endif } #endif LDisplayString::~LDisplayString() { #if defined(LGI_SDL) Img.Reset(); #elif defined __GTK_H__ DeleteObj(d); #elif defined MAC #if USE_CORETEXT if (Hnd) { CFRelease(Hnd); Hnd = NULL; } if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } #else if (Hnd) ATSUDisposeTextLayout(Hnd); #endif #endif DeleteArray(Str); #if LGI_DSP_STR_CACHE DeleteArray(Wide); #endif } void LDisplayString::DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r) { if (Ch == '\t') { r.Inset(3, 3); if (r.Y()/2 == 0) r.y2++; int Cy = (r.Y() >> 1); pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2); } else // Space { int x = r.x1 + (r.X()>>1) - 1; int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2; pDC->Rectangle(x, Cy, x+1, Cy+1); } } void LDisplayString::Layout(bool Debug) { if (LaidOut || !Font) return; LaidOut = 1; #if defined(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes LArray Glyphs; for (OsChar *s = Str; *s; s++) { FT_UInt index = FT_Get_Char_Index(Fnt, *s); if (index) Glyphs.Add(index); } // Measure the string... LPoint Sz; int FontHt = Font->GetHeight(); int AscentF = (int) (Font->Ascent() * FScale); int LoadMode = FT_LOAD_FORCE_AUTOHINT; for (unsigned i=0; iglyph->metrics.horiBearingY; Sz.x += Fnt->glyph->metrics.horiAdvance; Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height); } } // Create the memory context to draw into xf = Sz.x; x = ((Sz.x + FScale - 1) >> FShift) + 1; yf = FontHt << FShift; y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1; if (Img.Reset(new LMemDC(x, y, CsIndex8))) { // Clear the context to black Img->Colour(0); Img->Rectangle(); bool IsItalic = Font->Italic(); int CurX = 0; int FBaseline = Fnt->size->metrics.ascender; for (unsigned i=0; iglyph); error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL); if (error == 0) { FT_Bitmap &bmp = Fnt->glyph->bitmap; if (bmp.buffer) { // Copy rendered glyph into our image memory int Px = (CurX + (FScale >> 1)) >> FShift; int PyF = AscentF - Fnt->glyph->metrics.horiBearingY; int Py = PyF >> FShift; int Skip = 0; if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP) { Px += Fnt->glyph->bitmap_left; Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0; Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top; } LAssert(Px + bmp.width <= Img->X()); for (int y=0; y= 0); out += Px+Skip; memcpy(out, in+Skip, bmp.width-Skip); } /* else { LAssert(!"No scanline?"); break; } */ } } if (i < Glyphs.Length() - 1) { FT_Vector kerning; FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning); CurX += Fnt->glyph->metrics.horiAdvance + kerning.x; } else { CurX += Fnt->glyph->metrics.horiAdvance; } } } } } else LgiTrace("::Layout Create MemDC failed\n"); #elif defined(__GTK_H__) y = Font->GetHeight(); yf = y * PANGO_SCALE; if (!d->Blocks.Length() || !Font->Handle()) return; LUtf8Ptr Utf(Str); int32 Wide; while (*Utf.GetPtr()) { Wide = Utf; if (!Wide) { LgiTrace("%s:%i - Not utf8\n", _FL); return; } Utf++; } LFontSystem *FSys = LFontSystem::Inst(); Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle()); int TabSizePx = Font->TabSize(); int TabSizeF = TabSizePx * FScale; int TabOffsetF = DrawOffsetF % TabSizeF; int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0; d->UpdateTabs(OffsetF / FScale, Font->TabSize()); if (Font->Underline()) { Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new(); Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE); Gtk::pango_attr_list_insert(attrs, attr); for (auto &b: d->Blocks) Gtk::pango_layout_set_attributes(b.Hnd, attrs); Gtk::pango_attr_list_unref(attrs); } int Fx = 0; for (auto &b: d->Blocks) { int bx = 0, by = 0; if (b.Hnd) { if (!LIsUtf8(b.Str, b.Bytes)) { LgiTrace("Invalid UTF8: '%.*S'\n", (int)b.Bytes, b.Str); } else { Gtk::pango_layout_set_text(b.Hnd, b.Str, b.Bytes); } Gtk::pango_layout_get_size(b.Hnd, &bx, &by); } else if (b.Fnt) { b.Fnt->_Measure(bx, by, b.Str, b.Bytes); bx <<= FShift; by <<= FShift; } b.ZOff(bx-1, by-1); b.Offset(Fx, 0); xf += bx; yf = MAX(yf, by); } x = (xf + PANGO_SCALE - 1) / PANGO_SCALE; #if 1 y = Font->GetHeight(); #else y = (yf + PANGO_SCALE - 1) / PANGO_SCALE; #endif if (y > Font->GetHeight()) { printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight()); } #elif defined MAC && !defined(LGI_SDL) #if USE_CORETEXT int height = Font->GetHeight(); y = height; if (AttrStr) { LAssert(!Hnd); Hnd = CTLineCreateWithAttributedString(AttrStr); if (Hnd) { CGFloat ascent = 0.0; CGFloat descent = 0.0; CGFloat leading = 0.0; double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading); x = ceil(width); xf = width * FScale; yf = height * FScale; } } #else if (!Hnd || !Str) return; OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } ATSUTextMeasurement fTextBefore; ATSUTextMeasurement fTextAfter; if (pDC) { OsPainter dc = pDC->Handle(); ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } ATSUTab Tabs[32]; for (int i=0; iTabSize()) << 16; Tabs[i].tabType = kATSULeftTab; } e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs)); if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e); e = ATSUGetUnjustifiedBounds( Hnd, kATSUFromTextBeginning, kATSUToTextEnd, &fTextBefore, &fTextAfter, &fAscent, &fDescent); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } xf = fTextAfter - fTextBefore; yf = fAscent + fDescent; x = (xf + 0xffff) >> 16; y = (yf + 0xffff) >> 16; ATSUSetTransientFontMatching(Hnd, true); #endif #elif defined(HAIKU) if (!Font) { LgiTrace("%s:%i - Missing pointer: %p\n", _FL, Font); return; } BFont *fnt = Font->Handle(); if (!fnt) { LgiTrace("%s:%i - Missing handle. %p/%p\n", _FL, fnt); return; } int tabSize = Font->TabSize() ? Font->TabSize() : 32; font_height height = {0}; fnt->GetHeight(&height); yf = y = height.ascent + height.descent + height.leading; if (!Str) return; LUtf8Ptr start(Str); int isTab = -1; for (LUtf8Ptr p(Str); true; p++) { int32_t ch = p; if (isTab < 0) { isTab = IsTabChar(ch); } else if (!ch || IsTabChar(ch) ^ isTab) { auto &l = Info.New(); l.Str = start.GetPtr(); l.Len = p.GetPtr() - start.GetPtr(); const char *strArr[] = { l.Str }; const int32 strLen[] = { l.Len }; float width[1] = { 0 }; fnt->GetStringWidths(strArr, strLen, 1, width); if (isTab) { // Handle tab(s) for (int t=0; tHandle()) Font->Create(); y = Font->GetHeight(); LFontSystem *Sys = LFontSystem::Inst(); if (Sys && Str) { LFont *f = Font; bool GlyphSub = Font->SubGlyphs(); Info[i].Str = Str; int TabSize = Font->TabSize() ? Font->TabSize() : 32; bool WasTab = IsTabChar(*Str); f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font; if (f && f != Font) { f->Size(Font->Size()); f->SetWeight(Font->GetWeight()); if (!f->Handle()) f->Create(); } bool Debug = WasTab; uint32_t u32; for (LUnicodeString u(Str, StrWords); true; u++) { u32 = *u; LFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font; bool Change = n != f || // The font changed (IsTabChar(u32) ^ WasTab) || // Entering/leaving a run of tabs !u32 || // Hit a NULL character (u.Get() - Info[i].Str) >= 1000; // This is to stop very long segments not rendering if (Change) { // End last segment if (n && n != Font) { n->Size(Font->Size()); n->SetWeight(Font->GetWeight()); if (!n->Handle()) n->Create(); } Info[i].Len = (int) (u.Get() - Info[i].Str); if (Info[i].Len) { if (WasTab) { // Handle tab(s) for (int t=0; tGetHeight() > Font->GetHeight())) { Info[i].SizeDelta = -1; f->PointSize(Font->PointSize() + Info[i].SizeDelta); f->Create(); } #endif if (!f) { // no font, so ? out the chars... as they aren't available anyway // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); m = Font; for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); x += Info[i].X = sx > 0xffff ? 0xffff : sx; } auto Ch = Info[i].First(); Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; i++; } f = n; // Start next segment WasTab = IsTabChar(u32); Info[i].Str = u.Get(); } if (!u32) break; } if (Info.Length() > 0 && Info.Last().Len == 0) { Info.Length(Info.Length()-1); } } xf = x; yf = y; #endif } int LDisplayString::GetDrawOffset() { return DrawOffsetF >> FShift; } void LDisplayString::SetDrawOffset(int Px) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Px << FShift; } bool LDisplayString::ShowVisibleTab() { return VisibleTab; } void LDisplayString::ShowVisibleTab(bool i) { VisibleTab = i; } bool LDisplayString::IsTruncated() { return AppendDots; } void LDisplayString::TruncateWithDots(int Width) { Layout(); #if defined __GTK_H__ int Fx = 0; int Fwid = Width << FShift; for (auto &b: d->Blocks) { if (Fwid < Fx + b.X()) { if (b.Hnd) { Gtk::pango_layout_set_ellipsize(b.Hnd, Gtk::PANGO_ELLIPSIZE_END); Gtk::pango_layout_set_width(b.Hnd, Fwid - Fx); Gtk::pango_layout_set_single_paragraph_mode(b.Hnd, true); } else if (b.Fnt) { } break; } Fx += b.X(); } #elif WINNATIVE if (Width < X() + 8) { ssize_t c = CharAt(Width); if (c >= 0 && c < StrWords) { if (c > 0) c--; // fudge room for dots if (c > 0) c--; AppendDots = 1; if (Info.Length()) { int Width = 0; int Pos = 0; for (int i=0; i= Pos && c < Pos + Info[i].Len) { Info[i].Len = (int) (c - Pos); Info[i].Str[Info[i].Len] = 0; LFont *f = Font; if (Info[i].FontId) { LFontSystem *Sys = LFontSystem::Inst(); f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize() + Info[i].SizeDelta); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { int Sx, Sy; f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len); Info[i].X = Sx; Width += Info[i].X; } Info.Length(i + 1); break; } Pos += Info[i].Len; Width += Info[i].X; } int DotsX, DotsY; Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3); x = Width + DotsX; } } } #elif defined(LGI_SDL) #elif defined(MAC) #if USE_CORETEXT if (Hnd) { /* CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes()); if (truncationString) { CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); if (truncationToken) { CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken); CFRelease(truncationToken); if (TruncatedLine) { CFRelease(Hnd); Hnd = TruncatedLine; } } } */ } #endif #endif } ssize_t LDisplayString::CharAt(int Px, LPxToIndexType Type) { Layout(); if (Px < 0) { return 0; } else if (Px >= (int)x) { #if defined __GTK_H__ if (Str) { - GUtf8Str u(Str); + LUtf8Str u(Str); return u.GetChars(); } return 0; #else #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif #endif } int Status = -1; #if defined(__GTK_H__) int Fx = 0; int Fpos = Px << FShift; Status = 0; for (auto &b: d->Blocks) { int Index = 0, Trailing = 0; int Foffset = Fpos - Fx; if (b.Hnd && Gtk::pango_layout_xy_to_index(b.Hnd, Foffset, 0, &Index, &Trailing)) { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Foffset=%g index=%i trailing=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, (double)Foffset/FScale, Index, Trailing); - GUtf8Str u(Str); + LUtf8Str u(Str); while ((OsChar*)u.GetPtr() < Str + Index + Trailing) { u++; Status++; } return Status; } else { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Chars=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, b.Chars); Status += b.Chars; } Fx += b.X(); } #elif defined(LGI_SDL) LAssert(!"Impl me"); #elif defined(MAC) if (Hnd && Str) { #if USE_CORETEXT CGPoint pos = { (CGFloat)Px, 1.0f }; CFIndex utf16 = CTLineGetStringIndexForPosition(Hnd, pos); // 'utf16' is in UTF-16, and the API needs to return a UTF-32 index... // So convert between the 2 here... LAssert(Str != NULL); int utf32 = 0; for (int i=0; Str[i] && iTabSize() ? Font->TabSize() : 32; int Cx = 0; int Char = 0; #if DEBUG_CHAR_AT printf("CharAt(%i) Str='%s'\n", Px, Str); #endif for (int i=0; i= Cx && Px < Cx + Info[i].X) { // The position is in this block of characters if (IsTabChar(Info[i].Str[0])) { // Search through tab block for (int t=0; t= Cx && Px < Cx + TabX) { Status = Char; #if DEBUG_CHAR_AT printf("\tIn tab block %i\n", i); #endif break; } Cx += TabX; Char++; } } else { // Find the pos in this block LFont *f = Font; #if defined(WIN32) if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->Colour(Font->Fore(), Font->Back()); f->Size(Font->Size()); if (!f->Handle()) { f->Create(); } } #endif int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type); #if DEBUG_CHAR_AT printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n", i, Fit, Px, Cx, Px-Cx, Info[i].Str); #endif if (Fit >= 0) Status = Char + Fit; else Status = -1; break; } } else { // Not in this block, skip the whole lot Cx += Info[i].X; Char += Info[i].Len; } } if (Status < 0) { Status = Char; } } #endif return Status; } ssize_t LDisplayString::Length() { return StrWords; } int LDisplayString::X() { Layout(); return x; } int LDisplayString::Y() { Layout(); return y; } LPoint LDisplayString::Size() { Layout(); return LPoint(x, y); } #if defined LGI_SDL template bool CompositeText8Alpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = Div255[a * fore_px.r]; map[a].g = Div255[a * fore_px.g]; map[a].b = Div255[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255; map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255; map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)d >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, o; OutPx *s; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *d = map[a]; break; default: // Blend o = 255 - a; s = map + a; #define NonPreMulOver32NoAlpha(c) d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255 NonPreMulOver32NoAlpha(r); NonPreMulOver32NoAlpha(g); NonPreMulOver32NoAlpha(b); break; } d++; } } else { while (i < e) { // Copy rop *d++ = map[*i++]; } } LAssert((uint8_t*)d <= EndOfBuffer); } return true; } template bool CompositeText8NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { LRgba32 map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *DivLut = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = DivLut[a * fore_px.r]; map[a].g = DivLut[a * fore_px.g]; map[a].b = DivLut[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)]; map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)]; map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)]; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = (OutPx*) (*Out)[py + y]; if (!dst) continue; dst += Clip.DstClip.x1; if ((uint8_t*)dst < StartOfBuffer) continue; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LRgba32 *src; LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, oma; while (i < e) { // Alpha blend map and output pixel a = *i++; src = map + a; switch (a) { case 0: break; case 255: // Copy dst->r = src->r; dst->g = src->g; dst->b = src->b; break; default: // Blend OverPm32toPm24(src, dst); break; } dst++; } } else { while (i < e) { // Copy rop src = map + *i++; dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } template bool CompositeText5NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... #define MASK_5BIT 0x1f #define MASK_6BIT 0x3f // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = (int)Div255[a * fore_px.r] >> 3; map[a].g = (int)Div255[a * fore_px.g] >> 2; map[a].b = (int)Div255[a * fore_px.b] >> 3; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3; map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2; map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = ((OutPx*) (*Out)[py + y]); if (!dst) continue; dst += Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a; OutPx *src; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *dst = map[a]; break; default: { // Blend #if 0 uint8_t oma = 255 - a; src = map + a; LRgb24 d = { G5bitTo8bit(dst->r), G6bitTo8bit(dst->g), G5bitTo8bit(dst->b)}; LRgb24 s = { G5bitTo8bit(src->r), G6bitTo8bit(src->g), G5bitTo8bit(src->b)}; dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3; dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2; dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3; #else uint8_t a5 = a >> 3; uint8_t a6 = a >> 2; uint8_t oma5 = MASK_5BIT - a5; uint8_t oma6 = MASK_6BIT - a6; src = map + a; dst->r = ((oma5 * (uint8_t)dst->r) + (a5 * (uint8_t)src->r)) / MASK_5BIT; dst->g = ((oma6 * (uint8_t)dst->g) + (a6 * (uint8_t)src->g)) / MASK_6BIT; dst->b = ((oma5 * (uint8_t)dst->b) + (a5 * (uint8_t)src->b)) / MASK_5BIT; #endif break; } } dst++; } } else { while (i < e) { // Copy rop *dst++ = map[*i++]; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } #endif void LDisplayString::Draw(LSurface *pDC, int px, int py, LRect *r, bool Debug) { Layout(); #if DISPLAY_STRING_FRACTIONAL_NATIVE // GTK / Mac use fractional pixels, so call the fractional version: LRect rc; if (r) { rc = *r; rc.x1 <<= FShift; rc.y1 <<= FShift; #if defined(MAC) && !defined(__GTK_H__) rc.x2 <<= FShift; rc.y2 <<= FShift; #else rc.x2 = (rc.x2 + 1) << FShift; rc.y2 = (rc.y2 + 1) << FShift; #endif } FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug); #elif defined(HAIKU) if (!Font || !pDC) { LgiTrace("%s:%i - No ptr: %p/%p.\n", _FL, Font, pDC); return; } BFont *fnt = Font->Handle(); BView *view = pDC->Handle(); if (!fnt || !view) { LgiTrace("%s:%i - No handle: %p/%p(%s).\n", _FL, fnt, view, pDC->GetClass()); return; } if (!Info.Length()) { LgiTrace("%s:%i - No layout.\n", _FL); return; } font_height height = {0}; fnt->GetHeight(&height); if (!Font->Transparent()) { view->SetHighColor(Font->Back()); if (r) view->FillRect(*r); else view->FillRect(BRect(px, py, px+x, py+y)); } view->SetFont(fnt); view->SetHighColor(Font->Fore()); int cx = px; for (auto &i: Info) { view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); cx += i.X; } #elif defined(LGI_SDL) if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); LBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r); LColourSpace DstCs = pDC->GetColourSpace(); switch (DstCs) { #define DspStrCase(px_fmt, comp) \ case Cs##px_fmt: \ CompositeText##comp(pDC, Img, Font, px-Ox, py-Oy, Clip); \ break; DspStrCase(Rgb16, 5NoAlpha) DspStrCase(Bgr16, 5NoAlpha) DspStrCase(Rgb24, 8NoAlpha) DspStrCase(Bgr24, 8NoAlpha) DspStrCase(Rgbx32, 8NoAlpha) DspStrCase(Bgrx32, 8NoAlpha) DspStrCase(Xrgb32, 8NoAlpha) DspStrCase(Xbgr32, 8NoAlpha) DspStrCase(Rgba32, 8Alpha) DspStrCase(Bgra32, 8Alpha) DspStrCase(Argb32, 8Alpha) DspStrCase(Abgr32, 8Alpha) default: LgiTrace("%s:%i - LDisplayString::Draw Unsupported colour space.\n", _FL); // LAssert(!"Unsupported colour space."); break; #undef DspStrCase } } else LgiTrace("::Draw argument error.\n"); #elif defined WINNATIVE if (Info.Length() && pDC && Font) { LFontSystem *Sys = LFontSystem::Inst(); COLOUR Old = pDC->Colour(); int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Ox = px; LColour cFore = Font->Fore(); LColour cBack = Font->Back(); LColour cWhitespace; if (VisibleTab) { cWhitespace = Font->WhitespaceColour(); LAssert(cWhitespace.IsValid()); } for (int i=0; iFont[Info[i].FontId]; f->Colour(cFore, cBack); auto Sz = Font->Size(); Sz.Value += Info[i].SizeDelta; f->Size(Sz); f->Transparent(Font->Transparent()); f->Underline(Font->Underline()); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { LRect b; if (r) { b.x1 = i ? px : r->x1; b.y1 = r->y1; b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Info[i].X - 1; b.y2 = py + Y() - 1; } if (b.Valid()) { if (IsTabChar(*Info[i].Str)) { // Invisible tab... draw blank space if (!Font->Transparent()) { pDC->Colour(cBack); pDC->Rectangle(&b); } if (VisibleTab) { int X = px; for (int n=0; nColour(cWhitespace); DrawWhiteSpace(pDC, '\t', r); X += Dx; } } } else { // Draw the character(s) LColour Fg = f->Fore(); LAssert(Fg.IsValid()); f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg); if (VisibleTab) { OsChar *start = Info[i].Str; OsChar *s = start; OsChar *e = s + Info[i].Len; int Sp = -1; while (s < e) { if (*s == ' ') { int Sx, Sy; if (Sp < 0) f->_Measure(Sp, Sy, s, 1); f->_Measure(Sx, Sy, start, (int)(s - start)); LRect r(0, 0, Sp-1, Sy-1); r.Offset(px + Sx, py); pDC->Colour(cWhitespace); DrawWhiteSpace(pDC, ' ', r); } s++; } } } } } // Inc my position px += Info[i].X; } if (AppendDots) { int Sx, Sy; Font->_Measure(Sx, Sy, GDisplayStringDots, 3); LRect b; if (r) { b.x1 = px; b.y1 = r->y1; b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Sx - 1; b.y2 = py + Y() - 1; } LColour Fg = Font->Fore(); Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } pDC->Colour(Old); } else if (r && Font && !Font->Transparent()) { pDC->Colour(Font->Back()); pDC->Rectangle(r); } #endif } int LDisplayString::GetDrawOffsetF() { return DrawOffsetF; } void LDisplayString::SetDrawOffsetF(int Fpx) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Fpx; } int LDisplayString::FX() { Layout(); return xf; } int LDisplayString::FY() { Layout(); return yf; } LPoint LDisplayString::FSize() { Layout(); return LPoint(xf, yf); } void LDisplayString::FDraw(LSurface *pDC, int fx, int fy, LRect *frc, bool Debug) { Layout(Debug); #if !DISPLAY_STRING_FRACTIONAL_NATIVE // Windows doesn't use fractional pixels, so call the integer version: LRect rc; if (frc) { rc = *frc; rc.x1 >>= FShift; rc.y1 >>= FShift; rc.x2 >>= FShift; rc.y2 >>= FShift; } Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug); #elif defined __GTK_H__ Gtk::cairo_t *cr = pDC->Handle(); if (!cr) { LAssert(!"Can't get cairo."); return; } pango_context_set_font_description(LFontSystem::Inst()->GetContext(), Font->Handle()); cairo_save(cr); LColour b = Font->Back(); double Dx = ((double)fx / FScale); double Dy = ((double)fy / FScale); double rx, ry, rw, rh; if (!Font->Transparent()) { // Background fill cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); cairo_new_path(cr); if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; } else { rx = Dx; ry = Dy; rw = x; rh = y; } cairo_rectangle(cr, rx, ry, rw, rh); cairo_fill(cr); if (frc) { cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } } else if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } cairo_translate(cr, Dx, Dy); LColour f = Font->Fore(); for (auto &b: d->Blocks) { double Bx = ((double)b.X()) / FScale; #if DEBUG_BOUNDS double By = Font->GetHeight(); cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); cairo_rectangle(cr, 0, 0, Bx, By); cairo_rectangle(cr, 1, 1, Bx - 2.0, By - 2.0); cairo_set_fill_rule(cr, Gtk::CAIRO_FILL_RULE_EVEN_ODD); cairo_fill(cr); #endif if (b.Hnd) { cairo_set_source_rgb( cr, (double)f.r()/255.0, (double)f.g()/255.0, (double)f.b()/255.0); pango_cairo_show_layout(cr, b.Hnd); if (VisibleTab && Str) { - GUtf8Str Ptr(Str); + LUtf8Str Ptr(Str); auto Ws = Font->WhitespaceColour(); pDC->Colour(Ws); for (int32 u, Idx = 0 ; (u = Ptr); Idx++) { if (IsTabChar(u) || u == ' ') { Gtk::PangoRectangle pos; Gtk::pango_layout_index_to_pos(b.Hnd, Idx, &pos); LRect r(0, 0, pos.width / FScale, pos.height / FScale); r.Offset(pos.x / FScale, pos.y / FScale); DrawWhiteSpace(pDC, u, r); } Ptr++; } } } else if (b.Fnt) { b.Fnt->Transparent(Font->Transparent()); b.Fnt->Back(Font->Back()); b.Fnt->_Draw(pDC, 0, 0, b.Str, b.Bytes, NULL, f); } else LAssert(0); cairo_translate(cr, Bx, 0); } cairo_restore(cr); #elif defined MAC && !defined(LGI_SDL) int Ox = 0, Oy = 0; int px = fx >> FShift; int py = fy >> FShift; LRect rc; if (frc) rc.Set( frc->x1 >> FShift, frc->y1 >> FShift, frc->x2 >> FShift, frc->y2 >> FShift); if (pDC && !pDC->IsScreen()) pDC->GetOrigin(Ox, Oy); if (pDC && !Font->Transparent()) { LColour Old = pDC->Colour(Font->Back()); if (frc) { pDC->Rectangle(&rc); } else { LRect a(px, py, px + x - 1, py + y - 1); pDC->Rectangle(&a); } pDC->Colour(Old); } if (Hnd && pDC && StrWords > 0) { OsPainter dc = pDC->Handle(); #if USE_CORETEXT int y = (pDC->Y() - py + Oy); CGContextSaveGState(dc); pDC->Colour(Font->Fore()); if (pDC->IsScreen()) { if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } } CGFloat Tx = (CGFloat)fx / FScale - Ox; CGFloat Ty = (CGFloat)y - Font->Ascent(); CGContextSetTextPosition(dc, Tx, Ty); CTLineDraw(Hnd, dc); CGContextRestoreGState(dc); #else ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) { printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } else { // Set style attr ATSURGBAlphaColor c; LColour Fore = Font->Fore(); c.red = (double) Fore.r() / 255.0; c.green = (double) Fore.g() / 255.0; c.blue = (double) Fore.b() / 255.0; c.alpha = 1.0; ATSUAttributeTag Tags[] = {kATSURGBAlphaColorTag}; ATSUAttributeValuePtr Values[] = {&c}; ByteCount Lengths[] = {sizeof(c)}; e = ATSUSetAttributes( Font->Handle(), CountOf(Tags), Tags, Lengths, Values); if (e) { printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e); } else { int y = (pDC->Y() - py + Oy); if (pDC->IsScreen()) { CGContextSaveGState(dc); if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent); CGContextRestoreGState(dc); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent); if (frc) CGContextRestoreGState(dc); } if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a); DeleteArray(a); } } } #endif } #endif } diff --git a/src/common/Gdc2/Font/Font.cpp b/src/common/Gdc2/Font/Font.cpp --- a/src/common/Gdc2/Font/Font.cpp +++ b/src/common/Gdc2/Font/Font.cpp @@ -1,1406 +1,1406 @@ /*hdr ** FILE: LFont.cpp ** AUTHOR: Matthew Allen ** DATE: 5/5/97 ** DESCRIPTION: Gdc2 Font Support ** ** Copyright (C) 1997-2002, Matthew Allen ** fret@memecode.com */ #ifndef WIN32 #define _WIN32_WINNT 0x500 #endif ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/DisplayString.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Css.h" #include "FontPriv.h" #ifdef FontChange #undef FontChange #endif ////////////////////////////////////////////////////////////////////// // Helpers #if defined(LGI_SDL) class FreetypeLib { FT_Library lib; FT_Error err; public: FreetypeLib() { err = FT_Init_FreeType(&lib); if (err) { LAssert(0); } } ~FreetypeLib() { if (!err) { FT_Done_FreeType(lib); } } FT_Library Handle() { return lib; } LString GetVersion() { FT_Int amajor = 0, aminor = 0, apatch = 0; FT_Library_Version(lib, &amajor, &aminor, &apatch); LString s; s.Printf("%i.%i.%i", amajor, aminor, apatch); return s; } } Freetype2; LString GetFreetypeLibraryVersion() { return Freetype2.GetVersion(); } #elif defined(WIN32) #ifndef __GNUC__ #include #endif int WinPointToHeight(int Pt, HDC hDC) { int Ht = 0; HWND hDestktop = NULL; if (!hDC) hDC = GetDC(hDestktop = GetDesktopWindow()); if (hDC) Ht = -MulDiv(Pt, GetDeviceCaps(hDC, LOGPIXELSY), 72); if (hDestktop) ReleaseDC(hDestktop, hDC); return Ht; } int WinHeightToPoint(int Ht, HDC hDC) { int Pt = 0; HWND hDestktop = NULL; if (!hDC) hDC = GetDC(hDestktop = GetDesktopWindow()); if (hDC) Pt = -MulDiv(Ht, 72, GetDeviceCaps(hDC, LOGPIXELSY)); if (hDestktop) ReleaseDC(hDestktop, hDC); return Pt; } #elif defined(__GTK_H__) #include #elif USE_CORETEXT // CTFontCreateUIFontForLanguage // #include #include #endif //////////////////////////////////////////////////////////////////// #ifdef WINDOWS LAutoPtr LFontPrivate::Gdi32; #endif LFont::LFont(const char *face, LCss::Len size) { d = new LFontPrivate; if (face && size.IsValid()) { Create(face, size); } } LFont::LFont(OsFont Handle) { d = new LFontPrivate; LFontType Type; if (Type.GetFromRef(Handle)) { Create(&Type); } } LFont::LFont(LFontType &Type) { d = new LFontPrivate; Create(&Type); } LFont::LFont(LFont &Fnt) { d = new LFontPrivate; *this = Fnt; } LFont::~LFont() { LAssert(d->WarnOnDelete == false); Destroy(); DeleteObj(d); } bool LFont::CreateFromCss(const char *Css) { if (!Css) return false; LCss c; c.Parse(Css); return CreateFromCss(&c); } bool LFont::CreateFromCss(LCss *Css) { if (!Css) return false; LCss::StringsDef Fam = Css->FontFamily(); if (Fam.Length()) Face(Fam[0]); else Face(LSysFont->Face()); LCss::Len Sz = Css->FontSize(); switch (Sz.Type) { case LCss::SizeSmaller: Size(LCss::Len(LCss::LenPt, (float)LSysFont->PointSize()-1)); break; case LCss::SizeLarger: Size(LCss::Len(LCss::LenPt, (float)LSysFont->PointSize()+1)); break; case LCss::LenInherit: Size(LSysFont->Size()); break; default: Size(Sz); break; } LCss::FontWeightType w = Css->FontWeight(); if (w == LCss::FontWeightBold) Bold(true); LCss::FontStyleType s = Css->FontStyle(); if (s == LCss::FontStyleItalic) Italic(true); LCss::TextDecorType dec = Css->TextDecoration(); if (dec == LCss::TextDecorUnderline) Underline(true); return Create(); } LString LFont::FontToCss() { LCss c; c.FontFamily(Face()); c.FontSize(Size()); if (Bold()) c.FontWeight(LCss::FontWeightBold); if (Italic()) c.FontStyle(LCss::FontStyleItalic); auto aStr = c.ToString(); return aStr.Get(); } void LFont::WarnOnDelete(bool w) { d->WarnOnDelete = w; } bool LFont::Destroy() { bool Status = true; if (d->hFont) { #if LGI_SDL FT_Done_Face(d->hFont); #elif defined(WIN32) DeleteObject(d->hFont); #elif defined __GTK_H__ if (d->PangoCtx) { g_object_unref(d->PangoCtx); d->PangoCtx = NULL; } Gtk::pango_font_description_free(d->hFont); #elif defined MAC #if USE_CORETEXT CFRelease(d->hFont); #else ATSUDisposeStyle(d->hFont); #endif #elif defined(HAIKU) DeleteObj(d->hFont); #else LAssert(0); #endif d->hFont = 0; } DeleteArray(d->GlyphMap); return Status; } #if USE_CORETEXT CFDictionaryRef LFont::GetAttributes() { return d->Attributes; } #endif uchar *LFont::GetGlyphMap() { return d->GlyphMap; } bool LFont::GetOwnerUnderline() { return d->OwnerUnderline; } void LFont::_OnPropChange(bool FontChange) { if (FontChange) { Destroy(); } } OsFont LFont::Handle() { return d->hFont; } int LFont::GetHeight() { if (!d->hFont) { Create(); } // I've decided for the moment to allow zero pt fonts to make a HTML test case render correctly. // LAssert(d->Height != 0); return d->Height; } bool LFont::IsValid() { bool Status = false; // recreate font #ifdef WIN32 if (!d->hFont) { Status = Create(Face(), Size()); } #else if (d->Dirty) { Status = Create(Face(), Size()); d->Dirty = false; } #endif else { Status = true; } return Status; } #ifdef WIN32 #define ENCODING_TABLE_SIZE 8 class type_4_cmap { public: uint16 format; uint16 length; uint16 language; uint16 seg_count_x_2; uint16 search_range; uint16 entry_selector; uint16 range_shift; // uint16 reserved; uint16 arrays[1]; uint16 SegCount() { return seg_count_x_2 / 2; } uint16 *GetIdRangeOffset() { return arrays + 1 + (SegCount()*3); } uint16 *GetStartCount() { return arrays + 1 + SegCount(); } uint16 *GetEndCount() { /* Apparently the reseved spot is not reserved for the end_count array!? */ return arrays; } }; class cmap_encoding_subtable { public: uint16 platform_id; uint16 encoding_id; uint32_t offset; }; #define INT16_SWAP(i) ( ((i)>>8) | (((i)&0xff)<<8) ) #define INT32_SWAP(i) ( ( ((i)&0x000000ff) << 24) | \ ( ((i)&0x0000ff00) << 8 ) | \ ( ((i)&0x00ff0000) >> 8 ) | \ ( ((i)&0xff000000) >> 24) ) #define MAKE_TT_TABLE_NAME(c1, c2, c3, c4) \ (((uint32_t)c4) << 24 | ((uint32_t)c3) << 16 | ((uint32_t)c2) << 8 | ((uint32_t)c1)) #define CMAP (MAKE_TT_TABLE_NAME('c','m','a','p')) #define CMAP_HEADER_SIZE 4 #define APPLE_UNICODE_PLATFORM_ID 0 #define MACINTOSH_PLATFORM_ID 1 #define ISO_PLATFORM_ID 2 #define MICROSOFT_PLATFORM_ID 3 #define SYMBOL_ENCODING_ID 0 #define UNICODE_ENCODING_ID 1 #define UCS4_ENCODING_ID 10 type_4_cmap *GetUnicodeTable(HFONT hFont, uint16_t &Length) { bool Status = false; type_4_cmap *Table = 0; HDC hDC = GetDC(0); if (hDC) { HFONT Old = (HFONT)SelectObject(hDC, hFont); uint16_t Tables = 0; uint32_t Offset = 0; // Get The number of encoding tables, at offset 2 auto Res = GetFontData(hDC, CMAP, 2, &Tables, sizeof(uint16_t)); if (Res == sizeof (uint16_t)) { Tables = INT16_SWAP(Tables); cmap_encoding_subtable *Sub = (cmap_encoding_subtable*)malloc(ENCODING_TABLE_SIZE*Tables); if (Sub) { Res = GetFontData(hDC, CMAP, CMAP_HEADER_SIZE, Sub, ENCODING_TABLE_SIZE*Tables); if (Res == ENCODING_TABLE_SIZE*Tables) { for (int i = 0; i < Tables; i++) { Sub[i].platform_id = INT16_SWAP(Sub[i].platform_id); Sub[i].encoding_id = INT16_SWAP(Sub[i].encoding_id); Sub[i].offset = INT32_SWAP(Sub[i].offset); if (Sub[i].platform_id == MICROSOFT_PLATFORM_ID && Sub[i].encoding_id == UNICODE_ENCODING_ID) { Offset = Sub[i].offset; } } } free(Sub); } } if (Offset) { Length = 0; uint Res = GetFontData(hDC, CMAP, Offset + 2, &Length, sizeof(uint16_t)); if (Res == sizeof(uint16)) { Length = INT16_SWAP(Length); Table = (type_4_cmap*)malloc(Length); Res = GetFontData(hDC, CMAP, Offset, Table, Length); if (Res == Length) { Table->format = INT16_SWAP(Table->format); Table->length = INT16_SWAP(Table->length); Table->language = INT16_SWAP(Table->language); Table->seg_count_x_2 = INT16_SWAP(Table->seg_count_x_2); Table->search_range = INT16_SWAP(Table->search_range); Table->entry_selector = INT16_SWAP(Table->entry_selector); Table->range_shift = INT16_SWAP(Table->range_shift); if (Table->format == 4) { uint16 *tbl_end = (uint16 *)((char *)Table + Length); uint16 *tbl = Table->arrays; while (tbl < tbl_end) { *tbl = INT16_SWAP(*tbl); tbl++; } Status = true; } } } } SelectObject(hDC, Old); ReleaseDC(0, hDC); } if (!Status) { free(Table); Table = 0; } return Table; } #endif LSurface *LFont::GetSurface() { return d->pSurface; } bool LFont::Create(const char *face, LCss::Len size, LSurface *pSurface) { bool FaceChanging = false; bool SizeChanging = false; bool ValidInitFaceSize = ValidStr(Face()) && Size().IsValid(); if (face) { if (!Face() || strcmp(Face(), face) != 0) { FaceChanging = true; } Face(face); } if (size.IsValid()) { SizeChanging = LTypeFace::d->_Size != size; LTypeFace::d->_Size = size; } if ((SizeChanging || FaceChanging) && this == LSysFont && ValidInitFaceSize) { LgiTrace("Warning: Changing sysfont definition.\n"); } if (this == LSysFont) { printf("Setting sysfont up '%s' %i\n", Face(), PointSize()); } #if LGI_SDL LString FaceName; #if defined(WIN32) const char *Ext = "ttf"; LString FontPath = "c:\\Windows\\Fonts"; #elif defined(LINUX) const char *Ext = "ttf"; LString FontPath = "/usr/share/fonts/truetype"; #elif defined(MAC) const char *Ext = "ttc"; LString FontPath = "/System/Library/Fonts"; #else #error "Put your font path here" #endif LFile::Path p = FontPath.Get(); FaceName.Printf("%s.%s", Face(), Ext); p += FaceName; LString Full = p.GetFull(); if (!LFileExists(Full)) { LArray Files; LArray Extensions; LString Pattern; Pattern.Printf("*.%s", Ext); Extensions.Add(Pattern.Get()); LRecursiveFileSearch(FontPath, &Extensions, &Files, NULL, NULL, NULL, NULL); char *Match = NULL; for (unsigned i=0; ihFont); if (error) { LgiTrace("%s:%i - FT_New_Face failed with %i\n", _FL, error); } else { - int Dpi = LScreenDpi(); + auto Dpi = LScreenDpi(); int PtSize = PointSize(); - int PxSize = (int) (PtSize * Dpi / 72.0); + int PxSize = (int) (PtSize * Dpi.x / 72.0); error = FT_Set_Char_Size( d->hFont, /* handle to face object */ 0, /* char_width in 1/64th of points */ PtSize*64, /* char_height in 1/64th of points */ Dpi, /* horizontal device resolution */ Dpi); if (error) { LgiTrace("%s:%i - FT_Set_Char_Size failed with %i\n", _FL, error); } d->Height = (int) (ceil((double)d->hFont->height * PxSize / d->hFont->units_per_EM) + 0.0001); LTypeFace::d->_Ascent = (double)d->hFont->ascender * PxSize / d->hFont->units_per_EM; LAssert(d->Height > LTypeFace::d->_Ascent); LTypeFace::d->_Descent = d->Height - LTypeFace::d->_Ascent; return true; } #elif WINNATIVE if (d->hFont) { DeleteObject(d->hFont); d->hFont = 0; } d->pSurface = pSurface; HDC hDC = pSurface ? pSurface->Handle() : GetDC(0); auto Sz = Size(); int Win32Height = 0; if (Sz.Type == LCss::LenPt) Win32Height = WinPointToHeight((int)Sz.Value, hDC); else if (Sz.Type == LCss::LenPx) Win32Height = (int)(Sz.Value * 1.2); else LAssert(!"What now?"); LTypeFace::d->IsSymbol = LTypeFace::d->_Face && ( stristr(LTypeFace::d->_Face, "wingdings") || stristr(LTypeFace::d->_Face, "symbol") ); int Cs; if (LTypeFace::d->IsSymbol) Cs = SYMBOL_CHARSET; else Cs = ANSI_CHARSET; d->OwnerUnderline = Face() && stricmp(Face(), "Courier New") == 0 && Size().Type == LCss::LenPt && (PointSize() == 8 || PointSize() == 9) && LTypeFace::d->_Underline; LAutoWString wFace(Utf8ToWide(LTypeFace::d->_Face)); if (Win32Height) d->hFont = ::CreateFont(Win32Height, 0, 0, 0, LTypeFace::d->_Weight, LTypeFace::d->_Italic, d->OwnerUnderline ? false : LTypeFace::d->_Underline, false, Cs, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, LTypeFace::d->_Quality, FF_DONTCARE, wFace); if (d->hFont) { HFONT hFnt = (HFONT) SelectObject(hDC, d->hFont); TEXTMETRIC tm; ZeroObj(tm); if (GetTextMetrics(hDC, &tm)) { d->Height = tm.tmHeight; LTypeFace::d->_Ascent = tm.tmAscent; LTypeFace::d->_Descent = tm.tmDescent; LTypeFace::d->_Leading = tm.tmInternalLeading; } else { SIZE Size = {0, 0}; GetTextExtentPoint32(hDC, L"Ag", 2, &Size); d->Height = Size.cy; } uint Bytes = (MAX_UNICODE + 1) >> 3; if (!d->GlyphMap) { d->GlyphMap = new uchar[Bytes]; } if (d->GlyphMap) { memset(d->GlyphMap, 0, Bytes); LArray OsVer; int OsType = LGetOs(&OsVer); if (OsType == LGI_OS_WIN9X || OsVer[0] < 5) // GetFontUnicodeRanges is supported on >= Win2k { bool HideUnihan = false; LAssert(sizeof(type_4_cmap)==16); uint16 Length = 0; type_4_cmap *t = GetUnicodeTable(Handle(), Length); if (t) { uint16 SegCount = t->seg_count_x_2 / 2; uint16 *EndCount = t->GetEndCount(); uint16 *StartCount = t->GetStartCount(); uint16 *IdRangeOffset = t->GetIdRangeOffset(); for (int i = 0; i= 0x3400 && u <= 0x9FAF) { // APPROXIMATE } else { // EXACT } if ((u >> 3) < Bytes) { d->GlyphMap[u>>3] |= 1 << (u & 7); } } } else { uint16 *End = (uint16*) (((char*)t)+Length); ssize_t IdBytes = End - IdRangeOffset; for (uint u = StartCount[i]; u <= EndCount[i] && IdRangeOffset[i] != 0xffff; u++) { uint id = *(IdRangeOffset[i]/2 + (u - StartCount[i]) + &IdRangeOffset[i]); if (id) { if (HideUnihan && u >= 0x3400 && u <= 0x9FAF) { // APPROXIMATE } else { // EXACT } if ((u >> 3) < Bytes) { d->GlyphMap[u>>3] |= 1 << (u & 7); } } } } } } else { // not a TTF? assume that it can handle 00-ff in the current ansi cp /* char *Cp = LAnsiToLgiCp(); for (int i=0; i<=0x7f; i++) { char16 u; uchar c = i; void *In = &c; int Size = sizeof(c); LBufConvertCp(&u, "ucs-2", sizeof(u), In, Cp, Size); if ((u >> 3) < Bytes) { GlyphMap[u>>3] |= 1 << (u & 7); } } */ } } else { typedef DWORD (WINAPI *Proc_GetFontUnicodeRanges)(HDC, LPGLYPHSET); if (!d->Gdi32) d->Gdi32.Reset(new LLibrary("Gdi32")); if (d->Gdi32) { Proc_GetFontUnicodeRanges GetFontUnicodeRanges = (Proc_GetFontUnicodeRanges)d->Gdi32->GetAddress("GetFontUnicodeRanges"); if (GetFontUnicodeRanges) { DWORD BufSize = GetFontUnicodeRanges(hDC, 0); LPGLYPHSET Set = (LPGLYPHSET) new char[BufSize]; if (Set && GetFontUnicodeRanges(hDC, Set) > 0) { for (DWORD i=0; icRanges; i++) { WCRANGE *Range = Set->ranges + i; for (int n=0; ncGlyphs; n++) { DWORD u = Range->wcLow + n; if (u >> 3 < Bytes) { d->GlyphMap[u>>3] |= 1 << (u & 7); } } } } DeleteArray((char*&)Set); } } if (LTypeFace::d->IsSymbol) { // Lies! It's all Lies! Symbol doesn't support non-breaking space. int u = 0xa0; d->GlyphMap[u >> 3] &= ~(1 << (u & 7)); } } if (d->GlyphMap) { memset(d->GlyphMap, 0xff, 128/8); } } SelectObject(hDC, hFnt); } if (!pSurface) ReleaseDC(0, hDC); return (d->hFont != 0); #elif defined __GTK_H__ Destroy(); auto Dpi = pSurface ? pSurface->GetDpi() : LScreenDpi(); float DpiScale = (float)Dpi.x / 96.0f; d->hFont = Gtk::pango_font_description_new(); if (!d->hFont) printf("%s:%i - pango_font_description_new failed: Face='%s' Size=%i Bold=%i Italic=%i\n", _FL, Face(), PointSize(), Bold(), Italic()); else if (!ValidStr(Face())) printf("%s:%i - No font face.\n", _FL); else if (!Size().IsValid()) printf("%s:%i - Invalid size.\n", _FL); else { auto Sz = Size(); LString sFace = Face(); Gtk::pango_font_description_set_family(d->hFont, sFace); if (Sz.Type == LCss::LenPt) Gtk::pango_font_description_set_size(d->hFont, Sz.Value * DpiScale * PANGO_SCALE); else if (Sz.Type == LCss::LenPx) Gtk::pango_font_description_set_absolute_size(d->hFont, Sz.Value * DpiScale * PANGO_SCALE); else { LAssert(0); return false; } if (Bold()) Gtk::pango_font_description_set_weight(d->hFont, Gtk::PANGO_WEIGHT_BOLD); // Get metrics for this font... Gtk::GtkPrintContext *PrintCtx = pSurface ? pSurface->GetPrintContext() : NULL; Gtk::PangoContext *SysCtx = LFontSystem::Inst()->GetContext(); if (PrintCtx) d->PangoCtx = gtk_print_context_create_pango_context(PrintCtx); auto EffectiveCtx = d->PangoCtx ? d->PangoCtx : SysCtx; Gtk::PangoFontMetrics *m = Gtk::pango_context_get_metrics(d->PangoCtx ? d->PangoCtx : SysCtx, d->hFont, 0); if (!m) printf("pango_font_get_metrics failed.\n"); else { LTypeFace::d->_Ascent = (double)Gtk::pango_font_metrics_get_ascent(m) / PANGO_SCALE; LTypeFace::d->_Descent = (double)Gtk::pango_font_metrics_get_descent(m) / PANGO_SCALE; d->Height = ceil(LTypeFace::d->_Ascent + LTypeFace::d->_Descent + 1/*hack the underscores to work*/); #if 0 if (PrintCtx) { LgiTrace("LFont::Create %s,%f (%i,%i,%i) (%.1f,%.1f,%i)\n", Gtk::pango_font_description_get_family(d->hFont), (double)Gtk::pango_font_description_get_size(d->hFont) / PANGO_SCALE, Gtk::pango_font_metrics_get_ascent(m), Gtk::pango_font_metrics_get_descent(m), PANGO_SCALE, LTypeFace::d->_Ascent, LTypeFace::d->_Descent, d->Height); } #endif Gtk::pango_font_metrics_unref(m); #if 1 Gtk::PangoFont *fnt = pango_font_map_load_font(Gtk::pango_cairo_font_map_get_default(), EffectiveCtx, d->hFont); if (fnt) { Gtk::PangoCoverage *c = Gtk::pango_font_get_coverage(fnt, Gtk::pango_language_get_default()); if (c) { uint Bytes = (MAX_UNICODE + 1) >> 3; if ((d->GlyphMap = new uchar[Bytes])) { memset(d->GlyphMap, 0, Bytes); int Bits = Bytes << 3; for (int i=0; iGlyphMap[i>>3] |= 1 << (i & 0x7); } } Gtk::pango_coverage_unref(c); } g_object_unref(fnt); } #endif return true; } } #elif defined MAC Destroy(); if (this == LSysFont) LgiTrace("%s:%i - WARNING: you are re-creating the system font... this is bad!!!!\n", _FL); if (Face()) { if (d->Attributes) CFRelease(d->Attributes); auto Sz = Size(); LString sFamily(Face()); LString sBold("Bold"); LArray key; LArray values; key.Add(kCTFontFamilyNameAttribute); values.Add(sFamily.CreateStringRef()); if (!values[0]) return false; if (Bold()) { key.Add(kCTFontStyleNameAttribute); values.Add(sBold.CreateStringRef()); } CFDictionaryRef FontAttrD = CFDictionaryCreate( kCFAllocatorDefault, (const void**)key.AddressOf(), (const void**)values.AddressOf(), key.Length(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (FontAttrD) { CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(FontAttrD); if (descriptor) { float PtSz = 0.0; if (Sz.Type == LCss::LenPt) PtSz = Sz.Value; else if (Sz.Type == LCss::LenPx) // This seems to give fonts that are too small: // PtSz = Sz.Value * 72.0f / LScreenDpi(); PtSz = Sz.Value; else LAssert(!"Impl me."); d->hFont = CTFontCreateWithFontDescriptor(descriptor, PtSz, NULL); CFRelease(descriptor); } else LAssert(0); CFRelease(FontAttrD); } else { LAssert(0); return false; } for (size_t i=0; ihFont) { LTypeFace::d->_Ascent = CTFontGetAscent(d->hFont); LTypeFace::d->_Descent = CTFontGetDescent(d->hFont); LTypeFace::d->_Leading = CTFontGetLeading(d->hFont); d->Height = ceil(LTypeFace::d->_Ascent + LTypeFace::d->_Descent + LTypeFace::d->_Leading); #if 0 if (Sz.Type == LCss::LenPx) { LStringPipe p; Sz.ToString(p); LgiTrace("%s:%i - LFont::Create(%s,%s) = %f,%f,%f (%i)\n", _FL, Face(), p.NewGStr().Get(), LTypeFace::d->_Ascent, LTypeFace::d->_Descent, LTypeFace::d->_Leading, GetHeight()); } #endif key.Add(kCTFontAttributeName); values.Add(d->hFont); key.Add(kCTForegroundColorFromContextAttributeName); values.Add(kCFBooleanTrue); if (Underline()) { key.Add(kCTUnderlineStyleAttributeName); CTUnderlineStyle u = kCTUnderlineStyleSingle; values.Add(CFNumberCreate(NULL, kCFNumberSInt32Type, &u)); } d->Attributes = CFDictionaryCreate( kCFAllocatorDefault, (const void**)key.AddressOf(), (const void**)values.AddressOf(), key.Length(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); return true; } for (int i=2; ihFont = new BFont(); d->hFont->SetSize(PointSize()); status_t r = d->hFont->SetFamilyAndStyle(Face(), "Regular"); // printf("SetFamilyAndFace(%s)=%i\n", Face(), r); if (r == B_OK) { font_height height; d->hFont->GetHeight(&height); d->Height = height.ascent + height.descent + height.leading; LTypeFace::d->_Ascent = height.ascent; LTypeFace::d->_Descent = height.descent; LTypeFace::d->_Leading = height.leading; return true; } #endif return false; } char16 *LFont::_ToUnicode(char *In, ssize_t &Len) { char16 *WStr = 0; if (In && Len > 0) { WStr = (char16*)LNewConvertCp(LGI_WideCharset, In, LTypeFace::d->_CodePage, Len); if (WStr) { ssize_t l = StrlenW(WStr); if (l < Len) { Len = l; } } } return WStr; } #if defined WINNATIVE bool LFont::Create(LFontType *LogFont, LSurface *pSurface) { if (d->hFont) { DeleteObject(d->hFont); d->hFont = 0; } if (LogFont) { // set props PointSize(WinHeightToPoint(LogFont->Info.lfHeight)); LString uFace = LogFont->Info.lfFaceName; if (ValidStr(uFace)) { Face(uFace); Quality(LogFont->Info.lfQuality); Bold(LogFont->Info.lfWeight >= FW_BOLD); Italic(LogFont->Info.lfItalic != FALSE); Underline(LogFont->Info.lfUnderline != FALSE); // create the handle Create(0, 0, pSurface); } } return (d->hFont != 0); } #else bool LFont::Create(LFontType *LogFont, LSurface *pSurface) { if (LogFont) { LCss::Len Sz(LCss::LenPt, (float)LogFont->GetPointSize()); return Create(LogFont->GetFace(), Sz, pSurface); } return false; } #endif LFont &LFont::operator =(const LFont &f) { Face(f.Face()); Size(f.Size()); TabSize(f.TabSize()); Quality(f.Quality()); Fore(f.Fore()); Back(f.Back()); SetWeight(f.GetWeight()); Italic(f.Italic()); Underline(f.Underline()); Transparent(f.Transparent()); return *this; } char16 WinSymbolToUnicode[256] = { /* 0 to 15 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16 to 31 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32 to 47 */ 32, 9998, 9986, 9985, 0, 0, 0, 0, 9742, 9990, 9993, 9993, 0, 0, 0, 0, /* 48 to 63 */ 0, 0, 0, 0, 0, 0, 8987, 9000, 0, 0, 0, 0, 0, 0, 9991, 9997, /* 64 to 79 */ 9997, 9996, 0, 0, 0, 9756, 9758, 9757, 9759, 0, 9786, 9786, 9785, 0, 9760, 0, /* 80 to 95 */ 0, 9992, 9788, 0, 10052, 10014, 10014, 10013, 10016, 10017, 9770, 9775, 2384, 9784, 9800, 9801, /* 96 to 111 */ 9802, 9803, 9804, 9805, 9806, 9807, 9808, 9809, 9810, 9811, 38, 38, 9679, 10061, 9632, 9633, /* 112 to 127 */ 9633, 10065, 10066, 9674, 9674, 9670, 10070, 9670, 8999, 9043, 8984, 10048, 10047, 10077, 10078, 0, /* 128 to 143 */ 9450, 9312, 9313, 9314, 9315, 9316, 9317, 9318, 9319, 9320, 9321, 0, 10102, 10103, 10104, 10105, /* 144 to 159 */ 10106, 10107, 10108, 10109, 10110, 10111, 10087, 9753, 9753, 10087, 10087, 9753, 9753, 10087, 8226, 9679, /* 160 to 175 */ 160, 9675, 9675, 9675, 9737, 9737, 10061, 9642, 9633, 0, 10022, 9733, 10038, 10039, 10040, 10037, /* 176 to 191 */ 0, 0, 10023, 0, 65533, 10026, 10032, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 192 to 207 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10086, 10086, 10086, /* 208 to 223 */ 10086, 10086, 10086, 10086, 10086, 9003, 8998, 0, 10146, 0, 0, 0, 10162, 0, 0, 0, /* 224 to 239 */ 0, 0, 0, 0, 0, 0, 0, 0, 10132, 0, 0, 0, 0, 0, 0, 8678, /* 240 to 255 */ 8680, 8679, 8681, 8660, 8661, 8662, 8663, 8665, 8664, 0, 0, 10007, 10003, 9746, 9745, 0, }; LAutoString LFont::ConvertToUnicode(char16 *Input, ssize_t Len) { LAutoString a; if (LTypeFace::d->IsSymbol) { // F***ing wingdings. if (Input) { LStringPipe p(256); if (Len < 0) Len = StrlenW(Input); char16 *c = Input, *e = Input + Len; while (c < e) { if (*c < 256 && WinSymbolToUnicode[*c]) { p.Write(WinSymbolToUnicode + *c, sizeof(char16)); } else { p.Write(c, sizeof(char16)); } c++; } LAutoWString w(p.NewStrW()); a.Reset(WideToUtf8(w)); } } else { // Normal utf-8 text... a.Reset(WideToUtf8(Input, Len)); } return a; } #if WINNATIVE #include "lgi/common/FontSelect.h" #include "lgi/common/GdiLeak.h" ///////////////////////////////////////////////////////////////////////////// void LFont::_Measure(int &x, int &y, OsChar *Str, int Len) { if (!Handle()) { x = 0; y = 0; return; } HDC hDC = GetSurface() ? GetSurface()->Handle() : CreateCompatibleDC(0); HFONT hOldFont = (HFONT) SelectObject(hDC, Handle()); SIZE Size; if (GetTextExtentPoint32W(hDC, Str, Len, &Size)) { x = Size.cx; y = Size.cy; } else { x = y = 0; } SelectObject(hDC, hOldFont); if (!GetSurface()) DeleteDC(hDC); } int LFont::_CharAt(int x, OsChar *Str, int Len, LPxToIndexType Type) { if (!Handle()) return -1; INT Fit = 0; HDC hDC = CreateCompatibleDC(GetSurface()?GetSurface()->Handle():0); HFONT hOldFont = (HFONT) SelectObject(hDC, Handle()); if (hOldFont) { SIZE Size = {0, 0}; if (!GetTextExtentExPointW(hDC, Str, Len, x, &Fit, 0, &Size)) { DWORD e = GetLastError(); Fit = -1; } else if (Type == LgiNearest && Fit < Len) { // Check if the next char is nearer... SIZE Prev, Next; if (GetTextExtentPoint32W(hDC, Str, Fit, &Prev) && GetTextExtentPoint32W(hDC, Str, Fit + 1, &Next)) { int PrevDist = abs(Prev.cx - x); int NextDist = abs(Next.cx - x); if (NextDist <= PrevDist) Fit++; } } SelectObject(hDC, hOldFont); } else { DWORD e = GetLastError(); Fit = -1; LAssert(0); } DeleteDC(hDC); return Fit; } void LFont::_Draw(LSurface *pDC, int x, int y, OsChar *Str, int Len, LRect *r, LColour &fore) { if (!Handle()) return; HDC hDC = pDC->StartDC(); HFONT hOldFont = (HFONT) SelectObject(hDC, Handle()); if (hOldFont) { bool IsTransparent = Transparent(); SetTextColor(hDC, fore.GetNative()); if (!IsTransparent) SetBkColor(hDC, Back().GetNative()); SetBkMode(hDC, IsTransparent ? TRANSPARENT : OPAQUE); SIZE Size; if ((!IsTransparent && !r) || (GetOwnerUnderline())) { GetTextExtentPoint32W(hDC, Str, Len, &Size); } if (Transparent() && !r) { TextOutW(hDC, x, y, Str, Len); } else { RECT rc; if (r) rc = *r; else { rc.left = x; rc.top = y; rc.right = x + Size.cx; rc.top = y + Size.cy; } /* Debugging stuff... POINT _ori; auto res = GetWindowOrgEx(hDC, &_ori); RECT _rc; int res2 = GetClipBox(hDC, &_rc); auto Addr = (*pDC)[y - _ori.y + 6] + ((x - _ori.x + 4) * pDC->GetBits() / 8); */ ExtTextOutW(hDC, x, y, ETO_CLIPPED | (Transparent()?0:ETO_OPAQUE), &rc, Str, Len, 0); } if (GetOwnerUnderline()) { pDC->Colour(fore); pDC->Line(x, y + GetHeight() - 1, x + Size.cx + 1, y + GetHeight() - 1); } HANDLE h = SelectObject(hDC, hOldFont); } else { DWORD e = GetLastError(); LAssert(0); } pDC->EndDC(); } #elif defined(HAIKU) void LFont::_Measure(int &x, int &y, OsChar *Str, int Len) { printf("%s:%i - _Measure not impl.\n", _FL); } int LFont::_CharAt(int x, OsChar *Str, int Len, LPxToIndexType Type) { if (!d->hFont) return -1; BString s(Str, Len); d->hFont->TruncateString(&s, B_TRUNCATE_BEGINNING, x); return s.CountChars(); } void LFont::_Draw(LSurface *pDC, int x, int y, OsChar *Str, int Len, LRect *r, LColour &fore) { printf("%s:%i - _Draw not impl.\n", _FL); } #else void LFont::_Measure(int &x, int &y, OsChar *Str, int Len) { } int LFont::_CharAt(int x, OsChar *Str, int Len, LPxToIndexType Type) { return -1; } void LFont::_Draw(LSurface *pDC, int x, int y, OsChar *Str, int Len, LRect *r, LColour &fore) { } #endif diff --git a/src/common/General/DateTime.cpp b/src/common/General/DateTime.cpp --- a/src/common/General/DateTime.cpp +++ b/src/common/General/DateTime.cpp @@ -1,2155 +1,2157 @@ /* ** FILE: LDateTime.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Date Time Object ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _DEFAULT_SOURCE #include #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" #if !defined(WINDOWS) constexpr const char *LDateTime::WeekdaysShort[]; constexpr const char *LDateTime::WeekdaysLong[]; constexpr const char *LDateTime::MonthsShort[]; constexpr const char *LDateTime::MonthsLong[]; #define MIN_YEAR 1800 #endif ////////////////////////////////////////////////////////////////////////////// uint16 LDateTime::DefaultFormat = GDTF_DEFAULT; char LDateTime::DefaultSeparator = '/'; uint16 LDateTime::GetDefaultFormat() { if (DefaultFormat == GDTF_DEFAULT) { #ifdef WIN32 TCHAR s[80] = _T("1"); GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE, s, CountOf(s)); switch (_tstoi(s)) { case 0: DefaultFormat = GDTF_MONTH_DAY_YEAR; break; default: case 1: DefaultFormat = GDTF_DAY_MONTH_YEAR; break; case 2: DefaultFormat = GDTF_YEAR_MONTH_DAY; break; } GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, s, sizeof(s)); if (_tstoi(s) == 1) { DefaultFormat |= GDTF_24HOUR; } else { DefaultFormat |= GDTF_12HOUR; } if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, s, sizeof(s))) DefaultSeparator = (char)s[0]; if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, s, sizeof(s))) { char Sep[] = { DefaultSeparator, '/', '\\', '-', '.', 0 }; LString Str = s; auto t = Str.SplitDelimit(Sep); for (int i=0; i= low && (v) <= high) bool LDateTime::IsValid() const { return InRange(_Day, 1, 31) && InRange(_Year, 1600, 2100) && InRange(_Thousands, 0, 999) && InRange(_Month, 1, 12) && InRange(_Seconds, 0, 59) && InRange(_Minutes, 0, 59) && InRange(_Hours, 0, 23) && InRange(_Tz, -720, 720); } void LDateTime::SetTimeZone(int NewTz, bool ConvertTime) { if (ConvertTime && NewTz != _Tz) { // printf("SetTimeZone: %i\n", NewTz - _Tz); AddMinutes(NewTz - _Tz); } _Tz = NewTz; } int LDateTime::SystemTimeZone(bool ForceUpdate) { if (ForceUpdate || CurTz == NO_ZONE) { CurTz = 0; CurTzOff = 0; #ifdef MAC #ifdef LGI_COCOA NSTimeZone *timeZone = [NSTimeZone localTimeZone]; if (timeZone) { NSDate *Now = [NSDate date]; CurTz = (int) [timeZone secondsFromGMTForDate:Now] / 60; CurTzOff = [timeZone daylightSavingTimeOffsetForDate:Now] / 60; CurTz -= CurTzOff; } #elif defined LGI_CARBON CFTimeZoneRef tz = CFTimeZoneCopySystem(); CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); Boolean dst = CFTimeZoneIsDaylightSavingTime(tz, now); if (dst) { CFAbsoluteTime next = CFTimeZoneGetNextDaylightSavingTimeTransition(tz, now); CurTz = CFTimeZoneGetSecondsFromGMT(tz, next + 100) / 60; } else { CurTz = CFTimeZoneGetSecondsFromGMT(tz, now) / 60; } CurTzOff = CFTimeZoneGetDaylightSavingTimeOffset(tz, now) / 60; CFRelease(tz); #endif #elif defined(WIN32) timeb tbTime; ftime(&tbTime); CurTz = -tbTime.timezone; TIME_ZONE_INFORMATION Tzi; if (GetTimeZoneInformation(&Tzi) == TIME_ZONE_ID_DAYLIGHT) CurTzOff = -Tzi.DaylightBias; #elif defined(LINUX) || defined(HAIKU) int six_months = (365 * 24 * 60 * 60) / 2; time_t now = 0, then = 0; time (&now); then = now - six_months; tm now_tz, then_tz; tm *t = localtime_r(&now, &now_tz); if (t) { localtime_r(&then, &then_tz); CurTz = now_tz.tm_gmtoff / 60; if (now_tz.tm_isdst) { CurTzOff = (now_tz.tm_gmtoff - then_tz.tm_gmtoff) / 60; CurTz = then_tz.tm_gmtoff / 60; } else // This is not DST so there is no offset right? CurTzOff = 0; // (then_tz.tm_gmtoff - now_tz.tm_gmtoff) / 60; } else return NO_ZONE; #else #error "Impl me." #endif } return CurTz + CurTzOff; } int LDateTime::SystemTimeZoneOffset() { if (CurTz == NO_ZONE) SystemTimeZone(); return CurTzOff; } #if defined WIN32 LDateTime ConvertSysTime(SYSTEMTIME &st, int year) { LDateTime n; if (st.wYear) { n.Year(st.wYear); n.Month(st.wMonth); n.Day(st.wDay); } else { n.Year(year); n.Month(st.wMonth); // Find the 'nth' matching weekday, starting from the first day in the month n.Day(1); LDateTime c = n; for (int i=0; iCompare(b); } #elif defined(LINUX) static bool ParseValue(char *s, LAutoString &var, LAutoString &val) { if (!s) return false; char *e = strchr(s, '='); if (!e) return false; *e++ = 0; var.Reset(NewStr(s)); val.Reset(NewStr(e)); *e = '='; return var != 0 && val != 0; } #endif /* Testing code... LDateTime Start, End; LArray Info; Start.Set("1/1/2010"); End.Set("31/12/2014"); LDateTime::GetDaylightSavingsInfo(Info, Start, &End); LStringPipe p; for (int i=0; i,int> { MonthHash() { for (int i=0; i &Info, LDateTime &Start, LDateTime *End) { bool Status = false; #if defined(WIN32) TIME_ZONE_INFORMATION Tzi; auto r = GetTimeZoneInformation(&Tzi); if (r > TIME_ZONE_ID_UNKNOWN) { Info.Length(0); // Find the dates for the previous year from Start. This allows // us to cover the start of the current year. LDateTime s = ConvertSysTime(Tzi.StandardDate, Start.Year() - 1); LDateTime d = ConvertSysTime(Tzi.DaylightDate, Start.Year() - 1); // Create initial Info entry, as the last change in the previous year auto *i = &Info.New(); if (s < d) { // Year is: Daylight->Standard->Daylight LDateTime tmp = d; i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } else { // Year is: Standard->Daylight->Standard LDateTime tmp = s; i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts();; } for (auto y=Start.Year(); y<=(End?End->Year():Start.Year()); y++) { if (s < d) { // Cur year, first event: end of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.StandardDate, y); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); // Cur year, second event: start of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.DaylightDate, y); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } else { // Cur year, first event: start of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.DaylightDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); // Cur year, second event: end of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.StandardDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } } Status = true; } #elif defined(MAC) LDateTime Before = Start; Before.AddMonths(-6); NSTimeZone *tz = [NSTimeZone systemTimeZone]; NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSince1970:(Before.Ts() / Second64Bit) - Offset1800]; for (int n=0; n<6; n++) { NSDate *next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; auto &i = Info.New(); i.UtcTimeStamp = ([next timeIntervalSince1970] + Offset1800) * Second64Bit; i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); #if DEBUG_DST_INFO { LDateTime dt; dt.Set(i.UtcTimeStamp); LgiTrace("%s:%i - Ts=%s Off=%i\n", _FL, dt.Get().Get(), i.Offset); } #endif [startDate release]; startDate = next; } #elif defined(LINUX) if (!Zdump.Length()) { FILE *f = popen("zdump -v /etc/localtime", "r"); if (f) { char s[256]; size_t r; LStringPipe p(1024); while ((r = fread(s, 1, sizeof(s), f)) > 0) { p.Write(s, (int)r); } fclose(f); LString ps = p.NewGStr(); Zdump = ps.Split("\n"); } } MonthHash Lut; LDateTime Prev; int PrevOff = 0; for (auto Line: Zdump) { auto l = Line.SplitDelimit(" \t"); if (l.Length() >= 16 && l[0].Equals("/etc/localtime")) { // /etc/localtime Sat Oct 3 15:59:59 2037 UTC = Sun Oct 4 01:59:59 2037 EST isdst=0 gmtoff=36000 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #if DEBUG_DST_INFO printf("DST: %s\n", Line); #endif LDateTime Utc; Utc.Year(l[5].Int()); auto Tm = l[4].SplitDelimit(":"); if (Tm.Length() != 3) { #if DEBUG_DST_INFO printf("%s:%i - Tm '%s' has wrong parts: %s\n", _FL, l[4], Line); #endif continue; } Utc.Hours(Tm[0].Int()); Utc.Minutes(Tm[1].Int()); Utc.Seconds(Tm[2].Int()); if (Utc.Minutes() < 0) { #if DEBUG_DST_INFO printf("%s:%i - Mins is zero: %s\n", _FL, l[4]); #endif continue; } int m = Lut.Find(l[2]); if (!m) { #if DEBUG_DST_INFO printf("%s:%i - Unknown month '%s'\n", _FL, l[2]); #endif continue; } Utc.Day(l[3].Int()); Utc.Month(m); LAutoString Var, Val; if (!ParseValue(l[14], Var, Val) || stricmp(Var, "isdst")) { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } if (!ParseValue(l[15], Var, Val) || stricmp(Var, "gmtoff")) { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } int Off = atoi(Val) / 60; if (Utc.Ts() == 0) continue; if (Prev.Year() && Prev < Start && Start < Utc) { // Emit initial entry for 'start' auto &inf = Info.New(); inf.UtcTimeStamp = Prev; inf.Offset = PrevOff; #if DEBUG_DST_INFO printf("Info: Start=%s %i\n", Prev.Get().Get(), inf.Offset); #endif } if (Utc > Start) { // Emit furthur entries for DST events between start and end. auto &inf = Info.New(); inf.UtcTimeStamp = Utc; inf.Offset = Off; #if DEBUG_DST_INFO printf("Info: Next=%s %i\n", Utc.Get().Get(), inf.Offset); #endif if (End && Utc > *End) { // printf("Utc after end: %s > %s\n", Utc.Get().Get(), End->Get().Get()); break; } } Prev = Utc; PrevOff = Off; } } Status = Info.Length() > 1; #else LAssert(!"Not implemented."); #endif return Status; } bool LDateTime::DstToLocal(LArray &Dst, LDateTime &dt) { if (dt.GetTimeZone()) { LAssert(!"Should be a UTC date."); return true; } // LgiTrace("DstToLocal: %s\n", dt.Get().Get()); LAssert(Dst.Length() > 1); // Needs to have at least 2 entries...? for (size_t i=0; i %s\n", (int)i, start.Get().Get(), end.Get().Get()); if (dt >= start && dt < end) { dt.SetTimeZone(a.Offset, true); return true; } } auto Last = Dst.Last(); LDateTime d; d.Set(Last.UtcTimeStamp); if (dt >= d && dt.Year() == d.Year()) { // If it's after the last DST change but in the same year... it's ok... // Just use the last offset. dt.SetTimeZone(Last.Offset, true); return true; } LAssert(!"No valid DST range for this date."); return false; } int LDateTime::DayOfWeek() const { int Index = 0; int Day = IsLeapYear() ? 29 : 28; switch (_Year / 100) { case 19: { Index = 3; break; } case 20: { Index = 2; break; } } // get year right int y = _Year % 100; int r = y % 12; Index = (Index + (y / 12) + r + (r / 4)) % 7; // get month right if (_Month % 2 == 0) { // even month if (_Month > 2) Day = _Month; } else { // odd month switch (_Month) { case 1: { Day = 31; if (IsLeapYear()) { Index = Index > 0 ? Index - 1 : Index + 6; } break; } case 11: case 3: { Day = 7; break; } case 5: { Day = 9; break; } case 7: { Day = 11; break; } case 9: { Day = 5; break; } } } // get day right int Diff = Index - (Day - _Day); while (Diff < 0) Diff += 7; return Diff % 7; } LDateTime LDateTime::Now() { LDateTime dt; dt.SetNow(); return dt; } void LDateTime::SetNow() { #ifdef WIN32 SYSTEMTIME stNow; FILETIME ftNow; GetSystemTime(&stNow); SystemTimeToFileTime(&stNow, &ftNow); uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; Set(i64); #else time_t now; time(&now); struct tm *time = localtime(&now); if (time) *this = time; #ifndef LGI_STATIC else { LgiTrace("%s:%i - Error: localtime failed, now=%u\n", _FL, now); } #endif #endif } #define Convert24HrTo12Hr(h) ( (h) == 0 ? 12 : (h) > 12 ? (h) % 12 : (h) ) #define Convert24HrToAmPm(h) ( (h) >= 12 ? "p" : "a" ) LString LDateTime::GetDate() const { char s[32]; int Ch = GetDate(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetDate(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_DATE_MASK) { case GDTF_MONTH_DAY_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%2.2i" :"%i" , _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; default: case GDTF_DAY_MONTH_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%2.2i" :"%i" , _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; case GDTF_YEAR_MONTH_DAY: Ch += sprintf_s(Str+Ch, SLen-Ch, "%i", _Year); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); break; } } return Ch; } LString LDateTime::GetTime() const { char s[32]; int Ch = GetTime(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetTime(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_TIME_MASK) { case GDTF_12HOUR: default: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i%s", Convert24HrTo12Hr(_Hours), _Minutes, _Seconds, Convert24HrToAmPm(_Hours)); break; } case GDTF_24HOUR: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i", _Hours, _Minutes, _Seconds); break; } } } return Ch; } uint64 LDateTime::Ts() const { uint64 ts = 0; Get(ts); return ts; } bool LDateTime::SetUnix(uint64 s) { #if defined(WINDOWS) return Set(s * LDateTime::Second64Bit + 116445168000000000LL); #else return Set((s + Offset1800) * LDateTime::Second64Bit); #endif } bool LDateTime::Set(uint64 s) { #if defined WIN32 FILETIME Utc; SYSTEMTIME System; // Adjust to the desired timezone uint64 u = s + ((int64)_Tz * 60 * Second64Bit); Utc.dwHighDateTime = u >> 32; Utc.dwLowDateTime = u & 0xffffffff; if (!FileTimeToSystemTime(&Utc, &System)) return false; _Year = System.wYear; _Month = System.wMonth; _Day = System.wDay; _Hours = System.wHour; _Minutes = System.wMinute; _Seconds = System.wSecond; _Thousands = System.wMilliseconds; return true; #else time_t t = (time_t) (((int64)(s / Second64Bit)) - Offset1800); Set(t); _Thousands = s % Second64Bit; return true; #endif } bool LDateTime::Set(time_t tt) { struct tm *t; #if !defined(_MSC_VER) || _MSC_VER < _MSC_VER_VS2005 if (_Tz) { tt += _Tz * 60; } t = gmtime(&tt); if (t) #else struct tm tmp; if (_localtime64_s(t = &tmp, &tt) == 0) #endif { _Year = t->tm_year + 1900; _Month = t->tm_mon + 1; _Day = t->tm_mday; _Hours = t->tm_hour; _Minutes = t->tm_min; _Seconds = t->tm_sec; _Thousands = 0; // _Tz = SystemTimeZone(); return true; } return false; } bool LDateTime::Get(uint64 &s) const { #ifdef WINDOWS FILETIME Utc; SYSTEMTIME System; System.wYear = _Year; System.wMonth = limit(_Month, 1, 12); System.wDay = limit(_Day, 1, 31); System.wHour = limit(_Hours, 0, 23); System.wMinute = limit(_Minutes, 0, 59); System.wSecond = limit(_Seconds, 0, 59); System.wMilliseconds = limit(_Thousands, 0, 999); System.wDayOfWeek = DayOfWeek(); BOOL b1; if (b1 = SystemTimeToFileTime(&System, &Utc)) { // Convert to 64bit s = ((uint64)Utc.dwHighDateTime << 32) | Utc.dwLowDateTime; // Adjust for timezone s -= (int64)_Tz * 60 * Second64Bit; return true; } DWORD Err = GetLastError(); s = 0; LAssert(!"SystemTimeToFileTime failed."); return false; #else if (_Year < MIN_YEAR) return false; struct tm t; ZeroObj(t); t.tm_year = _Year - 1900; t.tm_mon = _Month - 1; t.tm_mday = _Day; t.tm_hour = _Hours; t.tm_min = _Minutes; t.tm_sec = _Seconds; t.tm_isdst = -1; time_t sec = timegm(&t); #if 0 printf("timegm(y=%i m=%i d=%i h=%i m=%i s=%i)=%lli\n", t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, sec); #endif if (sec == -1) return false; if (_Tz) { // Adjust the output to UTC from the current timezone. sec -= _Tz * 60; // printf("Adjusting -= %i (%i)\n", _Tz * 60, _Tz); } s = (uint64)(sec + Offset1800) * Second64Bit + _Thousands; return true; #endif } LString LDateTime::Get() const { char buf[32]; int Ch = GetDate(buf, sizeof(buf)); buf[Ch++] = ' '; Ch += GetTime(buf+Ch, sizeof(buf)-Ch); return LString(buf, Ch); } void LDateTime::Get(char *Str, size_t SLen) const { if (Str) { GetDate(Str, SLen); size_t len = strlen(Str); if (len < SLen - 1) { Str[len++] = ' '; GetTime(Str+len, SLen-len); } } } bool LDateTime::Set(const char *Str) { if (!Str) return false; char Local[256]; strcpy_s(Local, sizeof(Local), Str); char *Sep = strchr(Local, ' '); if (Sep) { *Sep++ = 0; if (!SetTime(Sep)) return false; } if (!SetDate(Local)) return false; return true; } void LDateTime::Month(char *m) { int i = IsMonth(m); if (i >= 0) _Month = i + 1; } int DateComponent(const char *s) { int64 i = Atoi(s); return i ? (int)i : LDateTime::IsMonth(s); } bool LDateTime::SetDate(const char *Str) { bool Status = false; if (Str) { auto T = LString(Str).SplitDelimit("/-.,_\\"); if (T.Length() == 3) { int i[3] = { DateComponent(T[0]), DateComponent(T[1]), DateComponent(T[2]) }; int fmt = _Format & GDTF_DATE_MASK; // Do some guessing / overrides. // Don't let _Format define the format completely. if (i[0] > 1000) { fmt = GDTF_YEAR_MONTH_DAY; } else if (i[2] > 1000) { if (i[0] > 12) fmt = GDTF_DAY_MONTH_YEAR; else if (i[1] > 12) fmt = GDTF_MONTH_DAY_YEAR; } switch (fmt) { case GDTF_MONTH_DAY_YEAR: { _Month = i[0]; _Day = i[1]; _Year = i[2]; break; } case GDTF_DAY_MONTH_YEAR: { _Day = i[0]; _Month = i[1]; _Year = i[2]; break; } case GDTF_YEAR_MONTH_DAY: { _Year = i[0]; _Month = i[1]; _Day = i[2]; break; } default: { _Year = i[2]; if ((DefaultFormat & GDTF_DATE_MASK) == GDTF_MONTH_DAY_YEAR) { // Assume m/d/yyyy _Day = i[1]; _Month = i[0]; } else { // Who knows??? // Assume d/m/yyyy _Day = i[0]; _Month = i[1]; } break; } } if (_Year < 100) { LAssert(_Day < 1000 && _Month < 1000); if (_Year >= 80) _Year += 1900; else _Year += 2000; } Status = true; } else { // Fall back to fuzzy matching auto T = LString(Str).SplitDelimit(" ,"); MonthHash Lut; int FMonth = 0; int FDay = 0; int FYear = 0; for (unsigned i=0; i 0) { if (i >= 1000) { FYear = i; } else if (i < 32) { FDay = i; } } } else { int i = Lut.Find(p); if (i) FMonth = i; } } if (FMonth && FDay) { Day(FDay); Month(FMonth); } if (FYear) { Year(FYear); } else { LDateTime Now; Now.SetNow(); Year(Now.Year()); } } } return Status; } bool LDateTime::SetTime(const char *Str) { if (!Str) return false; auto T = LString(Str).SplitDelimit(":."); if (T.Length() < 2 || T.Length() > 4) return false; _Hours = (int)T[0].Int(); _Minutes = (int)T[1].Int(); if (_Hours < 0 || _Minutes < 0) return false; char *s = T[2]; if (s) _Seconds = atoi(s); else _Seconds = 0; _Thousands = 0; s = T.Last(); if (s) { if (strchr(s, 'p') || strchr(s, 'P')) { if (_Hours != 12) _Hours += 12; } else if (strchr(s, 'a') || strchr(s, 'A')) { if (_Hours == 12) _Hours -= 12; } } if (T.Length() > 3) { LString t = "0."; t += s; _Thousands = (int) (t.Float() * 1000); } return true; } int LDateTime::IsWeekDay(const char *s) { for (unsigned n=0; n= 4) { Year((int)t[0].Int()); Month((int)t[1].Int()); Day((int)t[2].Int()); } else if (t[2].Length() >= 4) { Day((int)t[0].Int()); Month((int)t[1].Int()); Year((int)t[2].Int()); } else { LAssert(!"Unknown date format?"); return false; } } } else if (a[i].Length() == 4) Year((int)a[i].Int()); else if (!Day()) Day((int)a[i].Int()); } else if (IsAlpha(*c)) { int WkDay = IsWeekDay(c); if (WkDay >= 0) continue; int Mnth = IsMonth(c); if (Mnth >= 0) Month(Mnth + 1); } else if (*c == '-' || *c == '+') { c++; if (strlen(c) == 4) { // Timezone.. int64 Tz = a[i].Int(); int Hrs = (int) (Tz / 100); int Min = (int) (Tz % 100); SetTimeZone(Hrs * 60 + Min, false); } } } return IsValid(); } int LDateTime::Sizeof() { return sizeof(int) * 7; } bool LDateTime::Serialize(LFile &f, bool Write) { int32 i; if (Write) { #define wf(fld) i = fld; f << i; wf(_Day); wf(_Month); wf(_Year); wf(_Thousands); wf(_Seconds); wf(_Minutes); wf(_Hours); } else { #define rf(fld) f >> i; fld = i; rf(_Day); rf(_Month); rf(_Year); rf(_Thousands); rf(_Seconds); rf(_Minutes); rf(_Hours); } return true; } /* bool LDateTime::Serialize(ObjProperties *Props, char *Name, bool Write) { #ifndef LGI_STATIC if (Props && Name) { struct _Date { uint8_t Day; uint8_t Month; int16_t Year; uint8_t Hour; uint8_t Minute; uint16_t ThouSec; }; LAssert(sizeof(_Date) == 8); if (Write) { _Date d; d.Day = _Day; d.Month = _Month; d.Year = _Year; d.Hour = _Hours; d.Minute = _Minutes; d.ThouSec = (_Seconds * 1000) + _Thousands; return Props->Set(Name, &d, sizeof(d)); } else // Read { void *Ptr; int Len; if (Props->Get(Name, Ptr, Len) && sizeof(_Date) == Len) { _Date *d = (_Date*) Ptr; _Day = d->Day; _Month = d->Month; _Year = d->Year; _Hours = d->Hour; _Minutes = d->Minute; _Seconds = d->ThouSec / 1000; _Thousands = d->ThouSec % 1000; return true; } } } #endif return false; } */ int LDateTime::Compare(const LDateTime *Date) const { // this - *Date auto ThisTs = IsValid() ? Ts() : 0; auto DateTs = Date->IsValid() ? Date->Ts() : 0; // If these ever fire, the cast to int64_t will overflow LAssert((ThisTs & 0x800000000000000) == 0); LAssert((DateTs & 0x800000000000000) == 0); int64_t Diff = (int64_t)ThisTs - DateTs; if (Diff < 0) return -1; return Diff > 0 ? 1 : 0; } #define DATETIME_OP(op) \ bool LDateTime::operator op(const LDateTime &dt) const \ { \ auto a = Ts(); \ auto b = dt.Ts(); \ return a op b; \ } DATETIME_OP(<) DATETIME_OP(<=) DATETIME_OP(>) DATETIME_OP(>=) bool LDateTime::operator ==(const LDateTime &dt) const { return _Year == dt._Year && _Month == dt._Month && _Day == dt._Day && _Hours == dt._Hours && _Minutes == dt._Minutes && _Seconds == dt._Seconds && _Thousands == dt._Thousands; } bool LDateTime::operator !=(const LDateTime &dt) const { return _Year != dt._Year || _Month != dt._Month || _Day != dt._Day || _Hours != dt._Hours || _Minutes != dt._Minutes || _Seconds != dt._Seconds || _Thousands != dt._Thousands; } int LDateTime::DiffMonths(const LDateTime &dt) { int a = (Year() * 12) + Month(); int b = (dt.Year() * 12) + dt.Month(); return b - a; } LDateTime LDateTime::operator -(const LDateTime &dt) { uint64 a, b; Get(a); dt.Get(b); /// Resolution of a second when using 64 bit timestamps int64 Sec = Second64Bit; int64 Min = 60 * Sec; int64 Hr = 60 * Min; int64 Day = 24 * Hr; int64 d = (int64)a - (int64)b; LDateTime r; r._Day = (int16) (d / Day); d -= r._Day * Day; r._Hours = (int16) (d / Hr); d -= r._Hours * Hr; r._Minutes = (int16) (d / Min); d -= r._Minutes * Min; r._Seconds = (int16) (d / Sec); #ifdef WIN32 d -= r._Seconds * Sec; r._Thousands = (int16) (d / 10000); #else r._Thousands = 0; #endif return r; } LDateTime LDateTime::operator +(const LDateTime &dt) { LDateTime s = *this; s.AddMonths(dt.Month()); s.AddDays(dt.Day()); s.AddHours(dt.Hours()); s.AddMinutes(dt.Minutes()); // s.AddSeconds(dt.Seconds()); return s; } LDateTime &LDateTime::operator =(const LDateTime &t) { _Day = t._Day; _Year = t._Year; _Thousands = t._Thousands; _Month = t._Month; _Seconds = t._Seconds; _Minutes = t._Minutes; _Hours = t._Hours; _Tz = t._Tz; _Format = t._Format; return *this; } LDateTime &LDateTime::operator =(struct tm *time) { if (time) { _Seconds = time->tm_sec; _Minutes = time->tm_min; _Hours = time->tm_hour; _Day = time->tm_mday; _Month = time->tm_mon + 1; _Year = time->tm_year + 1900; } else Empty(); return *this; } bool LDateTime::IsSameDay(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month() && Year() == d.Year(); } bool LDateTime::IsSameMonth(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month(); } bool LDateTime::IsSameYear(LDateTime &d) const { return Year() == d.Year(); } bool LDateTime::IsLeapYear(int Year) const { if (Year < 0) Year = _Year; if (Year % 4 != 0) { return false; } if (Year % 400 == 0) { return true; } if (Year % 100 == 0) { return false; } return true; } int LDateTime::DaysInMonth() const { if (_Month == 2 && IsLeapYear()) { return 29; } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return _Month >= 1 && _Month <= 12 ? DaysInMonth[_Month-1] : 0; } #define MinutesInDay (60*24) void LDateTime::AddSeconds(int64 Seconds) { uint64 i; if (Get(i)) { i += Seconds * Second64Bit; Set(i); } } void LDateTime::AddMinutes(int64 Minutes) { uint64 i; if (Get(i)) { int64 delta = Minutes * 60 * Second64Bit; uint64 n = i + delta; // printf("AddMin " LPrintfInt64 " + " LPrintfInt64 " = " LPrintfInt64 "\n", i, delta, n); Set(n); #if 0 uint64 i2; Get(i2); int64 diff = (int64)i2-(int64)i; #endif } } void LDateTime::AddHours(int64 Hours) { uint64 i; if (Get(i)) { i += Hours * LDateTime::HourLength * Second64Bit; Set(i); } } bool LDateTime::AddDays(int64 Days) { if (!Days) return true; uint64 Ts; if (!Get(Ts)) return false; Ts += Days * LDateTime::DayLength * Second64Bit; bool b = Set(Ts); return b; } void LDateTime::AddMonths(int64 Months) { int64 m = _Month + Months; do { if (m < 1) { _Year--; m += 12; } else if (m > 12) { _Year++; m -= 12; } else { break; } } while (1); _Month = (int16) m; if (_Day > DaysInMonth()) _Day = DaysInMonth(); } LString LDateTime::DescribePeriod(LDateTime to) { auto ThisTs = Ts(); auto ToTs = to.Ts(); auto diff = ThisTs < ToTs ? ToTs - ThisTs : ThisTs - ToTs; auto seconds = diff / LDateTime::Second64Bit; int mins = (int) (seconds / 60); seconds -= mins * 60; int hrs = mins / 60; mins -= hrs * 60; int days = hrs / 24; hrs -= days * 24; LString s; if (days > 0) s.Printf("%id %ih %im %is", days, hrs, mins, (int)seconds); else if (hrs > 0) s.Printf("%ih %im %is", hrs, mins, (int)seconds); else if (mins > 0) s.Printf("%im %is", mins, (int)seconds); else s.Printf("%is", (int)seconds); return s; } int LDateTime::MonthFromName(const char *Name) { if (Name) { for (int m=0; m<12; m++) { if (strnicmp(Name, MonthsShort[m], strlen(MonthsShort[m])) == 0) { return m + 1; break; } } } return -1; } bool LDateTime::Decode(const char *In) { // Test data: // // Tue, 6 Dec 2005 1:25:32 -0800 Empty(); if (!In) { LAssert(0); return false; } bool Status = false; // Tokenize delimited by whitespace LString::Array T = LString(In).SplitDelimit(", \t\r\n"); if (T.Length() < 2) { if (T[0].IsNumeric()) { // Some sort of timestamp? uint64_t Ts = Atoi(T[0].Get()); if (Ts > 0) { return SetUnix(Ts); } else return false; } else { // What now? return false; } } else { bool GotDate = false; for (unsigned i=0; i 31) { // Y/M/D? Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else if (Date[2].Int() > 31) { // D/M/Y? Day((int)Date[0].Int()); Year((int)Date[2].Int()); } else { // Ambiguous year... bool YrFirst = true; if (Date[0].Length() == 1) YrFirst = false; // else we really can't tell.. just go with year first if (YrFirst) { Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else { Day((int)Date[0].Int()); Year((int)Date[2].Int()); } LDateTime Now; Now.SetNow(); if (Year() + 2000 <= Now.Year()) Year(2000 + Year()); else Year(1900 + Year()); } if (Date[1].IsNumeric()) Month((int)Date[1].Int()); else { int m = MonthFromName(Date[1]); if (m > 0) Month(m); } GotDate = true; Status = true; } else if (s.Find(":") >= 0) { // whole time // Do some validation bool Valid = true; for (char *c = s; *c && Valid; c++) { if (!(IsDigit(*c) || *c == ':')) Valid = false; } if (Valid) { LString::Array Time = s.Split(":"); if (Time.Length() == 2 || Time.Length() == 3) { // Hour int i = (int) Time[0].Int(); if (i >= 0) Hours(i); if (s.Lower().Find("p") >= 0) { if (Hours() < 12) Hours(Hours() + 12); } // Minute i = (int) Time[1].Int(); if (i >= 0) Minutes(i); if (Time.Length() == 3) { // Second i = (int) Time[2].Int(); if (i >= 0) Seconds(i); } Status = true; } } } else if (IsAlpha(s(0))) { // text int m = MonthFromName(s); if (m > 0) Month(m); } else if (strchr("+-", *s)) { // timezone DoTimeZone: LDateTime Now; double OurTmz = (double)Now.SystemTimeZone() / 60; if (s && strchr("-+", *s) && strlen(s) == 5) { #if 1 int i = atoi(s); int hr = i / 100; int min = i % 100; SetTimeZone(hr * 60 + min, false); #else // adjust for timezone char Buf[32]; memcpy(Buf, s, 3); Buf[3] = 0; double TheirTmz = atof(Buf); memcpy(Buf+1, s + 3, 2); TheirTmz += (atof(Buf) / 60); if (Tz) { *Tz = TheirTmz; } double AdjustHours = OurTmz - TheirTmz; AddMinutes((int) (AdjustHours * 60)); #endif } else { // assume GMT AddMinutes((int) (OurTmz * 60)); } } else if (s.IsNumeric()) { int Count = 0; for (char *c = s; *c; c++) { if (!IsDigit(*c)) break; Count++; } if (Count <= 2) { if (Day()) { // We already have a day... so this might be // a 2 digit year... LDateTime Now; Now.SetNow(); int Yr = atoi(s); if (2000 + Yr <= Now.Year()) Year(2000 + Yr); else Year(1900 + Yr); } else { // A day number (hopefully)? Day((int)s.Int()); } } else if (Count == 4) { if (!Year()) { // A year! Year((int)s.Int()); Status = true; } else { goto DoTimeZone; } // My one and only Y2K fix // d.Year((Yr < 100) ? (Yr > 50) ? 1900+Yr : 2000+Yr : Yr); } } } } return Status; } bool LDateTime::GetVariant(const char *Name, LVariant &Dst, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: // Type: Int32 Dst = Year(); break; case DateMonth: // Type: Int32 Dst = Month(); break; case DateDay: // Type: Int32 Dst = Day(); break; case DateHour: // Type: Int32 Dst = Hours(); break; case DateMinute: // Type: Int32 Dst = Minutes(); break; case DateSecond: // Type: Int32 Dst = Seconds(); break; case DateDate: // Type: String { char s[32]; GetDate(s, sizeof(s)); Dst = s; break; } case DateTime: // Type: String { char s[32]; GetTime(s, sizeof(s)); Dst = s; break; } + case TypeString: // Type: String case DateDateAndTime: // Type: String { char s[32]; Get(s, sizeof(s)); Dst = s; break; } + case TypeInt: // Type: Int64 case DateTimestamp: // Type: Int64 { uint64 i = 0; Get(i); Dst = (int64)i; break; } case DateSecond64Bit: { Dst = Second64Bit; break; } default: { return false; } } return true; } bool LDateTime::SetVariant(const char *Name, LVariant &Value, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: Year(Value.CastInt32()); break; case DateMonth: Month(Value.CastInt32()); break; case DateDay: Day(Value.CastInt32()); break; case DateHour: Hours(Value.CastInt32()); break; case DateMinute: Minutes(Value.CastInt32()); break; case DateSecond: Seconds(Value.CastInt32()); break; case DateDate: SetDate(Value.Str()); break; case DateTime: SetTime(Value.Str()); break; case DateDateAndTime: Set(Value.Str()); break; case DateTimestamp: Set((uint64)Value.CastInt64()); break; default: return false; } return true; } bool LDateTime::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { switch (LStringToDomProp(Name)) { case DateSetNow: SetNow(); if (ReturnValue) *ReturnValue = true; break; case DateSetStr: if (Args.Length() < 1) return false; bool Status; if (Args[0]->Type == GV_INT64) Status = Set((uint64) Args[0]->Value.Int64); else Status = Set(Args[0]->Str()); if (ReturnValue) *ReturnValue = Status; break; case DateGetStr: { char s[256] = ""; Get(s, sizeof(s)); if (ReturnValue) *ReturnValue = s; break; } default: return false; } return true; } #ifdef _DEBUG #define DATE_ASSERT(i) \ if (!(i)) \ { \ LAssert(!"LDateTime unit test failed."); \ return false; \ } bool LDateTime_Test() { // Check 64bit get/set LDateTime t("1/1/2017 0:0:0"); uint64 i; DATE_ASSERT(t.Get(i)); LgiTrace("Get='%s'\n", t.Get().Get()); uint64 i2 = i + (24ULL * 60 * 60 * LDateTime::Second64Bit); LDateTime t2; t2.SetFormat(GDTF_DAY_MONTH_YEAR); t2.Set(i2); LString s = t2.Get(); LgiTrace("Set='%s'\n", s.Get()); DATE_ASSERT(!stricmp(s, "2/1/2017 12:00:00a") || !stricmp(s, "2/01/2017 12:00:00a")); t.SetNow(); LgiTrace("Now.Local=%s Tz=%.2f\n", t.Get().Get(), t.GetTimeZoneHours()); t2 = t; t2.ToUtc(); LgiTrace("Now.Utc=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); t2.ToLocal(); LgiTrace("Now.Local=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); DATE_ASSERT(t == t2); return true; } #endif diff --git a/src/common/Lgi/CssTools.cpp b/src/common/Lgi/CssTools.cpp --- a/src/common/Lgi/CssTools.cpp +++ b/src/common/Lgi/CssTools.cpp @@ -1,452 +1,448 @@ #include "lgi/common/Lgi.h" #include "lgi/common/CssTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" struct CssImageCache { LArray> Store; LHashTbl, LSurface*> Map; LSurface *Get(const char *uri) { auto Uri = LString(uri).Strip("\'\""); auto i = Map.Find(Uri); if (i) return i; // Check theme folder first... LString File; auto Res = LgiGetResObj(); auto ThemeFolder = Res ? Res->GetThemeFolder() : NULL; if (ThemeFolder) { LFile::Path p = ThemeFolder; p += Uri; if (p.Exists()) File = p.GetFull(); } // Then find it normally... if (!File) File = LFindFile(Uri); if (!File) return NULL; LAutoPtr img(GdcD->Load(File)); if (!img) return NULL; Map.Add(Uri, img); auto &s = Store.New(); s = img; return s; } } Cache; LColour &LCssTools::GetFore(LColour *Default) { if (!ForeInit) { ForeInit = 1; if (Default) Fore = *Default; else Fore = LColour(L_TEXT); if (View) { Fore = View->StyleColour(LCss::PropColor, Fore); } else if (Css) { LCss::ColorDef Fill = Css->Color(); if (Fill.Type == LCss::ColorRgb) Fore.Set(Fill.Rgb32, 32); else if (Fill.Type == LCss::ColorTransparent) Fore.Empty(); } } return Fore; } LColour &LCssTools::GetBack(LColour *Default, int Depth) { if (!BackInit) { BackInit = 1; if (Default) Back = *Default; else Back = LColour(L_MED); if (View) { Back = View->StyleColour(LCss::PropBackgroundColor, Back, Depth >= 0 ? Depth : 6); } else if (Css) { LCss::ColorDef Fill = Css->BackgroundColor(); if (Fill.Type == LCss::ColorRgb) Back.Set(Fill.Rgb32, 32); else if (Fill.Type == LCss::ColorTransparent) Back.Empty(); } } return Back; } LRect LCssTools::ApplyMargin(LRect &in) { if (!Css) return in; LRect r = in; // Insert by the margin LCss::Len margin = Css->Margin(); #define DoMargin(name, edge, box, sign) \ { LCss::Len m = Css->Margin##name(); \ r.edge sign (m.IsValid() ? &m : &margin)->ToPx(in.box(), Font); } DoMargin(Left, x1, X, +=) DoMargin(Top, y1, Y, +=) DoMargin(Right, x2, X, -=) DoMargin(Bottom, y2, Y, -=) return r; } LRect LCssTools::GetPadding(LRect &box, LRect *def) { LRect r(0, 0, 0, 0); if (Css) { LCss::Len padding = Css->Padding(); #define DoPadding(name, edge, dim) \ { \ LCss::Len p = Css->Padding##name(); \ auto v = p.IsValid() ? &p : &padding; \ r.edge = v->IsValid() ? v->ToPx(box.dim(), Font) : (def ? def->edge : 0); \ } DoPadding(Left, x1, X) DoPadding(Top, y1, Y) DoPadding(Right, x2, X) DoPadding(Bottom, y2, Y) #undef DoPadding } else if (def) { return *def; } return r; } LRect LCssTools::GetBorder(LRect &box, LRect *def) { LRect r(0, 0, 0, 0); if (Css) { LCss::Len border = Css->Border(); #define _(name, edge, dim) \ { \ LCss::Len p = Css->Border##name(); \ auto v = p.IsValid() ? &p : &border; \ r.edge = v->IsValid() ? v->ToPx(box.dim(), Font) : (def ? def->edge : 0); \ } _(Left, x1, X) _(Top, y1, Y) _(Right, x2, X) _(Bottom, y2, Y) #undef _ } else if (def) { return *def; } return r; } LRect LCssTools::ApplyBorder(LRect &in) { if (!Css) return in; // Insert by the padding LRect b = GetBorder(in); return LRect(in.x1 + b.x1, in.y1 + b.y1, in.x2 - b.x2, in.y2 - b.y2); } LRect LCssTools::ApplyPadding(LRect &in) { if (!Css) return in; // Insert by the padding LRect pad = GetPadding(in); return LRect(in.x1 + pad.x1, in.y1 + pad.y1, in.x2 - pad.x2, in.y2 - pad.y2); } bool LCssTools::SetLineStyle(LSurface *pDC, LCss::BorderDef &b) { if (b.Color.Type == LCss::ColorRgb) { pDC->Colour(b.Color.Rgb32, 32); switch (b.Style) { case LCss::BorderHidden: return false; case LCss::BorderDotted: pDC->LineStyle(LSurface::LineDot); break; case LCss::BorderDashed: pDC->LineStyle(LSurface::LineDash); break; default: pDC->LineStyle(LSurface::LineSolid); break; } } else return false; return true; } LRect LCssTools::PaintBorder(LSurface *pDC, LRect &in) { LRect Content = in; if (!Css) return Content; // Draw the border LCss::BorderDef b = Css->Border(); bool Drawn = false; switch (b.Style) { default: case LCss::BorderNone: case LCss::BorderHidden: // Do nothing break; case LCss::BorderInset: // Sunken { int Px = b.ToPx(Content.X(), Font); Drawn = true; - if (Px == 1) - LThinBorder(pDC, Content, DefaultSunkenEdge); - else if (Px == 2) + if (Px == 2) LWideBorder(pDC, Content, DefaultSunkenEdge); else - LAssert(!"Unsupported sunken border width"); + LThinBorder(pDC, Content, DefaultSunkenEdge); break; } case LCss::BorderOutset: // Raised { int Px = b.ToPx(Content.X(), Font); Drawn = true; - if (Px == 1) - LThinBorder(pDC, Content, DefaultRaisedEdge); - else if (Px == 2) + if (Px == 2) LWideBorder(pDC, Content, DefaultRaisedEdge); else - LAssert(!"Unsupported raised border width"); + LThinBorder(pDC, Content, DefaultRaisedEdge); break; } case LCss::BorderSolid: { int Px = b.ToPx(Content.X(), Font); if (SetLineStyle(pDC, b)) { Drawn = true; for (int i=0; iBox(&Content); Content.Inset(1, 1); } } break; } /* case BorderDotted: break; case BorderDashed: break; case BorderDouble: break; case BorderGroove: break; case BorderRidge: break; */ } if (!Drawn) { // Check for individual borders of different styles... LCss::BorderDef Top = Css->BorderTop(); if (SetLineStyle(pDC, Top)) { int Px = Top.ToPx(Content.Y(), Font); for (int i=0; iHLine(Content.x1, Content.x2, Content.y1++); } LCss::BorderDef Bottom = Css->BorderBottom(); if (SetLineStyle(pDC, Bottom)) { int Px = Bottom.ToPx(Content.Y(), Font); for (int i=0; iHLine(Content.x1, Content.x2, Content.y2--); } LCss::BorderDef Left = Css->BorderLeft(); if (SetLineStyle(pDC, Left)) { int Px = Left.ToPx(Content.X(), Font); for (int i=0; iVLine(Content.x1++, Content.y1, Content.y2); } LCss::BorderDef Right = Css->BorderRight(); if (SetLineStyle(pDC, Right)) { int Px = Right.ToPx(Content.X(), Font); for (int i=0; iVLine(Content.x2--, Content.y1, Content.y2); } pDC->LineStyle(LSurface::LineSolid); } return Content; } LRect LCssTools::PaintPadding(LSurface *pDC, LRect &in) { LRect Content = in; if (!Css) return Content; // Insert by the padding LRect r = ApplyPadding(Content); if (r != Content) { // Draw the padding in the background colour LColour Background = GetBack(); pDC->Colour(Background); if (r.x1 > Content.x1) pDC->Rectangle(Content.x1, Content.y1, r.x1 - 1, Content.y2); if (r.x2 < Content.x2) pDC->Rectangle(r.x2 + 1, Content.y1, Content.x2, Content.y2); if (r.y1 > Content.y1) pDC->Rectangle(r.x1, Content.y1, r.x2, r.y1 - 1); if (r.y2 < Content.y2) pDC->Rectangle(r.x1, r.y2 + 1, r.x2, Content.y2); Content = r; } return Content; } LSurface *LCssTools::GetCachedImage(const char *Uri) { return Cache.Get(Uri); } bool LCssTools::Tile(LSurface *pDC, LRect in, LSurface *Img, int Ox, int Oy) { if (!pDC || !Img) return false; for (int y=in.y1; yY()) for (int x=in.x1; xX()) pDC->Blt(x - Ox, y - Oy, Img); return true; } LSurface *LCssTools::GetBackImage() { LCss::ImageDef BackDef; auto Parent = View ? View->GetParent() : NULL; if (Css) BackDef = Css->BackgroundImage(); if (BackDef.IsValid() && View) BackPos = View->GetClient(); else if (Parent && Parent->GetCss()) { BackDef = Parent->GetCss()->BackgroundImage(); BackPos = View->GetPos(); } if (BackDef.IsValid()) BackImg = GetCachedImage(BackDef.Uri); return BackImg; } void LCssTools::PaintContent(LSurface *pDC, LRect &in, const char *utf8, LSurface *img) { bool BackgroundDrawn = false; auto BkImg = GetBackImage(); if (BkImg) BackgroundDrawn = Tile(pDC, in, BkImg, BackPos.x1, BackPos.y1); if (!BackgroundDrawn) { LColour Background = GetBack(); if (Background) { pDC->Colour(Background); pDC->Rectangle(&in); BackgroundDrawn = true; } } if (utf8 || img) { LAutoPtr Ds; auto Fnt = View ? View->GetFont() : LSysFont; if (utf8) { Ds.Reset(new LDisplayString(Fnt, utf8)); } int Spacer = Ds && img ? 8 : 0; int Wid = Spacer + (Ds ? Ds->X() : 0) + (img ? img->X() : 0); int x = in.x1 + ((in.X() - Wid) >> 1); // Draw any icon if (img) { int y = in.y1 + ((in.Y() - img->Y()) >> 1); int Op = pDC->Op(GDC_ALPHA); pDC->Blt(x, y, img); pDC->Op(Op); x += img->X() + Spacer; } // Draw any text... if (Ds) { int y = in.y1 + ((in.Y() - Ds->Y()) >> 1); LCss::ColorDef Fore; if (Css) Fore = Css->Color(); LColour ForeCol(L_TEXT); if (Fore.IsValid()) ForeCol = Fore; Fnt->Fore(ForeCol); Fnt->Transparent(true); Ds->Draw(pDC, x, y); } } } diff --git a/src/common/Lgi/FileSelect.cpp b/src/common/Lgi/FileSelect.cpp --- a/src/common/Lgi/FileSelect.cpp +++ b/src/common/Lgi/FileSelect.cpp @@ -1,2278 +1,2254 @@ /*hdr ** FILE: LFileSelect.cpp ** AUTHOR: Matthew Allen ** DATE: 20/5/2002 ** DESCRIPTION: Common file/directory selection dialog ** ** Copyright (C) 1998-2002, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Popup.h" #include "lgi/common/List.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Tree.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Box.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #define FSI_FILE 0 #define FSI_DIRECTORY 1 #define FSI_BACK 2 #define FSI_UPDIR 3 #define FSI_NEWDIR 4 #define FSI_DESKTOP 5 #define FSI_HARDDISK 6 #define FSI_CDROM 7 #define FSI_FLOPPY 8 #define FSI_NETWORK 9 enum DlgType { TypeNone, TypeOpenFile, TypeOpenFolder, TypeSaveFile }; class LFileSelectDlg; char ModuleName[] = "File Select"; uint32_t IconBits[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC980FA8A, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430F81F, 0x84308430, 0x84308430, 0xF81F8430, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCCE0F81F, 0x9800FCF9, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xCE6C9E73, 0x738EC638, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9FFF738E, 0x9FFF9FFF, 0x9FFF9FFF, 0x00009FFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0x04F904F9, 0xAD720313, 0xAD72AD72, 0xAD72AD72, 0xFE60CCE0, 0x00009B00, 0x0000AD72, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738EF81F, 0x667334F3, 0xCE6C9E73, 0xC638B5B6, 0x3186C638, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0x667F04F9, 0x031304F9, 0xFFFFCE73, 0xFFF9FFFF, 0xCCE0FFFF, 0x9B00FFF3, 0x04F90000, 0x00000313, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0x6673738E, 0x34F36673, 0xCE736673, 0xB5B6C638, 0xB5B6DEFB, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xCFFFCFFF, 0x0000CFFF, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x0000FFFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0xF81F0000, 0x0000F81F, 0x0000F81F, 0x0000F81F, 0xF81FF81F, 0x04F904F9, 0xCE730313, 0xFFFFFFFF, 0xFFF9FFF9, 0xFE60CCE0, 0x00009B00, 0x667F3313, 0x00000313, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xF81FF81F, 0xF81FF81F, 0x34F98430, 0x667364F9, 0x84306679, 0xD6BAB5B6, 0xB5B6C638, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCFFF738E, 0xCFFFCFFF, 0xCFFFCFFF, 0x0000CFFF, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF3, 0x00009CE0, 0xF81FF81F, 0xF81F0000, 0xF81F0000, 0xF81FF81F, 0x031304F9, 0xFFFFCE73, 0x94B294B2, 0xCCE094B2, 0x9B00FFF3, 0xAD720000, 0x3313FFF9, 0x00000313, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xA53494B2, 0x667964F3, 0x00008430, 0xA5348430, 0xCE79B5B6, 0x3186CE79, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE79738E, 0xCE79C638, 0xC638B5B6, 0x0000B5B6, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE0CE6C, 0x9CE09CE0, 0xF81F9CE0, 0x0000F81F, 0x0000F81F, 0xCE730313, 0xFFFFFFFF, 0xFFFF94B2, 0xFE60CCE0, 0x00009B00, 0xFFF9AD72, 0xCE73CE73, 0x00003313, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xB5B6B5B6, 0x8430CE79, 0xF81F0000, 0x84300000, 0xCE79CE79, 0x3186CE79, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0xC638738E, 0x84308430, 0x84308430, 0x0000C638, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x00000000, 0xFFF30000, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF3FFF9, 0x0000CE6C, 0xF81F0000, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFFF, 0xFFFF94B2, 0x9B009CEC, 0x00000000, 0xCE73FFF9, 0xAD72FFF9, 0x000094B2, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xF7BEE73C, 0xB5B6E73C, 0x00008430, 0xB5B68430, 0xF7BEEF7D, 0x3186CE79, 0x8430F81F, 0xC638C638, 0xC638C638, 0xC638C638, 0xCE79738E, 0x0000738E, 0xFFFF738E, 0x0000B5B6, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0x0000FFF3, 0x00000000, 0x00000000, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0x0000CE6C, 0x0000F81F, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFF0000, 0x0000FFF9, 0xFFF9CE73, 0xCE73AD72, 0x000094B2, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0x8430F81F, 0x84308430, 0xA5348430, 0xA534B5B6, 0x8430A534, 0xDEFB34F3, 0xFFFFE73C, 0xF81F3186, 0xFFFF8430, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81F0000, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73FFF9, 0xAD72CE73, 0x000094B2, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF8430, 0xFFFFFFFF, 0xA534738E, 0xB5B6A534, 0xCE73D6BA, 0x34F96673, 0xB5B6CFFF, 0xF81F3186, 0xC6388430, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638F800, 0x738E8430, 0xF81F0000, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73CE73, 0xCE73AD72, 0x000094B2, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF8430, 0xC638C638, 0x738EC638, 0xB5B6A534, 0xCE73CE79, 0x04F99E73, 0x000004F9, 0xF81FF81F, 0xC6388430, 0xC638C638, 0x84308430, 0x84308430, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0x00000000, 0x00000000, 0xFE730000, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF904F9, 0xFFF9FFF9, 0xFFF994B2, 0xFFF9FFF9, 0x0000FFF9, 0xAD72CE73, 0xAD72CE73, 0x00003313, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF8430, 0x31863186, 0x31863186, 0x84308430, 0xCE73CE79, 0x31869E73, 0x00003186, 0xF81FF81F, 0xC6388430, 0x84308430, 0x00000000, 0x00000000, 0x84308430, 0xC6388430, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FE73, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0xFFF9FFF9, 0x00000000, 0x00000000, 0x00000000, 0xCE73AD72, 0x3313AD72, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF8430, 0x042007E0, 0xFFFFFFFF, 0xC638C638, 0x31863186, 0xC6383186, 0x0000738E, 0xF81FF81F, 0xC6388430, 0xC638C638, 0xFFFFFFFF, 0xFFFFFFFF, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xD699D699, 0xD699D699, 0xD699D699, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x667F04F9, 0xFFF904F9, 0xCE73FFF9, 0xCE73FFF9, 0xAD72CE73, 0xAD72CE73, 0x667F3313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430738E, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0xB5B6F81F, 0xB5B6F81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xF81FB5B6, 0xF81FB5B6, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0x03130313, 0x04F90313, 0x94B294B2, 0x94B294B2, 0x94B294B2, 0x331394B2, 0x33133313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F}; LInlineBmp FileSelectIcons = { 160, 16, 16, IconBits }; enum Icons2Idx { IcoApps, IcoHome, IcoDesktop, IcoDocuments, IcoDownloads, IcoMovies, IcoMusic, IcoPhotos, IcoFolder, IcoComputer, IcoCDROM, IcoDrive, }; uint32_t Icons2[] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF7FFFFFF, 0xFFF56264, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x0E000000, 0xFFFFFF93, 0xFFFFFFFF, 0xFFFFFFFF, 0xC3FFFFFF, 0x04042368, 0xFFB86421, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x5487BCEF, 0xFFFF1821, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xBFFFFFFF, 0x06062568, 0xFFBF6825, 0xFFFFFFFF, 0xFFFFFFFF, 0x43B0FFFF, 0x31313131, 0x31313131, 0xB0413131, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x62A4F8FF, 0xA8664D45, 0xFFFFFFF8, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x180089FF, 0xFFFFFFF5, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x31DEFFFF, 0xDD2F0000, 0xFF0000FF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9800FFFF, 0x02319498, 0xFFFF8029, 0xFFFFFFFF, 0xFFFFFFFF, 0x0445DFFF, 0x1616120C, 0x3D040C14, 0xFFFFFFD5, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x23568CCF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0060FFFF, 0x88000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x4141D9FF, 0xB2B2A483, 0x414180A4, 0xFFFFFFD7, 0xFFFFFFFF, 0xBA35F6FF, 0xD9D9D9D9, 0xD9D9D9D9, 0x35BAD9D9, 0xFFFFFFF3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x5E1F0E92, 0x2C4D4D76, 0xFFFF900F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000D4FF, 0xFFFFFF7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0010B5FF, 0x0E000000, 0xFF0000B4, 0xFFFFFFFF, 0x1DC3FFFF, 0x00000000, 0x00000000, 0x00000000, 0xC11A0000, 0xFFFFFFFF, 0xFEFEFFFF, 0x9800FFFF, 0x00969898, 0xFF7E1AE3, 0xFFFFFFFF, 0xFFFFFFFF, 0x160C10C9, 0x16161616, 0x0C161616, 0xFFFFBC09, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000004, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xE500FFFF, 0x00CBE5E5, 0xFFFFFF98, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC9900CC5, 0xB4B4B4C5, 0x78B4B4B4, 0xFFFFC30B, 0xFFFFFFFF, 0xD937D3FF, 0x1212127C, 0xD5701212, 0x37D9BAB2, 0xFFFFFFD3, 0x087EFFFF, 0x00000000, 0x00000000, 0x00000000, 0x7E080000, 0xFFFFFFFF, 0x3FF3FFFF, 0x94C15A16, 0x73C17070, 0xF442285B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x006899A9, 0xFFFFE006, 0xFFFFFFFF, 0xFFFFFFFF, 0xFDFFFFFF, 0x0E000080, 0x000EABAB, 0xFF000000, 0xFFFFFFFF, 0xC321FFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x1FC1FFFF, 0xFFFFFFFF, 0x000000FF, 0x47000000, 0x00989898, 0x7E18D6FF, 0xFFFFFFFF, 0xDFFFFFFF, 0x16160E12, 0x00000008, 0x16160800, 0xFFD7090E, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0x0200F3FF, 0x0014BCFF, 0x14000000, 0xFFFFFFBE, 0xFFFFFFFF, 0xE500FFFF, 0xBAE5E5E5, 0x00000060, 0x00000000, 0x64000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD9FFFFFF, 0xE7FFAC0E, 0xB4B4B6C3, 0xB4B4B4B4, 0xFFD70D81, 0xFFFFFFFF, 0xD943C3FF, 0xD9D9D9D9, 0xA2CBD9D9, 0x43D9B2C7, 0xFFFFFFBF, 0xCD0AFFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x0ACDCDCD, 0xFFFFFFFF, 0x2339FCFF, 0x6A92AA68, 0x6BBA6B6B, 0x3C249298, 0xFFFFFFFE, 0xFFFFFFFF, 0xDCFFFFFF, 0x0E9D0004, 0xFFFF5800, 0xFFFFFFFF, 0xFFFFFFFF, 0x49ECFFFF, 0xD22D0000, 0x2BD1F2F2, 0xEB000000, 0xFFFFFFFF, 0x0800FFFF, 0x08080808, 0x08080808, 0x06060606, 0x00060606, 0xFFFFFFFF, 0xE3E300FF, 0x04025CD9, 0x0098983D, 0x25DDFFFF, 0xFFFFFF8C, 0x45FFFFFF, 0x1616160A, 0xFFFFFF00, 0x161600FF, 0xFF340B16, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x62270000, 0xFFFF007E, 0xFFFFFFFF, 0xFFFFFFFF, 0x66431FCF, 0x43666666, 0xFFFFD723, 0xFFFFFFFF, 0xE500FFFF, 0xE5E5E5E5, 0xE5E5E5E5, 0xE5E5E5E5, 0x00E5E5E5, 0xFFFFFFFF, 0x020274FF, 0x02020202, 0x02020202, 0x02020202, 0xFFFFFF74, 0x39FFFFFF, 0xFFFFFF8A, 0x76769CD5, 0xB2B4B494, 0xFF3877B2, 0xFFFFFFFF, 0xD954A8FF, 0xAAC3DBD9, 0xC7989E9E, 0x54D9AAF3, 0xFFFFFFAA, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0x641691FF, 0x6666B47A, 0x9CB66C66, 0x176365A6, 0xFFFFFF94, 0xFFFFFFFF, 0xA5FFFFFF, 0x8DEE1F7E, 0xFFC80000, 0xFFFFFFFF, 0xFFFFFFFF, 0x001FCCFF, 0x91EC5C00, 0xEB854343, 0x1D00005A, 0xFFFFFFCB, 0x9800FFFF, 0x97979798, 0x97979797, 0x96969696, 0x00969696, 0xFFFFFFFF, 0xE3E300FF, 0x8700DDE3, 0x00982502, 0x02000000, 0xFFFFFF0C, 0x02CDFFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0xB8031616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x763B0400, 0xFFFFEBB0, 0xFFFF00FF, 0xFFFFFFFF, 0x00000035, 0x2D583D12, 0x582D1212, 0x00000E39, 0xFFFF3D00, 0x1000FFFF, 0x00000000, 0x00000000, 0x00000000, 0x00100000, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xCDD1D1D1, 0xFFFFFF04, 0x41BCFFFF, 0xF6FCDBC9, 0x8A8A6074, 0xB2AE5A5E, 0xBA40B2B2, 0xFFFFFFFF, 0xD96698FF, 0xDBB2AAD5, 0xF3E5EDED, 0x66D9A6F3, 0xFFFFFF94, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0x604F10FB, 0x606068B4, 0x96FCD160, 0x92A66E60, 0xFFFFF611, 0xFFFFFFFF, 0x0AF7FFFF, 0xFDFFDE1F, 0xFF6A002F, 0xFFFFFFFF, 0xFFFFFFFF, 0x0400069B, 0x43F2F291, 0xF133DEDE, 0x00028EF1, 0xFFFF9C04, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xFE00E3E3, 0x98250AC5, 0x2D949898, 0xFFFFFF00, 0x0C74FFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0x620C1616, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE700, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x8C143966, 0x148CCFCD, 0x122F5239, 0xFFFF005E, 0x9C00FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x009CB2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x8366FFFF, 0x70C9BABA, 0xE7E7E794, 0xB25990E7, 0x6581B2B2, 0xFFFFFFFF, 0xD9767EFF, 0xCDF3BFB6, 0xF3CD8585, 0x76D99AF1, 0xFFFFFF7C, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xC9B125AC, 0x8C6060E5, 0x60DBC5B8, 0x5868A4AC, 0xFFFFA61B, 0xFFFFFFFF, 0x0082FFFF, 0xFFFFFF31, 0xE30000E1, 0xFFFFFFFF, 0xFFFFFFFF, 0xC20E0068, 0x43F2F2F2, 0xF133FFFF, 0x0AC1F1F1, 0xFFFF6600, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xFE00E3E3, 0x2B0EC9FE, 0x92969898, 0xFFFFFF00, 0x1231FFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0x1F141616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x62DB1F49, 0xD7620A0A, 0x12314721, 0xFFFF005E, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xA823FFFF, 0x5E94B4B4, 0x5E5EBFE7, 0x925FE7BF, 0x22A6B2B2, 0xFFFFFFFF, 0xD98C68FF, 0x6CF3EDA0, 0xF370D1D5, 0x8CD9A0ED, 0xFFFFFF68, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xFF622B66, 0x7EA2B6FF, 0xBCC35254, 0x5252526C, 0xFFFF642B, 0x0818FFFF, 0x0000E231, 0x0A0A23D8, 0x35006A7E, 0x166A78B0, 0xFFFFFFFF, 0xF2007AFE, 0x43F2F2F2, 0xF1334747, 0x00F1F1F1, 0xFFFFFE78, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0x0000E3E3, 0x00060000, 0x96969898, 0xFFFFFF00, 0x1414FFFF, 0x00081216, 0xFFFFFF00, 0x080000FF, 0x04161612, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x394BAE1F, 0x4B396060, 0x5E521DB0, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xB206FFFF, 0x8A76B4B4, 0xF6F65EE7, 0x7588E75E, 0x05B0B2B2, 0xFFFFFFFF, 0xD99E52FF, 0xAAF3EDA0, 0xF3A86E6E, 0x9CD9A0ED, 0xFFFFFF50, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xB8492F4D, 0x49494994, 0xA8FF7E49, 0x49494949, 0xFFFF4531, 0x0000FFFF, 0x640043AB, 0x00000064, 0x0002CD00, 0x0008A8C1, 0xFFFFFFFF, 0xF200FFFF, 0xE2F2F2F2, 0xF1DFD2D2, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x005ADDE3, 0x96969898, 0xFFFFFF00, 0x141AFFFF, 0xFF980816, 0xFFFFFFFF, 0x98FFFFFF, 0x04161608, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x64666600, 0x660ADF00, 0x0A666666, 0x666402DF, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xAC0CFFFF, 0x8A76B4B4, 0xF6F65EE7, 0x7588E55E, 0x05B2B2B2, 0xFFFFFFFF, 0xD9AC39FF, 0xF3F6C3B6, 0xF6F3EDED, 0xAED9B6C3, 0xFFFFFF39, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xA8413F49, 0x41414141, 0x83C98841, 0x41414141, 0xFFFF452B, 0x4308FFFF, 0xD20200C6, 0x0A0A0A0A, 0x8AB25A00, 0x1808C383, 0xFFFFFFFF, 0xF200FFFF, 0xF2F2F2F2, 0xF1F1F2F2, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00D9E3E3, 0x96969898, 0xFFFFFF00, 0x1235FFFF, 0xBC001216, 0xFFFFFFFF, 0x02BCFFFF, 0x21141612, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x64666600, 0x660CDD04, 0x0C666666, 0x666204DD, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xA227FFFF, 0x6094B4B4, 0x605EBFE7, 0x925FE5BF, 0x21A8B2B2, 0xFFFFFFFF, 0xD9C123FF, 0xE5BAAAD5, 0xBAE3F8FA, 0xC1D9D5AA, 0xFFFFFF23, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xA8435A5E, 0x3B3B3B3B, 0xB43FA83B, 0x3B3B3B3D, 0xFFFF702B, 0xF9FFFFFF, 0xFF9F0012, 0xFFFFFFFF, 0x47FFFFFF, 0xFFFF7E00, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00DDE3E3, 0x9696989A, 0xFFFFFF00, 0x0C6CFFFF, 0x0C0E1616, 0xFFFFFFDB, 0x0E0EDBFF, 0x640C1616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00F5, 0xFFFFFFFF, 0x66666600, 0x392DC716, 0x2D396666, 0x666614C9, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x885EFFFF, 0x5AB4B4B4, 0xE7E7E790, 0xC76F92E5, 0x5C85B8B8, 0xFFFFFFFF, 0xD9D30AFF, 0xAAC3DBD9, 0xC3AA9E9E, 0xD5D9D9DB, 0xFFFFFF08, 0xCD0AFFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x0ACDCDCD, 0xFFFFFFFF, 0xE1A814AC, 0x33333388, 0x56337C54, 0x70416EA2, 0xFFFFA62B, 0x51FFFFFF, 0xFFFB1200, 0xFFFFFFFF, 0xCBFFFFFF, 0xFFDBB03F, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x4B4B9798, 0x4B4B4B4B, 0x49494949, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00DDE3E3, 0x9696989A, 0xFFFFFF00, 0x02C7FFFF, 0x0C161616, 0xFFFFF323, 0x160C23F3, 0xB9041616, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFAFFFFFF, 0xFFFFFF00, 0x8EDDFFFF, 0xFFFF0035, 0xFFFFFFFF, 0x66666600, 0x2FD93741, 0xD92F0E0E, 0x66663F39, 0xFFFF0066, 0xB002FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B0B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x43BAFFFF, 0xAEB4B4B4, 0x888A5E5A, 0xFAF4735F, 0xB841C7D9, 0xFFFFFFFF, 0xD18C2FFF, 0xD9D9D9D9, 0xD9D9D9D9, 0x8ED1D9D9, 0xFFFFFF2F, 0x087EFFFF, 0x00000000, 0x00000000, 0x00000000, 0x7C080000, 0xFFFFFFFF, 0xFF720CFC, 0x2B2B49ED, 0x2B2B458E, 0x62EDFF9A, 0xFFFFF80C, 0x76F0FFFF, 0xFFFF9702, 0xFFFFFFFF, 0xFFFFFFFF, 0xE50C0094, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1006A00, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0xFF4B9798, 0xFFFF4BFF, 0x49FFFF4B, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0x00000000, 0xFFFFFF00, 0x45FFFFFF, 0x1616160A, 0xFFFF410A, 0x16160A41, 0xFF350C16, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x398CDBFF, 0xFFFFFF00, 0x000085FF, 0xFFFF0000, 0xFFFFFFFF, 0x66666600, 0xBC353966, 0x35BCDBDB, 0x66666639, 0xFFFF0066, 0xB302FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0xFFFF0202, 0x39FFFFFF, 0xB4B4B478, 0x757594B4, 0xFFFFD39A, 0xFF378CFF, 0xFFFFFFFF, 0x165000FF, 0x16161616, 0x16161616, 0x50161616, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x787878FF, 0xFFFFFF78, 0xFFFFFFFF, 0xFFFFFFFF, 0xA40A90FF, 0xAA947892, 0x9E98C9FF, 0x21BFFAAC, 0xFFFFFF96, 0x376EFFFF, 0xFFFFFEB7, 0xFFFFFFFF, 0xFFFFFFFF, 0x7C000083, 0xFFFFFFFF, 0xF300FFFF, 0x00F2F2F2, 0xF1005800, 0x00F1F1F1, 0xFFFFFFFF, 0x7421FFFF, 0xFF4B9798, 0xFFFF4BFF, 0x49FFFF4B, 0x1F749696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0xFEFEFFFE, 0xFFFFFFFF, 0xDFFFFFFF, 0x16160E12, 0x6A680816, 0x16161608, 0xFFD9080E, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x0000008A, 0xFFFFFF00, 0x00000CFF, 0xFFFF0C00, 0xFFFFFFFF, 0x66666600, 0x18436666, 0x43180000, 0x66666666, 0xFFFF0066, 0x9902FFFF, 0x98989899, 0x98989898, 0x98989898, 0x00989898, 0xFFFFFFFF, 0x02020280, 0x02020202, 0x02020202, 0x02020202, 0xFFFF7F02, 0xD9FFFFFF, 0xB4B4830E, 0xB2B2B2B4, 0xFFE5C1B4, 0xFFD90CAA, 0xFFFFFFFF, 0x1D9800FF, 0x98989898, 0x984B984B, 0x9898984B, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x9E9E9ED3, 0xFFFFD39E, 0xFFFFFFFF, 0xFFFFFFFF, 0x0A3BFFFF, 0x2F1D1DA4, 0x1D1D50D1, 0x3F081D1D, 0xFFFFFFFF, 0x2129FFFF, 0xFFFFFFE2, 0xFFFFFFFF, 0xFFFFFFFF, 0x450039F1, 0xFFFFFFFF, 0xF300FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0x10C3FFFF, 0x00000000, 0x00000000, 0x00000000, 0xBE100000, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x160C0EC9, 0x0C0C1616, 0x0C161616, 0xFFFFBE0A, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x0000000C, 0xFFFFFF04, 0x00001AFF, 0xFFFF4D00, 0xFFFFFFFF, 0x00000045, 0x00000000, 0x00000000, 0x00000000, 0xFFFF4300, 0x0235FFFF, 0x00020202, 0x00000000, 0x00000000, 0x31000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xB4780CC5, 0xB2B2B2B2, 0x8CC7C3B2, 0xFFFFC30A, 0xFFFFFFFF, 0x1D9800FF, 0x98989898, 0x984B984B, 0x9800984B, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x64646464, 0xFFFF6464, 0xFFFFFFFF, 0xFFFFFFFF, 0x39F6FFFF, 0x1616391D, 0x16161694, 0xF63D0610, 0xFFFFFFFF, 0xE82BFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x665CFEFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x043DDDFF, 0x1616120C, 0x35040C14, 0xFFFFFFD5, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0x0000001F, 0xFFFFFF43, 0x181DB8FF, 0xFFFFED5C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x4239D9FF, 0xACB0A481, 0x374180A0, 0xFFFFFFD7, 0xFFFFFFFF, 0x1D7810FF, 0x98989898, 0x984B984B, 0x7898984B, 0xFFFFFF10, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x080C1088, 0x08080C62, 0xFFFE880A, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xBEFFFFFF, 0x020A2766, 0xFFB6621F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x6E2521B8, 0xFFFFFFF1, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xB8FFFFFF, 0x08022164, 0xFFB86427, 0xFFFFFFFF, 0xFFFFFFFF, 0x00028CFF, 0x00000000, 0x00000000, 0x02000000, 0xFFFFFF8C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x62A8FAFF, 0xAC624349, 0xFFFFFFFA, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, }; LInlineBmp TreeIconsImg = { 308, 22, 8, Icons2 }; ////////////////////////////////////////////////////////////////////////// char *LFileType::DefaultExtension() { char *Status = 0; auto T = LString(Extension()).SplitDelimit(";"); if (T.Length()) { char s[256]; strcpy(s, T[0]); char *Dir = strchr(s, '.'); if (Dir) { Status = NewStr(Dir+1); if (Status) strlwr(Status); } } return Status; } ////////////////////////////////////////////////////////////////////////// class LFolderItem : public LListItem { LFileSelectDlg *Dlg; LString Path; public: char *File; bool IsDir; LFolderItem(LFileSelectDlg *dlg, char *FullPath, LDirectory *Dir); ~LFolderItem(); void OnActivate(); const char *GetText(int i); int GetImage(int Flags); void OnSelect(); void OnDelete(bool Ask = true); void OnRename(); void OnMouseClick(LMouse &m); }; ////////////////////////////////////////////////////////////////////////// // This is just a private data container to make it easier to change the // implementation of this class without effecting headers and applications. class LFileSelectPrivate { friend class LFileSelect; friend class LFileSelectDlg; friend class LFolderList; - LView *Parent; - LFileSelect *Select; + LView *Parent = NULL; + LFileSelect *Select = NULL; - DlgType Type; - char *Title; - char *DefExt; - bool MultiSelect; + DlgType Type = TypeNone; + LString Title; + LString DefExt; + bool MultiSelect = false; List Files; - int CurrentType; + int CurrentType = -1; List Types; List History; - bool ShowReadOnly; - bool ReadOnly; - - bool EatClose; + bool ShowReadOnly = false; + bool ReadOnly = false; + bool EatClose = false; public: static LImageList *BtnIcons, *TreeIcons; - static char *InitPath; + static LString InitPath; static bool InitShowHiddenFiles; static LRect InitSize; LFileSelectPrivate(LFileSelect *select) { - ShowReadOnly = false; - ReadOnly = false; - EatClose = false; Select = select; - Type = TypeNone; - Title = 0; - DefExt = 0; - MultiSelect = false; - Parent = 0; - CurrentType = -1; if (!BtnIcons) BtnIcons = new LImageList(16, 16, FileSelectIcons.Create(0xF81F)); if (!TreeIcons) { LAutoPtr a(TreeIconsImg.Create(0xF81F)); LAutoPtr m(new LMemDC(a->X(), a->Y(), System32BitColourSpace)); if (a && m) { LColour fore(128, 128, 128); for (int y=0; yY(); y++) { uint8_t *i = (uint8_t*)(*a)[y]; auto *e = i + a->X(); System32BitPixel *o = (System32BitPixel*)(*m)[y]; while (i < e) { o->r = (int)fore.r() * *i / 255; o->g = (int)fore.g() * *i / 255; o->b = (int)fore.b() * *i / 255; o->a = *i; i++; o++; } } TreeIcons = new LImageList(22, 22, m.Release()); } } } virtual ~LFileSelectPrivate() { - DeleteArray(Title); - DeleteArray(DefExt); - Types.DeleteObjects(); Files.DeleteArrays(); History.DeleteArrays(); } }; LImageList *LFileSelectPrivate::BtnIcons = NULL; LImageList *LFileSelectPrivate::TreeIcons = NULL; -char *LFileSelectPrivate::InitPath = 0; +LString LFileSelectPrivate::InitPath; bool LFileSelectPrivate::InitShowHiddenFiles = false; LRect LFileSelectPrivate::InitSize(0, 0, -1, -1); ////////////////////////////////////////////////////////////////////////// // This class implements the UI for the selector. class LFileSelectDlg; class LFolderView { protected: LFileSelectDlg *Dlg; public: LFolderView(LFileSelectDlg *dlg) { Dlg = dlg; } virtual void OnFolder() {} }; class LFolderDrop : public LDropDown, public LFolderView { public: LFolderDrop(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); void OnFolder(); bool OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = Inf.Width.Max = 18; return true; } }; class LIconButton : public LLayout { LImageList *Icons; int Icon; bool Down; public: LIconButton(int Id, int x, int y, int cx, int cy, LImageList *icons, int icon) { Icons = icons; Icon = icon; SetId(Id); LRect r(x, y, x+cx, y+cy); SetPos(r); Down = false; SetTabStop(true); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); LColour Background(L_MED); c.Offset(-c.x1, -c.y1); LWideBorder(pDC, c, Down ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(Background); pDC->Rectangle(&c); int x = (c.X()-Icons->TileX()) / 2; int y = (c.Y()-Icons->TileY()) / 2; if (Focus()) { #if WINNATIVE RECT r = c; DrawFocusRect(pDC->Handle(), &r); #endif } Icons->Draw(pDC, c.x1+x+Down, c.y1+y+Down, Icon, Background, !Enabled()); } void OnFocus(bool f) { Invalidate(); } void OnMouseClick(LMouse &m) { if (Enabled()) { bool Trigger = Down && !m.Down(); Capture(Down = m.Down()); if (Down) Focus(true); Invalidate(); if (Trigger) { LViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, LNotification(m)); } } } void OnMouseEnter(LMouse &m) { if (IsCapturing()) { Down = true; Invalidate(); } } void OnMouseExit(LMouse &m) { if (IsCapturing()) { Down = false; Invalidate(); } } bool OnKey(LKey &k) { if (k.c16 == ' ' || k.c16 == LK_RETURN) { if (Enabled() && Down ^ k.Down()) { Down = k.Down(); Invalidate(); if (!Down) { LViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, LNotifyActivate); } } return true; } return false; } bool OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = Inf.Width.Max = Icons->TileX() + 4; Inf.Width.Max += 4; Inf.Height.Min = Inf.Height.Max = Icons->TileY() + 4; Inf.Height.Max += 4; return true; } }; class LFolderList : public LList, public LFolderView { LString FilterKey; public: LFolderList(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); - ~LFolderList() - { - } void OnFolder(); bool OnKey(LKey &k); void SetFilterKey(LString s) { FilterKey = s; OnFolder(); } }; -#define IDC_STATIC -1 -#define IDD_FILE_SELECT 1000 -#define IDC_PATH 1002 -#define IDC_DROP 1003 -#define IDC_BACK 1004 -#define IDC_UP 1005 -#define IDC_NEW 1006 -#define IDC_VIEW 1007 -#define IDC_FILE 1010 -#define IDC_TYPE 1011 -#define IDC_SHOWHIDDEN 1012 -#define IDC_SUB_TBL 1013 -#define IDC_BOOKMARKS 1014 -#define IDC_FILTER 1015 -#define IDC_FILTER_CLEAR 1016 +enum Ctrls +{ + IDC_STATIC = -1, + IDD_FILE_SELECT = 1000, + IDC_PATH, + IDC_DROP, + IDC_BACK, + IDC_UP, + IDC_NEW, + IDC_VIEW, + IDC_FILE, + IDC_TYPE, + IDC_SHOWHIDDEN, + IDC_SUB_TBL, + IDC_BOOKMARKS, + IDC_FILTER, + IDC_FILTER_CLEAR, +}; #if 1 #define USE_FOLDER_CTRL 1 enum FolderCtrlMessages { M_DELETE_EDIT = M_USER + 100, M_NOTIFY_VALUE_CHANGED, M_LOST_FOCUS, }; class FolderCtrlEdit : public LEdit { public: FolderCtrlEdit(int id, LRect c) : LEdit(id, c.x1, c.y1, c.X()-1, c.Y()-1) { } void OnFocus(bool f) { printf("OnFocus(%i)\n", f); if (!f && GetParent()) GetParent()->PostEvent(M_LOST_FOCUS); } }; class FolderCtrl : public LView { struct Part { LAutoPtr ds; LRect Arrow; LRect Text; }; LEdit *e; LArray p; Part *Over; ssize_t Cursor; Part *HitPart(int x, int y, int *Sub = NULL) { for (unsigned i=0; iGetHeight() + 4; Inf.Height.Max = Inf.Height.Min; } return true; } LString NameAt(ssize_t Level) { LString n; #ifndef WINDOWS n += "/"; #endif for (unsigned i=0; i<=Level && iTransparent(false); LDisplayString Arrow(f, ">"); for (unsigned i=0; iArrow.ZOff(Arrow.X()+1, c.Y()-1); n->Arrow.Offset(c.x1, c.y1); f->Colour(LColour(192,192,192), Bk); Arrow.DrawCenter(pDC, &n->Arrow); c.x1 = n->Arrow.x2 + 1; if (n->ds) { // Layout and draw text n->Text.ZOff(n->ds->X() + 4, c.Y()-1); n->Text.Offset(c.x1, c.y1); f->Colour(Fore, Bk); n->ds->DrawCenter(pDC, &n->Text); c.x1 = n->Text.x2 + 1; } } if (p.Length() == 0) { // Layout and draw arrow for the "root folder" f->Colour(LColour(192,192,192), L_WORKSPACE); LRect a; a.ZOff(Arrow.X()+1, c.Y()-1); a.Offset(c.x1, c.y1); Arrow.DrawCenter(pDC, &a); c.x1 = a.x2 + 1; } pDC->Colour(L_WORKSPACE); pDC->Rectangle(&c); } void ExitEditMode() { if (e) { Name(e->Name()); DeleteObj(e); PostEvent(M_DELETE_EDIT); PostEvent(M_NOTIFY_VALUE_CHANGED); Invalidate(); } } void OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { } else if (m.Left()) { if (m.Down()) { Over = HitPart(m.x, m.y); if (p.PtrCheck(Over)) { // Over a path node... Cursor = Over - p.AddressOf(0); Part &o = p[Cursor]; Invalidate(); SendNotify(LNotifyValueChanged); if (o.Arrow.Overlap(m.x, m.y)) { // Show sub-menu at this level ShowMenu(Cursor); } } else if (!e) { // In empty space LRect c = GetClient(); e = new FolderCtrlEdit(GetId()+1, c); if (e) { e->Attach(this); LString s = Name(); e->Name(s); e->SetCaret(s.Length()); e->Focus(true); } } } } } void OnMouseMove(LMouse &m) { Part *o = Over; Over = HitPart(m.x, m.y); if (o != Over) Invalidate(); } void OnMouseExit(LMouse &m) { if (Over) { Over = NULL; Invalidate(); } } int OnNotify(LViewI *c, LNotification n) { if (e != NULL && c->GetId() == e->GetId()) { if (n.Type == LNotifyReturnKey) { ExitEditMode(); } } return 0; } LMessage::Result OnEvent(LMessage *m) { switch (m->Msg()) { case M_LOST_FOCUS: { ExitEditMode(); break; } case M_DELETE_EDIT: { DeleteObj(e); break; } case M_NOTIFY_VALUE_CHANGED: { SendNotify(LNotifyValueChanged); break; } } return LView::OnEvent(m); } virtual bool ShowMenu(ssize_t Level) { if (Level <= 0) return false; LString dir = NameAt(Level-1); LSubMenu s; LDirectory d; LString::Array Opts; for (int b = d.First(dir); b; b = d.Next()) { if (d.IsDir()) { Opts.New() = d.GetName(); s.AppendItem(d.GetName(), (int)Opts.Length()); } } Part &i = p[Level]; LPoint pt(i.Arrow.x1, i.Arrow.y2+1); PointToScreen(pt); int Cmd = s.Float(this, pt.x, pt.y, true); if (Cmd) { LString np; np = dir + DIR_STR + Opts[Cmd-1]; Name(np); PostEvent(M_NOTIFY_VALUE_CHANGED); } else return false; return true; } }; #else #define USE_FOLDER_CTRL 0 #endif class LFileSelectDlg : public LDialog { LRect OldPos; LRect MinSize; LArray Links; LArray Hidden; public: LFileSelectPrivate *d = NULL; LTableLayout *Tbl = NULL; LBox *Sub = NULL; LTree *Bookmarks = NULL; LTextLabel *Ctrl1 = NULL; #if USE_FOLDER_CTRL FolderCtrl *Ctrl2 = NULL; #else LEdit *Ctrl2 = NULL; #endif LFolderDrop *Ctrl3 = NULL; LIconButton *BackBtn = NULL; LIconButton *UpBtn = NULL; LIconButton *NewDirBtn = NULL; LFolderList *FileLst = NULL; LTextLabel *Ctrl8 = NULL; LTextLabel *Ctrl9 = NULL; LEdit *FileNameEdit = NULL; LCombo *FileTypeCbo = NULL; LButton *SaveBtn = NULL; LButton *CancelBtn = NULL; LCheckBox *ShowHidden = NULL; LEdit *FilterEdit = NULL; LFileSelectDlg(LFileSelectPrivate *Select); ~LFileSelectDlg(); const char *GetClass() override { return "LFileSelectDlg"; } int OnNotify(LViewI *Ctrl, LNotification n); void OnUpFolder(); void SetFolder(char *f); void OnFolder(); void OnFile(char *f); void OnFilter(const char *Key); bool OnViewKey(LView *v, LKey &k) { if (k.vkey == LK_UP && k.Alt()) { if (k.Down()) { UpBtn->SendNotify(); } return true; } return false; } void Add(LTreeItem *i, LVolume *v) { if (!i || !v) return; auto Path = v->Path(); i->SetText(v->Name()); i->SetText(Path, 1); for (unsigned n=0; nFirst(); cv; cv = cv->Next()) { LTreeItem *ci = new LTreeItem; if (ci) { i->Insert(ci); Add(ci, cv); } } i->Expanded(true); } }; LFileSelectDlg::LFileSelectDlg(LFileSelectPrivate *select) { d = select; SetParent(d->Parent); MinSize.ZOff(450, 300); if (!d->InitSize.Valid()) { auto Dpi = LScreenDpi(); auto Scale = (float)Dpi.x / 96.0; d->InitSize.Set(0, 0, (int)(650*Scale), (int)(450*Scale) + LAppInst->GetMetric(LGI_MET_DECOR_Y) ); } SetPos(d->InitSize); int x = 0, y = 0; AddView(Tbl = new LTableLayout); // Top Row auto *c = Tbl->GetCell(x++, y); c->Add(Ctrl1 = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Look in:")); c->VerticalAlign(LCss::Len(LCss::VerticalMiddle)); c = Tbl->GetCell(x++, y); #if USE_FOLDER_CTRL c->Add(Ctrl2 = new FolderCtrl(IDC_PATH)); #else c->Add(Ctrl2 = new LEdit(IDC_PATH, 0, 0, 245, 21, "")); #endif c = Tbl->GetCell(x++, y); c->Add(Ctrl3 = new LFolderDrop(this, IDC_DROP, 336, 7, 16, 21)); c = Tbl->GetCell(x++, y); c->Add(BackBtn = new LIconButton(IDC_BACK, 378, 7, 27, 21, d->BtnIcons, FSI_BACK)); c = Tbl->GetCell(x++, y); c->Add(UpBtn = new LIconButton(IDC_UP, 406, 7, 27, 21, d->BtnIcons, FSI_UPDIR)); c = Tbl->GetCell(x++, y); c->Add(NewDirBtn = new LIconButton(IDC_NEW, 434, 7, 27, 21, d->BtnIcons, FSI_NEWDIR)); // Folders/items row x = 0; y++; c = Tbl->GetCell(x, y, true, 6, 1); c->Add(Sub = new LBox(IDC_SUB_TBL)); Sub->AddView(Bookmarks = new LTree(IDC_BOOKMARKS, 0, 0, -1, -1)); Bookmarks->GetCss(true)->Width(LCss::Len(LCss::LenPx, 150.0f)); Bookmarks->SetImageList(d->TreeIcons, false); LTableLayout *t; Sub->AddView(t = new LTableLayout(11)); // Filter / search row c = t->GetCell(0, 0); c->Add(new LCheckBox(IDC_FILTER_CLEAR, 0, 0, -1, -1, "Filter items:")); c->VerticalAlign(LCss::Len(LCss::VerticalMiddle)); c = t->GetCell(1, 0); c->Add(FilterEdit = new LEdit(IDC_FILTER, 0, 0, 60, 20)); c = t->GetCell(0, 1, true, 2); c->Add(FileLst = new LFolderList(this, IDC_VIEW, 14, 35, 448, 226)); // File name row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl8 = new LTextLabel(IDC_STATIC, 14, 275, -1, -1, "File name:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileNameEdit = new LEdit(IDC_FILE, 100, 268, 266, 21, "")); c = Tbl->GetCell(x, y, true, 3); c->Add(SaveBtn = new LButton(IDOK, 392, 268, 70, 21, "Ok")); // 4th row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl9 = new LTextLabel(IDC_STATIC, 14, 303, -1, -1, "Files of type:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileTypeCbo = new LCombo(IDC_TYPE, 100, 296, 266, 21, "")); c = Tbl->GetCell(x++, y, true, 3); c->Add(CancelBtn = new LButton(IDCANCEL, 392, 296, 70, 21, "Cancel")); // 5th row x = 0; y++; c = Tbl->GetCell(x++, y, true, 6); c->Add(ShowHidden = new LCheckBox(IDC_SHOWHIDDEN, 14, 326, -1, -1, "Show hidden files.")); // Init if (BackBtn) BackBtn->Enabled(false); if (SaveBtn) SaveBtn->Enabled(false); if (FileLst) FileLst->MultiSelect(d->MultiSelect); if (ShowHidden) ShowHidden->Value(d->InitShowHiddenFiles); // Load types if (!d->Types.Length()) { LFileType *t = new LFileType; if (t) { t->Description("All Files"); t->Extension(LGI_ALL_FILES); d->Types.Insert(t); } } for (auto t: d->Types) { char s[256]; sprintf(s, "%s (%s)", t->Description(), t->Extension()); if (FileTypeCbo) FileTypeCbo->Insert(s); } d->CurrentType = 0; // File + Path char *File = d->Files[0]; if (File) { char *Dir = strrchr(File, DIR_CHAR); if (Dir) { OnFile(Dir + 1); } else { OnFile(File); } } if (d->InitPath) { SetFolder(d->InitPath); } else { SetFolder(LGetExePath()); } OnFolder(); // Size/layout SetPos(d->InitSize); MoveToCenter(); RegisterHook(this, LKeyEvents); FileLst->Focus(true); LgiGetUsersLinks(Links); auto v = FileDev->GetRootVolume(); if (v) { for (auto vol = v; vol; vol = vol->Next()) { if (auto *ti = new LTreeItem) { Bookmarks->Insert(ti); Add(ti, vol); ti->Expanded(true); } } } if (Links.Length()) { if (auto *ti = new LTreeItem) { ti->SetText("Bookmarks"); Bookmarks->Insert(ti); for (unsigned n=0; nSetText(leaf?leaf+1:p, 0); ci->SetText(p, 1); ti->Insert(ci); } } ti->Expanded(true); } } } LFileSelectDlg::~LFileSelectDlg() { UnregisterHook(this); d->InitShowHiddenFiles = ShowHidden ? ShowHidden->Value() : false; d->InitSize = GetPos(); auto CurPath = GetCtrlName(IDC_PATH); if (ValidStr(CurPath)) - { - DeleteArray(d->InitPath); - d->InitPath = NewStr(CurPath); - } + d->InitPath = CurPath; } void LFileSelectDlg::OnFile(char *f) { if (d->Type != TypeOpenFolder) { FileNameEdit->Name(f ? f : (char*)""); SaveBtn->Enabled(ValidStr(f)); } } void LFileSelectDlg::SetFolder(char *f) { auto CurPath = GetCtrlName(IDC_PATH); if (CurPath) { d->History.Insert(NewStr(CurPath)); SetCtrlEnabled(IDC_BACK, true); } SetCtrlName(IDC_PATH, f); } void LFileSelectDlg::OnFolder() { if (Ctrl3) Ctrl3->OnFolder(); if (FileLst) FileLst->OnFolder(); auto CurPath = GetCtrlName(IDC_PATH); if (CurPath && UpBtn) UpBtn->Enabled(strlen(CurPath)>3); } void LFileSelectDlg::OnUpFolder() { auto Cur = GetCtrlName(IDC_PATH); if (Cur) { char Dir[MAX_PATH_LEN]; strcpy(Dir, Cur); if (strlen(Dir) > 3) { LTrimDir(Dir); if (!strchr(Dir, DIR_CHAR)) strcat(Dir, DIR_STR); SetFolder(Dir); OnFolder(); } } } - - void LFileSelectDlg::OnFilter(const char *Key) { if (FileLst) FileLst->SetFilterKey(Key); } int LFileSelectDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BOOKMARKS: { if (n.Type == LNotifyItemSelect && Bookmarks) { LTreeItem *s = Bookmarks->Selection(); if (s) { const char *p = s->GetText(1); if (LDirExists(p)) { SetCtrlName(IDC_PATH, p); OnFolder(); } } } break; } case IDC_PATH: { if (n.Type == LNotifyValueChanged) OnFolder(); break; } case IDC_VIEW: { if (FileLst) { /* These functions are handled by the list control's OnKey implementation if (Flags == GLIST_NOTIFY_RETURN) { List s; if (FileLst->GetSelection(s)) { LFolderItem *i = dynamic_cast(s.First()); if (i) { i->OnActivate(); } } } else if (Flags == GLIST_NOTIFY_BACKSPACE) { OnUpFolder(); } */ } break; } case IDC_FILE: { auto f = Ctrl->Name(); if (!f) break; if (n.Type == LNotifyReturnKey) { // allow user to insert new type by typing the pattern into the file name edit box and // hitting enter if (strchr(f, '?') || strchr(f, '*')) { // it's a mask, push the new type on the the type stack and // refilter the content int TypeIndex = -1; int n = 0; for (auto t: d->Types) { if (t->Extension() && stricmp(t->Extension(), f) == 0) { TypeIndex = n; break; } n++; } // insert the new type if not already there if (TypeIndex < 0) { LFileType *n = new LFileType; if (n) { n->Description(f); n->Extension(f); TypeIndex = (int)d->Types.Length(); d->Types.Insert(n); FileTypeCbo->Insert(f); } } // select the new type if (TypeIndex >= 0) { FileTypeCbo->Value(d->CurrentType = TypeIndex); } // clear the edit box Ctrl->Name(""); // Update and don't do normal save btn processing. OnFolder(); // Skip the IDOK message generated by the default button d->EatClose = true; printf("%s:%i - eat close true\n", _FL); break; } if (LDirExists(f)) { // Switch to the folder... SetCtrlName(IDC_PATH, f); OnFolder(); Ctrl->Name(NULL); d->EatClose = true; } else if (LFileExists(f)) { // Select the file... d->Files.Insert(NewStr(f)); EndModal(IDOK); break; } } bool HasFile = ValidStr(f); bool BtnEnabled = SaveBtn->Enabled(); if (HasFile ^ BtnEnabled) { SaveBtn->Enabled(HasFile); } break; } case IDC_BACK: { auto It = d->History.rbegin(); char *Dir = *It; if (Dir) { d->History.Delete(Dir); SetCtrlName(IDC_PATH, Dir); OnFolder(); DeleteArray(Dir); if (!d->History[0]) { SetCtrlEnabled(IDC_BACK, false); } } break; } case IDC_SHOWHIDDEN: { FileLst->OnFolder(); break; } case IDC_TYPE: { d->CurrentType = (int)FileTypeCbo->Value(); FileLst->OnFolder(); if (d->Type == TypeSaveFile) { // change extension of current file LFileType *Type = d->Types.ItemAt(d->CurrentType); auto File = FileNameEdit->Name(); if (Type && File) { char *Ext = strchr(File, '.'); if (Ext) { char *DefExt = Type->DefaultExtension(); if (DefExt) { Ext++; char s[256]; ZeroObj(s); memcpy(s, File, Ext-File); strcat(s, DefExt); OnFile(s); DeleteArray(DefExt); } } } } break; } case IDC_UP: { OnUpFolder(); break; } case IDC_FILTER: { const char *n = Ctrl->Name(); SetCtrlValue(IDC_FILTER_CLEAR, ValidStr(n)); OnFilter(n); break; } case IDC_FILTER_CLEAR: { if (!Ctrl->Value()) { SetCtrlName(IDC_FILTER, NULL); OnFilter(NULL); } break; } case IDC_NEW: { LInput Dlg(this, "", "Create new folder:", "New Folder"); Dlg.DoModal([&](auto d, auto code) { char New[MAX_PATH_LEN]; strcpy(New, GetCtrlName(IDC_PATH)); if (New[strlen(New)-1] != DIR_CHAR) strcat(New, DIR_STR); strcat(New, Dlg.GetStr()); FileDev->CreateFolder(New); OnFolder(); }); break; } case IDOK: { if (d->EatClose) { printf("%s:%i - SKIPPING eat close false\n", _FL); d->EatClose = false; break; } auto Path = GetCtrlName(IDC_PATH); auto File = GetCtrlName(IDC_FILE); if (Path) { char f[MAX_PATH_LEN]; d->Files.DeleteArrays(); if (d->Type == TypeOpenFolder) { d->Files.Insert(NewStr(Path)); } else { List Sel; if (d->Type != TypeSaveFile && FileLst && FileLst->GetSelection(Sel) && Sel.Length() > 1) { for (auto i: Sel) { LMakePath(f, sizeof(f), Path, i->GetText(0)); d->Files.Insert(NewStr(f)); } } else if (ValidStr(File)) { if (strchr(File, DIR_CHAR)) strcpy_s(f, sizeof(f), File); else LMakePath(f, sizeof(f), Path, File); d->Files.Insert(NewStr(f)); } } } // fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } ////////////////////////////////////////////////////////////////////////// class LFileSystemItem : public LTreeItem { class LFileSystemPopup *Popup; LString Path; public: LFileSystemItem(LFileSystemPopup *popup, LVolume *vol, char *path = 0); char *GetPath() { return Path; } void OnPath(const char *p); void OnMouseClick(LMouse &m); bool OnKey(LKey &k); }; #define IDC_TREE 100 class LFileSystemPopup : public LPopup { friend class LFileSystemItem; LFileSelectDlg *Dlg; LTree *Tree; LFileSystemItem *Root; public: LFileSystemPopup(LView *owner, LFileSelectDlg *dlg, int x) : LPopup(owner) { Dlg = dlg; LRect r(0, 0, x, 150); SetPos(r); Children.Insert(Tree = new LTree(IDC_TREE, 1, 1, X()-3, Y()-3)); if (Tree) { Tree->Sunken(false); LVolume *v = FileDev->GetRootVolume(); if (v) { Tree->SetImageList(Dlg->d->BtnIcons, false); Tree->Insert(Root = new LFileSystemItem(this, v)); for (auto next = v->Next(); next; next = next->Next()) { Tree->SetImageList(Dlg->d->BtnIcons, false); Tree->Insert(Root = new LFileSystemItem(this, next)); } } } } ~LFileSystemPopup() { } void Visible(bool i) { if (i && Root) { Root->OnPath(Dlg->GetCtrlName(IDC_PATH)); } LPopup::Visible(i); } void OnPaint(LSurface *pDC) { // Draw border LRect c = GetClient(); c.Offset(-c.x1, -c.y1); pDC->Colour(L_BLACK); pDC->Box(&c); c.Inset(1, 1); } void OnActivate(LFileSystemItem *i) { if (i) { Dlg->SetFolder(i->GetPath()); Dlg->OnFolder(); Visible(false); } } }; LFileSystemItem::LFileSystemItem(LFileSystemPopup *popup, LVolume *Vol, char *path) { Popup = popup; Expanded(true); if (Vol) { Path = Vol->Path(); SetText(Vol->Name()); switch (Vol->Type()) { case VT_FLOPPY: SetImage(FSI_FLOPPY); break; case VT_HARDDISK: case VT_RAMDISK: SetImage(FSI_HARDDISK); break; case VT_CDROM: SetImage(FSI_CDROM); break; case VT_NETWORK_SHARE: SetImage(FSI_NETWORK); break; case VT_DESKTOP: SetImage(FSI_DESKTOP); break; default: SetImage(FSI_DIRECTORY); break; } for (LVolume *v=Vol->First(); v; v=v->Next()) { Insert(new LFileSystemItem(Popup, v)); } } else { Path = NewStr(path); SetText(strrchr(Path, DIR_CHAR)+1); SetImage(FSI_DIRECTORY); } } void LFileSystemItem::OnPath(const char *p) { switch (GetImage()) { case FSI_DESKTOP: { if (p && Path && stricmp(Path, p) == 0) { Select(true); p = 0; } break; } case FSI_DIRECTORY: { return; } default: { LTreeItem *Old = Items[0]; if (Old) { Old->Remove(); DeleteObj(Old); } break; } } if (p) { auto PathLen = strlen(Path); if (Path && strnicmp(Path, p, PathLen) == 0 && (p[PathLen] == DIR_CHAR || p[PathLen] == 0) #ifdef LINUX && strcmp(Path, "/") != 0 #endif ) { LTreeItem *Item = this; if (GetImage() != FSI_DESKTOP && strlen(p) > 3) { auto Start = p + strlen(Path); if (Start) { char s[256]; strcpy(s, Path); auto T = LString(Start).SplitDelimit(DIR_STR); for (int i=0; iInsert(New); Item = New; } } } } if (Item) { Item->Select(true); } } } for (auto item: Items) { LFileSystemItem *i = dynamic_cast(item); if (i) i->OnPath(p); } } void LFileSystemItem::OnMouseClick(LMouse &m) { if (m.Left() && m.Down()) { Popup->OnActivate(this); } } bool LFileSystemItem::OnKey(LKey &k) { if ((k.c16 == ' ' || k.c16 == LK_RETURN)) { if (k.Down() && k.IsChar) { Popup->OnActivate(this); } return true; } return false; } LFolderDrop::LFolderDrop(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : LDropDown(Id, x, y, cx, cy, 0), LFolderView(dlg) { SetPopup(new LFileSystemPopup(this, dlg, cx + (dlg->Ctrl2 ? dlg->Ctrl2->X() : 0) )); } void LFolderDrop::OnFolder() { } ////////////////////////////////////////////////////////////////////////// #define IDM_OPEN 1000 #define IDM_CUT 1001 #define IDM_COPY 1002 #define IDM_RENAME 1003 #define IDM_PROPERTIES 1004 #define IDM_CREATE_SHORTCUT 1005 #define IDM_DELETE 1006 LFolderItem::LFolderItem(LFileSelectDlg *dlg, char *FullPath, LDirectory *Dir) { Dlg = dlg; Path = FullPath; File = strrchr(Path, DIR_CHAR); if (File) File++; IsDir = Dir->IsDir(); } LFolderItem::~LFolderItem() { } const char *LFolderItem::GetText(int i) { return File; } int LFolderItem::GetImage(int Flags) { return IsDir ? 1 : 0; } void LFolderItem::OnSelect() { if (!IsDir && File) { Dlg->OnFile(Select() ? File : 0); } } void LFolderItem::OnDelete(bool Ask) { if (!Ask || LgiMsg(Parent, "Do you want to delete '%s'?", ModuleName, MB_YESNO, Path.Get()) == IDYES) { bool Status = false; if (IsDir) { Status = FileDev->RemoveFolder(Path, true); } else { Status = FileDev->Delete(Path); } if (Status) { Parent->Remove(this); delete this; } } } void LFolderItem::OnRename() { LInput *Inp = new LInput(Dlg, File, "New name:", Dlg->Name()); Inp->DoModal([&](auto d, auto code) { if (!code) return; char Old[MAX_PATH_LEN]; strcpy_s(Old, sizeof(Old), Path); char New[MAX_PATH_LEN]; File[0] = 0; LMakePath(New, sizeof(New), Path, Inp->GetStr()); if (FileDev->Move(Old, New)) { DeleteArray(Path); Path = NewStr(New); File = strrchr(Path, DIR_CHAR); if (File) File++; Update(); } else { LgiMsg(Dlg, "Renaming '%s' failed.", Dlg->Name(), MB_OK); } delete Inp; }); } void LFolderItem::OnActivate() { if (File) { if (IsDir) { char Dir[256]; strcpy(Dir, Dlg->GetCtrlName(IDC_PATH)); if (Dir[strlen(Dir)-1] != DIR_CHAR) strcat(Dir, DIR_STR); strcat(Dir, File); Dlg->SetFolder(Dir); Dlg->OnFolder(); } else // Is file { Dlg->OnNotify(Dlg->SaveBtn, LNotifyActivate); } } } void LFolderItem::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { if (m.Double()) { OnActivate(); } } else if (m.Right()) { LSubMenu *RClick = new LSubMenu; if (RClick) { RClick->AppendItem("Select", IDM_OPEN, true); RClick->AppendSeparator(); RClick->AppendItem("Cut", IDM_CUT, false); RClick->AppendItem("Copy", IDM_COPY, false); RClick->AppendSeparator(); RClick->AppendItem("Create Shortcut", IDM_CREATE_SHORTCUT, false); RClick->AppendItem("Delete", IDM_DELETE, true); RClick->AppendItem("Rename", IDM_RENAME, true); RClick->AppendSeparator(); RClick->AppendItem("Properties", IDM_PROPERTIES, false); if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_OPEN: { break; } case IDM_DELETE: { OnDelete(); break; } case IDM_RENAME: { OnRename(); break; } } } DeleteObj(RClick); } } } } -int GFolderItemCompare(LListItem *A, LListItem *B, NativeInt Data) +int LFolderItemCompare(LListItem *A, LListItem *B, NativeInt Data) { LFolderItem *a = dynamic_cast(A); LFolderItem *b = dynamic_cast(B); if (a && b) { if (a->IsDir ^ b->IsDir) { if (a->IsDir) return -1; else return 1; } else if (a->File && b->File) { return stricmp(a->File, b->File); } } return 0; } LFolderList::LFolderList(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : LList(Id, x, y, cx, cy), LFolderView(dlg) { SetImageList(Dlg->d->BtnIcons, false); ShowColumnHeader(false); AddColumn("Name", cx-20); SetMode(LListColumns); } bool LFolderList::OnKey(LKey &k) { bool Status = LList::OnKey(k); switch (k.vkey) { case LK_BACKSPACE: { if (k.Down() && GetWindow()) { // Go up a directory LViewI *v = GetWindow()->FindControl(IDC_UP); if (v) { GetWindow()->OnNotify(v, LNotifyBackspaceKey); } } Status = true; break; } case LK_RETURN: #ifdef LK_KP_ENTER case LK_KP_ENTER: #endif { if (k.Down() && GetWindow()) { LFolderItem *Sel = dynamic_cast(GetSelected()); if (Sel) { if (Sel->IsDir) { auto Cur = GetWindow()->GetCtrlName(IDC_PATH); if (Cur) { char Path[256]; LMakePath(Path, sizeof(Path), Cur, Sel->GetText(0)); if (LDirExists(Path)) { GetWindow()->SetCtrlName(IDC_PATH, Path); Dlg->OnFolder(); } } } else { LViewI *Ok = GetWindow()->FindControl(IDOK); if (Ok) { GetWindow()->SetCtrlName(IDC_FILE, Sel->GetText(0)); GetWindow()->OnNotify(Ok, LNotification(k)); } } } } Status = true; break; } case LK_DELETE: { if (k.Down() && !k.IsChar && GetWindow()) { List Sel; if (GetSelection(Sel)) { LStringPipe Msg; Msg.Push("Do you want to delete:\n\n"); List Delete; for (auto i: Sel) { LFolderItem *s = dynamic_cast(i); if (s) { Delete.Insert(s); Msg.Push("\t"); Msg.Push(s->GetText(0)); Msg.Push("\n"); } } char *Mem = Msg.NewStr(); if (Mem) { if (LgiMsg(this, Mem, ModuleName, MB_YESNO) == IDYES) { for (auto d: Delete) { d->OnDelete(false); } } DeleteArray(Mem); } } } Status = true; break; } } // LgiTrace("%s:%i LFolderList::OnKey, key=%i down=%i status=%i\n", _FL, k.vkey, k.Down(), Status); return Status; } void LFolderList::OnFolder() { Empty(); LDirectory Dir; List New; // Get current type LFileType *Type = Dlg->d->Types.ItemAt(Dlg->d->CurrentType); List Ext; if (Type) { auto T = LString(Type->Extension()).SplitDelimit(";"); - for (int i=0; iCtrl2) return; bool ShowHiddenFiles = Dlg->ShowHidden ? Dlg->ShowHidden->Value() : false; for (auto Found = Dir.First(Dlg->Ctrl2->Name()); Found; Found = Dir.Next()) { char Name[LDirectory::MaxPathLen]; Dir.Path(Name, sizeof(Name)); bool Match = true; if (!ShowHiddenFiles && Dir.IsHidden()) { Match = false; } else if (!Dir.IsDir() && Ext.Length() > 0) { Match = false; for (auto e: Ext) { bool m = MatchStr(e, Name); if (m) { Match = true; break; } } } if (FilterKey && Match) Match = stristr(Dir.GetName(), FilterKey) != NULL; if (Match) New.Insert(new LFolderItem(Dlg, Name, &Dir)); } // Sort items... - New.Sort(GFolderItemCompare); + New.Sort(LFolderItemCompare); // Display items... Insert(New); } ////////////////////////////////////////////////////////////////////////// LFileSelect::LFileSelect(LViewI *Window) { d = new LFileSelectPrivate(this); if (Window) Parent(Window); } LFileSelect::~LFileSelect() { DeleteObj(d); } void LFileSelect::ShowReadOnly(bool b) { d->ShowReadOnly = b;; } bool LFileSelect::ReadOnly() { return d->ReadOnly; } const char *LFileSelect::Name() { return d->Files[0]; } bool LFileSelect::Name(const char *n) { d->Files.DeleteArrays(); if (n) { d->Files.Insert(NewStr(n)); } return true; } const char *LFileSelect::operator [](size_t i) { return d->Files.ItemAt(i); } size_t LFileSelect::Length() { return d->Files.Length(); } size_t LFileSelect::Types() { return d->Types.Length(); } void LFileSelect::ClearTypes() { d->Types.DeleteObjects(); } LFileType *LFileSelect::TypeAt(ssize_t n) { return d->Types.ItemAt(n); } bool LFileSelect::Type(const char *Description, const char *Extension, int Data) { LFileType *Type = new LFileType; if (Type) { Type->Description(Description); Type->Extension(Extension); d->Types.Insert(Type); } return Type != 0; } ssize_t LFileSelect::SelectedType() { return d->CurrentType; } LViewI *LFileSelect::Parent() { return d->Parent; } void LFileSelect::Parent(LViewI *Window) { d->Parent = dynamic_cast(Window); } bool LFileSelect::MultiSelect() { return d->MultiSelect; } void LFileSelect::MultiSelect(bool Multi) { d->MultiSelect = Multi; } #define CharPropImpl(Func, Var) \ const char *LFileSelect::Func() \ { \ return Var; \ } \ void LFileSelect::Func(const char *i) \ { \ - DeleteArray(Var); \ - if (i) \ - { \ - Var = NewStr(i); \ - } \ + Var = i; \ } CharPropImpl(InitialDir, d->InitPath); CharPropImpl(Title, d->Title); CharPropImpl(DefaultExtension, d->DefExt); void LFileSelect::Open(SelectCb Cb) { LFileSelectDlg *Dlg = new LFileSelectDlg(d); d->Type = TypeOpenFile; Dlg->Name("Open"); if (Dlg->SaveBtn) Dlg->SaveBtn->Name("Open"); // printf("LFileSelect domodal.. thread=%p\n", LGetCurrentThread()); Dlg->DoModal([select=this, cb=Cb](auto dlg, auto code) { // printf("LFileSelect cb.. thread=%u lock=%u\n", GetCurrentThreadId(), dlg->WindowHandle()->LockingThread()); if (cb) cb(select, code == IDOK); // printf("LFileSelect deleting.. lock=%u\n", dlg->WindowHandle()->LockingThread()); delete dlg; // printf("LFileSelect deleted..\n"); }); } void LFileSelect::OpenFolder(SelectCb Cb) { auto Dlg = new LFileSelectDlg(d); d->Type = TypeOpenFolder; Dlg->SaveBtn->Enabled(true); Dlg->FileNameEdit->Enabled(false); Dlg->Name("Open Folder"); Dlg->SaveBtn->Name("Open"); Dlg->DoModal([FileSelect=this,Cb](auto d, auto code) { if (Cb) Cb(FileSelect, code != IDOK); delete d; }); } void LFileSelect::Save(SelectCb Cb) { auto *Dlg = new LFileSelectDlg(d); d->Type = TypeSaveFile; Dlg->Name("Save As"); Dlg->SaveBtn->Name("Save As"); // printf("LFileSelect domodal.. thread=%u\n", GetCurrentThreadId()); Dlg->DoModal([FileSelect=this,Cb](auto dlg, auto code) { // printf("LFileSelect cb.. thread=%u lock=%u\n", GetCurrentThreadId(), dlg->WindowHandle()->LockingThread()); if (Cb) Cb(FileSelect, code != IDOK); // printf("LFileSelect deleting.. lock=%u\n", dlg->WindowHandle()->LockingThread()); delete dlg; // printf("LFileSelect deleted..\n"); }); } /////////////////////////////////////////////////////////////////////////////////// #if defined(LINUX) #include "lgi/common/Net.h" #endif bool LgiGetUsersLinks(LArray &Links) { LString Folder = LGetSystemPath(LSP_USER_LINKS); if (!Folder) return false; #if defined(WINDOWS) LDirectory d; for (int b = d.First(Folder); b; b = d.Next()) { char *s = d.GetName(); if (s && stristr(s, ".lnk")) { char lnk[MAX_PATH_LEN]; if (d.Path(lnk, sizeof(lnk)) && LResolveShortcut(lnk, lnk, sizeof(lnk))) { Links.New() = lnk; } } } #elif defined(LINUX) char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), Folder, "bookmarks")) { LgiTrace("%s:%i - Failed to make path '%s'\n", _FL, Folder.Get()); return false; } if (!LFileExists(p)) return false; LAutoString Txt(LReadTextFile(p)); if (!Txt) { LgiTrace("%s:%i - failed to read '%s'\n", _FL, p); return false; } LString s = Txt.Get(); LString::Array a = s.Split("\n"); for (unsigned i=0; i #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" //////////////////////////////////////////////////////////////////////////// LFindReplaceCommon::LFindReplaceCommon() { MatchWord = false; MatchCase = false; SelectionOnly = false; } //////////////////////////////////////////////////////////////////////////// // Find Window #define IDS_16 1000 #define IDC_TEXT 1001 #define IDC_MATCH_WORD 1004 #define IDC_MATCH_CASE 1005 #define IDC_PREV_SEARCH 1006 #define IDC_SELECTION_ONLY 1007 #define IDC_FIND_TABLE 1008 #define IDC_SEARCH_UP 1009 class LFindDlgPrivate { public: LEdit *Edit; LFindReplaceCommon::Callback Callback; void *CallbackData; }; LFindDlg::LFindDlg(LView *Parent, Callback Cb, const char *Init) { d = new LFindDlgPrivate; if (Init) Find = Init; d->Callback = Cb; SetParent(Parent); Name(LLoadString(L_FR_FIND, "Find")); LRect r(0, 0, 450, 370); - auto Dpi = LScreenDpi(); - auto Scale = (float)Dpi.x / 96.0; - r.x2 = (int)(r.x2 * Scale); - r.y2 = (int)(r.y2 * Scale); - SetPos(r); + ScaleSizeToDpi(); MoveSameScreen(Parent); LTableLayout *t; if (AddView(t = new LTableLayout(IDC_FIND_TABLE))) { int Row = 0; LLayoutCell *c = t->GetCell(0, Row); c->Add(new LTextLabel(IDS_16, 14, 14, -1, -1, LLoadString(L_FR_FIND_WHAT, "Find what:"))); c = t->GetCell(1, Row); c->Add(d->Edit = new LEdit(IDC_TEXT, 91, 7, 168, 21, "")); c = t->GetCell(2, Row++); c->Add(new LButton(IDOK, 294, 7, 70, 21, LLoadString(L_FR_FIND_NEXT, "Find Next"))); c = t->GetCell(0, Row, true, 2, 1); c->Add(new LCheckBox(IDC_MATCH_WORD, 14, 42, -1, -1, LLoadString(L_FR_MATCH_WORD, "Match &whole word only"))); c = t->GetCell(2, Row++); c->Add(new LButton(IDCANCEL, 294, 35, 70, 21, LLoadString(L_BTN_CANCEL, "Cancel"))); c = t->GetCell(0, Row++, true, 2, 1); c->Add(new LCheckBox(IDC_MATCH_CASE, 14, 63, -1, -1, LLoadString(L_FR_MATCH_CASE, "Match &case"))); c = t->GetCell(0, Row); c->Add(new LCheckBox(IDC_SELECTION_ONLY, 14, 84, -1, -1, LLoadString(L_FR_SELECTION_ONLY, "&Selection only"))); c = t->GetCell(1, Row++); c->Add(new LCheckBox(IDC_SEARCH_UP, 0, 0, -1, -1, "Search &upwards")); OnPosChange(); LRect TblPos = t->GetPos(); int TblY = TblPos.Y(); LRect Used = t->GetUsedArea(); int UsedY = Used.Y(); int Over = TblY - UsedY; r = GetPos(); r.y2 -= Over - (LTableLayout::CellSpacing<<1); SetPos(r); } if (d->Edit) d->Edit->Focus(true); } LFindDlg::~LFindDlg() { DeleteObj(d); } void LFindDlg::OnPosChange() { LTableLayout *t; if (GetViewById(IDC_FIND_TABLE, t)) { LRect c = GetClient(); c.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(c); } } void LFindDlg::OnCreate() { // Load controls if (Find) SetCtrlName(IDC_TEXT, Find); SetCtrlValue(IDC_MATCH_WORD, MatchWord); SetCtrlValue(IDC_MATCH_CASE, MatchCase); if (d->Edit) { d->Edit->Select(0); d->Edit->Focus(true); } } int LFindDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { // Save controls Find = GetCtrlName(IDC_TEXT); MatchWord = GetCtrlValue(IDC_MATCH_WORD) != 0; MatchCase = GetCtrlValue(IDC_MATCH_CASE) != 0; SelectionOnly = GetCtrlValue(IDC_SELECTION_ONLY) != 0; SearchUpwards = GetCtrlValue(IDC_SEARCH_UP) != 0; // printf("%s:%i Find OnNot %s, %i, %i\n", _FL, Find, MatchWord, MatchCase); if (d->Callback) { d->Callback(this, Ctrl->GetId()); break; } // else fall thru } case IDCANCEL: { if (IsModal()) EndModal(Ctrl->GetId()); else EndModeless(); break; } } return 0; } //////////////////////////////////////////////////////////////////////////// // Replace Window #define IDS_33 1009 #define IDC_REPLACE_WITH 1010 #define IDC_PREV_REPLACE 1011 class LReplaceDlgPrivate { public: LFindReplaceCommon::Callback Callback; void *CallbackData; }; LReplaceDlg::LReplaceDlg(LView *Parent, Callback Cb, char *InitFind, char *InitReplace) { d = new LReplaceDlgPrivate; d->Callback = Cb; if (InitFind) Find = InitFind; Replace = NewStr(InitReplace); MatchWord = false; MatchCase = false; SetParent(Parent); Name(LLoadString(L_FR_REPLACE, "Replace")); LView *f = 0; LRect r(0, 0, 450, 300); - auto Dpi = LScreenDpi(); - auto Scale = (float)Dpi.x / 96.0; - r.x2 = (int)(r.x2 * Scale); - r.y2 = (int)(r.y2 * Scale); - SetPos(r); + ScaleSizeToDpi(); MoveToCenter(); LTableLayout *t; if (AddView(t = new LTableLayout(IDC_FIND_TABLE))) { int Row = 0; LLayoutCell *c = t->GetCell(0, Row); c->Add(new LTextLabel(-1, 14, 14, -1, -1, LLoadString(L_FR_FIND_WHAT, "Find what:"))); c = t->GetCell(1, Row); c->Add(f = new LEdit(IDC_TEXT, 0, 0, 60, 20, "")); c = t->GetCell(2, Row++); c->Add(new LButton(IDC_FR_FIND, 0, 0, -1, -1, LLoadString(L_FR_FIND_NEXT, "Find Next"))); c = t->GetCell(0, Row); c->Add(new LTextLabel(-1, 0, 0, -1, -1, LLoadString(L_FR_REPLACE_WITH, "Replace with:"))); c = t->GetCell(1, Row); c->Add(new LEdit(IDC_REPLACE_WITH, 0, 0, -1, -1, "")); c = t->GetCell(2, Row++); c->Add(new LButton(IDC_FR_REPLACE, 0, 0, -1, -1, LLoadString(L_FR_REPLACE, "Replace"))); c = t->GetCell(0, Row, true, 2); c->Add(new LCheckBox(IDC_MATCH_WORD, 14, 70, -1, -1, LLoadString(L_FR_MATCH_WORD, "Match whole &word only"))); c = t->GetCell(2, Row++); c->Add(new LButton(IDOK, 0, 0, -1, -1, LLoadString(L_FR_REPLACE_ALL, "Replace &All"))); c = t->GetCell(0, Row, true, 2); c->Add(new LCheckBox(IDC_MATCH_CASE, 14, 91, -1, -1, LLoadString(L_FR_MATCH_CASE, "Match &case"))); c = t->GetCell(2, Row++); c->Add(new LButton(IDCANCEL, 0, 0, -1, -1, LLoadString(L_BTN_CANCEL, "Cancel"))); c = t->GetCell(0, Row); c->Add(new LCheckBox(IDC_SELECTION_ONLY, 14, 112, -1, -1, LLoadString(L_FR_SELECTION_ONLY, "&Selection only"))); c = t->GetCell(1, Row); c->Add(new LCheckBox(IDC_SEARCH_UP, 14, 91, -1, -1, "Search &upwards")); OnPosChange(); LRect u = t->GetUsedArea(); int Over = t->GetPos().Y() - u.Y(); r = GetPos(); r.y2 -= Over - (LTableLayout::CellSpacing << 1); SetPos(r); } if (f) f->Focus(true); } LReplaceDlg::~LReplaceDlg() { DeleteObj(d); } void LReplaceDlg::OnCreate() { if (Find) SetCtrlName(IDC_TEXT, Find); if (Replace) SetCtrlName(IDC_REPLACE_WITH, Replace); SetCtrlValue(IDC_MATCH_WORD, MatchWord); SetCtrlValue(IDC_MATCH_CASE, MatchCase); SetCtrlValue(IDC_SELECTION_ONLY, SelectionOnly); SetCtrlValue(IDC_SEARCH_UP, SearchUpwards); } int LReplaceDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: case IDC_FR_FIND: case IDC_FR_REPLACE: { Find = GetCtrlName(IDC_TEXT); Replace = GetCtrlName(IDC_REPLACE_WITH); MatchWord = GetCtrlValue(IDC_MATCH_WORD) != 0; MatchCase = GetCtrlValue(IDC_MATCH_CASE) != 0; SelectionOnly = GetCtrlValue(IDC_SELECTION_ONLY) != 0; SearchUpwards = GetCtrlValue(IDC_SEARCH_UP) != 0; if (d->Callback) { d->Callback(this, Ctrl->GetId()); break; } // else fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } void LReplaceDlg::OnPosChange() { LTableLayout *t; if (GetViewById(IDC_FIND_TABLE, t)) { LRect c = GetClient(); c.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(c); } } diff --git a/src/common/Lgi/WindowCommon.cpp b/src/common/Lgi/WindowCommon.cpp --- a/src/common/Lgi/WindowCommon.cpp +++ b/src/common/Lgi/WindowCommon.cpp @@ -1,198 +1,206 @@ // // LWindowCommon.cpp // LgiCarbon // // Created by Matthew Allen on 11/09/14. // // #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/DropFiles.h" +void LWindow::ScaleSizeToDpi() +{ + auto s = GetDpiScale(); + auto p = GetPos(); + p.SetSize( (int) (p.X() * s.x), (int) (p.Y() * s.y) ); + SetPos(p); +} + void LWindow::BuildShortcuts(ShortcutMap &Map, LViewI *v) { if (!v) v = this; for (auto c: v->IterateViews()) { const char *n = c->Name(), *amp; if (n && (amp = strchr(n, '&'))) { amp++; if (*amp && *amp != '&') { uint8_t *i = (uint8_t*)amp; ssize_t sz = Strlen(amp); int32 ch = LgiUtf8To32(i, sz); if (ch) Map.Add(ToUpper(ch), c); } } BuildShortcuts(Map, c); } } void LWindow::MoveOnScreen() { LRect p = GetPos(); LArray Displays; LRect Screen(0, 0, -1, -1); if ( #if WINNATIVE !IsZoomed(Handle()) && !IsIconic(Handle()) && #endif LGetDisplays(Displays)) { int Best = -1; int Pixels = 0; int Close = 0x7fffffff; for (int i=0; ir); if (o.Valid()) { int Pix = o.X()*o.Y(); if (Best < 0 || Pix > Pixels) { Best = i; Pixels = Pix; } } else if (Pixels == 0) { int n = Displays[i]->r.Near(p); if (n < Close) { Best = i; Close = n; } } } if (Best >= 0) Screen = Displays[Best]->r; } if (!Screen.Valid()) Screen.Set(0, 0, GdcD->X()-1, GdcD->Y()-1); if (p.x2 >= Screen.x2) p.Offset(Screen.x2 - p.x2, 0); if (p.y2 >= Screen.y2) p.Offset(0, Screen.y2 - p.y2); if (p.x1 < Screen.x1) p.Offset(Screen.x1 - p.x1, 0); if (p.y1 < Screen.y1) p.Offset(0, Screen.y1 - p.y1); SetPos(p, true); Displays.DeleteObjects(); } void LWindow::MoveToCenter() { LRect Screen(0, 0, GdcD->X()-1, GdcD->Y()-1); ::LArray Displays; LRect p = GetPos(); p.Offset(-p.x1, -p.y1); if (LGetDisplays(Displays, &Screen) && Displays.Length() > 0) { GDisplayInfo *Dsp = NULL; for (auto d: Displays) { if (d->r.Overlap(&p)) { Dsp = d; break; } } if (!Dsp) goto ScreenPos; p.Offset(Dsp->r.x1 + ((Dsp->r.X() - p.X()) / 2), Dsp->r.y1 + ((Dsp->r.Y() - p.Y()) / 2)); } else { ScreenPos: p.Offset((Screen.X() - p.X()) / 2, (Screen.Y() - p.Y()) / 2); } SetPos(p, true); Displays.DeleteObjects(); } void LWindow::MoveToMouse() { LMouse m; if (GetMouse(m, true)) { LRect p = GetPos(); p.Offset(-p.x1, -p.y1); p.Offset(m.x-(p.X()/2), m.y-(p.Y()/2)); SetPos(p, true); MoveOnScreen(); } } bool LWindow::MoveSameScreen(LViewI *View) { if (!View) { LAssert(0); return false; } auto Wnd = View->GetWindow(); LRect p = Wnd ? Wnd->GetPos() : View->GetPos(); int cx = p.x1 + (p.X() >> 4); int cy = p.y1 + (p.Y() >> 4); LRect np = GetPos(); np.Offset(cx - np.x1, cy - np.y1); SetPos(np); MoveOnScreen(); return true; } int LWindow::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.SupportsFileDrops(); return true; } int LWindow::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; #ifdef DEBUG_DND LgiTrace("%s:%i - OnDrop Data=%i Pt=%i,%i Key=0x%x\n", _FL, Data.Length(), Pt.x, Pt.y, KeyState); #endif for (auto &dd: Data) { #ifdef DEBUG_DND LgiTrace("\tFmt=%s\n", dd.Format.Get()); #endif if (dd.IsFileDrop()) { LDropFiles Files(dd); if (Files.Length()) { Status = DROPEFFECT_COPY; OnReceiveFiles(Files); break; } } } return Status; } diff --git a/src/common/Net/OpenSSLSocket.cpp b/src/common/Net/OpenSSLSocket.cpp --- a/src/common/Net/OpenSSLSocket.cpp +++ b/src/common/Net/OpenSSLSocket.cpp @@ -1,1480 +1,1481 @@ /*hdr ** FILE: OpenSSLSocket.cpp ** AUTHOR: Matthew Allen ** DATE: 24/9/2004 ** DESCRIPTION: Open SSL wrapper socket ** ** Copyright (C) 2004-2014, Matthew Allen ** fret@memecode.com ** */ #include #ifdef WINDOWS #pragma comment(lib,"Ws2_32.lib") #else #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/OpenSSLSocket.h" #include #ifdef WIN32 #include #endif #include "lgi/common/Variant.h" #include "lgi/common/Net.h" #define PATH_OFFSET "../" LString LibName(const char *Fmt) { LString s; #if defined(HAIKU) s = LString(Fmt).Strip("."); #elif defined(OPENSSL_SHLIB_VERSION) s.Printf(Fmt, OPENSSL_SHLIB_VERSION); #elif defined(MAC) s.Printf(Fmt, 3); #else s.Printf(Fmt, 1); #endif #ifdef _WIN64 s += "-x64"; #endif return s; } #ifdef WIN32 #define SSL_LIBRARY LibName("libssl-%i") #define EAY_LIBRARY LibName("libcrypto-%i") #elif defined(LINUX) // Building openssl on linux: // git clone https://github.com/openssl/openssl.git // cd openssl // ./config // make -j8 #define SSL_LIBRARY LibName("libssl.so.%i") #else // Building openssl on mac: // ./configure darwin64-x86_64-cc -mmacosx-version-min=10.10 #define SSL_LIBRARY LibName("libssl.%i") #endif #define HasntTimedOut() ((To < 0) || (LCurrentTime() - Start < To)) static const char* MinimumVersion = "3.0.1"; void SSL_locking_function(int mode, int n, const char *file, int line); unsigned long SSL_id_function(); class LibSSL : public LLibrary { public: LibSSL() { #if defined MAC char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExeFile(), LString("Contents/Frameworks/") + SSL_LIBRARY); if (!Load(p)) LgiTrace("%s:%i - Failed to load '%s'\n", _FL, p); #elif defined LINUX char p[MAX_PATH_LEN]; - if (LMakePath(p, sizeof(p), LGetExePath(), "libssl.so")) + if (LMakePath(p, sizeof(p), LGetExePath(), SSL_LIBRARY)) { if (LFileExists(p)) { - LgiTrace("%s:%i - loading SSL library '%s'\n", _FL, p); - Load(p); + auto result = Load(p); + LgiTrace("%s:%i - Local SSL '%s' = %i\n", _FL, p, result); } + else LgiTrace("%s:%i - No local SSL library '%s'\n", _FL, p); } #endif if (!IsLoaded()) Load(SSL_LIBRARY); if (!IsLoaded()) { #ifdef WIN32 char p[MAX_PATH_LEN], leaf[32]; int bits = sizeof(size_t)*8; sprintf_s(leaf, sizeof(leaf), "OpenSSL-Win%i", bits); LMakePath(p, sizeof(p), LGetSystemPath(LSP_USER_APPS, bits), leaf); auto prev = FileDev->GetCurrentFolder(); FileDev->SetCurrentFolder(p); LMakePath(p, sizeof(p), p, SSL_LIBRARY); auto loaded = Load(p); FileDev->SetCurrentFolder(prev); #endif } } #if OPENSSL_VERSION_NUMBER >= 0x10100000L DynFunc0(int, OPENSSL_library_init); DynFunc0(int, OPENSSL_load_error_strings); DynFunc2(int, OPENSSL_init_ssl, uint64_t, opts, const OPENSSL_INIT_SETTINGS *, settings); DynFunc0(const SSL_METHOD *, TLS_method); DynFunc0(const SSL_METHOD *, TLS_server_method); DynFunc0(const SSL_METHOD *, TLS_client_method); #else DynFunc0(int, SSL_library_init); DynFunc0(int, SSL_load_error_strings); DynFunc0(SSL_METHOD*, SSLv23_client_method); DynFunc0(SSL_METHOD*, SSLv23_server_method); #endif DynFunc1(int, SSL_open, SSL*, s); DynFunc1(int, SSL_connect, SSL*, s); DynFunc4(long, SSL_ctrl, SSL*, ssl, int, cmd, long, larg, void*, parg); DynFunc1(int, SSL_shutdown, SSL*, s); DynFunc1(int, SSL_free, SSL*, ssl); DynFunc1(int, SSL_get_fd, const SSL *, s); DynFunc2(int, SSL_set_fd, SSL*, s, int, fd); DynFunc1(SSL*, SSL_new, SSL_CTX*, ctx); DynFunc1(BIO*, BIO_new_ssl_connect, SSL_CTX*, ctx); DynFunc1(X509*, SSL_get_peer_certificate, SSL*, s); DynFunc1(int, SSL_set_connect_state, SSL*, s); DynFunc1(int, SSL_set_accept_state, SSL*, s); DynFunc2(int, SSL_get_error, SSL*, s, int, ret_code); DynFunc3(int, SSL_set_bio, SSL*, s, BIO*, rbio, BIO*, wbio); DynFunc3(int, SSL_write, SSL*, ssl, const void*, buf, int, num); DynFunc3(int, SSL_read, SSL*, ssl, const void*, buf, int, num); DynFunc1(int, SSL_pending, SSL*, ssl); DynFunc1(BIO *, SSL_get_rbio, const SSL *, s); DynFunc1(int, SSL_accept, SSL *, ssl); DynFunc1(SSL_CTX*, SSL_CTX_new, const SSL_METHOD*, meth); DynFunc3(int, SSL_CTX_load_verify_locations, SSL_CTX*, ctx, const char*, CAfile, const char*, CApath); DynFunc3(int, SSL_CTX_use_certificate_file, SSL_CTX*, ctx, const char*, file, int, type); DynFunc3(int, SSL_CTX_use_PrivateKey_file, SSL_CTX*, ctx, const char*, file, int, type); DynFunc1(int, SSL_CTX_check_private_key, const SSL_CTX*, ctx); DynFunc1(int, SSL_CTX_free, SSL_CTX*, ctx); #ifdef WIN32 // If this is freaking you out then good... openssl-win32 ships // in 2 DLL's and on Linux everything is 1 shared object. Thus // the code reflects that. }; class LibEAY : public LLibrary { public: LibEAY() : LLibrary(EAY_LIBRARY) { if (!IsLoaded()) { #ifdef WIN32 char p[MAX_PATH_LEN], leaf[32]; int bits = sizeof(size_t)*8; sprintf_s(leaf, sizeof(leaf), "OpenSSL-Win%i", bits); LMakePath(p, sizeof(p), LGetSystemPath(LSP_USER_APPS, bits), leaf); auto prev = FileDev->GetCurrentFolder(); FileDev->SetCurrentFolder(p); LMakePath(p, sizeof(p), p, EAY_LIBRARY); auto loaded = Load(p); FileDev->SetCurrentFolder(prev); #endif } } #endif typedef void (*locking_callback)(int mode,int type, const char *file,int line); typedef unsigned long (*id_callback)(); DynFunc1(BIO*, BIO_new, BIO_METHOD*, type); DynFunc0(BIO_METHOD*, BIO_s_socket); DynFunc0(BIO_METHOD*, BIO_s_mem); DynFunc1(BIO*, BIO_new_connect, char *, host_port); DynFunc4(long, BIO_ctrl, BIO*, bp, int, cmd, long, larg, void*, parg); DynFunc4(long, BIO_int_ctrl, BIO *, bp, int, cmd, long, larg, int, iarg); DynFunc3(int, BIO_read, BIO*, b, void*, data, int, len); DynFunc3(int, BIO_write, BIO*, b, const void*, data, int, len); DynFunc1(int, BIO_free, BIO*, a); DynFunc1(int, BIO_free_all, BIO*, a); DynFunc2(int, BIO_test_flags, const BIO *, b, int, flags); DynFunc1(int, BIO_get_retry_reason, BIO *, bio); DynFunc0(int, ERR_load_BIO_strings); #if OPENSSL_VERSION_NUMBER < 0x10100000L DynFunc0(int, ERR_free_strings); DynFunc0(int, EVP_cleanup); DynFunc0(int, OPENSSL_add_all_algorithms_noconf); DynFunc1(int, CRYPTO_set_locking_callback, locking_callback, func); DynFunc1(int, CRYPTO_set_id_callback, id_callback, func); DynFunc0(int, CRYPTO_num_locks); DynFunc1(const char *, SSLeay_version, int, type); #else DynFunc2(int, OPENSSL_init_crypto, uint64_t, opts, const OPENSSL_INIT_SETTINGS *, settings); DynFunc1(const char *, OpenSSL_version, int, type); #endif DynFunc1(const char *, ERR_lib_error_string, unsigned long, e); DynFunc1(const char *, ERR_func_error_string, unsigned long, e); DynFunc1(const char *, ERR_reason_error_string, unsigned long, e); DynFunc1(int, ERR_print_errors, BIO *, bp); DynFunc3(char*, X509_NAME_oneline, X509_NAME*, a, char*, buf, int, size); DynFunc1(X509_NAME*, X509_get_subject_name, X509*, a); DynFunc2(char*, ERR_error_string, unsigned long, e, char*, buf); DynFunc0(unsigned long, ERR_get_error); DynFunc2(int, RAND_bytes, unsigned char *, buf, int, num); }; typedef LArray SslVer; SslVer ParseSslVersion(const char *v) { auto t = LString(v).SplitDelimit("."); SslVer out; for (unsigned i=0; i(SslVer &a, SslVer &b) { return CompareSslVersion(a, b) > 0; } static const char *FileLeaf(const char *f) { const char *l = strrchr(f, DIR_CHAR); return l ? l + 1 : f; } #undef _FL #define _FL FileLeaf(__FILE__), __LINE__ class OpenSSL : #ifdef WINDOWS public LibEAY, #endif public LibSSL { SSL_CTX *Server; public: SSL_CTX *Client; LArray Locks; LString ErrorMsg; bool IsLoaded() { return LibSSL::IsLoaded() #ifdef WINDOWS && LibEAY::IsLoaded() #endif ; } bool InitLibrary(SslSocket *sock) { LStringPipe Err; LArray Ver; LArray MinimumVer = ParseSslVersion(MinimumVersion); LString::Array t; int Len = 0; const char *v = NULL; if (!IsLoaded()) { #ifdef EAY_LIBRARY Err.Print("%s:%i - SSL libraries missing (%s, %s)\n", _FL, SSL_LIBRARY.Get(), EAY_LIBRARY.Get()); #else Err.Print("%s:%i - SSL library missing (%s)\n", _FL, SSL_LIBRARY.Get()); #endif goto OnError; } SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); Len = CRYPTO_num_locks(); Locks.Length(Len); CRYPTO_set_locking_callback(SSL_locking_function); CRYPTO_set_id_callback(SSL_id_function); v = SSLeay_version(SSLEAY_VERSION); if (!v) { Err.Print("%s:%i - SSLeay_version failed.\n", _FL); goto OnError; } t = LString(v).SplitDelimit(" "); if (t.Length() < 2) { Err.Print("%s:%i - SSLeay_version: no version\n", _FL); goto OnError; } Ver = ParseSslVersion(t[1]); if (Ver.Length() < 3) { Err.Print("%s:%i - SSLeay_version: not enough tokens\n", _FL); goto OnError; } if (Ver < MinimumVer) { #if WINDOWS char FileName[MAX_PATH_LEN] = ""; DWORD r = GetModuleFileNameA(LibEAY::Handle(), FileName, sizeof(FileName)); #endif Err.Print("%s:%i - SSL version '%s' is too old (minimum '%s')\n" #if WINDOWS "%s\n" #endif , _FL, t[1].Get(), MinimumVersion #if WINDOWS ,FileName #endif ); goto OnError; } Client = SSL_CTX_new(SSLv23_client_method()); if (!Client) { long e = ERR_get_error(); char *Msg = ERR_error_string(e, 0); Err.Print("%s:%i - SSL_CTX_new(client) failed with '%s' (%i)\n", _FL, Msg, e); goto OnError; } return true; OnError: ErrorMsg = Err.NewGStr(); if (sock) sock->DebugTrace("%s", ErrorMsg.Get()); return false; } OpenSSL() { Client = NULL; Server = NULL; } ~OpenSSL() { if (Client) { SSL_CTX_free(Client); Client = NULL; } if (Server) { SSL_CTX_free(Server); Server = NULL; } Locks.DeleteObjects(); } SSL_CTX *GetServer(SslSocket *sock, const char *CertFile, const char *KeyFile) { if (!Server) { Server = SSL_CTX_new(SSLv23_server_method()); if (Server) { if (CertFile) SSL_CTX_use_certificate_file(Server, CertFile, SSL_FILETYPE_PEM); if (KeyFile) SSL_CTX_use_PrivateKey_file(Server, KeyFile, SSL_FILETYPE_PEM); if (!SSL_CTX_check_private_key(Server)) { LAssert(0); } } else { long e = ERR_get_error(); char *Msg = ERR_error_string(e, 0); LStringPipe p; p.Print("%s:%i - SSL_CTX_new(server) failed with '%s' (%i)\n", _FL, Msg, e); ErrorMsg = p.NewGStr(); sock->DebugTrace("%s", ErrorMsg.Get()); } } return Server; } bool IsOk(SslSocket *sock) { bool Loaded = #ifdef WIN32 LibSSL::IsLoaded() && LibEAY::IsLoaded(); #else IsLoaded(); #endif if (Loaded) return true; // Try and load again... cause the library can be provided by install on demand. #ifdef WIN32 Loaded = LibSSL::Load(SSL_LIBRARY) && LibEAY::Load(EAY_LIBRARY); #else Loaded = Load(SSL_LIBRARY); #endif if (Loaded) InitLibrary(sock); return Loaded; } }; static OpenSSL *Library = 0; #if 0 #define SSL_DEBUG_LOCKING #endif void SSL_locking_function(int mode, int n, const char *file, int line) { LAssert(Library != NULL); if (Library) { if (!Library->Locks[n]) { #ifdef SSL_DEBUG_LOCKING LgiTrace("SSL[%i] create\n", n); #endif Library->Locks[n] = new LMutex("SSL_locking_function"); } #ifdef SSL_DEBUG_LOCKING LgiTrace("SSL[%i] lock=%i, unlock=%i, re=%i, wr=%i (mode=0x%x, cnt=%i, thr=0x%x, %s:%i)\n", n, TestFlag(mode, CRYPTO_LOCK), TestFlag(mode, CRYPTO_UNLOCK), TestFlag(mode, CRYPTO_READ), TestFlag(mode, CRYPTO_WRITE), mode, Library->Locks[n]->GetCount(), LGetCurrentThread(), file, line); #endif if (mode & CRYPTO_LOCK) Library->Locks[n]->Lock((char*)file, line); else if (mode & CRYPTO_UNLOCK) Library->Locks[n]->Unlock(); } } unsigned long SSL_id_function() { return (unsigned long) GetCurrentThreadId(); } bool StartSSL(LString &ErrorMsg, SslSocket *sock) { static LMutex Lock("StartSSL"); if (Lock.Lock(_FL)) { if (!Library) { Library = new OpenSSL; if (Library && !Library->InitLibrary(sock)) { ErrorMsg = Library->ErrorMsg; DeleteObj(Library); } } Lock.Unlock(); } return Library != NULL; } void EndSSL() { DeleteObj(Library); } struct SslSocketPriv : public LCancel { LCapabilityClient *Caps; bool SslOnConnect; bool IsSSL; bool UseSSLrw; int Timeout; bool RawLFCheck; #ifdef _DEBUG bool LastWasCR; #endif bool IsBlocking; LCancel *Cancel; // This is just for the UI. LStreamI *Logger; // This is for the connection logging. LAutoString LogFile; bool LogFileError = false; LAutoPtr LogStream; int LogFormat; SslSocketPriv() { #ifdef _DEBUG LastWasCR = false; #endif Cancel = this; Timeout = 20 * 1000; IsSSL = false; UseSSLrw = false; LogFormat = 0; } }; bool SslSocket::DebugLogging = false; SslSocket::SslSocket(LStreamI *logger, LCapabilityClient *caps, bool sslonconnect, bool RawLFCheck) : Lock("SslSocket") { d = new SslSocketPriv; Bio = 0; Ssl = 0; d->RawLFCheck = RawLFCheck; d->SslOnConnect = sslonconnect; d->Caps = caps; d->Logger = logger; d->IsBlocking = true; LString ErrMsg; if (StartSSL(ErrMsg, this)) { if (Library->IsOk(this)) { char s[MAX_PATH_LEN]; #ifdef WIN32 char n[MAX_PATH_LEN]; if (GetModuleFileNameA(Library->LibSSL::Handle(), n, sizeof(n))) { sprintf_s(s, sizeof(s), "Using '%s'", n); OnInformation(s); } if (GetModuleFileNameA(Library->LibEAY::Handle(), n, sizeof(n))) { sprintf_s(s, sizeof(s), "Using '%s'", n); OnInformation(s); } #else LString fp = Library->GetFullPath(); if (fp) { sprintf_s(s, sizeof(s), "Using '%s'", fp.Get()); OnInformation(s); } #endif } } else if (caps) { caps->NeedsCapability("openssl", ErrMsg); } else { OnError(0, "Can't load or find OpenSSL library."); } } SslSocket::~SslSocket() { Close(); DeleteObj(d); } LStreamI *SslSocket::Clone() { return new SslSocket(d->Logger, d->Caps, true); } LCancel *SslSocket::GetCancel() { return d->Cancel; } void SslSocket::SetCancel(LCancel *c) { d->Cancel = c; } int SslSocket::GetTimeout() { return d->Timeout; } void SslSocket::SetTimeout(int ms) { d->Timeout = ms; } void SslSocket::SetLogger(LStreamI *logger) { d->Logger = logger; } LStreamI *SslSocket::GetLog() { return d->Logger; } void SslSocket::SetSslOnConnect(bool b) { d->SslOnConnect = b; } LStream *SslSocket::GetLogStream() { if (!d->LogStream && d->LogFile && !d->LogFileError) { if (!d->LogStream.Reset(new LFile)) return NULL; if (!d->LogStream->Open(d->LogFile, O_WRITE)) { d->LogStream.Reset(); d->LogFileError = true; // Stop retrying the open and spamming the trace log. LgiTrace("%s:%i - Can't open '%s' for SSL logging.\n", _FL, d->LogFile.Get()); return NULL; } // Seek to the end d->LogStream->SetPos(d->LogStream->GetSize()); } return d->LogStream; } bool SslSocket::GetVariant(const char *Name, LVariant &Val, const char *Arr) { if (!Name) return false; if (!_stricmp(Name, "isSsl")) // Type: Bool { Val = true; return true; } return false; } void SslSocket::Log(const char *Str, ssize_t Bytes, SocketMsgType Type) { if (!ValidStr(Str)) return; if (d->Logger) d->Logger->Write(Str, Bytes<0?(int)strlen(Str):Bytes, Type); else if (Type == SocketMsgError) LgiTrace("%.*s", Bytes, Str); } const char *SslSocket::GetErrorString() { return ErrMsg; } void SslSocket::SslError(const char *file, int line, const char *Msg) { char *Part = strrchr((char*)file, DIR_CHAR); #ifndef WIN32 printf("%s:%i - %s\n", file, line, Msg); #endif ErrMsg.Printf("Error: %s:%i - %s\n", Part ? Part + 1 : file, line, Msg); Log(ErrMsg, ErrMsg.Length(), SocketMsgError); } OsSocket SslSocket::Handle(OsSocket Set) { OsSocket h = INVALID_SOCKET; if (Set != INVALID_SOCKET) { long r; bool IsError = false; if (!Ssl) { Ssl = Library->SSL_new(Library->GetServer(this, NULL, NULL)); } if (Ssl) { r = Library->SSL_set_fd(Ssl, (int) Set); Bio = Library->SSL_get_rbio(Ssl); r = Library->SSL_accept(Ssl); if (r <= 0) IsError = true; else if (r == 1) h = Set; } else IsError = true; if (IsError) { long e = Library->ERR_get_error(); char *Msg = Library->ERR_error_string(e, 0); Log(Msg, -1, SocketMsgError); return INVALID_SOCKET; } } else if (Bio) { int hnd = (int)INVALID_SOCKET; Library->BIO_get_fd(Bio, &hnd); h = hnd; } return h; } bool SslSocket::IsOpen() { return Bio != 0 && !d->Cancel->IsCancelled(); } LString SslGetErrorAsString(OpenSSL *Library) { BIO *bio = Library->BIO_new (Library->BIO_s_mem()); Library->ERR_print_errors (bio); char *buf = NULL; size_t len = Library->BIO_get_mem_data (bio, &buf); LString s(buf, len); Library->BIO_free (bio); return s; } int SslSocket::Open(const char *HostAddr, int Port) { bool Status = false; LMutex::Auto Lck(&Lock, _FL); DebugTrace("%s:%i - SslSocket::Open(%s,%i)\n", _FL, HostAddr, Port); if (Library && Library->IsOk(this) && HostAddr) { char h[256]; sprintf_s(h, sizeof(h), "%s:%i", HostAddr, Port); // Do SSL handshake? if (d->SslOnConnect) { // SSL connection.. d->IsSSL = true; if (Library->Client) { const char *CertDir = "/u/matthew/cert"; int r = Library->SSL_CTX_load_verify_locations(Library->Client, 0, CertDir); DebugTrace("%s:%i - SSL_CTX_load_verify_locations=%i\n", _FL, r); if (r > 0) { Bio = Library->BIO_new_ssl_connect(Library->Client); DebugTrace("%s:%i - BIO_new_ssl_connect=%p\n", _FL, Bio); if (Bio) { Library->BIO_get_ssl(Bio, &Ssl); DebugTrace("%s:%i - BIO_get_ssl=%p\n", _FL, Ssl); if (Ssl) { // SNI setup Library->SSL_set_tlsext_host_name(Ssl, HostAddr); // Library->SSL_CTX_set_timeout() Library->BIO_set_conn_hostname(Bio, HostAddr); #if OPENSSL_VERSION_NUMBER < 0x10100000L Library->BIO_set_conn_int_port(Bio, &Port); #else LString sPort; sPort.Printf("%i", Port); Library->BIO_set_conn_port(Bio, sPort.Get()); #endif // Do non-block connect uint64 Start = LCurrentTime(); int To = GetTimeout(); IsBlocking(false); r = Library->SSL_connect(Ssl); DebugTrace("%s:%i - initial SSL_connect=%i\n", _FL, r); while (r != 1 && !d->Cancel->IsCancelled()) { int err = Library->SSL_get_error(Ssl, r); if (err != SSL_ERROR_WANT_CONNECT) { DebugTrace("%s:%i - SSL_get_error=%i\n", _FL, err); } LSleep(50); try { r = Library->SSL_connect(Ssl); } catch (...) { r = -1; LgiTrace("%s:%i - SSL_connect crashed.\n", _FL); } DebugTrace("%s:%i - SSL_connect=%i (%i of %i ms)\n", _FL, r, (int)(LCurrentTime() - Start), (int)To); bool TimeOut = !HasntTimedOut(); if (TimeOut) { DebugTrace("%s:%i - SSL connect timeout, to=%i\n", _FL, To); SslError(_FL, "Connection timeout."); break; } } DebugTrace("%s:%i - open loop finished, r=%i, Cancelled=%i\n", _FL, r, d->Cancel->IsCancelled()); if (r == 1) { IsBlocking(true); Library->SSL_set_mode(Ssl, SSL_MODE_AUTO_RETRY); Status = true; // d->UseSSLrw = true; char m[256]; sprintf_s(m, sizeof(m), "Connected to '%s' using SSL", h); OnInformation(m); } else if (!d->Cancel->IsCancelled()) { LString Err = SslGetErrorAsString(Library).Strip(); if (!Err) Err.Printf("BIO_do_connect(%s:%i) failed.", HostAddr, Port); SslError(_FL, Err); } } else SslError(_FL, "BIO_get_ssl failed."); } else SslError(_FL, "BIO_new_ssl_connect failed."); } else SslError(_FL, "SSL_CTX_load_verify_locations failed."); } else SslError(_FL, "No Ctx."); } else { Bio = Library->BIO_new_connect(h); DebugTrace("%s:%i - BIO_new_connect=%p\n", _FL, Bio); if (Bio) { // Non SSL... go into non-blocking mode so that if ::Close() is called we // can quit out of the connect loop. IsBlocking(false); uint64 Start = LCurrentTime(); int To = GetTimeout(); long r = Library->BIO_do_connect(Bio); DebugTrace("%s:%i - BIO_do_connect=%i\n", _FL, r); while (r != 1 && !d->Cancel->IsCancelled()) { if (!Library->BIO_should_retry(Bio)) { break; } LSleep(50); r = Library->BIO_do_connect(Bio); DebugTrace("%s:%i - BIO_do_connect=%i\n", _FL, r); if (!HasntTimedOut()) { DebugTrace("%s:%i - open timeout, to=%i\n", _FL, To); OnError(0, "Connection timeout."); break; } } DebugTrace("%s:%i - open loop finished=%i\n", _FL, r); if (r == 1) { IsBlocking(true); Status = true; char m[256]; sprintf_s(m, sizeof(m), "Connected to '%s'", h); OnInformation(m); } else SslError(_FL, "BIO_do_connect failed"); } else SslError(_FL, "BIO_new_connect failed"); } } if (!Status) { Close(); } DebugTrace("%s:%i - SslSocket::Open status=%i\n", _FL, Status); return Status; } bool SslSocket::SetVariant(const char *Name, LVariant &Value, const char *Arr) { bool Status = false; if (!Library || !Name) return false; if (!_stricmp(Name, SslSocket_LogFile)) { d->LogFile.Reset(Value.ReleaseStr()); } else if (!_stricmp(Name, SslSocket_LogFormat)) { d->LogFormat = Value.CastInt32(); } else if (!_stricmp(Name, LSocket_Protocol)) { char *v = Value.CastString(); if (v && stristr(v, "SSL")) { if (!Bio) { d->SslOnConnect = true; } else { if (!Library->Client) { SslError(_FL, "Library->Client is null."); } else { Ssl = Library->SSL_new(Library->Client); DebugTrace("%s:%i - SSL_new=%p\n", _FL, Ssl); if (!Ssl) { SslError(_FL, "SSL_new failed."); } else { int r = Library->SSL_set_bio(Ssl, Bio, Bio); DebugTrace("%s:%i - SSL_set_bio=%i\n", _FL, r); uint64 Start = LCurrentTime(); int To = GetTimeout(); while (HasntTimedOut()) { r = Library->SSL_connect(Ssl); DebugTrace("%s:%i - SSL_connect=%i\n", _FL, r); if (r < 0) LSleep(100); else break; } if (r > 0) { Status = d->UseSSLrw = d->IsSSL = true; OnInformation("Session is now using SSL"); X509 *ServerCert = Library->SSL_get_peer_certificate(Ssl); DebugTrace("%s:%i - SSL_get_peer_certificate=%p\n", _FL, ServerCert); if (ServerCert) { char Txt[256] = ""; Library->X509_NAME_oneline(Library->X509_get_subject_name(ServerCert), Txt, sizeof(Txt)); DebugTrace("%s:%i - X509_NAME_oneline=%s\n", _FL, Txt); OnInformation(Txt); } // SSL_get_verify_result } else { SslError(_FL, "SSL_connect failed."); r = Library->SSL_get_error(Ssl, r); char *Msg = Library->ERR_error_string(r, 0); if (Msg) { OnError(r, Msg); } } } } } } } return Status; } int SslSocket::Close() { bool Prev = d->Cancel->IsCancelled(); d->Cancel->Cancel(); LMutex::Auto Lck(&Lock, _FL); if (!Library) { LgiTrace("%s:%i - Error: no library.\n", _FL); return false; } if (Bio) { auto result = Library->BIO_free_all(Bio); if (result != 1) printf("%s:%i result =%i\n", _FL, result); } Ssl = NULL; Bio = NULL; d->Cancel->Cancel(Prev); return true; } bool SslSocket::Listen(int Port) { return false; } bool SslSocket::IsBlocking() { return d->IsBlocking; } void SslSocket::IsBlocking(bool block) { d->IsBlocking = block; if (Bio) Library->BIO_set_nbio(Bio, !d->IsBlocking); } bool SslSocket::IsReadable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Handle(); if (!d->Cancel->IsCancelled() && ValidSocket(s)) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { return true; } else if (v < 0) { // Error(); } } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } bool SslSocket::IsWritable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Handle(); if (!d->Cancel->IsCancelled() && ValidSocket(s)) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set w; FD_ZERO(&w); FD_SET(s, &w); int v = select((int)s+1, &w, 0, 0, &t); if (v > 0 && FD_ISSET(s, &w)) { return true; } else if (v < 0) { // Error(); } } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } void SslSocket::OnWrite(const char *Data, ssize_t Len) { #ifdef _DEBUG if (d->RawLFCheck) { const char *End = Data + Len; while (Data < End) { LAssert(*Data != '\n' || d->LastWasCR); d->LastWasCR = *Data == '\r'; Data++; } } #endif // Log(Data, Len, SocketMsgSend); } void SslSocket::OnRead(char *Data, ssize_t Len) { #ifdef _DEBUG if (d->RawLFCheck) { const char *End = Data + Len; while (Data < End) { LAssert(*Data != '\n' || d->LastWasCR); d->LastWasCR = *Data == '\r'; Data++; } } #endif // Log(Data, Len, SocketMsgReceive); } ssize_t SslSocket::Write(const void *Data, ssize_t Len, int Flags) { LMutex::Auto Lck(&Lock, _FL); if (!Library || d->Cancel->IsCancelled()) return -1; if (!Bio) { DebugTrace("%s:%i - BIO is NULL\n", _FL); return -1; } int r = 0; if (d->UseSSLrw) { if (Ssl) { uint64 Start = LCurrentTime(); int To = GetTimeout(); while (HasntTimedOut()) { r = Library->SSL_write(Ssl, Data, (int)Len); if (r <= 0) { if (!Library->BIO_should_retry(Bio)) break; if (d->IsBlocking) LSleep(1); else break; } else { DebugTrace("%s:%i - SSL_write(%p,%i)=%i\n", _FL, Data, Len, r); OnWrite((const char*)Data, r); break; } } } else { r = -1; DebugTrace("%s:%i - No SSL\n", _FL); } } else { uint64 Start = LCurrentTime(); int To = GetTimeout(); while (HasntTimedOut()) { if (!Library) break; r = Library->BIO_write(Bio, Data, (int)Len); DebugTrace("%s:%i - BIO_write(%p,%i)=%i\n", _FL, Data, Len, r); if (r <= 0) { if (!Library->BIO_should_retry(Bio)) break; if (d->IsBlocking) LSleep(1); else break; } else { OnWrite((const char*)Data, r); break; } } } if (r > 0) { LStream *l = GetLogStream(); if (l) l->Write(Data, r); } else if (Ssl) { auto Err = Library->SSL_get_error(Ssl, r); if (Err == SSL_ERROR_ZERO_RETURN) { DebugTrace("%s:%i - ::Write closing %i\n", _FL, r); Close(); } else if (Err != SSL_ERROR_WANT_WRITE && Err != SSL_ERROR_SYSCALL) { char Buf[256] = ""; char *e = Library->ERR_error_string(Err, Buf); DebugTrace("%s:%i - ::Write error %i, %s\n", _FL, Err, e); OnError(Err, e ? e : "ERR_error_string failed"); } } return r; } ssize_t SslSocket::Read(void *Data, ssize_t Len, int Flags) { LMutex::Auto Lck(&Lock, _FL); if (!Library || d->Cancel->IsCancelled()) return -1; if (Bio) { int r = 0; if (d->UseSSLrw) { if (Ssl) { uint64 Start = LCurrentTime(); int To = GetTimeout(); while (HasntTimedOut()) { r = Library->SSL_read(Ssl, Data, (int)Len); DebugTrace("%s:%i - SSL_read(%p,%i)=%i\n", _FL, Data, Len, r); if (r < 0) { if (!Library->BIO_should_retry(Bio)) { DebugTrace("%s:%i - BIO_should_retry is false\n", _FL); break; } if (d->IsBlocking) LSleep(1); else break; } else { OnRead((char*)Data, r); break; } } } else { DebugTrace("%s:%i - Ssl is NULL\n", _FL); r = -1; } } else { uint64 Start = LCurrentTime(); int To = GetTimeout(); while (HasntTimedOut()) { r = Library->BIO_read(Bio, Data, (int)Len); DebugTrace("%s:%i - BIO_read(%p,%i)=%i\n", _FL, Data, Len, r); if (r < 0) { auto Retry = Library->BIO_should_retry(Bio); DebugTrace("%s:%i - BIO_should_retry=%i IsBlocking=%i\n", _FL, Retry, d->IsBlocking); if (!Retry) { Library->BIO_get_retry_reason(Bio); break; } if (d->IsBlocking) LSleep(1); else break; } else { if (r > 0) OnRead((char*)Data, r); break; } } } if (r > 0) { LStream *l = GetLogStream(); if (l) l->Write(Data, r); } else if (Ssl) { int Err = Library->SSL_get_error(Ssl, r); DebugTrace("%s:%i - SSL_get_error = %i\n", _FL, Err); if (Err == SSL_ERROR_ZERO_RETURN) { DebugTrace("%s:%i - ::Read closing %i\n", _FL, r); Close(); } else if (Err != SSL_ERROR_WANT_READ && Err != SSL_ERROR_SYSCALL) { char Buf[256]; char *e = Library->ERR_error_string(Err, Buf); OnError(Err, e ? e : "ERR_error_string failed"); } } return r; } return -1; } void SslSocket::OnError(int ErrorCode, const char *ErrorDescription) { DebugTrace("%s:%i - OnError=%i,%s\n", _FL, ErrorCode, ErrorDescription); LString s; s.Printf("Error %i: %s\n", ErrorCode, ErrorDescription); Log(s, s.Length(), SocketMsgError); } void SslSocket::DebugTrace(const char *fmt, ...) { if (DebugLogging) { char Buffer[512]; va_list Arg; va_start(Arg, fmt); int Ch = vsprintf_s(Buffer, sizeof(Buffer), fmt, Arg); va_end(Arg); if (Ch > 0) { #if 0 LgiTrace("SSL:%p: %s", this, Buffer); #else OnInformation(Buffer); #endif } } } void SslSocket::OnInformation(const char *Str) { while (Str && *Str) { LAutoString a; const char *nl = Str; while (*nl && *nl != '\n') nl++; int Len = (int) (nl - Str + 2); a.Reset(new char[Len]); char *o; for (o = a; Str < nl; Str++) { if (*Str != '\r') *o++ = *Str; } *o++ = '\n'; *o++ = 0; LAssert((o-a) <= Len); Log(a, -1, SocketMsgInfo); Str = *nl ? nl + 1 : nl; } } LString SslSocket::Random(int Len) { LString s; s.Length(Len); auto r = Library ? Library->RAND_bytes((uint8_t*) s.Get(), Len) : 0; return r ? s : NULL; } diff --git a/src/common/Text/Utf8.cpp b/src/common/Text/Utf8.cpp --- a/src/common/Text/Utf8.cpp +++ b/src/common/Text/Utf8.cpp @@ -1,277 +1,277 @@ #include "lgi/common/Lgi.h" ///////////////////////////////////////////////////////////////////////////// bool LUtf8Ptr::Warn = true; LUtf8Ptr::LUtf8Ptr(const void *p) { Ptr = (uint8_t*)p; } LUtf8Ptr::operator int32() { uint8_t *p = Ptr; ssize_t l = 6; return LgiUtf8To32(p, l); } LUtf8Ptr &LUtf8Ptr::operator =(uint32_t ch) { uint8_t *p = Ptr; ssize_t l = 6; LgiUtf32To8(ch, p, l); Ptr = p; return *this; } void LUtf8Ptr::Add(wchar_t c) { ssize_t l = 6; LgiUtf32To8(c, Ptr, l); } LUtf8Ptr &LUtf8Ptr::operator++() { if (IsUtf8_Lead(*Ptr)) { Ptr++; while (IsUtf8_Trail(*Ptr)) { Ptr++; } } else { if (IsUtf8_Trail(*Ptr)) { if (Warn) { Warn = false; LAssert(!"Invalid UTF"); } Ptr++; while (IsUtf8_Trail(*Ptr)) Ptr++; } else Ptr++; } return *this; } LUtf8Ptr &LUtf8Ptr::operator--() { Ptr--; if (IsUtf8_Trail(*Ptr)) { Ptr--; while (IsUtf8_Trail(*Ptr)) { Ptr--; } LAssert(IsUtf8_Lead(*Ptr)); } else { LAssert((*Ptr & 0x80) == 0); } return *this; } LUtf8Ptr &LUtf8Ptr::operator++(const int i) { if (IsUtf8_Lead(*Ptr)) { Ptr++; while (IsUtf8_Trail(*Ptr)) { Ptr++; } } else { if (IsUtf8_Trail(*Ptr)) { if (Warn) { Warn = false; LAssert(!"Invalid UTF"); } Ptr++; while (IsUtf8_Trail(*Ptr)) Ptr++; } else Ptr++; } return *this; } LUtf8Ptr &LUtf8Ptr::operator--(const int i) { Ptr--; if (IsUtf8_Trail(*Ptr)) { Ptr--; while (IsUtf8_Trail(*Ptr)) { Ptr--; } LAssert(IsUtf8_Lead(*Ptr)); } else { LAssert((*Ptr & 0x80) == 0); } return *this; } LUtf8Ptr &LUtf8Ptr::operator += (ssize_t n) { while (*Ptr && n-- > 0) { (*this)++; } return *this; } LUtf8Ptr &LUtf8Ptr::operator-=(ssize_t n) { while (*Ptr && n-- > 0) { (*this)--; } return *this; } int LUtf8Ptr::GetBytes() { return (int)strlen((char*)Ptr); } int LUtf8Ptr::GetChars() { int Count = 0; uint8_t *p = Ptr; while (*p) { if (IsUtf8_Lead(*p)) { p++; while (IsUtf8_Trail(*p)) { p++; } } else { p++; } Count++; } return Count; } ///////////////////////////////////////////////////////////////////////////// -GUtf8Str::GUtf8Str(char *utf, int bytes, bool Copy) +LUtf8Str::LUtf8Str(char *utf, int bytes, bool Copy) { Own = Copy; Ptr = Start = Copy ? (uint8_t*)NewStr(utf) : (uint8_t*)utf; End = bytes >= 0 ? Start + bytes : 0; Cur = Start; } -GUtf8Str::GUtf8Str(wchar_t *wide, int chars) +LUtf8Str::LUtf8Str(wchar_t *wide, int chars) { Own = true; Start = (uint8_t*)WideToUtf8(wide); End = chars >= 0 ? Start + chars : 0; Cur = Start; } -GUtf8Str::~GUtf8Str() +LUtf8Str::~LUtf8Str() { Empty(); } -void GUtf8Str::Empty() +void LUtf8Str::Empty() { if (Own) { DeleteArray(Start); } else Start = 0; Cur = (char*)0; End = 0; Own = false; } -GUtf8Str &GUtf8Str::operator =(char *s) +LUtf8Str &LUtf8Str::operator =(char *s) { Empty(); Start = (uint8_t*)s; Cur = Start; End = 0; return *this; } -wchar_t *GUtf8Str::ToWide() +wchar_t *LUtf8Str::ToWide() { if (End) { size_t Len = End - Ptr; if (Len > 0) { return Utf8ToWide((char*)Ptr, (int)Len); } } else { return Utf8ToWide((char*)Ptr); } return 0; } -bool GUtf8Str::Valid() +bool LUtf8Str::Valid() { if (!Start || !Ptr) return false; if (Ptr < Start) return false; if (End && Ptr > End) return false; return true; } -bool GUtf8Str::IsStart() +bool LUtf8Str::IsStart() { return !Ptr || Ptr == Start; } -bool GUtf8Str::IsEnd() +bool LUtf8Str::IsEnd() { if (End) return Ptr >= End; return !Ptr || *Ptr == 0; } 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,2699 +1,2707 @@ /*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; - int ListItem_Image; + bool Selected = false; + bool Visible = true; + int ListItem_Image = -1; List Cols; LArray Str; LArray Display; - int16 LayoutColumn; + int16 LayoutColumn = -1; LListItemPrivate() { - Selected = 0; - ListItem_Image = -1; - LayoutColumn = -1; } ~LListItemPrivate() { Cols.DeleteObjects(); EmptyStrings(); EmptyDisplay(); } void EmptyStrings() { - for (int i=0; id->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() { d = new LListItemPrivate; Pos.ZOff(-1, -1); Parent = 0; } 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) { if (Parent && Parent->GetMode() == LListDetails) { Info->x = 1024; } else { LDisplayString *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 && c) { LRect ng = Ctx; // non-grid area if (c->InDrag()) { pDC->Colour(DragColumnColour); pDC->Rectangle(&ng); } else { LColour Background = Ctx.Back; if (Parent->GetMode() == LListDetails && c->Mark() && !d->Selected) { Background = GdcMixColour(LColour(0, 24), Background, (double)1/32); } if (GridLines()) { ng.x2--; ng.y2--; } if (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->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) + if (!Parent || !d->Visible) return; int x = Ctx.x1; 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; 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; } // 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 (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; } - else - { - break; - } } } 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(); - } - - Invalidate(); + + 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; } diff --git a/src/common/Widgets/ProgressDlg.cpp b/src/common/Widgets/ProgressDlg.cpp --- a/src/common/Widgets/ProgressDlg.cpp +++ b/src/common/Widgets/ProgressDlg.cpp @@ -1,635 +1,639 @@ /*hdr ** FILE: LProgressDlg.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Progress stuff ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" ///////////////////////////////////////////////////////////////////////////////////////// -Progress::Progress() : LMutex("ProgressObj") +Progress::Progress() : LMutex("Progress") { } Progress::Progress(char *desc, int64 l, int64 h, char *type, double scale) : LMutex("Progress") { Description = desc; Val = Low = l; High = h; Type = type; Scale = scale; } +Progress::~Progress() +{ +} + LString Progress::GetDescription() { LString r; LMutex::Auto lck(this, _FL); if (!lck) LAssert(0); else r = Description.Get(); return r; } void Progress::SetDescription(const char *d) { LMutex::Auto lck(this, _FL); if (!lck) { LAssert(0); return; } if (d != Description) Description = d; } LString Progress::GetType() { LString s; { LMutex::Auto lck(this, _FL); if (lck) s = Type.Get(); } return s; } void Progress::SetType(const char *t) { LMutex::Auto lck(this, _FL); Type = t; } Progress &Progress::operator =(Progress &p) { SetDescription(p.GetDescription()); SetRange(p.GetRange()); SetScale(p.GetScale()); SetType(p.GetType()); Value(p.Value()); return *this; } ///////////////////////////////////////////////////////////////////////////////////////// #define IDC_DESCRIPTION 100 #define IDC_VALUE 101 #define IDC_RATE 102 #define IDC_PROGRESS 103 #define IDC_BUTTON 104 #define IDC_TABLE 105 #define IDC_PANE 106 #define IDC_REMAINING 107 #define PANE_X 300 #define PANE_Y 100 LProgressPane::LProgressPane(LProgressDlg *dlg) : Dlg(dlg) { LRect r(0, 0, PANE_X-1, PANE_Y-1); SetPos(r); Name(LLoadString(L_PROGRESSDLG_PROGRESS, "Progress")); SetId(IDC_PANE); Ref = 0; if (AddView(t = new LTableLayout(IDC_TABLE))) { OnPosChange(); #define PAD c->Padding(LCss::Len(LCss::LenPx, 1)); #define COLUMNS 3 int Row = 0, Col = 0; LLayoutCell *c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->Height("1.2em"); // This stops the layout flickering c->Add(Desc = new LTextLabel(IDC_DESCRIPTION, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row); PAD c->Add(ValText = new LTextLabel(IDC_VALUE, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row); PAD c->Add(Rate = new LTextLabel(IDC_RATE, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row++); PAD c->Add(Remaining = new LTextLabel(IDC_REMAINING, 0, 0, -1, -1, "##")); c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->Add(Bar = new LProgressView(IDC_PROGRESS, 0, 0, PANE_X - 14, 10, "Progress")); c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->TextAlign(LCss::Len(LCss::AlignCenter)); c->Add(But = new LButton(IDC_BUTTON, 0, 0, -1, -1, LLoadString(L_PROGRESSDLG_REQ_ABORT, "Request Abort"))); } } LProgressPane::~LProgressPane() { } bool LProgressPane::SetRange(const LRange &r) { UiDirty = true; Progress::SetRange(r); if (Bar) Bar->SetRange(r); if (InThread()) { LProgressDlg *Pd = dynamic_cast(GetParent()); if (Pd && But) But->Enabled(Pd->CanCancel); } return true; } void LProgressPane::UpdateUI() { if (!UiDirty) return; char Str[256]; bool Update = false; UiDirty = false; uint64 Now = LCurrentTime(); if (Start == 0) { // initialize the clock Start = Now; StartDt.SetNow(); } else if (Rate) { // calc rate double ElapsedSeconds = ((double)(Now - Start)) / 1000.0; double PerSec = 0.0; if (ElapsedSeconds != 0.0) PerSec = ((double) Val - Low) / ElapsedSeconds; sprintf_s(Str, sizeof(Str), LLoadString(L_PROGRESSDLG_RATE_FMT, "@ %.2f %s / sec"), PerSec * Scale, (Type) ? Type.Get() : ""); Update |= Rate->Name(Str); if (Remaining && PerSec > 0.0) { auto TotalSeconds = (High - Low + 1) / PerSec; // auto RemainingSeconds = TotalSeconds - ElapsedSeconds; LDateTime End; End.Set(StartDt.Ts() + (uint64_t)(TotalSeconds * LDateTime::Second64Bit)); if (auto Dur = LDateTime::Now().DescribePeriod(End)) Update |= Remaining->Name(Dur); } } if (ValText) { auto ValFmt = LLoadString(L_PROGRESSDLG_VALUE_FMT, "%g of %g %s"); sprintf_s(Str, sizeof(Str), ValFmt, (double)Val * Scale, (double)(High - Low) * Scale, (Type) ? Type.Get() : ""); Update |= ValText->Name(Str); } if (Bar) { if (High != Low) { #ifdef ALT_SCALE double Raw = ((double) v - Low) / ((double) High - Low); Bar->Value((int)(Raw * ALT_SCALE)); #else Bar->Value(Value()); #endif Bar->Invalidate(); } else { Bar->Value(0); } } if (ValText) ValText->SendNotify(LNotifyTableLayoutRefresh); } void LProgressPane::Value(int64 v) { Progress::Value(v); UiDirty = true; if (Dlg) Dlg->TimeCheck(); } void LProgressPane::OnCreate() { AttachChildren(); } LProgressPane &LProgressPane::operator++(int) { Value(Progress::Value() + 1); return *this; } LProgressPane &LProgressPane::operator--(int) { Value(Progress::Value() - 1); return *this; } int LProgressPane::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_TABLE: { if (n.Type == LNotifyTableLayoutChanged) { LRect p = GetPos(); LRect tbl_pos = t->GetPos(); LRect tbl_req = t->GetUsedArea(); if (tbl_req.Valid() && tbl_req.Y() > tbl_pos.Y()) { p.y2 = p.y1 + (tbl_req.Y() + LTableLayout::CellSpacing * 2); SetPos(p); SendNotify(LNotifyTableLayoutChanged); } } break; } case IDC_BUTTON: { Cancel(true); if (But) { But->Name("Waiting..."); } break; } } return 0; } void LProgressPane::OnPaint(LSurface *pDC) { LRect r = GetClient(); LThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); } void LProgressPane::OnPosChange() { if (t) { LRect cr = GetClient(); cr.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(cr); } } LFont *LProgressPane::GetFont() { // GdcBeTtf *Fnt = LSysFont; // return (Fnt) ? Fnt->Handle() : 0; return 0; } void LProgressPane::SetDescription(const char *d) { Progress::SetDescription(d); if (Desc) { Desc->Name(d); Desc->SendNotify(LNotifyTableLayoutRefresh); } } ///////////////////////////////////////////////////////////////////////////////////////// // #ifdef WIN32 // #define DefX ((GetSystemMetrics(SM_CXDLGFRAME) * 2)) // #define DefY ((GetSystemMetrics(SM_CYDLGFRAME) * 2) + GetSystemMetrics(SM_CYCAPTION)) // #else #define DefX LAppInst->GetMetric(LGI_MET_DECOR_X) #define DefY LAppInst->GetMetric(LGI_MET_DECOR_Y) // #endif LProgressDlg::LProgressDlg(LView *parent, uint64 timeout) { Ts = LCurrentTime(); Timeout = timeout; CanCancel = true; SetParent(parent); Resize(); if (parent) MoveSameScreen(parent); else MoveToCenter(); Name(LLoadString(L_PROGRESSDLG_PROGRESS, "Progress")); if (Timeout == 0) DoModeless(); else Push(); } LProgressDlg::~LProgressDlg() { if (Visible()) EndModeless(true); } bool LProgressDlg::OnRequestClose(bool OsClose) { for (auto p: Panes) p->Cancel(true); return false; } void LProgressDlg::Resize() { LRect r, c = GetPos(); int DecorX = LAppInst->GetMetric(LGI_MET_DECOR_X); int DecorY = LAppInst->GetMetric(LGI_MET_DECOR_Y); size_t Items = MAX(1, Panes.Length()); int Width = DecorX + PANE_X; int Height = (int) (DecorY + (PANE_Y * Items)); r.ZOff(Width - 1, Height - 1); r.Offset(c.x1, c.y1); SetPos(r); // Layout all the panes... int y = 0; for (auto p: Panes) { LRect r(0, y, PANE_X - 1, y + PANE_Y - 1); p->SetPos(r); p->Visible(true); y = r.y2 + 1; } } void LProgressDlg::OnCreate() { if (Panes.Length() == 0) Push(); SetPulse(500); } void LProgressDlg::OnPulse() { for (auto p: Panes) p->UpdateUI(); } void LProgressDlg::OnPosChange() { LRect c = GetClient(); // Layout all the panes... int y = 0; for (auto p: Panes) { LRect r = p->GetPos(); r.Offset(0, y-r.y1); r.x2 = c.x2; p->SetPos(r); p->Visible(true); y = r.y2 + 1; } } int LProgressDlg::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_PANE && n.Type == LNotifyTableLayoutChanged) { // This code recalculates the size needed by all the progress panes // and then resizes the window to contain them all. LRect u(0, 0, -1, -1); for (auto p: Panes) { LRect r = p->GetPos(); if (u.Valid()) u.Union(&r); else u = r; } if (u.Valid()) { int x = u.X(); int y = u.Y(); LRect p = GetPos(); p.SetSize(x + LAppInst->GetMetric(LGI_MET_DECOR_X), y + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(p); } } return 0; } LMessage::Result LProgressDlg::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #ifdef WIN32 case WM_CLOSE: { return 0; } #endif } return LDialog::OnEvent(Msg); } LProgressPane *LProgressDlg::ItemAt(int i) { return Panes.ItemAt(i); } LProgressPane *LProgressDlg::Push() { LProgressPane *Pane = new LProgressPane(this); if (Pane) { // Attach the new pane.. if (Visible()) Pane->Attach(this); else AddView(Pane); Panes.Add(Pane); Resize(); } return Pane; } void LProgressDlg::Pop(LProgressPane *p) { LProgressPane *Pane = (p) ? p : Panes.Last(); if (Pane) { Pane->Detach(); Panes.Delete(Pane); LView::Invalidate(); DeleteObj(Pane); Resize(); } } void LProgressDlg::SetCanCancel(bool cc) { CanCancel = cc; } LString LProgressDlg::GetDescription() { LString s; if (Panes.Length()) s = Panes.First()->GetDescription(); return s; } void LProgressDlg::SetDescription(const char *d) { if (Panes.Length()) Panes.First()->SetDescription(d); } LRange LProgressDlg::GetRange() { if (Panes.Length()) return Panes.First()->GetRange(); return LRange(); } bool LProgressDlg::SetRange(const LRange &r) { if (!Panes.Length()) return false; Panes.First()->SetRange(r); return true; } LProgressDlg &LProgressDlg::operator++(int) { if (Panes.Length()) { auto p = Panes.First(); (*p)++; } return *this; } LProgressDlg &LProgressDlg::operator--(int) { if (Panes.Length()) { auto p = Panes.First(); (*p)--; } return *this; } void LProgressDlg::TimeCheck() { if (!InThread()) return; uint64 Now = LCurrentTime(); if (Timeout) { if (Now - Ts >= Timeout) { DoModeless(); Timeout = 0; } } } int64 LProgressDlg::Value() { return Panes.Length() ? Panes.First()->Value() : -1; } void LProgressDlg::Value(int64 v) { if (Panes.Length()) Panes.First()->Value(v); } double LProgressDlg::GetScale() { return Panes.Length() ? Panes.First()->GetScale() : 0.0; } void LProgressDlg::SetScale(double s) { if (Panes.Length()) Panes.First()->SetScale(s); } LString LProgressDlg::GetType() { return Panes.Length() ? Panes.First()->GetType() : NULL; } void LProgressDlg::SetType(const char *t) { if (Panes.Length()) Panes.First()->SetType(t); } bool LProgressDlg::IsCancelled() { for (auto p: Panes) { if (p->IsCancelled()) return true; } return false; } void LProgressDlg::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } diff --git a/src/common/Widgets/Tree.cpp b/src/common/Widgets/Tree.cpp --- a/src/common/Widgets/Tree.cpp +++ b/src/common/Widgets/Tree.cpp @@ -1,2270 +1,2258 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Tree.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #define TREE_BLOCK 16 #define DRAG_THRESHOLD 4 #define DRAG_SCROLL_EDGE 20 #define DRAG_SCROLL_X 8 #define DRAG_SCROLL_Y 1 #define TreeUpdateNow false #define TREELOCK LMutex::Auto Lck(d, _FL); #define ForAll(Items) for (auto c : Items) ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility class LTreePrivate : public LMutex { public: // Private data int LineFlags[4]; bool LayoutDirty; LPoint Limit; LPoint LastClick; LPoint DragStart; int DragData; LMemDC *IconCache; bool InPour; int64 DropSelectTime; int8 IconTextGap; int LastLayoutPx; LMouse *CurrentClick; LTreeItem *ScrollTo; // Visual style LTree::ThumbStyle Btns; bool JoiningLines; // Pointers into items... be careful to clear when deleting items... LTreeItem *LastHit; List Selection; LTreeItem *DropTarget; LTreePrivate() : LMutex("LTreePrivate") { CurrentClick = NULL; LastLayoutPx = -1; DropSelectTime = 0; InPour = false; LastHit = 0; DropTarget = 0; IconCache = 0; LayoutDirty = true; IconTextGap = 0; ScrollTo = NULL; Btns = LTree::TreeTriangle; JoiningLines = false; } ~LTreePrivate() { DeleteObj(IconCache); } }; class LTreeItemPrivate { LArray Ds; LArray ColPx; public: LTreeItem *Item; LRect Pos; LRect Thumb; LRect Text; LRect Icon; bool Open; bool Selected; bool Visible; bool Last; int Depth; LTreeItemPrivate(LTreeItem *it) { Item = it; Ds = NULL; Pos.ZOff(-1, -1); Open = false; Selected = false; Visible = false; Last = false; Depth = 0; Text.ZOff(-1, -1); Icon.ZOff(-1, -1); } ~LTreeItemPrivate() { Ds.DeleteObjects(); } LDisplayString *GetDs(int Col, int FixPx) { if (!Ds[Col]) { LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; Ds[Col] = new LDisplayString(f, Item->GetText(Col)); if (Ds[Col]) { ColPx[Col] = Ds[Col]->X(); if (FixPx > 0) { Ds[Col]->TruncateWithDots(FixPx); } } } return Ds[Col]; } void ClearDs(int Col = -1) { if (Col >= 0) { delete Ds[Col]; Ds[Col] = NULL; } else { Ds.DeleteObjects(); } } int GetColumnPx(int Col) { int BasePx = 0; GetDs(Col, 0); if (Col == 0) { BasePx = (Depth + 1) * TREE_BLOCK; } return ColPx[Col] + BasePx; } }; ////////////////////////////////////////////////////////////////////////////// LTreeNode::LTreeNode() { Parent = NULL; Tree = NULL; } LTreeNode::~LTreeNode() { } void LTreeNode::SetLayoutDirty() { Tree->d->LayoutDirty = true; } void LTreeNode::_Visible(bool v) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { LAssert(i != this); i->OnVisible(v); i->_Visible(v); } } void LTreeNode::_ClearDs(int Col) { List::I it = Items.begin(); for (LTreeItem *c = *it; c; c = *++it) { c->_ClearDs(Col); } } LItemContainer *LTreeItem::GetContainer() { return Tree; } LTreeItem *LTreeNode::Insert(LTreeItem *Obj, ssize_t Idx) { LAssert(Obj != this); if (Obj && Obj->Tree) Obj->Remove(); LTreeItem *NewObj = Obj ? Obj : new LTreeItem; if (NewObj) { NewObj->Parent = Item(); NewObj->_SetTreePtr(Tree); Items.Delete(NewObj); Items.Insert(NewObj, Idx); if (Tree) { Tree->d->LayoutDirty = true; if (Pos() && Pos()->Y() > 0) Tree->_UpdateBelow(Pos()->y1); else Tree->Invalidate(); } } return NewObj; } void LTreeNode::Detach() { if (Parent) { LTreeItem *It = Item(); if (It) { LAssert(Parent->Items.HasItem(It)); Parent->Items.Delete(It); } Parent = 0; } if (Tree) { Tree->d->LayoutDirty = true; Tree->Invalidate(); } if (Item()) Item()->_SetTreePtr(0); } void LTreeNode::Remove() { int y = 0; if (Parent) { LTreeItem *i = Item(); if (i && i->IsRoot()) { LRect *p = Pos(); LTreeItem *Prev = GetPrev(); if (Prev) { y = Prev->d->Pos.y1; } else { y = p->y1; } } else { y = Parent->d->Pos.y1; } } LTree *t = Tree; if (Item()) Item()->_Remove(); if (t) { t->_UpdateBelow(y); } } bool LTreeNode::IsRoot() { return Parent == 0 || (LTreeNode*)Parent == (LTreeNode*)Tree; } size_t LTreeNode::Length() { return Items.Length(); } bool LTreeNode::HasItem(LTreeItem *obj, bool recurse) { if (!obj) return false; if (this == (LTreeNode*)obj) return true; for (auto i: Items) { if (i == obj) return true; if (recurse && i->HasItem(obj, recurse)) return true; } return false; } int LTreeNode::ForEach(std::function Fn) { int Count = 0; for (auto t : Items) { Fn(t); Count += t->ForEach(Fn); } return Count + 1; } ssize_t LTreeNode::IndexOf() { if (Parent) { return Parent->Items.IndexOf(Item()); } else if (Tree) { return Tree->Items.IndexOf(Item()); } return -1; } LTreeItem *LTreeNode::GetChild() { return Items.Length() ? Items[0] : NULL; } LTreeItem *LTreeNode::GetPrev() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index-1); } } return 0; } LTreeItem *LTreeNode::GetNext() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index+1); } } return 0; } ////////////////////////////////////////////////////////////////////////////// LTreeItem::LTreeItem() { d = new LTreeItemPrivate(this); Str = 0; Sys_Image = -1; } LTreeItem::~LTreeItem() { if (Tree) { if (Tree->d->DropTarget == this) { Tree->d->DropTarget = 0; } if (Tree->d->LastHit == this) { Tree->d->LastHit = 0; } if (Tree->IsCapturing()) Tree->Capture(false); } int y = 0; LTree *t = 0; if (Parent && (LTreeNode*)Parent != (LTreeNode*)Tree) { t = Tree; y = Parent->d->Pos.y1; } else if ((LTreeNode*)this != (LTreeNode*)Tree) { t = Tree; LTreeItem *p = GetPrev(); if (p) { y = p->d->Pos.y1; } else { y = d->Pos.y1; } } _Remove(); while (Items.Length()) { auto It = Items.begin(); delete *It; } DeleteObj(d); if (t) { t->_UpdateBelow(y); } } int LTreeItem::GetColumnSize(int Col) { int Px = d->GetColumnPx(Col); if (Expanded()) { ForAll(Items) { int ChildPx = c->GetColumnSize(Col); Px = MAX(ChildPx, Px); } } return Px; } LRect *LTreeItem::Pos() { return &d->Pos; } LPoint LTreeItem::_ScrollPos() { LPoint p; if (Tree) p = Tree->_ScrollPos(); return p; } LRect *LTreeItem::_GetRect(LTreeItemRect Which) { switch (Which) { case TreeItemPos: return &d->Pos; case TreeItemThumb: return &d->Thumb; case TreeItemText: return &d->Text; case TreeItemIcon: return &d->Icon; } return 0; } -/* -bool LTreeItem::SortChildren(int (*compare)(LTreeItem *a, LTreeItem *b, NativeInt data), NativeInt data) -{ - Items.Sort(compare, data); - if (Tree) - { - Tree->_Pour(); - Tree->Invalidate(); - } - return true; -} -*/ - bool LTreeItem::IsDropTarget() { LTree *t = GetTree(); if (t && t->d && t->d->DropTarget == this) return true; return false; } LRect *LTreeItem::GetPos(int Col) { if (!d->Pos.Valid() && Tree) Tree->_Pour(); static LRect r; r = d->Pos; if (Col >= 0) { LItemColumn *Column = 0; int Cx = Tree->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Tree->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } return &r; } void LTreeItem::_RePour() { if (Tree) - { Tree->_Pour(); - } } void LTreeItem::ScrollTo() { if (!Tree) return; if (Tree->VScroll) { LRect c = Tree->GetClient(); LRect p = d->Pos; int y = d->Pos.Y() ? d->Pos.Y() : 16; p.Offset(0, (int) (-Tree->VScroll->Value() * y)); if (p.y1 < c.y1) { int Lines = (c.y1 - p.y1 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() - Lines); } else if (p.y2 > c.y2) { int Lines = (p.y2 - c.y2 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() + Lines); } } else { Tree->d->ScrollTo = this; if (Tree->IsAttached()) Tree->PostEvent(M_SCROLL_TO); } } void LTreeItem::_SetTreePtr(LTree *t) { if (Tree && !t) { // Clearing tree pointer, must remove all references to this item that // the tree might still have. if (d->Selected) { Tree->d->Selection.Delete(this); d->Selected = false; } if (Tree->d->LastHit == this) { Tree->d->LastHit = 0; } if (Tree->d->DropTarget == this) { Tree->d->DropTarget = 0; } } Tree = t; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) { i->_SetTreePtr(t); } } void LTreeItem::_Remove() { if ((LTreeNode*)this != (LTreeNode*)Tree) { if (Parent) { LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); } else if (Tree) { LAssert(Tree->Items.HasItem(this)); Tree->Items.Delete(this); } if (Tree) { LAssert(Tree->d != NULL); Tree->d->LayoutDirty = true; if (Tree->IsCapturing()) Tree->Capture(false); } } Parent = 0; _SetTreePtr(0); } void LTreeItem::_PourText(LPoint &Size) { LFont *f = Tree ? Tree->GetFont() : LSysFont; auto *Txt = GetText(); #if defined(_WIN64) && defined(_DEBUG) if ((void*)Txt == (void*)0xfeeefeeefeeefeee || (void*)Txt == (void*)0xcdcdcdcdcdcdcdcd) { LAssert(!"Yeah nah..."); } #endif LDisplayString ds(f, Txt); Size.x = ds.X() + 4; Size.y = 0; } void LTreeItem::_PaintText(LItem::ItemPaintCtx &Ctx) { const char *Text = GetText(); if (Text) { LDisplayString *Ds = d->GetDs(0, d->Text.X()); LFont *f = Tree ? Tree->GetFont() : LSysFont; int Tab = f->TabSize(); f->TabSize(0); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.TxtBack); if (Ds) { Ds->Draw(Ctx.pDC, d->Text.x1 + 2, d->Text.y1 + 1, &d->Text); if (Ctx.x2 > d->Text.x2) { LRect r = Ctx; r.x1 = d->Text.x2 + 1; Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } f->TabSize(Tab); } else { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } void LTreeItem::_Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible) { - d->Visible = Visible; + auto css = GetCss(false); + auto display = css ? css->Display() != LCss::DispNone : true; + + d->Visible = display && Visible; d->Depth = Depth; if (d->Visible) { LPoint TextSize; _PourText(TextSize); LImageList *ImgLst = Tree->GetImageList(); // int IconX = (ImgLst && GetImage() >= 0) ? ImgLst->TileX() + Tree->d->IconTextGap : 0; int IconY = (ImgLst && GetImage() >= 0) ? ImgLst->TileY() : 0; int Height = MAX(TextSize.y, IconY); if (!Height) Height = 16; LDisplayString *Ds = d->GetDs(0, 0); d->Pos.ZOff(ColumnPx - 1, (Ds ? MAX(Height, Ds->Y()) : Height) - 1); d->Pos.Offset(0, Limit->y); if (!d->Pos.Valid()) { printf("Invalid pos: %s, ColumnPx=%i\n", d->Pos.GetStr(), ColumnPx); } Limit->x = MAX(Limit->x, d->Pos.x2 + 1); Limit->y = MAX(Limit->y, d->Pos.y2 + 1); } else { d->Pos.ZOff(-1, -1); } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(Limit, ColumnPx, Depth+1, d->Open && d->Visible); } } void LTreeItem::_ClearDs(int Col) { d->ClearDs(Col); LTreeNode::_ClearDs(Col); } const char *LTreeItem::GetText(int i) { return Str[i]; } bool LTreeItem::SetText(const char *s, int i) { if (!Str[i].Reset(NewStr(s))) return false; if (Tree) Update(); return true; } int LTreeItem::GetImage(int Flags) { return Sys_Image; } void LTreeItem::SetImage(int i) { Sys_Image = i; } void LTreeItem::Update() { if (Tree) { LRect p = d->Pos; p.x2 = 10000; d->ClearDs(); Tree->_Update(&p, TreeUpdateNow); } } bool LTreeItem::Select() { return d->Selected; } void LTreeItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; if (b) { LTreeItem *p = this; while ((p = p->GetParent())) { p->Expanded(true); } } Update(); if (b && Tree) { Tree->_OnSelect(this); Tree->OnItemSelect(this); } } } bool LTreeItem::Expanded() { return d->Open; } void LTreeItem::Expanded(bool b) { if (d->Open != b) { d->Open = b; if (Items.Length() > 0) { if (Tree) { Tree->d->LayoutDirty = true; Tree->_UpdateBelow(d->Pos.y1); } OnExpand(b); } } } void LTreeItem::OnExpand(bool b) { _Visible(b); } LTreeItem *LTreeItem::_HitTest(int x, int y, bool Debug) { LTreeItem *Status = 0; if (d->Pos.Overlap(x, y) && x > (d->Depth*TREE_BLOCK)) { Status = this; } if (d->Open) { List::I it = Items.begin(); for (LTreeItem *i=*it; i && !Status; i=*++it) { Status = i->_HitTest(x, y, Debug); } } return Status; } void LTreeItem::_MouseClick(LMouse &m) { if (m.Down()) { if ((Items.Length() > 0 && d->Thumb.Overlap(m.x, m.y)) || m.Double()) { Expanded(!Expanded()); } LRect rText = d->Text; if (Tree && Tree->Columns.Length() > 0) rText.x2 = Tree->X(); if (rText.Overlap(m.x, m.y) || d->Icon.Overlap(m.x, m.y)) { Select(true); if (Tree) - { Tree->OnItemClick(this, m); - } } } } void LTreeItem::OnPaint(ItemPaintCtx &Ctx) { LAssert(Tree != NULL); + if (!d->Visible) + return; // background up to text LSurface *&pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(0, d->Pos.y1, (d->Depth*TREE_BLOCK)+TREE_BLOCK, d->Pos.y2); // draw trunk LRect Pos = d->Pos; Pos.x2 = Pos.x1 + Ctx.ColPx[0] - 1; int x = 0; LColour Ws(L_WORKSPACE); LColour Lines = Ws.Invert().Mix(Ws); pDC->Colour(Lines); if (Tree->d->JoiningLines) { for (int i=0; iDepth; i++) { if (Tree->d->LineFlags[0] & (1 << i)) pDC->Line(x + 8, Pos.y1, x + 8, Pos.y2); x += TREE_BLOCK; } } else { x += TREE_BLOCK * d->Depth; } // draw node int cy = Pos.y1 + (Pos.Y() >> 1); if (Items.Length() > 0) { d->Thumb.ZOff(8, 8); d->Thumb.Offset(x + 4, cy - 4); switch (Tree->d->Btns) { case LTree::TreePlus: { // plus/minus symbol pDC->Colour(L_LOW); pDC->Box(&d->Thumb); pDC->Colour(L_WHITE); pDC->Rectangle(d->Thumb.x1+1, d->Thumb.y1+1, d->Thumb.x2-1, d->Thumb.y2-1); pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+2, d->Thumb.y1+4, d->Thumb.x1+6, d->Thumb.y1+4); if (!d->Open) { // not open, so draw the cross bar making the '-' into a '+' pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+4, d->Thumb.y1+2, d->Thumb.x1+4, d->Thumb.y1+6); } break; } case LTree::TreeTriangle: { // Triangle style expander pDC->Colour(Lines); int Off = 2; if (d->Open) { for (int y=0; yThumb.Y(); y++) { int x1 = d->Thumb.x1 + y; int x2 = d->Thumb.x2 - y; if (x2 < x1) break; pDC->HLine(x1, x2, d->Thumb.y1 + y + Off); } } else { for (int x=0; xThumb.X(); x++) { int y1 = d->Thumb.y1 + x; int y2 = d->Thumb.y2 - x; if (y2 < y1) break; pDC->VLine(d->Thumb.x1 + x + Off, y1, y2); } } break; } } pDC->Colour(Lines); if (Tree->d->JoiningLines) { if (Parent || IndexOf() > 0) // draw line to item above pDC->Line(x + 8, Pos.y1, x + 8, d->Thumb.y1-1); // draw line to leaf beside pDC->Line(d->Thumb.x2+1, cy, x + (TREE_BLOCK-1), cy); if (!d->Last) // draw line to item below pDC->Line(x + 8, d->Thumb.y2+1, x + 8, Pos.y2); } } else if (Tree->d->JoiningLines) { // leaf node pDC->Colour(L_MED); if (d->Last) pDC->Rectangle(x + 8, Pos.y1, x + 8, cy); else pDC->Rectangle(x + 8, Pos.y1, x + 8, Pos.y2); pDC->Rectangle(x + 8, cy, x + (TREE_BLOCK-1), cy); } x += TREE_BLOCK; // draw icon int Image = GetImage(Select()); LImageList *Lst = Tree->GetImageList(); if (Image >= 0 && Lst) { d->Icon.ZOff(Lst->TileX() + Tree->d->IconTextGap - 1, Pos.Y() - 1); d->Icon.Offset(x, Pos.y1); pDC->Colour(Ctx.Back); if (Tree->d->IconCache) { // no flicker LRect From; From.ZOff(Lst->TileX()-1, Tree->d->IconCache->Y()-1); From.Offset(Lst->TileX()*Image, 0); pDC->Blt(d->Icon.x1, d->Icon.y1, Tree->d->IconCache, &From); pDC->Rectangle(d->Icon.x1 + Lst->TileX(), d->Icon.y1, d->Icon.x2, d->Icon.y2); } else { // flickers... int Px = d->Icon.y1 + ((Lst->TileY()-Pos.Y()) >> 1); pDC->Rectangle(&d->Icon); Tree->GetImageList()->Draw(pDC, d->Icon.x1, Px, Image, Ctx.Back); } x += d->Icon.X(); } LColour SelFore(Tree->Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Tree->Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); bool IsSelected = (Tree->d->DropTarget == this) || (Tree->d->DropTarget == NULL && Select()); LColour Fore = Ctx.Fore; LColour TxtBack = Ctx.TxtBack; auto Css = GetCss(); LCss::ColorDef f, b; if (Css) { f = Css->Color(); b = Css->BackgroundColor(); } // text: first column Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : (IsSelected ? SelFore : Fore); Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : (IsSelected ? SelBack : Ctx.Back); auto ColourDiff = abs(Ctx.Fore.GetGray() - Ctx.TxtBack.GetGray()); if (ColourDiff < 32) // Check if the colours are too similar and then disambiguate... { // LgiTrace("%s %s are too similar %i\n", Ctx.Fore.GetStr(), Ctx.TxtBack.GetStr(), (int)ColourDiff); Ctx.TxtBack = Ctx.TxtBack.Mix(L_WORKSPACE); } LPoint TextSize; _PourText(TextSize); d->Text.ZOff(TextSize.x-1, Pos.Y()-1); d->Text.Offset(x, Pos.y1); (LRect&)Ctx = d->Text; Ctx.x2 = Ctx.ColPx[0] - 1; _PaintText(Ctx); x = Pos.x2 + 1; // text: other columns Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : Fore; Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : Ctx.Back; for (int i=1; iColumns[i]); x = Ctx.x2 + 1; } Ctx.Fore = Fore; Ctx.TxtBack = TxtBack; // background after text pDC->Colour(Ctx.Back); pDC->Rectangle(x, Pos.y1, MAX(Tree->X(), Tree->d->Limit.x), Pos.y2); // children if (d->Open) { if (!d->Last) Tree->d->LineFlags[0] |= 1 << d->Depth; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->OnPaint(Ctx); Tree->d->LineFlags[0] &= ~(1 << d->Depth); } } void LTreeItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LDisplayString *ds = d->GetDs(i, Ctx.ColPx[i]); if (ds) { LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } } ////////////////////////////////////////////////////////////////////////////// LTree::LTree(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_TreeView) { d = new LTreePrivate; SetId(id); LRect e(x, y, x+cx, y+cy); SetPos(e); if (name) Name(name); else Name("LGI.LTree"); Sunken(true); Tree = this; Lines = true; Buttons = true; LinesAtRoot = true; EditLabels = false; ColumnHeaders = false; rItems.ZOff(-1, -1); #if WINNATIVE SetStyle(GetStyle() | WS_CHILD | WS_VISIBLE | WS_TABSTOP); #endif SetTabStop(true); LResources::StyleElement(this); } LTree::~LTree() { Empty(); DeleteObj(d); } bool LTree::Lock(const char *file, int line, int TimeOut) { if (TimeOut > 0) return d->LockWithTimeout(TimeOut, file, line); return d->Lock(file, line); } void LTree::Unlock() { return d->Unlock(); } // Internal tree methods List *LTree::GetSelLst() { return &d->Selection; } void LTree::_Update(LRect *r, bool Now) { TREELOCK if (r) { LRect u = *r; LPoint s = _ScrollPos(); LRect c = GetClient(); u.Offset(c.x1-s.x, c.y1-s.y); Invalidate(&u, Now && !d->InPour); } else { Invalidate((LRect*)0, Now && !d->InPour); } } void LTree::_UpdateBelow(int y, bool Now) { TREELOCK LPoint s = _ScrollPos(); LRect c = GetClient(); LRect u(c.x1, y - s.y + c.y1, X()-1, Y()-1); Invalidate(&u, Now); } void LTree::ClearDs(int Col) { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { TREELOCK LPoint Status; Status.x = (HScroll) ? (int)HScroll->Value() : 0; Status.y = (VScroll) ? (int)VScroll->Value() * TREE_BLOCK : 0; return Status; } void LTree::_UpdateScrollBars() { static bool Processing = false; if (!Processing) { Processing = true; { TREELOCK LPoint Old = _ScrollPos(); LRect Client = GetClient(); bool x = d->Limit.x > Client.X(); bool y = d->Limit.y > Client.Y(); SetScrollBars(x, y); Client = GetClient(); // x scroll... in pixels if (HScroll) { HScroll->SetRange(d->Limit.x); HScroll->SetPage(Client.X()); int Max = d->Limit.x - Client.X(); if (HScroll->Value() > Max) { HScroll->Value(Max+1); } } // y scroll... in items if (VScroll) { int All = (d->Limit.y + TREE_BLOCK - 1) / TREE_BLOCK; int Visible = Client.Y() / TREE_BLOCK; VScroll->SetRange(All); VScroll->SetPage(Visible); /* Why is this commented out? -fret Dec2018 int Max = All - Visible + 1; if (VScroll->Value() > Max) VScroll->Value(Max); */ } LPoint New = _ScrollPos(); if (Old.x != New.x || Old.y != New.y) { Invalidate(); } } Processing = false; } } void LTree::_OnSelect(LTreeItem *Item) { TREELOCK if ( !MultiSelect() || !d->CurrentClick || ( d->CurrentClick && !d->CurrentClick->Ctrl() ) ) { for (auto i: d->Selection) { if (i != Item) i->Select(false); } d->Selection.Empty(); } else { d->Selection.Delete(Item); } d->Selection.Insert(Item); } void LTree::_Pour() { TREELOCK d->InPour = true; d->Limit.x = rItems.x1; d->Limit.y = rItems.y1; int ColumnPx = 0; if (Columns.Length()) { for (int i=0; iWidth(); } } else { ColumnPx = d->LastLayoutPx = GetClient().X(); if (ColumnPx < 16) ColumnPx = 16; } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(&d->Limit, ColumnPx, 0, true); } _UpdateScrollBars(); d->LayoutDirty = false; d->InPour = false; } // External methods and events void LTree::OnItemSelect(LTreeItem *Item) { if (!Item) return; TREELOCK Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { TREELOCK if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { TREELOCK LTreeItem *Ret = NULL; if (i) { if (Down) { LTreeItem *n = i->GetChild(); if (!n || !n->d->Visible) { for (n = i; n; ) { LTreeItem *p = n->GetParent(); if (p) { ssize_t Index = n->IndexOf(); if (Index < (ssize_t)p->Items.Length()-1) { n = n->GetNext(); break; } else { n = p; } } else { n = n->GetNext(); break; } } } Ret = n; } else { LTreeItem *p = i->GetParent() ? i->GetParent() : 0; ssize_t Index = i->IndexOf(); if (p) { LTreeItem *n = p; if (Index > 0) { n = i->GetPrev(); while ( n->GetChild() && n->GetChild()->d->Visible) { n = n->Items.ItemAt(n->Items.Length()-1); } } Ret = n; } else if (Index > 0) { p = i->GetTree()->ItemAt(Index - 1); while (p->GetChild() && p->GetChild()->d->Visible) { if (p->Items.Length()) { p = p->Items.ItemAt(p->Items.Length()-1); } else break; } Ret = p; } } } return Ret; } bool LTree::OnKey(LKey &k) { if (!Lock(_FL)) return false; bool Status = false; LTreeItem *i = d->Selection[0]; if (!i) { i = Items[0]; if (i) i->Select(); } if (k.Down()) { switch (k.vkey) { case LK_PAGEUP: case LK_PAGEDOWN: { if (i && i->d->Pos.Y() > 0) { int Page = GetClient().Y() / i->d->Pos.Y(); for (int j=0; jSelect(true); i->ScrollTo(); } } Status = true; break; } case LK_HOME: { LTreeItem *i; if ((i = Items[0])) { i->Select(true); i->ScrollTo(); } Status = true; break; } case LK_END: { LTreeItem *n = i, *p = 0; while ((n = GetAdjacent(n, true))) { p = n; } if (p) { p->Select(true); p->ScrollTo(); } Status = true; break; } case LK_LEFT: { if (i) { if (i->Items.Length() && i->Expanded()) { i->Expanded(false); break; } else { LTreeItem *p = i->GetParent(); if (p) { p->Select(true); p->Expanded(false); _Pour(); break; } } } // fall thru } case LK_UP: { LTreeItem *n = GetAdjacent(i, false); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_RIGHT: { if (i) { i->Expanded(true); if (d->LayoutDirty) { _Pour(); break; } } // fall thru } case LK_DOWN: { LTreeItem *n = GetAdjacent(i, true); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_DELETE: { if (k.Down()) { Unlock(); // before potentially being deleted...? SendNotify(LNotification(k)); // This might delete the item... so just return here. return true; } break; } #ifdef VK_APPS case VK_APPS: { LTreeItem *s = Selection(); if (s) { LRect *r = &s->d->Text; if (r) { LMouse m; m.x = r->x1 + (r->X() >> 1); m.y = r->y1 + (r->Y() >> 1); m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); s->OnMouseClick(m); } } break; } #endif default: { switch (k.c16) { case 'F': case 'f': { if (k.Ctrl()) SendNotify(LNotifyContainerFind); break; } } break; } } } if (i && i != (LTreeItem*)this) { i->OnKey(k); } Unlock(); return Status; } LTreeItem *LTree::ItemAtPoint(int x, int y, bool Debug) { TREELOCK LPoint s = _ScrollPos(); List::I it = Items.begin(); LTreeItem *Hit = NULL; for (LTreeItem *i = *it; i; i=*++it) { Hit = i->_HitTest(s.x + x, s.y + y, Debug); if (Hit) break; } return Hit; } bool LTree::OnMouseWheel(double Lines) { TREELOCK if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { TREELOCK d->CurrentClick = &m; if (m.Down()) { DragMode = DRAG_NONE; if (ColumnHeaders && ColumnHeader.Overlap(m.x, m.y)) { d->DragStart.x = m.x; d->DragStart.y = m.y; // Clicked on a column heading LItemColumn *Resize; LItemColumn *Over = NULL; HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { Resize->Width(Resize->GetContentSize() + DEFAULT_COLUMN_SPACING); Invalidate(); } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } /* else { DragMode = CLICK_COLUMN; d->DragData = Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } */ } else if (rItems.Overlap(m.x, m.y)) { Focus(true); Capture(true); d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y, true); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } else { SendNotify(LNotification(m, LNotifyContainerClick)); } } } else if (IsCapturing()) { Capture(false); if (rItems.Overlap(m.x, m.y)) { d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } } } d->CurrentClick = NULL; } void LTree::OnMouseMove(LMouse &m) { if (!IsCapturing()) return; TREELOCK 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; } default: { if (rItems.Overlap(m.x, m.y)) { if (abs(d->LastClick.x - m.x) > DRAG_THRESHOLD || abs(d->LastClick.y - m.y) > DRAG_THRESHOLD) { OnItemBeginDrag(d->LastHit, m); Capture(false); } } break; } } } void LTree::OnPosChange() { TREELOCK if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { TREELOCK LCssTools Tools(this); #if 0 // coverage testing... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif rItems = GetClient(); LFont *f = GetFont(); if (ShowColumnHeader()) { ColumnHeader.ZOff(rItems.X()-1, f->GetHeight() + 4); PaintColumnHeadings(pDC); rItems.y1 = ColumnHeader.y2 + 1; } else { ColumnHeader.ZOff(-1, -1); } d->IconTextGap = GetFont()->GetHeight() / 6; auto cText = LColour(L_TEXT); auto cWs = LColour(L_WORKSPACE); LColour Fore = Tools.GetFore(&cText); LColour Background = Tools.GetBack(&cWs, 0); // icon cache if (GetImageList() && !d->IconCache) { int CacheHeight = MAX(LSysFont->GetHeight(), GetImageList()->Y()); d->IconCache = new LMemDC; if (d->IconCache && d->IconCache->Create(GetImageList()->X(), CacheHeight, GdcD->GetColourSpace())) { if (d->IconCache->GetColourSpace() == CsIndex8) { d->IconCache->Palette(new LPalette(GdcD->GetGlobalColour()->GetPalette())); } d->IconCache->Colour(Background); d->IconCache->Rectangle(); d->IconCache->Op(GDC_ALPHA); GetImageList()->Lock(); int DrawY = (CacheHeight - GetImageList()->TileY()) >> 1; LAssert(DrawY >= 0); for (int i=0; iGetItems(); i++) { GetImageList()->Draw(d->IconCache, i * GetImageList()->TileX(), DrawY, i, Background); } GetImageList()->Unlock(); d->IconCache->Unlock(); } } // scroll LPoint s = _ScrollPos(); int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox + s.x, Oy + s.y); // selection colour LArray ColPx; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; if (Columns.Length() > 0) { Ctx.Columns = (int)Columns.Length(); for (int i=0; iWidth(); } else { Ctx.Columns = 1; ColPx[0] = rItems.X(); } Ctx.ColPx = &ColPx[0]; Ctx.Fore = Fore; Ctx.Back = Background; Ctx.TxtBack = Background; LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); // layout items if (d->LayoutDirty) { _Pour(); } // paint items ZeroObj(d->LineFlags); List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) i->OnPaint(Ctx); pDC->SetOrigin(Ox, Oy); if (d->Limit.y-s.y < rItems.Y()) { // paint after items pDC->Colour(Background); pDC->Rectangle(rItems.x1, d->Limit.y - s.y, rItems.x2, rItems.y2); } } int LTree::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_HSCROLL: case IDC_VSCROLL: { TREELOCK if (Flags == LNotifyScrollBarCreate) { _UpdateScrollBars(); if (VScroll) { if (HasItem(d->ScrollTo)) d->ScrollTo->ScrollTo(); d->ScrollTo = NULL; } } Invalidate(); break; } } return LLayout::OnNotify(Ctrl, n); } LMessage::Result LTree::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SCROLL_TO: { LTreeItem *Item = (LTreeItem*)Msg->A(); if (!HasItem(Item)) break; if (VScroll) Item->ScrollTo(); break; } } return LItemContainer::OnEvent(Msg); } LTreeItem *LTree::Insert(LTreeItem *Obj, ssize_t Pos) { TREELOCK LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { TREELOCK if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { TREELOCK LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; TREELOCK if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { TREELOCK if (d->DropTarget) { int64 p = LCurrentTime() - d->DropSelectTime; if (p >= 1000) { SetPulse(); if (!d->DropTarget->Expanded() && d->DropTarget->GetChild()) { d->DropTarget->Expanded(true); } } } if (InsideDragOp()) { LMouse m; if (GetMouse(m)) { if (!m.Left() && !m.Right() && !m.Middle()) { // Be robust against missing drag exit events (Mac specific?) InsideDragOp(false); } else { LRect c = GetClient(); if (VScroll) { if (m.y < DRAG_SCROLL_EDGE) { // Scroll up... VScroll->Value(VScroll->Value() - DRAG_SCROLL_Y); } else if (m.y > c.Y() - DRAG_SCROLL_EDGE) { // Scroll down... VScroll->Value(VScroll->Value() + DRAG_SCROLL_Y); } } if (HScroll) { if (m.x < DRAG_SCROLL_EDGE) { // Scroll left... HScroll->Value(HScroll->Value() - DRAG_SCROLL_X); } else if (m.x > c.X() - DRAG_SCROLL_EDGE) { // Scroll right... HScroll->Value(HScroll->Value() + DRAG_SCROLL_X); } } } } } } int LTree::GetContentSize(int ColumnIdx) { TREELOCK int MaxPx = 0; List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) { int ItemPx = i->GetColumnSize(ColumnIdx); MaxPx = MAX(ItemPx, MaxPx); } return MaxPx; } LCursor LTree::GetCursor(int x, int y) { TREELOCK LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { TREELOCK InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { TREELOCK InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { TREELOCK if (Item != d->DropTarget) { bool Update = (d->DropTarget != 0) ^ (Item != 0); LTreeItem *Old = d->DropTarget; d->DropTarget = Item; if (Old) { Old->Update(); } if (d->DropTarget) { d->DropTarget->Update(); d->DropSelectTime = LCurrentTime(); } if (Update) { OnFocus(true); } } } bool LTree::Select(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && IsAttached()) { Obj->Select(true); Status = true; } else if (d->Selection.Length()) { d->Selection.Empty(); OnItemSelect(0); Status = true; } return Status; } LTreeItem *LTree::Selection() { TREELOCK return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { TREELOCK return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { TREELOCK // errors during deletion of the control can cause // this to be called after the destructor if (d) { List::I it = d->Selection.begin(); for (LTreeItem *i=*it; i; i=*++it) i->Update(); } } static void LTreeItemUpdateAll(LTreeNode *n) { for (LTreeItem *i=n->GetChild(); i; i=i->GetNext()) { i->Update(); LTreeItemUpdateAll(i); } } void LTree::UpdateAllItems() { TREELOCK d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { TREELOCK d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/src/linux/Lgi/ClipBoard.cpp b/src/linux/Lgi/ClipBoard.cpp --- a/src/linux/Lgi/ClipBoard.cpp +++ b/src/linux/Lgi/ClipBoard.cpp @@ -1,461 +1,508 @@ // Clipboard Implementation #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/ClipBoard.h" #define DEBUG_CLIPBOARD 0 #define VAR_COUNT 16 #define LGI_CLIP_BINARY "lgi.binary" #define LGI_RECEIVE_CLIPBOARD_TIMEOUT 4000 using namespace Gtk; struct ClipData : public LMutex { ::LVariant v[VAR_COUNT]; ClipData() : LMutex("ClipData") { } } Data; class LClipBoardPriv { public: GtkClipboard *c; }; +static LAutoPtr Img; + /////////////////////////////////////////////////////////////////////////////////////////////// LClipBoard::LClipBoard(LView *o) { d = new LClipBoardPriv; Owner = o; Open = false; d->c = gtk_clipboard_get(GDK_NONE); // gdk_atom_intern("CLIPBOARD", false) if (d->c) Open = true; #if DEBUG_CLIPBOARD printf("d->c = %i\n", d->c); #endif } LClipBoard::~LClipBoard() { d->c = 0; DeleteObj(d); } bool LClipBoard::Empty() { if (d->c) { gtk_clipboard_clear(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_clear(%i)\n", d->c); #endif return true; } return false; } bool LClipBoard::EnumFormats(::LArray &Formats) { return false; } bool LClipBoard::Html(const char *doc, bool AutoEmpty) { return false; } ::LString LClipBoard::Html() { return ::LString(); } bool LClipBoard::Text(const char *Str, bool AutoEmpty) { bool Status = false; if (AutoEmpty) { Empty(); } if (Str && d->c) { gtk_clipboard_set_text(d->c, Str, strlen(Str)); #if DEBUG_CLIPBOARD printf("gtk_clipboard_set_text(%i,%s,%i)\n", d->c, Str, strlen(Str)); #endif Status = true; } return Status; } char *LClipBoard::Text() { char *t = 0; if (d->c) { #if DEBUG_CLIPBOARD printf("gtk_clipboard_wait_for_text starting...\n"); #endif gchar *txt = gtk_clipboard_wait_for_text(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_wait_for_text(%i)='%s'\n", d->c, txt); #endif if (txt) { t = NewStr(txt); g_free(txt); } } return t; } bool LClipBoard::TextW(const char16 *Str, bool AutoEmpty) { LAutoString u(WideToUtf8(Str)); return Text(u, AutoEmpty); } char16 *LClipBoard::TextW() { LAutoString u(Text()); return Utf8ToWide(u); } +void LClipBoard::FreeImage(unsigned char *pixels) +{ + if (Img) + { + if (pixels == (*Img)[0]) + Img.Reset(); + else + LgiTrace("%s:%i - LClipBoard::FreeImage wrong ptr.\n", _FL); + } +} + bool LClipBoard::Bitmap(LSurface *pDC, bool AutoEmpty) { - bool Status = false; - if (pDC && d->c) + if (!pDC || !d->c) + { + LAssert(!"Param error."); + return false; + } + + if (!Img.Reset(new LMemDC)) + { + LAssert(!"Can't create surface..."); + return false; + } + + if (!Img->Create(pDC->X(), pDC->Y(), CsArgb32)) { - LMemDC *Mem = dynamic_cast(pDC); - if (Mem) + LAssert(!"Can't create surface copy..."); + return false; + } + + // We have to create a copy to get the byte order right: + Img->Blt(0, 0, pDC); + + // Img->SwapRedAndBlue(); + // Img->Colour(LColour(0xff, 0, 0)); + // Img->Rectangle(); + + // And also if the caller free's their copy of the image before the pixbuf is done it'll crash... + auto pb = gdk_pixbuf_new_from_data( (*Img)[0], + GDK_COLORSPACE_RGB, + LColourSpaceHasAlpha(Img->GetColourSpace()), + 8, + Img->X(), + Img->Y(), + Img->GetRowStep(), + [](auto pixels, auto obj) + { + auto This = (LClipBoard*)obj; + This->FreeImage(pixels); + }, + this); + if (!pb) + { + if (Img) { - /* - GdkPixbuf *pb = gdk_pixbuf_new_from_data ( const guchar *data, - GDK_COLORSPACE_RGB, - gboolean has_alpha, - int bits_per_sample, - int width, - int height, - int rowstride, - GdkPixbufDestroyNotify destroy_fn, - gpointer destroy_fn_data); - // gtk_clipboard_set_image(d->c, pb); - */ + LgiTrace("%s:%i - gdk_pixbuf_new_from_data failed for %s, params:\n" + "ptr: %p, alpha: %i, bits: %i, size: %ix%i, row: %i\n", + LColourSpaceToString(Img->GetColourSpace()), + (*Img)[0], LColourSpaceHasAlpha(Img->GetColourSpace()), + Img->GetBits(), Img->X(), Img->Y(), Img->GetRowStep()); } - } - return Status; + LAssert(!"gdk_pixbuf_new_from_data failed."); + return false; + } + + gtk_clipboard_set_image(d->c, pb); + return true; // have to assume it worked... } void ClipboardImageReceived(GtkClipboard *Clipboard, GdkPixbuf *Img, LClipBoard::BitmapCb *Cb) { auto chan = gdk_pixbuf_get_n_channels(Img); auto alpha = gdk_pixbuf_get_has_alpha(Img); LColourSpace cs = System32BitColourSpace; LAutoPtr Out; if (chan == 4) { cs = System32BitColourSpace; } else if (chan == 3) { if (alpha) cs = System32BitColourSpace; else cs = System24BitColourSpace; } else if (chan == 1) { cs = CsIndex8; } else { LString s; s.Printf("Unexpected colourspace: %i channels.", (int)chan); (*Cb)(Out, s); delete Cb; return; } auto x = gdk_pixbuf_get_width(Img), y = gdk_pixbuf_get_height(Img); LAutoPtr m(new LMemDC(x, y, cs)); if (!m) { (*Cb)(Out, "Alloc failed"); delete Cb; return; } auto px = gdk_pixbuf_get_pixels(Img); auto row = gdk_pixbuf_get_rowstride(Img); for (int yy=0; yyr = in->r; out->g = in->g; out->b = in->b; \ out++; in++; \ } \ break; \ } #define Rop32(out_cs, in_cs) \ case Cs##out_cs: \ { \ auto in = (L##in_cs*)(px + (yy*row)); \ auto out = (L##out_cs*) (*m)[yy]; \ auto end = out + x; \ while (out < end) \ { \ out->r = in->r; out->g = in->g; out->b = in->b; out->a = in->a; \ out++; in++; \ } \ break; \ } switch (m->GetColourSpace()) { Rop24(Bgr24, Rgb24); Rop24(Rgb24, Rgb24); Rop24(Bgrx32, Rgb24); Rop24(Rgbx32, Rgb24); Rop24(Xrgb32, Rgb24); Rop24(Xbgr32, Rgb24); Rop32(Bgra32, Rgba32); Rop32(Rgba32, Rgba32); Rop32(Argb32, Rgba32); Rop32(Abgr32, Rgba32); default: LAssert(!"Unsupported colour space."); yy = y; break; } } Out.Reset(m.Release()); (*Cb)(Out, NULL); delete Cb; } bool LClipBoard::Bitmap(LClipBoard::BitmapCb Callback) { if (!Callback) return false; gtk_clipboard_request_image(d->c, (GtkClipboardImageReceivedFunc) ClipboardImageReceived, new LClipBoard::BitmapCb(Callback)); return true; } LAutoPtr LClipBoard::Bitmap() { auto Ts = LCurrentTime(); LString Error; LAutoPtr Img; LClipBoard::BitmapCb Callback = [&](auto img, auto err) { Img = img; Error = err; }; gtk_clipboard_request_image(d->c, (GtkClipboardImageReceivedFunc) ClipboardImageReceived, new LClipBoard::BitmapCb(Callback)); while (!Error && !Img && (LCurrentTime() - Ts) < LGI_RECEIVE_CLIPBOARD_TIMEOUT) LYield(); return Img; } void LgiClipboardGetFunc(GtkClipboard *clipboard, GtkSelectionData *data, guint info, gpointer user_data) { if (Data.Lock(_FL)) { ::LVariant *p = (::LVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardGetFunc: %p, %i\n", _FL, p, info); #endif switch (info) { case GV_BINARY: { if (p->Type == info) { // data->data = p->Value.Binary.Data; // data->length = p->Value.Binary.Length; gtk_selection_data_set(data, gtk_selection_data_get_target(data), 8, (guchar*)p->Value.Binary.Data, p->Value.Binary.Length); } else LgiTrace("%s:%i - Variant is the wrong type: %i\n", _FL, p->Type); break; } default: { LgiTrace("%s:%i - Undefined data type: %i\n", _FL, info); break; } } Data.Unlock(); } } void LgiClipboardClearFunc(GtkClipboard *clipboard, gpointer user_data) { if (Data.Lock(_FL)) { ::LVariant *p = (::LVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardClearFunc: %i\n", _FL, p->Type); #endif p->Empty(); Data.Unlock(); } } bool LClipBoard::Binary(FormatType Format, uchar *Ptr, ssize_t Len, bool AutoEmpty) { if (!Ptr || Len <= 0) return false; ::LVariant *p = NULL; if (Data.Lock(_FL)) { for (int i=0; iSetBinary(Len, Ptr); break; } } Data.Unlock(); } if (!p) { #if DEBUG_CLIPBOARD printf("%s:%i - no slots to store data\n", _FL); #endif return false; } GtkTargetEntry te; te.target = (char*)LGI_CLIP_BINARY; te.flags = 0; // GTK_TARGET_SAME_APP? te.info = GV_BINARY; // App defined data type ID Gtk::gboolean r = gtk_clipboard_set_with_data(d->c, &te, 1, LgiClipboardGetFunc, LgiClipboardClearFunc, p); #if DEBUG_CLIPBOARD printf("%s:%i - gtk_clipboard_set_with_data = %i\n", _FL, r); #endif return r; } ::LString::Array LClipBoard::Files() { ::LString::Array a; return a; } bool LClipBoard::Files(::LString::Array &a, bool AutoEmpty) { return false; } struct ReceiveData { LAutoPtr *Ptr; ssize_t *Len; }; void LgiClipboardReceivedFunc(GtkClipboard *clipboard, GtkSelectionData *data, gpointer user_data) { ReceiveData *r = (ReceiveData*) user_data; if (!data || !r) { LgiTrace("%s:%i - Missing ptr: %p %p\n", _FL, data, r); return; } auto Bytes = gtk_selection_data_get_length(data); if (Bytes < 0) { LgiTrace("%s:%i - No data? (%i)\n", _FL, Bytes); return; } uint8_t *d = new uint8_t[Bytes]; if (!d) { LgiTrace("%s:%i - Alloc failed %i\n", _FL, Bytes); return; } memcpy(d, gtk_selection_data_get_data(data), Bytes); if (r->Len) *r->Len = Bytes; r->Ptr->Reset(d); #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardReceivedFunc\n", _FL); #endif } bool LClipBoard::Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len) { ReceiveData r = {&Ptr, Len}; gtk_clipboard_request_contents( d->c, gdk_atom_intern(LGI_CLIP_BINARY, false), LgiClipboardReceivedFunc, &r); uint64 Start = LCurrentTime(); do { if (r.Ptr->Get()) break; LYield(); LSleep(1); } while (LCurrentTime() - Start > LGI_RECEIVE_CLIPBOARD_TIMEOUT); #if DEBUG_CLIPBOARD printf("%s:%i - LClipBoard::Binary %p, %i\n", _FL, r.Ptr->Get(), Len ? *Len : -1); #endif return r.Ptr->Get() != NULL; } diff --git a/src/linux/Lgi/Window.cpp b/src/linux/Lgi/Window.cpp --- a/src/linux/Lgi/Window.cpp +++ b/src/linux/Lgi/Window.cpp @@ -1,1854 +1,1854 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Popup.h" #include "lgi/common/Panel.h" #include "lgi/common/Notifications.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" using namespace Gtk; #undef Status #include "LgiWidget.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 extern Gtk::GdkDragAction EffectToDragAction(int Effect); /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate { public: int Sx, Sy; bool Dynamic; LKey LastKey; ::LArray Hooks; bool SnapToEdge; ::LString Icon; LRect Decor; gulong DestroySig; LAutoPtr IconImg; LAttachState AttachState; // State GdkWindowState State; bool HadCreateEvent; // Focus stuff OsView FirstFocus; LViewI *Focus; bool Active; LWindowPrivate() { AttachState = LUnattached; DestroySig = 0; Decor.ZOff(-1, -1); FirstFocus = NULL; Focus = NULL; Active = false; State = (Gtk::GdkWindowState)0; HadCreateEvent = false; Sx = Sy = 0; Dynamic = true; SnapToEdge = false; LastKey.vkey = 0; LastKey.c16 = 0; LastKey.Data = 0; LastKey.IsChar = 0; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////// #define GWND_CREATE 0x0010000 LWindow::LWindow(GtkWidget *w) : LView(0) { d = new LWindowPrivate; _QuitOnClose = false; Menu = NULL; Wnd = GTK_WINDOW(w); if (Wnd) g_object_set_data(G_OBJECT(Wnd), "LViewI", (LViewI*)this); _Root = NULL; _MenuBar = NULL; _VBox = NULL; _Default = 0; _Window = this; WndFlags |= GWND_CREATE; ClearFlag(WndFlags, GWF_VISIBLE); _Lock = new ::LMutex("LWindow"); } LWindow::~LWindow() { d->AttachState = LDetaching; if (Wnd && d->DestroySig > 0) { // As we are already in the destructor, we don't want // GtkWindowDestroy to try and delete the object again. g_signal_handler_disconnect(Wnd, d->DestroySig); } if (LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (_Root) { lgi_widget_detach(_Root); _Root = NULL; } if (Wnd) { gtk_widget_destroy(GTK_WIDGET(Wnd)); Wnd = NULL; } d->AttachState = LUnattached; DeleteObj(Menu); DeleteObj(d); DeleteObj(_Lock); } int LWindow::WaitThread() { return 0; // Nop for linux } bool LWindow::SetIcon(const char *FileName) { LString a; if (Wnd) { if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } if (!LFileExists(FileName)) { LgiTrace("%s:%i - SetIcon failed to find '%s'\n", _FL, FileName); return false; } else { #if defined(LINUX) LAppInst->SetApplicationIcon(FileName); #endif #if _MSC_VER GError *error = NULL; if (gtk_window_set_icon_from_file(Wnd, FileName, &error)) return true; #else // On windows this is giving a red for blue channel swap error... if (d->IconImg.Reset(GdcD->Load(a))) gtk_window_set_icon(Wnd, d->IconImg->CreatePixBuf()); #endif } } if (FileName != d->Icon.Get()) d->Icon = FileName; return d->Icon != NULL; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { return d->Active; } bool LWindow::SetActive() { if (!Wnd) return false; gtk_window_present(Wnd); return true; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool i) { ThreadCheck(); auto w = GTK_WIDGET(Wnd); if (i) gtk_widget_show(w); else gtk_widget_hide(w); } bool LWindow::Obscured() { return d->State == GDK_WINDOW_STATE_WITHDRAWN || d->State == GDK_WINDOW_STATE_ICONIFIED; } void LWindow::_SetDynamic(bool i) { d->Dynamic = i; } void LWindow::_OnViewDelete() { if (d->Dynamic) { delete this; } } void LWindow::OnGtkRealize() { d->AttachState = LAttached; LView::OnGtkRealize(); } void LWindow::OnGtkDelete() { // Delete everything we own... // DeleteObj(Menu); #if 0 while (Children.Length()) { LViewI *c = Children.First(); c->Detach(); } #else for (unsigned i=0; iGetGView(); if (v) v->OnGtkDelete(); } #endif // These will be destroyed by GTK after returning from LWindowCallback Wnd = NULL; #ifndef __GTK_H__ _View = NULL; #endif } LRect *LWindow::GetDecorSize() { return d->Decor.x2 >= 0 ? &d->Decor : NULL; } void LWindow::SetDecor(bool Visible) { if (Wnd) gtk_window_set_decorated (Wnd, Visible); else LgiTrace("%s:%i - No window to set decor.\n", _FL); } LViewI *LWindow::WindowFromPoint(int x, int y, bool Debug) { if (!_Root) return NULL; auto rpos = GtkGetPos(_Root).ZeroTranslate(); if (!rpos.Overlap(x, y)) return NULL; return LView::WindowFromPoint(x - rpos.x1, y - rpos.y1, Debug); } bool LWindow::TranslateMouse(LMouse &m) { m.Target = WindowFromPoint(m.x, m.y, false); if (!m.Target) return false; LViewI *w = this; for (auto p = m.Target; p; p = p->GetParent()) { if (p == w) { auto ppos = GtkGetPos(GTK_WIDGET(WindowHandle())); m.x -= ppos.x1; m.y -= ppos.y1; break; } else { auto pos = p->GetPos(); m.x -= pos.x1; m.y -= pos.y1; } } return true; } gboolean LWindow::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { if (!event) { printf("%s:%i - No event.\n", _FL); return FALSE; } #if 0 if (event->type != 28) LgiTrace("%s::OnGtkEvent(%i) name=%s\n", GetClass(), event->type, Name()); #endif switch (event->type) { case GDK_DELETE: { bool Close = OnRequestClose(false); if (Close) OnGtkDelete(); return !Close; } case GDK_DESTROY: { delete this; return true; } case GDK_KEY_PRESS: case GDK_KEY_RELEASE: { auto ModFlags = LAppInst->GetKeyModFlags(); auto e = &event->key; #define KEY(name) GDK_KEY_##name LKey k; k.Down(e->type == GDK_KEY_PRESS); k.c16 = k.vkey = e->keyval; k.Shift((e->state & ModFlags->Shift) != 0); k.Ctrl((e->state & ModFlags->Ctrl) != 0); k.Alt((e->state & ModFlags->Alt) != 0); k.System((e->state & ModFlags->System) != 0); #if 0//def _DEBUG if (k.vkey == GDK_KEY_Meta_L || k.vkey == GDK_KEY_Meta_R) break; #endif k.IsChar = !k.Ctrl() && !k.Alt() && !k.System() && (k.c16 >= ' ') && (k.c16 >> 8 != 0xff); if (e->keyval > 0xff && e->string != NULL) { // Convert string to unicode char auto *i = e->string; ptrdiff_t len = strlen(i); k.c16 = LgiUtf8To32((uint8_t *&) i, len); } switch (k.vkey) { case GDK_KEY_ISO_Left_Tab: case KEY(Tab): k.IsChar = true; k.c16 = k.vkey = LK_TAB; break; case KEY(Return): case KEY(KP_Enter): k.IsChar = true; k.c16 = k.vkey = LK_RETURN; break; case GDK_KEY_BackSpace: k.c16 = k.vkey = LK_BACKSPACE; k.IsChar = !k.Ctrl() && !k.Alt() && !k.System(); break; case KEY(Left): k.vkey = k.c16 = LK_LEFT; break; case KEY(Right): k.vkey = k.c16 = LK_RIGHT; break; case KEY(Up): k.vkey = k.c16 = LK_UP; break; case KEY(Down): k.vkey = k.c16 = LK_DOWN; break; case KEY(Page_Up): k.vkey = k.c16 = LK_PAGEUP; break; case KEY(Page_Down): k.vkey = k.c16 = LK_PAGEDOWN; break; case KEY(Home): k.vkey = k.c16 = LK_HOME; break; case KEY(End): k.vkey = k.c16 = LK_END; break; case KEY(Delete): k.vkey = k.c16 = LK_DELETE; break; #define KeyPadMap(gdksym, ch, is) \ case gdksym: k.c16 = ch; k.IsChar = is; break; KeyPadMap(KEY(KP_0), '0', true) KeyPadMap(KEY(KP_1), '1', true) KeyPadMap(KEY(KP_2), '2', true) KeyPadMap(KEY(KP_3), '3', true) KeyPadMap(KEY(KP_4), '4', true) KeyPadMap(KEY(KP_5), '5', true) KeyPadMap(KEY(KP_6), '6', true) KeyPadMap(KEY(KP_7), '7', true) KeyPadMap(KEY(KP_8), '8', true) KeyPadMap(KEY(KP_9), '9', true) KeyPadMap(KEY(KP_Space), ' ', true) KeyPadMap(KEY(KP_Tab), '\t', true) KeyPadMap(KEY(KP_F1), LK_F1, false) KeyPadMap(KEY(KP_F2), LK_F2, false) KeyPadMap(KEY(KP_F3), LK_F3, false) KeyPadMap(KEY(KP_F4), LK_F4, false) KeyPadMap(KEY(KP_Home), LK_HOME, false) KeyPadMap(KEY(KP_Left), LK_LEFT, false) KeyPadMap(KEY(KP_Up), LK_UP, false) KeyPadMap(KEY(KP_Right), LK_RIGHT, false) KeyPadMap(KEY(KP_Down), LK_DOWN, false) KeyPadMap(KEY(KP_Page_Up), LK_PAGEUP, false) KeyPadMap(KEY(KP_Page_Down), LK_PAGEDOWN, false) KeyPadMap(KEY(KP_End), LK_END, false) KeyPadMap(KEY(KP_Begin), LK_HOME, false) KeyPadMap(KEY(KP_Insert), LK_INSERT, false) KeyPadMap(KEY(KP_Delete), LK_DELETE, false) KeyPadMap(KEY(KP_Equal), '=', true) KeyPadMap(KEY(KP_Multiply), '*', true) KeyPadMap(KEY(KP_Add), '+', true) KeyPadMap(KEY(KP_Separator), '|', true) // is this right? KeyPadMap(KEY(KP_Subtract), '-', true) KeyPadMap(KEY(KP_Decimal), '.', true) KeyPadMap(KEY(KP_Divide), '/', true) } if (ModFlags->Debug) { :: LString Msg; Msg.Printf("e->state=%x %s", e->state, ModFlags->FlagsToString(e->state).Get()); k.Trace(Msg); } auto v = d->Focus ? d->Focus : this; if (!HandleViewKey(v->GetGView(), k)) { if (!k.Down()) return false; if (k.vkey == LK_TAB || k.vkey == KEY(ISO_Left_Tab)) { // Do tab between controls ::LArray a; BuildTabStops(this, a); int idx = a.IndexOf((LViewI*)v); if (idx >= 0) { idx += k.Shift() ? -1 : 1; int next_idx = idx == 0 ? a.Length() -1 : idx % a.Length(); LViewI *next = a[next_idx]; if (next) { // LgiTrace("Setting focus to %i of %i: %s, %s, %i\n", next_idx, a.Length(), next->GetClass(), next->GetPos().GetStr(), next->GetId()); next->Focus(true); } } } else if (k.System()) { if (ToLower(k.c16) == 'q') { auto AppWnd = LAppInst->AppWnd; auto Wnd = AppWnd ? AppWnd : this; if (Wnd->OnRequestClose(false)) { Wnd->Quit(); return true; } } } else return false; } break; } case GDK_CONFIGURE: { GdkEventConfigure *c = &event->configure; Pos.Set(c->x, c->y, c->x+c->width-1, c->y+c->height-1); // printf("%s::GDK_CONFIGURE %s\n", GetClass(), Pos.GetStr()); OnPosChange(); return FALSE; break; } case GDK_FOCUS_CHANGE: { d->Active = event->focus_change.in; #if 0 printf("%s/%s::GDK_FOCUS_CHANGE(%i)\n", GetClass(), Name(), event->focus_change.in); #endif break; } case GDK_WINDOW_STATE: { d->State = event->window_state.new_window_state; break; } case GDK_PROPERTY_NOTIFY: { gchar *Name = gdk_atom_name (event->property.atom); if (!Name) break; if (!_stricmp(Name, "_NET_FRAME_EXTENTS")) { // printf("PropChange: %i - %s\n", event->property.atom, Name); unsigned long *extents = NULL; if (gdk_property_get(event->property.window, gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE), gdk_atom_intern ("CARDINAL", FALSE), 0, sizeof (unsigned long) * 4, FALSE, NULL, NULL, NULL, (guchar **)&extents)) { d->Decor.Set(extents[0], extents[2], extents[1], extents[3]); g_free(extents); } else printf("%s:%i - Error: gdk_property_get failed.\n", _FL); } g_free(Name); break; } case GDK_UNMAP: { // LgiTrace("%s:%i - Unmap %s\n", _FL, GetClass()); break; } case GDK_VISIBILITY_NOTIFY: { // LgiTrace("%s:%i - Visible %s\n", _FL, GetClass()); break; } case GDK_DRAG_ENTER: { LgiTrace("%s:%i - GDK_DRAG_ENTER\n", _FL); break; } case GDK_DRAG_LEAVE: { LgiTrace("%s:%i - GDK_DRAG_LEAVE\n", _FL); break; } case GDK_DRAG_MOTION: { LgiTrace("%s:%i - GDK_DRAG_MOTION\n", _FL); break; } case GDK_DRAG_STATUS: { LgiTrace("%s:%i - GDK_DRAG_STATUS\n", _FL); break; } case GDK_DROP_START: { LgiTrace("%s:%i - GDK_DROP_START\n", _FL); break; } case GDK_DROP_FINISHED: { LgiTrace("%s:%i - GDK_DROP_FINISHED\n", _FL); break; } default: { printf("%s:%i - Unknown event %i\n", _FL, event->type); return false; } } return true; } static gboolean GtkWindowDestroy(GtkWidget *widget, LWindow *This) { delete This; return true; } static void GtkWindowRealize(GtkWidget *widget, LWindow *This) { #if 0 LgiTrace("GtkWindowRealize, This=%p(%s\"%s\")\n", This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif This->OnGtkRealize(); } static void GtkRootResize(GtkWidget *widget, GdkRectangle *alloc, LView *This) { LWindow *w = This->GetWindow(); if (w) w->PourAll(); } void LWindowUnrealize(GtkWidget *widget, LWindow *wnd) { // printf("%s:%i - LWindowUnrealize %s\n", _FL, wnd->GetClass()); } bool DndPointMap(LViewI *&v, LPoint &p, LDragDropTarget *&t, LWindow *Wnd, int x, int y) { LRect cli = Wnd->GetClient(); t = NULL; v = Wnd->WindowFromPoint(x - cli.x1, y - cli.y1, false); if (!v) { LgiTrace("%s:%i - @ %i,%i\n", _FL, x, y); return false; } v->WindowVirtualOffset(&p); p.x = x - p.x; p.y = y - p.y; for (LViewI *view = v; !t && view; view = view->GetParent()) t = view->DropTarget(); if (t) return true; LgiTrace("%s:%i - No target for %s\n", _FL, v->GetClass()); return false; } void LWindowDragBegin(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataDelete(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataGet(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return; for (auto &d: t->Data) { auto type = gdk_atom_name(gtk_selection_data_get_data_type(data)); if (d.Format.Equals(type)) { gint length = 0; auto ptr = gtk_selection_data_get_data_with_length(data, &length); if (ptr) { d.Data[0].SetBinary(length, (void*)ptr, false); } break; } } } int GetAcceptFmts(::LString::Array &Formats, GdkDragContext *context, LDragDropTarget *t, LPoint &p) { int KeyState = 0; LDragFormats Fmts(true); int Flags = DROPEFFECT_NONE; GList *targets = gdk_drag_context_list_targets(context); Gtk::GList *i = targets; while (i) { auto a = gdk_atom_name((GdkAtom)i->data); if (a) Fmts.Supports(a); i = i->next; } Fmts.SetSource(false); Flags = t->WillAccept(Fmts, p, KeyState); auto Sup = Fmts.GetSupported(); for (auto &s: Sup) Formats.New() = s; return Flags; } gboolean LWindowDragDataDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { // Map the point to a view... LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; t->Data.Length(0); // Request the data... ::LArray Data; ::LString::Array Formats; int KeyState = 0; int Flags = GetAcceptFmts(Formats, context, t, p); for (auto f: Formats) { t->Data.New().Format = f; gtk_drag_get_data(widget, context, gdk_atom_intern(f, true), time); } // Wait for the data to arrive... uint64_t Start = LCurrentTime(); while (LCurrentTime()-Start < 2000) { int HasData = 0; for (auto d: t->Data) if (d.Data.Length() > 0) HasData++; if (HasData >= Formats.Length()) break; LYield(); } auto Result = t->OnDrop(t->Data, p, KeyState); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Result != DROPEFFECT_NONE; } void LWindowDragEnd(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); return false; } void LWindowDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; ::LString::Array Formats; int Flags = GetAcceptFmts(Formats, context, t, p); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Flags != DROPEFFECT_NONE; } bool LWindow::Attach(LViewI *p) { bool Status = false; ThreadCheck(); // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Create a window if needed.. if (!Wnd) Wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (Wnd) { auto Widget = GTK_WIDGET(Wnd); LView *i = this; if (Pos.X() > 0 && Pos.Y() > 0) gtk_window_resize(Wnd, Pos.X(), Pos.Y()); gtk_window_move(Wnd, Pos.x1, Pos.y1); auto Obj = G_OBJECT(Wnd); g_object_set_data(Obj, "LViewI", (LViewI*)this); d->DestroySig = g_signal_connect(Obj, "destroy", G_CALLBACK(GtkWindowDestroy), this); g_signal_connect(Obj, "delete_event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-in-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-out-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "window-state-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "property-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "configure-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "unmap-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "visibility-notify-event",G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "realize", G_CALLBACK(GtkWindowRealize), i); g_signal_connect(Obj, "unrealize", G_CALLBACK(LWindowUnrealize), i); g_signal_connect(Obj, "drag-begin", G_CALLBACK(LWindowDragBegin), i); g_signal_connect(Obj, "drag-data-delete", G_CALLBACK(LWindowDragDataDelete), i); g_signal_connect(Obj, "drag-data-get", G_CALLBACK(LWindowDragDataGet), i); g_signal_connect(Obj, "drag-data-received", G_CALLBACK(LWindowDragDataReceived), i); g_signal_connect(Obj, "drag-drop", G_CALLBACK(LWindowDragDataDrop), i); g_signal_connect(Obj, "drag-end", G_CALLBACK(LWindowDragEnd), i); g_signal_connect(Obj, "drag-failed", G_CALLBACK(LWindowDragFailed), i); g_signal_connect(Obj, "drag-leave", G_CALLBACK(LWindowDragLeave), i); g_signal_connect(Obj, "drag-motion", G_CALLBACK(LWindowDragMotion), i); #if 0 g_signal_connect(Obj, "button-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "button-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "motion-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "scroll-event", G_CALLBACK(GtkViewCallback), i); #endif gtk_widget_add_events( Widget, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK); gtk_window_set_title(Wnd, LBase::Name()); d->AttachState = LAttaching; // g_action_map_add_action_entries (G_ACTION_MAP(Wnd), app_entries, G_N_ELEMENTS (app_entries), Wnd); if ((_Root = lgi_widget_new(this, true))) { g_signal_connect(_Root, "size-allocate", G_CALLBACK(GtkRootResize), i); GtkContainer *AttachPoint = NULL; if (GTK_IS_DIALOG(Wnd)) { auto content = gtk_dialog_get_content_area(GTK_DIALOG(Wnd)); if (!content) { LAssert(!"No content area"); return false; } AttachPoint = GTK_CONTAINER(content); } else { AttachPoint = GTK_CONTAINER(Wnd); } LAssert(AttachPoint != NULL); gtk_container_add(AttachPoint, _Root); // Check it actually worked... (would a return value kill you GTK? no it would not) auto p = gtk_widget_get_parent(_Root); if (!p) { LAssert(!"Add failed"); return false; } gtk_widget_show(_Root); } // This call sets up the GdkWindow handle gtk_widget_realize(Widget); // Do a rough layout of child windows PourAll(); // Add icon if (d->Icon) { SetIcon(d->Icon); d->Icon.Empty(); } Status = true; } return Status; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return LView::OnRequestClose(OsShuttingDown); } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { if (m.Down() && !m.IsMove()) { bool InPopup = false; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) { InPopup = true; break; } } if (!InPopup && LPopup::CurrentPopups.Length()) { for (int i=0; iVisible()) p->Visible(false); } } } for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { if (!d->Hooks[i].Target->OnViewMouse(v, m)) { return false; } } } return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { bool Status = false; LViewI *Ctrl = 0; #if DEBUG_HANDLEVIEWKEY bool Debug = 1; // k.vkey == LK_RETURN; char SafePrint = k.c16 < ' ' ? ' ' : k.c16; // if (Debug) { LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s (d->Focus=%s/%p)\n", v->GetClass(), v, k.c16, k.IsChar, (char*)(k.Down()?" Down":" Up"), (char*)(k.Shift()?" Shift":""), (char*)(k.Alt()?" Alt":""), (char*)(k.Ctrl()?" Ctrl":""), d->Focus?d->Focus->GetClass():0, d->Focus); } #endif // Any window in a popup always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tSending key to popup\n"); #endif return v->OnKey(k); } } // Give key to popups if (LAppInst && LAppInst->GetMouseHook() && LAppInst->GetMouseHook()->OnViewKey(v, k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMouseHook got key\n"); #endif goto AllDone; } // Allow any hooks to see the key... for (int i=0; iHooks.Length(); i++) { #if DEBUG_HANDLEVIEWKEY // if (Debug) LgiTrace("\tHook[%i]\n", i); #endif if (d->Hooks[i].Flags & LKeyEvents) { LView *Target = d->Hooks[i].Target; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i].Target=%p %s\n", i, Target, Target->GetClass()); #endif if (Target->OnViewKey(v, k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i] ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", i, SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } } } // Give the key to the window... if (v->OnKey(k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tView ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif Status = true; goto AllDone; } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\t%s didn't eat '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", v->GetClass(), SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif // Window didn't want the key... switch (k.vkey) { case LK_RETURN: #ifdef LK_KEYPADENTER case LK_KEYPADENTER: #endif { Ctrl = _Default; break; } case LK_ESCAPE: { Ctrl = FindControl(IDCANCEL); break; } } // printf("Ctrl=%p\n", Ctrl); if (Ctrl) { if (Ctrl->Enabled()) { if (Ctrl->OnKey(k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tDefault Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } // else printf("OnKey()=false\n"); } // else printf("Ctrl=disabled\n"); } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\tNo default ctrl to handle key.\n"); #endif if (Menu) { Status = Menu->OnKey(v, k); if (Status) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMenu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif } } // Tab through controls if (k.vkey == LK_TAB && k.Down() && !k.IsChar) { LViewI *Wnd = GetNextTabStop(v, k.Shift()); #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tTab moving focus shift=%i Wnd=%p\n", k.Shift(), Wnd); #endif if (Wnd) Wnd->Focus(true); } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } AllDone: if (d) d->LastKey = k; return Status; } void LWindow::Raise() { if (Wnd) gtk_window_present(Wnd); } LWindowZoom LWindow::GetZoom() { switch (d->State) { case GDK_WINDOW_STATE_ICONIFIED: return LZoomMin; case GDK_WINDOW_STATE_MAXIMIZED: return LZoomMax; default: break; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (!Wnd) { // LgiTrace("%s:%i - No window.\n", _FL); return; } ThreadCheck(); switch (i) { case LZoomMin: { gtk_window_iconify(Wnd); break; } case LZoomNormal: { gtk_window_deiconify(Wnd); gtk_window_unmaximize(Wnd); break; } case LZoomMax: { gtk_window_maximize(Wnd); break; } default: { LgiTrace("%s:%i - Error: unsupported zoom.\n", _FL); break; } } } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { if (v && v->GetWindow() == this) { if (_Default != v) { LViewI *Old = _Default; _Default = v; if (Old) Old->Invalidate(); if (_Default) _Default->Invalidate(); } } else { _Default = 0; } } bool LWindow::Name(const char *n) { if (Wnd) { ThreadCheck(); gtk_window_set_title(Wnd, n); } return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } struct CallbackParams { LRect Menu; int Depth; CallbackParams() { Menu.ZOff(-1, -1); Depth = 0; } }; void ClientCallback(GtkWidget *w, CallbackParams *p) { const char *Name = gtk_widget_get_name(w); if (Name && !_stricmp(Name, "GtkMenuBar")) { GtkAllocation alloc = {0}; gtk_widget_get_allocation(w, &alloc); p->Menu.ZOff(alloc.width-1, alloc.height-1); // LgiTrace("GtkMenuBar = %s\n", p->Menu.GetStr()); } if (!p->Menu.Valid()) { p->Depth++; if (GTK_IS_CONTAINER(w)) gtk_container_forall(GTK_CONTAINER(w), (GtkCallback)ClientCallback, p); p->Depth--; } } LPoint LWindow::GetDpi() { - return LPoint(96, 96); + return LScreenDpi(); } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); return LPointF((double)Dpi.x/96.0, (double)Dpi.y/96.0); } LRect &LWindow::GetClient(bool ClientSpace) { static LRect r; r = LView::GetClient(ClientSpace); if (Wnd) { CallbackParams p; gtk_container_forall(GTK_CONTAINER(Wnd), (GtkCallback)ClientCallback, &p); if (p.Menu.Valid()) { if (ClientSpace) r.y2 -= p.Menu.Y(); else r.y1 += p.Menu.Y(); } } return r; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; if (Load) { ::LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; // printf("SerializeState load %s\n", v.Str()); LToken t(v.Str(), ";"); for (int i=0; iName()); v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, "%s>%s", Buf, v->GetClass()); } return LAutoString(NewStr(s)); } #endif void LWindow::SetFocus(LViewI *ctrl, FocusType type) { #if DEBUG_SETFOCUS const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } #endif switch (type) { case GainFocus: { if (d->Focus == ctrl) { #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LgiTrace("SetFocus(%s, %s) already has focus.\n", _ctrl.Get(), TypeName); #endif return; } if (d->Focus) { LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus LView: %s\n", _foc.Get()); #endif gv->_Focus(false); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus view: %s (active=%i)\n", _foc.Get(), IsActive()); #endif d->Focus->OnFocus(false); d->Focus->Invalidate(); } } d->Focus = ctrl; if (d->Focus) { #if DEBUG_SETFOCUS static int Count = 0; #endif LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing LView\n", _set.Get(), TypeName, Count++); #endif gv->_Focus(true); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing nonGView (active=%i)\n", _set.Get(), TypeName, Count++, IsActive()); #endif d->Focus->OnFocus(true); d->Focus->Invalidate(); } } break; } case LoseFocus: { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LAutoString _Focus = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) d->Focus=%s\n", _Ctrl.Get(), TypeName, _Focus.Get()); #endif if (ctrl == d->Focus) { d->Focus = NULL; } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) on delete\n", _Ctrl.Get(), TypeName); #endif d->Focus = NULL; } break; } } } void LWindow::SetDragHandlers(bool On) { } bool LWindow::IsAttached() { return d->AttachState == LAttaching || d->AttachState == LAttached; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m.x, m.y); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } void LWindow::Quit(bool DontDelete) { ThreadCheck(); if (Wnd) { d->AttachState = LDetaching; auto wnd = Wnd; Wnd = NULL; gtk_widget_destroy(GTK_WIDGET(wnd)); } } void LWindow::SetAlwaysOnTop(bool b) { } diff --git a/src/win/Lgi/FileSelect.cpp b/src/win/Lgi/FileSelect.cpp --- a/src/win/Lgi/FileSelect.cpp +++ b/src/win/Lgi/FileSelect.cpp @@ -1,396 +1,381 @@ /*hdr ** FILE: LFileSelect.cpp ** AUTHOR: Matthew Allen ** DATE: 26/9/2001 ** DESCRIPTION: Common file/directory selection dialog ** ** Copyright (C) 1998-2001, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/FileSelect.h" class LFileSelectPrivate { friend class LFileSelect; bool CanMultiSelect; bool MultiSelected; LString InitDir; LString DefExt; LString TitleStr; bool WaitForMessage; int SelectedType; LViewI *ParentWnd; List TypeList; LString::Array Files; bool ShowReadOnly; bool ReadOnly; - char *TypeStrA() - { - LStringPipe p; - - for (auto Type: TypeList) - { - p.Push(Type->Description()); - p.Push("", 1); - p.Push(Type->Extension()); - p.Push("", 1); - } - - return (char*)p.New(1); - } - char16 *TypeStrW() { LMemQueue p; for (auto Type: TypeList) { char16 *d = Utf8ToWide(Type->Description()); char16 *e = Utf8ToWide(Type->Extension()); p.Write((uchar*)d, sizeof(char16)*StrlenW(d)); p.Write((uchar*)L"", sizeof(char16)); p.Write((uchar*)e, sizeof(char16)*StrlenW(e)); p.Write((uchar*)L"", sizeof(char16)); DeleteArray(d); DeleteArray(e); } return (char16*)p.New(sizeof(char16)); } bool DoFallback(OPENFILENAMEA &Info) { DWORD err = CommDlgExtendedError(); if (err == FNERR_INVALIDFILENAME) { // Something about the filename is making the dialog unhappy... // So nuke it and go without. Info.lpstrFile[0] = 0; return true; } return false; } bool DoFallback(OPENFILENAMEW &Info) { DWORD err = CommDlgExtendedError(); if (err == FNERR_INVALIDFILENAME) { // Something about the filename is making the dialog unhappy... // So nuke it and go without. Info.lpstrFile[0] = 0; return true; } return false; } void BeforeDlg(OPENFILENAMEW &Info) { ZeroObj(Info); Info.lStructSize = sizeof(Info); Info.lpstrFilter = TypeStrW(); Info.nMaxFile = 4096; Info.lpstrFile = new char16[Info.nMaxFile]; Info.hwndOwner = ParentWnd ? ParentWnd->Handle() : 0; if (Info.lpstrFile) { memset(Info.lpstrFile, 0, sizeof(*Info.lpstrFile) * Info.nMaxFile); if (Files.Length()) { LAutoWString s(Utf8ToWide(Files[0])); if (s) StrcpyW(Info.lpstrFile, s); } } Info.lpstrInitialDir = Utf8ToWide(InitDir); if (CanMultiSelect) Info.Flags |= OFN_ALLOWMULTISELECT; else Info.Flags &= ~OFN_ALLOWMULTISELECT; if (ShowReadOnly) Info.Flags &= ~OFN_HIDEREADONLY; else Info.Flags |= OFN_HIDEREADONLY; Info.Flags |= OFN_EXPLORER | OFN_ENABLESIZING | OFN_NOCHANGEDIR; } void AfterDlg(OPENFILENAMEW &Info, bool Status) { Files.Empty(); if (Status) { MultiSelected = StrlenW(Info.lpstrFile) < Info.nFileOffset; if (MultiSelected) { char16 s[256]; StrcpyW(s, Info.lpstrFile); char16 *e = s + StrlenW(s); if (*e != DIR_CHAR) *e++ = DIR_CHAR; char16 *f = Info.lpstrFile + Info.nFileOffset; while (*f) { StrcpyW(e, f); Files.New() = s; f += StrlenW(f) + 1; } } else { Files.New() = Info.lpstrFile; } ReadOnly = TestFlag(Info.Flags, OFN_READONLY); } else { DWORD err = CommDlgExtendedError(); LgiTrace("%s:%i - FileNameDlg error 0x%x\n", _FL, err); } DeleteArray(Info.lpstrFile); DeleteArray((char16*&)Info.lpstrInitialDir); DeleteArray((char16*&)Info.lpstrFilter); SelectedType = Info.nFilterIndex - 1; } LFileSelectPrivate() { CanMultiSelect = false; MultiSelected = false; InitDir = 0; DefExt = 0; TitleStr = 0; SelectedType = 0; ParentWnd = 0; ReadOnly = false; ShowReadOnly = false; } ~LFileSelectPrivate() { } }; /////////////////////////////////////////////////////////////////////////////////////////////////// LFileSelect::LFileSelect(LViewI *Window) { d = new LFileSelectPrivate; if (Window) Parent(Window); } LFileSelect::~LFileSelect() { ClearTypes(); DeleteObj(d); } const char *LFileSelect::Name() { return d->Files.Length() ? d->Files[0] : NULL; } bool LFileSelect::Name(const char *n) { d->Files.Empty(); return d->Files.New().Set(n); } const char *LFileSelect::operator [](size_t i) { return d->Files.IdxCheck(i) ? d->Files[i] : NULL; } size_t LFileSelect::Length() { return d->Files.Length(); } size_t LFileSelect::Types() { return d->TypeList.Length(); } ssize_t LFileSelect::SelectedType() { return d->SelectedType; } LFileType *LFileSelect::TypeAt(ssize_t n) { return d->TypeList.ItemAt(n); } void LFileSelect::ClearTypes() { d->TypeList.DeleteObjects(); } bool LFileSelect::Type(const char *Description, const char *Extension, int Data) { LFileType *Type = new LFileType; if (Type) { Type->Description(Description); Type->Extension(Extension); Type->Data(Data); d->TypeList.Insert(Type); } return Type != 0; } LViewI *LFileSelect::Parent() { return d->ParentWnd; } void LFileSelect::Parent(LViewI *Window) { d->ParentWnd = Window; } bool LFileSelect::ReadOnly() { return d->ReadOnly; } void LFileSelect::ShowReadOnly(bool ro) { d->ShowReadOnly = ro; } bool LFileSelect::MultiSelect() { return d->CanMultiSelect; } void LFileSelect::MultiSelect(bool Multi) { d->CanMultiSelect = Multi; } const char *LFileSelect::InitialDir() { return d->InitDir; } void LFileSelect::InitialDir(const char *InitDir) { d->InitDir = InitDir; } const char *LFileSelect::Title() { return d->TitleStr; } void LFileSelect::Title(const char *Title) { d->TitleStr = Title; } const char *LFileSelect::DefaultExtension() { return d->DefExt; } void LFileSelect::DefaultExtension(const char *DefExt) { d->DefExt = DefExt; } void LFileSelect::Open(SelectCb Cb) { bool Status = FALSE; OPENFILENAMEW Info; d->BeforeDlg(Info); Status = GetOpenFileNameW(&Info) != 0; if (!Status && d->DoFallback(Info)) Status = GetOpenFileNameW(&Info) != 0; d->AfterDlg(Info, Status); if (Cb) Cb(this, Status && Length() > 0); } #include "shlobj.h" int CALLBACK GFileSelectBrowseCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { char *Name = (char*)lpData; if (uMsg == BFFM_INITIALIZED && Name) { PostMessage(hwnd, BFFM_SETSELECTION, true, (LMessage::Param)Name); } return 0; } void LFileSelect::OpenFolder(SelectCb Cb) { bool Status = FALSE; Name("(folder selection)"); OPENFILENAMEW Info; d->BeforeDlg(Info); Info.Flags &= ~OFN_FILEMUSTEXIST; Info.Flags &= ~OFN_ALLOWMULTISELECT; Info.Flags |= OFN_NOVALIDATE; Info.Flags |= OFN_NOTESTFILECREATE; if (!Info.lpstrInitialDir && d->InitDir) Info.lpstrInitialDir = Utf8ToWide(d->InitDir); Status = GetSaveFileNameW(&Info) != 0; d->AfterDlg(Info, Status); auto f = d->Files.Length() ? d->Files[0] : NULL; if (f) { auto d = strrchr(f, DIR_CHAR); if (d && d > f) { if (d[-1] == ':') d[1] = 0; else *d = 0; } } if (Cb) Cb(this, Status && Length() > 0); } void LFileSelect::Save(SelectCb Cb) { bool Status = FALSE; OPENFILENAMEW Info; d->BeforeDlg(Info); Status = GetSaveFileNameW(&Info) != 0; if (!Status && d->DoFallback(Info)) Status = GetSaveFileNameW(&Info) != 0; d->AfterDlg(Info, Status); if (Cb) Cb(this, Status && Length() > 0); }