diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,87 +1,89 @@ syntax: glob .DS_Store Lgi*.sdf Lgi*.suo Lib Win32* x64* Debug* Release* +*.o +*.d */.DS_Store *.xcuserstate *.xcbkptlist xcschememanagement.plist contents.xcworkspacedata HtmlEditor/HtmlEdit*.sdf HtmlEditor/HtmlEdit*.suo HtmlEditor/x64Debug HtmlEditor/x64Release HtmlEditor/build HtmlEditor/Gtk3/build HtmlTest/Files/* HtmlTest/HtmlTest*.sdf HtmlTest/HtmlTest*.suo HtmlTest/x64Debug HtmlTest/x64Release HtmlTest/MacCarbon/build HtmlTest/MacCocoa/build Ide/HtmlTest*.sdf Ide/HtmlTest*.suo Ide/x64Debug* Ide/Debug* Ide/x64Release* Ide/Release* Ide/LgiIde.xml.* Ide/LgiIde*.sdf Ide/LgiIde*.suo Ide/Gtk3/build* Ide/MacCarbon/build Ide/MacCocoa/build LgiTest/Test*.sdf LgiTest/Test*.suo LgiTest/x64 LgiTest/Mac/build Lvc/Lvc.xml Lvc/Lvc*.sdf Lvc/Lvc*.suo Lvc/x64* Lvc/MacCarbon/build/* Lvc/MacCocoa/build/* ResourceEditor/x64* ResourceEditor/MacCarbon/build ResourceEditor/MacCocoa/build ScriptingUnitTests/Win32* ScriptingUnitTests/x64* src/common/Gdc2/Path/test/*.sdf src/common/Gdc2/Path/test/*.suo src/common/Gdc2/Path/test/x64* src/mac/carbon/build src/mac/cocoa/build src/mac/lldb_callback/build src/common/INet/libntlm-0.4.2/build-win32/Win32* src/common/INet/libntlm-0.4.2/build-win32/x64* Static_Win32* Static_x64* tests/Debug tests/Release tests/x64 Updater/Updater*.user Updater/Win32* Updater/x64* VsLaunch/VsLaunch*.sdf VsLaunch/VsLaunch*.suo VsLaunch/Win32* VsLaunch/x64* diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,621 +1,645 @@ cmake_minimum_required (VERSION 3.18) -if(WIN32) +project(Lgi) + +if (WIN32) set(CMAKE_SYSTEM_VERSION 10.0.19041.0) endif() -project (Lgi) - -#get_cmake_property(_variableNames VARIABLES) -#list (SORT _variableNames) -#foreach (_variableName ${_variableNames}) -# message(STATUS "${_variableName}=${${_variableName}}") -#endforeach() +if (FALSE) + # dump all vars for debugging. + get_cmake_property(_variableNames VARIABLES) + list (SORT _variableNames) + foreach (_variableName ${_variableNames}) + message(STATUS "${_variableName}=${${_variableName}}") + endforeach() +endif() 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 AND NOT HAIKU) set(LINUX TRUE) endif() -if(WIN32) +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}") endif() message("CODELIB=${CODELIB}") if(hasParent) set(CODELIB "${CODELIB}" PARENT_SCOPE) 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 OR HAIKU) 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 libpng16) - add_subdirectory(${PNG_LIBRARY} libpng) +if (HAIKU) + find_package(PNG REQUIRED) + set(LIBPNG_TARGET PNG::PNG) +else() + get_filename_component(PNG_LIBRARY "${CODELIB}/libpng" ABSOLUTE) + if (EXISTS ${PNG_LIBRARY} AND NOT TARGET libpng16) + add_subdirectory(${PNG_LIBRARY} libpng) + set(LIBPNG_TARGET libpng16) + endif() endif() -get_filename_component(JPEG_LIBRARY "${CODELIB}/libjpeg-9a" ABSOLUTE) -if (NOT TARGET libjpeg9a) - add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) +if (HAIKU) + find_package(JPEG REQUIRED) +else() + get_filename_component(JPEG_LIBRARY "${CODELIB}/libjpeg-9a" ABSOLUTE) + if (NOT TARGET libjpeg9a) + add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) + endif() 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) 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 include/lgi/common/Filter.h src/common/Gdc2/Filters/Filter.cpp include/lgi/common/DisplayString.h src/common/Gdc2/Font/DisplayString.cpp include/lgi/common/StringLayout.h src/common/Gdc2/Font/StringLayout.cpp include/lgi/common/Font.h src/common/Gdc2/Font/Font.cpp src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/Font/FontType.cpp src/common/Gdc2/Font/TypeFace.cpp include/lgi/common/Charset.h src/common/Gdc2/Font/Charset.cpp include/lgi/common/Colour.h src/common/Gdc2/Colour.cpp include/lgi/common/Gdc2.h src/common/Gdc2/GdcCommon.cpp include/lgi/common/Rect.h src/common/Gdc2/Rect.cpp src/common/Gdc2/Surface.cpp include/lgi/common/Path.h src/common/Gdc2/Path/Path.cpp src/common/Gdc2/Tools/ColourReduce.cpp include/lgi/common/Containers.h src/common/General/Containers.cpp include/lgi/common/DateTime.h src/common/General/DateTime.cpp src/common/General/ExeCheck.cpp include/lgi/common/File.h src/common/General/FileCommon.cpp include/lgi/common/Growl.h 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 include/lgi/common/Net.h src/common/Net/Net.cpp src/common/Net/NetTools.cpp src/common/Net/MDStringToDigest.cpp src/common/Lgi/Alert.cpp include/lgi/common/Css.h src/common/Lgi/Css.cpp include/lgi/common/CssTools.h src/common/Lgi/CssTools.cpp include/lgi/common/FindReplaceDlg.h src/common/Lgi/FindReplace.cpp include/lgi/common/FontSelect.h src/common/Lgi/FontSelect.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Input.cpp include/lgi/common/Library.h src/common/Lgi/Library.cpp include/lgi/common/Mru.h src/common/Lgi/Mru.cpp include/lgi/common/Mutex.h src/common/Lgi/Mutex.cpp src/common/Lgi/Object.cpp include/lgi/common/OptionsFile.h src/common/Lgi/OptionsFile.cpp include/lgi/common/SubProcess.h src/common/Lgi/SubProcess.cpp include/lgi/common/Stream.h src/common/Lgi/Stream.cpp src/common/Lgi/MemStream.cpp include/lgi/common/Thread.h src/common/Lgi/ThreadCommon.cpp src/common/Lgi/ThreadEvent.cpp include/lgi/common/ToolTip.h src/common/Lgi/ToolTip.cpp include/lgi/common/TrayIcon.h src/common/Lgi/TrayIcon.cpp include/lgi/common/Variant.h src/common/Lgi/Variant.cpp include/lgi/common/App.h src/common/Lgi/AppCommon.cpp include/lgi/common/View.h src/common/Lgi/ViewCommon.cpp include/lgi/common/Window.h src/common/Lgi/WindowCommon.cpp include/lgi/common/DragAndDrop.h src/common/Lgi/DragAndDropCommon.cpp include/lgi/common/Lgi.h src/common/Lgi/LgiCommon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp include/lgi/common/DropFiles.h src/common/Lgi/DropFiles.cpp include/lgi/common/Menu.h src/common/Lgi/MenuCommon.cpp include/lgi/common/LgiRes.h src/common/Resource/LgiRes.cpp src/common/Resource/Res.cpp src/common/Skins/Gel/Gel.cpp include/lgi/common/DocView.h src/common/Text/DocView.cpp include/lgi/common/StringClass.h include/lgi/common/LgiString.h src/common/Text/String.cpp include/lgi/common/TextView3.h src/common/Text/TextView3.cpp include/lgi/common/TextView4.h src/common/Text/TextView4.cpp include/lgi/common/Token.h src/common/Text/Token.cpp include/lgi/common/Unicode.h src/common/Text/Unicode.cpp include/lgi/common/Unicode.h src/common/Text/Utf8.cpp include/lgi/common/XmlTree.h src/common/Text/XmlTree.cpp include/lgi/common/Button.h src/common/Widgets/Button.cpp include/lgi/common/Bitmap.h src/common/Widgets/Bitmap.cpp include/lgi/common/Box.h src/common/Widgets/Box.cpp src/common/Widgets/ItemContainer.cpp include/lgi/common/List.h src/common/Widgets/List.cpp include/lgi/common/Panel.h src/common/Widgets/Panel.cpp include/lgi/common/Popup.h src/common/Widgets/Popup.cpp include/lgi/common/ProgressDlg.h src/common/Widgets/ProgressDlg.cpp include/lgi/common/RadioGroup.h src/common/Widgets/RadioGroup.cpp include/lgi/common/Splitter.h src/common/Widgets/Splitter.cpp include/lgi/common/StatusBar.h src/common/Widgets/StatusBar.cpp include/lgi/common/TableLayout.h src/common/Widgets/TableLayout.cpp include/lgi/common/TabView.h src/common/Widgets/TabView.cpp include/lgi/common/TextLabel.h src/common/Widgets/TextLabel.cpp include/lgi/common/ToolBar.h src/common/Widgets/ToolBar.cpp include/lgi/common/Tree.h src/common/Widgets/Tree.cpp include/lgi/common/CheckBox.h include/lgi/common/Combo.h include/lgi/common/Edit.h include/lgi/common/Layout.h include/lgi/common/Slider.h include/lgi/common/Progress.h include/lgi/common/ClipBoard.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) elseif (HAIKU) set(Source ${CommonSource} - src/haiku/ + src/common/Lgi/FileSelect.cpp + src/common/Widgets/Combo.cpp + src/common/Widgets/Edit.cpp + src/common/Widgets/CheckBox.cpp + src/common/Widgets/ScrollBar.cpp + src/common/Widgets/Progress.cpp + src/common/Widgets/Slider.cpp src/haiku/App.cpp src/haiku/ClipBoard.cpp src/haiku/DragAndDrop.cpp src/haiku/File.cpp src/haiku/Gdc2.cpp src/haiku/General.cpp src/haiku/Layout.cpp src/haiku/Mem.cpp src/haiku/MemDC.cpp src/haiku/Menu.cpp src/haiku/PrintDC.cpp src/haiku/Printer.cpp src/haiku/ScreenDC.cpp src/haiku/ShowFileProp_Haiku.cpp src/haiku/Thread.cpp src/haiku/View.cpp src/haiku/Widgets.cpp src/haiku/Window.cpp) endif () add_library(lgi SHARED ${Source}) add_library(lgi-stub STATIC src/common/Gdc2/Font/Charset.cpp src/common/General/Containers.cpp src/common/General/DateTime.cpp src/common/General/FileCommon.cpp src/common/Lgi/Library.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/Mutex.cpp src/common/Lgi/Stream.cpp src/common/Lgi/ThreadCommon.cpp src/common/Lgi/Variant.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp src/common/Text/String.cpp src/common/Text/Token.cpp src/common/Text/Unicode.cpp) target_compile_definitions(lgi-stub PRIVATE LGI_LIBRARY LGI_STATIC) target_include_directories(lgi-stub PUBLIC include) 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 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 WIN64 _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) target_sources(lgi-stub PRIVATE src/win/General/File.cpp src/win/General/Mem.cpp src/win/Lgi/General.cpp src/win/Lgi/Thread.cpp) target_include_directories(lgi-stub PUBLIC include/lgi/win) target_compile_definitions(lgi-stub PUBLIC WINDOWS _UNICODE UNICODE) elseif (HAIKU) target_include_directories(lgi-stub PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi-stub PUBLIC HAIKU _GNU_SOURCE) target_include_directories(lgi PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi PUBLIC HAIKU _GNU_SOURCE) + target_link_libraries(lgi + PUBLIC + gnu + network + be) 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) add_subdirectory(src/common/Net/libntlm-0.4.2) if(WIN32) add_subdirectory(VsLaunch) endif() if(LINUX) add_subdirectory(src/linux/CrashHandler) endif() diff --git a/HtmlEditor/Rich.cpp b/HtmlEditor/Rich.cpp --- a/HtmlEditor/Rich.cpp +++ b/HtmlEditor/Rich.cpp @@ -1,731 +1,735 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/Tree.h" #include "lgi/common/TabView.h" #include "lgi/common/Box.h" #include "resource.h" #include "lgi/common/SpellCheck.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" #include "lgi/common/Http.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/List.h" #include "lgi/common/Menu.h" #include "lgi/common/FileSelect.h" #if 1 #include "lgi/common/RichTextEdit.h" typedef LRichTextEdit EditCtrl; #else #include "LHtmlEdit.h" typedef LHtmlEdit EditCtrl; #endif enum Ctrls { IDC_EDITOR = 100, IDC_HTML, IDC_HTML_BOX, IDC_IMAGES, IDC_TABS, IDC_TREE, IDC_TO_HTML, IDC_TO_NODES, IDC_VIEW_IN_BROWSER, IDC_SAVE_FILE, IDC_SAVE, IDC_EXIT, IDC_INSTALL }; enum Messages { M_INSTALL = M_USER + 200, }; const char *AppName = "HtmlEdit"; #define LOAD_DOC 1 #define SrcFileName "Reply4.html" #if 0 char Src[] = "This is a test. A longer string for the purpose of inter-line cursor testing.
\n" "A longer string for the purpose of inter-line cursor testing.
\n" "--
" "Matthew Allen"; #else char Src[] = "\n" "Hi Suzy,
\n" "
\n" "The aircon is a little warm in our area of the woods. There seems to be nothing coming out of the 2nd AC unit.
\n" "
\n" "Regards
\n" "--
\n" "Matthew Allen\n" "\n" ""; #endif class LCapabilityInstallTarget : public LCapabilityTarget { public: virtual void StartInstall(CapsHash *Caps) = 0; }; class CapsBar : public LView { LCapabilityInstallTarget *App; LCapabilityTarget::CapsHash *Caps; LButton *Ok, *Install; public: CapsBar(LCapabilityInstallTarget *Parent, LCapabilityTarget::CapsHash *caps) { App = Parent; Caps = caps; Ok = new LButton(IDOK, 0, 0, -1, -1, "Ok"); Install = new LButton(IDC_INSTALL, 0, 0, -1, -1, "Install"); } bool Pour(LRegion &r) { LRect *rc = FindLargestEdge(r, GV_EDGE_TOP); if (!rc) return false; LRect p = *rc; p.y2 = p.y1 + LSysFont->GetHeight() + 11; SetPos(p); return true; } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { App->OnCloseInstaller(); break; } case IDC_INSTALL: { App->StartInstall(Caps); break; } } return 0; } void OnPaint(LSurface *pDC) { pDC->Colour(LColour::Red); pDC->Rectangle(); LSysFont->Colour(LColour::White, LColour::Red); LSysFont->Transparent(true); LString s = "Missing components: "; // const char *k; // for (bool b = Caps->First(&k); b; b = Caps->Next(&k)) for (auto k : *Caps) { s += k.key; s += " "; } LDisplayString d(LSysFont, s); d.Draw(pDC, 6, 6); if (Ok && Install) { int x = GetClient().x2 - 6; if (!Ok->IsAttached()) Ok->Attach(this); LRect r; r.Set(0, 0, Ok->X()-1, Ok->Y()-1); r.Offset(x - r.X(), (Y() - r.Y()) >> 1); Ok->SetPos(r); if (!Install->IsAttached()) Install->Attach(this); r.Set(0, 0, Install->X()-1, Install->Y()-1); r.Offset(Ok->GetPos().x1 - 6 - r.X(), (Y() - r.Y()) >> 1); Install->SetPos(r); } } }; class InstallThread : public LEventTargetThread { int AppHnd; public: InstallThread(int appHnd) : LEventTargetThread("InstallThread") { AppHnd = appHnd; } LMessage::Result OnEvent(LMessage *m) { switch (m->Msg()) { case M_INSTALL: { LAutoPtr Component((LString*)m->A()); const char *Base = "http://memecode.com/components/lookup.php?app=Scribe&wordsize=%i&component=%s&os=win64&version=2.2.0"; LString s; s.Printf(Base, sizeof(NativeInt)*8, Component->Get()); #if _MSC_VER == _MSC_VER_VS2013 s += "&tags=vc12"; #elif _MSC_VER == _MSC_VER_VS2008 s += "&tags=vc9"; #endif LMemStream o(1024); LString err; int Installed = 0; if (!LgiGetUri(this, &o, &err, s)) { LgiTrace("%s:%i - Get URI failed.\n", _FL); break; } LXmlTree t; LXmlTag r; o.SetPos(0); if (!t.Read(&r, &o)) { LgiTrace("%s:%i - Bad XML.\n", _FL); break; } if (!r.IsTag("components")) { LgiTrace("%s:%i - No components tag.\n", _FL); break; } for (auto c: r.Children) { if (c->IsTag("file")) { const char *Link = c->GetContent(); LMemStream File(1024); if (LgiGetUri(this, &File, &err, Link)) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExeFile(), ".."); LMakePath(p, sizeof(p), p, LGetLeaf(Link)); LFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); if (f.Write(File.GetBasePtr(), File.GetSize()) == File.GetSize()) { LAutoPtr comp(new LString(*Component)); PostObject(AppHnd, M_INSTALL, comp); Installed++; } else LgiTrace("%s:%i - Couldn't write to '%p'.\n", _FL, p); } else LgiTrace("%s:%i - Can't open '%s' for writing.\n", _FL, p); } else LgiTrace("%s:%i - Link download failed.\n", _FL); } } if (Installed == 0) { LgiTrace("%s:%i - No installed components from URI:\n%s\n", _FL, s.Get()); } break; } } return 0; } }; class MediaItem : public LListItem { LDocView::ContentMedia *Cm; LString sSize; public: MediaItem(LDocView::ContentMedia *cm) { Cm = cm; } void OnMouseClick(LMouse &m) { if (m.Left() && m.Double()) { LFile::Path p(LSP_TEMP); p += Cm->FileName; LFile f; if (f.Open(p, O_WRITE)) { if (Cm->Stream) { LCopyStreamer Cp; Cp.Copy(Cm->Stream, &f); } else if (Cm->Data.Type == GV_BINARY) f.Write(Cm->Data.Value.Binary.Data, Cm->Data.Value.Binary.Length); f.Close(); } LExecute(p); } } const char *GetText(int i) { switch (i) { case 0: // Filename { return Cm->FileName; } case 1: // Size { int64 Sz = 0; if (Cm->Stream.Get()) Sz = Cm->Stream->GetSize(); else if (Cm->Data.Type == GV_BINARY) Sz = Cm->Data.Value.Binary.Length; char s[64]; LFormatSize(s, sizeof(s), Sz); sSize = s; return sSize; } case 2: { return Cm->MimeType; } case 3: { return Cm->Id; } } return NULL; } }; class App : public LWindow, public LCapabilityInstallTarget { LBox *Split; LTextView3 *Txt; LList *Imgs; LTabView *Tabs; LTree *Tree; uint64 LastChange; EditCtrl *Edit; LAutoPtr Speller; CapsBar *Bar; LCapabilityTarget::CapsHash Caps; LAutoPtr Installer; LOptionsFile Options; LArray Media; public: App() : Options(LOptionsFile::PortableMode, AppName) { LastChange = 0; Edit = 0; Txt = 0; Bar = NULL; Tabs = NULL; Tree = NULL; Imgs = NULL; Name("Rich Text Testbed"); if (!Options.SerializeFile(false) || !SerializeState(&Options, "WndState", true)) { LRect r(0, 0, 1200, 800); SetPos(r); MoveToCenter(); } SetQuitOnClose(true); #ifdef WINNATIVE SetIcon((const char*)IDI_APP); #endif #if defined(WINNATIVE) Speller = CreateWindowsSpellCheck(); #elif defined(LINUX) // This is currently in the Scribe code base... should be moved into Lgi? // Speller = CreateAspellObject(); #elif defined(MAC) && !defined(__GTK_H__) Speller = CreateAppleSpellCheck(); #endif if (Attach(0)) { Menu = new LMenu; if (Menu) { Menu->Attach(this); auto s = Menu->AppendSub("&File"); s->AppendItem("To &HTML", IDC_TO_HTML, true, -1, "F5"); s->AppendItem("To &Nodes", IDC_TO_NODES, true, -1, "F6"); s->AppendItem("View In &Browser", IDC_VIEW_IN_BROWSER, true, -1, "F7"); s->AppendItem("&Save", IDC_SAVE, true, -1, "Ctrl+S"); s->AppendItem("Save &As", IDC_SAVE_FILE, true, -1, "Ctrl+Shift+S"); s->AppendSeparator(); s->AppendItem("E&xit", IDC_EXIT, true, -1, "Ctrl+W"); } AddView(Split = new LBox); if (Split) { // Split->Debug(); Split->AddView(Edit = new EditCtrl(IDC_EDITOR)); if (Edit) { if (Speller) { LVariant v; Edit->SetValue("SpellCheckLanguage", v = "English"); Edit->SetValue("SpellCheckDictionary", v = "AU"); Edit->SetSpellCheck(Speller); } Edit->Sunken(true); Edit->SetId(IDC_EDITOR); Edit->Register(this); LVariant v; Edit->SetValue("HtmlImagesLinkCid", v = true); #if LOAD_DOC #ifndef SrcFileName Edit->Name(Src); #else char p[MAX_PATH_LEN]; LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LMakePath(p, sizeof(p), p, "Test"); LMakePath(p, sizeof(p), p, SrcFileName); LAutoString html(LReadTextFile(p)); if (html) Edit->Name(html); #endif #endif } Split->AddView(Tabs = new LTabView(IDC_TABS)); if (Tabs) { // Tabs->Debug(); LTabPage *p = Tabs->Append("Html Output"); if (p) { LBox *b = new LBox(IDC_HTML_BOX); b->SetVertical(true); b->AddView(Txt = new LTextView3(IDC_HTML, 0, 0, 100, 100)); Txt->SetPourLargest(true); b->AddView(Imgs = new LList(IDC_IMAGES)); Imgs->GetCss(true)->Height(LCss::Len("150px")); Imgs->AddColumn("Filename", 200); Imgs->AddColumn("Size", 100); Imgs->AddColumn("MimeType", 100); Imgs->AddColumn("Cid", 100); p->Append(b); } p = Tabs->Append("Node View"); if (p) { p->AddView(Tree = new LTree(IDC_TREE, 0, 0, 100, 100)); Tree->SetPourLargest(true); } } Split->Value(GetClient().X()/2); } AttachChildren(); PourAll(); Visible(true); if (Edit) Edit->Focus(true); } } ~App() { SerializeState(&Options, "WndState", false); Options.SerializeFile(true); Installer.Reset(); } bool NeedsCapability(const char *Name, const char *Param = NULL) { if (Caps.Find(Name)) return true; Caps.Add(Name, true); if (!Bar) { Bar = new CapsBar(this, &Caps); if (Bar) { AddView(Bar, 0); AttachChildren(); PourAll(); Invalidate(); } } return true; } void StartInstall(CapsHash *Caps) { if (!Installer) Installer.Reset(new InstallThread(AddDispatch())); if (Installer) { // const char *c; // for (bool b = Caps->First(&c); b; b = Caps->Next(&c)) for (auto c : *Caps) { LAutoPtr s(new LString(c.key)); Installer->PostObject(Installer->GetHandle(), M_INSTALL, s); } } } void OnInstall(CapsHash *Caps, bool Status) { DeleteObj(Bar); if (Edit && Caps && Status) { // const char *k; // for (bool b = Caps->First(&k); b; b = Caps->Next(&k)) for (auto k : *Caps) Edit->PostEvent(M_COMPONENT_INSTALLED, new LString(k.key)); } PourAll(); } void OnCloseInstaller() { DeleteObj(Bar); PourAll(); } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDC_EXIT: LCloseApp(); break; case IDC_TO_HTML: { Tabs->Value(0); LString Out; Imgs->Empty(); Media.Empty(); if (Edit->GetFormattedContent("text/html", Out, &Media)) { Txt->Name(Out); for (auto &m : Media) { Imgs->Insert(new MediaItem(&m)); } Imgs->ResizeColumnsToContent(); } break; } case IDC_TO_NODES: Tabs->Value(1); #ifdef _DEBUG Tree->Empty(); Edit->DumpNodes(Tree); #endif break; case IDC_VIEW_IN_BROWSER: { LFile::Path p(LSP_TEMP); p += "export.html"; if (Edit->Save(p)) { LExecute(p); } break; } case IDC_SAVE_FILE: { - LFileSelect s; - s.Parent(this); - s.Type("HTML", "*.html"); - if (s.Save()) - Edit->Save(s.Name()); + auto s = new LFileSelect; + s->Parent(this); + s->Type("HTML", "*.html"); + s->Save([&](auto dlg, auto status) + { + if (status) + Edit->Save(dlg->Name()); + delete dlg; + }); break; } case IDC_SAVE: { if (Edit) { LString Html; LArray Media; if (Edit->GetFormattedContent("text/html", Html, &Media)) { LFile::Path p(LSP_APP_INSTALL); p += "Output"; if (!p.IsFolder()) FileDev->CreateFolder(p); p += "output.html"; LFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); f.Write(Html); f.Close(); LString FileName = p.GetFull(); for (unsigned i=0; iGetId()) { case IDC_EDITOR: { if ((n.Type == LNotifyDocChanged || n.Type == LNotifyCursorChanged) && Edit) { LastChange = LCurrentTime(); Tree->Empty(); } break; } case IDC_TREE: { LTreeItem *i = Tree->Selection(); LHashTbl,LString> vars; if (i) { auto p = LString(i->GetText()).Split(","); for (auto i : p) { LString::Array a = i.Strip().Split("="); if (a.Length() == 2) vars.Add(a[0], a[1]); } } switch (n.Type) { case LNotifyItemSelect: { #ifdef _DEBUG LString Ptr = vars.Find("ptr"); if (Ptr) Edit->SelectNode(Ptr); #endif break; } default: break; } break; } } return 0; } void OnReceiveFiles(LArray &Files) { if (Edit && Files.Length() > 0) { LAutoString t(LReadTextFile(Files[0])); if (t) Edit->Name(t); } } LMessage::Result OnEvent(LMessage *m) { if (m->Msg() == M_INSTALL) { LAutoPtr c((LString*)m->A()); if (c) { LCapabilityTarget::CapsHash h; h.Add(*c, true); OnInstall(&h, true); } } return LWindow::OnEvent(m); } }; int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, "RichEditTest"); a.AppWnd = new App; a.Run(); return 0; } diff --git a/Ide/CMakeLists.txt b/Ide/CMakeLists.txt --- a/Ide/CMakeLists.txt +++ b/Ide/CMakeLists.txt @@ -1,147 +1,161 @@ cmake_minimum_required (VERSION 3.18) -set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_CXX_STANDARD 14) project (LgiIde) -if (NOT TARGET libpng16) +if (NOT DEFINED LIBPNG_TARGET OR NOT TARGET ${LIBPNG_TARGET}) message(FATAL_ERROR "Find the libpng target (see parent folder cmake)") endif() if (APPLE) + set(CMAKE_OSX_ARCHITECTURES "x86_64") 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 () +if (NOT MSVC) + find_package(ZLIB REQUIRED) + set(ZLIB_TARGET ZLIB::ZLIB) +else() + set(ZLIB_TARGET zlib) +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 libpng16) +target_link_libraries(LgiIde lgi ${LIBPNG_TARGET} ${ZLIB_TARGET}) 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 () +if (NOT MSVC) + target_compile_definitions(LgiIde PRIVATE LIBPNG_SHARED=0) +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/DebugContext.cpp b/Ide/Code/DebugContext.cpp --- a/Ide/Code/DebugContext.cpp +++ b/Ide/Code/DebugContext.cpp @@ -1,773 +1,777 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextLog.h" #include "lgi/common/List.h" #include "LgiIde.h" #include "IdeProject.h" enum DebugMessages { M_RUN_STATE = M_USER + 100, M_ON_CRASH, M_FILE_LINE, }; class LDebugContextPriv : public LMutex { public: LDebugContext *Ctx; AppWnd *App; IdeProject *Proj; bool InDebugging; LAutoPtr Db; LString Exe, Args; LString SeekFile; int SeekLine; bool SeekCurrentIp; LString MemDumpAddr; NativeInt MemDumpStart; LArray MemDump; LDebugContextPriv(LDebugContext *ctx) : LMutex("LDebugContextPriv") { Ctx = ctx; MemDumpStart = 0; App = NULL; Proj = NULL; InDebugging = false; SeekLine = 0; SeekCurrentIp = false; } ~LDebugContextPriv() { #if DEBUG_SESSION_LOGGING LgiTrace("~LDebugContextPriv freeing debugger...\n"); #endif Db.Reset(); #if DEBUG_SESSION_LOGGING LgiTrace("...done.\n"); #endif } void UpdateThreads() { if (!Db || !Ctx->Threads || !InDebugging) { LgiTrace("%s:%i - No debugger.\n", _FL); return; } LArray Threads; int CurrentThread = -1; if (!Db->GetThreads(Threads, &CurrentThread)) { LgiTrace("%s:%i - Failed to get threads from debugger.\n", _FL); return; } Ctx->Threads->Empty(); for (unsigned i=0; iSetText(f, 0); it->SetText(Sp, 1); Ctx->Threads->Insert(it); it->Select(ThreadId == CurrentThread); } } } Ctx->Threads->SendNotify(); } void UpdateCallStack() { if (Db && Ctx->CallStack && InDebugging) { LArray Stack; if (Db->GetCallStack(Stack)) { Ctx->CallStack->Empty(); for (int i=0; iSetText(f, 0); it->SetText(Sp, 1); } else { it->SetText(Stack[i], 1); } } else { it->SetText(Stack[i], 1); } Ctx->CallStack->Insert(it); } Ctx->CallStack->SendNotify(); } } } void Log(const char *Fmt, ...) { if (Ctx->DebuggerLog) { va_list Arg; va_start(Arg, Fmt); LStreamPrintf(Ctx->DebuggerLog, 0, Fmt, Arg); va_end(Arg); } } int Main() { return 0; } }; LDebugContext::LDebugContext(AppWnd *App, IdeProject *Proj, const char *Exe, const char *Args, bool RunAsAdmin, const char *Env, const char *InitDir) { Watch = NULL; Locals = NULL; DebuggerLog = NULL; ObjectDump = NULL; Registers = NULL; Threads = NULL; d = new LDebugContextPriv(this); d->App = App; d->Proj = Proj; d->Exe = Exe; - + + #ifdef HAIKU + LAssert(!"No GDB"); + #else if (d->Db.Reset(CreateGdbDebugger(App->GetDebugLog()))) { LFile::Path p; if (InitDir) { p = InitDir; } else { p = Exe; p--; } if (!d->Db->Load(this, Exe, Args, RunAsAdmin, p, Env)) { d->Log("Failed to load '%s' into debugger.\n", d->Exe.Get()); d->Db.Reset(); } } + #endif } LDebugContext::~LDebugContext() { DeleteObj(d); } LMessage::Param LDebugContext::OnEvent(LMessage *m) { switch (m->Msg()) { case M_ON_CRASH: { #if DEBUG_SESSION_LOGGING LgiTrace("LDebugContext::OnEvent(M_ON_CRASH)\n"); #endif d->UpdateCallStack(); break; } case M_FILE_LINE: { #if DEBUG_SESSION_LOGGING LgiTrace("LDebugContext::OnEvent(M_FILE_LINE)\n"); #endif LString File; { LMutex::Auto a(d, _FL); File = d->SeekFile; } // printf("%s:%i - %s %i\n", _FL, File.Get(), d->SeekLine); if (File && d->SeekLine > 0) d->App->GotoReference(File, d->SeekLine, d->SeekCurrentIp); break; } } return 0; } bool LDebugContext::SetFrame(int Frame) { return d->Db ? d->Db->SetFrame(Frame) : false; } bool LDebugContext::DumpObject(const char *Var, const char *Val) { if (!d->Db || !Var || !ObjectDump || !d->InDebugging) return false; ObjectDump->Name(NULL); if (!d->Db->PrintObject(Var, ObjectDump)) return false; ObjectDump->SetCaret(0); return true; } bool LDebugContext::UpdateRegisters() { if (!d->Db || !Registers || !d->InDebugging) return false; return d->Db->GetRegisters(Registers); } bool LDebugContext::UpdateLocals() { if (!Locals || !d->Db || !d->InDebugging) return false; LArray Vars; if (!d->Db->GetVariables(true, Vars, false)) return false; Locals->Empty(); for (int i=0; iSetText("local", 0); break; case LDebugger::Variable::Global: it->SetText("global", 0); break; case LDebugger::Variable::Arg: it->SetText("arg", 0); break; } char s[256]; switch (v.Value.Type) { case GV_BOOL: { it->SetText(v.Type ? v.Type.Get() : "bool", 1); sprintf_s(s, sizeof(s), "%s", v.Value.Value.Bool ? "true" : "false"); break; } case GV_INT32: { it->SetText(v.Type ? v.Type.Get() : "int32", 1); sprintf_s(s, sizeof(s), "%i (0x%x)", v.Value.Value.Int, v.Value.Value.Int); break; } case GV_INT64: { it->SetText(v.Type ? v.Type.Get() : "int64", 1); sprintf_s(s, sizeof(s), LPrintfInt64, v.Value.Value.Int64); break; } case GV_DOUBLE: { it->SetText(v.Type ? v.Type.Get() : "double", 1); sprintf_s(s, sizeof(s), "%g", v.Value.Value.Dbl); break; } case GV_STRING: { it->SetText(v.Type ? v.Type.Get() : "string", 1); sprintf_s(s, sizeof(s), "%s", v.Value.Value.String); break; } case GV_WSTRING: { it->SetText(v.Type ? v.Type.Get() : "wstring", 1); #ifdef MAC LAutoString tmp(WideToUtf8(v.Value.Value.WString)); sprintf_s(s, sizeof(s), "%s", tmp.Get()); #else sprintf_s(s, sizeof(s), "%S", v.Value.Value.WString); #endif break; } case GV_VOID_PTR: { it->SetText(v.Type ? v.Type.Get() : "void*", 1); sprintf_s(s, sizeof(s), "%p", v.Value.Value.Ptr); break; } default: { sprintf_s(s, sizeof(s), "notimp(%s)", LVariant::TypeToString(v.Value.Type)); it->SetText(v.Type ? v.Type : s, 1); s[0] = 0; break; } } it->SetText(v.Name, 2); it->SetText(s, 3); Locals->Insert(it); } } Locals->ResizeColumnsToContent(); return true; } bool LDebugContext::UpdateWatches() { LArray Vars; for (LTreeItem *i = Watch->GetChild(); i; i = i->GetNext()) { LDebugger::Variable &v = Vars.New(); v.Name = i->GetText(0); v.Type = i->GetText(1); } printf("Update watches %i\n", (int)Vars.Length()); if (!d->Db->GetVariables(false, Vars, false)) return false; int Idx = 0; for (LTreeItem *i = Watch->GetChild(); i; i = i->GetNext(), Idx++) { LDebugger::Variable &v = Vars[Idx]; WatchItem *wi = dynamic_cast(i); if (!wi) { LgiTrace("%s:%i - Error: not watch item.\n", _FL); continue; } if (v.Name == (const char*)i->GetText(0)) { i->SetText(v.Type, 1); wi->SetValue(v.Value); } else { LgiTrace("%s:%i - Error: Not the same name.\n", _FL); } } return true; } void LDebugContext::UpdateCallStack() { d->UpdateCallStack(); } void LDebugContext::UpdateThreads() { d->UpdateThreads(); } bool LDebugContext::SelectThread(int ThreadId) { if (!d->Db) { LgiTrace("%s:%i - No debugger.\n", _FL); return false; } return d->Db->SetCurrentThread(ThreadId); } bool LDebugContext::ParseFrameReference(const char *Frame, LAutoString &File, int &Line) { if (!Frame) return false; const char *At = NULL, *s = Frame; while ((s = stristr(s, "at"))) { At = s; s += 2; } if (!At) return false; At += 3; if (!File.Reset(LTokStr(At))) return false; char *Colon = strchr(File, ':'); if (!Colon) return false; *Colon++ = 0; Line = atoi(Colon); return Line > 0; } bool LDebugContext::OnCommand(int Cmd) { #if DEBUG_SESSION_LOGGING LgiTrace("LDebugContext::OnCommand(%i)\n", Cmd); #endif switch (Cmd) { case IDM_START_DEBUG: { if (d->Db) { d->App->LoadBreakPoints(d->Db); d->Db->SetRunning(true); } break; } case IDM_CONTINUE: { if (d->Db) d->Db->SetRunning(true); break; } case IDM_ATTACH_TO_PROCESS: { break; } case IDM_STOP_DEBUG: { if (d->Db) return d->Db->Unload(); else return false; break; } case IDM_PAUSE_DEBUG: { if (d->Db) d->Db->SetRunning(false); break; } case IDM_RESTART_DEBUGGING: { if (d->Db) d->Db->Restart(); break; } case IDM_RUN_TO: { break; } case IDM_STEP_INTO: { if (d->Db) d->Db->StepInto(); break; } case IDM_STEP_OVER: { if (d->Db) d->Db->StepOver(); break; } case IDM_STEP_OUT: { if (d->Db) d->Db->StepOut(); break; } case IDM_TOGGLE_BREAKPOINT: { break; } default: return false; } return true; } void LDebugContext::OnUserCommand(const char *Cmd) { if (d->Db) d->Db->UserCommand(Cmd); } void NonPrintable(uint64 ch, uint8_t *&out, ssize_t &len) { if (ch == '\r') { if (len > 1) { *out++ = '\\'; *out++ = 'r'; len -= 2; } else LAssert(0); } else if (ch == '\n') { if (len > 1) { *out++ = '\\'; *out++ = 'n'; len -= 2; } else LAssert(0); } else if (len > 0) { *out++ = '.'; len--; } } void LDebugContext::FormatMemoryDump(int WordSize, int Width, bool InHex) { if (!MemoryDump) { LgiTrace("%s:%i - No MemoryDump.\n", _FL); return; } if (d->MemDump.Length() == 0) { MemoryDump->Name("No data."); return; } if (!Width) Width = 16 / WordSize; if (!WordSize) WordSize = 1; // Format output to the mem dump window LStringPipe p; int LineBytes = WordSize * Width; LPointer ptr; ptr.u8 = &d->MemDump[0]; for (NativeInt i = 0; i < d->MemDump.Length(); i += LineBytes) { // LPointer Start = ptr; ssize_t DisplayBytes = MIN(d->MemDump.Length() - i, LineBytes); ssize_t DisplayWords = DisplayBytes / WordSize; NativeInt iAddr = d->MemDumpStart + i; p.Print("%p ", iAddr); char Char[256] = ""; uint8_t *ChPtr = (uint8_t*)Char; ssize_t Len = sizeof(Char); for (int n=0; nName(p.NewGStr()); } void LDebugContext::OnMemoryDump(const char *Addr, int WordSize, int Width, bool IsHex) { if (MemoryDump && d->Db) { MemoryDump->Name(NULL); LString ErrMsg; if (d->Db->ReadMemory(d->MemDumpAddr = Addr, 1024, d->MemDump, &ErrMsg)) { d->MemDumpStart = (int)d->MemDumpAddr.Int(16); FormatMemoryDump(WordSize, Width, IsHex); } else { MemoryDump->Name(ErrMsg ? ErrMsg : "ReadMemory failed."); } } } void LDebugContext::OnState(bool Debugging, bool Running) { #if DEBUG_SESSION_LOGGING LgiTrace("LDebugContext::OnState(%i, %i)\n", Debugging, Running); #endif if (d->InDebugging != Debugging && d->Db) { d->InDebugging = Debugging; } if (d->App) { d->App->OnDebugState(Debugging, Running); } #if DEBUG_SESSION_LOGGING LgiTrace("LDebugContext::OnState(%i, %i) ###ENDED###\n", Debugging, Running); #endif // This object may be deleted at this point... don't access anything. } void LDebugContext::OnFileLine(const char *File, int Line, bool CurrentIp) { if (!d->App) { printf("%s:%i - No app.\n", _FL); return; } if (!File || Line < 1) { LgiTrace("%s:%i - Error: No File or Line... one or both must be valid.\n", _FL); LAssert(!"Invalid Param"); return; } if (d->App->InThread()) { d->App->GotoReference(File, Line, CurrentIp); } else { { LMutex::Auto a(d, _FL); if (File) d->SeekFile = File; d->SeekLine = Line; d->SeekCurrentIp = CurrentIp; // printf("%s:%i - %s %i, %i\n", _FL, d->SeekFile.Get(), d->SeekLine, d->SeekCurrentIp); } d->App->PostEvent(M_FILE_LINE); } } ssize_t LDebugContext::Write(const void *Ptr, ssize_t Size, int Flags) { if (DebuggerLog && Ptr) { // LgiTrace("Write '%.*s'\n", Size, Ptr); return DebuggerLog->Write(Ptr, Size, 0); } return -1; } void LDebugContext::OnError(int Code, const char *Str) { if (DebuggerLog) DebuggerLog->Print("Error(%i): %s\n", Code, Str); } void LDebugContext::OnCrash(int Code) { d->App->PostEvent(M_ON_CRASH); } bool LDebugContext::OnBreakPoint(LDebugger::BreakPoint &b, bool Add) { if (!d->Db) { LgiTrace("%s:%i - No debugger loaded.\n", _FL); return false; } if (Add) return d->Db->SetBreakPoint(&b); else return d->Db->RemoveBreakPoint(&b); } \ No newline at end of file diff --git a/Ide/Code/Debugger.h b/Ide/Code/Debugger.h --- a/Ide/Code/Debugger.h +++ b/Ide/Code/Debugger.h @@ -1,113 +1,115 @@ #ifndef _GDEBUGGER_H_ #define _GDEBUGGER_H_ #include "lgi/common/Variant.h" #include "lgi/common/StringClass.h" #define DEBUG_SESSION_LOGGING 0 class LDebugEvents : public LStream { public: virtual ~LDebugEvents() {} virtual void OnState(bool Debugging, bool Running) = 0; virtual void OnFileLine(const char *File, int Line, bool CurrentIp) = 0; virtual void OnError(int Code, const char *Str) = 0; virtual void OnCrash(int Code) = 0; }; class LDebugger { public: struct BreakPoint { int Index; bool Added; // Use File:Line LString File; ssize_t Line; // -or- // A symbol reference LString Symbol; BreakPoint() { Index = 0; Line = 0; Added = false; } BreakPoint &operator =(const BreakPoint &b) { Index = b.Index; File = b.File; Line = b.Line; Symbol = b.Symbol; return *this; } bool operator ==(const BreakPoint &b) { if (File == b.File && Line == b.Line && Symbol == b.Symbol) return true; return false; } }; struct Variable { enum ScopeType { Local, Arg, Global } Scope; LString Name; LString Type; LVariant Value; LString Detail; }; virtual ~LDebugger() {} virtual bool Load(LDebugEvents *EventHandler, const char *Exe, const char *Args, bool RunAsAdmin, const char *InitDir, const char *Env) = 0; virtual bool Restart() = 0; virtual bool Unload() = 0; virtual bool GetCallStack(LArray &Stack) = 0; virtual bool GetThreads(LArray &Threads, int *CurrentThread) = 0; virtual bool SetCurrentThread(int ThreadId) = 0; virtual bool GetFrame(int &Frame, LAutoString &File, int &Line) = 0; virtual bool SetFrame(int Frame) = 0; virtual bool SetBreakPoint(BreakPoint *bp) = 0; virtual bool RemoveBreakPoint(BreakPoint *bp) = 0; virtual bool GetBreakPoints(LArray &bps) = 0; virtual bool GetVariables(bool Locals, LArray &vars, bool Detailed) = 0; virtual bool PrintObject(const char *Var, LStream *Output) = 0; virtual bool ReadMemory(LString &BaseAddr, int Length, LArray &OutBuf, LString *ErrorMsg = NULL) = 0; virtual bool GetRegisters(LStream *Out) = 0; virtual bool GetLocation(LAutoString &File, int &Line) = 0; virtual bool SetLocation(const char *File, int Line) = 0; virtual bool GetRunning() = 0; virtual bool SetRunning(bool Run) = 0; virtual bool StepInto() = 0; virtual bool StepOver() = 0; virtual bool StepOut() = 0; virtual bool Break(bool SuppressFileLine = false) = 0; virtual bool UserCommand(const char *Cmd) = 0; }; +#ifndef HAIKU extern LDebugger *CreateGdbDebugger(LStream *Log); +#endif #endif diff --git a/Ide/Code/DocEdit.cpp b/Ide/Code/DocEdit.cpp --- a/Ide/Code/DocEdit.cpp +++ b/Ide/Code/DocEdit.cpp @@ -1,512 +1,530 @@ #include "lgi/common/Lgi.h" #include "lgi/common/LgiRes.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "LgiIde.h" #include "DocEdit.h" #include "IdeDocPrivate.h" #define EDIT_TRAY_HEIGHT (LSysFont->GetHeight() + 10) #define EDIT_LEFT_MARGIN 16 // gutter for debug break points int DocEdit::LeftMarginPx = EDIT_LEFT_MARGIN; LAutoPtr GlobalFindReplace; DocEdit::DocEdit(IdeDoc *d, LFontType *f) : LTextView3(IDC_EDIT, 0, 0, 100, 100, f), DocEditStyling(this) { RefreshSize = 0; RefreshEdges = NULL; FileType = SrcUnknown; Doc = d; CurLine = -1; if (!GlobalFindReplace) { GlobalFindReplace.Reset(CreateFindReplaceParams()); } SetFindReplaceParams(GlobalFindReplace); CanScrollX = true; GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, (float)(LeftMarginPx + 2))); if (!f) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = Type.Create(); if (f) { #if defined LINUX f->PointSize(9); #elif defined WIN32 f->PointSize(8); #endif SetFont(f); } } } SetWrapType(TEXTED_WRAP_NONE); SetEnv(this); } DocEdit::~DocEdit() { ParentState = KExiting; Event.Signal(); while (!IsExited()) LSleep(1); SetEnv(0); } void DocEdit::OnCreate() { LTextView3::OnCreate(); Run(); } bool DocEdit::AppendItems(LSubMenu *Menu, const char *Param, int Base) { LSubMenu *Insert = Menu->AppendSub("Insert..."); if (Insert) { Insert->AppendItem("File Comment", IDM_FILE_COMMENT, Doc->GetProject() != 0); Insert->AppendItem("Function Comment", IDM_FUNC_COMMENT, Doc->GetProject() != 0); } return true; } -bool DocEdit::DoGoto() +void DocEdit::DoGoto(std::function Callback) { - LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); - if (Dlg.DoModal() != IDOK || !ValidStr(Dlg.GetStr())) - return false; - LString s = Dlg.GetStr(); - LString::Array p = s.SplitDelimit(":,"); - if (p.Length() == 2) + LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); + Dlg->DoModal([&, App=Doc->GetApp()](auto d, auto code) { - LString file = p[0]; - int line = (int)p[1].Int(); - Doc->GetApp()->GotoReference(file, line, false, true); - } - else if (p.Length() == 1) - { - int line = (int)p[0].Int(); - if (line > 0) - SetLine(line); - else - // Probably a filename with no line number.. - Doc->GetApp()->GotoReference(p[0], 1, false, true); - } - - return true; + bool status = code && ValidStr(Dlg->GetStr()); + if (status) + { + auto s = Dlg->GetStr(); + LString::Array p = s.SplitDelimit(":,"); + if (p.Length() == 2) + { + LString file = p[0]; + int line = (int)p[1].Int(); + App->GotoReference(file, line, false, true); + } + else if (p.Length() == 1) + { + int line = (int)p[0].Int(); + if (line > 0) + SetLine(line); + else + // Probably a filename with no line number.. + App->GotoReference(p[0], 1, false, true); + } + } + + if (Callback) + Callback(status); + + delete Dlg; + }); } bool DocEdit::SetPourEnabled(bool b) { bool e = PourEnabled; PourEnabled = b; if (PourEnabled) { PourText(0, Size); PourStyle(0, Size); } return e; } int DocEdit::GetTopPaddingPx() { return GetCss(true)->PaddingTop().ToPx(GetClient().Y(), GetFont()); } void DocEdit::InvalidateLine(int Idx) { LTextLine *Ln = LTextView3::Line[Idx]; if (Ln) { int PadPx = GetTopPaddingPx(); LRect r = Ln->r; r.Offset(0, -ScrollYPixel() + PadPx); // LgiTrace("%s:%i - r=%s\n", _FL, r.GetStr()); Invalidate(&r); } } + +void DocEdit::OnPaint(LSurface *pDC) +{ + LTextView3::OnPaint(pDC); + #if 0 + LRect cli = GetClient(); + pDC->Colour(LColour::Red); + pDC->Box(&cli); + + for (LViewI *p = GetParent(); p; p = p->GetParent()) + { + if (p == (LViewI*)GetWindow()) + break; + auto pos = p->GetPos(); + cli.Offset(pos.x1, pos.y1); + } + + LgiTrace("cli=%s\n", cli.GetStr()); + #endif +} + void DocEdit::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LColour GutterColour(0xfa, 0xfa, 0xfa); LTextView3::OnPaintLeftMargin(pDC, r, GutterColour); int Y = ScrollYLine(); int TopPaddingPx = GetTopPaddingPx(); pDC->Colour(LColour(200, 0, 0)); List::I it = LTextView3::Line.begin(Y); int DocOffset = (*it)->r.y1; for (LTextLine *l = *it; l; l = *++it, Y++) { if (Doc->d->BreakPoints.Find(Y+1)) { int r = l->r.Y() >> 1; pDC->FilledCircle(8, l->r.y1 + r + TopPaddingPx - DocOffset, r - 1); } } bool DocMatch = Doc->IsCurrentIp(); { // We have the current IP location it = LTextView3::Line.begin(); int Idx = 1; for (LTextLine *ln = *it; ln; ln = *++it, Idx++) { if (DocMatch && Idx == IdeDoc::CurIpLine) { ln->Back = LColour(L_DEBUG_CURRENT_LINE); } else { ln->Back.Empty(); } } } } void DocEdit::OnMouseClick(LMouse &m) { if (m.Button1()) { if (m.Down()) Doc->GetApp()->SeekHistory(-1); return; } else if (m.Button2()) { if (m.Down()) Doc->GetApp()->SeekHistory(1); return; } if (m.Down()) { if (HasSelection()) { MsClick.x = -100; MsClick.y = -100; } else { MsClick.x = m.x; MsClick.y = m.y; } } else if ( m.x < LeftMarginPx && abs(m.x - MsClick.x) < 5 && abs(m.y - MsClick.y) < 5 ) { // Margin click... work out the line int Y = (VScroll) ? (int)VScroll->Value() : 0; LFont *f = GetFont(); if (!f) return; LCss::Len PaddingTop = GetCss(true)->PaddingTop(); int TopPx = PaddingTop.ToPx(GetClient().Y(), f); int Idx = ((m.y - TopPx) / f->GetHeight()) + Y + 1; if (Idx > 0 && Idx <= LTextView3::Line.Length()) { Doc->OnMarginClick(Idx); } } LTextView3::OnMouseClick(m); } void DocEdit::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { LTextView3::SetCaret(i, Select, ForceFullUpdate); if (IsAttached()) { auto Line = (int)GetLine(); if (Line != CurLine) { Doc->OnLineChange(CurLine = Line); } } } char *DocEdit::TemplateMerge(const char *Template, const char *Name, List *Params) { // Parse template and insert into doc LStringPipe T; for (const char *t = Template; *t; ) { char *e = strstr((char*) t, "<%"); if (e) { // Push text before tag T.Push(t, e-t); char *TagStart = e; e += 2; skipws(e); char *Start = e; while (*e && isalpha(*e)) e++; // Get tag char *Tag = NewStr(Start, e-Start); if (Tag) { // Process tag if (Name && stricmp(Tag, "name") == 0) { T.Push(Name); } else if (Params && stricmp(Tag, "params") == 0) { char *Line = TagStart; while (Line > Template && Line[-1] != '\n') Line--; int i = 0; for (auto p: *Params) { if (i) T.Push(Line, TagStart-Line); T.Push(p); if (i < Params->Length()-1) T.Push("\n"); i++; } } DeleteArray(Tag); } e = strstr(e, "%>"); if (e) { t = e + 2; } else break; } else { T.Push(t); break; } } T.Push("\n"); return T.NewStr(); } bool DocEdit::GetVisible(LStyle &s) { LRect c = GetClient(); auto a = HitText(c.x1, c.y1, false); auto b = HitText(c.x2, c.y2, false); s.Start = a; s.Len = b - a + 1; return true; } bool DocEdit::Pour(LRegion &r) { LRect c = r.Bound(); + c.y2 -= EDIT_TRAY_HEIGHT; SetPos(c); return true; } bool DocEdit::Insert(size_t At, const char16 *Data, ssize_t Len) { int Old = PourEnabled ? CountRefreshEdges(At, 0) : 0; bool Status = LTextView3::Insert(At, Data, Len); int New = PourEnabled ? CountRefreshEdges(At, Len) : 0; if (Old != New) Invalidate(); return Status; } bool DocEdit::Delete(size_t At, ssize_t Len) { int Old = CountRefreshEdges(At, Len); bool Status = LTextView3::Delete(At, Len); int New = CountRefreshEdges(At, 0); if (Old != New) Invalidate(); return Status; } bool DocEdit::OnKey(LKey &k) { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif { // This allows the Alt+Left/Right to be processed by the prev/next navigator menu. if (k.vkey == LK_LEFT || k.vkey == LK_RIGHT) { return false; } } if (k.AltCmd()) { if (ToLower(k.c16) == 'm') { if (k.Down()) Doc->GotoSearch(IDC_METHOD_SEARCH); return true; } else if (ToLower(k.c16) == 'o' && k.Shift()) { if (k.Down()) Doc->GotoSearch(IDC_FILE_SEARCH); return true; } } return LTextView3::OnKey(k); } LMessage::Result DocEdit::OnEvent(LMessage *m) { switch (m->Msg()) { case M_STYLING_DONE: OnApplyStyles(); break; } return LTextView3::OnEvent(m); } bool DocEdit::OnMenu(LDocView *View, int Id, void *Context) { if (View) { switch (Id) { case IDM_FILE_COMMENT: { const char *Template = Doc->GetProject()->GetFileComment(); if (Template) { auto File = strrchr(Doc->GetFileName(), DIR_CHAR); if (File) { auto Comment = TemplateMerge(Template, File + 1, 0); if (Comment) { char16 *C16 = Utf8ToWide(Comment); DeleteArray(Comment); if (C16) { Insert(Cursor, C16, StrlenW(C16)); DeleteArray(C16); Invalidate(); } } } } break; } case IDM_FUNC_COMMENT: { const char *Template = Doc->GetProject()->GetFunctionComment(); if (ValidStr(Template)) { const char16 *n = NameW(); if (n) { List Tokens; char16 *s; char16 *p = (char16*)n + GetCaret(); char16 OpenBrac[] = { '(', 0 }; char16 CloseBrac[] = { ')', 0 }; ssize_t OpenBracketIndex = -1; // Parse from cursor to the end of the function defn while ((s = LexCpp(p, LexStrdup))) { if (StricmpW(s, OpenBrac) == 0) { OpenBracketIndex = Tokens.Length(); } Tokens.Insert(s); if (StricmpW(s, CloseBrac) == 0) { break; } } if (OpenBracketIndex > 0) { char *FuncName = WideToUtf8(Tokens[OpenBracketIndex-1]); if (FuncName) { // Get a list of parameter names List Params; for (auto i = OpenBracketIndex+1; (p = Tokens[i]); i++) { char16 Comma[] = { ',', 0 }; if (StricmpW(p, Comma) == 0 || StricmpW(p, CloseBrac) == 0) { char16 *Param = Tokens[i-1]; if (Param) { Params.Insert(WideToUtf8(Param)); } } } // Do insertion char *Comment = TemplateMerge(Template, FuncName, &Params); if (Comment) { char16 *C16 = Utf8ToWide(Comment); DeleteArray(Comment); if (C16) { Insert(Cursor, C16, StrlenW(C16)); DeleteArray(C16); Invalidate(); } } // Clean up DeleteArray(FuncName); Params.DeleteArrays(); } - else - { - LgiTrace("%s:%i - No function name.\n", _FL); - } + else LgiTrace("%s:%i - No function name.\n", _FL); } - else - { - LgiTrace("%s:%i - OpenBracketIndex not found.\n", _FL); - } + else LgiTrace("%s:%i - OpenBracketIndex not found.\n", _FL); Tokens.DeleteArrays(); } - else - { - LgiTrace("%s:%i - No input text.\n", _FL); - } + else LgiTrace("%s:%i - No input text.\n", _FL); } - else - { - LgiTrace("%s:%i - No template.\n", _FL); - } + else LgiTrace("%s:%i - No template.\n", _FL); break; } } } return true; } diff --git a/Ide/Code/DocEdit.h b/Ide/Code/DocEdit.h --- a/Ide/Code/DocEdit.h +++ b/Ide/Code/DocEdit.h @@ -1,198 +1,204 @@ #ifndef _DOC_EDIT_H_ #define _DOC_EDIT_H_ #include "lgi/common/TextView3.h" #include "IdeDoc.h" #define IDM_FILE_COMMENT 100 #define IDM_FUNC_COMMENT 101 enum SourceType { SrcUnknown, SrcPlainText, SrcCpp, SrcPython, SrcXml, SrcHtml, }; class DocEdit; class DocEditStyling : public LThread, public LMutex { public: enum DocType { CodeHtml, CodePhp, CodeCss, CodeComment, CodePre, CodeJavascript }; protected: SourceType FileType; - DocEdit *View; + DocEdit *View = NULL; enum WordType { KNone, KLang, KType }; enum ThreadState { KWaiting, KStyling, KCancel, KExiting, }; struct Node { const int Captials = 26; const int Numbers = Captials + 26; const int Symbols = Numbers + 10; Node *Next[26 + 26 + 10 + 1]; WordType Type; int Map(char16 c) { if (c >= 'a' && c <= 'z') return c - 'a'; if (c >= 'A' && c <= 'Z') return c - 'A' + Captials; if (IsDigit(c)) return c - '0' + Numbers; if (c == '_') return Symbols; // LAssert(0); return -1; } Node() { ZeroObj(Next); Type = KNone; } ~Node() { for (int i=0; i Text; LString FileName; LUnrolledList Styles; LTextView3::LStyle Visible; LTextView3::LStyle Dirty; StylingParams(LTextView3 *view) : Dirty(LTextView3::STYLE_NONE) { View = view; } void StyleString(char16 *&s, char16 *e, LColour c, LUnrolledList *Out = NULL) { if (!Out) Out = &Styles; auto &st = Out->New().Construct(View, LTextView3::STYLE_IDE); auto pText = Text.AddressOf(); st.Start = s - pText; st.Font = View->GetFont(); char16 Delim = *s++; while (s < e && *s != Delim) { if (*s == '\\') s++; s++; } st.Len = (s - pText) - st.Start + 1; st.Fore = c; } }; // Styling data... LThreadEvent Event; ThreadState ParentState, WorkerState; // Lock before access: StylingParams Params; // EndLock - // Thread only - Node Root; - LUnrolledList PrevStyle; - // End Thread only - - // Styling functions.. - int Main(); - void StyleCpp(StylingParams &p); - void StylePython(StylingParams &p); - void StyleDefault(StylingParams &p); - void StyleXml(StylingParams &p); - void StyleHtml(StylingParams &p); - void AddKeywords(const char **keys, bool IsType); // Full refresh triggers int RefreshSize; const char **RefreshEdges; + void AddKeywords(const char **keys, bool IsType); + +private: + // Thread only + + // Styling functions.. + int Main(); + void StyleCpp(StylingParams &p); + void StylePython(StylingParams &p); + void StyleDefault(StylingParams &p); + void StyleXml(StylingParams &p); + void StyleHtml(StylingParams &p); + + Node Root; + LUnrolledList PrevStyle; + // End Thread only + public: DocEditStyling(DocEdit *view); LColour ColourFromType(DocType t); }; class DocEdit : public LTextView3, public LDocumentEnv, public DocEditStyling { IdeDoc *Doc; int CurLine; LPoint MsClick; void OnApplyStyles(); int CountRefreshEdges(size_t At, ssize_t Len); public: static int LeftMarginPx; DocEdit(IdeDoc *d, LFontType *f); ~DocEdit(); const char *GetClass() override { return "DocEdit"; } const char *Name() override { return LTextView3::Name(); } bool Name(const char *s) override { return LTextView3::Name(s); } bool SetPourEnabled(bool b); int GetTopPaddingPx(); void InvalidateLine(int Idx); char *TemplateMerge(const char *Template, const char *Name, List *Params); bool GetVisible(LStyle &s); void OnCreate() override; // Overrides bool AppendItems(LSubMenu *Menu, const char *Param, int Base) override; - bool DoGoto() override; + void DoGoto(std::function Callback) override; void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) override; void OnMouseClick(LMouse &m) override; bool OnKey(LKey &k) override; bool OnMenu(LDocView *View, int Id, void *Context) override; LMessage::Result OnEvent(LMessage *m) override; void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) override; void PourStyle(size_t Start, ssize_t EditSize) override; bool Pour(LRegion &r) override; bool Insert(size_t At, const char16 *Data, ssize_t Len) override; bool Delete(size_t At, ssize_t Len) override; + + void OnPaint(LSurface *pDC); }; #endif diff --git a/Ide/Code/DocEditStyling.cpp b/Ide/Code/DocEditStyling.cpp --- a/Ide/Code/DocEditStyling.cpp +++ b/Ide/Code/DocEditStyling.cpp @@ -1,1240 +1,1248 @@ #include "lgi/common/Lgi.h" #include "LgiIde.h" #include "DocEdit.h" + #define COMP_STYLE 1 #define LOG_STYLE 0 #define PROFILE_STYLE 0 #if PROFILE_STYLE #define PROF(str) Prof.Add(str) #else #define PROF(str) #endif #define ColourComment LColour(0, 140, 0) #define ColourHashDef LColour(0, 0, 222) #define ColourLiteral LColour(192, 0, 0) #define ColourKeyword LColour::Black #define ColourType LColour(0, 0, 222) #define ColourCode LColour(140, 140, 180) #define ColourHtml LColour(80, 80, 255) #define ColourPre LColour(150, 110, 110) #define ColourStyle LColour(110, 110, 150) #define IsSymbolChar(ch) (IsAlpha(ch) || (ch) == '_') #define DetectKeyword() \ Node *n = &Root; \ char16 *e = s; \ do \ { \ int idx = n->Map(*e); \ if (idx < 0) \ break; \ n = n->Next[idx]; \ e++; \ } \ while (n); struct LanguageParams { const char **Keywords; const char **Types; const char **Edges; }; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CPP const char *DefaultKeywords[] = {"if", "elseif", "endif", "else", "ifeq", "ifdef", "ifndef", "ifneq", "include", NULL}; const char *CppKeywords[] = {"extern", "class", "struct", "static", "default", "case", "break", "switch", "new", "delete", "sizeof", "return", "enum", "else", "if", "for", "while", "do", "continue", "public", "private", "virtual", "protected", "friend", "union", "template", "typedef", "dynamic_cast", "inline", NULL}; const char *CppTypes[] = { "int", "char", "short", "long", "signed", "unsigned", "double", "float", "bool", "const", "void", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "char16", "wchar_t", "LArray", "GHashTbl", "List", "LString", "LAutoString", "LAutoWString", "LAutoPtr", "LHashTbl", NULL}; const char *CppEdges[] = { "/*", "*/", "\"", NULL }; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Python const char *PythonKeywords[] = {"def", "try", "except", "import", "if", "for", "elif", "else", "class", NULL}; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // XML + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // HTML const char *HtmlEdges[] = { "", "/*", "*/", "", "", "\"", "\'", NULL }; + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LanguageParams LangParam[] = { // Unknown {NULL, NULL, NULL}, // Plain text {DefaultKeywords, NULL, NULL}, // C/C++ {CppKeywords, CppTypes, CppEdges}, // Python {PythonKeywords, NULL, NULL}, // Xml {NULL, NULL, NULL}, // Html/Php {NULL, NULL, HtmlEdges} }; + DocEditStyling::DocEditStyling(DocEdit *view) : LThread("DocEditStyling.Thread"), LMutex("DocEditStyling.Lock"), View(view), Event("DocEditStyling.Event"), ParentState(KWaiting), WorkerState(KWaiting), Params(view) { } + void DocEdit::OnApplyStyles() { #if PROFILE_STYLE LProfile Prof("OnApplyStyles"); #endif PROF("Lock"); LTextView3::LStyle Vis(STYLE_NONE); GetVisible(Vis); if (DocEditStyling::Lock(_FL)) { PROF("Insert"); if (Params.Styles.Length()) { Style.Swap(Params.Styles); #if LOG_STYLE LgiTrace("Swapped in %i styles.\n", (int)Style.Length()); #endif PROF("Inval"); if (Params.Dirty.Start >= 0) { #if LOG_STYLE LgiTrace("Visible rgn: %i + %i = %i\n", Vis.Start, Vis.Len, Vis.End()); LgiTrace("Dirty rgn: %i + %i = %i\n", Params.Dirty.Start, Params.Dirty.Len, Params.Dirty.End()); #endif ssize_t CurLine = -1, DirtyStartLine = -1, DirtyEndLine = -1; GetTextLine(Cursor, &CurLine); LTextLine *Start = GetTextLine(Params.Dirty.Start, &DirtyStartLine); LTextLine *End = GetTextLine(MIN(Size, Params.Dirty.End()), &DirtyEndLine); if (CurLine >= 0 && DirtyStartLine >= 0 && DirtyEndLine >= 0) { #if LOG_STYLE LgiTrace("Dirty lines %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine); #endif if (DirtyStartLine != CurLine || DirtyEndLine != CurLine) { LRect c = GetClient(); LRect r(c.x1, Start->r.Valid() ? DocToScreen(Start->r).y1 : c.y1, c.x2, Params.Dirty.End() >= Vis.End() ? c.y2 : DocToScreen(End->r).y2); #if LOG_STYLE LgiTrace("Cli: %s, Start rgn: %s, End rgn: %s, Update: %s\n", c.GetStr(), Start->r.GetStr(), End->r.GetStr(), r.GetStr()); #endif Invalidate(&r); } } else { #if LOG_STYLE LgiTrace("No Change: %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine); #endif } } else { #if LOG_STYLE LgiTrace("Invalidate everything\n"); #endif Invalidate(); } } PROF("Unlock"); DocEditStyling::Unlock(); } } + int DocEditStyling::Main() { LThreadEvent::WaitStatus s; while (ParentState != KExiting && (s = Event.Wait()) == LThreadEvent::WaitSignaled) { StylingParams p(View); if (Lock(_FL)) { Params.Dirty.Empty(); Params.Styles.Empty(); p = Params; Unlock(); } WorkerState = KStyling; #if LOG_STYLE LgiTrace("DocEdit.Worker starting style...\n"); #endif switch (FileType) { case SrcCpp: StyleCpp(p); break; case SrcPython: StylePython(p); break; case SrcXml: StyleXml(p); break; case SrcHtml: StyleHtml(p); break; default: StyleDefault(p); break; } - if (ParentState != KCancel) - { - #if LOG_STYLE - LgiTrace("DocEdit.Worker finished style... Items=%i ParentState=%i\n", (int)p.Styles.Length(), ParentState); - #endif - auto r = View->PostEvent(M_STYLING_DONE); - if (ParentState != KExiting) - { - LAssert(r); - } - } - else - { - #if LOG_STYLE - LgiTrace("DocEdit.Worker canceled style...\n"); - #endif - } + if (LMutex::Lock(_FL)) { Params.Dirty = p.Dirty; Params.Styles.Swap(p.Styles); LMutex::Unlock(); } WorkerState = KWaiting; + + if (ParentState != KCancel && ParentState != KExiting) + { + #if LOG_STYLE + LgiTrace("DocEdit.Worker finished style... Items=%i ParentState=%i\n", (int)p.Styles.Length(), ParentState); + #endif + auto r = View->PostEvent(M_STYLING_DONE); + } } return 0; } void DocEditStyling::StyleCpp(StylingParams &p) { #if PROFILE_STYLE LProfile Prof("DocEdit::StyleCpp"); #endif if (!p.Text.Length()) return; char16 *Text = p.Text.AddressOf(); char16 *s = Text; char16 *e = s + p.Text.Length(); PROF("Scan"); LUnrolledList Out; for (; ParentState != KCancel && s < e; s++) { switch (*s) { case '\"': case '\'': p.StyleString(s, e, ColourLiteral, &Out); break; case '#': { // Check that the '#' is the first non-whitespace on the line bool IsWhite = true; for (char16 *w = s - 1; w >= Text && *w != '\n'; w--) { if (!IsWhiteSpace(*w)) { IsWhite = false; break; } } if (IsWhite) { auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); char LastNonWhite = 0; while (s < e) { if ( // Break at end of line (*s == '\n' && LastNonWhite != '\\') || // Or the start of a comment (*s == '/' && s[1] == '/') || (*s == '/' && s[1] == '*') ) break; if (!IsWhiteSpace(*s)) LastNonWhite = *s; s++; } st.Len = (s - Text) - st.Start; st.Fore = ColourHashDef; s--; } break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } case '/': { if (s[1] == '/') { auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } else if (s[1] == '*') { auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); s += 2; while (s < e && !(s[-2] == '*' && s[-1] == '/')) s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } break; } default: { wchar_t Ch = ToLower(*s); if (Ch >= 'a' && Ch <= 'z') { DetectKeyword(); if (n && n->Type) { auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; #ifdef MAC #warning "Weird mac bold issues fixme.") st.Font = View->GetFont(); #else st.Font = n->Type == KType ? View->GetFont() : View->GetBold(); #endif st.Len = e - s; st.Fore = n->Type == KType ? ColourType : ColourKeyword; } else { while ( IsAlpha(*e) || IsDigit(*e) || *e == '_') e++; } s = e - 1; } } } } #if COMP_STYLE PROF("Compare"); auto &Vis = p.Visible; if (Vis.Valid() && ParentState != KCancel && PrevStyle.Length()) { LArray Old, Cur; for (auto s : PrevStyle) { if (s.Overlap(Vis)) Old.Add(&s); else if (Old.Length()) break; } for (auto s : Out) { if (s.Overlap(Vis)) Cur.Add(&s); else if (Cur.Length()) break; } for (int o=0; oGetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } default: { wchar_t Ch = ToLower(*s); if (Ch >= 'a' && Ch <= 'z') { DetectKeyword(); if (n && n->Type) { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = n->Type == KType ? View->GetFont() : View->GetBold(); st.Len = e - s; st.Fore = n->Type == KType ? ColourType : ColourKeyword; } s = e - 1; } break; } } } } + void DocEditStyling::StyleDefault(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; for (char16 *s = Text; s < e && ParentState < KCancel; s++) { switch (*s) { case '\"': case '\'': case '`': { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); bool Quoted = s > Text && s[-1] == '\\'; st.Start = s - Text - Quoted; st.Font = View->GetFont(); char16 Delim = *s++; while ( s < e && *s != Delim && !(Delim == '`' && *s == '\'') ) { if (*s == '\\') { if (!Quoted || s[1] != Delim) s++; } else if (*s == '\n') break; s++; } st.Len = (s - Text) - st.Start + 1; st.Fore = ColourLiteral; break; } case '#': { // Single line comment auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); while (s < e && *s != '\n') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text - ((s > Text && strchr("-+", s[-1])) ? 1 : 0); st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } if (*s == '%') s++; st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } default: { if (*s >= 'a' && *s <= 'z') { /* if (HasKeyword[*s - 'a']) { static char16 buf[64], *o = buf; char16 *e = s; while (IsSymbolChar(*e)) *o++ = *e++; *o = 0; KeyworkType type = Keywords.Find(buf); if (type != KNone) { LAutoPtr st(new LTextView3::LStyle(STYLE_IDE)); if (st) { st->View = this; st->Start = s - Text; st->Font = Bold; st->Len = e - s; st->Fore = ColourKeyword; InsertStyle(st); s = e - 1; } } } */ } break; } } } } + void DocEditStyling::StyleXml(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; for (char16 *s = Text; s < e; s++) { switch (*s) { case '\"': case '\'': p.StyleString(s, e, ColourLiteral); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (s == Text || !IsSymbolChar(s[-1])) { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); bool IsHex = false; if (s[0] == '0' && ToLower(s[1]) == 'x') { s += 2; IsHex = true; } while (s < e) { if ( IsDigit(*s) || *s == '.' || ( IsHex && ( (*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'F') ) ) ) s++; else break; } st.Len = (s - Text) - st.Start; st.Fore = ColourLiteral; s--; } while (s < e - 1 && IsDigit(s[1])) s++; break; } case '<': { if (s[1] == '!' && s[2] == '-' && s[3] == '-') { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); s += 4; while ( s < e && ! ( s[-3] == '-' && s[-2] == '-' && s[-1] == '>' ) ) s++; st.Len = (s - Text) - st.Start; st.Fore = ColourComment; s--; } break; } default: { if (*s >= 'a' && *s <= 'z') { /* if (HasKeyword[*s - 'a']) { static char16 buf[64], *o = buf; char16 *e = s; while (IsSymbolChar(*e)) *o++ = *e++; *o = 0; KeyworkType type = Keywords.Find(buf); if (type != KNone) { LAutoPtr st(new LTextView3::LStyle(STYLE_IDE)); if (st) { st->View = this; st->Start = s - Text; st->Font = Bold; st->Len = e - s; st->Fore = ColourKeyword; InsertStyle(st); s = e - 1; } } } */ } break; } } } } + LColour DocEditStyling::ColourFromType(DocType t) { switch (t) { default: return LColour::Black; case CodePhp: case CodeJavascript: return ColourCode; case CodeCss: return ColourStyle; case CodePre: return ColourPre; case CodeComment: return ColourComment; } } + void DocEditStyling::StyleHtml(StylingParams &p) { char16 *Text = p.Text.AddressOf(); char16 *e = Text + p.Text.Length(); auto &Style = p.Styles; LString Ext = LGetExtension(p.FileName); DocType Type = CodeHtml; LTextView3::LStyle *Cur = NULL; #define START_CODE() \ if (Type != CodeHtml) \ { \ Cur = &Style.New().Construct(View, LTextView3::STYLE_IDE); \ Cur->Start = s - Text; \ Cur->Font = View->GetFont(); \ Cur->Fore = ColourFromType(Type); \ } #define END_CODE() \ if (Cur) \ { \ Cur->Len = (s - Text) - Cur->Start; \ if (Cur->Len <= 0) \ Style.Delete(Style.rbegin()); \ Cur = NULL; \ } char16 *s = Text; bool IsJavascript = Ext.Equals("js"); bool IsPHP = Ext.Equals("php"); bool IsCSS = Ext.Equals("css"); if (IsJavascript) { Type = CodeJavascript; START_CODE(); } else if (IsPHP) { Type = CodePhp; START_CODE(); } else if (IsCSS) { Type = CodeCss; START_CODE(); } for (s = Text; s < e; s++) { switch (*s) { case '`': { if (Type == CodeJavascript) { END_CODE(); p.StyleString(s, e, ColourLiteral); s++; START_CODE(); s--; } break; } case '\'': { if (s > Text && IsAlpha(s[-1])) break; // else fall through } case '\"': { if (Type != CodeComment) { END_CODE(); p.StyleString(s, e, ColourLiteral); s++; START_CODE(); s--; } break; } case '/': { if (Type == CodeJavascript || Type == CodePhp || Type == CodeCss) { if (s[1] == '/') { END_CODE(); char16 *nl = Strchr(s, '\n'); if (!nl) nl = s + Strlen(s); auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourComment; st.Len = nl - s; s = nl; START_CODE(); } else if (s[1] == '*') { END_CODE(); char16 *end_comment = Stristr(s, L"*/"); if (!end_comment) end_comment = s + Strlen(s); else end_comment += 2; auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourComment; st.Len = end_comment - s; s = end_comment; START_CODE(); } } break; } case '<': { #define IS_TAG(name) \ ((tmp = strlen(#name)) && \ len == tmp && \ !Strnicmp(tag, L ## #name, tmp)) #define SCAN_TAG() \ char16 *tag = s + 1; \ while (tag < e && strchr(WhiteSpace, *tag)) tag++; \ char16 *c = tag; \ while (c < e && (IsAlpha(*c) || strchr("!_/0123456789", *c))) c++; \ size_t len = c - tag, tmp; if (Type == CodeHtml) { if (s[1] == '?' && s[2] == 'p' && s[3] == 'h' && s[4] == 'p') { // Start PHP block Type = CodePhp; START_CODE(); s += 4; } else if ( s[1] == '!' && s[2] == '-' && s[3] == '-') { // Start comment Type = CodeComment; START_CODE(); s += 3; } else { // Html element SCAN_TAG(); bool start = false; if (IS_TAG(pre)) { Type = CodePre; while (*c && *c != '>') c++; if (*c) c++; start = true; } else if (IS_TAG(style)) { Type = CodeCss; while (*c && *c != '>') c++; if (*c) c++; start = true; } else if (IS_TAG(script)) { Type = CodeJavascript; while (c < e && *c != '>') c++; if (c < e) c++; start = true; } auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourHtml; st.Len = c - s; s = c - 1; if (start) { s++; START_CODE(); s--; } } } else if (Type == CodePre) { SCAN_TAG(); if (IS_TAG(/pre)) { END_CODE(); Type = CodeHtml; s--; } } else if (Type == CodeCss) { SCAN_TAG(); if (IS_TAG(/style)) { END_CODE(); Type = CodeHtml; s--; } } break; } case '>': { if (Type == CodeHtml) { auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourHtml; st.Len = 1; } else if (Type == CodeComment) { if (s - 2 >= Text && s[-1] == '-' && s[-2] == '-') { s++; END_CODE(); Type = CodeHtml; } } else if (Type == CodeJavascript) { // Check for if (!Strnicmp(s - 8, L"", 9)) { s -= 8; END_CODE(); auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE); st.Start = s - Text; st.Font = View->GetFont(); st.Fore = ColourHtml; st.Len = 9; s += 9; Type = CodeHtml; } } break; } case '?': { if (Type == CodePhp && s[1] == '>') { Type = CodeHtml; s += 2; END_CODE(); s--; } break; } } } END_CODE(); } void DocEditStyling::AddKeywords(const char **keys, bool IsType) { for (const char **k = keys; *k; k++) { Node *n = &Root; for (const char *c = *k; *c && n; c++) { int idx = n->Map(*c); LAssert(idx >= 0); if (!n->Next[idx]) n->Next[idx] = new Node; n = n->Next[idx]; } if (n) n->Type = IsType ? KType : KLang; } } + void DocEdit::PourStyle(size_t Start, ssize_t EditSize) { if (FileType == SrcUnknown) { char *Ext = LGetExtension(Doc->GetFileName()); if (!Ext) FileType = SrcPlainText; else if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "mm") || !stricmp(Ext, "cc") || !stricmp(Ext, "h") || !stricmp(Ext, "hpp") || !stricmp(Ext, "java") ) FileType = SrcCpp; else if (!stricmp(Ext, "py")) FileType = SrcPython; else if (!stricmp(Ext, "xml")) FileType = SrcXml; else if (!stricmp(Ext, "html") || !stricmp(Ext, "htm") || !stricmp(Ext, "php") || !stricmp(Ext, "js")) FileType = SrcHtml; else FileType = SrcPlainText; if (LangParam[FileType].Keywords) AddKeywords(LangParam[FileType].Keywords, false); if (LangParam[FileType].Types) AddKeywords(LangParam[FileType].Types, true); if (LangParam[FileType].Edges) { RefreshEdges = LangParam[FileType].Edges; RefreshSize = 0; for (const char **e = RefreshEdges; *e; e++) RefreshSize = MAX((int)strlen(*e), RefreshSize); } } // Get into the waiting mode (so the worker is not using 'StyleIn' data) if (WorkerState == KStyling) { #ifdef _DEBUG uint64 Start = LgiMicroTime(); #endif ParentState = KCancel; while (WorkerState != KWaiting) LSleep(1); #ifdef _DEBUG uint64 Tm = LgiMicroTime() - Start; if (Tm >= 10000) LgiTrace("DocEdit: Styling->Waiting time: %.1g ms\n", (double) Tm / 1000.0); #endif } // Create the StyleIn array from the current document // Lock the object and give the text to the worker #if LOG_STYLE LgiTrace("DocEdit starting style...\n"); #endif ParentState = KStyling; if (DocEditStyling::Lock(_FL)) { Params.PourStart = Start; Params.PourSize = EditSize; Params.Dirty.Empty(); Params.Text.Length(Size); Params.FileName = Doc->GetFileName(); GetVisible(Params.Visible); memcpy(Params.Text.AddressOf(), Text, sizeof(*Text) * Size); DocEditStyling::Unlock(); Event.Signal(); } } + int DocEdit::CountRefreshEdges(size_t At, ssize_t Len) { if (!RefreshEdges) return 0; size_t s = MAX(0, At - (RefreshSize - 1)); bool t[256] = {0}; for (const char **Edge = RefreshEdges; *Edge; Edge++) { const char *e = *Edge; t[(int)e[0]] = true; } int Edges = 0; for (size_t i = s; i <= At; i++) { if (Text[i] < 256 && t[Text[i]]) { for (const char **Edge = RefreshEdges; *Edge; Edge++) { auto n = i; const char *e; for (e = *Edge; *e; e++) if (Text[n++] != *e) break; if (!*e) Edges++; } } } return Edges; } \ No newline at end of file diff --git a/Ide/Code/FindInFiles.cpp b/Ide/Code/FindInFiles.cpp --- a/Ide/Code/FindInFiles.cpp +++ b/Ide/Code/FindInFiles.cpp @@ -1,467 +1,467 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" #include "lgi/common/Token.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/TextLabel.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #define DEBUG_HIST 0 #define DEBUG_SEARCH 0 ///////////////////////////////////////////////////////////////////////////////////// FindInFiles::FindInFiles(AppWnd *app, FindParams *params) { TypeHistory = 0; FolderHistory = 0; SetParent(App = app); if (params) { Params = params; OwnParams = false; } else { Params = new FindParams; OwnParams = true; } if (LoadFromResource(IDD_FIND_IN_FILES)) { auto r = GetPos(); auto Scale = (float)LScreenDpi().x / 96.0; r.x2 = (int)(r.x2 * Scale); r.y2 = (int)(r.y2 * Scale); SetPos(r); MoveToCenter(); if (GetViewById(IDC_TYPE_HISTORY, TypeHistory)) TypeHistory->SetTargetId(IDC_FILE_TYPES); if (GetViewById(IDC_FOLDER_HISTORY, FolderHistory)) FolderHistory->SetTargetId(IDC_DIR); } } FindInFiles::~FindInFiles() { if (OwnParams) DeleteObj(Params); } void SerializeHistory(LHistory *h, const char *opt, LOptionsFile *p, bool Write) { if (h && p) { LString last; last.Printf("%sSelect", opt); LViewI *Edit = NULL; h->GetWindow()->GetViewById(h->GetTargetId(), Edit); #if DEBUG_HIST LgiTrace("%s:%i - SerializeHistory '%s', Write=%i\n", _FL, last.Get(), Write); #endif LVariant v; if (Write) { LString::Array a; a.SetFixedLength(false); int64 i = 0; int64 Selected = h->Value(); LString EdTxt; if (Edit) EdTxt = Edit->Name(); for (auto s: *h) { if (EdTxt && EdTxt.Equals(s)) { Selected = i; } a.Add(s); #if DEBUG_HIST LgiTrace("\t[%i]='%s'\n", i, s); #endif i++; } LString strs = LString(OptFileSeparator).Join(a); p->SetValue(opt, v = strs.Get()); #if DEBUG_HIST LgiTrace("\tstrs='%s'\n", strs.Get()); #endif p->SetValue(last, v = Selected); #if DEBUG_HIST LgiTrace("\tv=%i\n", v.CastInt32()); #endif } else { if (p->GetValue(opt, v)) { LString raw(v.Str()); LString::Array t = raw.Split("|"); h->DeleteArrays(); for (unsigned i=0; iInsert(NewStr(t[i])); #if DEBUG_HIST LgiTrace("\t[%i]='%s'\n", i, t[i].Get()); #endif } h->Update(); if (p->GetValue(last, v)) { h->Value(v.CastInt64()); #if DEBUG_HIST LgiTrace("\tValue=%i\n", v.CastInt32()); #endif } else LgiTrace("%s:%i - No option '%s'\n", _FL, last.Get()); } } } } void FindInFiles::OnCreate() { if (Params) { SetCtrlValue(IDC_ENTIRE_SOLUTION, Params->Type == FifSearchSolution); SetCtrlValue(IDC_SEARCH_DIR, Params->Type == FifSearchDirectory); SetCtrlName(IDC_LOOK_FOR, Params->Text); SetCtrlName(IDC_FILE_TYPES, Params->Ext); SetCtrlName(IDC_DIR, Params->Dir); SetCtrlValue(IDC_WHOLE_WORD, Params->MatchWord); SetCtrlValue(IDC_CASE, Params->MatchCase); SetCtrlValue(IDC_SUB_DIRS, Params->SubDirs); SerializeHistory(TypeHistory, "TypeHist", App->GetOptions(), false); SerializeHistory(FolderHistory, "FolderHist", App->GetOptions(), false); LEdit *v; if (GetViewById(IDC_LOOK_FOR, v)) { v->Focus(true); v->SelectAll(); } } } int FindInFiles::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_SET_DIR: { LFileSelect s; s.Parent(this); s.InitialDir(GetCtrlName(IDC_DIR)); - if (s.OpenFolder()) + s.OpenFolder([&](auto fs, auto ok) { int Idx = FolderHistory->Add(s.Name()); if (Idx >= 0) FolderHistory->Value(Idx); else SetCtrlName(IDC_DIR, s.Name()); - } + }); break; } case IDC_ENTIRE_SOLUTION: case IDC_SEARCH_DIR: { bool SearchSol = GetCtrlValue(IDC_ENTIRE_SOLUTION) != 0; SetCtrlEnabled(IDC_DIR, !SearchSol); SetCtrlEnabled(IDC_SET_DIR, !SearchSol); SetCtrlEnabled(IDC_FOLDER_HISTORY, !SearchSol); SetCtrlEnabled(IDC_SUB_DIRS, !SearchSol); break; } case IDOK: case IDCANCEL: { Params->Type = GetCtrlValue(IDC_ENTIRE_SOLUTION) != 0 ? FifSearchSolution : FifSearchDirectory; Params->Text = GetCtrlName(IDC_LOOK_FOR); Params->Ext = GetCtrlName(IDC_FILE_TYPES); Params->Dir = GetCtrlName(IDC_DIR); Params->MatchWord = GetCtrlValue(IDC_WHOLE_WORD); Params->MatchCase = GetCtrlValue(IDC_CASE); Params->SubDirs = GetCtrlValue(IDC_SUB_DIRS); if (TypeHistory) TypeHistory->Add(Params->Ext); SerializeHistory(TypeHistory, "TypeHist", App->GetOptions(), true); if (FolderHistory) FolderHistory->Add(Params->Dir); SerializeHistory(FolderHistory, "FolderHist", App->GetOptions(), true); EndModal(v->GetId() == IDOK); break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////////// class FindInFilesThreadPrivate { public: int AppHnd; LAutoPtr Params; bool Loop, Busy; LStringPipe Pipe; int64 Last; }; FindInFilesThread::FindInFilesThread(int AppHnd) : LEventTargetThread("FindInFiles") { d = new FindInFilesThreadPrivate; d->AppHnd = AppHnd; d->Loop = true; d->Busy = false; d->Last = 0; } FindInFilesThread::~FindInFilesThread() { d->Loop = false; EndThread(); DeleteObj(d); } void FindInFilesThread::Log(const char *Str) { PostThreadEvent(d->AppHnd, M_APPEND_TEXT, (LMessage::Param)Str, AppWnd::FindTab); } void FindInFilesThread::SearchFile(char *File) { if (!File || !ValidStr(d->Params->Text)) return; LFile f; if (!f.Open(File, O_READ)) { LString s; s.Printf("Couldn't Read file '%s'\n", File); Log(NewStr(s)); return; } auto Doc = f.Read(); if (!Doc) return; auto Len = d->Params->Text.Length(); const char *Text = d->Params->Text; char *LineStart = 0; int Line = 0; for (char *s = Doc; *s && d->Loop; s++) { if (*s == '\n') { Line++; LineStart = 0; } else { if (!LineStart) LineStart = s; bool Match = false; if (d->Params->MatchCase) { if (Text[0] == *s) { Match = strncmp(s, Text, Len) == 0; } } else { if (toupper(Text[0]) == toupper(*s)) { Match = strnicmp(s, Text, Len) == 0; } } if (Match) { char *Eol = s + Len; while (*Eol && *Eol != '\n') Eol++; auto LineLen = Eol - LineStart; bool StartOk = true; bool EndOk = true; if (d->Params->MatchWord) { if (s > Doc) { StartOk = IsWordBoundry(s[-1]); } EndOk = IsWordBoundry(s[Len]); } if (StartOk && EndOk) { static char Buf[1024]; int Chars = snprintf(Buf, sizeof(Buf), "%s:%i:%.*s\n", File, Line + 1, (int)LineLen, LineStart); d->Pipe.Push(Buf, Chars); int64 Now = LCurrentTime(); if (Now > d->Last + 500) Log(d->Pipe.NewStr()); } s = Eol - 1; } } } } void FindInFilesThread::Stop() { d->Loop = false; while (d->Busy) LSleep(1); } LMessage::Result FindInFilesThread::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_START_SEARCH: { d->Params.Reset((FindParams*)Msg->A()); if (!d->AppHnd || !d->Params || !ValidStr(d->Params->Text)) { LAssert(!"Invalid params."); break; } char Msg[256]; PostThreadEvent(d->AppHnd, M_SELECT_TAB, AppWnd::FindTab); d->Loop = true; d->Busy = true; snprintf(Msg, sizeof(Msg), "Searching for '%s'...\n", d->Params->Text.Get()); Log(NULL); Log(NewStr(Msg)); LArray Ext; LToken e(d->Params->Ext, ";, "); for (int i=0; i Files; if (d->Params->Type == FifSearchSolution) { // Do the extension filtering... for (auto p: d->Params->ProjectFiles) { if (p) { const char *Leaf = LGetLeaf(p); for (auto e: Ext) { if (MatchStr(e, Leaf)) { Files.Add(NewStr(p)); break; } } } else LgiTrace("%s:%i - Null string in project files array.\n", _FL); } } else { // Find the files recursively... LRecursiveFileSearch(d->Params->Dir, &Ext, &Files, NULL, NULL, [](auto Path, auto Dir) { if (Dir->IsDir()) { char *p = Dir->GetName(); if ( p && ( stricmp(p, ".svn") == 0 || stricmp(p, ".git") == 0 ) ) return false; } return true; }); } if (Files.Length() > 0) { sprintf(Msg, "in %i files...\n", (int)Files.Length()); Log(NewStr(Msg)); for (auto f: Files) { if (!d->Loop) break; if (f) { auto Dir = strrchr(f, DIR_CHAR); if (!Dir || Dir[1] != '.') { #if DEBUG_SEARCH LgiTrace("Searching '%s'\n", f); #endif SearchFile(f); } #if DEBUG_SEARCH else LgiTrace("Skipping '%s'\n", f); #endif } else LgiTrace("%s:%i - NULL Ptr in list????", _FL); } auto Str = d->Pipe.NewStr(); if (Str) Log(Str); Files.DeleteArrays(); Log(NewStr("Done.\n")); } else { Log(NewStr("No files matched.\n")); } d->Busy = false; break; } } return 0; } diff --git a/Ide/Code/FindSymbol.cpp b/Ide/Code/FindSymbol.cpp --- a/Ide/Code/FindSymbol.cpp +++ b/Ide/Code/FindSymbol.cpp @@ -1,681 +1,681 @@ #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 "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; + int MissingFiles = 0; LArray IncPaths; #if USE_HASH LHashTbl, FileSyms*> Files; #else LArray Files; #endif - uint32_t Tasks; - uint64 MsgTs; - bool DoingProgress; + int Tasks = 0; + uint64 MsgTs = 0; + bool DoingProgress = false; 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 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()); + int Remaining = (int)(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; } -FindSymResult FindSymbolSystem::OpenSearchDlg(LViewI *Parent) +void FindSymbolSystem::OpenSearchDlg(LViewI *Parent, std::function Callback) { - FindSymbolDlg Dlg(Parent, this); - Dlg.DoModal(); - return Dlg.Result; + 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/FindSymbol.h b/Ide/Code/FindSymbol.h --- a/Ide/Code/FindSymbol.h +++ b/Ide/Code/FindSymbol.h @@ -1,122 +1,122 @@ #ifndef _FIND_SYMBOL_H_ #define _FIND_SYMBOL_H_ #include "lgi/common/EventTargetThread.h" struct FindSymResult { LString Symbol, File; int Line; int Score; FindSymResult(const FindSymResult &c) { *this = c; } FindSymResult(const char *f = NULL, int line = 0) { Score = 0; File = f; Line = line; } FindSymResult &operator =(const FindSymResult &c) { Score = c.Score; Symbol = c.Symbol; File = c.File; Line = c.Line; return *this; } int Class() { if (Score > 0) return 0; char *e = LGetExtension(File); if (!e) return 3; if (!_stricmp(e, "h") || !_stricmp(e, "hpp") || !_stricmp(e, "hxx")) return 1; return 2; } // Sort symbol results by: int Compare(FindSymResult *r) { // Score first... if (Score != r->Score) return r->Score - Score; // Then headers... int c1 = Class(); int c2 = r->Class(); if (c1 != c2) return c1 - c2; // Then by file name... int c = _stricmp(File, r->File); if (c) return c; // Then by line number... return Line - r->Line; } }; struct FindSymRequest { int SinkHnd; LString Str; LArray Results; FindSymRequest(int sinkhnd) { SinkHnd = sinkhnd; } ~FindSymRequest() { Results.DeleteObjects(); } }; class FindSymbolSystem { struct FindSymbolSystemPriv *d; public: enum SymAction { FileAdd, FileRemove, FileReparse, FileRemoveAll, }; struct SymFileParams { SymAction Action; LString File; int Platforms; }; FindSymbolSystem(int AppHnd); ~FindSymbolSystem(); bool SetIncludePaths(LString::Array &Paths); bool OnFile(const char *Path, SymAction Action, int Platforms); - FindSymResult OpenSearchDlg(LViewI *Parent); + void OpenSearchDlg(LViewI *Parent, std::function Callback); /// This function searches the database for symbols and returns /// the results as a M_FIND_SYM_REQUEST message. void Search(int ResultsSinkHnd, const char *SearchStr, bool AllPlat); }; #endif \ No newline at end of file diff --git a/Ide/Code/IdeDoc.cpp b/Ide/Code/IdeDoc.cpp --- a/Ide/Code/IdeDoc.cpp +++ b/Ide/Code/IdeDoc.cpp @@ -1,2082 +1,2119 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/Net.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Edit.h" #include "lgi/common/List.h" #include "lgi/common/PopupList.h" #include "lgi/common/TableLayout.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Http.h" #include "lgi/common/Menu.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #include "ProjectNode.h" #include "SpaceTabConv.h" #include "DocEdit.h" #include "IdeDocPrivate.h" const char *Untitled = "[untitled]"; // static const char *White = " \r\t\n"; #define USE_OLD_FIND_DEFN 1 #define POPUP_WIDTH 700 // px #define POPUP_HEIGHT 350 // px enum { IDM_COPY_FILE = 1100, IDM_COPY_PATH, IDM_BROWSE }; int FileNameSorter(char **a, char **b) { char *A = strrchr(*a, DIR_CHAR); char *B = strrchr(*b, DIR_CHAR); return stricmp(A?A:*a, B?B:*b); } EditTray::EditTray(LTextView3 *ctrl, IdeDoc *doc) { Ctrl = ctrl; Doc = doc; Line = Col = 0; FuncBtn.ZOff(-1, -1); SymBtn.ZOff(-1, -1); int Ht = LSysFont->GetHeight() + 6; AddView(FileSearch = new LEdit(IDC_FILE_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); AddView(FuncSearch = new LEdit(IDC_METHOD_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); AddView(SymSearch = new LEdit(IDC_SYMBOL_SEARCH, 0, 0, EDIT_CTRL_WIDTH, Ht)); AddView(AllPlatforms = new LCheckBox(IDC_ALL_PLATFORMS, 0, 0, 20, Ht, "All Platforms")); } EditTray::~EditTray() { } void EditTray::GotoSearch(int CtrlId, char *InitialText) { if (FileSearch && FileSearch->GetId() == CtrlId) { FileSearch->Name(InitialText); FileSearch->Focus(true); if (InitialText) FileSearch->SendNotify(LNotifyDocChanged); } if (FuncSearch && FuncSearch->GetId() == CtrlId) { FuncSearch->Name(InitialText); FuncSearch->Focus(true); if (InitialText) FuncSearch->SendNotify(LNotifyDocChanged); } if (SymSearch && SymSearch->GetId() == CtrlId) { SymSearch->Name(InitialText); SymSearch->Focus(true); if (InitialText) SymSearch->SendNotify(LNotifyDocChanged); } } void EditTray::OnCreate() { AttachChildren(); } int MeasureText(const char *s) { LDisplayString Ds(LSysFont, s); return Ds.X(); } #define HEADER_BTN_LABEL "h" #define FUNCTION_BTN_LABEL "{ }" #define SYMBOL_BTN_LABEL "s" void EditTray::OnPosChange() { LLayoutRect c(this, 2); c.Left(FileBtn, MeasureText(HEADER_BTN_LABEL)+10); if (FileSearch) c.Left(FileSearch, EDIT_CTRL_WIDTH); c.x1 += 8; c.Left(FuncBtn, MeasureText(FUNCTION_BTN_LABEL)+10); if (FuncSearch) c.Left(FuncSearch, EDIT_CTRL_WIDTH); c.x1 += 8; c.Left(SymBtn, MeasureText(SYMBOL_BTN_LABEL)+10); if (SymSearch) c.Left(SymSearch, EDIT_CTRL_WIDTH); c.x1 += 8; if (AllPlatforms) { LViewLayoutInfo Inf; AllPlatforms->OnLayout(Inf); c.Left(AllPlatforms, Inf.Width.Max); } c.Remaining(TextMsg); } void EditTray::OnPaint(LSurface *pDC) { LRect c = GetClient(); pDC->Colour(L_MED); pDC->Rectangle(); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(true); LString s; s.Printf("Cursor: %i,%i", Col, Line + 1); { LDisplayString ds(LSysFont, s); ds.Draw(pDC, TextMsg.x1, TextMsg.y1 + ((c.Y()-TextMsg.Y())/2), &TextMsg); } LRect f = FileBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, HEADER_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } f = FuncBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, FUNCTION_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } f = SymBtn; LThinBorder(pDC, f, DefaultRaisedEdge); { LDisplayString ds(LSysFont, SYMBOL_BTN_LABEL); ds.Draw(pDC, f.x1 + 4, f.y1); } } bool EditTray::Pour(LRegion &r) { LRect *c = FindLargest(r); if (c) { LRect n = *c; SetPos(n); return true; } return false; } void EditTray::OnHeaderList(LMouse &m) { // Header list button LArray Paths; if (Doc->BuildIncludePaths(Paths, PlatformCurrent, false)) { LArray Headers; if (Doc->BuildHeaderList(Ctrl->NameW(), Headers, Paths)) { // Sort them.. Headers.Sort(FileNameSorter); LSubMenu *s = new LSubMenu; if (s) { // Construct the menu LHashTbl, int> Map; int DisplayLines = GdcD->Y() / LSysFont->GetHeight(); if (Headers.Length() > (0.9 * DisplayLines)) { LArray Letters[26]; LArray Other; for (int i=0; i 1) { char *First = LGetLeaf(Letters[i][0]); char *Last = LGetLeaf(Letters[i].Last()); char Title[256]; sprintf_s(Title, sizeof(Title), "%s - %s", First, Last); LSubMenu *sub = s->AppendSub(Title); if (sub) { for (int n=0; n 0); sub->AppendItem(LGetLeaf(h), Id, true); } } } else if (Letters[i].Length() == 1) { char *h = Letters[i][0]; int Id = Map.Find(h); LAssert(Id > 0); s->AppendItem(LGetLeaf(h), Id, true); } } if (Other.Length() > 0) { for (int n=0; n 0); s->AppendItem(LGetLeaf(h), Id, true); } } } else { for (int i=0; i 0) s->AppendItem(LGetLeaf(h), Id, true); else LgiTrace("%s:%i - Failed to get id for '%s' (map.len=%i)\n", _FL, h, Map.Length()); } if (!Headers.Length()) { s->AppendItem("(none)", 0, false); } } // Show the menu LPoint p(m.x, m.y); PointToScreen(p); int Goto = s->Float(this, p.x, p.y, true); if (Goto > 0) { char *File = Headers[Goto-1]; if (File) { // Open the selected file Doc->GetProject()->GetApp()->OpenFile(File); } } DeleteObj(s); } } // Clean up memory Headers.DeleteArrays(); } else { LgiTrace("%s:%i - No include paths set.\n", _FL); } } void EditTray::OnFunctionList(LMouse &m) { LArray Funcs; if (BuildDefnList(Doc->GetFileName(), (char16*)Ctrl->NameW(), Funcs, DefnNone /*DefnFunc | DefnClass*/)) { LSubMenu s; LArray a; int ScreenHt = GdcD->Y(); int ScreenLines = ScreenHt / LSysFont->GetHeight(); float Ratio = ScreenHt ? (float)(LSysFont->GetHeight() * Funcs.Length()) / ScreenHt : 0.0f; bool UseSubMenus = Ratio > 0.9f; int Buckets = UseSubMenus ? (int)(ScreenLines * 0.9) : 1; int BucketSize = MAX(2, (int)Funcs.Length() / Buckets); LSubMenu *Cur = NULL; for (unsigned n=0; nType != DefnEnumValue) { for (char *k = i->Name; *k && o < Buf+sizeof(Buf)-8; k++) { if (*k == '&') { *o++ = '&'; *o++ = '&'; } else if (*k == '\t') { *o++ = ' '; } else { *o++ = *k; } } *o++ = 0; a[n] = i; if (UseSubMenus) { if (!Cur || n % BucketSize == 0) { LString SubMsg; SubMsg.Printf("%s...", Buf); Cur = s.AppendSub(SubMsg); } if (Cur) Cur->AppendItem(Buf, n+1, true); } else { s.AppendItem(Buf, n+1, true); } } } LPoint p(m.x, m.y); PointToScreen(p); int Goto = s.Float(this, p.x, p.y, true); if (Goto) { DefnInfo *Info = a[Goto-1]; if (Info) { Ctrl->SetLine(Info->Line); } } } else { LgiTrace("%s:%i - No functions in input.\n", _FL); } } void EditTray::OnSymbolList(LMouse &m) { LAutoString s(Ctrl->GetSelection()); if (s) { LAutoWString sw(Utf8ToWide(s)); if (sw) { #if USE_OLD_FIND_DEFN List Matches; Doc->FindDefn(sw, Ctrl->NameW(), Matches); #else LArray Matches; Doc->GetApp()->FindSymbol(s, Matches); #endif LSubMenu *s = new LSubMenu; if (s) { // Construct the menu int n=1; #if USE_OLD_FIND_DEFN for (auto Def: Matches) { char m[512]; char *d = strrchr(Def->File, DIR_CHAR); sprintf(m, "%s (%s:%i)", Def->Name.Get(), d ? d + 1 : Def->File.Get(), Def->Line); s->AppendItem(m, n++, true); } #else for (int i=0; iAppendItem(m, n++, true); } #endif if (!Matches.Length()) { s->AppendItem("(none)", 0, false); } // Show the menu LPoint p(m.x, m.y); PointToScreen(p); int Goto = s->Float(this, p.x, p.y, true); if (Goto) { #if USE_OLD_FIND_DEFN DefnInfo *Def = Matches[Goto-1]; #else FindSymResult *Def = &Matches[Goto-1]; #endif { // Open the selected symbol if (Doc->GetProject() && Doc->GetProject()->GetApp()) { AppWnd *App = Doc->GetProject()->GetApp(); IdeDoc *Doc = App->OpenFile(Def->File); if (Doc) { Doc->SetLine(Def->Line, false); } else { char *f = Def->File; LgiTrace("%s:%i - Couldn't open doc '%s'\n", _FL, f); } } else { LgiTrace("%s:%i - No project / app ptr.\n", _FL); } } } DeleteObj(s); } } } else { LSubMenu *s = new LSubMenu; if (s) { s->AppendItem("(No symbol currently selected)", 0, false); LPoint p(m.x, m.y); PointToScreen(p); s->Float(this, p.x, p.y, true); DeleteObj(s); } } } void EditTray::OnMouseClick(LMouse &m) { if (m.Left() && m.Down()) { if (FileBtn.Overlap(m.x, m.y)) { OnHeaderList(m); } else if (FuncBtn.Overlap(m.x, m.y)) { OnFunctionList(m); } else if (SymBtn.Overlap(m.x, m.y)) { OnSymbolList(m); } } } class ProjMethodPopup : public LPopupList { AppWnd *App; public: LArray All; ProjMethodPopup(AppWnd *app, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; } LString ToString(DefnInfo *Obj) { return Obj->Name; } void OnSelect(DefnInfo *Obj) { App->GotoReference(Obj->File, Obj->Line, false); } bool Name(const char *s) { LString InputStr = s; LString::Array p = InputStr.SplitDelimit(" \t"); LArray Matching; for (unsigned i=0; iName, p[n])) { Match = false; break; } } if (Match) Matching.Add(Def); } return SetItems(Matching); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { Name(Edit->Name()); } return LPopupList::OnNotify(Ctrl, n); } }; class ProjSymPopup : public LPopupList { AppWnd *App; IdeDoc *Doc; int CommonPathLen; public: LArray All; ProjSymPopup(AppWnd *app, IdeDoc *doc, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; Doc = doc; CommonPathLen = 0; } void FindCommonPathLength() { LString s; for (unsigned i=0; iFile.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 = All[i]->File; } } LString ToString(FindSymResult *Obj) { LString s; s.Printf("%s:%i - %s", CommonPathLen < Obj->File.Length() ? Obj->File.Get() + CommonPathLen : Obj->File.Get(), Obj->Line, Obj->Symbol.Get()); return s; } void OnSelect(FindSymResult *Obj) { App->GotoReference(Obj->File, Obj->Line, false); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { // Kick off search... LString s = Ctrl->Name(); int64 AllPlatforms = Ctrl->GetParent()->GetCtrlValue(IDC_ALL_PLATFORMS); s = s.Strip(); if (s.Length() > 2) App->FindSymbol(Doc->AddDispatch(), s, AllPlatforms != 0); } return LPopupList::OnNotify(Ctrl, n); } }; void FilterFiles(LArray &Perfect, LArray &Nodes, LString InputStr) { LString::Array p = InputStr.SplitDelimit(" \t"); auto InputLen = InputStr.RFind("."); if (InputLen < 0) InputLen = InputStr.Length(); LArray Partial; auto Start = LCurrentTime(); for (unsigned i=0; i 400) { break; } ProjectNode *Pn = Nodes[i]; auto Fn = Pn->GetFileName(); if (Fn) { char *Dir = strchr(Fn, '/'); if (!Dir) Dir = strchr(Fn, '\\'); auto Leaf = Dir ? strrchr(Fn, *Dir) : Fn; bool Match = true; for (unsigned n=0; n { AppWnd *App; public: LArray Nodes; ProjFilePopup(AppWnd *app, LViewI *target) : LPopupList(target, PopupAbove, POPUP_WIDTH) { App = app; } LString ToString(ProjectNode *Obj) { return LString(Obj->GetFileName()); } void OnSelect(ProjectNode *Obj) { auto Fn = Obj->GetFileName(); if (LIsRelativePath(Fn)) { IdeProject *Proj = Obj->GetProject(); LAutoString Base = Proj->GetBasePath(); LFile::Path p(Base); p += Fn; App->GotoReference(p, 1, false); } else { App->GotoReference(Fn, 1, false); } } void Update(LString InputStr) { LArray Matches; FilterFiles(Matches, Nodes, InputStr); SetItems(Matches); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { auto s = Ctrl->Name(); if (ValidStr(s)) Update(s); } return LPopupList::OnNotify(Ctrl, n); } }; class LStyleThread : public LEventTargetThread { public: LStyleThread() : LEventTargetThread("StyleThread") { } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { } return 0; - } + } + } StyleThread; //////////////////////////////////////////////////////////////////////////////////////////// IdeDocPrivate::IdeDocPrivate(IdeDoc *d, AppWnd *a, NodeSource *src, const char *file) : NodeView(src), LMutex("IdeDocPrivate.Lock") { FilePopup = NULL; MethodPopup = NULL; SymPopup = NULL; App = a; Doc = d; Project = 0; FileName = file; LFontType Font, *Use = 0; if (Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Use = &Font; } Doc->AddView(Edit = new DocEdit(Doc, Use)); Doc->AddView(Tray = new EditTray(Edit, Doc)); } void IdeDocPrivate::OnDelete() { IdeDoc *Temp = Doc; DeleteObj(Temp); } void IdeDocPrivate::UpdateName() { char n[MAX_PATH_LEN+30]; LString Dsp = GetDisplayName(); char *File = Dsp; #if MDI_TAB_STYLE char *Dir = File ? strrchr(File, DIR_CHAR) : NULL; if (Dir) File = Dir + 1; #endif strcpy_s(n, sizeof(n), File ? File : Untitled); if (Edit->IsDirty()) { strcat(n, " (changed)"); } Doc->Name(n); } LString IdeDocPrivate::GetDisplayName() { if (nSrc) { auto Fn = nSrc->GetFileName(); if (Fn) { if (stristr(Fn, "://")) { LUri u(nSrc->GetFileName()); if (u.sPass) { u.sPass = "******"; } return u.ToString(); } else if (*Fn == '.') { return nSrc->GetFullPath(); } } return LString(Fn); } return LString(FileName); } bool IdeDocPrivate::IsFile(const char *File) { LString Mem; char *f = NULL; if (nSrc) { Mem = nSrc->GetFullPath(); f = Mem; } else { f = FileName; } if (!f) return false; LToken doc(f, DIR_STR); LToken in(File, DIR_STR); ssize_t in_pos = (ssize_t)in.Length() - 1; ssize_t doc_pos = (ssize_t)doc.Length() - 1; while (in_pos >= 0 && doc_pos >= 0) { char *i = in[in_pos--]; char *d = doc[doc_pos--]; if (!i || !d) { return false; } if (!strcmp(i, ".") || !strcmp(i, "..")) { continue; } if (stricmp(i, d)) { return false; } } return true; } const char *IdeDocPrivate::GetLocalFile() { if (nSrc) { if (nSrc->IsWeb()) return nSrc->GetLocalCache(); auto fp = nSrc->GetFullPath(); if (_stricmp(fp.Get()?fp.Get():"", Buffer.Get()?Buffer.Get():"")) Buffer = fp; return Buffer; } return FileName; } void IdeDocPrivate::SetFileName(const char *f) { nSrc = NULL; FileName = f; Edit->IsDirty(true); } bool IdeDocPrivate::Load() { bool Status = false; if (nSrc) { Status = nSrc->Load(Edit, this); } else if (FileName) { if (LFileExists(FileName)) { Status = Edit->Open(FileName); } else LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, FileName.Get()); } if (Status) ModTs = GetModTime(); return Status; } bool IdeDocPrivate::Save() { bool Status = false; if (nSrc) { Status = nSrc->Save(Edit, this); } else if (FileName) { Status = Edit->Save(FileName); if (!Status) { const char *Err = Edit->GetLastError(); LgiMsg(App, "%s", AppName, MB_OK, Err ? Err : "$unknown_error"); } OnSaveComplete(Status); } else { Edit->IsDirty(false); } if (Status) ModTs = GetModTime(); return Status; } void IdeDocPrivate::OnSaveComplete(bool Status) { if (Status) Edit->IsDirty(false); UpdateName(); ProjectNode *Node = dynamic_cast(nSrc); if (Node) { auto Full = nSrc->GetFullPath(); App->OnNode(Full, Node, FindSymbolSystem::FileReparse); } } void IdeDocPrivate::CheckModTime() { if (!ModTs.IsValid()) return; LDateTime Ts = GetModTime(); if (Ts.IsValid() && Ts > ModTs) { static bool InCheckModTime = false; if (!InCheckModTime) { InCheckModTime = true; if (!Edit->IsDirty() || LgiMsg(Doc, "Do you want to reload modified file from\ndisk and lose your changes?", AppName, MB_YESNO) == IDYES) { auto Ln = Edit->GetLine(); Load(); Edit->IsDirty(false); UpdateName(); Edit->SetLine((int)Ln); } InCheckModTime = false; } } } /////////////////////////////////////////////////////////////////////////////////////////// LString IdeDoc::CurIpDoc; int IdeDoc::CurIpLine = -1; IdeDoc::IdeDoc(AppWnd *a, NodeSource *src, const char *file) { d = new IdeDocPrivate(this, a, src, file); d->UpdateName(); /* if (src || file) d->Load(); */ } IdeDoc::~IdeDoc() { d->App->OnDocDestroy(this); DeleteObj(d); } class WebBuild : public LThread { IdeDocPrivate *d; LString Uri; int64 SleepMs; LStream *Log; LCancel Cancel; public: WebBuild(IdeDocPrivate *priv, LString uri, int64 sleepMs) : LThread("WebBuild"), d(priv), Uri(uri), SleepMs(sleepMs) { Log = d->App->GetBuildLog(); Run(); } ~WebBuild() { Cancel.Cancel(); while (!IsExited()) { LSleep(1); } } int Main() { if (SleepMs > 0) { // Sleep for a number of milliseconds to allow the file to upload/save to the website uint64 Ts = LCurrentTime(); while (!Cancel.IsCancelled() && (LCurrentTime()-Ts) < SleepMs) LSleep(1); } // Download the file... LStringPipe Out; LString Error; bool r = LgiGetUri(&Cancel, &Out, &Error, Uri, NULL/*InHdrs*/, NULL/*Proxy*/); if (r) { // Parse through it and extract any errors... } else { // Show the download error in the build log... Log->Print("%s:%i - Web build download failed: %s\n", _FL, Error.Get()); } return 0; } }; bool IdeDoc::Build() { if (!d->Edit) return false; int64 SleepMs = -1; LString s = d->Edit->Name(), Uri; LString::Array Lines = s.Split("\n"); for (auto Ln : Lines) { s = Ln.Strip(); if (s.Find("//") == 0) { LString::Array p = s(2,-1).Strip().Split(":", 1); if (p.Length() == 2) { if (p[0].Equals("build-sleep")) { SleepMs = p[1].Strip().Int(); } else if (p[0].Equals("build-uri")) { Uri = p[1].Strip(); break; } } } } if (Uri) { if (d->Build && !d->Build->IsExited()) { // Already building... LStream *Log = d->App->GetBuildLog(); if (Log) Log->Print("%s:%i - Already building...\n"); return false; } return d->Build.Reset(new WebBuild(d, Uri, SleepMs)); } return false; } void IdeDoc::OnLineChange(int Line) { d->App->OnLocationChange(d->GetLocalFile(), Line); } void IdeDoc::OnMarginClick(int Line) { LString Dn = d->GetDisplayName(); d->App->ToggleBreakpoint(Dn, Line); } void IdeDoc::OnTitleClick(LMouse &m) { LMdiChild::OnTitleClick(m); if (m.IsContextMenu()) { char Full[MAX_PATH_LEN] = "", sFile[MAX_PATH_LEN] = "", sFull[MAX_PATH_LEN] = "", sBrowse[MAX_PATH_LEN] = ""; const char *Fn = GetFileName(), *Dir = NULL; IdeProject *p = GetProject(); if (Fn) { strcpy_s(Full, sizeof(Full), Fn); if (LIsRelativePath(Fn) && p) { LAutoString Base = p->GetBasePath(); if (Base) LMakePath(Full, sizeof(Full), Base, Fn); } Dir = strrchr(Full, DIR_CHAR); if (Dir) sprintf_s(sFile, sizeof(sFile), "Copy '%s'", Dir + 1); sprintf_s(sFull, sizeof(sFull), "Copy '%s'", Full); sprintf_s(sBrowse, sizeof(sBrowse), "Browse to '%s'", Dir ? Dir + 1 : Full); } LSubMenu s; s.AppendItem("Save", IDM_SAVE, d->Edit->IsDirty()); s.AppendItem("Close", IDM_CLOSE, true); if (Fn) { s.AppendSeparator(); if (Dir) s.AppendItem(sFile, IDM_COPY_FILE, true); s.AppendItem(sFull, IDM_COPY_PATH, true); s.AppendItem(sBrowse, IDM_BROWSE, true); s.AppendItem("Show In Project", IDM_SHOW_IN_PROJECT, true); } if (p) { s.AppendSeparator(); s.AppendItem("Properties", IDM_PROPERTIES, true); } m.ToScreen(); int Cmd = s.Float(this, m.x, m.y, m.Left()); switch (Cmd) { case IDM_SAVE: { - SetClean(); + SetClean(NULL); break; } case IDM_CLOSE: { if (OnRequestClose(false)) Quit(); break; } case IDM_COPY_FILE: { if (Dir) { LClipBoard c(this); c.Text(Dir + 1); } break; } case IDM_COPY_PATH: { LClipBoard c(this); c.Text(Full); break; } case IDM_BROWSE: { LBrowseToFile(Full); break; } case IDM_SHOW_IN_PROJECT: { d->App->ShowInProject(Fn); break; } case IDM_PROPERTIES: { p->ShowFileProperties(Full); break; } } } } AppWnd *IdeDoc::GetApp() { return d->App; } bool IdeDoc::IsFile(const char *File) { return File ? d->IsFile(File) : false; } bool IdeDoc::AddBreakPoint(ssize_t Line, bool Add) { if (Add) d->BreakPoints.Add(Line, true); else d->BreakPoints.Delete(Line); if (d->Edit) d->Edit->Invalidate(); return true; } void IdeDoc::GotoSearch(int CtrlId, char *InitialText) { LString File; if (CtrlId == IDC_SYMBOL_SEARCH) { // Check if the cursor is on a #include line... in which case we // should look up the header and go to that instead of looking for // a symbol in the code. if (d->Edit) { // Get current line LString Ln = (*d->Edit)[d->Edit->GetLine()]; if (Ln.Find("#include") >= 0) { LString::Array a = Ln.SplitDelimit(" \t", 1); if (a.Length() == 2) { File = a[1].Strip("\'\"<>"); InitialText = File; CtrlId = IDC_FILE_SEARCH; } } } } if (d->Tray) d->Tray->GotoSearch(CtrlId, InitialText); } #define IsVariableChar(ch) \ ( \ IsAlpha(ch) \ || \ IsDigit(ch) \ || \ strchr("-_~", ch) != NULL \ ) void IdeDoc::SearchSymbol() { if (!d->Edit || !d->Tray) { LAssert(0); return; } ssize_t Cur = d->Edit->GetCaret(); auto Txt = d->Edit->NameW(); if (Cur >= 0 && Txt != NULL) { ssize_t Start = Cur; while ( Start > 0 && IsVariableChar(Txt[Start-1])) Start--; ssize_t End = Cur; while ( Txt[End] && IsVariableChar(Txt[End])) End++; LString Word(Txt + Start, End - Start); GotoSearch(IDC_SYMBOL_SEARCH, Word); } } void IdeDoc::UpdateControl() { if (d->Edit) d->Edit->Invalidate(); } void IdeDoc::SearchFile() { GotoSearch(IDC_FILE_SEARCH, NULL); } bool IdeDoc::IsCurrentIp() { auto Fn = GetFileName(); bool DocMatch = CurIpDoc && Fn && !_stricmp(Fn, CurIpDoc); return DocMatch; } void IdeDoc::ClearCurrentIp() { CurIpDoc.Empty(); CurIpLine = -1; } void IdeDoc::SetCrLf(bool CrLf) { if (d->Edit) d->Edit->SetCrLf(CrLf); } bool IdeDoc::OpenFile(const char *File) { if (!d->Edit) return false; auto Cs = d->GetSrc() ? d->GetSrc()->GetCharset() : NULL; return d->Edit->Open(File, Cs); } void IdeDoc::SetEditorParams(int IndentSize, int TabSize, bool HardTabs, bool ShowWhiteSpace) { if (d->Edit) { d->Edit->SetIndentSize(IndentSize > 0 ? IndentSize : 4); d->Edit->SetTabSize(TabSize > 0 ? TabSize : 4); d->Edit->SetHardTabs(HardTabs); d->Edit->SetShowWhiteSpace(ShowWhiteSpace); d->Edit->Invalidate(); } } bool IdeDoc::HasFocus(int Set) { if (!d->Edit) return false; if (Set) d->Edit->Focus(Set); return d->Edit->Focus(); } void IdeDoc::SplitSelection(LString Sep) { if (!d->Edit) return; auto r = d->Edit->GetSelectionRange(); LString s = d->Edit->GetSelection(); if (!s) return; auto parts = s.SplitDelimit(Sep); auto joined = LString("\n").Join(parts); LAutoWString w(Utf8ToWide(joined)); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); } void IdeDoc::JoinSelection(LString Sep) { if (!d->Edit) return; auto r = d->Edit->GetSelectionRange(); LString s = d->Edit->GetSelection(); if (!s) return; LAutoWString w(Utf8ToWide(s.Replace("\n", Sep))); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); } void IdeDoc::EscapeSelection(bool ToEscaped) { if (!d->Edit) return; LString s = d->Edit->GetSelection(); if (!s) return; if (ToEscaped) { LMouse m; GetMouse(m); LString Delim = "\r\n\\"; if (m.Ctrl()) { LInput Inp(this, LString::Escape(Delim, -1, "\\"), "Delimiter chars:", "Escape"); - if (Inp.DoModal()) + Inp.DoModal([&](auto d, auto code) { Delim = LString::UnEscape(Inp.GetStr().Get(), -1); + }); } s = LString::Escape(s, -1, Delim); } else { s = LString::UnEscape(s.Get(), -1); } auto r = d->Edit->GetSelectionRange(); LAutoWString w(Utf8ToWide(s)); d->Edit->DeleteSelection(); d->Edit->Insert(r.Start, w, StrlenW(w)); } void IdeDoc::ConvertWhiteSpace(bool ToTabs) { if (!d->Edit) return; LAutoString Sp( ToTabs ? SpacesToTabs(d->Edit->Name(), d->Edit->GetTabSize()) : TabsToSpaces(d->Edit->Name(), d->Edit->GetTabSize()) ); if (Sp) { d->Edit->Name(Sp); SetDirty(); } } ssize_t IdeDoc::GetLine() { return d->Edit ? d->Edit->GetLine() : -1; } void IdeDoc::SetLine(int Line, bool CurIp) { if (CurIp) { LString CurDoc = GetFileName(); if (ValidStr(CurIpDoc) ^ ValidStr(CurDoc) || (CurIpDoc && CurDoc && strcmp(CurDoc, CurIpDoc) != 0) || Line != CurIpLine) { bool Cur = IsCurrentIp(); if (d->Edit && Cur && CurIpLine >= 0) { // Invalidate the old IP location d->Edit->InvalidateLine(CurIpLine - 1); } CurIpLine = Line; CurIpDoc = CurDoc; // LgiTrace("%s:%i - CurIpLine=%i\n", _FL, CurIpLine); d->Edit->InvalidateLine(CurIpLine - 1); } } if (d->Edit) { d->Edit->SetLine(Line); } } IdeProject *IdeDoc::GetProject() { return d->Project; } void IdeDoc::SetProject(IdeProject *p) { d->Project = p; if (d->Project->GetApp() && d->BreakPoints.Length() == 0) d->Project->GetApp()->LoadBreakPoints(this); } const char *IdeDoc::GetFileName() { return d->GetLocalFile(); } void IdeDoc::SetFileName(const char *f, bool Write) { d->SetFileName(f); if (Write) d->Edit->Save(d->GetLocalFile()); } void IdeDoc::Focus(bool f) { d->Edit->Focus(f); } LMessage::Result IdeDoc::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_FIND_SYM_REQUEST: { LAutoPtr Resp((FindSymRequest*)Msg->A()); if (Resp && d->SymPopup) { LViewI *SymEd; if (GetViewById(IDC_SYMBOL_SEARCH, SymEd)) { LString Input = SymEd->Name(); if (Input == Resp->Str) // Is the input string still the same? { /* The problem with this is that the user it still typing something and the cursor / focus jumps to the document now they are typing a search string into the document, which is not what they intended. if (Resp->Results.Length() == 1) { FindSymResult *r = Resp->Results[0]; d->SymPopup->Visible(false); d->App->GotoReference(r->File, r->Line, false); } else */ { d->SymPopup->All = Resp->Results; Resp->Results.Length(0); d->SymPopup->FindCommonPathLength(); d->SymPopup->SetItems(d->SymPopup->All); d->SymPopup->Visible(true); } } } } break; } } return LMdiChild::OnEvent(Msg); } void IdeDoc::OnPulse() { d->CheckModTime(); if (d->Lock(_FL)) { if (d->WriteBuf.Length()) { bool Pour = d->Edit->SetPourEnabled(false); for (auto s: d->WriteBuf) { LAutoWString w(Utf8ToWide(s, s.Length())); d->Edit->Insert(d->Edit->Length(), w, Strlen(w.Get())); } d->Edit->SetPourEnabled(Pour); d->WriteBuf.Empty(); d->Edit->Invalidate(); } d->Unlock(); } } LString IdeDoc::Read() { return d->Edit->Name(); } ssize_t IdeDoc::Write(const void *Ptr, ssize_t Size, int Flags) { if (d->Lock(_FL)) { d->WriteBuf.New().Set((char*)Ptr, Size); d->Unlock(); } return 0; } void IdeDoc::OnProjectChange() { DeleteObj(d->FilePopup); DeleteObj(d->MethodPopup); DeleteObj(d->SymPopup); } int IdeDoc::OnNotify(LViewI *v, LNotification n) { // printf("IdeDoc::OnNotify(%i, %i)\n", v->GetId(), f); switch (v->GetId()) { case IDC_EDIT: { switch (n.Type) { case LNotifyDocChanged: { d->UpdateName(); break; } case LNotifyCursorChanged: { if (d->Tray) { LPoint Pt; if (d->Edit->GetLineColumnAtIndex(Pt, d->Edit->GetCaret())) { d->Tray->Col = Pt.x; d->Tray->Line = Pt.y; d->Tray->Invalidate(); } } break; } default: break; } break; } case IDC_FILE_SEARCH: { if (n.Type == LNotifyEscapeKey) { d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->FilePopup) { if ((d->FilePopup = new ProjFilePopup(d->App, v))) { // Populate with files from the project... // Find the root project... IdeProject *p = d->Project; while (p && p->GetParentProject()) p = p->GetParentProject(); if (p) { // Get all the nodes List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(d->FilePopup->Nodes); } } } } if (d->FilePopup) { // Update list elements... d->FilePopup->OnNotify(v, n); } } else if (d->FilePopup) { DeleteObj(d->FilePopup); } break; } case IDC_METHOD_SEARCH: { if (n.Type == LNotifyEscapeKey) { printf("%s:%i Got LNotifyEscapeKey\n", _FL); d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->MethodPopup) d->MethodPopup = new ProjMethodPopup(d->App, v); if (d->MethodPopup) { // Populate with symbols d->MethodPopup->All.Length(0); BuildDefnList(GetFileName(), (char16*)d->Edit->NameW(), d->MethodPopup->All, DefnFunc); // Update list elements... d->MethodPopup->OnNotify(v, n); } } else if (d->MethodPopup) { DeleteObj(d->MethodPopup); } break; } case IDC_SYMBOL_SEARCH: { if (n.Type == LNotifyEscapeKey) { printf("%s:%i Got LNotifyEscapeKey\n", _FL); d->Edit->Focus(true); break; } auto SearchStr = v->Name(); if (ValidStr(SearchStr)) { if (!d->SymPopup) d->SymPopup = new ProjSymPopup(d->App, this, v); if (d->SymPopup) d->SymPopup->OnNotify(v, n); } else if (d->SymPopup) { DeleteObj(d->SymPopup); } break; } } return 0; } void IdeDoc::SetDirty() { d->Edit->IsDirty(true); d->UpdateName(); } -bool IdeDoc::SetClean() +bool IdeDoc::GetClean() +{ + return !d->Edit->IsDirty(); +} + +LString IdeDoc::GetFullPath() +{ + LAutoString Base; + if (GetProject()) + Base = GetProject()->GetBasePath(); + + LString LocalPath; + if (d->GetLocalFile() && + LIsRelativePath(LocalPath) && + Base) + { + char p[MAX_PATH_LEN]; + LMakePath(p, sizeof(p), Base, d->GetLocalFile()); + LocalPath = p; + } + else + { + LocalPath = d->GetLocalFile(); + } + + if (d->Project) + d->Project->CheckExists(LocalPath); + + return LocalPath; +} + +void IdeDoc::SetClean(std::function Callback) { static bool Processing = false; bool Status = false; if (!Processing) { - Status = Processing = true; + Processing = true; LAutoString Base; if (GetProject()) Base = GetProject()->GetBasePath(); - - LString LocalPath; - if (d->GetLocalFile() && - LIsRelativePath(LocalPath) && - Base) + LString LocalPath = GetFullPath(); + + // printf("OnSave setup d=%p\n", d); + auto OnSave = [this, Callback](bool ok) { - char p[MAX_PATH_LEN]; - LMakePath(p, sizeof(p), Base, d->GetLocalFile()); - LocalPath = p; - } - else - { - LocalPath = d->GetLocalFile(); - } + // printf("OnSave %i d=%p\n", ok, d); + if (ok) + d->Save(); + // printf("OnSave cb\n"); + if (Callback) + Callback(ok); + // printf("OnSave done\n"); + }; - if (d->Project) - d->Project->CheckExists(LocalPath); - if (d->Edit->IsDirty() && !LFileExists(LocalPath)) { // We need a valid filename to save to... - LFileSelect s; - s.Parent(this); + auto s = new LFileSelect; + s->Parent(this); if (Base) - s.InitialDir(Base); - - if (s.Save()) - d->SetFileName(s.Name()); + s->InitialDir(Base); + s->Save([this, OnSave](auto fileSel, auto ok) + { + // printf("Doc.Save.Cb ok=%i d=%p\n", ok, d); + if (ok) + d->SetFileName(fileSel->Name()); + // printf("Doc.Save.Cb onsave\n", ok); + OnSave(ok); + // printf("Doc.Save.Cb del\n", ok); + delete fileSel; + }); } - - if (d->Edit->IsDirty()) - d->Save(); + else OnSave(true); Processing = false; } - - return Status; } void IdeDoc::OnPaint(LSurface *pDC) { LMdiChild::OnPaint(pDC); #if !MDI_TAB_STYLE LRect c = GetClient(); LWideBorder(pDC, c, SUNKEN); #endif } void IdeDoc::OnPosChange() { LMdiChild::OnPosChange(); } bool IdeDoc::OnRequestClose(bool OsShuttingDown) { if (d->Edit->IsDirty()) { LString Dsp = d->GetDisplayName(); int a = LgiMsg(this, "Save '%s'?", AppName, MB_YESNOCANCEL, Dsp ? Dsp.Get() : Untitled); switch (a) { case IDYES: { - if (!SetClean()) - { - return false; - } - break; + SetClean([](bool ok) + { + if (ok) + { + LAssert(!"Impl close doc."); + } + }); + + // This is returned immediately... before the user has decided to save or not + return false; } case IDNO: { break; } default: case IDCANCEL: { return false; } } } return true; } /* LTextView3 *IdeDoc::GetEdit() { return d->Edit; } */ bool IdeDoc::BuildIncludePaths(LArray &Paths, IdePlatform Platform, bool IncludeSysPaths) { if (!GetProject()) { LgiTrace("%s:%i - GetProject failed.\n", _FL); return false; } bool Status = GetProject()->BuildIncludePaths(Paths, true, IncludeSysPaths, Platform); if (Status) { if (IncludeSysPaths) GetApp()->GetSystemIncludePaths(Paths); } else { LgiTrace("%s:%i - GetProject()->BuildIncludePaths failed.\n", _FL); } return Status; } bool IdeDoc::BuildHeaderList(const char16 *Cpp, LArray &Headers, LArray &IncPaths) { LAutoString c8(WideToUtf8(Cpp)); if (!c8) return false; return ::BuildHeaderList(c8, Headers, IncPaths, true); } bool MatchSymbol(DefnInfo *Def, char16 *Symbol) { static char16 Dots[] = {':', ':', 0}; LBase o; o.Name(Def->Name); auto Name = o.NameW(); auto Sep = StristrW((char16*)Name, Dots); auto Start = Sep ? Sep : Name; // char16 *End = StrchrW(Start, '('); ssize_t Len = StrlenW(Symbol); char16 *Match = StristrW((char16*)Start, Symbol); if (Match) // && Match + Len <= End) { if (Match > Start && isword(Match[-1])) { return false; } char16 *e = Match + Len; if (*e && isword(*e)) { return false; } return true; } return false; } bool IdeDoc::FindDefn(char16 *Symbol, const char16 *Source, List &Matches) { if (!Symbol || !Source) { LgiTrace("%s:%i - Arg error.\n", _FL); return false; } #if DEBUG_FIND_DEFN LStringPipe Dbg; LgiTrace("FindDefn(%S)\n", Symbol); #endif LString::Array Paths; LArray Headers; if (!BuildIncludePaths(Paths, PlatformCurrent, true)) { LgiTrace("%s:%i - BuildIncludePaths failed.\n", _FL); // return false; } char Local[MAX_PATH_LEN]; strcpy_s(Local, sizeof(Local), GetFileName()); LTrimDir(Local); Paths.New() = Local; if (!BuildHeaderList(Source, Headers, Paths)) { LgiTrace("%s:%i - BuildHeaderList failed.\n", _FL); // return false; } { LArray Defns; for (int i=0; i Defns; if (BuildDefnList(h, c16, Defns, DefnNone, false )) { bool Found = false; for (unsigned n=0; n 0; } diff --git a/Ide/Code/IdeDoc.h b/Ide/Code/IdeDoc.h --- a/Ide/Code/IdeDoc.h +++ b/Ide/Code/IdeDoc.h @@ -1,75 +1,79 @@ #ifndef _IDE_DOC_H_ #define _IDE_DOC_H_ +#include + #include "lgi/common/Mdi.h" #include "lgi/common/TextView3.h" #include "ParserCommon.h" extern void FilterFiles(LArray &Perfect, LArray &Nodes, LString InputStr); class IdeDoc : public LMdiChild, public LStream { friend class DocEdit; class IdeDocPrivate *d; static LString CurIpDoc; static int CurIpLine; public: IdeDoc(class AppWnd *a, NodeSource *src, const char *file); ~IdeDoc(); AppWnd *GetApp(); void SetProject(IdeProject *p); IdeProject *GetProject(); const char *GetFileName(); + LString GetFullPath(); void SetFileName(const char *f, bool Write); void Focus(bool f) override; - bool SetClean(); + bool GetClean(); + void SetClean(std::function Callback); void SetDirty(); bool OnRequestClose(bool OsShuttingDown) override; void OnPosChange() override; void OnPaint(LSurface *pDC) override; bool IsFile(const char *File); bool AddBreakPoint(ssize_t Line, bool Add); bool OpenFile(const char *File); void SetEditorParams(int IndentSize, int TabSize, bool HardTabs, bool ShowWhiteSpace); bool HasFocus(int Set = -1); void ConvertWhiteSpace(bool ToTabs); void EscapeSelection(bool ToEscaped); void SplitSelection(LString s); void JoinSelection(LString s); void SetCrLf(bool CrLf); ssize_t GetLine(); void SetLine(int Line, bool CurIp); static void ClearCurrentIp(); bool IsCurrentIp(); void GotoSearch(int CtrlId, char *InitialText = NULL); void SearchSymbol(); void SearchFile(); void UpdateControl(); bool Build(); // Source tools bool BuildIncludePaths(LArray &Paths, IdePlatform Platform, bool IncludeSysPaths); bool BuildHeaderList(const char16 *Cpp, LArray &Headers, LArray &IncPaths); bool FindDefn(char16 *Def, const char16 *Source, List &Matches); // Events void OnLineChange(int Line); void OnMarginClick(int Line); void OnProjectChange(); // Impl void OnTitleClick(LMouse &m) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *v, LNotification n) override; void OnPulse() override; LString Read(); ssize_t Read(void *Ptr, ssize_t Size, int Flags = 0) override { return 0; } ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) override; }; #endif diff --git a/Ide/Code/IdeProject.cpp b/Ide/Code/IdeProject.cpp --- a/Ide/Code/IdeProject.cpp +++ b/Ide/Code/IdeProject.cpp @@ -1,4056 +1,4100 @@ #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 ); 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[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[BuildDebug] += " -D_FILE_OFFSET_BITS=64"; // >:-( sDefines[BuildDebug] += " -DPOSIX"; #endif 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 (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" "Objects := $(addprefix $(BuildDir)/,$(SourceLst))\n" "Deps := $(patsubst %%.o,%%.d,$(Objects))\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); + LSubProcess PostCmd(p[0], p.Length() > 1 ? p[1] : LString()); 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); LString Exe; if (!PExe) { // Use the default exe name? Exe = GetExecutable(GetCurrentPlatform()); if (Exe) { 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 return false; } else { strcpy_s(Path, Len, PExe); } return true; } LString IdeProject::GetMakefile(IdePlatform Platform) { + const char *PMakefile = d->Settings.GetStr(ProjMakefile, NULL, Platform); + if (!PMakefile) + return LString(); + LString Path; - const char *PMakefile = d->Settings.GetStr(ProjMakefile, NULL, Platform); - if (PMakefile) + if (LIsRelativePath(PMakefile)) { - if (LIsRelativePath(PMakefile)) + auto Base = GetBasePath(); + if (Base) { - LAutoString Base = GetBasePath(); - if (Base) - { - char p[MAX_PATH_LEN]; - LMakePath(p, sizeof(p), Base, PMakefile); - Path = p; - } + char p[MAX_PATH_LEN]; + LMakePath(p, sizeof(p), Base, PMakefile); + Path = p; } - else - { - Path = PMakefile; - } + } + 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, LString *ErrMsg) { auto Base = GetBasePath(); if (!Base) { 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); + + #ifndef HAIKU 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); + #endif + + new ExecuteThread( this, + e, + Args, + Base, + #if defined(HAIKU) || defined(WINDOWS) + ExeRun // No gdb or valgrind + #else + Act + #endif + ); 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(); - - if (!IsMakefileUpToDate()) - CreateMakefile(GetCurrentPlatform(), true); - else - // Start the build thread... - d->Thread.Reset - ( - new BuildThread + SetClean([&](bool ok) + { + if (!ok) + return; + + if (!IsMakefileUpToDate()) + CreateMakefile(GetCurrentPlatform(), true); + else + // Start the build thread... + d->Thread.Reset ( - this, - m, - false, - Config, - All, - sizeof(size_t)*8 - ) - ); + 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) { auto Log = d->App->GetBuildLog(); 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) { Log->Print("%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)) { Log->Print("%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)) { Log->Print("%s:%i - Error: Can't read XML: %s\n", _FL, x.GetErrorMsg()); 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")) { Log->Print("%s:%i - No 'Project' tag.\n", _FL); 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::SetClean() +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) { - // printf("IdeProject::SetClean %i %i\n", d->Dirty, d->UserFileDirty); + 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)) + if (ValidStr(d->FileName)) + SaveFile(); + else { LFileSelect s; s.Parent(Tree); s.Name("Project.xml"); - if (s.Save()) + s.Save([&](auto s, auto ok) { - d->FileName = s.Name(); + if (!ok) + { + if (OnDone) + OnDone(false); + return; + } + + d->FileName = s->Name(); d->UserFile = d->FileName + "." + LCurrentUserName(); d->App->OnFile(d->FileName, true); Update(); - } - else return false; + + CleanNodes(); + }); + return; } - - SaveFile(); } - - for (auto i:*this) - { - ProjectNode *p = dynamic_cast(i); - if (!p) break; - - p->SetClean(); - } - - return true; + + 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); - if (Name.DoModal()) + Name.DoModal([&](auto d, auto code) { GetSubFolder(this, Name.GetStr(), true); - } + }); break; } case IDM_WEB_FOLDER: { - WebFldDlg Dlg(Tree, 0, 0, 0); - if (Dlg.DoModal()) + WebFldDlg *Dlg = new WebFldDlg(Tree, 0, 0, 0); + Dlg->DoModal([&](auto d, auto code) { - if (Dlg.Ftp && Dlg.Www) + if (Dlg->Ftp && Dlg->Www) { - IdeCommon *f = GetSubFolder(this, Dlg.Name, true); + IdeCommon *f = GetSubFolder(this, Dlg->Name, true); if (f) { - f->SetAttr(OPT_Ftp, Dlg.Ftp); - f->SetAttr(OPT_Www, Dlg.Www); + 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: { - if (d->Settings.Edit(Tree)) + d->Settings.Edit(Tree, [&]() { SetDirty(); - } + }); break; } case IDM_INSERT_DEP: { - LFileSelect s; - s.Parent(Tree); - s.Type("Project", "*.xml"); - if (s.Open()) + 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->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; + return LString(); } const char *TargetType = d->Settings.GetStr(ProjTargetType); if (!TargetType) { LAssert(!"Needs a target type."); - return NULL; + return LString(); } 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; + return LString(); } 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,222 +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, 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 SetClean(); + 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/IdeProjectSettings.cpp b/Ide/Code/IdeProjectSettings.cpp --- a/Ide/Code/IdeProjectSettings.cpp +++ b/Ide/Code/IdeProjectSettings.cpp @@ -1,1108 +1,1122 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #include "IdeProject.h" #include "ProjectNode.h" #include "resdefs.h" const char TagSettings[] = "Settings"; const char sRemote[] = "Remote"; const char sGeneral[] = "General"; const char sBuild[] = "Build"; const char sEditor[] = "Editor"; const char sAdvanced[] = "Advanced"; const char sDebug[] = "Debug"; const char sRelease[] = "Release"; const char sAllPlatforms[] = "All"; const char sCurrentPlatform[] = #if defined WIN32 "Windows" #elif defined LINUX "Linux" #elif defined MAC "Mac" #elif defined HAIKU "Haiku" #else #error "Not impl" #endif ; const char *sCompilers[] = { #if defined WIN32 "VisualStudio", "Cygwin", "MingW", "IAR", #elif defined MAC "XCode", #else "gcc", "cross", #endif NULL }; const char *sBuildTypes[] = { "Executable", "StaticLibrary", "DynamicLibrary", NULL }; static const char **GetEnumValues(ProjSetting s) { switch (s) { case ProjCompiler: return sCompilers; case ProjTargetType: return sBuildTypes; default: LAssert(!"Unknown enum type."); break; } return NULL; } #define SF_MULTILINE 0x01 // String setting is multiple lines, otherwise it's single line #define SF_CROSSPLATFORM 0x02 // Just has a "all" platforms setting (no platform specific) #define SF_PLATFORM_SPECIFC 0x04 // Just has a platform specific setting (no all) #define SF_CONFIG_SPECIFIC 0x08 // Can have a different setting in different configs #define SF_ENUM 0x10 // Integer is actually an enum index, not a straight number #define SF_FILE_SELECT 0x20 // UI needs a file selection button #define SF_FOLDER_SELECT 0x40 // UI needs a file selection button #define SF_PASSWORD 0x80 // UI needs to hide the characters of the password #define IDC_TEXT_BASE 100 #define IDC_EDIT_BASE 200 #define IDC_CHECKBOX_BASE 300 #define IDC_COMBO_BASE 400 #define IDC_BROWSE_FILE 500 #define IDC_BROWSE_FOLDER 600 struct SettingInfo { struct BitFlags { uint32_t MultiLine : 1; uint32_t CrossPlatform : 1; uint32_t PlatformSpecific : 1; uint32_t ConfigSpecific : 1; uint32_t Enum : 1; uint32_t FileSelect : 1; uint32_t FolderSelect : 1; uint32_t IsPassword : 1; }; ProjSetting Setting; int Type; const char *Name; const char *Category; union { uint32_t Flags; BitFlags Flag; }; }; SettingInfo AllSettings[] = { {ProjRemoteUri, GV_STRING, "RemoteUri", sRemote, {SF_CROSSPLATFORM}}, {ProjRemotePass, GV_STRING, "RemotePass", sRemote, {SF_CROSSPLATFORM|SF_PASSWORD}}, {ProjMakefile, GV_STRING, "Makefile", sGeneral, {SF_PLATFORM_SPECIFC|SF_FILE_SELECT}}, {ProjExe, GV_STRING, "Executable", sGeneral, {SF_PLATFORM_SPECIFC|SF_CONFIG_SPECIFIC|SF_FILE_SELECT}}, {ProjCompiler, GV_INT32, "Compiler", sGeneral, {SF_PLATFORM_SPECIFC|SF_ENUM}}, {ProjCCrossCompiler, GV_STRING, "CCrossCompiler", sGeneral, {SF_PLATFORM_SPECIFC|SF_FILE_SELECT}}, {ProjCppCrossCompiler, GV_STRING, "CppCrossCompiler", sGeneral, {SF_PLATFORM_SPECIFC|SF_FILE_SELECT}}, {ProjEnv, GV_STRING, "Environment", sDebug, {SF_CROSSPLATFORM|SF_MULTILINE}}, {ProjArgs, GV_STRING, "Arguments", sDebug, {SF_CROSSPLATFORM|SF_CONFIG_SPECIFIC}}, {ProjDebugAdmin, GV_BOOL, "DebugAdmin", sDebug, {SF_CROSSPLATFORM}}, {ProjInitDir, GV_STRING, "InitialDir", sDebug, {SF_CROSSPLATFORM|SF_FOLDER_SELECT}}, {ProjDefines, GV_STRING, "Defines", sBuild, {SF_MULTILINE|SF_CONFIG_SPECIFIC}}, {ProjIncludePaths, GV_STRING, "IncludePaths", sBuild, {SF_MULTILINE|SF_CONFIG_SPECIFIC}}, {ProjSystemIncludes, GV_STRING, "SystemIncludes", sBuild, {SF_MULTILINE|SF_CONFIG_SPECIFIC|SF_PLATFORM_SPECIFC}}, {ProjLibraries, GV_STRING, "Libraries", sBuild, {SF_MULTILINE|SF_CONFIG_SPECIFIC}}, {ProjLibraryPaths, GV_STRING, "LibraryPaths", sBuild, {SF_MULTILINE|SF_CONFIG_SPECIFIC}}, {ProjTargetType, GV_INT32, "TargetType", sBuild, {SF_CROSSPLATFORM|SF_ENUM}}, {ProjTargetName, GV_STRING, "TargetName", sBuild, {SF_PLATFORM_SPECIFC|SF_CONFIG_SPECIFIC}}, {ProjApplicationIcon, GV_STRING, "ApplicationIcon", sBuild, {SF_PLATFORM_SPECIFC|SF_FILE_SELECT}}, {ProjEditorTabSize, GV_INT32, "TabSize", sEditor, {SF_CROSSPLATFORM}}, {ProjEditorIndentSize, GV_INT32, "IndentSize", sEditor, {SF_CROSSPLATFORM}}, {ProjEditorShowWhiteSpace, GV_BOOL, "ShowWhiteSpace", sEditor, {SF_CROSSPLATFORM}}, {ProjEditorUseHardTabs, GV_BOOL, "UseHardTabs", sEditor, {SF_CROSSPLATFORM}}, {ProjCommentFile, GV_STRING, "CommentFile", sEditor, {SF_MULTILINE|SF_CROSSPLATFORM}}, {ProjCommentFunction, GV_STRING, "CommentFunction", sEditor, {SF_MULTILINE|SF_CROSSPLATFORM}}, {ProjMakefileRules, GV_STRING, "OtherMakefileRules", sAdvanced, {SF_MULTILINE}}, {ProjPostBuildCommands, GV_STRING, "PostBuildCommands", sAdvanced, {SF_PLATFORM_SPECIFC|SF_CONFIG_SPECIFIC|SF_MULTILINE}}, {ProjNone, GV_NULL, NULL, NULL, {0}}, }; static void ClearEmptyTags(LXmlTag *t) { for (int i=0; iChildren.Length(); i++) { LXmlTag *c = t->Children[i]; if (!c->GetContent() && !c->Children.Length()) { c->RemoveTag(); i--; DeleteObj(c); } else { ClearEmptyTags(c); } } } struct IdeProjectSettingsPriv { char PathBuf[MAX_PATH_LEN]; public: IdeProject *Project; LHashTbl, SettingInfo*> Map; IdeProjectSettings *Parent; LXmlTag Active; LXmlTag Editing; LString CurConfig; LString::Array Configs; LAutoString StrBuf; // Temporary storage for generated settings IdeProjectSettingsPriv(IdeProjectSettings *parent) : Active(TagSettings) { Parent = parent; Project = NULL; for (SettingInfo *i = AllSettings; i->Setting; i++) { Map.Add(i->Setting, i); } Configs.New() = sDebug; Configs.New() = sRelease; CurConfig = sDebug; } ~IdeProjectSettingsPriv() { } int FindConfig(const char *Cfg) { for (int i=0; i= 0 && Platform < PlatformMax) { return PlatformNames[Platform]; } return sCurrentPlatform; } return sAllPlatforms; } char *BuildPath(ProjSetting s, int Flags, IdePlatform Platform, int Config = -1) { SettingInfo *i = Map.Find(s); LAssert(i); const char *PlatformStr = PlatformToString(Flags, Platform); int Ch = sprintf_s( PathBuf, sizeof(PathBuf), "%s.%s.%s", i->Category, i->Name, PlatformStr); if (i->Flag.ConfigSpecific) { sprintf_s( PathBuf+Ch, sizeof(PathBuf)-Ch, ".%s", Config >= 0 ? Configs[Config].Get() : CurConfig.Get()); } return PathBuf; } }; class LSettingDetail : public LLayout, public ResObject { LTableLayout *Tbl; IdeProjectSettingsPriv *d; SettingInfo *Setting; int Flags; struct CtrlInfo { LTextLabel *Text; LEdit *Edit; LCheckBox *Chk; LCombo *Cbo; CtrlInfo() { Text = NULL; Edit = NULL; Chk = NULL; Cbo = NULL; } }; LArray Ctrls; public: LSettingDetail() : ResObject(Res_Custom) { Flags = 0; d = NULL; Setting = NULL; AddView(Tbl = new LTableLayout); } void OnCreate() { AttachChildren(); } void OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } bool OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = -1; Inf.Width.Max = -1; Inf.Height.Min = -1; Inf.Height.Max = -1; return true; } void SetPriv(IdeProjectSettingsPriv *priv) { d = priv; } void AddLine(int i, int Config) { char *Path; int CellY = i * 2; // Do label cell auto *c = Tbl->GetCell(0, CellY); c->Add(Ctrls[i].Text = new LTextLabel(IDC_TEXT_BASE + i, 0, 0, -1, -1, Path = d->BuildPath(Setting->Setting, Flags, PlatformCurrent, Config))); // Do value cell c = Tbl->GetCell(0, CellY + 1); LXmlTag *t = d->Editing.GetChildTag(Path); if (Setting->Type == GV_STRING) { Ctrls[i].Edit = new LEdit(IDC_EDIT_BASE + i, 0, 0, 60, 20); Ctrls[i].Edit->MultiLine(Setting->Flag.MultiLine); Ctrls[i].Edit->Password(Setting->Flag.IsPassword); if (t && t->GetContent()) Ctrls[i].Edit->Name(t->GetContent()); c->Add(Ctrls[i].Edit); if (Setting->Flag.FileSelect || Setting->Flag.FolderSelect) { c = Tbl->GetCell(1, CellY + 1); int Base = Setting->Flag.FileSelect ? IDC_BROWSE_FILE : IDC_BROWSE_FOLDER; if (c) c->Add(new LButton(Base + i, 0, 0, -1, -1, "...")); else LgiTrace("%s:%i - No cell.\n", _FL); } } else if (Setting->Type == GV_INT32) { if (Setting->Flag.Enum) { // Enum setting c->Add(Ctrls[i].Cbo = new LCombo(IDC_COMBO_BASE + i, 0, 0, 60, 20)); const char **Init = GetEnumValues(Setting->Setting); if (Init) { for (int n=0; Init[n]; n++) { Ctrls[i].Cbo->Insert(Init[n]); if (t && t->GetContent() && !stricmp(t->GetContent(), Init[n])) Ctrls[i].Cbo->Value(n); } } } else { // Straight integer c->Add(Ctrls[i].Edit = new LEdit(IDC_EDIT_BASE + i, 0, 0, 60, 20)); if (t) Ctrls[i].Edit->Value(t->GetContent() ? atoi(t->GetContent()) : 0); } } else if (Setting->Type == GV_BOOL) { c->Add(Ctrls[i].Chk = new LCheckBox(IDC_CHECKBOX_BASE + i, 0, 0, -1, -1, NULL)); if (t && t->GetContent()) Ctrls[i].Chk->Value(atoi(t->GetContent())); } else LAssert(!"Unknown type?"); } void SetSetting(SettingInfo *setting, int flags) { if (Setting) { // Save previous values for (int i=0; iName(); if (!Path) { LAssert(0); continue; } LXmlTag *t = d->Editing.GetChildTag(Path, true); if (!t) { LAssert(0); continue; } if (Ctrls[i].Edit) { auto Val = Ctrls[i].Edit->Name(); // LgiTrace("Saving edit setting '%s': '%s'\n", Path, Val); t->SetContent(Val); } else if (Ctrls[i].Chk) { int64 Val = Ctrls[i].Chk->Value(); // LgiTrace("Saving bool setting '%s': "LGI_PrintfInt64"\n", Path, Val); t->SetContent((int)Val); } else if (Ctrls[i].Cbo) { auto Val = Ctrls[i].Cbo->Name(); // LgiTrace("Saving enum setting '%s': '%s'\n", Path, Val); t->SetContent(Val); } else LAssert(0); } } Tbl->Empty(); Ctrls.Length(0); Setting = setting; Flags = flags; if (Setting) { if (Setting->Flag.ConfigSpecific) { for (int i=0; iConfigs.Length(); i++) AddLine(i, i); } else { AddLine(0, -1); } Tbl->InvalidateLayout(); Tbl->AttachChildren(); Tbl->SetPos(GetClient()); Invalidate(); } } void OnPosChange() { Tbl->SetPos(GetClient()); } }; class LSettingDetailFactory : public LViewFactory { LView *NewView ( const char *Class, LRect *Pos, const char *Text ) { if (!stricmp(Class, "LSettingDetail")) return new LSettingDetail; return NULL; } } SettingDetailFactory; class ProjectSettingsDlg; class SettingItem : public LTreeItem { ProjectSettingsDlg *Dlg; public: SettingInfo *Setting; int Flags; SettingItem(SettingInfo *setting, int flags, ProjectSettingsDlg *dlg) { Setting = setting; Dlg = dlg; Flags = flags; } void Select(bool b); }; class ProjectSettingsDlg : public LDialog { IdeProjectSettingsPriv *d; LTree *Tree; LSettingDetail *Detail; uint64 DefLockOut; public: ProjectSettingsDlg(LViewI *parent, IdeProjectSettingsPriv *priv) { Tree = NULL; Detail = NULL; DefLockOut = 0; d = priv; SetParent(parent); if (LoadFromResource(IDD_PROJECT_SETTINGS)) { MoveToCenter(); auto FullPath = d->Project->GetFullPath(); if (FullPath) { SetCtrlName(IDC_PATH, FullPath); } if (GetViewById(IDC_SETTINGS, Tree)) { const char *Section = NULL; LTreeItem *SectionItem = NULL; for (SettingInfo *i = AllSettings; i->Setting; i++) { if (!SectionItem || (Section && stricmp(i->Category, Section))) { Section = i->Category; SectionItem = new LTreeItem(); SectionItem->SetText(i->Category); Tree->Insert(SectionItem); } LTreeItem *Item = new LTreeItem(); Item->SetText(i->Name); SectionItem->Insert(Item); SectionItem->Expanded(true); if (!i->Flag.PlatformSpecific) { SettingItem *All = new SettingItem(i, 0, this); All->SetText(sAllPlatforms); Item->Insert(All); } if (!i->Flag.CrossPlatform) { SettingItem *Platform = new SettingItem(i, SF_PLATFORM_SPECIFC, this); Platform->SetText(sCurrentPlatform); Item->Insert(Platform); } } } if (GetViewById(IDC_DETAIL, Detail)) { Detail->SetPriv(d); } LView *Search; if (GetViewById(IDC_SEARCH, Search)) Search->Focus(true); } } LTreeItem *FindItem(LTreeItem *i, const char *search) { const char *s = i->GetText(0); if (s && search && stristr(s, search)) return i; for (LTreeItem *c = i->GetChild(); c; c = c->GetNext()) { LTreeItem *f = FindItem(c, search); if (f) return f; } return NULL; } void OnSearch(const char *s) { if (!Tree) return; LTreeItem *f = NULL; for (LTreeItem *i = Tree->GetChild(); i && !f; i = i->GetNext()) { f = FindItem(i, s); } if (f) { f->Select(true); for (LTreeItem *i = f; i; i = i->GetParent()) i->Expanded(true); Tree->Focus(true); } } bool InsertPath(ProjSetting setting, IdePlatform platform, bool PlatformSpecific, LString newPath) { for (size_t Cfg = 0; Cfg < 2; Cfg++) { LString path = d->BuildPath(setting, PlatformSpecific ? SF_PLATFORM_SPECIFC : 0, platform, Cfg); auto t = d->Editing.GetChildTag(path); if (!t) { LgiTrace("%s:%i - Can't find element for '%s'\n", _FL, path.Get()); return false; } LString nl("\n"); auto lines = LString(t->GetContent()).Split(nl); bool has = false; for (auto ln: lines) if (ln.Equals(newPath)) has = true; if (!has) { lines.SetFixedLength(false); lines.New() = newPath; auto newVal = nl.Join(lines); t->SetContent(newVal); } } return true; } void SetDefaults() { // Find path to Lgi... IdeProject *LgiProj = NULL; if (d->Project) { LArray Nodes; if (d->Project->GetAllNodes(Nodes)) { for (auto n: Nodes) { auto dep = n->GetDep(); if (dep) { auto fn = LString(dep->GetFileName()).SplitDelimit(DIR_STR); if (fn.Last().Equals("Lgi.xml")) { LgiProj = dep; break; } } } } } #ifdef LINUX if (LgiProj) { auto lgiBase = LgiProj->GetBasePath(); LFile::Path lgiInc(lgiBase, "include"); LString lgiIncPath; if (!d->Project->RelativePath(lgiIncPath, lgiInc)) lgiIncPath = lgiInc; LFile::Path lgiLinuxInc(lgiIncPath, "lgi/linux"); LFile::Path lgiGtkInc(lgiIncPath, "lgi/linux/Gtk"); InsertPath(ProjIncludePaths, PlatformLinux, false, lgiIncPath); InsertPath(ProjIncludePaths, PlatformLinux, true, lgiLinuxInc.GetFull()); InsertPath(ProjIncludePaths, PlatformLinux, true, lgiGtkInc.GetFull()); InsertPath(ProjSystemIncludes, PlatformLinux, true, "`pkg-config --cflags gtk+-3.0`"); InsertPath(ProjLibraries, PlatformLinux, true, "pthread"); InsertPath(ProjLibraries, PlatformLinux, true, "`pkg-config --libs gtk+-3.0`"); InsertPath(ProjLibraries, PlatformLinux, true, "-static-libgcc"); } #endif } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BROWSE: { const char *s = GetCtrlName(IDC_PATH); if (s) LBrowseToFile(s); break; } case IDC_SEARCH: { if (n.Type == LNotifyReturnKey) { DefLockOut = LCurrentTime(); OnSearch(GetCtrlName(IDC_SEARCH)); } else { SetCtrlEnabled(IDC_CLEAR_SEARCH, ValidStr(Ctrl->Name())); } break; } case IDC_CLEAR_SEARCH: { SetCtrlName(IDC_SEARCH, NULL); OnSearch(NULL); break; } case IDOK: { if (LCurrentTime() - DefLockOut < 500) break; Detail->SetSetting(NULL, 0); EndModal(1); break; } case IDCANCEL: { if (LCurrentTime() - DefLockOut < 500) break; EndModal(0); break; } case IDC_DEFAULTS: { SetDefaults(); return 0; // bypass default btn handler } default: { int Id = Ctrl->GetId(); int BrowseIdx = -1; bool BrowseFolder = false; if (Id >= IDC_BROWSE_FILE && Id < IDC_BROWSE_FILE+100) { BrowseIdx = Id - IDC_BROWSE_FILE; } else if (Id >= IDC_BROWSE_FOLDER && Id < IDC_BROWSE_FOLDER+100) { BrowseIdx = Id - IDC_BROWSE_FOLDER; BrowseFolder = true; } if (BrowseIdx >= 0) { LEdit *e; if (GetViewById(IDC_EDIT_BASE + BrowseIdx, e)) { - LFileSelect s; - s.Parent(this); + LFileSelect *s = new LFileSelect; + s->Parent(this); LFile::Path Path(d->Project->GetBasePath()); LFile::Path Cur(e->Name()); Path.Join(Cur.GetFull()); Path--; if (Path.Exists()) - s.InitialDir(Path); + s->InitialDir(Path); - if (BrowseFolder ? s.OpenFolder() : s.Open()) + auto Process = [&](LFileSelect *s, bool ok) { + if (!ok) + return; const char *Base = GetCtrlName(IDC_PATH); LString Rel; if (Base) { LFile::Path p = Base; - Rel = LMakeRelativePath(--p, s.Name()); + Rel = LMakeRelativePath(--p, s->Name()); } - e->Name(Rel ? Rel.Get() : s.Name()); - } + e->Name(Rel ? Rel.Get() : s->Name()); + + delete s; + }; + + if (BrowseFolder) + s->OpenFolder(Process); + else + s->Open(Process); return 0; // no default btn handling. } } break; } } return LDialog::OnNotify(Ctrl, n); } void OnSelect(SettingItem *si) { Detail->SetSetting(si->Setting, si->Flags); } }; void SettingItem::Select(bool b) { LTreeItem::Select(b); if (b) Dlg->OnSelect(this); } IdeProjectSettings::IdeProjectSettings(IdeProject *Proj) { d = new IdeProjectSettingsPriv(this); d->Project = Proj; InitAllSettings(); } IdeProjectSettings::~IdeProjectSettings() { DeleteObj(d); } const char *IdeProjectSettings::GetCurrentConfig() { return d->CurConfig; } bool IdeProjectSettings::SetCurrentConfig(const char *Config) { int i = d->FindConfig(Config); if (i < 0) return false; d->CurConfig = d->Configs[i]; return true; } bool IdeProjectSettings::AddConfig(const char *Config) { int i = d->FindConfig(Config); if (i >= 0) return true; d->Configs.New() = Config; return true; } bool IdeProjectSettings::DeleteConfig(const char *Config) { int i = d->FindConfig(Config); if (i < 0) return false; d->Configs.DeleteAt(i, true); return true; } void IdeProjectSettings::InitAllSettings(bool ClearCurrent) { char *p; for (SettingInfo *i = AllSettings; i->Setting; i++) { LVariant Default; LXmlTag *t = NULL; switch (i->Setting) { default: break; case ProjMakefile: { char s[64]; sprintf_s(s, sizeof(s), "Makefile.%s", LGetOsName()); strlwr(s + 1); Default = s; break; } case ProjTargetType: { Default = "Executable"; break; } /* This won't work in the constructor because the IdeProject isn't initialized yet. case ProjTargetName: { if (d->Project) { char s[256]; char *Fn = d->Project->GetFileName(); char *Dir = Fn ? strrchr(Fn, DIR_CHAR) : NULL; if (Dir) { strsafecpy(s, ++Dir, sizeof(s)); char *Dot = strrchr(s, '.'); #ifdef WIN32 strcpy(Dot, ".exe"); #else if (Dot) *Dot = 0; #endif Default = s; } } break; } */ case ProjEditorTabSize: { Default = 4; break; } case ProjEditorIndentSize: { Default = 4; break; } case ProjEditorShowWhiteSpace: { Default = false; break; } case ProjEditorUseHardTabs: { Default = true; break; } } if (i->Flag.ConfigSpecific) { for (int Cfg = 0; Cfg < d->Configs.Length(); Cfg++) { if (!i->Flag.PlatformSpecific) { p = d->BuildPath(i->Setting, 0, PlatformCurrent, Cfg); d->Active.GetChildTag(p, true); if (t && !t->GetContent() && Default.Type != GV_NULL) t->SetContent(Default.Str()); } if (!i->Flag.CrossPlatform) { p = d->BuildPath(i->Setting, SF_PLATFORM_SPECIFC, PlatformCurrent, Cfg); d->Active.GetChildTag(p, true); if (t && !t->GetContent() && Default.Type != GV_NULL) t->SetContent(Default.Str()); } } } else { if (!i->Flag.PlatformSpecific) { p = d->BuildPath(i->Setting, 0, PlatformCurrent, -1); t = d->Active.GetChildTag(p, true); if (t && !t->GetContent() && Default.Type != GV_NULL) t->SetContent(Default.Str()); } if (!i->Flag.CrossPlatform) { p = d->BuildPath(i->Setting, SF_PLATFORM_SPECIFC, PlatformCurrent, -1); t = d->Active.GetChildTag(p, true); if (t && !t->GetContent() && Default.Type != GV_NULL) t->SetContent(Default.Str()); } } } } -bool IdeProjectSettings::Edit(LViewI *parent) +void IdeProjectSettings::Edit(LViewI *parent, std::function OnChanged) { // Copy all the settings to the edit tag... d->Editing.Copy(d->Active, true); // Show the dialog... - ProjectSettingsDlg Dlg(parent, d); - bool Changed = Dlg.DoModal(); - if (Changed) + auto *Dlg = new ProjectSettingsDlg(parent, d); + Dlg->DoModal([this,OnChanged](auto dlg, auto code) { - // User elected to save settings... so copy all the values - // back over to the active settings... - d->Active.Copy(d->Editing, true); - d->Editing.Empty(true); - } - - return Changed; + if (code) + { + // User elected to save settings... so copy all the values + // back over to the active settings... + d->Active.Copy(d->Editing, true); + d->Editing.Empty(true); + + if (OnChanged) + OnChanged(); + } + + delete dlg; + }); } bool IdeProjectSettings::Serialize(LXmlTag *Parent, bool Write) { if (!Parent) { LAssert(!"No parent tag?"); return false; } LXmlTag *t = Parent->GetChildTag(TagSettings, Write); if (!t) { LAssert(!"Can't find settings tags?"); return false; } if (Write) { // d->Active -> Parent ClearEmptyTags(&d->Active); return t->Copy(d->Active, true); } else { // Parent -> d->Active bool Status = d->Active.Copy(*t, true); InitAllSettings(); return Status; } return false; } const char *IdeProjectSettings::GetStr(ProjSetting Setting, const char *Default, IdePlatform Platform) { SettingInfo *s = d->Map.Find(Setting); LAssert(s); LArray Strs; int Bytes = 0; if (!s->Flag.PlatformSpecific) { LXmlTag *t = d->Active.GetChildTag(d->BuildPath(Setting, 0, Platform)); if (t && t->GetContent()) { Strs.Add(t->GetContent()); Bytes += strlen(t->GetContent()) + 1; } } if (!s->Flag.CrossPlatform) { LXmlTag *t = d->Active.GetChildTag(d->BuildPath(Setting, SF_PLATFORM_SPECIFC, Platform)); if (t && t->GetContent()) { Strs.Add(t->GetContent()); Bytes += strlen(t->GetContent()) + 1; } } if (Strs.Length() == 0) return Default; if (Strs.Length() == 1) return Strs[0]; if (!d->StrBuf.Reset(new char[Bytes])) return Default; // char *c = d->StrBuf; int ch = 0; for (int i=0; iStrBuf + ch, Bytes - ch, "%s%s", Strs[i], iStrBuf; } int IdeProjectSettings::GetInt(ProjSetting Setting, int Default, IdePlatform Platform) { int Status = Default; SettingInfo *s = d->Map.Find(Setting); LAssert(s); if (!s->Flag.PlatformSpecific) { LXmlTag *t = d->Active.GetChildTag(d->BuildPath(Setting, 0, Platform)); if (t) { Status = t->GetContent() ? atoi(t->GetContent()) : 0; } } else if (!s->Flag.CrossPlatform) { LXmlTag *t = d->Active.GetChildTag(d->BuildPath(Setting, SF_PLATFORM_SPECIFC, Platform)); if (t) { Status = t->GetContent() ? atoi(t->GetContent()) : 0; } } else LAssert(0); return Status; } bool IdeProjectSettings::Set(ProjSetting Setting, const char *Value, IdePlatform Platform, bool PlatformSpecific) { bool Status = false; char *path = d->BuildPath(Setting, PlatformSpecific ? SF_PLATFORM_SPECIFC : 0, Platform); LXmlTag *t = d->Active.GetChildTag(path, true); if (t) { t->SetContent(Value); } else LgiTrace("%s:%i - Warning: missing setting tag '%s'\n", _FL, path); return Status; } bool IdeProjectSettings::Set(ProjSetting Setting, int Value, IdePlatform Platform, bool PlatformSpecific) { bool Status = false; char *path = d->BuildPath(Setting, PlatformSpecific ? SF_PLATFORM_SPECIFC : 0, Platform); LXmlTag *t = d->Active.GetChildTag(path, true); if (t) { t->SetContent(Value); } else LgiTrace("%s:%i - Warning: missing setting tag '%s'\n", _FL, path); return Status; } diff --git a/Ide/Code/IdeProjectSettings.h b/Ide/Code/IdeProjectSettings.h --- a/Ide/Code/IdeProjectSettings.h +++ b/Ide/Code/IdeProjectSettings.h @@ -1,65 +1,67 @@ #ifndef _IDE_PROJECT_SETTINGS_H_ #define _IDE_PROJECT_SETTINGS_H_ +#include + enum ProjSetting { ProjNone, ProjMakefile, ProjExe, ProjArgs, ProjDebugAdmin, ProjDefines, ProjCompiler, ProjCCrossCompiler, ProjCppCrossCompiler, ProjIncludePaths, ProjSystemIncludes, ProjLibraries, ProjLibraryPaths, ProjTargetType, ProjTargetName, ProjEditorTabSize, ProjEditorIndentSize, ProjEditorShowWhiteSpace, ProjEditorUseHardTabs, ProjCommentFile, ProjCommentFunction, ProjMakefileRules, ProjApplicationIcon, ProjPostBuildCommands, ProjRemoteUri, ProjRemotePass, ProjEnv, ProjInitDir }; class IdeProjectSettings { struct IdeProjectSettingsPriv *d; public: IdeProjectSettings(IdeProject *Proj); ~IdeProjectSettings(); void InitAllSettings(bool ClearCurrent = false); // Configuration const char *GetCurrentConfig(); bool SetCurrentConfig(const char *Config); bool AddConfig(const char *Config); bool DeleteConfig(const char *Config); // UI - bool Edit(LViewI *parent); + void Edit(LViewI *parent, std::function OnChanged); // Serialization bool Serialize(LXmlTag *Parent, bool Write); // Accessors const char *GetStr(ProjSetting Setting, const char *Default = NULL, IdePlatform Platform = PlatformCurrent); int GetInt(ProjSetting Setting, int Default = 0, IdePlatform Platform = PlatformCurrent); bool Set(ProjSetting Setting, const char *Value, IdePlatform Platform = PlatformCurrent, bool PlatformSpecific = false); bool Set(ProjSetting Setting, int Value, IdePlatform Platform = PlatformCurrent, bool PlatformSpecific = false); }; #endif \ No newline at end of file diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4495 +1,4678 @@ #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(); + 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; + LSubMenu *RecentFilesMenu = NULL; LString::Array RecentProjects; - LSubMenu *RecentProjectsMenu; + 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; - RecentFilesMenu = 0; - RecentProjectsMenu = 0; Icons = LLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { - if (RecentFilesMenu) - RecentFilesMenu->Empty(); - if (RecentProjectsMenu) - RecentProjectsMenu->Empty(); - FindSym.Reset(); Finder.Reset(); if (Output) Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); - Docs.DeleteObjects(); - Projects.DeleteObjects(); + 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, 1300, 900); + 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); + 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(); - CloseAll(); - - LAppInst->AppWnd = 0; + 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(); } for (auto n: NeedsPulse) n->OnPulse(); } 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 Raw = p.NewGStr().Replace("\r", ""); auto Sect = Raw.Split("\n\n"); #if 0 // Debug output Exp = Raw; #else if (IsLib) { for (auto &s : Sect) { if (s.Find("COFF/PE Dumper") >= 0) continue; auto ln = s.Split("\n"); if (ln.Length() == 1) continue; for (auto &l: ln) l = l.LStrip(); Exp = LString("\n").Join(ln); break; } } 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; } } #endif 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(""); } } -void AppWnd::SaveAll() +bool AppWnd::IsClean() { - List::I Docs = d->Docs.begin(); - for (IdeDoc *Doc = *Docs; Doc; Doc = *++Docs) + for (auto Doc: d->Docs) { - Doc->SetClean(); - d->OnFile(Doc->GetFileName()); + if (!Doc->GetClean()) + return false; + } + + for (auto Proj: d->Projects) + { + if (!Proj->GetClean()) + return false; } - List::I Projs = d->Projects.begin(); - for (IdeProject *Proj = *Projs; Proj; Proj = *++Projs) + return true; +} + +struct SaveState +{ + AppWndPrivate *d = NULL; + LArray Docs; + LArray Projects; + std::function Callback; + bool Status = true; + bool CloseDirty = false; + + void Iterate() { - Proj->SetClean(); - d->OnFile(Proj->GetFileName(), true); + 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(); - while (d->Docs[0]) - delete d->Docs[0]; - - IdeProject *p = RootProject(); - if (p) - DeleteObj(p); + 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); - while (d->Projects[0]) - delete d->Projects[0]; - - DeleteObj(d->DbgContext); -} - -bool AppWnd::OnRequestClose(bool IsClose) -{ - SaveAll(); - return LWindow::OnRequestClose(IsClose); + 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 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) probablyLgiProj = 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() { - IdeProject *Root = 0; - for (auto p: d->Projects) - { if (!p->GetParentProject()) - { - LAssert(Root == 0); - Root = p; - } - } + return p; - return Root; + 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; - d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); + 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); - - DoModal(); } } 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)); + // Fall through.. } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { - if (Font.DoUI(this)) + 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(); - - IdeDoc *Top; - IdeProject *p = RootProject(); - if (p) - { - UpdateState(-1, true); - - p->Build(false, GetBuildMode()); - - return true; - } - else if ((Top = TopDoc())) - { - return Top->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: { - Options Dlg(this); + 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"); + auto dlg = new LAbout( this, + AppName, APP_VER, + "\nLGI Integrated Development Environment", + "icon128.png", + APP_URL, + "fret@memecode.com"); + dlg->DoModal(NULL); 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; - s.Parent(this); - if (s.Open()) + LFileSelect *s = new LFileSelect; + s->Parent(this); + + // printf("File open dlg from thread=%u\n", GetCurrentThreadId()); + s->Open([&](auto s, auto ok) { - OpenFile(s.Name()); - } + // 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(); + SaveAll(NULL); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) - Top->SetClean(); + Top->SetClean(NULL); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { - LFileSelect s; - s.Parent(this); - if (s.Save()) + LFileSelect *s = new LFileSelect; + s->Parent(this); + s->Save([&](auto s, auto ok) { - Top->SetFileName(s.Name(), true); - d->OnFile(s.Name()); - } + 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(); + Doc->DoFind(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { LTextView3 *Doc = FocusEdit(); if (Doc) { - Doc->DoFindNext(); + Doc->DoFindNext(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { LTextView3 *Doc = FocusEdit(); if (Doc) { - Doc->DoReplace(); + Doc->DoReplace(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { LTextView3 *Doc = FocusEdit(); if (Doc) - Doc->DoGoto(); + Doc->DoGoto(NULL); else { - LInput Inp(this, NULL, LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); - if (Inp.DoModal()) + LInput *Inp = new LInput(this, NULL, LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); + Inp->DoModal([Inp,this](auto dlg, auto code) { - LString s = Inp.GetStr(); + 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 dlg; + }); } 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; } - if (Dlg.DoModal()) + 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 { - FindSymResult r = d->FindSym->OpenSearchDlg(this); - if (r.File) + d->FindSym->OpenSearchDlg(this, [&](auto r) { - GotoReference(r.File, r.Line, false); - } + if (r.File) + GotoReference(r.File, r.Line, false); + }); } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { - FindInProject Dlg(this); - Dlg.DoModal(); + 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; - s.Parent(this); - s.Type("Projects", "*.xml"); - if (s.Open()) + LFileSelect *s = new LFileSelect; + s->Parent(this); + s->Type("Projects", "*.xml"); + s->Open([&](auto s, auto ok) { - CloseAll(); - OpenProject(s.Name(), NULL, Cmd == IDM_NEW_PROJECT); - if (d->Tree) + if (ok) { - d->Tree->Focus(true); + 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; - s.Parent(this); - s.Type("Developer Studio Project", "*.dsp"); - if (s.Open()) + LFileSelect *s = new LFileSelect; + s->Parent(this); + s->Type("Developer Studio Project", "*.dsp"); + s->Open([&](auto s, auto ok) { - p->ImportDsp(s.Name()); - } + if (ok) + p->ImportDsp(s->Name()); + delete s; + }); } break; } case IDM_RUN: { - SaveAll(); - IdeProject *p = RootProject(); - if (p) + SaveAll([&](bool status) { - p->Execute(); - } + if (!status) + { + LgiTrace("%s:%i - status=%i\n", _FL, status); + return; + } + + IdeProject *p = RootProject(); + if (p) + p->Execute(); + }); break; } case IDM_VALGRIND: { - SaveAll(); - IdeProject *p = RootProject(); - if (p) + SaveAll([&](bool status) { - p->Execute(ExeValgrind); - } + 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(); - IdeProject *p = RootProject(); - if (!p) - { - LgiMsg(this, "No project loaded.", "Error"); - break; - } - - LString ErrMsg; - if (d->DbgContext) - { - d->DbgContext->OnCommand(IDM_CONTINUE); - } - else if ((d->DbgContext = p->Execute(ExeDebug, &ErrMsg))) + SaveAll([&](bool status) { - 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; + if (!status) + { + LgiTrace("%s:%i - status=%i\n", _FL, status); + return; + } - d->DbgContext->OnCommand(IDM_START_DEBUG); + IdeProject *p = RootProject(); + if (!p) + { + LgiMsg(this, "No project loaded.", "Error"); + return; + } + + 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->Output->Value(AppWnd::DebugTab); - d->Output->DebugEdit->Focus(true); - } - else if (ErrMsg) - { - LgiMsg(this, "Error: %s", AppName, MB_OK, ErrMsg.Get()); - } + 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(); - IdeProject *p = RootProject(); - if (p) - p->Clean(true, GetBuildMode()); + 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(this, "", "Separator:", AppName); - if (!i.DoModal()) - break; - - Doc->SplitSelection(i.GetStr()); + 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(this, "", "Separator:", AppName); - if (!i.DoModal()) - break; - - Doc->JoinSelection(i.GetStr()); + 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: { - LString r = d->RecentFiles[Cmd - IDM_RECENT_FILE]; + int index = Cmd - IDM_RECENT_FILE; + auto r = d->RecentFiles.IdxCheck(index)? d->RecentFiles[index] : LString(); if (r) { auto idx = Cmd - IDM_RECENT_FILE; if (idx > 0) { d->RecentFiles.DeleteAt(idx, true); d->RecentFiles.AddAt(0, r); } IdeDoc *f = d->IsFileOpen(r); if (f) f->Raise(); else OpenFile(r); } - LString p = d->RecentProjects[Cmd - IDM_RECENT_PROJECT]; + index = Cmd - IDM_RECENT_PROJECT; + auto p = d->RecentProjects.IdxCheck(index) ? d->RecentProjects[index] : LString(); if (p) { auto idx = Cmd - IDM_RECENT_PROJECT; if (idx > 0) { d->RecentProjects.DeleteAt(idx, true); d->RecentProjects.AddAt(0, 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) { - d->Projects.Delete(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::GetOutputLog() { return d->Output->Txt[AppWnd::OutputTab]; } 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, 110); + 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->Rectangle(); - pdc->Line(0, 0, X()-1, Y()-1); - pdc->Circle(50, 50, 50); + 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); + // 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()) { LPoint dpi = LScreenDpi(); a.AppWnd = new AppWnd; + // a.AppWnd->_Dump(); a.Run(); } return 0; } diff --git a/Ide/Code/LgiIde.h b/Ide/Code/LgiIde.h --- a/Ide/Code/LgiIde.h +++ b/Ide/Code/LgiIde.h @@ -1,343 +1,346 @@ #ifndef _LGI_IDE_H_ #define _LGI_IDE_H_ // // Compiler specific macros: // // Microsoft Visual C++: _MSC_VER // // GCC: __GNUC__ // #include "lgi/common/DocView.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/StringClass.h" #include "lgi/common/TextView3.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "resdefs.h" #include "FindSymbol.h" #include "Debugger.h" #ifdef WIN32 #include "resource.h" #endif #define APP_VER "1.0" -#define APP_URL "http://www.memecode.com/lgi/ide" +#define APP_URL "http://www.memecode.com/lgi/ide/" #define DEBUG_FIND_DEFN 0 #define OptFileSeparator "\n" enum IdeMessages { M_APPEND_TEXT = M_USER+200, // A=(char*)heapStr, B=(int)tabIndex M_APPEND_STR, M_START_BUILD, M_BUILD_ERR, M_BUILD_DONE, M_DEBUG_ON_STATE, M_MAKEFILES_CREATED, M_LAST_MAKEFILE_CREATED, M_SELECT_TAB, // A=(int)tabIndex /// Find symbol results message: /// LAutoPtr Req((FindSymRequest*)Msg->A()); M_FIND_SYM_REQUEST, /// Send a file to the worker thread... /// FindSymbolSystem::SymFileParams *Params = (FindSymbolSystem::SymFileParams*)Msg->A(); M_FIND_SYM_FILE, /// Send a file to the worker thread... /// LAutoPtr Paths((LString::Array*)Msg->A()); M_FIND_SYM_INC_PATHS, /// Styling is finished M_STYLING_DONE, }; #define ICON_PROJECT 0 #define ICON_DEPENDANCY 1 #define ICON_FOLDER 2 #define ICON_SOURCE 3 #define ICON_HEADER 4 #define ICON_RESOURCE 5 #define ICON_GRAPHIC 6 #define ICON_WEB 7 enum IdeIcon { CMD_NEW, CMD_OPEN, CMD_SAVE, CMD_CUT, CMD_COPY, CMD_PASTE, CMD_COMPILE, CMD_BUILD, CMD_STOP_BUILD, CMD_EXECUTE, CMD_DEBUG, CMD_PAUSE, CMD_KILL, CMD_RESTART, CMD_RUN_TO, CMD_STEP_INTO, CMD_STEP_OVER, CMD_STEP_OUT, CMD_FIND_IN_FILES, CMD_SEARCH, CMD_SAVE_ALL, }; enum IdeControls { IDC_STATIC = -1, IDC_WATCH_LIST = 700, IDC_LOCALS_LIST, IDC_DEBUG_EDIT, IDC_DEBUGGER_LOG, IDC_BUILD_LOG, IDC_OUTPUT_LOG, IDC_FIND_LOG, IDC_CALL_STACK, IDC_DEBUG_TAB, IDC_OBJECT_DUMP, IDC_MEMORY_DUMP, IDC_MEMORY_TABLE, IDC_MEM_ADDR, IDC_MEM_SIZE, IDC_MEM_ROW_LEN, IDC_MEM_HEX, IDC_REGISTERS, IDC_THREADS, IDC_EDIT, IDC_FILE_SEARCH, IDC_METHOD_SEARCH, IDC_SYMBOL_SEARCH, IDC_PROJECT_TREE, IDC_ALL_PLATFORMS, }; enum IdeMenuCmds { IDM_CONTINUE = 900 }; enum BuildConfig { BuildDebug, BuildRelease, BuildMax }; inline const char *toString(BuildConfig c) { switch (c) { default: case BuildDebug: return "Debug"; case BuildRelease: return "Release"; } return "#err"; } #define OPT_EditorFont "EdFont" #define OPT_Jobs "Jobs" ////////////////////////////////////////////////////////////////////// // Platform stuff enum IdePlatform { PlatformCurrent = -1, PlatformWin = 0, // 0x1 PlatformLinux, // 0x2 PlatformMac, // 0x4 PlatformHaiku, // 0x8 PlatformMax, }; #define PLATFORM_WIN32 (1 << PlatformWin) #define PLATFORM_LINUX (1 << PlatformLinux) #define PLATFORM_MAC (1 << PlatformMac) #define PLATFORM_HAIKU (1 << PlatformHaiku) #define PLATFORM_ALL (PLATFORM_WIN32|PLATFORM_LINUX|PLATFORM_MAC|PLATFORM_HAIKU) #if defined(_WIN32) #define PLATFORM_CURRENT PLATFORM_WIN32 #elif defined(MAC) #define PLATFORM_CURRENT PLATFORM_MAC #elif defined(LINUX) #define PLATFORM_CURRENT PLATFORM_LINUX #elif defined(HAIKU) #define PLATFORM_CURRENT PLATFORM_HAIKU #endif extern const char *PlatformNames[]; extern const char sCurrentPlatform[]; extern const char *Untitled; extern const char SourcePatterns[]; ////////////////////////////////////////////////////////////////////// class IdeDoc; class IdeProject; extern char AppName[]; extern char *FindHeader(char *Short, LArray &Paths); extern bool BuildHeaderList(char *Cpp, LArray &Headers, LArray &IncPaths, bool Recurse); class NodeView; class NodeSource { friend class NodeView; protected: NodeView *nView; public: NodeSource() { nView = 0; } virtual ~NodeSource(); virtual LString GetFullPath() = 0; virtual bool IsWeb() = 0; virtual const char *GetFileName() = 0; virtual const char *GetLocalCache() = 0; virtual const char *GetCharset() = 0; virtual bool Load(LDocView *Edit, NodeView *Callback) = 0; virtual bool Save(LDocView *Edit, NodeView *Callback) = 0; virtual IdeProject *GetProject() = 0; }; class NodeView { friend class NodeSource; protected: NodeSource *nSrc; public: NodeView(NodeSource *s) { if ((nSrc = s)) { nSrc->nView = this; } } virtual ~NodeView(); virtual void OnDelete() = 0; virtual void OnSaveComplete(bool Status) = 0; NodeSource *GetSrc() { return nSrc; } }; class AppWnd : public LWindow { class AppWndPrivate *d; friend class AppWndPrivate; void UpdateMemoryDump(); void DumpHistory(); public: enum Channels { BuildTab, OutputTab, FindTab, FtpTab, DebugTab, ChannelMax, }; enum DebugTabs { LocalsTab, ObjectTab, WatchTab, MemoryTab, ThreadsTab, CallStackTab, RegistersTab }; LArray NeedsPulse; AppWnd(); ~AppWnd(); + + const char *GetClass() override { return "AppWnd"; } - void SaveAll(); + bool IsClean(); + void SaveAll(std::function Callback, bool CloseDirty = false); void CloseAll(); IdeDoc *OpenFile(const char *FileName, NodeSource *Src = 0); IdeDoc *NewDocWnd(const char *FileName, NodeSource *Src); IdeDoc *GetCurrentDoc(); IdeProject *OpenProject(const char *FileName, IdeProject *ParentProj, bool Create = false, bool Dep = false); IdeProject *RootProject(); IdeDoc *TopDoc(); IdeDoc *FocusDoc(); LTextView3 *FocusEdit(); void AppendOutput(char *Txt, Channels Channel); int OnFixBuildErrors(); void OnBuildStateChanged(bool NewState); void UpdateState(int Debugging = -1, int Building = -1); void OnReceiveFiles(LArray &Files) override; BuildConfig GetBuildMode(); LTree *GetTree(); LOptionsFile *GetOptions(); LList *GetFtpLog(); LStream *GetOutputLog(); LStream *GetBuildLog(); LStream *GetDebugLog(); IdeDoc *FindOpenFile(char *FileName); IdeDoc *GotoReference(const char *File, int Line, bool CurIp, bool WithHistory = true); void FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms); bool GetSystemIncludePaths(LArray &Paths); bool ShowInProject(const char *Fn); bool Build(); void SeekHistory(int Offset); // Events void OnLocationChange(const char *File, int Line); int OnCommand(int Cmd, int Event, OsView Wnd) override; void OnDocDestroy(IdeDoc *Doc); void OnProjectDestroy(IdeProject *Proj); void OnProjectChange(); void OnFile(char *File, bool IsProject = false); bool OnRequestClose(bool IsClose) override; int OnNotify(LViewI *Ctrl, LNotification n) override; LMessage::Result OnEvent(LMessage *m) override; bool OnNode(const char *Path, class ProjectNode *Node, FindSymbolSystem::SymAction Action); void OnPulse() override; // Debugging support class LDebugContext *GetDebugContext(); bool ToggleBreakpoint(const char *File, ssize_t Line); bool OnBreakPoint(LDebugger::BreakPoint &b, bool Add); bool LoadBreakPoints(IdeDoc *doc); bool LoadBreakPoints(LDebugger *db); void OnDebugState(bool Debugging, bool Running); }; #include "IdeDoc.h" #include "IdeProject.h" #include "FindInFiles.h" extern void NewMemDumpViewer(AppWnd *App, const char *file = 0); extern void NewProjectFromTemplate(LViewI *parent); class SysCharSupport : public LWindow { class SysCharSupportPriv *d; public: SysCharSupport(AppWnd *app); ~SysCharSupport(); int OnNotify(LViewI *v, LNotification n); void OnPosChange(); }; #endif diff --git a/Ide/Code/MemDumpViewer.cpp b/Ide/Code/MemDumpViewer.cpp --- a/Ide/Code/MemDumpViewer.cpp +++ b/Ide/Code/MemDumpViewer.cpp @@ -1,388 +1,389 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/Edit.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/List.h" #include "lgi/common/Splitter.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #define IDC_LIST 100 class DumpItem : public LListItem { public: int Size; int Count; char *Alloc; char *Stack; DumpItem() { Size = Count = 0; Alloc = Stack = 0; } ~DumpItem() { DeleteArray(Alloc); DeleteArray(Stack); } const char *GetText(int c) { static char s[64]; switch (c) { case 0: { LFormatSize(s, sizeof(s), Size); return s; break; } case 1: { return Alloc; break; } case 2: { sprintf(s, "%i", Count); return s; break; } } return 0; } }; int Cmp(LListItem *A, LListItem *B, NativeInt d) { DumpItem *a = (DumpItem*) A; DumpItem *b = (DumpItem*) B; switch (d) { case 0: { return b->Size - a->Size; } case 1: { return stricmp(a->Alloc, b->Alloc); } case 2: { return b->Count - a->Count; } } return 0; } /* char *Strnstr(char *s, const char *find, int len) { if (!s || !find || len < 0) return 0; char *End = s + len; auto FindLen = strlen(find); while (s < End) { if (*s == *find) { bool match = true; char *to = s + FindLen; if (to > End) return 0; for (int n=1; nValue(400); Split->IsVertical(false); Split->SetViewA(Lst = new LList(IDC_LIST, 0, 0, 100, 100), false); Lst->AddColumn("Size", 200); Lst->AddColumn("Location", 300); Lst->AddColumn("Count", 100); Split->SetViewB(Ed = new LEdit(101, 0, 0, 100, 100, ""), false); Ed->Enabled(false); Ed->MultiLine(true); AttachChildren(); Visible(true); if (file) Load(file); else { - LFileSelect s; - s.Parent(this); - s.Type("Dump", "*.mem"); - s.Type("All Files", LGI_ALL_FILES); - if (s.Open()) + LFileSelect *s = new LFileSelect; + s->Parent(this); + s->Type("Dump", "*.mem"); + s->Type("All Files", LGI_ALL_FILES); + s->Open([&](auto s, auto ok) { - Load(s.Name()); - } + if (ok) + Load(s->Name()); + delete s; + }); } } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_LIST: { switch (n.Type) { case LNotifyItemSelect: { LListItem *s = Lst->GetSelected(); if (s) { DumpItem *di = dynamic_cast(s); if (di && Ed) { Ed->Name(di->Stack); } } break; } case LNotifyItemColumnClicked: { int Col; LMouse m; if (Lst->GetColumnClickInfo(Col, m)) { Lst->Sort(Cmp, Col); } break; } default: break; } break; } } return 0; } void Load(const char *File) { LHashTbl, bool> Except(0, false); Except.Add("LString.cpp", true); Except.Add("LVariant.cpp", true); Except.Add("LContainers.cpp", true); Except.Add("LContainers.h", true); Except.Add("LFile.cpp", true); Except.Add("Mail.h", true); Except.Add("LArray.h", true); LFile f; if (!f.Open(File, O_READ)) LgiMsg(this, "Couldn't read '%s'", AppName, MB_OK, File); else { LProgressDlg Prog(this, true); LArray Buf; Buf.Length(1 << 20); ssize_t Pos = 0, Used = 0; bool First = true; char s[512]; LHashTbl,DumpItem*> h; Prog.SetDescription("Reading memory dump..."); Prog.SetRange(f.GetSize()); Prog.SetScale(1.0 / 1024.0 / 1024.0); Prog.SetType("MB"); while (true) { // Consume data auto Len = Used - Pos; char *Cur = &Buf[Pos]; char *End = Strnstr(Cur, "\r\n\r\n", Len); if (End) { if (First) { First = false; LToken t(Cur, " \t\r\n", true, End-Cur); char *Blocks = t[0]; if (Blocks) { char *c = strchr(Blocks, ':'); if (c) *c = 0; sprintf(s, "%s (%s)", File, t[0]); Name(s); } else break; } else { int Size = 0; LToken Lines(Cur, "\r\n", true, End - Cur); LArray Stack; for (int i=0; i 0) { char *Alloc = 0; for (int k=0; kAlloc = NewStr(Alloc); LStringPipe p; for (int k=0; kStack = p.NewStr(); } } if (di) { di->Count++; di->Size += Size; } } else { LAssert(0); } Stack.Length(0); Size = 0; } } Pos = End - &Buf[0] + 4; } else { // Update status Prog.Value(f.GetPos()); - LYield(); // Read more data in? memmove(&Buf[0], &Buf[Pos], Used - Pos); Used -= Pos; Pos = 0; auto r = f.Read(&Buf[Used], Buf.Length() - Used); if (r <= 0) break; Used += r; } } List Items; // for (void *p = h.First(); p; p = h.Next()) for (auto p : h) { Items.Insert((DumpItem*)p.value); } Lst->Insert(Items); Lst->Sort(Cmp); } } }; void NewMemDumpViewer(AppWnd *App, const char *File) { new DumpView(App, File); } diff --git a/Ide/Code/MissingFiles.cpp b/Ide/Code/MissingFiles.cpp --- a/Ide/Code/MissingFiles.cpp +++ b/Ide/Code/MissingFiles.cpp @@ -1,400 +1,402 @@ #include "lgi/common/Lgi.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #include "ProjectNode.h" #include "levenshtein.h" enum Msgs { M_CHECK_FILE = M_USER + 100, M_MISSING_FILE, M_ADD_SEARCH_PATH, M_SEARCH, M_RESULTS, M_RECURSE, }; struct SearchResults { ProjectNode *Node; LString Path; LString::Array Matches; SearchResults() { Node = 0; } }; class FileExistsThread : public LEventTargetThread { int Hnd; public: FileExistsThread(int hnd) : LEventTargetThread("FileExistsThread") { Hnd = hnd; } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CHECK_FILE: { LAutoPtr Sr((SearchResults*)Msg->A()); bool e = LFileExists(Sr->Path); // printf("Checking '%s' = %i\n", Sr->Path.Get(), e); if (!e) PostObject(Hnd, M_MISSING_FILE, Sr); break; } } return 0; } }; bool IsParentFolder(LString p, LString c) { LString::Array d1 = p.Split(DIR_STR); LString::Array d2 = c.Split(DIR_STR); if (d1.Length() > d2.Length()) return false; for (unsigned i=0; i Search; LArray Files; public: SearchThread(int hnd) : LEventTargetThread("SearchThread") { Hnd = hnd; } ~SearchThread() { Files.DeleteArrays(); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_ADD_SEARCH_PATH: { LAutoPtr p((LString*)Msg->A()); bool IsParent = false; for (unsigned i=0; iEquals(Search[i]) || (IsParent = IsParentFolder(Search[i], *p))) { printf("'%s' is parent of '%s'\n", Search[i].Get(), p->Get()); break; } } if (!IsParent) { printf("Adding '%s'\n", p->Get()); Search.New() = *p; } break; } case M_RECURSE: { for (unsigned i=0; i Ext; Ext.Add("*.h"); Ext.Add("*.hpp"); Ext.Add("*.c"); Ext.Add("*.cpp"); Ext.Add("*.mm"); // printf("Recursing '%s'\n", Search[i].Get()); LRecursiveFileSearch(Search[i], &Ext, &Files); } break; } case M_SEARCH: { LAutoPtr Sr((SearchResults*)Msg->A()); char *leaf1 = LGetLeaf(Sr->Path); for (unsigned i=0; iMatches.New() = Files[i]; } } printf("Posting '%s' with %i results.\n", Sr->Path.Get(), (int)Sr->Matches.Length()); PostObject(Hnd, M_RESULTS, Sr); break; } } return 0; } }; class MissingFiles : public LDialog { IdeProject *Proj; int SearchHnd; int ExistsHnd; LList *Lst; LArray Files; public: MissingFiles(IdeProject *proj) { Proj = proj; SearchHnd = -1; ExistsHnd = -1; Lst = NULL; SetParent(Proj->GetApp()); if (LoadFromResource(IDD_MISSING_FILES)) { GetViewById(IDC_RESULTS, Lst); MoveSameScreen(Proj->GetApp()); SetCtrlEnabled(IDC_MISSING, false); SetCtrlEnabled(IDC_RESULTS, false); SetCtrlEnabled(IDC_BROWSE, false); ExistsHnd = (new FileExistsThread(AddDispatch()))->GetHandle(); SearchHnd = (new SearchThread(AddDispatch()))->GetHandle(); LHashTbl,bool> Flds; List Child; Proj->GetChildProjects(Child); Child.Add(Proj); LArray Nodes; for (auto p: Child) { if (p->GetAllNodes(Nodes)) { for (auto Node: Nodes) { auto s = Node->GetFullPath(); if (s) { LString sOld = s.Get(); if (p->CheckExists(s) && Strcmp(sOld.Get(), s.Get()) != 0 && Stricmp(sOld.Get(), s.Get()) == 0) { // Case change? Node->SetFileName(s); } SearchResults *Sr = new SearchResults; Sr->Node = Node; Sr->Path = s; PostThreadEvent(ExistsHnd, M_CHECK_FILE, (LMessage::Param) Sr); LString Parent = s.Get(); LTrimDir(Parent); Flds.Add(Parent, true); } } } auto s = p->GetBasePath(); if (s) { LTrimDir(s); Flds.Add(s, true); } } for (auto i: Flds) PostThreadEvent(SearchHnd, M_ADD_SEARCH_PATH, (LMessage::Param) new LString(i.key)); PostThreadEvent(SearchHnd, M_RECURSE); } } ~MissingFiles() { if (SearchHnd >= 0) LEventSinkMap::Dispatch.CancelThread(SearchHnd); if (ExistsHnd >= 0) LEventSinkMap::Dispatch.CancelThread(ExistsHnd); } void OnReplace(const char *NewPath) { SearchResults *Sr = Files.First(); if (!Sr) return; printf("%s:%i - Setting node to '%s'\n", _FL, Sr->Path.Get()); Sr->Node->SetFileName(NewPath); Files.DeleteAt(0, true); delete Sr; OnFile(); } void OnDelete() { SearchResults *Sr = Files.First(); if (!Sr) return; bool Has = Proj->HasNode(Sr->Node); if (Has) Sr->Node->Delete(); Files.DeleteAt(0, true); if (Has) delete Sr; OnFile(); } void OnFile() { bool Has = Files.Length() > 0; SetCtrlEnabled(IDC_MISSING, Has); SetCtrlEnabled(IDC_BROWSE, Has); SetCtrlEnabled(IDC_RESULTS, Has); Lst->Empty(); if (Has) { SetCtrlName(IDC_MISSING, Files[0]->Path); for (unsigned i=0; iMatches.Length(); i++) { LListItem *li = new LListItem; li->SetText(Files[0]->Matches[i]); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } else { SetCtrlName(IDC_MISSING, NULL); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BROWSE: { - LFileSelect s; + LFileSelect *s = new LFileSelect; LAutoString Dir = Proj->GetBasePath(); - s.Parent(this); - s.InitialDir(Dir); - if (s.Open()) + s->Parent(this); + s->InitialDir(Dir); + s->Open([&](auto s, auto ok) { - OnReplace(s.Name()); - } + if (ok) + OnReplace(s->Name()); + delete s; + }); break; } case IDC_DELETE: { OnDelete(); break; } case IDC_RESULTS: { switch (n.Type) { case LNotifyItemDoubleClick: { LListItem *li = Lst->GetSelected(); if (li) { OnReplace(li->GetText(0)); } break; } default: break; } break; } case IDOK: { EndModal(); break; } } return 0; } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_MISSING_FILE: { LAutoPtr Sr((SearchResults*)Msg->A()); if (Sr) { printf("Missing file '%s'\n", Sr->Path.Get()); PostThreadEvent(SearchHnd, M_SEARCH, (LMessage::Param) Sr.Release()); } break; } case M_RESULTS: { SearchResults *Sr = (SearchResults*)Msg->A(); bool HasNode = false; for (unsigned i=0; iNode == Sr->Node) { LgiTrace("%s:%i - Node already in Files.\n", _FL); HasNode = true; break; } } if (!HasNode) { Files.Add((SearchResults*)Msg->A()); OnFile(); } break; } } return LDialog::OnEvent(Msg); } }; void FixMissingFilesDlg(IdeProject *Proj) { MissingFiles Dlg(Proj); - Dlg.DoModal(); + Dlg.DoModal(NULL); } diff --git a/Ide/Code/NewProjectFromTemplate.cpp b/Ide/Code/NewProjectFromTemplate.cpp --- a/Ide/Code/NewProjectFromTemplate.cpp +++ b/Ide/Code/NewProjectFromTemplate.cpp @@ -1,173 +1,177 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Tree.h" #include "lgi/common/SubProcess.h" #include "lgi/common/FileSelect.h" #include "LgiIde.h" #include "resdefs.h" static LString TemplatesPath; class NewProjFromTemplate : public LDialog { public: NewProjFromTemplate(LViewI *parent) { SetParent(parent); if (LoadFromResource(IDD_NEW_PROJ_FROM_TEMPLATE)) { MoveSameScreen(parent); LTree *t; if (TemplatesPath && GetViewById(IDC_TEMPLATES, t)) Add(t, TemplatesPath); } } void Add(LTreeNode *t, const char *path) { LDirectory d; for (auto b=d.First(path); b; b=d.Next()) { auto Full = d.FullPath(); if (d.IsDir()) { LTreeItem *c = new LTreeItem; c->SetText(d.GetName()); c->SetText(Full, 1); t->Insert(c); Add(c, Full); } } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_BROWSE_OUTPUT: { - LFileSelect s; - s.Parent(this); - if (s.OpenFolder()) - SetCtrlName(IDC_FOLDER, s.Name()); + LFileSelect *s = new LFileSelect; + s->Parent(this); + s->OpenFolder([&](auto s, auto ok) + { + if (ok) + SetCtrlName(IDC_FOLDER, s->Name()); + delete s; + }); break; } case IDOK: case IDCANCEL: EndModal(c->GetId() == IDOK); break; } return 0; } }; LString GetPython3() { auto Path = LGetPath(); for (auto i: Path) { LFile::Path p(i); p += #ifdef MAC "python3" #else "python" #endif LGI_EXECUTABLE_EXT; if (p.Exists()) { printf("Got python '%s'\n", p.GetFull().Get()); // But what version is it? LSubProcess sp(p, "--version"); if (sp.Start()) { LStringPipe out; sp.Communicate(&out); auto s = out.NewGStr(); // printf("out=%s\n", s.Get()); if (s.Find(" 3.") >= 0) return p.GetFull(); } } } return LString(); } bool CreateProject(const char *Name, const char *Template, const char *Folder) { auto Py3 = GetPython3(); if (!Py3) { LgiTrace("%s:%i - Can't find python3.\n", _FL); return false; } // Make sure output folder exists? if (!LDirExists(Folder)) { if (!FileDev->CreateFolder(Folder)) { LgiTrace("%s:%i - Create folder '%s' failed.\n", _FL, Folder); return false; } } // Copy in script... auto CreateProjectPy = "create_project.py"; LFile::Path ScriptIn(TemplatesPath); ScriptIn += CreateProjectPy; LFile::Path ScriptOut(Folder); ScriptOut += CreateProjectPy; if (!FileDev->Copy(ScriptIn, ScriptOut)) { LgiTrace("%s:%i - Copy '%s' to '%s' failed.\n", _FL, ScriptIn.GetFull().Get(), ScriptOut.GetFull().Get()); return false; } // Call the script LString Args; Args.Printf("\"%s\" \"%s\" \"%s\"", ScriptOut.GetFull().Get(), Template, Name); LSubProcess p(Py3, Args); if (!p.Start()) { LgiTrace("%s:%i - Start process failed.\n", _FL); return false; } LStringPipe Out; p.Communicate(&Out); LgiTrace("Out=%s\n", Out.NewGStr().Get()); FileDev->Delete(ScriptOut); return true; } + void NewProjectFromTemplate(LViewI *parent) { LFile::Path p(LSP_APP_INSTALL); p += #ifdef MAC "../../" #endif "../templates"; TemplatesPath = p; - NewProjFromTemplate Dlg(parent); - if (!Dlg.DoModal()) - { - LgiTrace("%s:%i - Dialog cancelled.\n", _FL); - return; - } - LTree *t; - if (!Dlg.GetViewById(IDC_TEMPLATES, t)) + NewProjFromTemplate *Dlg = new NewProjFromTemplate(parent); + Dlg->DoModal([&](auto dlg, auto code) { - LgiTrace("%s:%i - No tree.\n", _FL); - return; - } + LTree *t; + if (!Dlg->GetViewById(IDC_TEMPLATES, t)) + { + LgiTrace("%s:%i - No tree.\n", _FL); + return; + } - auto sel = t->Selection(); - if (!sel) - { - LgiTrace("%s:%i - No selection.\n", _FL); - return; - } + auto sel = t->Selection(); + if (!sel) + { + LgiTrace("%s:%i - No selection.\n", _FL); + return; + } - CreateProject(Dlg.GetCtrlName(IDC_PROJ_NAME), sel->GetText(1), Dlg.GetCtrlName(IDC_FOLDER)); + CreateProject(Dlg->GetCtrlName(IDC_PROJ_NAME), sel->GetText(1), Dlg->GetCtrlName(IDC_FOLDER)); + delete Dlg; + }); } diff --git a/Ide/Code/ProjectNode.cpp b/Ide/Code/ProjectNode.cpp --- a/Ide/Code/ProjectNode.cpp +++ b/Ide/Code/ProjectNode.cpp @@ -1,1535 +1,1574 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/Combo.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Menu.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Charset.h" #include "LgiIde.h" #include "IdeProject.h" #include "ProjectNode.h" #include "AddFtpFile.h" #include "WebFldDlg.h" #define DEBUG_SHOW_NODE_COUNTS 0 const char *TypeNames[NodeMax] = { "None", "Folder", "Source", "Header", "Dependancy", "Resources", "Graphic", "Web", "Text", "MakeFile", }; ////////////////////////////////////////////////////////////////////////////////// class FileProps : public LDialog { public: NodeType Type; LString Charset; int Platforms; FileProps(LView *p, char *m, NodeType t, int plat, const char *charset) { Platforms = plat; Type = t; SetParent(p); if (LoadFromResource(IDD_FILE_PROPS)) { MoveToCenter(); SetCtrlName(IDC_MSG, m); LCombo *c; if (GetViewById(IDC_TYPE, c)) { for (int i=NodeNone; iInsert(TypeNames[i]); } c->Value(Type); } else LgiTrace("%s:%i - Failed to get Type combo.\n", _FL); if (GetViewById(IDC_CHARSET, c)) { if (!charset) charset = "utf-8"; for (LCharset *cs = LGetCsList(); cs->Charset; cs++) { c->Insert(cs->Charset); if (!Stricmp(charset, cs->Charset)) c->Value(c->Length()-1); } } for (int i=0; PlatformNames[i]; i++) { SetCtrlValue(PlatformCtrlId[i], ((1 << i) & Platforms) != 0); } OnPosChange(); // Make sure the dialog can display the whole table... LTableLayout *t; if (GetViewById(IDC_TABLE, t)) { LRect u = t->GetUsedArea(); u.Inset(-LTableLayout::CellSpacing, -LTableLayout::CellSpacing); LRect p = GetPos(); if (u.X() < p.X() || u.Y() < p.Y()) { p.SetSize(MAX(u.X(), p.X()), MAX(u.Y(), p.Y())); SetPos(p); } } } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { int64 t = GetCtrlValue(IDC_TYPE); if (t >= NodeSrc) { Type = (NodeType) t; } Platforms = 0; for (int i=0; PlatformNames[i]; i++) { if (GetCtrlValue(PlatformCtrlId[i])) { Platforms |= 1 << i; } } Charset = GetCtrlName(IDC_CHARSET); if (!Stricmp(Charset.Get(), "utf-8")) Charset.Empty(); // fall thru } case IDCANCEL: case IDC_COPY_PATH: { EndModal(c->GetId()); break; } } return 0; } }; //////////////////////////////////////////////////////////////////// ProjectNode::ProjectNode(IdeProject *p) : IdeCommon(p) { Platforms = PLATFORM_ALL; NodeId = 0; Type = NodeNone; ChildCount = -1; IgnoreExpand = false; Dep = 0; Tag = NewStr("Node"); } ProjectNode::~ProjectNode() { NeedsPulse(false); if (sFile && Project) Project->OnNode(sFile, this, false); if (Dep) DeleteObj(Dep); } void ProjectNode::NeedsPulse(bool yes) { if (!Project) { LAssert(!"No project."); return; } auto app = Project->GetApp(); if (!app) { LAssert(!"No app."); return; } app->NeedsPulse.Delete(this); if (yes) app->NeedsPulse.Add(this); } void ProjectNode::OpenLocalCache(IdeDoc *&Doc) { if (sLocalCache) { Doc = Project->GetApp()->OpenFile(sLocalCache, this); if (Doc) { Doc->SetProject(Project); IdeProjectSettings *Settings = Project->GetSettings(); Doc->SetEditorParams(Settings->GetInt(ProjEditorIndentSize), Settings->GetInt(ProjEditorTabSize), Settings->GetInt(ProjEditorUseHardTabs), Settings->GetInt(ProjEditorShowWhiteSpace)); } else { LgiMsg(Tree, "Couldn't open file '%s'", AppName, MB_OK, sLocalCache.Get()); } } } int64 ProjectNode::CountNodes() { int64 n = 1; for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; n += c->CountNodes(); } return n; } void ProjectNode::OnCmdComplete(FtpCmd *Cmd) { if (!Cmd) return; if (Cmd->Status && Cmd->File) { if (Cmd->Cmd == FtpRead) { sLocalCache = Cmd->File; IdeDoc *Doc; OpenLocalCache(Doc); } else if (Cmd->Cmd == FtpWrite) { Cmd->View->OnSaveComplete(Cmd->Status); } } } int ProjectNode::GetPlatforms() { return Platforms; } const char *ProjectNode::GetLocalCache() { return sLocalCache; } bool ProjectNode::Load(LDocView *Edit, NodeView *Callback) { bool Status = false; if (IsWeb()) { if (sLocalCache) Status = Edit->Open(sLocalCache); else LAssert(!"No LocalCache"); } else { auto Full = GetFullPath(); Status = Edit->Open(Full, Charset); } return Status; } bool ProjectNode::Save(LDocView *Edit, NodeView *Callback) { bool Status = false; if (IsWeb()) { if (sLocalCache) { if (Edit->Save(sLocalCache)) { FtpThread *t = GetFtpThread(); if (t) { FtpCmd *c = new FtpCmd(FtpWrite, this); if (c) { c->View = Callback; c->Watch = Project->GetApp()->GetFtpLog(); c->Uri = NewStr(sFile); c->File = NewStr(sLocalCache); t->Post(c); } } } else LAssert(!"Editbox save failed."); } else LAssert(!"No LocalCache"); } else { auto f = GetFullPath(); if (Project) Project->CheckExists(f); Status = Edit->Save(f, Charset); if (Callback) Callback->OnSaveComplete(Status); } return Status; } int ProjectNode::GetId() { if (!NodeId && Project) NodeId = Project->AllocateId(); return NodeId; } bool ProjectNode::IsWeb() { char *Www = GetAttr(OPT_Www); char *Ftp = GetAttr(OPT_Ftp); if ( Www || Ftp || (sFile && strnicmp(sFile, "ftp://", 6) == 0) ) return true; return false; } bool ProjectNode::HasNode(ProjectNode *Node) { printf("Has %s %s %p\n", sFile.Get(), sName.Get(), Dep); if (this == Node) return true; if (Dep && Dep->HasNode(Node)) return true; for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; if (c->HasNode(Node)) return true; } return false; } void ProjectNode::AddNodes(LArray &Nodes) { Nodes.Add(this); for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) break; c->AddNodes(Nodes); } } -void ProjectNode::SetClean() +bool ProjectNode::GetClean() { if (Dep) - { - Dep->SetClean(); - } - - for (auto i:*this) + return Dep->GetClean(); + + return true; +} + +void ProjectNode::SetClean() +{ + auto CleanProj = [&]() { - ProjectNode *p = dynamic_cast(i); - if (!p) break; + for (auto i: *this) + { + ProjectNode *p = dynamic_cast(i); + if (p) + p->SetClean(); + } + }; - p->SetClean(); - } + if (Dep) + Dep->SetClean([&](bool ok) + { + if (ok) + CleanProj(); + }); + else + CleanProj(); + } IdeProject *ProjectNode::GetDep() { return Dep; } IdeProject *ProjectNode::GetProject() { return Project; } bool ProjectNode::GetFormats(LDragFormats &Formats) { Formats.Supports(NODE_DROP_FORMAT); return true; } bool ProjectNode::GetData(LArray &Data) { for (unsigned i=0; iOnNode(sFile, this, false); if (Project->RelativePath(Rel, f, true)) sFile = Rel; else sFile = f; if (sFile) { auto FullPath = GetFullPath(); if (Project) Project->OnNode(FullPath, this, true); AutoDetectType(); } Project->SetDirty(); } char *ProjectNode::GetName() { return sName; } void ProjectNode::SetName(const char *f) { sName = f; Type = NodeDir; } NodeType ProjectNode::GetType() { return Type; } void ProjectNode::SetType(NodeType t) { Type = t; } int ProjectNode::GetImage(int f) { if (IsWeb()) { return sFile ? ICON_SOURCE : ICON_WEB; } switch (Type) { default: break; case NodeDir: return ICON_FOLDER; case NodeSrc: return ICON_SOURCE; case NodeDependancy: return ICON_DEPENDANCY; case NodeGraphic: return ICON_GRAPHIC; case NodeResources: return ICON_RESOURCE; } return ICON_HEADER; } const char *ProjectNode::GetText(int c) { if (sFile) { char *d = 0; if (IsWeb()) { d = sFile ? strrchr(sFile, '/') : 0; } else { #ifdef WIN32 char Other = '/'; #else char Other = '\\'; #endif char *s; while ((s = strchr(sFile, Other))) { *s = DIR_CHAR; } d = strrchr(sFile, DIR_CHAR); } if (d) return d + 1; else return sFile; } #if DEBUG_SHOW_NODE_COUNTS if (Type == NodeDir) { if (ChildCount < 0) ChildCount = CountNodes(); Label.Printf("%s ("LGI_PrintfInt64")", Name, ChildCount); return Label; } #endif return sName ? sName.Get() : Untitled; } void ProjectNode::OnExpand(bool b) { if (!IgnoreExpand) { Project->SetExpanded(GetId(), b); } } bool ProjectNode::Serialize(bool Write) { if (!Write && sFile) Project->OnNode(sFile, this, false); SerializeAttr("File", sFile); SerializeAttr("Name", sName); SerializeAttr("Charset", Charset); SerializeAttr("Type", (int&)Type); SerializeAttr("Platforms", (int&)Platforms); if (!Write && sFile) Project->OnNode(sFile, this, true); if (Type == NodeNone) { AutoDetectType(); } if (Type == NodeDir) { if (Write && !NodeId) GetId(); // Make sure we have an ID SerializeAttr("Id", NodeId); if (Write) Project->SetExpanded(GetId(), Expanded()); else { IgnoreExpand = true; Expanded(Project->GetExpanded(GetId())); IgnoreExpand = false; } } else if (Type == NodeDependancy) { SerializeAttr("LinkAgainst", LinkAgainst); if (!Write) { auto Full = GetFullPath(); Dep = Project->GetApp()->OpenProject(Full, Project, false, true); } } else { #if 0 if (!Write) { // Check that file exists. auto p = GetFullPath(); if (p) { if (LFileExists(p)) { if (!LIsRelativePath(File)) { // Try and fix up any non-relative paths that have crept in... char Rel[MAX_PATH_LEN]; if (Project->RelativePath(Rel, File)) { if (File) Project->OnNode(File, this, false); DeleteArray(File); File = NewStr(Rel); Project->SetDirty(); Project->OnNode(File, this, true); } } } else { // File doesn't exist... has it moved??? LAutoString Path = Project->GetBasePath(); if (Path) { // Find the file. char *d = strrchr(p, DIR_CHAR); LArray Files; LArray Ext; Ext.Add(d ? d + 1 : p.Get()); if (LRecursiveFileSearch(Path, &Ext, &Files)) { if (Files.Length()) { LStringPipe Buf; Buf.Print( "The file:\n" "\n" "\t%s\n" "\n" "doesn't exist. Is this the right file:\n" "\n" "\t%s", p.Get(), Files[0]); char *Msg = Buf.NewStr(); if (Msg) { LAlert a(Project->GetApp(), "Missing File", Msg, "Yes", "No", "Browse..."); switch (a.DoModal()) { case 1: // Yes { SetFileName(Files[0]); break; } case 2: // No { break; } case 3: // Browse { LFileSelect s; s.Parent(Project->GetApp()); s.Type("Code", SourcePatterns); if (s.Open()) { SetFileName(s.Name()); } break; } } DeleteArray(Msg); } } else { LStringPipe Buf; Buf.Print( "The file:\n" "\n" "\t%s\n" "\n" "doesn't exist.", p.Get()); char *Msg = Buf.NewStr(); if (Msg) { LAlert a(Project->GetApp(), "Missing File", Msg, "Skip", "Delete", "Browse..."); switch (a.DoModal()) { case 1: // Skip { break; } case 2: // Delete { Project->SetDirty(); delete this; return false; break; } case 3: // Browse { LFileSelect s; s.Parent(Project->GetApp()); s.Type("Code", SourcePatterns); if (s.Open()) { SetFileName(s.Name()); } break; } } DeleteArray(Msg); } } } } else { LgiTrace("%s:%i - Project::GetBasePath failed.\n", _FL); } } } } #endif } return true; } LString ProjectNode::GetFullPath() { LString FullPath; if (LIsRelativePath(sFile)) { // Relative path auto Path = Project->GetBasePath(); if (Path) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Path, sFile); FullPath = p; } } else { // Absolute path FullPath = sFile; } return FullPath; } IdeDoc *ProjectNode::Open() { static bool Processing = false; IdeDoc *Doc = 0; if (Processing) { LAssert(!"Not recursive"); } if (!Processing) { Processing = true; if (IsWeb()) { if (sFile) { if (sLocalCache) { OpenLocalCache(Doc); } else { FtpThread *t = GetFtpThread(); if (t) { FtpCmd *c = new FtpCmd(FtpRead, this); if (c) { c->Watch = Project->GetApp()->GetFtpLog(); c->Uri = NewStr(sFile); t->Post(c); } } } } } else { switch (Type) { case NodeDir: { Expanded(true); break; } case NodeResources: { auto FullPath = GetFullPath(); if (FullPath) { char Exe[MAX_PATH_LEN]; LMakePath(Exe, sizeof(Exe), LGetExePath(), ".."); #if defined WIN32 LMakePath(Exe, sizeof(Exe), Exe, "Debug\\LgiRes.exe"); #elif defined LINUX LMakePath(Exe, sizeof(Exe), Exe, "LgiRes/lgires"); #endif if (LFileExists(Exe)) { LExecute(Exe, FullPath); } } else { LgiMsg(Tree, "No Path", AppName); } break; } case NodeGraphic: { auto FullPath = GetFullPath(); if (FullPath) { LExecute(FullPath); } else { LgiMsg(Tree, "No Path", AppName); } break; } default: { auto FullPath = GetFullPath(); if (Project->CheckExists(FullPath)) { Doc = Project->GetApp()->FindOpenFile(FullPath); if (!Doc) { Doc = Project->GetApp()->NewDocWnd(0, this); if (Doc) { if (Doc->OpenFile(FullPath)) { IdeProjectSettings *Settings = Project->GetSettings(); Doc->SetProject(Project); Doc->SetEditorParams(Settings->GetInt(ProjEditorIndentSize), Settings->GetInt(ProjEditorTabSize), Settings->GetInt(ProjEditorUseHardTabs), Settings->GetInt(ProjEditorShowWhiteSpace)); } else { LgiMsg(Tree, "Couldn't open file '%s'", AppName, MB_OK, FullPath.Get()); } } } else { Doc->Raise(); Doc->Focus(true); } } else { LgiMsg(Tree, "No Path", AppName); } break; } } } Processing = false; } return Doc; } void ProjectNode::Delete() { if (Select()) { LTreeItem *s = GetNext(); if (s || (s = GetParent())) s->Select(true); } if (nView) { nView->OnDelete(); nView = NULL; } Project->SetDirty(); LXmlTag::RemoveTag(); delete this; } bool ProjectNode::OnKey(LKey &k) { if (k.Down()) { if (k.vkey == LK_RETURN && k.IsChar) { Open(); return true; } else if (k.vkey == LK_DELETE) { Delete(); return true; } } return false; } void ProjectNode::OnPulse() { auto StartTs = LCurrentTime(); int TimeSlice = 700; //ms auto Start = strlen(ImportPath) + 1; while (ImportFiles.Length()) { LAutoString f(ImportFiles[0]); // Take ownership of the string ImportFiles.DeleteAt(0); if (ImportProg) (*ImportProg)++; LToken p(f + Start, DIR_STR); ProjectNode *Insert = this; // Find sub nodes, and drill into directory heirarchy, // creating the nodes if needed. for (int i=0; Insert && i(it); if (!c) break; if (c->GetType() == NodeDir && c->GetName() && stricmp(c->GetName(), p[i]) == 0) { Insert = c; Found = true; break; } } if (!Found) { // Create the node IdeCommon *Com = Insert->GetSubFolder(Project, p[i], true); Insert = dynamic_cast(Com); LAssert(Insert); } } // Insert the file into the tree... if (Insert) { ProjectNode *New = new ProjectNode(Project); if (New) { New->SetFileName(f); Insert->InsertTag(New); Insert->SortChildren(); Project->SetDirty(); } } if (LCurrentTime() - StartTs >= TimeSlice) break; // Yeild to the message loop } if (ImportFiles.Length() == 0) NeedsPulse(false); } void ProjectNode::OnMouseClick(LMouse &m) { LTreeItem::OnMouseClick(m); if (m.IsContextMenu()) { LSubMenu Sub; Select(true); if (Type == NodeDir) { if (IsWeb()) { Sub.AppendItem("Insert FTP File", IDM_INSERT_FTP, true); } else { Sub.AppendItem("Insert File", IDM_INSERT, true); } Sub.AppendItem("New Folder", IDM_NEW_FOLDER, true); Sub.AppendItem("Import Folder", IDM_IMPORT_FOLDER, true); Sub.AppendSeparator(); if (!IsWeb()) { Sub.AppendItem("Rename", IDM_RENAME, true); } } Sub.AppendItem("Remove", IDM_DELETE, true); Sub.AppendItem("Sort", IDM_SORT_CHILDREN, true); Sub.AppendSeparator(); Sub.AppendItem("Browse Folder", IDM_BROWSE_FOLDER, ValidStr(sFile)); Sub.AppendItem("Open Terminal", IDM_OPEN_TERM, ValidStr(sFile)); Sub.AppendItem("Properties", IDM_PROPERTIES, true); m.ToScreen(); LPoint c = _ScrollPos(); m.x -= c.x; m.y -= c.y; switch (Sub.Float(Tree, m.x, m.y)) { case IDM_INSERT_FTP: { - AddFtpFile Add(Tree, GetAttr(OPT_Ftp)); - if (Add.DoModal()) + AddFtpFile *Add = new AddFtpFile(Tree, GetAttr(OPT_Ftp)); + Add->DoModal([&](auto dlg, auto code) { - for (int i=0; iSetFileName(Add.Uris[i]); - InsertTag(New); - SortChildren(); - Project->SetDirty(); - } - } - } - break; - } - case IDM_INSERT: - { - LFileSelect s; - s.Parent(Tree); - s.Type("Source", SourcePatterns); - s.Type("Makefiles", "*makefile"); - s.Type("All Files", LGI_ALL_FILES); - s.MultiSelect(true); - - LAutoString Dir = Project->GetBasePath(); - if (Dir) - { - s.InitialDir(Dir); - } - - if (s.Open()) - { - for (int i=0; iInProject(false, s[i], false)) + for (int i=0; iUris.Length(); i++) { ProjectNode *New = new ProjectNode(Project); if (New) { - New->SetFileName(s[i]); + New->SetFileName(Add->Uris[i]); InsertTag(New); SortChildren(); Project->SetDirty(); } } - else + } + delete Add; + }); + break; + } + case IDM_INSERT: + { + LFileSelect *s = new LFileSelect; + s->Parent(Tree); + s->Type("Source", SourcePatterns); + s->Type("Makefiles", "*makefile"); + s->Type("All Files", LGI_ALL_FILES); + s->MultiSelect(true); + + LAutoString Dir = Project->GetBasePath(); + if (Dir) + { + s->InitialDir(Dir); + } + + s->Open([&](auto s, auto ok) + { + if (ok) + { + for (int i=0; iLength(); i++) { - LgiMsg(Tree, "'%s' is already in the project.\n", AppName, MB_OK, s[i]); + if (!Project->InProject(false, (*s)[i], false)) + { + ProjectNode *New = new ProjectNode(Project); + if (New) + { + New->SetFileName((*s)[i]); + InsertTag(New); + SortChildren(); + Project->SetDirty(); + } + } + else + { + LgiMsg(Tree, "'%s' is already in the project.\n", AppName, MB_OK, s[i]); + } } } - } + delete s; + }); break; } case IDM_IMPORT_FOLDER: { - LFileSelect s; - s.Parent(Tree); + LFileSelect *s = new LFileSelect; + s->Parent(Tree); auto Dir = Project->GetBasePath(); if (Dir) - s.InitialDir(Dir); + s->InitialDir(Dir); - if (!s.OpenFolder()) - break; - + s->OpenFolder([&](auto s, auto ok) + { + if (ok) ImportPath = s.Name(); LArray Ext; LToken e(SourcePatterns, ";"); for (auto i: e) { Ext.Add(i); } if (!LRecursiveFileSearch(s.Name(), &Ext, &ImportFiles)) break; ImportProg.Reset(new LProgressDlg(GetTree(), 300)); ImportProg->SetDescription("Importing..."); ImportProg->SetRange(ImportFiles.Length()); NeedsPulse(true); + delete s; + }); break; } case IDM_SORT_CHILDREN: { SortChildren(); Project->SetDirty(); break; } case IDM_NEW_FOLDER: { - LInput Name(Tree, "", "Name:", AppName); - if (Name.DoModal()) + LInput *Name = new LInput(Tree, "", "Name:", AppName); + Name->DoModal([&](auto dlg, auto ok) { - GetSubFolder(Project, Name.GetStr(), true); - } + if (ok) + GetSubFolder(Project, Name->GetStr(), true); + delete Name; + }); break; } case IDM_RENAME: { - LInput Name(Tree, "", "Name:", AppName); - if (Name.DoModal()) + LInput *Name = new LInput(Tree, GetName(), "Name:", AppName); + Name->DoModal([&](auto dlg, auto ok) { - SetName(Name.GetStr()); - Project->SetDirty(); - Update(); - } + if (ok) + { + SetName(Name->GetStr()); + Project->SetDirty(); + Update(); + } + delete Name; + }); break; } case IDM_DELETE: { Delete(); return; break; } case IDM_BROWSE_FOLDER: { auto Path = GetFullPath(); if (Path) LBrowseToFile(Path); break; } case IDM_OPEN_TERM: { if (Type == NodeDir) { } else { auto Path = GetFullPath(); if (Path) { LTrimDir(Path); #if defined LINUX char *Term = 0; char *Format = 0; switch (LGetWindowManager()) { case WM_Kde: Term = "konsole"; Format = "--workdir "; break; case WM_Gnome: Term = "gnome-terminal"; Format = "--working-directory="; break; } if (Term) { char s[256]; strcpy_s(s, sizeof(s), Format); char *o = s + strlen(s), *i = Path; *o++ = '\"'; while (*i) { if (*i == ' ') { *o++ = '\\'; } *o++ = *i++; } *o++ = '\"'; *o++ = 0; LExecute(Term, s); } #elif defined WIN32 LExecute("cmd", 0, Path); #endif } } break; } case IDM_PROPERTIES: { OnProperties(); break; } } } else if (m.Left()) { if (Type != NodeDir && m.Double()) { if ( ( IsWeb() || Type != NodeDir ) && ValidStr(sFile) ) { Open(); } else { LAssert(!"Unknown file type"); } } } } #if 0 #define LOG_FIND_FILE(...) printf(__VA_ARGS__) #else #define LOG_FIND_FILE(...) #endif ProjectNode *ProjectNode::FindFile(const char *In, char **Full) { LOG_FIND_FILE("%s:%i Node='%s' '%s'\n", _FL, sFile.Get(), sName.Get()); if (sFile) { bool Match = false; const char *AnyDir = "\\/"; if (IsWeb()) { Match = sFile ? stricmp(In, sFile) == 0 : false; } else if (strchr(In, DIR_CHAR)) { // Match partial or full path char Full[MAX_PATH_LEN] = ""; if (LIsRelativePath(sFile)) { auto Base = Project->GetBasePath(); if (Base) LMakePath(Full, sizeof(Full), Base, sFile); else LgiTrace("%s,%i - Couldn't get full IDoc path.\n", _FL); } else { strcpy_s(Full, sizeof(Full), sFile); } LOG_FIND_FILE("%s:%i Full='%s'\n", _FL, Full); LString MyPath(Full); LString::Array MyArr = MyPath.SplitDelimit(AnyDir); LString InPath(In); LString::Array InArr = InPath.SplitDelimit(AnyDir); auto Common = MIN(MyArr.Length(), InArr.Length()); Match = true; for (int i = 0; i < Common; i++) { char *a = MyArr[MyArr.Length()-(i+1)]; char *b = InArr[InArr.Length()-(i+1)]; auto res = _stricmp(a, b); LOG_FIND_FILE("%s:%i cmp '%s','%s'=%i\n", _FL, a, b, res); if (res) { Match = false; break; } } } else { // Match file name only auto p = sFile.SplitDelimit(AnyDir); Match = p.Last().Equals(In); LOG_FIND_FILE("%s:%i cmp '%s','%s'=%i\n", _FL, p.Last().Get(), In, Match); } if (Match) { if (Full) { if (sFile(0) == '.') { LAutoString Base = Project->GetBasePath(); if (Base) { char f[256]; LMakePath(f, sizeof(f), Base, sFile); *Full = NewStr(f); } } else { *Full = NewStr(sFile); } } return this; } } for (auto i:*this) { ProjectNode *c = dynamic_cast(i); if (!c) { LAssert(!"Not a node?"); break; } ProjectNode *n = c->FindFile(In, Full); if (n) { return n; } } return 0; } struct DepDlg : public LDialog { ProjectNode *Node; DepDlg(ProjectNode *node) : Node(node) { auto proj = node->GetProject(); auto app = proj ? proj->GetApp() : NULL; if (!app) LAssert(!"Can't get app ptr."); else { SetParent(app); MoveSameScreen(app); } if (!LoadFromResource(IDD_DEP_NODE)) LAssert(!"Resource missing."); else { auto Path = node->GetFullPath(); if (Path) SetCtrlName(IDC_PATH, Path); SetCtrlValue(IDC_LINK_AGAINST, node->GetLinkAgainst()); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { Node->SetLinkAgainst(GetCtrlValue(IDC_LINK_AGAINST)); break; } } return LDialog::OnNotify(Ctrl, n); } }; void ProjectNode::OnProperties() { if (IsWeb()) { bool IsFolder = sFile.IsEmpty(); - WebFldDlg Dlg(Tree, sName, IsFolder ? GetAttr(OPT_Ftp) : sFile.Get(), GetAttr(OPT_Www)); - if (Dlg.DoModal()) + WebFldDlg *Dlg = new WebFldDlg(Tree, sName, IsFolder ? GetAttr(OPT_Ftp) : sFile.Get(), GetAttr(OPT_Www)); + Dlg->DoModal([&](auto dlg, auto ok) { - if (IsFolder) + if (ok) { - SetName(Dlg.Name); - SetAttr(OPT_Ftp, Dlg.Ftp); - SetAttr(OPT_Www, Dlg.Www); + if (IsFolder) + { + SetName(Dlg->Name); + SetAttr(OPT_Ftp, Dlg->Ftp); + SetAttr(OPT_Www, Dlg->Www); + } + else + { + sFile = Dlg->Ftp; + } + + Project->SetDirty(); + Update(); } - else - { - sFile = Dlg.Ftp; - } - - Project->SetDirty(); - Update(); - } + delete Dlg; + }); } else if (Type == NodeDir) { } else if (Type == NodeDependancy) { DepDlg dlg(this); - dlg.DoModal(); + dlg.DoModal(NULL); } else { auto Path = GetFullPath(); if (Path) { char Size[32]; int64 FSize = LFileSize(Path); LFormatSize(Size, sizeof(Size), FSize); char Msg[512]; sprintf(Msg, "Source Code:\n\n\t%s\n\nSize: %s (%i bytes)", Path.Get(), Size, (int32)FSize); - FileProps Dlg(Tree, Msg, Type, Platforms, Charset); - switch (Dlg.DoModal()) + FileProps *Dlg = new FileProps(Tree, Msg, Type, Platforms, Charset); + Dlg->DoModal([this, Dlg, Path](auto dlg, auto code) { - case IDOK: + switch (code) { - if (Type != Dlg.Type) + case IDOK: { - Type = Dlg.Type; - Project->SetDirty(); - } - if (Platforms != Dlg.Platforms) - { - Platforms = Dlg.Platforms; - Project->SetDirty(); + if (Type != Dlg->Type) + { + Type = Dlg->Type; + Project->SetDirty(); + } + if (Platforms != Dlg->Platforms) + { + Platforms = Dlg->Platforms; + Project->SetDirty(); + } + if (Charset != Dlg->Charset) + { + Charset = Dlg->Charset; + Project->SetDirty(); + } + + Update(); + break; } - if (Charset != Dlg.Charset) + case IDC_COPY_PATH: { - Charset = Dlg.Charset; - Project->SetDirty(); + LClipBoard Clip(Tree); + Clip.Text(Path); + break; } - - Update(); - break; } - case IDC_COPY_PATH: - { - LClipBoard Clip(Tree); - Clip.Text(Path); - break; - } - } + delete Dlg; + }); } } } diff --git a/Ide/Code/ProjectNode.h b/Ide/Code/ProjectNode.h --- a/Ide/Code/ProjectNode.h +++ b/Ide/Code/ProjectNode.h @@ -1,107 +1,108 @@ #ifndef _PROJECT_NODE_H_ #define _PROJECT_NODE_H_ #include "FtpThread.h" // Linked to 'TypeNames' global variable, update that too. enum NodeType { NodeNone, NodeDir, NodeSrc, NodeHeader, NodeDependancy, NodeResources, NodeGraphic, NodeWeb, NodeText, NodeMakeFile, NodeMax, // Always last }; extern int NodeSort(LTreeItem *a, LTreeItem *b, NativeInt d = 0); class ProjectNode : public IdeCommon, public LDragDropSource, public FtpCallback, public NodeSource { NodeType Type; int NodeId; int Platforms; LString sFile; LString sLocalCache; LString sName; LString Charset; bool IgnoreExpand; int64 ChildCount; LString Label; IdeProject *Dep; int LinkAgainst = true; // Import process LArray ImportFiles; LString ImportPath; LAutoPtr ImportProg; void OpenLocalCache(IdeDoc *&Doc); void OnCmdComplete(FtpCmd *Cmd) override; int64 CountNodes(); void NeedsPulse(bool yes); public: ProjectNode(IdeProject *p); ~ProjectNode(); // Actions IdeDoc *Open(); void Delete(); + bool GetClean(); void SetClean(); void AddNodes(LArray &Nodes); bool HasNode(ProjectNode *Node); // Props int GetId(); int GetLinkAgainst() { return LinkAgainst; } void SetLinkAgainst(bool b) { LinkAgainst = b; } bool IsWeb() override; IdeProject *GetDep(); IdeProject *GetProject() override; const char *GetFileName() override; void SetFileName(const char *f); char *GetName(); void SetName(const char *f); NodeType GetType(); void SetType(NodeType t); int GetImage(int f) override; const char *GetText(int c) override; LString GetFullPath() override; ProjectNode *FindFile(const char *In, char **Full); /// \sa Some combination of PLATFORM_WIN32, PLATFORM_LINUX, PLATFORM_MAC, PLATFORM_HAIKU or PLATFORM_ALL int GetPlatforms() override; const char *GetLocalCache() override; void AutoDetectType(); const char *GetCharset() override { return Charset; } // Dnd bool GetFormats(LDragFormats &Formats) override; bool GetData(LArray &Data) override; // Ui events bool OnBeginDrag(LMouse &m) override; bool OnKey(LKey &k) override; void OnExpand(bool b) override; void OnMouseClick(LMouse &m) override; void OnProperties(); void OnPulse(); // Serialization bool Load(LDocView *Edit, NodeView *Callback) override; bool Save(LDocView *Edit, NodeView *Callback) override; bool Serialize(bool Write) override; }; #endif diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml --- a/Ide/LgiIdeProj.xml +++ b/Ide/LgiIdeProj.xml @@ -1,277 +1,274 @@ - - - - + + + + - - - - - - + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + + + - + - .\LgiIde_vs2015.sln - ./Makefile.linux + .\buildHaiku.py + ./buildHaiku.py ./MacCocoa/LgiIde.xcodeproj Makefile.haiku ./lgiide ./lgiide LgiIde.exe LgiIde.exe WINDOWS WINNATIVE WINDOWS WINNATIVE POSIX POSIX LIBPNG_VERSION=\"1.2\" LIBPNG_VERSION=\"1.2\" MingW /home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp - - ./Code ./Resources ../include ./Code ./Resources ../include ..\include\lgi\win /local/include ..\include\lgi\win /local/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/haiku ../include/lgi/haiku ../include/lgi/mac/cocoa ../include/lgi/mac/cocoa imm32 imm32 magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc - be - be + -static-libgcc +gnu +network +be + -static-libgcc +gnu +network +be - ..\Debug ..\Release Executable lgiide lgiide LgiIde.exe LgiIde.exe lgiide lgiide C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gtk+-3.0` - - ./Code/icon-haiku - - + + POSIX +_GNU_SOURCE + POSIX +_GNU_SOURCE + 4 4 0 1 - - SOME_TEST=testing /home/matthew/code/scribe/trunk_os/Linux/ScribeProj.xml - - + - - - - + - - - - diff --git a/Ide/LgiIde_vs2015.vcxproj.filters b/Ide/LgiIde_vs2015.vcxproj.filters --- a/Ide/LgiIde_vs2015.vcxproj.filters +++ b/Ide/LgiIde_vs2015.vcxproj.filters @@ -1,265 +1,267 @@  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {6b83273a-b199-4420-ae65-9663c7c7e75b} {e809cc3d-4694-4e36-b2d4-60e9f9450e56} {448bd709-7433-4368-a79b-6b1091071ddf} {b327086f-7d0e-4840-ac95-7dc18a89fe19} {3cefce7e-26bb-4e9e-8711-c7fa5c51373d} {63c6319b-3755-4b59-95f2-1e7b5b2c1a75} {f1667c53-311d-44b9-aafb-6db5c2bb7ea7} {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav {0823126b-5e9f-417c-b233-7025ea646dad} {2be01d30-b91b-466c-8aaf-bb656db55afb} {404b5cc0-5e53-4ae3-945c-274b0fc3f889} {29793244-2c02-4203-b755-49237ba42e24} {f793da4b-cf25-40f4-a90c-4a549d33d87e} {a789d0ae-552d-4e98-902b-7ffb0d018bbc} {5aca11fa-b1cb-4707-bb7f-22f85d399ab9} Source Files Source Files\Search Source Files\Search Source Files\Search\Cpp Parser Source Files\Search\Scripting Source Files\Search\Scripting Source Files\Search\Simple Cpp Parser Source Files\Process Source Files\Process Source Files\Dialogs Source Files\Dialogs Header Files Header Files Header Files Header Files Header Files Lgi Source Files\Projects Source Files\Projects Source Files\Docs Source Files\Utils Source Files\levenshtein Source Files\Docs Source Files\Search\Python Parser Header Files Source Files Source Files Source Files\Search Source Files\Search Source Files\Search\Simple Cpp Parser + + Source Files\Dialogs Source Files\Dialogs Lgi Lgi Source Files\Projects Source Files\Projects Source Files\Projects Source Files\Utils Source Files\Utils Source Files\Utils Source Files\Utils Source Files\Utils Source Files\Docs Source Files\Projects Source Files\levenshtein Source Files\Docs Source Files\Docs Source Files\Search\Python Parser Source Files\Search\Javascript Source Files\Projects Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Resource Files Source Files\Search\Scripting \ No newline at end of file diff --git a/Ide/Makefile.haiku b/Ide/Makefile.haiku --- a/Ide/Makefile.haiku +++ b/Ide/Makefile.haiku @@ -1,393 +1,411 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = lgiide ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d - Defs = -D_DEBUG -DHAIKU -D_REENTRANT + Defs = -D_DEBUG -DHAIKU -D_REENTRANT -DPOSIX -D_GNU_SOURCE Libs = \ -L../Debug \ + -static-libgcc \ + -lgnu \ + -lnetwork \ -lbe \ -llgi$(Tag) \ -L../$(BuildDir) Inc = \ -I./Resources \ -I./Code \ -I../include/lgi/haiku \ -I../include else Flags += -s -Os -std=c++14 - Defs = -DHAIKU -D_REENTRANT + Defs = -DHAIKU -D_REENTRANT -DPOSIX -D_GNU_SOURCE Libs = \ -L../Release \ + -static-libgcc \ + -lgnu \ + -lnetwork \ -lbe \ -llgi$(Tag) \ -L../$(BuildDir) Inc = \ -I./Resources \ -I./Code \ -I../include/lgi/haiku \ -I../include endif # Dependencies Source = Code/WebFldDlg.cpp \ Code/SysCharSupport.cpp \ Code/SpaceTabConv.cpp \ Code/SimpleCppParser.cpp \ Code/PythonParser.cpp \ Code/ProjectNode.cpp \ Code/NewProjectFromTemplate.cpp \ Code/MissingFiles.cpp \ - Code/History.cpp \ Code/MemDumpViewer.cpp \ Code/LgiUtils.cpp \ Code/LgiIde.cpp \ Code/levenshtein.c \ Code/JavascriptParser.cpp \ Code/IdeProjectSettings.cpp \ Code/IdeProject.cpp \ Code/IdeDoc.cpp \ Code/IdeCommon.cpp \ - Code/GHistory.cpp \ - Code/GDebugger.cpp \ - Code/GDebugContext.cpp \ + Code/History.cpp \ Code/FtpThread.cpp \ Code/FindSymbol.cpp \ Code/FindInFiles.cpp \ Code/DocEditStyling.cpp \ Code/DocEdit.cpp \ + Code/DebugContext.cpp \ Code/AddFtpFile.cpp \ ../src/common/Text/TextConvert.cpp \ ../src/common/Text/HtmlParser.cpp \ ../src/common/Text/HtmlCommon.cpp \ ../src/common/Text/Html.cpp \ ../src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../src/common/Text/DocView.cpp \ ../src/common/Net/OpenSSLSocket.cpp \ ../src/common/Net/Http.cpp \ ../src/common/Net/Ftp.cpp \ - ../src/common/Lgi/SubProcess.cpp \ ../src/common/Lgi/Mdi.cpp \ ../src/common/Lgi/LgiMain.cpp \ ../src/common/Lgi/About.cpp \ ../src/common/Gdc2/Filters/Png.cpp \ ../src/common/Coding/ParseCpp.cpp \ ../src/common/Coding/LexCpp.cpp -SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Sources))) - +SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) +Deps := $(patsubst %.o,%.d,$(Objects)) # Target # Executable target -$(Target) : ../$(BuildDir)/liblgi$(Tag).so $(Depends) +$(Target) : ../$(BuildDir)/liblgi$(Tag).so $(Objects) + mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ - $(Target) $(addprefix $(BuildDir)/,$(Depends)) $(Libs) - addattr -f ./Code/icon-haiku -t "'VICN'" "BEOS:ICON" $(Target) + $(Target) $(Objects) $(Libs) @echo Done. ../$(BuildDir)/liblgi$(Tag).so : ../include/lgi/common/App.h \ ../include/lgi/common/Array.h \ ../include/lgi/common/AutoPtr.h \ ../include/lgi/common/Base64.h \ ../include/lgi/common/Bitmap.h \ ../include/lgi/common/Box.h \ ../include/lgi/common/Button.h \ ../include/lgi/common/CairoSurface.h \ ../include/lgi/common/Cancel.h \ ../include/lgi/common/Capabilities.h \ + ../include/lgi/common/Charset.h \ ../include/lgi/common/CheckBox.h \ ../include/lgi/common/ClipBoard.h \ ../include/lgi/common/Colour.h \ ../include/lgi/common/ColourSpace.h \ ../include/lgi/common/Com.h \ ../include/lgi/common/Combo.h \ ../include/lgi/common/Containers.h \ ../include/lgi/common/Core.h \ ../include/lgi/common/Css.h \ ../include/lgi/common/CssTools.h \ ../include/lgi/common/CurrentTime.h \ ../include/lgi/common/DataDlg.h \ ../include/lgi/common/DateTime.h \ ../include/lgi/common/Dialog.h \ ../include/lgi/common/DisplayString.h \ ../include/lgi/common/DocView.h \ ../include/lgi/common/Dom.h \ ../include/lgi/common/DomFields.h \ ../include/lgi/common/DragAndDrop.h \ ../include/lgi/common/DropFiles.h \ ../include/lgi/common/Edit.h \ ../include/lgi/common/Error.h \ ../include/lgi/common/EventTargetThread.h \ ../include/lgi/common/File.h \ ../include/lgi/common/FileSelect.h \ ../include/lgi/common/Filter.h \ ../include/lgi/common/FindReplaceDlg.h \ ../include/lgi/common/Font.h \ ../include/lgi/common/FontCache.h \ ../include/lgi/common/FontSelect.h \ ../include/lgi/common/Gdc2.h \ ../include/lgi/common/GdcTools.h \ ../include/lgi/common/GdiLeak.h \ ../include/lgi/common/HashTable.h \ ../include/lgi/common/ImageList.h \ ../include/lgi/common/Input.h \ ../include/lgi/common/ItemContainer.h \ ../include/lgi/common/Json.h \ ../include/lgi/common/Layout.h \ ../include/lgi/common/Lgi.h \ ../include/lgi/common/LgiClasses.h \ ../include/lgi/common/LgiCommon.h \ ../include/lgi/common/LgiDefs.h \ ../include/lgi/common/LgiInc.h \ ../include/lgi/common/LgiInterfaces.h \ ../include/lgi/common/LgiMsgs.h \ ../include/lgi/common/LgiNetInc.h \ ../include/lgi/common/LgiRes.h \ ../include/lgi/common/LgiString.h \ ../include/lgi/common/LgiUiBase.h \ ../include/lgi/common/Library.h \ ../include/lgi/common/LibraryUtils.h \ ../include/lgi/common/List.h \ ../include/lgi/common/ListItemCheckBox.h \ ../include/lgi/common/ListItemRadioBtn.h \ + ../include/lgi/common/LMallocArray.h \ ../include/lgi/common/Mail.h \ ../include/lgi/common/Matrix.h \ ../include/lgi/common/Mem.h \ ../include/lgi/common/Menu.h \ ../include/lgi/common/Message.h \ ../include/lgi/common/Mime.h \ ../include/lgi/common/Mru.h \ ../include/lgi/common/Mutex.h \ ../include/lgi/common/Net.h \ ../include/lgi/common/NetTools.h \ ../include/lgi/common/Notifications.h \ ../include/lgi/common/OAuth2.h \ ../include/lgi/common/OptionsFile.h \ ../include/lgi/common/Palette.h \ ../include/lgi/common/Panel.h \ ../include/lgi/common/Password.h \ ../include/lgi/common/Path.h \ ../include/lgi/common/PixelRops.h \ ../include/lgi/common/Point.h \ ../include/lgi/common/Popup.h \ ../include/lgi/common/PopupList.h \ ../include/lgi/common/Printer.h \ ../include/lgi/common/Profile.h \ ../include/lgi/common/Progress.h \ ../include/lgi/common/ProgressDlg.h \ ../include/lgi/common/ProgressView.h \ ../include/lgi/common/Properties.h \ ../include/lgi/common/RadioGroup.h \ ../include/lgi/common/Range.h \ ../include/lgi/common/Rect.h \ ../include/lgi/common/RectF.h \ ../include/lgi/common/RefCount.h \ ../include/lgi/common/RegKey.h \ ../include/lgi/common/Res.h \ ../include/lgi/common/Rops.h \ ../include/lgi/common/ScrollBar.h \ ../include/lgi/common/SkinEngine.h \ ../include/lgi/common/Slider.h \ ../include/lgi/common/Splitter.h \ ../include/lgi/common/StatusBar.h \ ../include/lgi/common/Store3Defs.h \ ../include/lgi/common/Stream.h \ ../include/lgi/common/StringClass.h \ ../include/lgi/common/StringLayout.h \ + ../include/lgi/common/StructuredIo.h \ + ../include/lgi/common/StructuredLog.h \ ../include/lgi/common/SubProcess.h \ ../include/lgi/common/TableLayout.h \ ../include/lgi/common/TabView.h \ ../include/lgi/common/TextFile.h \ ../include/lgi/common/TextLabel.h \ ../include/lgi/common/TextLog.h \ ../include/lgi/common/TextView3.h \ ../include/lgi/common/Thread.h \ ../include/lgi/common/ThreadEvent.h \ ../include/lgi/common/Token.h \ ../include/lgi/common/ToolBar.h \ ../include/lgi/common/ToolTip.h \ ../include/lgi/common/TrayIcon.h \ ../include/lgi/common/Tree.h \ ../include/lgi/common/Undo.h \ ../include/lgi/common/Unicode.h \ ../include/lgi/common/UnicodeString.h \ ../include/lgi/common/UnrolledList.h \ ../include/lgi/common/Variant.h \ ../include/lgi/common/View.h \ ../include/lgi/common/Widgets.h \ ../include/lgi/common/Window.h \ ../include/lgi/common/XmlTree.h \ ../include/lgi/haiku/LgiOsClasses.h \ ../include/lgi/haiku/LgiOsDefs.h \ + ../include/lgi/linux/Gtk/LgiWidget.h \ + ../include/lgi/linux/Gtk/LgiWinManGlue.h \ + ../include/lgi/mac/cocoa/LCocoaView.h \ + ../include/lgi/mac/cocoa/LgiMac.h \ + ../include/lgi/mac/cocoa/LgiOs.h \ + ../include/lgi/mac/cocoa/LgiOsClasses.h \ + ../include/lgi/mac/cocoa/LgiOsDefs.h \ + ../include/lgi/mac/cocoa/ObjCWrapper.h \ + ../include/lgi/mac/cocoa/SymLookup.h \ ../private/common/FontPriv.h \ ../private/common/ViewPriv.h \ ../private/haiku/AppPriv.h \ + ../private/linux/AppPriv.h \ ../src/common/Gdc2/15Bit.cpp \ ../src/common/Gdc2/16Bit.cpp \ ../src/common/Gdc2/24Bit.cpp \ ../src/common/Gdc2/32Bit.cpp \ ../src/common/Gdc2/8Bit.cpp \ ../src/common/Gdc2/Alpha.cpp \ ../src/common/Gdc2/Colour.cpp \ ../src/common/Gdc2/Filters/Filter.cpp \ + ../src/common/Gdc2/Font/Charset.cpp \ ../src/common/Gdc2/Font/DisplayString.cpp \ ../src/common/Gdc2/Font/Font.cpp \ - ../src/common/Gdc2/Font/FontCodePages.cpp \ ../src/common/Gdc2/Font/FontSystem.cpp \ ../src/common/Gdc2/Font/FontType.cpp \ ../src/common/Gdc2/Font/StringLayout.cpp \ ../src/common/Gdc2/Font/TypeFace.cpp \ ../src/common/Gdc2/GdcCommon.cpp \ ../src/common/Gdc2/Path/Path.cpp \ ../src/common/Gdc2/Rect.cpp \ ../src/common/Gdc2/RopsCases.cpp \ ../src/common/Gdc2/Surface.cpp \ ../src/common/Gdc2/Tools/ColourReduce.cpp \ ../src/common/Gdc2/Tools/GdcTools.cpp \ ../src/common/General/Containers.cpp \ ../src/common/General/DateTime.cpp \ ../src/common/General/ExeCheck.cpp \ ../src/common/General/FileCommon.cpp \ ../src/common/General/Password.cpp \ ../src/common/General/Properties.cpp \ ../src/common/Hash/md5/md5.c \ ../src/common/Hash/md5/md5.h \ ../src/common/Hash/sha1/sha1.c \ ../src/common/Hash/sha1/sha1.h \ ../src/common/Lgi/Alert.cpp \ ../src/common/Lgi/AppCommon.cpp \ ../src/common/Lgi/Css.cpp \ ../src/common/Lgi/CssTools.cpp \ ../src/common/Lgi/DataDlg.cpp \ ../src/common/Lgi/DragAndDropCommon.cpp \ ../src/common/Lgi/FileSelect.cpp \ ../src/common/Lgi/FindReplace.cpp \ ../src/common/Lgi/FontSelect.cpp \ ../src/common/Lgi/GuiUtils.cpp \ ../src/common/Lgi/Input.cpp \ ../src/common/Lgi/LgiCommon.cpp \ ../src/common/Lgi/Library.cpp \ ../src/common/Lgi/LMsg.cpp \ ../src/common/Lgi/MemStream.cpp \ ../src/common/Lgi/MenuCommon.cpp \ ../src/common/Lgi/Mru.cpp \ ../src/common/Lgi/Mutex.cpp \ ../src/common/Lgi/Object.cpp \ ../src/common/Lgi/OptionsFile.cpp \ ../src/common/Lgi/Rand.cpp \ ../src/common/Lgi/Stream.cpp \ ../src/common/Lgi/SubProcess.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/ViewCommon.cpp \ ../src/common/Lgi/WindowCommon.cpp \ ../src/common/Net/Base64.cpp \ ../src/common/Net/MDStringToDigest.cpp \ ../src/common/Net/Net.cpp \ ../src/common/Net/NetTools.cpp \ ../src/common/Net/Uri.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/Token.cpp \ ../src/common/Text/Unicode.cpp \ ../src/common/Text/Utf8.cpp \ ../src/common/Text/XmlTree.cpp \ ../src/common/Widgets/Bitmap.cpp \ ../src/common/Widgets/Box.cpp \ ../src/common/Widgets/Button.cpp \ ../src/common/Widgets/CheckBox.cpp \ ../src/common/Widgets/Combo.cpp \ ../src/common/Widgets/Edit.cpp \ ../src/common/Widgets/ItemContainer.cpp \ ../src/common/Widgets/List.cpp \ ../src/common/Widgets/Panel.cpp \ ../src/common/Widgets/Popup.cpp \ ../src/common/Widgets/Progress.cpp \ ../src/common/Widgets/ProgressDlg.cpp \ ../src/common/Widgets/RadioGroup.cpp \ ../src/common/Widgets/ScrollBar.cpp \ ../src/common/Widgets/Slider.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 \ ../src/haiku/App.cpp \ ../src/haiku/ClipBoard.cpp \ ../src/haiku/DragAndDrop.cpp \ ../src/haiku/File.cpp \ ../src/haiku/Gdc2.cpp \ ../src/haiku/General.cpp \ ../src/haiku/Layout.cpp \ ../src/haiku/Mem.cpp \ ../src/haiku/MemDC.cpp \ ../src/haiku/Menu.cpp \ ../src/haiku/PrintDC.cpp \ ../src/haiku/Printer.cpp \ ../src/haiku/ScreenDC.cpp \ + ../src/haiku/ShowFileProp_Haiku.cpp \ ../src/haiku/Thread.cpp \ ../src/haiku/View.cpp \ ../src/haiku/Widgets.cpp \ ../src/haiku/Window.cpp export Build=$(Build); \ $(MAKE) -C .. -f Makefile.haiku .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $( 0: - s = ansi_escape.sub('', ln[0:-1]) - print(s, flush=True) - else: - break - -cmd("cd code/lgi/trunk/Ide && make -j4 2>&1") - - +#!/usr/bin/env python3 +import os +import sys +import paramiko +import re + +ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') +password = open("haikuPassword.txt", "r").read().strip() +ssh = paramiko.SSHClient() +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh.connect("haiku4", 22, "user", password) + +def cmd(c): + global ssh + stdin, stdout, stderr = ssh.exec_command(c, get_pty=True) + while True: + ln = stdout.readline() + if len(ln) > 0: + s = ansi_escape.sub('', ln[0:-1]) + print(s, flush=True) + else: + break + +cmd("cd code/lgi/trunk/Ide && make -j4 2>&1") diff --git a/Lgi.xml b/Lgi.xml --- a/Lgi.xml +++ b/Lgi.xml @@ -1,563 +1,600 @@ + - - - + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - + - - + + - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + + 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 + ./include/lgi/win +./private/win + ./include/lgi/win +./private/win + + ./include/lgi/haiku +./private/haiku + ./include/lgi/haiku +./private/haiku + /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` + + -static-libgcc +gnu +network +be + -static-libgcc +gnu +network +be + + lgi-gtk3 lgi-gtk3 + DynamicLibrary - - + + LGI_LIBRARY + LGI_LIBRARY + + + POSIX +_GNU_SOURCE + POSIX +_GNU_SOURCE + + - + + - + - + - diff --git a/Lgi_vs2015.vcxproj.filters b/Lgi_vs2015.vcxproj.filters --- a/Lgi_vs2015.vcxproj.filters +++ b/Lgi_vs2015.vcxproj.filters @@ -1,797 +1,806 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} {814a5d81-3fd5-461b-a4a3-cda593ea404b} {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl {4472f483-982a-4fb1-8995-6dc204cd81ae} Source Files\Core\Resources Source Files\Core\Resources Source Files\Core\Skin Source Files\General\Hash Source Files\General\Hash Source Files\Graphics Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Core\Threads Source Files\Core\Variant Source Files\Graphics Source Files\Dialogs Source Files\Interface Source Files\Interface Source Files\Network Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Text Source Files\Graphics Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Text Source Files\Text Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Dialogs Source Files\Graphics\Filters Source Files\Dialogs Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Core Source Files\Interface Source Files\Graphics Source Files\Dialogs Source Files\General Source Files\General Source Files\Core\Libraries Source Files\Dialogs Source Files\Network Source Files\Core\Memory Subsystem Source Files\Core\Memory Subsystem Source Files\Core\Memory Subsystem Source Files\General Source Files\Interface Source Files\Core\Mutex Source Files\Network Source Files\Network Source Files\Interface Source Files\General Source Files\General Source Files\Graphics Source Files\Interface Source Files\Graphics\Surfaces Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\General Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Text Source Files\Interface Source Files\Core\Process Source Files\Graphics\Surfaces Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Memory Subsystem Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text Source Files\Text Source Files\Widgets\TextViews Source Files\Widgets\TextViews + + Source Files + + + Source Files + + + Source Files + Source Files\Graphics\Font Source Files\Graphics\Surfaces Source Files\Graphics\Surfaces Source Files\Graphics\Surfaces Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Interface Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Dialogs Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Layout Source Files\Widgets\Xp Source Files\Widgets\Container Source Files\Widgets\Xp Source Files\Widgets\Container Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\General\Hash Source Files\General\Hash Source Files\Interface Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files\Core\Memory Subsystem Source Files\Widgets\TextViews Source Files\Widgets\TextViews Header Files Header Files Source Files\Graphics\Font \ No newline at end of file diff --git a/Lgi_vs2019.vcxproj b/Lgi_vs2019.vcxproj --- a/Lgi_vs2019.vcxproj +++ b/Lgi_vs2019.vcxproj @@ -1,470 +1,471 @@  Debug x64 ReleaseNoOptimize x64 Release x64 Lgi {95DF9CA4-6D37-4A85-A648-80C2712E0DA1} 10.0 DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode <_ProjectFileVersion>12.0.30501.0 .\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include - Lgi19x64 + LgiHaiku19x64 .\Lib\ $(Platform)$(Configuration)19\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include - Lgi19x64d + LgiHaiku19x64d .\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include - Lgi19x64nop + LgiHaiku19x64nop NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/Lgi.tlb Disabled include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;LGI_LIBRARY;_DEBUG;WINDOWS;LGI_RES;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level3 true ProgramDatabase Default _DEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) NotSet $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows false $(OutDir)$(TargetName).lib MachineX64 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 false true true false false false true true true true true true false false false true true true true true true + \ No newline at end of file diff --git a/Lgi_vs2019.vcxproj.filters b/Lgi_vs2019.vcxproj.filters --- a/Lgi_vs2019.vcxproj.filters +++ b/Lgi_vs2019.vcxproj.filters @@ -1,779 +1,782 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} {814a5d81-3fd5-461b-a4a3-cda593ea404b} {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl Source Files\Core\Resources Source Files\Core\Resources Source Files\Core\Skin Source Files\General\Hash Source Files\General\Hash Source Files\Graphics Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Dialogs Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Dialogs Source Files\Dialogs Source Files\Text Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Graphics\Font Source Files\General\Clipboard Source Files\Graphics Source Files\Graphics\Colour Reduction Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Text Source Files\Text Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Dialogs Source Files\Core\Drag and Drop Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Graphics\Filters Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\General Source Files\Graphics Source Files\Dialogs Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Libraries Source Files\General Source Files\General Source Files\General Source Files\Widgets\Xp Source Files\Dialogs Source Files\Text Source Files\Core\Memory Subsystem Source Files\Graphics\Surfaces Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\General Source Files\General\Mru Source Files\Core\Mutex Source Files\Network Source Files\Network Source Files\General Source Files\Core\File Source Files\Widgets\Xp Source Files\General Source Files\Graphics Source Files\Widgets\Xp Source Files\Graphics\Surfaces Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Dialogs Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\General Source Files\Graphics Source Files\Graphics\Surfaces Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Memory Subsystem Source Files\Text Source Files\Widgets\Xp Source Files\Core\Process Source Files\Graphics\Surfaces Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Threads Source Files\Text Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text Source Files\Core\Variant Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Text Source Files\Widgets\Xp Source Files\Core\Resources Source Files\Core\Resources Source Files\General\Hash Source Files\General\Hash Source Files\Graphics Source Files\Graphics\Gdi Leak Header Files Header Files Header Files Header Files Header Files Header Files Source Files\Graphics\Font Source Files\Interface Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files + + Header Files + \ No newline at end of file diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp --- a/Lvc/Src/Main.cpp +++ b/Lvc/Src/Main.cpp @@ -1,1678 +1,1695 @@ #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) { - LFileSelect s; - s.Parent(this); - if (s.Open()) + auto s = new LFileSelect; + s->Parent(this); + s->Open([&](auto dlg, auto status) { - SetCtrlName(EditId, s.Name()); - } + 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; } LString StripFirst(LString s) { return s.Replace("\\","/").SplitDelimit("/", 1).Last(); } 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"); LArray index; LString Diff, oldName, newName; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); oldName.Empty(); newName.Empty(); InDiff = false; InPreamble = true; } else if (!Strnicmp(Ln, "Index", 5)) { if (InPreamble) index = a[i].SplitDelimit(": ", 1).Slice(1); } else if (!strncmp(Ln, "--- ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) oldName = p[1]; } else if (!strncmp(Ln, "+++ ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) newName = p[1]; if (oldName && newName) { InDiff = true; InPreamble = false; f = d->FindFile(newName); if (!f) f = new VcFile(d, NULL, NULL, false); const char *nullFn = "dev/null"; if (newName.Find(nullFn) >= 0) { // Delete f->SetText(StripFirst(oldName), COL_FILENAME); f->SetText("D", COL_STATE); } else { f->SetText(StripFirst(newName), COL_FILENAME); if (oldName.Find(nullFn) >= 0) // Add f->SetText("A", COL_STATE); else // Modify 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 (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } } } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; LBox *FoldersBox = NULL; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *ReturnValue = (AppPriv*)this; return true; } return false; } public: App() { 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); 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); 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); 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_PATCH_VIEWER: { OpenPatchViewer(this, &Opts); break; } case IDM_OPEN_LOCAL: { OpenLocalFolder(); break; } case IDM_OPEN_REMOTE: { OpenRemoteFolder(); break; } case IDM_OPEN_DIFF: { - LFileSelect s; - s.Parent(this); - if (s.Open()) + auto s = new LFileSelect; + s->Parent(this); + s->Open([&](auto dlg, auto status) { - OpenDiff(s.Name()); - } + if (status) + OpenDiff(dlg->Name()); + delete dlg; + }); break; } case IDM_OPTIONS: { - OptionsDlg Dlg(this, Opts); - Dlg.DoModal(); + auto Dlg = new OptionsDlg(this, Opts); + Dlg->DoModal([](auto dlg, auto ctrlId) + { + delete dlg; + }); break; } case IDM_FIND: { - LInput i(this, "", "Search string:"); - if (i.DoModal()) + auto i = new LInput(this, "", "Search string:"); + i->DoModal([this, i](auto dlg, auto ctrlId) { - LString::Array Revs; - Revs.Add(i.GetStr()); + if (ctrlId == IDOK) + { + LString::Array Revs; + Revs.Add(i->GetStr()); - CommitList *cl; - if (GetViewById(IDC_LIST, cl)) - cl->SelectRevisions(Revs); - } + 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) { - LFileSelect s; - - if (!Fld) - { - s.Parent(this); - if (s.OpenFolder()) - Fld = s.Name(); - } - - if (Fld) + 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() { - RemoteFolderDlg dlg(this); - if (!dlg.DoModal()) - return; - - Tree->Insert(new VcFolder(this, dlg.Uri)); + 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() { if (!Commits) return; LArray a; if (!Commits->GetAll(a)) return; auto cols = Commits->GetColumns(); for (auto i: a) { bool vis = !CommitFilter; for (int c=1; !vis && cGetText(c); if (Stristr(txt, CommitFilter.Get())) vis = true; } i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Commits->UpdateAllItems(); Commits->Invalidate(); } 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); // Fall through } case IDC_FILTER_FOLDERS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FOLDERS, NULL); LString n = GetCtrlName(IDC_FILTER_FOLDERS); if (n != FolderFilter) { FolderFilter = n; OnFilterFolders(); } break; } case IDC_CLEAR_FILTER_COMMITS: { SetCtrlName(IDC_FILTER_COMMITS, NULL); // Fall through } case IDC_FILTER_COMMITS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_COMMITS, NULL); LString n = GetCtrlName(IDC_FILTER_COMMITS); if (n != CommitFilter) { CommitFilter = n; OnFilterCommits(); } break; } case IDC_CLEAR_FILTER_FILES: { SetCtrlName(IDC_FILTER_FILES, NULL); // Fall through } case IDC_FILTER_FILES: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FILES, NULL); LString n = GetCtrlName(IDC_FILTER_FILES); 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 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 (LNotifyType)LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case (LNotifyType)LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } default: break; } 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/VcCommit.cpp b/Lvc/Src/VcCommit.cpp --- a/Lvc/Src/VcCommit.cpp +++ b/Lvc/Src/VcCommit.cpp @@ -1,543 +1,549 @@ #include "Lvc.h" #include "lgi/common/ClipBoard.h" #include "../Resources/resdefs.h" #include "lgi/common/Path.h" #include "lgi/common/HashTable.h" const char *sPalette[] = { "9696ff", "ff8787", "ff934b", "a8d200", "00f0c0", "87e7ff", "a5a5ff", "f3c3ff", "b40090", "00b400" }; LColour GetPaletteColour(int i) { LColour c; const char *s = sPalette[i % CountOf(sPalette)]; #define Comp(comp, off) { const char h[3] = {s[off], s[off+1], 0}; c.comp(htoi(h)); } Comp(r, 0); Comp(g, 2); Comp(b, 4); return c; } VcEdge::~VcEdge() { if (Parent) Parent->Edges.Delete(this); if (Child) Child->Edges.Delete(this); } void VcEdge::Detach(VcCommit *c) { if (Parent == c) Parent = NULL; if (Child == c) Child = NULL; if (Parent == NULL && Child == NULL) delete this; } void VcEdge::Set(VcCommit *p, VcCommit *c) { if ((Parent = p)) Parent->Edges.Add(this); if ((Child = c)) Child->Edges.Add(this); } size_t VcCommit::Instances = 0; VcCommit::VcCommit(AppPriv *priv, VcFolder *folder) : Pos(32, -1) { VcCommit::Instances++; d = priv; Index = -1; Folder = folder; Vcs = Folder->GetType(); Current = false; NodeIdx = -1; NodeColour = GetPaletteColour(0); Parents.SetFixedLength(false); } VcCommit::~VcCommit() { for (auto e: Edges) e->Detach(this); VcCommit::Instances--; } char *VcCommit::GetRev() { return Rev; } char *VcCommit::GetAuthor() { return Author; } char *VcCommit::GetMsg() { return Msg; } char *VcCommit::GetBranch() { return Branch; } void VcCommit::SetCurrent(bool b) { Current = b; } void VcCommit::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { if (i == 0) { double Px = 12; int Ht = Ctx.Y(); double Half = 5.5; #define MAP(col) ((col) * Px + Half) LMemDC Mem(Ctx.X(), Ctx.Y(), System32BitColourSpace); #ifdef LINUX { LItem::ItemPaintCtx TmpCtx = Ctx; TmpCtx.Offset(-TmpCtx.x1, -TmpCtx.y1); TmpCtx.pDC = &Mem; LListItem::OnPaintColumn(TmpCtx, i, c); } #else LListItem::OnPaintColumn(Ctx, i, c); #endif double r = Half - 1; double x = MAP(NodeIdx); Mem.Colour(LColour::Black); VcCommit *Prev = NULL, *Next = NULL; Prev = Folder->Log.IdxCheck(Idx - 1) ? Folder->Log[Idx - 1] : NULL; Next = Folder->Log.IdxCheck(Idx + 1) ? Folder->Log[Idx + 1] : NULL; for (auto it: Pos) { VcEdge *e = it.key; int CurIdx = it.value; if (CurIdx < 0) { continue; } double CurX = MAP(CurIdx); #define I(v) ((int)(v)) if (e->Child != this) { // Line to previous commit int PrevIdx = Prev ? Prev->Pos.Find(e) : -1; if (PrevIdx >= 0) { double PrevX = MAP(PrevIdx); Mem.Line(I(PrevX), I(-(Ht/2)), I(CurX), I(Ht/2)); } else { Mem.Colour(LColour::Red); Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2-5)); Mem.Colour(LColour::Black); } } if (e->Parent != this) { int NextIdx = Next ? Next->Pos.Find(e) : -1; if (NextIdx >= 0) { double NextX = MAP(NextIdx); Mem.Line(I(NextX), I(Ht+(Ht/2)), I(CurX), I(Ht/2)); } else { Mem.Colour(LColour::Red); Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2+5)); Mem.Colour(LColour::Black); } } } if (NodeIdx >= 0) { double Cx = x; double Cy = Ht / 2; { LPath p; p.Circle(Cx, Cy, r + (Current ? 1 : 0)); LSolidBrush sb(LColour::Black); p.Fill(&Mem, sb); } { LPath p; p.Circle(Cx, Cy, r-1); LAssert(NodeColour.IsValid()); // LgiTrace("%s = %s\n", Branch.Get(), NodeColour.GetStr()); LSolidBrush sb(NodeColour); p.Fill(&Mem, sb); } } // Mem.ConvertPreMulAlpha(false); Ctx.pDC->Op(GDC_ALPHA); Ctx.pDC->Blt(Ctx.x1, Ctx.y1, &Mem); } else { LListItem::OnPaintColumn(Ctx, i, c); } } const char *VcCommit::GetFieldText(CommitField Fld) { switch (Fld) { case LRevision: return Rev; case LIndex: if (!sIndex) sIndex.Printf(LPrintfInt64, Index); return sIndex; case LBranch: if (Vcs == VcGit) { if (!sNames) sNames = Folder->GetGitNames(Rev); return sNames; } else { if (Branch) return Branch; return "default"; } break; case LAuthor: return Author; case LTimeStamp: if (!sTimeStamp) sTimeStamp = Ts.Get(); return sTimeStamp; case LMessageTxt: if (!Msg) return NULL; if (!sMsg) sMsg = Msg.Split("\n", 1)[0]; return sMsg; default: break; } return NULL; } const char *VcCommit::GetText(int Col) { if (!Folder) { LAssert(0); return "#nofolder"; } if (!Folder->Fields.IdxCheck(Col)) { LAssert(0); return "#nofield"; } return (char*)GetFieldText(Folder->Fields[Col]); } bool VcCommit::GitParse(LString s, bool RevList) { LString::Array lines = s.Replace("\r","").Split("\n"); if (lines.Length() < 3) return false; if (RevList) { auto a = lines[0].SplitDelimit(); if (a.Length() != 2) return false; Ts.SetUnix((uint64) a[0].Int()); Rev = a[1]; for (auto Ln: lines) { if (IsWhiteSpace(Ln(0))) { if (Msg) Msg += "\n"; Msg += Ln.Strip(); } else { a = Ln.SplitDelimit(" \t\r", 1); if (a.Length() <= 1) continue; if (a[0].Equals("parent")) Parents.Add(a[1]); else if (a[0].Equals("author")) Author = a[1].RStrip("0123456789+ "); } } } else { for (unsigned ln = 0; ln < lines.Length(); ln++) { LString &l = lines[ln]; if (ln == 0) Rev = l.SplitDelimit().Last(); else if (l.Find("Author:") >= 0) Author = l.Split(":", 1)[1].Strip(); else if (l.Find("Date:") >= 0) Ts.Parse(l.Split(":", 1)[1].Strip()); else if (l.Strip().Length() > 0) { if (Msg) Msg += "\n"; Msg += l.Strip(); } } } OnParse(); return Author && Rev; } bool VcCommit::CvsParse(LDateTime &Dt, LString Auth, LString Message) { Ts = Dt; Ts.ToLocal(); uint64 i; if (Ts.Get(i)) Rev.Printf(LPrintfInt64, i); Author = Auth; Msg = Message; OnParse(); return true; } void VcCommit::OnParse() { NodeColour = Folder->BranchColour(Branch); } bool VcCommit::HgParse(LString s) { LString::Array Lines = s.SplitDelimit("\n"); if (Lines.Length() < 1) return false; for (auto Ln: Lines) { LString::Array f = Ln.Split(":", 1); if (f.Length() == 2) { if (f[0].Equals("changeset")) { auto p = f[1].Strip().Split(":"); Index = p[0].Int(); Rev = p[1]; } else if (f[0].Equals("user")) Author = f[1].Strip(); else if (f[0].Equals("date")) Ts.Parse(f[1].Strip()); else if (f[0].Equals("summary")) Msg = f[1].Strip(); else if (f[0].Equals("parent")) { auto p = f[1].Strip().Split(":"); Parents.Add(p.Last()); } else if (f[0].Equals("branch")) Branch = f[1].Strip(); } } OnParse(); return Rev.Get() != NULL; } bool VcCommit::SvnParse(LString s) { LString::Array lines = s.Split("\n"); if (lines.Length() < 1) return false; for (unsigned ln = 0; ln < lines.Length(); ln++) { LString &l = lines[ln]; if (ln == 0) { LString::Array a = l.Split("|"); if (a.Length() > 3) { Rev = a[0].Strip(" \tr"); Index = Rev.Int(); Author = a[1].Strip(); Ts.Parse(a[2]); } } else { if (Msg) Msg += "\n"; Msg += l.Strip(); } } Msg = Msg.Strip(); OnParse(); // LgiTrace("SvnParse %s %i %s %s %s\n", Rev.Get(), (int)Index, Author.Get(), Ts.GetDate().Get(), Msg.Get()); return Author && Rev && Ts.IsValid(); } VcFolder *VcCommit::GetFolder() { for (LTreeItem *i = d->Tree->Selection(); i; i = i->GetParent()) { auto f = dynamic_cast(i); if (f) return f; } return NULL; } void VcCommit::Select(bool b) { LListItem::Select(b); if (Rev && b) { VcFolder *f = GetFolder(); // LgiTrace("%s:%i select %s %i f=%p\n", _FL, Rev.Get(), b, f); if (f) { auto Rng = d->GetCommitRange(); // LgiTrace("%s:%i select rng=%i\n", _FL, (int)Rng.Length()); if (Rng.Length() > 1) { // Show the diffs over a range of commits. f->DiffRange(Rng[0], Rng[1]); } else { // Show a single commit's files: f->ListCommit(this); } } if (d->Msg) { d->Msg->Name(Msg); auto *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, false); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, false); } } else LAssert(0); } } void VcCommit::OnMouseClick(LMouse &m) { LListItem::OnMouseClick(m); if (m.IsContextMenu()) { LArray Sel; GetList()->GetSelection(Sel); LSubMenu s; s.AppendItem("Update", IDM_UPDATE, !Current); s.AppendSeparator(); s.AppendItem("Merge With Local", IDM_MERGE, !Current); if (Rev) s.AppendItem("Copy Revision", IDM_COPY_REV); if (Index >= 0) s.AppendItem("Copy Index", IDM_COPY_INDEX); s.AppendItem("Rename Branch", IDM_RENAME_BRANCH); int Cmd = s.Float(GetList(), m); switch (Cmd) { case IDM_MERGE: { VcFolder *f = GetFolder(); if (!f) { LAssert(!"No folder?"); break; } f->MergeToLocal(Rev); break; } case IDM_UPDATE: { VcFolder *f = GetFolder(); if (!f) { LAssert(!"No folder?"); break; } f->OnUpdate(Rev); break; } case IDM_COPY_REV: { LClipBoard c(GetList()); LString::Array a; a.SetFixedLength(false); for (auto i: Sel) a.New() = i->GetRev(); c.Text(LString(",").Join(a)); break; } case IDM_COPY_INDEX: { LClipBoard c(GetList()); LString b; b.Printf(LPrintfInt64, Index); c.Text(b); break; } case IDM_RENAME_BRANCH: { LArray Revs; if (GetList()->GetSelection(Revs)) { - LInput Inp(GetList(), "", "New branch name:", AppName); - if (Inp.DoModal()) + auto Fld = Folder; + auto Inp = new LInput(GetList(), "", "New branch name:", AppName); + Inp->DoModal([this, Fld, Inp, Revs](auto dlg, auto ctrlId) { - Folder->RenameBranch(Inp.GetStr(), Revs); - } + if (ctrlId) + { + auto Revisions = Revs; + Fld->RenameBranch(Inp->GetStr(), Revisions); + } + delete dlg; + }); } break; } } } } diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,4465 +1,4475 @@ #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; } } } void VcFolder::FilterCurrentFiles() { LArray All; d->Files->GetAll(All); // Update the display property for (auto i: All) { auto fn = i->GetText(COL_FILENAME); 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); } 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 VcHg: { IsLogging = StartCmd("log", &VcFolder::ParseLog); StartCmd("resolve -l", &VcFolder::ParseResolveList); break; } case VcPending: { break; } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); break; } } CommitListDirty = false; } PROF("GetBranches"); if (GetBranches()) OnBranchesChange(); 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 = !d->CommitFilter; if (d->CommitFilter) { const char *s = l->GetRev(); if (s && strstr(s, d->CommitFilter) != NULL) Add = true; s = l->GetAuthor(); if (s && stristr(s, d->CommitFilter) != NULL) Add = true; s = l->GetMsg(); 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 { LAssert(!"Parse failed."); 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: case VcGit: { LString File = GetFilePart(uri); ParseParams *Params = new ParseParams(uri); Args.Printf("log \"%s\"", File.Get()); IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal); 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: { - LInput Dlg(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location"); - if (Dlg.DoModal()) + auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location"); + Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { - Uri.Set(Dlg.GetStr()); - Empty(); - Select(true); - } + 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) { - LInput i(GetTree(), "", "Git user name:", AppName); - if (i.DoModal()) + if (ctrlId) { LString Args; - Args.Printf("config --global user.name \"%s\"", i.GetStr().Get()); + 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; + }); } - } - { - LInput i(GetTree(), "", "Git user email:", AppName); - if (i.DoModal()) - { - LString Args; - Args.Printf("config --global user.email \"%s\"", i.GetStr().Get()); - StartCmd(Args); - } - } + + 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 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::ParseResolveList(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { auto lines = s.Replace("\r").Split("\n"); for (auto &ln: lines) { auto p = ln.Split(" ", 1); if (p.Length() == 2) { if (p[0].Equals("U")) { 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); } } } break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcHg: { d->Log->Print("Resolve: %s\n", s.Get()); break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path, LvcResolve Type) { if (!Path) return false; switch (GetType()) { case VcGit: { LString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcHg: { LString a; auto local = GetFilePart(Path); switch (Type) { case ResolveMark: a.Printf("resolve -m \"%s\"", local.Get()); break; case ResolveUnmark: a.Printf("resolve -u \"%s\"", local.Get()); break; case ResolveLocal: a.Printf("resolve -t internal:local \"%s\"", local.Get()); break; case ResolveIncoming: a.Printf("resolve -t internal:other \"%s\"", local.Get()); break; default: break; } if (a) return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); break; } case VcSvn: 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/Makefile.haiku b/Makefile.haiku --- a/Makefile.haiku +++ b/Makefile.haiku @@ -1,204 +1,209 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = lgi ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d - Defs = -D_DEBUG -DHAIKU -D_REENTRANT -DLGI_LIBRARY -DPOSIX -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 + Defs = -D_DEBUG -DHAIKU -D_REENTRANT -DLGI_LIBRARY -DPOSIX -D_GNU_SOURCE Libs = \ -static-libgcc \ -lgnu \ -lnetwork \ -lbe Inc = \ -I./private/haiku \ -I./private/common \ -I./include/lgi/haiku \ -I./include else Flags += -s -Os -std=c++14 - Defs = -DHAIKU -D_REENTRANT -DLGI_LIBRARY -DPOSIX -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 + Defs = -DHAIKU -D_REENTRANT -DLGI_LIBRARY -DPOSIX -D_GNU_SOURCE Libs = \ -static-libgcc \ -lgnu \ -lnetwork \ -lbe Inc = \ -I./private/haiku \ -I./private/common \ -I./include/lgi/haiku \ -I./include endif # Dependencies Source = src/haiku/Window.cpp \ src/haiku/Widgets.cpp \ src/haiku/View.cpp \ src/haiku/Thread.cpp \ + src/haiku/ShowFileProp_Haiku.cpp \ src/haiku/ScreenDC.cpp \ src/haiku/Printer.cpp \ src/haiku/PrintDC.cpp \ src/haiku/Menu.cpp \ src/haiku/MemDC.cpp \ src/haiku/Mem.cpp \ src/haiku/Layout.cpp \ src/haiku/General.cpp \ src/haiku/Gdc2.cpp \ src/haiku/File.cpp \ src/haiku/DragAndDrop.cpp \ src/haiku/ClipBoard.cpp \ src/haiku/App.cpp \ src/common/Widgets/Tree.cpp \ src/common/Widgets/ToolBar.cpp \ src/common/Widgets/TextLabel.cpp \ src/common/Widgets/TabView.cpp \ src/common/Widgets/TableLayout.cpp \ src/common/Widgets/StatusBar.cpp \ src/common/Widgets/Splitter.cpp \ src/common/Widgets/Slider.cpp \ src/common/Widgets/ScrollBar.cpp \ src/common/Widgets/RadioGroup.cpp \ src/common/Widgets/ProgressDlg.cpp \ src/common/Widgets/Progress.cpp \ src/common/Widgets/Popup.cpp \ src/common/Widgets/Panel.cpp \ src/common/Widgets/List.cpp \ src/common/Widgets/ItemContainer.cpp \ src/common/Widgets/Edit.cpp \ src/common/Widgets/Combo.cpp \ src/common/Widgets/CheckBox.cpp \ src/common/Widgets/Button.cpp \ src/common/Widgets/Box.cpp \ src/common/Widgets/Bitmap.cpp \ src/common/Text/XmlTree.cpp \ src/common/Text/Utf8.cpp \ src/common/Text/Unicode.cpp \ src/common/Text/Token.cpp \ src/common/Text/TextView3.cpp \ src/common/Text/String.cpp \ src/common/Text/DocView.cpp \ src/common/Skins/Gel/Gel.cpp \ src/common/Resource/Res.cpp \ src/common/Resource/LgiRes.cpp \ src/common/Net/Uri.cpp \ src/common/Net/NetTools.cpp \ src/common/Net/Net.cpp \ src/common/Net/MDStringToDigest.cpp \ src/common/Net/Base64.cpp \ src/common/Lgi/WindowCommon.cpp \ src/common/Lgi/ViewCommon.cpp \ src/common/Lgi/Variant.cpp \ src/common/Lgi/TrayIcon.cpp \ src/common/Lgi/ToolTip.cpp \ src/common/Lgi/ThreadEvent.cpp \ src/common/Lgi/ThreadCommon.cpp \ src/common/Lgi/SubProcess.cpp \ src/common/Lgi/Stream.cpp \ src/common/Lgi/Rand.cpp \ src/common/Lgi/OptionsFile.cpp \ src/common/Lgi/Object.cpp \ src/common/Lgi/Mutex.cpp \ src/common/Lgi/Mru.cpp \ src/common/Lgi/MenuCommon.cpp \ src/common/Lgi/MemStream.cpp \ src/common/Lgi/LMsg.cpp \ src/common/Lgi/Library.cpp \ src/common/Lgi/LgiCommon.cpp \ src/common/Lgi/Input.cpp \ src/common/Lgi/GuiUtils.cpp \ src/common/Lgi/FontSelect.cpp \ src/common/Lgi/FindReplace.cpp \ src/common/Lgi/FileSelect.cpp \ src/common/Lgi/DragAndDropCommon.cpp \ src/common/Lgi/DataDlg.cpp \ src/common/Lgi/CssTools.cpp \ src/common/Lgi/Css.cpp \ src/common/Lgi/AppCommon.cpp \ src/common/Lgi/Alert.cpp \ src/common/Hash/sha1/sha1.c \ src/common/Hash/md5/md5.c \ src/common/General/Properties.cpp \ src/common/General/Password.cpp \ src/common/General/FileCommon.cpp \ src/common/General/ExeCheck.cpp \ src/common/General/DateTime.cpp \ src/common/General/Containers.cpp \ src/common/Gdc2/Tools/GdcTools.cpp \ src/common/Gdc2/Tools/ColourReduce.cpp \ src/common/Gdc2/Surface.cpp \ src/common/Gdc2/Rect.cpp \ src/common/Gdc2/Path/Path.cpp \ src/common/Gdc2/GdcCommon.cpp \ src/common/Gdc2/Font/TypeFace.cpp \ src/common/Gdc2/Font/StringLayout.cpp \ src/common/Gdc2/Font/FontType.cpp \ src/common/Gdc2/Font/FontSystem.cpp \ - src/common/Gdc2/Font/FontCodePages.cpp \ src/common/Gdc2/Font/Font.cpp \ src/common/Gdc2/Font/DisplayString.cpp \ + src/common/Gdc2/Font/Charset.cpp \ src/common/Gdc2/Filters/Filter.cpp \ src/common/Gdc2/Colour.cpp \ src/common/Gdc2/Alpha.cpp \ src/common/Gdc2/8Bit.cpp \ src/common/Gdc2/32Bit.cpp \ src/common/Gdc2/24Bit.cpp \ src/common/Gdc2/16Bit.cpp \ src/common/Gdc2/15Bit.cpp -SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Sources))) - +SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) +Deps := $(patsubst %.o,%.d,$(Objects)) # Target TargetFile = lib$(Target)$(Tag).so $(TargetFile) : $(Objects) + mkdir -p $(BuildDir) @echo Linking $(TargetFile) [$(Build)]... $(CPP)$s -shared \ \ -o $(BuildDir)/$(TargetFile) \ $(Objects) \ $(Libs) @echo Done. .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $( #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: { - LInput Dlg(Tree, GetText(), "Enter the name for the object", "Object Name"); - if (Dlg.DoModal()) + auto Dlg = new LInput(Tree, GetText(), "Enter the name for the object", "Object Name"); + Dlg->DoModal([&](auto dlg, auto id) { - Obj->Wnd()->Name(Dlg.GetStr()); - Update(); - Obj->App()->SetDirty(true); - } + if (id) + { + Obj->Wnd()->Name(Dlg->GetStr()); + Update(); + Obj->App()->SetDirty(true); + } + delete dlg; + }); break; } case IDM_IMPORT: { - LFileSelect Select; - Select.Parent(Obj->App()); - Select.Type("Text", "*.txt"); - if (Select.Open()) + auto Select = new LFileSelect(Obj->App()); + Select->Type("Text", "*.txt"); + Select->Open([&](auto dlg, auto status) { - LFile F; - if (F.Open(Select.Name(), O_READ)) + if (status) { - SerialiseContext Ctx; - Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type()); - if (Res) + LFile F; + if (F.Open(dlg->Name(), O_READ)) { - // TODO - // Res->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."); } } - 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()) { - LFileSelect s; - s.Parent(this); - if (s.Open()) + auto s = new LFileSelect(this); + s->Open([&](auto dlg, auto status) { - auto File = App->GetCurFile(); - if (File) + if (status) { - LFile::Path p = File; - p--; - auto Rel = LMakeRelativePath(p, s.Name()); - if (Rel) - SetCtrlName(c->Id, Rel); - else - SetCtrlName(c->Id, s.Name()); + 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); } - else SetCtrlName(c->Id, s.Name()); - - Fields.SetMode(FieldTree::UiToObj); - Fields.SetView(this); - Source->Serialize(Fields); - - return 0; - } + 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: { - ShowLanguagesDlg Dlg(this); + 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: { - Search s(this); - if (s.DoModal()) + auto s = new Search(this); + s->DoModal([&](auto dlg, auto id) { - new Results(this, &s); - } + 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() { - LFileSelect s; - s.Parent(this); - s.Type("Lgi Resource", "*.lr8"); - if (s.Open()) + auto s = new LFileSelect(this); + s->Type("Lgi Resource", "*.lr8"); + s->Open([&](auto dlg, auto status) { - new ResCompare(GetCurFile(), s.Name()); - } + if (status) + new ResCompare(GetCurFile(), dlg->Name()); + delete dlg; + }); } void AppWnd::ImportLang() { // open dialog - LFileSelect Select; - - Select.Parent(this); - Select.Type("Lgi Resources", "*.lr8;*.xml"); - - if (Select.Open()) + auto Select = new LFileSelect(this); + Select->Type("Lgi Resources", "*.lr8;*.xml"); + Select->Open([&](auto dlg, auto status) { - LFile F; - if (F.Open(Select.Name(), O_READ)) + if (status) { - SerialiseContext Ctx; - Ctx.Format = GetFormat(Select.Name()); - - // convert file to Xml objects - LXmlTag *Root = new LXmlTag; - if (Root) + LFile F; + if (F.Open(dlg->Name(), O_READ)) { - LXmlTree Tree(GXT_NO_ENTITIES); - if (Tree.Read(Root, &F, 0)) + SerialiseContext Ctx; + Ctx.Format = GetFormat(dlg->Name()); + + // convert file to Xml objects + LXmlTag *Root = new LXmlTag; + if (Root) { - List Menus; - List Groups; - for (auto t: Root->Children) + LXmlTree Tree(GXT_NO_ENTITIES); + if (Tree.Read(Root, &F, 0)) { - if (t->IsTag("menu")) + List Menus; + List Groups; + for (auto t: Root->Children) { - ResMenu *Menu = new ResMenu(this); - if (Menu && Menu->Read(t, Ctx)) + if (t->IsTag("menu")) { - Menus.Insert(Menu); + ResMenu *Menu = new ResMenu(this); + if (Menu && Menu->Read(t, Ctx)) + { + Menus.Insert(Menu); + } + else break; } - else break; + else if (t->IsTag("string-group")) + { + ResStringGroup *g = new ResStringGroup(this); + if (g && g->Read(t, Ctx)) + { + Groups.Insert(g); + } + 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; + Ctx.PostLoad(this); + + bool HasData = false; for (auto g: Groups) { - for (int i=0; iGetLanguages(); i++) + g->SetLanguages(); + + if (g->GetStrs()->Length() > 0 && + g->GetLanguages() > 0) { - LLanguage *Lang = g->GetLanguage(i); - if (Lang) + HasData = true; + } + } + + if (HasData) + { + List Langs; + for (auto g: Groups) + { + for (int i=0; iGetLanguages(); i++) { - bool Has = false; - for (auto l: Langs) + LLanguage *Lang = g->GetLanguage(i); + if (Lang) { - if (stricmp((char*)l, (char*)Lang) == 0) + bool Has = false; + for (auto l: Langs) { - Has = true; - break; + if (stricmp((char*)l, (char*)Lang) == 0) + { + Has = true; + break; + } } - } - if (!Has) - { - Langs.Insert(Lang); + if (!Has) + { + Langs.Insert(Lang); + } } } } - } - - LangDlg Dlg(this, Langs); - if (Dlg.DoModal() == IDOK && - Dlg.Lang) - { - LStringPipe Errors; + + 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; + 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) + for (auto g: Groups) { - 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) - ) - ) + List::I Strings = g->GetStrs()->begin(); + for (ResString *s=*Strings; s; s=*++Strings) { - Different++; - d->Set(Str, Dlg.Lang->Id); - Imported++; + 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); + } } } - else + + List Lst; + if (ListObjects(Lst)) { - 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) + for (auto m: Menus) { - 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) + // find matching menu in our list + ResMenu *Match = 0; + for (auto r: Lst) { - if (s->GetRef() == d->GetRef()) + ResMenu *n = dynamic_cast(r); + if (n && stricmp(n->Name(), m->Name()) == 0) { - 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++; - } + Match = n; break; } } - if (!FoundRef) + + if (Match) { - NotFound++; - - char e[256]; - sprintf(e, "MenuString ref=%i (%s)\n", s->GetRef(), s->GetDefine()); - Errors.Push(e); + // 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(); } } - Match->SetLanguages(); + for (auto r: Lst) + { + ResStringGroup *StrRes = dynamic_cast(r); + if (StrRes) + { + StrRes->SetLanguages(); + } + } } - } - - for (auto r: Lst) - { - ResStringGroup *StrRes = dynamic_cast(r); - if (StrRes) - { - StrRes->SetLanguages(); - } - } - } - - char *ErrorStr = Errors.NewStr(); + + 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*)""); + 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, "No language information to import", AppName, MB_OK); + LgiMsg(this, "Failed to parse XML from file.\nError: %s", AppName, MB_OK, Tree.GetErrorMsg()); } - // Groups.DeleteObjects(); - // Menus.DeleteObjects(); + DeleteObj(Root); } - 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")) { - return LoadWin32(FileName); + 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 (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; } } } -bool AppWnd::LoadWin32(const char *FileName) + + +void AppWnd::LoadWin32(const char *FileName) { bool Status = false; - LFileSelect Select; 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(); - if (!FileName) + auto Load = [&](const char *FileName) { - Select.Parent(this); - Select.Type("Win32 Resource Script", "*.rc"); - if (Select.Open()) - { - FileName = Select.Name(); - } - } - - if (FileName) - { + if (!FileName) + return; + LProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("K"); Progress.SetScale(1.0/1024.0); - char *FileTxt = LReadTextFile(Select.Name()); + 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, Select.Name()); + 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; + }); } - - return Status; } 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/ResourceEditor/Code/LgiResEdit.h b/ResourceEditor/Code/LgiResEdit.h --- a/ResourceEditor/Code/LgiResEdit.h +++ b/ResourceEditor/Code/LgiResEdit.h @@ -1,899 +1,899 @@ /*hdr ** FILE: LgiRes.h ** AUTHOR: Matthew Allen ** DATE: 22/10/00 ** DESCRIPTION: Resource Editor App Header ** */ #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/DocApp.h" #include "lgi/common/Properties.h" #include "lgi/common/Variant.h" #include "lgi/common/DataDlg.h" #include "lgi/common/OptionsFile.h" #include "resource.h" #include "lgi/common/Tree.h" #include "lgi/common/Box.h" #include "lgi/common/EventTargetThread.h" //////////////////////////////////////////////////////////////////////////////////////////// // Defines // version #define APP_VER "4.1" // window messages #define IDM_UNDO 201 #define IDM_REDO 202 /* #define IDM_CUT 203 #define IDM_COPY 204 #define IDM_PASTE 205 */ #define IDM_DELETE 300 #define IDM_RENAME 301 #define IDM_SETTINGS 302 #define IDM_IMPORT 303 #define IDM_IMPORT_WIN32 304 #define IDM_EXPORT 305 #define IDM_EXPORT_WIN32 306 #define IDM_NEW_LANG 307 #define IDM_DELETE_LANG 308 #define IDM_IMPORT_LANG 309 #define IDM_PROPERTIES 310 #define IDM_NEW_SUB 311 #define IDM_NEW_ITEM 312 #define IDM_DELETE_ITEM 313 #define IDM_MOVE_LEFT 314 #define IDM_MOVE_RIGHT 315 #define IDM_SET_LANG 316 #define IDM_TAB_ORDER 317 #define IDM_DUMP 318 #define IDM_COPY_TEXT 319 #define IDM_PASTE_TEXT 320 #define IDM_NEW_ID 321 #define IDM_COMPARE 322 #define IDM_MOVE_UP 324 #define IDM_MOVE_DOWN 325 #define IDM_REF_EQ_ID 326 #define IDM_ID_EQ_REF 327 #define IDM_UP 328 #define IDM_DOWN 329 #define IDM_SET_OK 330 #define IDM_SET_CANCEL 331 #define IDM_LANG_BASE 2000 #define IDC_TABLE 999 #define IDC_CHAT_MSG 1000 #define STATUS_NORMAL 0 #define STATUS_INFO 1 #define STATUS_MAX 2 #define VAL_Ref "Ref" #define VAL_Id "Id" #define VAL_Define "Define" #define VAL_Tag "Tag" #define VAL_Text "Text" #define VAL_CellClass "CellClass" #define VAL_CellStyle "CellStyle" #define VAL_Text "Text" #define VAL_Pos "Pos" #define VAL_x1 "x1" #define VAL_y1 "y1" #define VAL_x2 "x2" #define VAL_y2 "y2" #define VAL_Visible "Visible" #define VAL_Enabled "Enabled" #define VAL_Class "Class" #define VAL_Style "Style" #define VAL_VerticalAlign "valign" #define VAL_HorizontalAlign "align" #define VAL_Children "children" #define VAL_Span "span" #define VAL_Image "image" #define VAL_Toggle "toggle" // Misc class AppWnd; #define MainWnd ((AppWnd*)LApp::ObjInstance()->AppWnd) // App enum ObjectTypes { TYPE_CSS = 1, TYPE_DIALOG, TYPE_STRING, TYPE_MENU }; enum IconTypes { ICON_FOLDER, ICON_IMAGE, ICON_ICON, ICON_CURSOR, ICON_DIALOG, ICON_STRING, ICON_MENU, ICON_DISABLED, ICON_CSS }; #define OPT_ShowLanguages "ShowLang" #define StrDialogSymbols "_Dialog Symbols_" extern char TranslationStrMagic[]; extern char AppName[]; //////////////////////////////////////////////////////////////////////////////////////////// // Functions extern char *EncodeXml(const char *s, int l = -1); extern char *DecodeXml(const char *s, int l = -1); //////////////////////////////////////////////////////////////////////////////////////////// // Classes class AppWnd; class ObjTreeItem; class ResString; typedef List StringList; class ResMenu; class ResDialog; class ResStringGroup; class ResMenuItem; struct ErrorInfo { ResString *Str; LAutoString Msg; }; class ErrorCollection { public: LArray StrErr; }; struct SerialiseContext { ResFileFormat Format; LStringPipe Log; LArray FixId; SerialiseContext() : Log(512) { Format = Lr8File; } void PostLoad(AppWnd *App); }; class Resource { friend class AppWnd; friend class ObjTreeItem; protected: int ResType; AppWnd *AppWindow; ObjTreeItem *Item; bool SysObject; public: Resource(AppWnd *w, int t = 0, bool enabled = true); virtual ~Resource(); AppWnd *App() { return AppWindow; } bool SystemObject() { return SysObject; } void SystemObject(bool i) { SysObject = i; } bool IsSelected(); virtual LView *Wnd() { return NULL; } virtual bool Attach(LViewI *Parent); virtual int Type() { return ResType; } virtual void Type(int i) { ResType = i; } virtual void Create(LXmlTag *load, SerialiseContext *ctx) = 0; // called when users creates virtual ResStringGroup *GetStringGroup() { return 0; } // Sub classes virtual ResStringGroup *IsStringGroup() { return 0; } virtual ResDialog *IsDialog() { return 0; } virtual ResMenu *IsMenu() { return 0; } // Serialization virtual bool Test(ErrorCollection *e) = 0; virtual bool Read(LXmlTag *t, SerialiseContext &Ctx) = 0; virtual bool Write(LXmlTag *t, SerialiseContext &Ctx) = 0; virtual StringList *GetStrs() { return NULL; } // Clipboard virtual void Delete() {} virtual void Copy(bool Delete = false) {} virtual void Paste() {} // UI virtual LView *CreateUI() { return 0; } virtual void OnRightClick(LSubMenu *RClick) {} virtual void OnCommand(int Cmd) {} virtual void OnShowLanguages() {} }; class ResFolder : public Resource, public LView { public: ResFolder(AppWnd *w, int t, bool enabled = true); LView *Wnd() { return dynamic_cast(this); } void Create(LXmlTag *load, SerialiseContext *ctx) { LAssert(0); } bool Test(ErrorCollection *e) { return false; } bool Read(LXmlTag *t, SerialiseContext &Ctx) { return false; } bool Write(LXmlTag *t, SerialiseContext &Ctx) { return false; } }; class ResFrame : public LLayout { Resource *Child; public: ResFrame(Resource *child); ~ResFrame(); bool Pour(LRegion &r); bool OnKey(LKey &k); void OnPaint(LSurface *pDC); bool Attach(LViewI *p); void OnFocus(bool b); }; class ObjTreeItem : public LTreeItem { friend class Resource; Resource *Obj; public: ObjTreeItem(Resource *Object); ~ObjTreeItem(); Resource *GetObj() { return Obj; } const char *GetText(int i=0); void OnSelect(); void OnMouseClick(LMouse &m); }; class ObjContainer : public LTree { friend class AppWnd; ObjTreeItem *Style; ObjTreeItem *Dialogs; ObjTreeItem *Strings; ObjTreeItem *Menus; LImageList *Images; AppWnd *Window; bool AppendChildren(ObjTreeItem *Item, List &Lst); public: ObjContainer(AppWnd *w); ~ObjContainer(); Resource *CurrentResource(); bool ListObjects(List &Lst); }; class FieldTree { public: enum FieldMode { None, UiToObj, ObjToUi, ObjToStore, StoreToObj, }; struct Field { // Global FieldTree *Tree; LAutoString Label; LAutoString Name; int Type; int Id; bool Multiline; void *Token; Field(FieldTree *tree) { Tree = tree; Type = 0; Id = 0; Token = 0; Multiline = false; } }; typedef LArray FieldArr; protected: int &NextId; FieldMode Mode; LViewI *View; LDom *Store; bool Deep; LHashTbl, FieldArr*> f; FieldArr *Get(void *Token, bool Create = false) { FieldArr *a = f.Find(Token); if (!a) { if (Create) f.Add(Token, a = new FieldArr); else LAssert(0); } return a; } Field *GetField(void *Token, const char *FieldName) { if (!Token || !FieldName) return 0; FieldArr *a = Get(Token); if (!a) return 0; for (int i=0; iLength(); i++) { if (!stricmp((*a)[i]->Name, FieldName)) return (*a)[i]; } return 0; } public: FieldTree(int &next, bool deep) : NextId(next) { Mode = None; View = 0; Store = 0; Deep = deep; } ~FieldTree() { Empty(); } FieldMode GetMode() { return Mode; } bool GetDeep() { return Deep; } void SetMode(FieldMode m) { Mode = m; } void SetView(LViewI *v) { View = v; } void SetStore(LDom *p) { Store = p; } void Empty() { // for (FieldArr *a = f.First(); a; a = f.Next()) for (auto a : f) { a.value->DeleteObjects(); } f.DeleteObjects(); View = 0; } void Insert(void *Token, int Type, int Reserved, const char *Name, const char *Label, int Idx = -1, bool Multiline = false) { FieldArr *a = Get(Token, true); if (!a) return; Field *n = new Field(this); if (n) { n->Token = Token; n->Label.Reset(NewStr(Label)); n->Name.Reset(NewStr(Name)); n->Id = NextId++; n->Type = Type; n->Multiline = Multiline; a->Add(n); } } void Serialize(void *Token, const char *FieldName, int &i) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlValue(f->Id, i); break; case UiToObj: i = (int)View->GetCtrlValue(f->Id); break; case StoreToObj: if (Store->GetValue(FieldName, v)) i = v.CastInt32(); break; case ObjToStore: Store->SetValue(FieldName, v = i); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, bool &b, int Default = -1) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant i; switch (Mode) { case ObjToUi: View->SetCtrlValue(f->Id, b); break; case UiToObj: b = View->GetCtrlValue(f->Id); break; case StoreToObj: { if (Store->GetValue(FieldName, i)) { b = i.CastInt32() != 0; } break; } case ObjToStore: if (Default < 0 || (bool)Default != b) Store->SetValue(FieldName, i = b); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, char *&s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: { DeleteArray(s); auto t = View->GetCtrlName(f->Id); if (ValidStr(t)) s = NewStr(t); break; } case StoreToObj: { DeleteArray(s); if (Store->GetValue(FieldName, v)) s = v.ReleaseStr(); break; } case ObjToStore: Store->SetValue(FieldName, v = s); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LAutoString &s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: { auto t = View->GetCtrlName(f->Id); s.Reset(ValidStr(t) ? NewStr(t) : 0); break; } case StoreToObj: { s.Reset(Store->GetValue(FieldName, v) ? v.ReleaseStr() : 0); break; } case ObjToStore: Store->SetValue(FieldName, v = s); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LString &s) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, s); break; case UiToObj: s = View->GetCtrlName(f->Id); break; case StoreToObj: if (Store->GetValue(FieldName, v)) s = v.Str(); else s.Empty(); break; case ObjToStore: Store->SetValue(FieldName, v = s.Get()); break; default: LAssert(0); } } void Serialize(void *Token, const char *FieldName, LRect &r) { Field *f = GetField(Token, FieldName); if (!f) return; LVariant v; switch (Mode) { case ObjToUi: View->SetCtrlName(f->Id, r.GetStr()); break; case UiToObj: { r.SetStr(View->GetCtrlName(f->Id)); break; } case StoreToObj: { if (Store->GetValue(FieldName, v)) { r.SetStr(v.Str()); } break; } case ObjToStore: { Store->SetValue(FieldName, v = r.GetStr()); break; } default: LAssert(0); } } static int FieldArrCmp(FieldArr **a, FieldArr **b) { return (**a)[0]->Id - (**b)[0]->Id; } void GetAll(LArray &Fields) { // for (FieldArr *a = f.First(0); a; a = f.Next(0)) for (auto a : f) { Fields.Add(a.value); } Fields.Sort(FieldArrCmp); } }; #define M_OBJECT_CHANGED (M_USER + 4567) class FieldSource { friend class FieldView; int _FieldView; public: FieldSource() { _FieldView = -1; } virtual ~FieldSource() {} virtual bool GetFields(FieldTree &Fields) = 0; virtual bool Serialize(FieldTree &Fields) = 0; void OnFieldChange() { if (_FieldView >= 0) PostThreadEvent(_FieldView, M_OBJECT_CHANGED, (LMessage::Param)this); } }; #include "LgiRes_Dialog.h" class FieldView : public LLayout { protected: FieldSource *Source; FieldTree Fields; int NextId; bool Ignore; AppWnd *App; public: FieldView(AppWnd *app); ~FieldView(); void Serialize(bool Write); void OnPosChange(); void OnSelect(FieldSource *s); void OnDelete(FieldSource *s); LMessage::Result OnEvent(LMessage *m); void OnPaint(LSurface *pDC); int OnNotify(LViewI *Ctrl, LNotification n); }; class ShortCutView : public LWindow { AppWnd *App; LList *Lst; public: ShortCutView(AppWnd *app); ~ShortCutView(); void OnDialogChange(ResDialog *Dlg); int OnNotify(LViewI *Ctrl, LNotification n); }; #include "LgiRes_String.h" class AppWnd : public LDocApp { protected: // UI LBox *HBox; LBox *VBox; LView *ContentView; LSubMenu *Edit; LSubMenu *Help; LSubMenu *ViewMenu; LStatusBar *Status; LStatusPane *StatusInfo[STATUS_MAX]; ShortCutView *ShortCuts; // App ObjContainer *Objs; Resource *LastRes; FieldView *Fields; // Languages int CurLang; LArray Languages; LHashTbl, bool> ShowLanguages; void SortDialogs(); void GetFileTypes(LFileSelect *Dlg, bool Write); public: AppWnd(); ~AppWnd(); // Languages bool ShowLang(LLanguageId Lang); void ShowLang(LLanguageId Lang, bool Show); LLanguage *GetCurLang(); void SetCurLang(LLanguage *L); LArray *GetLanguages(); void OnLanguagesChange(LLanguageId Lang, bool Add, bool Update = false); // --------------------------------------------------------------------- // Application Resource *NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select = true); bool InsertObject(int Type, Resource *r, bool Select = true); void DelObject(Resource *r); bool ListObjects(List &Lst); int GetUniqueStrRef(int Start = 1); int GetUniqueCtrlId(); void FindStrings(List &Strs, char *Define = 0, int *CtrlId = 0); ResString *GetStrFromRef(int Ref); ResStringGroup *GetDialogSymbols(); bool Empty(); void OnObjChange(FieldSource *r); void OnObjSelect(FieldSource *r); void OnObjDelete(FieldSource *r); void OnResourceSelect(Resource *r); void OnResourceDelete(Resource *r); void GotoObject(class ResString *s, ResStringGroup *g, ResDialog *d, ResMenuItem *m, ResDialogCtrl *c); ShortCutView *GetShortCutView(); void OnCloseView(ShortCutView *v); // --------------------------------------------------------------------- // Methods void SetStatusText(char *Text, int Pane = 0); bool TestLgi(bool Quite = true); bool LoadLgi(const char *FileName = 0); bool SaveLgi(const char *FileName = 0); - bool LoadWin32(const char *FileName = 0); + void LoadWin32(const char *FileName = 0); bool SaveWin32(); void ImportLang(); void Compare(); bool WriteDefines(LStream &Defs); bool OpenFile(const char *FileName, bool Ro); bool SaveFile(const char *FileName); // --------------------------------------------------------------------- // Window int OnNotify(LViewI *Ctrl, LNotification n); LMessage::Result OnEvent(LMessage *m); int OnCommand(int Cmd, int Event, OsView Handle); void OnReceiveFiles(LArray &Files); void OnCreate(); }; #define INVALID_INT -10000 #define NEW_UI 1 struct SearchParams { LString::Array Text; #if NEW_UI bool LimitToText; bool LimitToDefine; #else LString Define; #endif LLanguageId InLang; LLanguageId NotInLang; int Ref; int CtrlId; SearchParams() { InLang = NULL; NotInLang = NULL; Ref = INVALID_INT; CtrlId = INVALID_INT; #if NEW_UI LimitToText = false; LimitToDefine = false; #else #endif } }; class SearchThread : public LEventTargetThread { List Res; AppWnd *App; LList *Results; SearchParams Params; LCancel State; bool HasContent(char *s); class Result *Test(class ResString *s); class Result *Test(class ResMenuItem *mi); public: SearchThread(AppWnd *app, LList *results); void Search(SearchParams &p); LMessage::Result OnEvent(LMessage *Msg); }; class Search : public LDialog, public SearchParams { AppWnd *App; LAutoPtr Thread; void OnCheck(); public: Search(AppWnd *app); int OnNotify(LViewI *c, LNotification n); }; class Results : public LWindow { class ResultsPrivate *d; public: Results(AppWnd *app, Search *search); ~Results(); void OnPosChange(); int OnNotify(LViewI *v, LNotification n); }; class ShowLanguagesDlg : public LDialog { class ShowLanguagesDlgPriv *d; public: ShowLanguagesDlg(AppWnd *app); ~ShowLanguagesDlg(); int OnNotify(LViewI *v, LNotification n); }; class ResCss : public Resource, public LLayout { friend class ResCssUi; protected: class ResCssUi *Ui; LAutoString Style; public: ResCss(AppWnd *w, int type = TYPE_CSS); ~ResCss(); void Create(LXmlTag *Load, SerialiseContext *Ctx); LView *Wnd() { return dynamic_cast(this); } void OnShowLanguages(); // Resource LView *CreateUI(); void OnRightClick(LSubMenu *RClick); void OnCommand(int Cmd); int OnCommand(int Cmd, int Event, OsView hWnd); // Serialize bool Test(ErrorCollection *e); bool Read(LXmlTag *t, SerialiseContext &Ctx); bool Write(LXmlTag *t, SerialiseContext &Ctx); }; extern void OpenTableLayoutTest(LViewI *p); diff --git a/ResourceEditor/Code/LgiRes_Dialog.cpp b/ResourceEditor/Code/LgiRes_Dialog.cpp --- a/ResourceEditor/Code/LgiRes_Dialog.cpp +++ b/ResourceEditor/Code/LgiRes_Dialog.cpp @@ -1,4362 +1,4371 @@ /* ** FILE: LgiRes_Dialog.cpp ** AUTHOR: Matthew Allen ** DATE: 5/8/1999 ** DESCRIPTION: Dialog Resource Editor ** ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ // Old control offset 3,17 //////////////////////////////////////////////////////////////////// #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "lgi/common/Button.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Menu.h" #include "lgi/common/StatusBar.h" #include "lgi/common/ToolBar.h" #include "resdefs.h" //////////////////////////////////////////////////////////////////// #define IDC_UP 101 #define IDC_DOWN 102 #define DEBUG_OVERLAY 0 // Name mapping table class LgiObjectName { public: int Type; const char *ObjectName; char *ResourceName; bool ToolbarBtn; } NameMap[] = { // ID Lgi's name Resource editor name {UI_DIALOG, "LDialog", Res_Dialog, false}, {UI_TABLE, "LTableLayout", Res_Table, true}, {UI_TEXT, "LText", Res_StaticText, true}, {UI_EDITBOX, "LEdit", Res_EditBox, true}, {UI_CHECKBOX, "LCheckBox", Res_CheckBox, true}, {UI_BUTTON, "LButton", Res_Button, true}, {UI_GROUP, "LRadioGroup", Res_Group, true}, {UI_RADIO, "LRadioButton", Res_RadioBox, true}, {UI_TABS, "LTabView", Res_TabView, true}, {UI_TAB, "LTabPage", Res_Tab, false}, {UI_LIST, "LList", Res_ListView, true}, {UI_COLUMN, "LListColumn", Res_Column, false}, {UI_COMBO, "LCombo", Res_ComboBox, true}, {UI_TREE, "LTree", Res_TreeView, true}, {UI_BITMAP, "LBitmap", Res_Bitmap, true}, {UI_PROGRESS, "LProgressView", Res_Progress, true}, {UI_SCROLL_BAR, "LScrollBar", Res_ScrollBar, true}, {UI_CUSTOM, "LCustom", Res_Custom, true}, {UI_CONTROL_TREE, "LControlTree", Res_ControlTree, true}, // If you add a new control here update ResDialog::CreateCtrl(int Tool) as well {0, 0, 0, 0} }; class CtrlItem : public LListItem { friend class TabOrder; ResDialogCtrl *Ctrl; public: CtrlItem(ResDialogCtrl *ctrl) { Ctrl = ctrl; } const char *GetText(int Col) { switch (Col) { case 0: { if (Ctrl && Ctrl->GetStr()) return Ctrl->GetStr()->GetDefine(); break; } case 1: { if (Ctrl && Ctrl->GetStr()) return Ctrl->GetStr()->Get(); break; } } return NULL; } }; class TabOrder : public LDialog { ResDialogCtrl *Top; LList *Lst; LButton *Ok; LButton *Cancel; LButton *Up; LButton *Down; public: TabOrder(LView *Parent, ResDialogCtrl *top) { Top = top; SetParent(Parent); Children.Insert(Lst = new LList(IDC_LIST, 10, 10, 350, 300)); Children.Insert(Ok = new LButton(IDOK, Lst->GetPos().x2 + 10, 10, 60, 20, "Ok")); Children.Insert(Cancel = new LButton(IDCANCEL, Lst->GetPos().x2 + 10, Ok->GetPos().y2 + 5, 60, 20, "Cancel")); Children.Insert(Up = new LButton(IDC_UP, Lst->GetPos().x2 + 10, Cancel->GetPos().y2 + 15, 60, 20, "Up")); Children.Insert(Down = new LButton(IDC_DOWN, Lst->GetPos().x2 + 10, Up->GetPos().y2 + 5, 60, 20, "Down")); LRect r(0, 0, Ok->GetPos().x2 + 17, Lst->GetPos().y2 + 40); SetPos(r); MoveToCenter(); Lst->AddColumn("#define", 150); Lst->AddColumn("Text", 150); Lst->MultiSelect(false); if (Top) { for (auto c: Top->View()->IterateViews()) { ResDialogCtrl *Ctrl = dynamic_cast(c); if (Ctrl->GetType() != UI_TEXT) { Lst->Insert(new CtrlItem(Ctrl)); } } char s[256]; sprintf(s, "Set Tab Order: %s", Top->GetStr()->GetDefine()); Name(s); - - DoModal(); } } int OnNotify(LViewI *Ctrl, LNotification n) { int MoveDir = 1; switch (Ctrl->GetId()) { case IDC_UP: { MoveDir = -1; // fall through } case IDC_DOWN: { if (Lst) { CtrlItem *Sel = dynamic_cast(Lst->GetSelected()); if (Sel) { int Index = Lst->IndexOf(Sel); CtrlItem *Up = dynamic_cast(Lst->ItemAt(Index+MoveDir)); if (Up) { Lst->Remove(Sel); Lst->Insert(Sel, Index+MoveDir); Sel->Select(true); Sel->ScrollTo(); } } } break; } case IDOK: { if (Lst) { int i=0; List All; Lst->GetAll(All); for (auto n: All) { Top->View()->DelView(n->Ctrl->View()); Top->View()->AddView(n->Ctrl->View(), i++); } } // fall through } case IDCANCEL: { EndModal(0); break; } } return 0; } }; //////////////////////////////////////////////////////////////////// void DrawGoobers(LSurface *pDC, LRect &r, LRect *Goobers, LColour c, int OverIdx) { int Mx = (r.x2 + r.x1) / 2 - (GOOBER_SIZE / 2); int My = (r.y2 + r.y1) / 2 - (GOOBER_SIZE / 2); pDC->Colour(c); Goobers[0].x1 = r.x1 - GOOBER_BORDER; Goobers[0].y1 = r.y1 - GOOBER_BORDER; Goobers[0].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[0].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[1].x1 = Mx; Goobers[1].y1 = r.y1 - GOOBER_BORDER; Goobers[1].x2 = Mx + GOOBER_SIZE; Goobers[1].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].y1 = r.y1 - GOOBER_BORDER; Goobers[2].x2 = r.x2 + GOOBER_BORDER; Goobers[2].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].y1 = My; Goobers[3].x2 = r.x2 + GOOBER_BORDER; Goobers[3].y2 = My + GOOBER_SIZE; Goobers[4].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].x2 = r.x2 + GOOBER_BORDER; Goobers[4].y2 = r.y2 + GOOBER_BORDER; Goobers[5].x1 = Mx; Goobers[5].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[5].x2 = Mx + GOOBER_SIZE; Goobers[5].y2 = r.y2 + GOOBER_BORDER; Goobers[6].x1 = r.x1 - GOOBER_BORDER; Goobers[6].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].y2 = r.y2 + GOOBER_BORDER; Goobers[7].x1 = r.x1 - GOOBER_BORDER; Goobers[7].y1 = My; Goobers[7].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[7].y2 = My + GOOBER_SIZE; for (int i=0; i<8; i++) { if (OverIdx == i) pDC->Rectangle(Goobers+i); else pDC->Box(Goobers+i); } } //////////////////////////////////////////////////////////////////// int ResDialogCtrl::TabDepth = 0; ResDialogCtrl::ResDialogCtrl(ResDialog *dlg, char *CtrlTypeName, LXmlTag *load) : ResObject(CtrlTypeName) { Dlg = dlg; Client.ZOff(-1, -1); SelectStart.ZOff(-1, -1); if (load) { // Base a string off the xml int r = load->GetAsInt("ref"); if (Dlg) { SetStr(Dlg->Symbols->FindRef(r)); LAssert(GetStr()); if (!GetStr()) // oh well we should have one anyway... fix things up so to speak. SetStr(Dlg->Symbols->CreateStr()); } LAssert(GetStr()); } else if (Dlg->CreateSymbols) { // We create a symbol for this resource SetStr((Dlg && Dlg->Symbols) ? Dlg->Symbols->CreateStr() : NULL); if (GetStr()) { char Def[256]; sprintf(Def, "IDC_%i", GetStr()->GetRef()); GetStr()->SetDefine(Def); } } else { // Someone else is going to create the symbol SetStr(NULL); } } ResDialogCtrl::~ResDialogCtrl() { if (ResDialog::Symbols) { auto s = GetStr(); SetStr(NULL); ResDialog::Symbols->DeleteStr(s); } if (Dlg) { Dlg->App()->OnObjDelete(this); Dlg->OnDeselect(this); } } char *ResDialogCtrl::GetRefText() { static char Buf[64]; if (GetStr()) { sprintf(Buf, "Ref=%i", GetStr()->GetRef()); } else { Buf[0] = 0; } return Buf; } void ResDialogCtrl::ListChildren(List &l, bool Deep) { for (LViewI *w: View()->IterateViews()) { ResDialogCtrl *c = dynamic_cast(w); LAssert(c); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l); } } } } LRect ResDialogCtrl::GetMinSize() { LRect m(0, 0, GRID_X-1, GRID_Y-1); if (IsContainer()) { LRect cli = View()->GetClient(false); for (LViewI *c: View()->IterateViews()) { LRect cpos = c->GetPos(); cpos.Offset(cli.x1, cli.y1); m.Union(&cpos); } } return m; } bool ResDialogCtrl::SetPos(LRect &p, bool Repaint) { LRect m = GetMinSize(); if (m.X() > p.X()) p.x2 = p.x1 + m.X() - 1; if (m.Y() > p.Y()) p.y2 = p.y1 + m.Y() - 1; if (p != View()->GetPos()) { /* if (ParentCtrl) { if (p.x1 < ParentCtrl->Client.x1) { p.Offset(ParentCtrl->Client.x1 - p.x1, 0); } if (p.y1 < ParentCtrl->Client.y1) { p.Offset(0, ParentCtrl->Client.y1 - p.y1); } } */ // set our size bool Status = View()->SetPos(p, Repaint); // tell everyone else about the change OnFieldChange(); LRect r(0, 0, p.X()-1, p.Y()-1); r.Inset(-GOOBER_BORDER, -GOOBER_BORDER); View()->Invalidate(&r, false, true); // check our parents are big enough to show us... ResDialogCtrl *Par = ParentCtrl(); if (Par) { LRect t = Par->View()->GetPos(); Par->ResDialogCtrl::SetPos(t, true); } return Status; } return true; } bool ResDialogCtrl::SetStr(ResString *s) { if (_Str == s) { if (_Str) LAssert(_Str->Refs.HasItem(this)); return true; } if (_Str) { if (!_Str->Refs.HasItem(this)) { LAssert(!"Refs incorrect."); return false; } _Str->Refs.Delete(this); } _Str = s; if (_Str) { if (_Str->Refs.HasItem(this)) { LAssert(!"Refs already has us."); return false; } _Str->Refs.Add(this); } else { // LgiTrace("%s:%i - %p::SetStr(NULL)\n", _FL, this); } return true; } void ResDialogCtrl::TabString(char *Str) { if (!Str) return; memset(Str, '\t', TabDepth); Str[TabDepth] = 0; } LRect ResDialogCtrl::AbsPos() { LViewI *w = View(); LRect r = w->GetPos(); r.Offset(-r.x1, -r.y1); for (; w && w != Dlg; w = w->GetParent()) { LRect pos = w->GetPos(); if (w->GetParent()) { // LView *Ctrl = w->GetParent()->GetGView(); LRect client = w->GetParent()->GetClient(false); r.Offset(pos.x1 + client.x1, pos.y1 + client.y1); } else { r.Offset(pos.x1, pos.y1); } } return r; } void ResDialogCtrl::StrFromRef(int Ref) { // get the string object SetStr(Dlg->App()->GetStrFromRef(Ref)); if (!GetStr()) { LgiTrace("%s:%i - String with ref '%i' missing.\n", _FL, Ref); LAssert(0); if (SetStr(Dlg->App()->GetDialogSymbols()->CreateStr())) { GetStr()->SetRef(Ref); } else return; } // if this assert fails then the Ref doesn't exist // and the string can't be found // if this assert fails then the Str is already // associated with a control, and thus we would // duplicate the pointer to the string if we let // it go by // LAssert(Str->UpdateWnd == 0); // set the string's control to us GetStr()->UpdateWnd = View(); // make the strings refid unique GetStr()->UnDuplicate(); View()->Name(GetStr()->Get()); } bool ResDialogCtrl::GetFields(FieldTree &Fields) { if (GetStr()) { GetStr()->GetFields(Fields); } int Id = 101; Fields.Insert(this, DATA_STR, Id++, VAL_Pos, "Pos"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Visible, "Visible"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Enabled, "Enabled"); Fields.Insert(this, DATA_STR, Id++, VAL_Class, "Class", -1); Fields.Insert(this, DATA_STR, Id++, VAL_Style, "Style", -1, true); return true; } bool ResDialogCtrl::Serialize(FieldTree &Fields) { if ((Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::UiToObj) && GetStr()) { GetStr()->Serialize(Fields); } LRect r = View()->GetPos(), Old = View()->GetPos(); bool e = true; if (Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::ObjToStore) { r = View()->GetPos(); e = View()->Enabled(); } Fields.Serialize(this, VAL_Pos, r); Fields.Serialize(this, VAL_Visible, Vis, true); Fields.Serialize(this, VAL_Enabled, e, true); Fields.Serialize(this, VAL_Class, CssClass); Fields.Serialize(this, VAL_Style, CssStyle); if (Fields.GetMode() == FieldTree::UiToObj || Fields.GetMode() == FieldTree::StoreToObj) { View()->Enabled(e); SetPos(r); r.Union(&Old); r.Inset(-GOOBER_BORDER, -GOOBER_BORDER); if (View()->GetParent()) { View()->GetParent()->Invalidate(&r); } } if (Dlg && Dlg->Item) Dlg->Item->Update(); return true; } void ResDialogCtrl::CopyText() { if (GetStr()) GetStr()->CopyText(); } void ResDialogCtrl::PasteText() { if (GetStr()) { GetStr()->PasteText(); View()->Invalidate(); } } bool ResDialogCtrl::AttachCtrl(ResDialogCtrl *Ctrl, LRect *r) { bool Status = false; if (Ctrl) { Ctrl->View()->Visible(true); View()->AddView(Ctrl->View()); Ctrl->View()->SetParent(View()); if (r) { if (!dynamic_cast(Ctrl->View()->GetParent())) { Dlg->SnapRect(r, this); } Ctrl->SetPos(*r); } if (Dlg->Resource::IsSelected()) { Dlg->OnSelect(Ctrl); } Status = true; } return Status; } void ResDialogCtrl::OnPaint(LSurface *pDC) { if (DragCtrl >= 0) { LRect r = DragRgn; r.Normal(); pDC->Colour(L_FOCUS_SEL_BACK); pDC->Box(&r); } } LMouse ResDialogCtrl::MapToDialog(LMouse m) { // Convert co-ords from out own local space to be relative to 'Dlg' // the parent dialog. LMouse Ms = m; LViewI *Parent; for (LViewI *i = View(); i && i != (LViewI*)Dlg; i = Parent) { Parent = i->GetParent(); LRect Pos = i->GetPos(), Cli = i->GetClient(false); #if DEBUG_OVERLAY LgiTrace("%s %i,%i + %i,%i + %i,%i = %i,%i\n", i->GetClass(), Ms.x, Ms.y, Pos.x1, Pos.y1, Cli.x1, Cli.y1, Ms.x + Pos.x1 + Cli.x1, Ms.y + Pos.y1 + Cli.y1); #endif Ms.x += Pos.x1 + Cli.x1; Ms.y += Pos.y1 + Cli.y1; } return Ms; } void ResDialogCtrl::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { // LgiTrace("Click down=%i %i,%i\n", m.Down(), m.x, m.y); if (Dlg) { #if DEBUG_OVERLAY LPoint Prev(0, 0); auto &DebugOverlay = Dlg->DebugOverlay; if (!DebugOverlay) { DebugOverlay.Reset(new LMemDC(Dlg->X(), Dlg->Y(), System32BitColourSpace)); DebugOverlay->Colour(0, 32); DebugOverlay->Rectangle(); DebugOverlay->Colour(LColour(64, 192, 64)); } #endif bool Processed = false; LRect c = View()->GetClient(); bool ClickedThis = c.Overlap(m.x, m.y); // Convert co-ords from out own local space to be relative to 'Dlg' // the parent dialog. LMouse Ms = MapToDialog(m); #if DEBUG_OVERLAY if (DebugOverlay) { DebugOverlay->Line(Prev.x, Prev.y, Ms.x, Ms.y); DebugOverlay->Circle(Ms.x, Ms.y, 5); Prev.x = Ms.x; Prev.y = Ms.y; } #endif Dlg->OnMouseClick(Ms); if (ClickedThis && !Dlg->IsDraging()) { DragCtrl = Dlg->CurrentTool(); if ((DragCtrl > 0 && AcceptChildren) || ((DragCtrl == 0) && !Movable)) { LPoint p(m.x, m.y); Dlg->SnapPoint(&p, ParentCtrl()); DragStart.x = DragRgn.x1 = DragRgn.x2 = p.x; DragStart.y = DragRgn.y1 = DragRgn.y2 = p.y; View()->Capture(true); Processed = true; } else { DragCtrl = -1; if (Movable) { DragRgn.x1 = m.x; DragRgn.y1 = m.y; MoveCtrl = true; Processed = true; View()->Capture(true); } } SelectMode = (m.Shift()) ? SelAdd : SelSet; SelectStart = View()->GetPos(); } } } else if (m.IsContextMenu()) { LSubMenu RClick; bool PasteData = false; bool PasteTranslations = false; CtrlButton *Btn = dynamic_cast(this); { LClipBoard c(Dlg); char *Clip = c.Text(); if (Clip) { PasteTranslations = strstr(Clip, TranslationStrMagic); char *p = Clip; while (*p && strchr(" \r\n\t", *p)) p++; PasteData = *p == '<'; } } RClick.AppendItem("Cu&t", IDM_CUT, Dlg->Selection.Length()>0); RClick.AppendItem("&Copy Control", IDM_COPY, Dlg->Selection.Length()>0); RClick.AppendItem("&Paste", IDM_PASTE, PasteData); RClick.AppendSeparator(); RClick.AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick.AppendItem("Paste Text", IDM_PASTE_TEXT, PasteTranslations); RClick.AppendSeparator(); RClick.AppendItem("&Delete", IDM_DELETE, Dlg->Selection.Length()>0); if (Btn) { RClick.AppendSeparator(); RClick.AppendItem("Set 'Ok'", IDM_SET_OK); RClick.AppendItem("Set 'Cancel'", IDM_SET_CANCEL); } if (Dlg->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Dlg, m.x, m.y)) { case IDM_DELETE: { Dlg->Delete(); break; } case IDM_CUT: { Dlg->Copy(true); break; } case IDM_COPY: { Dlg->Copy(); break; } case IDM_PASTE: { Dlg->Paste(); break; } case IDM_COPY_TEXT: { ResDialogCtrl *Ctrl = Dlg->Selection[0]; if (Ctrl) Ctrl->CopyText(); break; } case IDM_PASTE_TEXT: { PasteText(); break; } case IDM_SET_OK: { ResString *s = Btn->GetStr(); if (s) { s->Set("Ok"); s->SetDefine("IDOK"); } break; } case IDM_SET_CANCEL: { ResString *s = Btn->GetStr(); if (s) { s->Set("Cancel"); s->SetDefine("IDCANCEL"); } break; } } } } } else { if (DragCtrl >= 0) { bool Exit = false; if (Dlg && (DragRgn.X() > 1 || DragRgn.Y() > 1)) { if (DragCtrl == 0) { Dlg->SelectRect(this, &DragRgn, SelectMode != SelAdd); } else { ResDialogCtrl *Ctrl = Dlg->CreateCtrl(DragCtrl, 0); if (Ctrl) { AttachCtrl(Ctrl, &DragRgn); } } Exit = true; } DragCtrl = -1; View()->Invalidate(); View()->Capture(false); if (Exit) { return; } } if (MoveCtrl) { View()->Capture(false); MoveCtrl = false; } if (SelectMode > SelNone) { LRect r = View()->GetPos(); if (SelectStart == r) { Dlg->OnSelect(this, SelectMode != SelAdd); } SelectMode = SelNone; } } } void ResDialogCtrl::OnMouseMove(LMouse &m) { // Drag a rubber band... if (DragCtrl >= 0) { LRect Old = DragRgn; DragRgn.x1 = DragStart.x; DragRgn.y1 = DragStart.y; DragRgn.x2 = m.x; DragRgn.y2 = m.y; DragRgn.Normal(); Dlg->SnapRect(&DragRgn, this); Old.Union(&DragRgn); Old.Inset(-1, -1); View()->Invalidate(&Old); } if (Dlg) { LMouse Ms = MapToDialog(m); Dlg->OnMouseMove(Ms); } // Move some ctrl(s) if (MoveCtrl && !m.Shift()) { int Dx = m.x - DragRgn.x1; int Dy = m.y - DragRgn.y1; if (Dx != 0 || Dy != 0) { // LgiTrace("Move %i,%i + %i,%i\n", m.x, m.y, Dx, Dy); if (!Dlg->IsSelected(this)) { Dlg->OnSelect(this); } LRect Old = View()->GetPos(); LRect New = Old; New.Offset( m.x - DragRgn.x1, m.y - DragRgn.y1); LPoint p(New.x1, New.y1); Dlg->SnapPoint(&p, ParentCtrl()); New.Set(p.x, p.y, p.x + New.X() - 1, p.y + New.Y() - 1); if (New != Old) { Dlg->MoveSelection(New.x1 - Old.x1, New.y1 - Old.y1); } } } } char *ReadInt(char *s, int &Value) { char *c = strchr(s, ','); if (c) { *c = 0; Value = atoi(s); return c+1; } Value = atoi(s); return 0; } void ResDialogCtrl::ReadPos(char *Str) { if (Str) { char *s = NewStr(Str); if (s) { LRect r = View()->GetPos(); char *p = ReadInt(s, r.x1); if (p) p = ReadInt(p, r.y1); if (p) p = ReadInt(p, r.x2); if (p) p = ReadInt(p, r.y2); DeleteArray(s); } } } //////////////////////////////////////////////////////////////////// CtrlDlg::CtrlDlg(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Dialog, load) { Movable = false; AcceptChildren = true; GetStr()->UpdateWnd = View(); View()->Name("CtrlDlg"); } IMPL_DIALOG_CTRL(CtrlDlg) LRect &CtrlDlg::GetClient(bool InClientSpace) { static LRect r; Client.Set(0, 0, View()->X()-1, View()->Y()-1); Client.Inset(2, 2); Client.y1 += LAppInst->GetMetric(LGI_MET_DECOR_CAPTION); if (Client.y1 > Client.y2) Client.y1 = Client.y2; if (InClientSpace) r = Client.ZeroTranslate(); else r = Client; return r; } void CtrlDlg::OnNcPaint(LSurface *pDC, LRect &r) { // Draw the border LWideBorder(pDC, r, DefaultRaisedEdge); // Draw the title bar int TitleY = LAppInst->GetMetric(LGI_MET_DECOR_CAPTION); LRect t = r; t.y2 = t.y1 + TitleY - 1; pDC->Colour(L_ACTIVE_TITLE); pDC->Rectangle(&t); if (GetStr()) { LDisplayString ds(LSysFont, GetStr()->Get()); LSysFont->Fore(L_ACTIVE_TITLE_TEXT); LSysFont->Transparent(true); ds.Draw(pDC, t.x1 + 10, t.y1 + ((t.Y()-ds.Y())/2)); } r.y1 = t.y2 + 1; } void CtrlDlg::OnPaint(LSurface *pDC) { Client = GetClient(); // Draw the grid pDC->Colour(L_MED); pDC->Rectangle(&Client); pDC->Colour(Rgb24(0x80, 0x80, 0x80), 24); for (int y=Client.y1; ySet(x, y); } ResDialogCtrl::OnPaint(pDC); } ///////////////////////////////////////////////////////////////////// // Text box CtrlText::CtrlText(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_StaticText, load) { if (GetStr() && !load) { GetStr()->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlText) void CtrlText::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); char *Text = GetStr()->Get(); LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); if (Text) { LRect Client = GetClient(); int y = 0; char *Start = Text; for (char *s = Text; 1; s++) { if ((*s == '\\' && *(s+1) == 'n') || (*s == 0)) { LDisplayString ds(LSysFont, Start, s - Start); ds.Draw(pDC, 0, y, &Client); y += 15; Start = s + 2; if (*s == '\\') s++; } if (*s == 0) break; } } } // Editbox CtrlEditbox::CtrlEditbox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_EditBox, load) { Password = false; MultiLine = false; } IMPL_DIALOG_CTRL(CtrlEditbox) void CtrlEditbox::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Enabled() ? L_WORKSPACE : L_MED); pDC->Rectangle(&r); char *Text = GetStr()->Get(); LSysFont->Fore(Enabled() ? L_TEXT : L_LOW); LSysFont->Transparent(true); if (Text) { if (Password) { char *t = NewStr(Text); if (t) { for (char *p = t; *p; p++) *p = '*'; LDisplayString ds(LSysFont, t); ds.Draw(pDC, 4, 4); DeleteArray(t); } } else { LDisplayString ds(LSysFont, Text); ds.Draw(pDC, 4, 4); } } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Password "Pw" #define VAL_MultiLine "MultiLine" bool CtrlEditbox::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_BOOL, 300, VAL_Password, "Password"); Fields.Insert(this, DATA_BOOL, 301, VAL_MultiLine, "MultiLine"); } return Status; } bool CtrlEditbox::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Password, Password, false); Fields.Serialize(this, VAL_MultiLine, MultiLine, false); } return Status; } // Check box CtrlCheckbox::CtrlCheckbox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_CheckBox, load) { } IMPL_DIALOG_CTRL(CtrlCheckbox) void CtrlCheckbox::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r(0, 0, 12, 12); // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(L_WORKSPACE); pDC->Rectangle(&r); LPoint Pt[6] = { LPoint(3, 4), LPoint(3, 7), LPoint(5, 10), LPoint(10, 5), LPoint(10, 2), LPoint(5, 7)}; pDC->Colour(0); pDC->Polygon(6, Pt); pDC->Set(3, 5); char *Text = GetStr()->Get(); if (Text) { LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Button CtrlButton::CtrlButton(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Button, load) { IsToggle = false; } IMPL_DIALOG_CTRL(CtrlButton) bool CtrlButton::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); int Id = 160; Fields.Insert(this, DATA_FILENAME, Id++, VAL_Image, "Image"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Toggle, "Toggle"); return Status; } bool CtrlButton::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Image, Image); Fields.Serialize(this, VAL_Toggle, IsToggle); } return Status; } void CtrlButton::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r = Client; char *Text = GetStr()->Get(); // Draw the ctrl LWideBorder(pDC, r, DefaultRaisedEdge); LSysFont->Fore(L_TEXT); if (ValidStr(Text)) { LSysFont->Back(L_MED); LSysFont->Transparent(false); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x1 + ((r.X()-ds.X())/2), r.y1 + ((r.Y()-ds.Y())/2), &r); } else { pDC->Colour(L_MED); pDC->Rectangle(&r); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Group CtrlGroup::CtrlGroup(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Group, load) { AcceptChildren = true; if (GetStr() && !load) { GetStr()->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlGroup) void CtrlGroup::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r = Client; // Draw the ctrl r.y1 += 5; LWideBorder(pDC, r, EdgeXpChisel); r.y1 -= 5; LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); char *Text = GetStr()->Get(); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x1 + 8, r.y1 - 2); // Draw children //LWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //Radio button uint32_t RadioBmp[] = { 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x80808080, 0x80808080, 0x80808080, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x8080C0C0, 0x80808080, 0x00000000, 0x00000000, 0x00000000, 0x80808080, 0xC0C08080, 0xC0C0C0C0, 0x80C0C0C0, 0x00008080, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFF0000, 0xC0C0C0FF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x80C0C0C0, 0xDFDF8080, 0xDFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFDFDFDF, 0xFFFFDFDF, 0xC0C0C0FF, 0xC0C0C0C0, 0xFFFFC0C0, 0xFFFFFFFF, 0xDFDFDFDF, 0xDFDFDFDF, 0xDFDFDFDF, 0xFFFFFFFF, 0xC0C0FFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0}; CtrlRadio::CtrlRadio(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_RadioBox, load) { Bmp = new LMemDC; if (Bmp && Bmp->Create(12, 12, GdcD->GetColourSpace())) { int Len = ((Bmp->X()*24)+31)/32*4; for (int y=0; yY(); y++) { for (int x=0; xX(); x++) { uchar *s = ((uchar*) RadioBmp) + (y * Len) + (x * 3); Bmp->Colour(Rgb24(s[0], s[1], s[2]), 24); Bmp->Set(x, y); } } } } CtrlRadio::~CtrlRadio() { DeleteObj(Bmp); } IMPL_DIALOG_CTRL(CtrlRadio) void CtrlRadio::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r(0, 0, 12, 12); // Draw the ctrl if (Bmp) pDC->Blt(r.x1, r.y1, Bmp); char *Text = GetStr()->Get(); if (Text) { LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Tab CtrlTab::CtrlTab(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Tab, load) { GetStr()->UpdateWnd = this; } IMPL_DIALOG_CTRL(CtrlTab) void CtrlTab::OnPaint(LSurface *pDC) { } void CtrlTab::ListChildren(List &l, bool Deep) { ResDialogCtrl *Ctrl = dynamic_cast(GetParent()); CtrlTabs *Par = dynamic_cast(Ctrl); LAssert(Par); auto MyIndex = Par->Tabs.IndexOf(this); LAssert(MyIndex >= 0); List *CList = (Par->Current == MyIndex) ? &Par->Children : &Children; for (auto w: *CList) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l, Deep); } } } } // Tab control CtrlTabs::CtrlTabs(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_TabView, load) { AcceptChildren = true; Current = 0; if (!load) { for (int i=0; i<3; i++) { CtrlTab *t = new CtrlTab(dlg, load); if (t) { char Text[256]; sprintf(Text, "Tab %i", i+1); if (t->GetStr()) { t->GetStr()->Set(Text); t->SetParent(this); Tabs.Insert(t); } else { DeleteObj(t); } } } } } CtrlTabs::~CtrlTabs() { Empty(); } void CtrlTabs::OnMouseMove(LMouse &m) { ResDialogCtrl::OnMouseMove(m); } void CtrlTabs::ShowMe(ResDialogCtrl *Child) { CtrlTab *t = dynamic_cast(Child); if (t) { auto Idx = Tabs.IndexOf(t); if (Idx >= 0) { ToTab(); Current = Idx; FromTab(); } } } void CtrlTabs::EnumCtrls(List &Ctrls) { List::I it = Tabs.begin(); for (CtrlTab *t = *it; t; t = *++it) { t->EnumCtrls(Ctrls); } ResDialogCtrl::EnumCtrls(Ctrls); } LRect CtrlTabs::GetMinSize() { List l; ListChildren(l, false); LRect r(0, 0, GRID_X-1, GRID_Y-1); /* // don't resize smaller than the tabs for (CtrlTab *t = Tabs.First(); t; t = Tabs.Next()) { r.x2 = max(r.x2, t->r.x2 + 10); r.y2 = max(r.y2, t->r.y2 + 10); } */ // don't resize smaller than any of the children // on any of the tabs LRect cli = GetClient(false); for (auto c: l) { LRect cpos = c->View()->GetPos(); cpos.Offset(cli.x1, cli.y1); r.Union(&cpos); } return r; } void CtrlTabs::ListChildren(List &l, bool Deep) { int n=0; for (auto t: Tabs) { l.Add(t); auto It = (Current == n ? (LViewI*)this : (LViewI*)t)->IterateViews(); for (LViewI *w: It) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l, Deep); } } n++; } } void CtrlTabs::Empty() { ToTab(); Tabs.DeleteObjects(); } void CtrlTabs::OnPaint(LSurface *pDC) { // Draw the ctrl Title.ZOff(X()-1, 17); Client.ZOff(X()-1, Y()-1); Client.y1 = Title.y2; LRect r = Client; LWideBorder(pDC, r, DefaultRaisedEdge); // Draw the tabs int i = 0; int x = 2; for (auto Tab: Tabs) { char *Str = Tab->GetStr() ? Tab->GetStr()->Get() : 0; LDisplayString ds(LSysFont, Str); int Width = 12 + ds.X(); LRect t(x, Title.y1 + 2, x + Width - 1, Title.y2 - 1); if (Current == i) { t.Inset(-2, -2); if (Tab->IterateViews().Length() > 0) FromTab(); } if (Tab->View()->GetPos() != t) Tab->View()->SetPos(t); pDC->Colour(L_LIGHT); pDC->Line(t.x1, t.y1+2, t.x1, t.y2); pDC->Set(t.x1+1, t.y1+1); pDC->Line(t.x1+2, t.y1, t.x2-2, t.y1); pDC->Colour(L_MED); pDC->Line(t.x1+1, t.y1+2, t.x1+1, t.y2); pDC->Line(t.x1+2, t.y1+1, t.x2-2, t.y1+1); pDC->Colour(L_LOW); pDC->Line(t.x2-1, t.y1, t.x2-1, t.y2); pDC->Colour(0, 24); pDC->Line(t.x2, t.y1+2, t.x2, t.y2); t.Inset(2, 2); t.y2 += 2; LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); ds.Draw(pDC, t.x1 + ((t.X()-ds.X())/2), t.y1 + ((t.Y()-ds.Y())/2), &t); x += Width + ((Current == i) ? 2 : 1); i++; } // Draw children //LWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } void CtrlTabs::ToTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // move all our children into the tab losing focus auto CurIt = Cur->IterateViews(); auto ThisIt = IterateViews(); for (auto v: CurIt) { Cur->DelView(v); } for (auto v: ThisIt) { DelView(v); Cur->AddView(v); } } } void CtrlTabs::FromTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // load all our children from the new tab auto CurIt = Cur->IterateViews(); auto ThisIt = IterateViews(); for (auto v: ThisIt) DelView(v); for (auto v: CurIt) { Cur->DelView(v); AddView(v); } } } void CtrlTabs::OnMouseClick(LMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { // select current tab int i = 0; for (auto Tab: Tabs) { if (Tab->View()->GetPos().Overlap(m.x, m.y) /* && i != Current*/) { ToTab(); Current = i; FromTab(); Dlg->OnSelect(Tab); Invalidate(); break; } i++; } } if (m.IsContextMenu() && Title.Overlap(m.x, m.y)) { auto RClick = new LSubMenu; if (RClick) { bool HasTab = Tabs.ItemAt(Current); RClick->AppendItem("New tab", IDM_NEW, true); RClick->AppendItem("Delete tab", IDM_DELETE, HasTab); RClick->AppendItem("Rename tab", IDM_RENAME, HasTab); RClick->AppendItem("Move tab left", IDM_MOVE_LEFT, HasTab); RClick->AppendItem("Move tab right", IDM_MOVE_RIGHT, HasTab); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, true); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_NEW: { CtrlTab *t = new CtrlTab(Dlg, 0); if (t) { char Text[256]; sprintf(Text, "Tab " LPrintfSizeT, Tabs.Length()+1); t->GetStr()->Set(Text); t->SetParent(this); Tabs.Insert(t); } break; } case IDM_DELETE: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { ToTab(); Tabs.Delete(t); DeleteObj(t); FromTab(); } break; } case IDM_RENAME: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { if (!t->GetStr()) t->SetStr(Dlg->CreateSymbol()); - LInput Input(this, t->GetStr()->Get(), "Enter tab name:", "Rename"); - Input.SetParent(Dlg); - if (Input.DoModal() && Input.GetStr()) + auto Input = new LInput(this, t->GetStr()->Get(), "Enter tab name:", "Rename"); + Input->SetParent(Dlg); + Input->DoModal([t, Input](auto dlg, auto id) { - t->GetStr()->Set(Input.GetStr()); - } + if (id) + t->GetStr()->Set(Input->GetStr()); + delete dlg; + }); } break; } case IDM_MOVE_LEFT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current > 0) { Tabs.Delete(t); Tabs.Insert(t, --Current); } break; } case IDM_MOVE_RIGHT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current < Tabs.Length()-1) { Tabs.Delete(t); Tabs.Insert(t, ++Current); } break; } case IDM_COPY_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->CopyText(); } break; } case IDM_PASTE_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->PasteText(); } break; } } Invalidate(); } } return; } } ResDialogCtrl::OnMouseClick(m); } // List column ListCol::ListCol(ResDialog *dlg, LXmlTag *load, char *s, int Width) : ResDialogCtrl(dlg, Res_Column, load) { if (s && GetStr()) { GetStr()->Set(s); } LRect r(0, 0, Width-1, 18); ResDialogCtrl::SetPos(r); } IMPL_DIALOG_CTRL(ListCol) void ListCol::OnPaint(LSurface *pDC) { } // List control CtrlList::CtrlList(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ListView, load) { DragCol = -1; } CtrlList::~CtrlList() { Empty(); } void CtrlList::ListChildren(List &l, bool Deep) { if (Deep) { for (auto w: Cols) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l); } } } } void CtrlList::Empty() { for (auto c: Cols) { DeleteObj(c); } Cols.Empty(); } void CtrlList::OnMouseClick(LMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { int x=0; ListCol *c = 0; ssize_t DragOver = -1; for (auto Col: Cols) { if (m.x >= Col->r().x1 && m.x <= Col->r().x2) { if (m.x > Col->r().x2 - 6) { DragOver = Cols.IndexOf(Col); } if (m.x < Col->r().x1 + 6) { DragOver = Cols.IndexOf(Col) - 1; } c = Col; break; } x += Col->r().X(); } if (m.Left()) { if (DragOver >= 0) { DragCol = DragOver; Capture(true); } } else if (m.IsContextMenu()) { auto RClick = new LSubMenu; if (RClick) { bool HasCol = c != 0; RClick->AppendItem("New column", IDM_NEW, true); RClick->AppendItem("Delete column", IDM_DELETE, HasCol); RClick->AppendItem("Rename column", IDM_RENAME, HasCol); RClick->AppendItem("Move column left", IDM_MOVE_LEFT, HasCol); RClick->AppendItem("Move column right", IDM_MOVE_RIGHT, HasCol); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, HasCol); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, HasCol); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_COPY_TEXT: { if (c && c->GetStr()) { c->GetStr()->CopyText(); } break; } case IDM_PASTE_TEXT: { if (c && c->GetStr()) { c->GetStr()->PasteText(); } break; } case IDM_NEW: { ListCol *c = dynamic_cast(Dlg->CreateCtrl(UI_COLUMN,0)); if (c) { char Text[256]; sprintf(Text, "Col " LPrintfSizeT, Cols.Length()+1); c->GetStr()->Set(Text); Cols.Insert(c); } break; } case IDM_DELETE: { Cols.Delete(c); DeleteObj(c); break; } case IDM_RENAME: { if (c) { - LInput Input(this, c->GetStr()->Get(), "Enter column name:", "Rename"); - Input.SetParent(Dlg); - if (Input.DoModal()) + auto Input = new LInput(this, c->GetStr()->Get(), "Enter column name:", "Rename"); + Input->SetParent(Dlg); + Input->DoModal([Input, c](auto dlg, auto id) { - c->GetStr()->Set(Input.GetStr()); - } + if (id) + c->GetStr()->Set(Input->GetStr()); + delete dlg; + }); } break; } case IDM_MOVE_LEFT: { auto Current = Cols.IndexOf(c); if (c && Current > 0) { Cols.Delete(c); Cols.Insert(c, --Current); } break; } case IDM_MOVE_RIGHT: { auto Current = Cols.IndexOf(c); if (c && Current < Cols.Length()-1) { Cols.Delete(c); Cols.Insert(c, ++Current); } break; } } Invalidate(); } DeleteObj(RClick); return; } } return; } } else { if (DragCol >= 0) { Capture(false); DragCol = -1; } } ResDialogCtrl::OnMouseClick(m); } void CtrlList::OnMouseMove(LMouse &m) { if (DragCol >= 0) { int i=0, x=0;; for (auto Col: Cols) { if (i == DragCol) { int Dx = (m.x - x - Title.x1); LRect r = Col->GetPos(); r.x2 = r.x1 + Dx; Col->ResDialogCtrl::SetPos(r); break; } x += Col->r().X(); i++; } Invalidate(); } ResDialogCtrl::OnMouseMove(m); } void CtrlList::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); Title = r; Title.y2 = Title.y1 + 15; Client = r; Client.y1 = Title.y2 + 1; pDC->Colour(L_WORKSPACE); pDC->Rectangle(&Client); int x = Title.x1; for (auto c: Cols) { int Width = c->r().X(); c->r().Set(x, Title.y1, x + Width - 1, Title.y2); LRect r = c->r(); r.x2 = MIN(r.x2, Title.x2); x = r.x2 + 1; if (r.Valid()) { LWideBorder(pDC, r, DefaultRaisedEdge); LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); const char *Str = c->GetStr()->Get(); if (!Str) Str = ""; LDisplayString ds(LSysFont, Str); ds.Draw(pDC, r.x1 + 2, r.y1 + ((r.Y()-ds.Y())/2) - 1, &r); } } LRect Client(x, Title.y1, Title.x2, Title.y2); if (Client.Valid()) { LWideBorder(pDC, Client, DefaultRaisedEdge); pDC->Colour(L_MED);; pDC->Rectangle(&Client); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Combo box CtrlComboBox::CtrlComboBox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ComboBox, load) { } IMPL_DIALOG_CTRL(CtrlComboBox) void CtrlComboBox::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); // Allocate space LRect e = r; e.x2 -= 15; LRect d = r; d.x1 = e.x2 + 1; // Draw edit pDC->Colour(L_WORKSPACE); pDC->Rectangle(&e); // Draw drap down LWideBorder(pDC, d, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&d); pDC->Colour(0, 24); int Size = 4; int cx = (d.X()/2) + d.x1 - 4; int cy = (d.Y()/2) + d.y1 - 3; for (int i=1; i<=Size; i++) { pDC->Line(cx+i, cy+i, cx+(Size*2)-i, cy+i); } // Text char *Text = GetStr()->Get(); LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); if (Text) { LDisplayString ds(LSysFont, Text); ds.Draw(pDC, 4, 4); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlScrollBar::CtrlScrollBar(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ScrollBar, load) { } IMPL_DIALOG_CTRL(CtrlScrollBar) void CtrlScrollBar::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl bool Vertical = r.Y() > r.X(); int ButSize = Vertical ? r.X() : r.Y(); LRect a, b, c; if (Vertical) { a.Set(r.x1, r.y1, r.x2, r.y1 + ButSize); c.Set(r.x1, r.y2 - ButSize, r.x2, r.y2); b.Set(r.x1, a.y2 + 1, r.x2, c.y1 - 1); } else { a.Set(r.x1, r.y1, r.x1 + ButSize, r.y2); c.Set(r.x2 - ButSize, r.y1, r.x2, r.y2); b.Set(a.x2 + 1, r.y1, c.x1 - 1, r.y2); } // Buttons LWideBorder(pDC, a, DefaultRaisedEdge); LWideBorder(pDC, c, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&a); pDC->Rectangle(&c); // Arrows pDC->Colour(0); int x = a.x1 + (a.X()/2) - 2; int y = a.y1 + (a.Y()/2); int i; for (i=0; i<5; i++) { pDC->Line(x+i, y-i, x+i, y+i); } x = c.x1 + (c.X()/2) - 2; y = c.y1 + (c.Y()/2); for (i=0; i<5; i++) { pDC->Line(x+i, y-(4-i), x+i, y+(4-i)); } // Slider pDC->Colour(0xd0d0d0, 24); pDC->Rectangle(&b); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlTree::CtrlTree(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_TreeView, load) { } IMPL_DIALOG_CTRL(CtrlTree) void CtrlTree::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); LSysFont->Colour(L_TEXT, L_WORKSPACE); LSysFont->Transparent(true); LDisplayString ds(LSysFont, "Tree"); ds.Draw(pDC, r.x1 + 3, r.y1 + 3, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlBitmap::CtrlBitmap(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Bitmap, load) { } IMPL_DIALOG_CTRL(CtrlBitmap) void CtrlBitmap::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); pDC->Colour(0, 24); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlProgress::CtrlProgress(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Progress, load) { } IMPL_DIALOG_CTRL(CtrlProgress) void CtrlProgress::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); COLOUR Flat = Rgb24(0x7f, 0x7f, 0x7f); COLOUR White = Rgb24(0xff, 0xff, 0xff); int Pos = 60; int x = Pos * r.X() / 100; pDC->Colour(Flat, 24); pDC->Rectangle(r.x1, r.y1, r.x1 + x, r.y2); pDC->Colour(White, 24); pDC->Rectangle(r.x1 + x + 1, r.y1, r.x2, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlCustom::CtrlCustom(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Custom, load) { Control = 0; } IMPL_DIALOG_CTRL(CtrlCustom) CtrlCustom::~CtrlCustom() { DeleteArray(Control); } void CtrlCustom::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); COLOUR White = Rgb24(0xff, 0xff, 0xff); pDC->Colour(White, 24); pDC->Rectangle(&r); char s[256] = "Custom: "; if (Control) { strcat(s, Control); } LSysFont->Colour(L_TEXT, L_WORKSPACE); LDisplayString ds(LSysFont, s); ds.Draw(pDC, r.x1+2, r.y1+1, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Control "Ctrl" bool CtrlCustom::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_STR, 320, VAL_Control, "Control", 1); } return Status; } bool CtrlCustom::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Control, Control); } return Status; } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// ResStringGroup *ResDialog::Symbols = 0; int ResDialog::SymbolRefs = 0; bool ResDialog::CreateSymbols = true; void ResDialog::AddLanguage(const char *Id) { if (Symbols) { Symbols->AppendLanguage(Id); } } void ResDialogCtrl::EnumCtrls(List &Ctrls) { Ctrls.Insert(this); for (LViewI *c: View()->IterateViews()) { ResDialogCtrl *dc = dynamic_cast(c); LAssert(dc); dc->EnumCtrls(Ctrls); } } void ResDialog::EnumCtrls(List &Ctrls) { for (auto ci: Children) { ResDialogCtrl *c = dynamic_cast(ci); if (c) c->EnumCtrls(Ctrls); } } int GooberCaps[] = { RESIZE_X1 | RESIZE_Y1, RESIZE_Y1, RESIZE_X2 | RESIZE_Y1, RESIZE_X2, RESIZE_X2 | RESIZE_Y2, RESIZE_Y2, RESIZE_X1 | RESIZE_Y2, RESIZE_X1}; ResDialog::ResDialog(AppWnd *w, int type) : Resource(w, type) { // Maintain a string group just for our dialog // defines if (!Symbols && w) { // First check to see of the symbols have been loaded from a file // and we don't have a pointer to it yet Symbols = App()->GetDialogSymbols(); if (!Symbols) { // else we need to create the symbols object Symbols = new ResStringGroup(w); if (Symbols) { Symbols->Name(StrDialogSymbols); w->InsertObject(TYPE_STRING, Symbols); } } if (Symbols) { Symbols->SystemObject(true); } } SymbolRefs++; // Init dialog resource Ui = 0; DlgPos.ZOff(500-1, 400-1); DragGoober = -1; DragX = 0; DragY = 0; DragCtrl = 0; } ResDialog::~ResDialog() { // Decrement and delete the shared string group SymbolRefs--; if (SymbolRefs < 1) { App()->DelObject(Symbols); Symbols = 0; } // Delete our Ui DeleteObj(Ui); } void ResDialog::OnShowLanguages() { // Current language changed. OnSelect(Selection[0]); Invalidate(); OnLanguageChange(); } void ResDialog::OnChildrenChanged(LViewI *Wnd, bool Attaching) { printf("ResDialog::OnChildrenChanged %p, %i\n", Wnd, Attaching); } const char *ResDialog::Name() { LViewI *v = Children[0]; ResDialogCtrl *Ctrl = dynamic_cast(v); if (!Ctrl) { static char msg[256]; sprintf_s(msg, sizeof(msg), "#no_ctrl=%p,children=%i", v, (int)Children.Length()); return msg; } if (!Ctrl->GetStr()) return "#no_str"; if (!Ctrl->GetStr()->GetDefine()) return "#no_defined"; return Ctrl->GetStr()->GetDefine(); } bool ResDialog::Name(const char *n) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl && Ctrl->GetStr()) { Ctrl->GetStr()->SetDefine((n)?n:""); return Ctrl->GetStr()->GetDefine() != 0; } return false; } char *ResDialog::StringFromRef(int Ref) { if (Symbols) { ResString *Str = Symbols->FindRef(Ref); if (Str) { return Str->Get(); } } return 0; } bool ResDialog::Res_GetProperties(ResObject *Obj, LDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::ObjToStore); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } LDom *ResDialog::Res_GetDom(ResObject *Obj) { return dynamic_cast(Obj); } bool ResDialog::Res_SetProperties(ResObject *Obj, LDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::StoreToObj); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } ResObject *ResDialog::CreateObject(LXmlTag *Tag, ResObject *Parent) { return dynamic_cast(CreateCtrl(Tag)); } void ResDialog::Res_SetPos(ResObject *Obj, int x1, int y1, int x2, int y2) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { LRect r(x1, y1, x2, y2); Ctrl->SetPos(r); } } } void ResDialog::Res_SetPos(ResObject *Obj, char *s) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Ctrl->ReadPos(s); } } } LRect ResDialog::Res_GetPos(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); LAssert(Ctrl); if (Ctrl) { return Ctrl->View()->GetPos(); } } return LRect(0, 0, 0, 0); } int ResDialog::Res_GetStrRef(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { return Ctrl->GetStr()->GetRef(); } } return -1; } bool ResDialog::Res_SetStrRef(ResObject *Obj, int Ref, ResReadCtx *Ctx) { ResDialogCtrl *Ctrl = 0; if (!Obj || !Symbols) return false; Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (!Ctrl) return false; Ctrl->StrFromRef(Ref); LAssert(Ctrl && Ctrl->GetStr()); return Ctrl->GetStr() != 0; } void ResDialog::Res_Attach(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); ResDialogCtrl *Par = dynamic_cast((ResDialogCtrl*)Parent); if (Ctrl && Par) { Par->AttachCtrl(Ctrl); } } } bool ResDialog::Res_GetChildren(ResObject *Obj, List *l, bool Deep) { bool Status = false; if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Status = true; List Child; Ctrl->ListChildren(Child, Deep); for (auto o: Child) { l->Insert(o); } } } return Status; } void ResDialog::Res_Append(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { CtrlTabs *Tabs = dynamic_cast(Obj); CtrlTab *Tab = dynamic_cast(Parent); if (Tabs && Tab) { Tab->SetParent(Tabs); Tabs->Tabs.Insert(Tab); if (Tabs->Tabs.Length() == 1) { Tabs->FromTab(); } return; } CtrlList *Lst = dynamic_cast(Obj); ListCol *Col = dynamic_cast(Parent); if (Lst && Col) { Lst->Cols.Insert(Col); return; } } } bool ResDialog::Res_GetItems(ResObject *Obj, List *l) { if (Obj && l) { CtrlTabs *Tabs = dynamic_cast(Obj); if (Tabs) { for (auto Tab: Tabs->Tabs) { l->Insert(Tab); } return true; } CtrlList *Lst = dynamic_cast(Obj); if (Lst) { for (auto Col: Lst->Cols) { l->Insert(Col); } return true; } } return false; } void ResDialog::Create(LXmlTag *load, SerialiseContext *Ctx) { CtrlDlg *Dlg = new CtrlDlg(this, load); if (Dlg) { LRect r = DlgPos; r.Offset(GOOBER_BORDER, GOOBER_BORDER); Children.Insert(Dlg); Dlg->SetParent(this); if (load) { if (Ctx) Read(load, *Ctx); else LAssert(0); } else { Dlg->ResDialogCtrl::SetPos(r); if (Dlg->GetStr()) Dlg->GetStr()->Set("Dialog"); } } } void ResDialog::Delete() { // Deselect the dialog ctrl OnDeselect(dynamic_cast(Children[0])); // Delete selected controls ResDialogCtrl *c; while ((c = Selection[0])) { c->View()->Detach(); DeleteObj(c); } // Repaint LView::Invalidate(); } bool IsChild(ResDialogCtrl *Parent, ResDialogCtrl *Child) { if (Parent && Child) { for ( ResDialogCtrl *c = Child->ParentCtrl(); c; c = c->ParentCtrl()) { if (c == Parent) { return true; } } } return false; } void GetTopCtrls(List &Top, List &Selection) { // all children will automatically be cut as well for (auto c: Selection) { // is c a child of an item already in Top? bool Ignore = false; for (auto p: Top) { if (IsChild(p, c)) { Ignore = true; break; } } if (!Ignore) { // is not a child Top.Insert(c); } } } void ResDialog::Copy(bool Delete) { bool Status = false; // Deselect the dialog... can't cut that OnDeselect(dynamic_cast(Children[0])); // Get top level list List Top; GetTopCtrls(Top, Selection); // ok we have a top level list of ctrls // write them to the file List All; LXmlTag *Root = new LXmlTag("Resources"); if (Root) { if (Delete) { // remove selection from UI AppWindow->OnObjSelect(0); } // write the string resources first for (auto c: Top) { All.Insert(c); c->ListChildren(All, true); } // write the strings out at the top of the block // so that we can reference them from the objects // below. SerialiseContext Ctx; for (auto c: All) { // Write the string out LXmlTag *t = new LXmlTag; if (t && c->GetStr()->Write(t, Ctx)) { Root->InsertTag(t); } else { DeleteObj(t); } } // write the objects themselves for (auto c: Top) { LXmlTag *t = new LXmlTag; if (t && Res_Write(c, t)) { Root->InsertTag(t); } else { DeleteObj(t); } } // Read the file in and copy to the clipboard LStringPipe Xml; LXmlTree Tree; if (Tree.Write(Root, &Xml)) { char *s = Xml.NewStr(); { LClipBoard Clip(Ui); char16 *w = Utf8ToWide(s); Clip.TextW(w); Status = Clip.Text(s, false); DeleteObj(w); } DeleteArray(s); } DeleteObj(Root); if (Delete && Status) { // delete them ResDialogCtrl *c; while ( Selection.Length() && (c = Selection[0])) { c->View()->Detach(); Selection.Delete(c); DeleteObj(c); } } // Repaint LView::Invalidate(); } } class StringId { public: ResString *Str; int OldRef; int NewRef; }; void RemapAllRefs(LXmlTag *t, List &Strs) { char *RefStr; if ((RefStr = t->GetAttr("Ref"))) { int r = atoi(RefStr); for (auto i: Strs) { if (i->OldRef == r) { // this string ref is to be remapped char Buf[32]; sprintf(Buf, "%i", i->NewRef); t->SetAttr("Ref", Buf); // delete the string ref map // it's not needed anymore Strs.Delete(i); DeleteObj(i); // leave the loop RefStr = 0; break; } } if (RefStr) { // if this assert failes then no map for this // string was found. every incomming string needs // to be remapped LAssert(0); } } for (auto c: t->Children) { RemapAllRefs(c, Strs); } } void ResDialog::Paste() { // Get the clipboard data char *Mem = 0; char *Data = 0; { LClipBoard Clip(Ui); char16 *w = Clip.TextW(); if (w) Data = Mem = WideToUtf8(w); else Data = Clip.Text(); } if (Data) { ResDialogCtrl *Container = 0; // Find a container to plonk the controls in ResDialogCtrl *c = Selection[0]; if (c && c->IsContainer()) { Container = c; } if (!Container) { // Otherwise just use the dialog as the container Container = dynamic_cast(Children[0]); } if (Container) { // remap list List Strings; int NextRef = 0; // Deselect everything OnSelect(NULL); // Parse the data List NewStrs; LXmlTree Tree; LStringPipe p; p.Push(Data); // Create the new controls, strings first // that way we can setup the remapping properly to avoid // string ref duplicates LXmlTag Root; if (Tree.Read(&Root, &p, 0)) { for (auto t: Root.Children) { if (t->IsTag("string")) { // string tag LAssert(Symbols); ResString *Str = Symbols->CreateStr(); SerialiseContext Ctx; if (Str && Str->Read(t, Ctx)) { // setup remap object, so that we can make all the strings // unique StringId *Id = new StringId; LAssert(Id); Id->Str = Str; Id->OldRef = Str->GetRef(); NextRef = Str->SetRef(Id->NewRef = AppWindow->GetUniqueStrRef(NextRef + 1)); Strings.Insert(Id); // insert out new string NewStrs.Insert(Str); } else { break; } } else { // object tag // all strings should have been processed by the time // we get in here. We remap the incomming string refs to // unique values so that we don't get conflicts later. RemapAllRefs(t, Strings); } } // load all the objects now List NewCtrls; for (auto t: Root.Children) { if (!t->IsTag("string")) { // object (not string) CreateSymbols = false; ResDialogCtrl *Ctrl = dynamic_cast(CreateCtrl(t)); CreateSymbols = true; ResReadCtx Ctx; if (Ctrl && Res_Read(Ctrl, t, Ctx)) { NewCtrls.Insert(Ctrl); } else { break; } } } // calculate the new control set's dimensions so we // can paste them in at the top of the container auto It = NewCtrls.begin(); ResDialogCtrl *c = *It; if (c) { LRect All = c->View()->GetPos(); while ((c = *(++It))) { All.Union(&c->View()->GetPos()); } // now paste in the controls for (auto c: NewCtrls) { LRect *Preference = Container->GetPasteArea(); LRect p = c->View()->GetPos(); p.Offset(-All.x1, -All.y1); p.Offset(Preference ? Preference->x1 : Container->Client.x1 + GRID_X, Preference ? Preference->y1 : Container->Client.y1 + GRID_Y); c->SetPos(p); Container->AttachCtrl(c, &c->View()->GetPos()); OnSelect(c, false); } // reset parent size to fit LRect cp = Container->View()->GetPos(); Container->SetPos(cp, true); } // Deduplicate all these new strings for (auto s: NewStrs) { s->UnDuplicate(); } } // Repaint LView::Invalidate(); } } DeleteArray(Mem); } void ResDialog::SnapPoint(LPoint *p, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (p && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; LView *Parent = dynamic_cast(Ctrl); if (From) { for (LViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } } p->x += Ox; p->y += Oy; p->x = ((p->x + (GRID_X / 2)) / GRID_X * GRID_X); p->y = ((p->y + (GRID_X / 2)) / GRID_X * GRID_X); p->x -= Ox; p->y -= Oy; } } void ResDialog::SnapRect(LRect *r, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (r && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; LView *Parent = dynamic_cast(Ctrl); for (LViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } r->Normal(); r->Offset(Ox, Oy); r->x1 = ((r->x1 + (GRID_X / 2)) / GRID_X * GRID_X); r->y1 = ((r->y1 + (GRID_X / 2)) / GRID_X * GRID_X); r->x2 = ((r->x2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->y2 = ((r->y2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->Offset(-Ox, -Oy); } } bool ResDialog::IsDraging() { return DragGoober >= 0; } int ResDialog::CurrentTool() { return (Ui) ? Ui->CurrentTool() : 0; } void ResDialog::MoveSelection(int Dx, int Dy) { auto It = Selection.begin(); ResDialogCtrl *w = *It; if (!w) return; ResDialogCtrl *Parent = w->ParentCtrl(); if (!Parent) return; // find dimensions of group LRect All = w->View()->GetPos(); for (; w; w = *(++It)) All.Union(&w->View()->GetPos()); // limit the move to the top-left corner of the parent's client LRect ParentClient = Parent->Client; if (dynamic_cast(Parent)) ParentClient.ZOff(-ParentClient.x1, -ParentClient.y1); if (All.x1 + Dx < ParentClient.x1) Dx = ParentClient.x1 - All.x1; if (All.y1 + Dy < ParentClient.y1) Dy = ParentClient.y1 - All.y1; // move the ctrls LRegion Update; for (auto w: Selection) { LRect Old = w->View()->GetPos(); LRect New = Old; New.Offset(Dx, Dy); // optionally limit the move to the containers bounds LRect *b = w->ParentCtrl()->GetChildArea(w); if (b) { if (New.x1 < b->x1) New.Offset(b->x1 - New.x1, 0); else if (New.x2 > b->x2) New.Offset(b->x2 - New.x2, 0); if (New.y1 < b->y1) New.Offset(0, b->y1 - New.y1); else if (New.y2 > b->y2) New.Offset(0, b->y2 - New.y2); } LRect Up = w->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); w->SetPos(New, false); Up = w->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } void ResDialog::SelectCtrl(ResDialogCtrl *c) { Selection.Empty(); if (c) { bool IsMine = false; for (LViewI *p = c->View(); p; p = p->GetParent()) { if ((LViewI*)this == p) { IsMine = true; break; } } if (IsMine) { Selection.Insert(c); // int TabIdx = -1; ResDialogCtrl *Prev = 0; for (LViewI *p = c->View()->GetParent(); p; p = p->GetParent()) { ResDialogCtrl *c = dynamic_cast(p); if (c) { c->ShowMe(Prev); Prev = c; } else break; } } else LgiTrace("%s:%i - Ctrl doesn't belong to me.\n", __FILE__, __LINE__); } else LgiTrace("Selecting '0'\n"); Invalidate(); if (AppWindow) AppWindow->OnObjSelect(c); } void ResDialog::SelectNone() { Selection.Empty(); Invalidate(); if (AppWindow) { AppWindow->OnObjSelect(0); } } void ResDialog::SelectRect(ResDialogCtrl *Parent, LRect *r, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Parent && r) { for (LViewI *c: Parent->View()->IterateViews()) { if (c->GetPos().Overlap(r)) { ResDialogCtrl *Ctrl = dynamic_cast(c); if (Ctrl) { if (Selection.IndexOf(Ctrl) >= 0) { Selection.Delete(Ctrl); } else { Selection.Insert(Ctrl); } } } } } Invalidate(); if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection[0] : 0); } } bool ResDialog::IsSelected(ResDialogCtrl *Ctrl) { return Selection.IndexOf(Ctrl) >= 0; } void ResDialog::OnSelect(ResDialogCtrl *Wnd, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Wnd) { ResDialogCtrl *Ctrl = dynamic_cast(Wnd); if (Ctrl) { if (!Selection.HasItem(Ctrl)) { Selection.Insert(Ctrl); } if (Ui) { Ui->SelectTool(UI_CURSOR); } Invalidate(); } if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection[0] : 0); } } } void ResDialog::OnDeselect(ResDialogCtrl *Wnd) { if (Selection.HasItem(Wnd)) { Selection.Delete(Wnd); AppWindow->OnObjSelect(0); } } ResDialogCtrl *ResDialog::CreateCtrl(LXmlTag *t) { if (t) { for (LgiObjectName *o = NameMap; o->Type; o++) { if (t->IsTag(o->ResourceName)) { return CreateCtrl(o->Type, t); } } } return 0; } ResDialogCtrl *ResDialog::CreateCtrl(int Tool, LXmlTag *load) { ResDialogCtrl *Ctrl = 0; switch (Tool) { case UI_DIALOG: { Ctrl = new CtrlDlg(this, load); break; } case UI_TABLE: { Ctrl = new CtrlTable(this, load); break; } case UI_TEXT: { Ctrl = new CtrlText(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Some text"); } break; } case UI_EDITBOX: { Ctrl = new CtrlEditbox(this, load); break; } case UI_CHECKBOX: { Ctrl = new CtrlCheckbox(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Checkbox"); } break; } case UI_BUTTON: { Ctrl = new CtrlButton(this, load); if (Ctrl && Ctrl->GetStr() && !load) { static int i = 1; char Text[256]; sprintf(Text, "Button %i", i++); Ctrl->GetStr()->Set(Text); } break; } case UI_GROUP: { Ctrl = new CtrlGroup(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Text"); } break; } case UI_RADIO: { Ctrl = new CtrlRadio(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Radio"); } break; } case UI_TAB: { Ctrl = new CtrlTab(this, load); break; } case UI_TABS: { Ctrl = new CtrlTabs(this, load); break; } case UI_LIST: { Ctrl = new CtrlList(this, load); break; } case UI_COLUMN: { Ctrl = new ListCol(this, load); break; } case UI_COMBO: { Ctrl = new CtrlComboBox(this, load); break; } case UI_TREE: { Ctrl = new CtrlTree(this, load); break; } case UI_BITMAP: { Ctrl = new CtrlBitmap(this, load); break; } case UI_SCROLL_BAR: { Ctrl = new CtrlScrollBar(this, load); break; } case UI_PROGRESS: { Ctrl = new CtrlProgress(this, load); break; } case UI_CUSTOM: { Ctrl = new CtrlCustom(this, load); break; } case UI_CONTROL_TREE: { Ctrl = new CtrlControlTree(this, load); break; } default: { LAssert(!"No control factory handler."); break; } } if (Ctrl && Ctrl->GetStr()) { Ctrl->GetStr()->UpdateWnd = Ctrl->View(); } return Ctrl; } LView *ResDialog::CreateUI() { return Ui = new ResDialogUi(this); } void ResDialog::DrawSelection(LSurface *pDC) { if (Selection.Length() == 0) return; // Draw selection for (auto Ctrl: Selection) { LRect r = Ctrl->AbsPos(); LColour s(255, 0, 0); LColour c = GetParent()->Focus() ? s : s.Mix(LColour(L_MED), 0.4); DrawGoobers(pDC, r, Ctrl->Goobers, c, Ctrl->OverGoober); } } #ifdef WINDOWS #define USE_MEM_DC 1 #else #define USE_MEM_DC 0 #endif void ResDialog::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if USE_MEM_DC LDoubleBuffer DblBuf(pDC); #endif LView::_Paint(pDC, Offset, Update); if (GetParent()) { #ifndef WIN32 LRect p = GetPos(); if (Offset) p.Offset(Offset); pDC->SetClient(&p); #endif DrawSelection(pDC); #ifndef WIN32 pDC->SetClient(NULL); #endif if (DebugOverlay) { pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, DebugOverlay); } } } void ResDialog::OnPaint(LSurface *pDC) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (!Ctrl) return; } void ResDialog::OnLanguageChange() { if (Ui && Ui->StatusInfo) { LLanguage *l = Symbols->GetLanguage(App()->GetCurLang()->Id); if (l) { char Str[256]; sprintf(Str, "Current Language: %s (Id: %s)", l->Name, l->Id); Ui->StatusInfo->Name(Str); } } } bool ResDialog::OnKey(LKey &k) { if (k.Ctrl()) { switch (k.c16) { case LK_UP: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx > 0) { LLanguage *l = Symbols->GetLanguage(Idx - 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } case LK_DOWN: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx < Symbols->GetLanguages() - 1) { LLanguage *l = Symbols->GetLanguage(Idx + 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } } } return false; } void ResDialog::OnMouseClick(LMouse &m) { if (m.Down()) { if (GetParent()) GetParent()->Focus(true); if (m.Left()) { DragGoober = -1; for (auto c: Selection) { for (int i=0; i<8; i++) { if (c->Goobers[i].Overlap(m.x, m.y)) { DragGoober = i; DragCtrl = c; // LgiTrace("IN goober[%i]=%s %i,%i\n", i, c->Goobers[i].GetStr(), m.x, m.y); break; } else { // LgiTrace("goober[%i]=%s %i,%i\n", i, c->Goobers[i].GetStr(), m.x, m.y); } } } if (DragGoober >= 0) { DragRgn = DragCtrl->View()->GetPos(); DragX = DragY = 0; int Cap = GooberCaps[DragGoober]; // Lock the dialog to the top left corner if (dynamic_cast(DragCtrl)) { Cap &= ~(RESIZE_X1|RESIZE_Y1); } if (!Cap) { // Can't move the object anyway so abort the drag DragGoober = -1; } else { // Allow movement along the appropriate edge if (Cap & RESIZE_X1) { DragX = &DragRgn.x1; } if (Cap & RESIZE_Y1) { DragY = &DragRgn.y1; } if (Cap & RESIZE_X2) { DragX = &DragRgn.x2; } if (Cap & RESIZE_Y2) { DragY = &DragRgn.y2; } // Remember the offset to the mouse from the egdes if (DragX) DragOx = m.x - *DragX; if (DragY) DragOy = m.y - *DragY; Capture(true); } } } } else { if (DragGoober >= 0) { Capture(false); DragGoober = -1; DragX = 0; DragY = 0; } } } void ResDialog::OnMouseMove(LMouse &m) { // This code hilights the goober when the mouse is over it. for (auto c: Selection) { int Old = c->OverGoober; c->OverGoober = -1; for (int i=0; i<8; i++) { if (c->Goobers[i].Overlap(m.x, m.y)) { c->OverGoober = i; break; } } if (c->OverGoober != Old) Invalidate(); } if (DragGoober >= 0) { if (DragX) *DragX = m.x - DragOx; if (DragY) *DragY = m.y - DragOy; // DragRgn in real coords LRect Old = DragCtrl->View()->GetPos(); LRect New = DragRgn; auto It = IterateViews(); if (DragCtrl->View() != It[0]) SnapRect(&New, DragCtrl->ParentCtrl()); // New now in snapped coords // If that means the dragging control changes then if (New != Old) { LRegion Update; // change everyone else by the same amount for (auto c: Selection) { LRect OldPos = c->View()->GetPos(); LRect NewPos = OldPos; NewPos.x1 += New.x1 - Old.x1; NewPos.y1 += New.y1 - Old.y1; NewPos.x2 += New.x2 - Old.x2; NewPos.y2 += New.y2 - Old.y2; LRect Up = c->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); c->SetPos(NewPos); Up = c->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } } } bool ResDialog::Test(ErrorCollection *e) { return true; } bool ResDialog::Read(LXmlTag *t, SerialiseContext &Ctx) { bool Status = false; // turn symbol creation off so that ctrls find their // symbol via Id matching instead of creating their own CreateSymbols = false; ResDialogCtrl *Dlg = dynamic_cast(Children[0]); if (Dlg) { // Load the resource ResReadCtx Ctx; Status = Res_Read(Dlg, t, Ctx); Item->Update(); } CreateSymbols = true; return Status; } void ResDialog::CleanSymbols() { // removed unreferenced dialog strings if (Symbols) { Symbols->RemoveUnReferenced(); } // re-ID duplicate entries ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl) { // list all the entries List l; Ctrl->ListChildren(l); l.Insert(Ctrl); // insert the dialog too // remove duplicate string entries for (auto c: l) { LAssert(c->GetStr()); c->GetStr()->UnDuplicate(); } } // sort list (cause I need to read the file myself) if (Symbols) { Symbols->Sort(); } } bool ResDialog::Write(LXmlTag *t, SerialiseContext &Ctx) { bool Status = false; ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl) { // duplicates symbols should have been removed before the // strings were written out // so we don't have to do it here. // write the resources out if (Ctrl) { Status = Res_Write(Ctrl, t); } } else LgiTrace("%s:%i - Not a ResDialogCtrl.\n", _FL); return Status; } void ResDialog::OnRightClick(LSubMenu *RClick) { if (RClick) { if (Enabled()) { RClick->AppendSeparator(); if (Type() > 0) { RClick->AppendItem("Dump to C++", IDM_DUMP, true); auto Export = RClick->AppendSub("Export to..."); if (Export) { Export->AppendItem("Lgi File", IDM_EXPORT, true); Export->AppendItem("Win32 Resource Script", IDM_EXPORT_WIN32, false); } } } } } const char *TypeOfRes(ResDialogCtrl *Ctrl) { // the default return "LWindow"; } const char *TextOfCtrl(ResDialogCtrl *Ctrl) { static char Buf[256]; switch (Ctrl->GetType()) { // Has text case UI_TEXT: case UI_EDITBOX: case UI_CHECKBOX: case UI_BUTTON: case UI_GROUP: case UI_RADIO: case UI_COMBO: { char *s = Ctrl->GetStr()->Get(); sprintf(Buf, ", \"%s\"", s?s:""); return Buf; } // Not processed case UI_COLUMN: case UI_TAB: { LAssert(0); break; } // No text... case UI_BITMAP: case UI_PROGRESS: case UI_TABS: case UI_LIST: case UI_TREE: case UI_SCROLL_BAR: { break; } } return ""; } void OutputCtrl(LStringPipe &Def, LStringPipe &Var, LStringPipe &Inst, ResDialogCtrl *Ctrl, ResDialogCtrl *Parent, int &Index) { char Str[256]; const char *Type = "LView"; for (LgiObjectName *on=NameMap; on->Type; on++) { if (Ctrl->GetType() == on->Type) { Type = on->ObjectName; break; } } if (stricmp(Type, "LDialog")) { if (ValidStr(Ctrl->GetStr()->GetDefine()) && stricmp(Ctrl->GetStr()->GetDefine(), "IDOK") && stricmp(Ctrl->GetStr()->GetDefine(), "IDCANCEL") && stricmp(Ctrl->GetStr()->GetDefine(), "IDC_STATIC")) { char Tab[8]; auto Tabs = (32 - strlen(Ctrl->GetStr()->GetDefine()) - 1) / 4; memset(Tab, '\t', Tabs); Tab[Tabs] = 0; sprintf(Str, "#define %s%s%i\n", Ctrl->GetStr()->GetDefine(), Tab, 1000 + Index); Def.Push(Str); } sprintf(Str, "\t%s *Ctrl%i;\n", Type, Index); Var.Push(Str); sprintf(Str, "\tChildren.Insert(Ctrl%i = new %s(%s, %i, %i, %i, %i%s));\n", Index, Type, Ctrl->GetStr()->GetDefine(), Ctrl->View()->GetPos().x1 - 3, Ctrl->View()->GetPos().y1 - 17, Ctrl->View()->X(), Ctrl->View()->Y(), TextOfCtrl(Ctrl)); Inst.Push(Str); CtrlList *List = dynamic_cast(Ctrl); if (List) { // output columns for (auto c: List->Cols) { sprintf(Str, "\tCtrl%i->AddColumn(\"%s\", %i);\n", Index, c->GetStr()->Get(), c->X()); Inst.Push(Str); } } Index++; } for (auto c: Ctrl->View()->IterateViews()) OutputCtrl(Def, Var, Inst, dynamic_cast(c), Ctrl, Index); } void ResDialog::OnCommand(int Cmd) { switch (Cmd) { case IDM_DUMP: { LStringPipe Def, Var, Inst; LStringPipe Buf; char Str[256]; ResDialogCtrl *Dlg = dynamic_cast(Children[0]); if (Dlg) { // List controls int i=0; OutputCtrl(Def, Var, Inst, Dlg, 0, i); // #define's char *Defs = Def.NewStr(); if (Defs) { Buf.Push(Defs); Def.Empty(); } // Class def Buf.Push( "\nclass Dlg : public LDialog\n" "{\n"); // Variables char *Vars = Var.NewStr(); if (Vars) { Buf.Push(Vars); Var.Empty(); } // Member functions Buf.Push( "\n" "public:\n" "\tDlg(LView *Parent);\n" "\t~Dlg();\n" "\n" "\tint OnNotify(LViewI *Ctrl, int Flags);\n" "};\n" "\n"); // Class impl Buf.Push( "Dlg::Dlg(LView *Parent)\n" "{\n" "\tSetParent(Parent);\n"); sprintf(Str, "\tName(\"%s\");\n" "\tGRegion r(0, 0, %i, %i);\n" "\tSetPos(r);\n", Dlg->GetStr()->Get(), Dlg->View()->X(), Dlg->View()->Y()); Buf.Push(Str); Buf.Push("\tMoveToCenter();\n\n"); // Ctrl instancing char *NewCtrls = Inst.NewStr(); if (NewCtrls) { Buf.Push(NewCtrls); Inst.Empty(); } Buf.Push( "}\n" "\n"); // Destructor Buf.Push( "Dlg::~Dlg()\n" "{\n" "}\n" "\n"); // ::OnNotify Buf.Push( "int Dlg::OnNotify(LViewI *Ctrl, int Flags)\n" "{\n" "\tswitch (Ctrl->GetId())\n" "\t{\n" "\t\tcase IDOK:\n" "\t\t{\n" "\t\t\t// Do something here\n" "\t\t\t// fall thru\n" "\t\t}\n" "\t\tcase IDCANCEL:\n" "\t\t{\n" "\t\t\tEndModal(Ctrl->GetId());\n" "\t\t\tbreak;\n" "\t\t}\n" "\t}\n" "\n" "\treturn 0;\n" "}\n"); // Output to clipboard char *Text = Buf.NewStr(); if (Text) { LClipBoard Clip(Ui); Clip.Text(Text); DeleteArray(Text); } } break; } case IDM_EXPORT: { - LFileSelect Select; - Select.Parent(AppWindow); - Select.Type("Text", "*.txt"); - if (Select.Save()) + auto Select = new LFileSelect(AppWindow); + Select->Type("Text", "*.txt"); + Select->Save([&](auto dlg, auto status) { - LFile F; - if (F.Open(Select.Name(), O_WRITE)) + if (status) { - F.SetSize(0); - // Serialize(F, true); + LFile F; + if (F.Open(Select->Name(), O_WRITE)) + { + F.SetSize(0); + // Serialize(F, true); + } + else + { + LgiMsg(AppWindow, "Couldn't open file for writing."); + } } - else - { - LgiMsg(AppWindow, "Couldn't open file for writing."); - } - } + delete dlg; + }); break; } case IDM_EXPORT_WIN32: { break; } } } int ResDialog::OnCommand(int Cmd, int Event, OsView hWnd) { switch (Cmd) { /* case IDM_SET_LANG: { Symbols->SetCurrent(); OnSelect(Selection.First()); Invalidate(); OnLanguageChange(); break; } */ case IDM_TAB_ORDER: { ResDialogCtrl *Top = 0; if (Selection.Length() == 1 && Selection[0]->IsContainer()) { Top = Selection[0]; } if (!Top) { Top = dynamic_cast(Children[0]); } if (Top) { - TabOrder Dlg(this, Top); + auto Dlg = new TabOrder(this, Top); + Dlg->DoModal([](auto dlg, auto id) + { + delete dlg; + }); } break; } } return 0; } ResString *ResDialog::CreateSymbol() { return (Symbols) ? Symbols->CreateStr() : 0; } //////////////////////////////////////////////////////////////////// ResDialogUi::ResDialogUi(ResDialog *Res) { Dialog = Res; Tools = 0; Status = 0; StatusInfo = 0; Name("ResDialogUi"); if (Res) { Res->OnSelect(Res->Selection[0]); ShortCutView *scv = Res->App()->GetShortCutView(); if (scv) scv->OnDialogChange(Res); } } ResDialogUi::~ResDialogUi() { if (Dialog) { ShortCutView *scv = Dialog->App()->GetShortCutView(); if (scv) scv->OnDialogChange(NULL); Dialog->Ui = 0; } } void ResDialogUi::OnPaint(LSurface *pDC) { LRegion Client(0, 0, X()-1, Y()-1); for (auto w: Children) { LRect r = w->GetPos(); Client.Subtract(&r); } pDC->Colour(L_MED); for (LRect *r = Client.First(); r; r = Client.Next()) { pDC->Rectangle(r); } } void ResDialogUi::PourAll() { LRegion Client(GetClient()); LRegion Update; for (auto v: Children) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible v->Visible(false); } } for (int i=0; iSetBitmap(FileName, 16, 16)) { Tools->Attach(this); Tools->AppendButton("Cursor", 0, TBT_RADIO); for (LgiObjectName *o=NameMap; o->Type; o++) { if (o->ToolbarBtn) { Tools->AppendButton(o->ObjectName, o->Type, TBT_RADIO); } } Tools->AppendSeparator(); // Tools->AppendButton("Change language", IDM_SET_LANG, TBT_PUSH); Tools->AppendButton("Tab Order", IDM_TAB_ORDER, TBT_PUSH, true, 17); } else { DeleteObj(Tools); } } Status = new LStatusBar; if (Status) { Status->Attach(this); StatusInfo = Status->AppendPane("", 2); } ResFrame *Frame = new ResFrame(Dialog); if (Frame) { Frame->Attach(this); } PourAll(); } int ResDialogUi::CurrentTool() { if (Tools) { auto It = Tools->IterateViews(); for (size_t i=0; i(It[i]); if (But && But->Value()) return But->GetId(); } } return -1; } void ResDialogUi::SelectTool(int i) { if (Tools) { auto It = Tools->IterateViews(); LViewI *w = It[i]; if (w) { LToolButton *But = dynamic_cast(w); if (But) But->Value(true); } } } LMessage::Result ResDialogUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { Dialog->OnCommand(Msg->A()&0xffff, (int)(Msg->A()>>16), (OsView) Msg->B()); break; } case M_DESCRIBE: { char *Text = (char*) Msg->B(); if (Text) { StatusInfo->Name(Text); } break; } } return LView::OnEvent(Msg); } diff --git a/ResourceEditor/Code/LgiRes_String.cpp b/ResourceEditor/Code/LgiRes_String.cpp --- a/ResourceEditor/Code/LgiRes_String.cpp +++ b/ResourceEditor/Code/LgiRes_String.cpp @@ -1,1683 +1,1691 @@ /* ** FILE: LgiRes_String.cpp ** AUTHOR: Matthew Allen ** DATE: 10/9/1999 ** DESCRIPTION: String Resource Editor ** ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ //////////////////////////////////////////////////////////////////// #include #include "LgiResEdit.h" #include "LgiRes_String.h" #include "lgi/common/Token.h" #include "lgi/common/Variant.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Menu.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" //////////////////////////////////////////////////////////////////////////// LangDlg::LangDlg(LView *parent, List &l, int Init) { Lang = 0; SetParent((parent) ? parent : MainWnd); LRect r(0, 0, 260, 90); SetPos(r); MoveToCenter(); Name("Language"); Children.Insert(new LTextLabel(-1, 10, 10, -1, -1, "Select language:")); Children.Insert(Sel = new LCombo(-1, 20, 30, 150, 20, "")); if (Sel) { if (l.Length() > 40) { Sel->Sub(GV_STRING); } for (auto li: l) { Langs.Insert(li); Sel->Insert(li->Name); } if (Init >= 0) { Sel->Value(Init); } } Children.Insert(new LButton(IDOK, 180, 5, 60, 20, "Ok")); Children.Insert(new LButton(IDCANCEL, 180, 30, 60, 20, "Cancel")); } int LangDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: { Lang = Langs.ItemAt(Sel->Value()); EndModal(1); break; } case IDCANCEL: { EndModal(0); break; } } return 0; } //////////////////////////////////////////////////////////////////// StrLang::StrLang() { Lang = "en"; Str = 0; } StrLang::~StrLang() { DeleteArray(Str); } LLanguageId StrLang::GetLang() { return Lang; } void StrLang::SetLang(LLanguageId i) { LAssert(i); Lang = i; } char *&StrLang::GetStr() { return Str; } void StrLang::SetStr(const char *s) { char *n = NewStr(s); DeleteArray(Str); Str = n; } bool StrLang::IsEnglish() { return stricmp(Lang, "en") == 0; } bool StrLang::operator ==(LLanguageId LangId) { return stricmp(Lang, LangId) == 0; } bool StrLang::operator !=(LLanguageId LangId) { return !(*this == LangId); } //////////////////////////////////////////////////////////////////// ResString::ResString(ResStringGroup *grp, int init_ref) { Ref = init_ref; Group = grp; Id = 0; Tag = 0; UpdateWnd = 0; IdStr[0] = 0; RefStr[0] = 0; if (Group) { LAssert(!Group->Strs.HasItem(this)); Group->Strs.Insert(this); Group->LList::Insert(this); } else { LAssert(0); } // LStackTrace("%p::ResString\n", this); } ResString::~ResString() { while (Refs.Length() != 0) { auto r = Refs[0]; r->SetStr(NULL); } if (Group) { Group->App()->OnObjDelete(this); if (!Group->Strs.Delete(this)) LAssert(0); Group->LList::Remove(this); } for (auto s: Items) { DeleteObj(s); } DeleteArray(Tag); } int ResString::SetRef(int r) { LAssert(r != 0 && r != -1); if (r != Ref) { Ref = r; Update(); if (UpdateWnd) { UpdateWnd->Invalidate(); } } return Ref; } int ResString::SetId(int id) { LAssert(id != 0); if (Id != id) { Id = id; Update(); if (UpdateWnd) { UpdateWnd->Invalidate(); } } return Id; } void ResString::SetDefine(const char *s) { Define.Reset(NewStr(s)); } ResString &ResString::operator =(ResString &s) { memcpy(RefStr, s.RefStr, sizeof(RefStr)); memcpy(IdStr, s.IdStr, sizeof(IdStr)); SetRef(s.GetRef()); SetId(s.GetId()); SetDefine(s.GetDefine()); DeleteObj(Tag); Tag = NewStr(s.Tag); for (auto l: s.Items) { Set(l->GetStr(), l->GetLang()); } return *this; } char *ResString::Get(LLanguageId Lang) { if (!Lang) { LLanguage *L = Group->App()->GetCurLang(); if (L) { Lang = L->Id; } else { LAssert(0); return 0; } } StrLang *s = GetLang(Lang); if (s) { return s->GetStr(); } return 0; } void ResString::Set(const char *p, LLanguageId Lang) { if (!Lang) { LLanguage *L = Group->App()->GetCurLang(); if (L) { Lang = L->Id; } else { LAssert(0); return; } } StrLang *s = GetLang(Lang); if (!s) { Items.Insert(s = new StrLang); if (s) { s->SetLang(Lang); } } if (s) { s->SetStr(p); Update(); if (UpdateWnd) { UpdateWnd->Invalidate(); } } else { LAssert(0); } } void ResString::UnDuplicate() { // reset Ref if it's a duplicate int OldStrRef = Ref; Ref = 0x80000000; ResString *Dupe = Group->App()->GetStrFromRef(OldStrRef); // now we return the Id to the string SetRef(OldStrRef); if (Dupe || Ref < 0) { // there is a duplicate string Id // so assign a new Id SetRef(Group->App()->GetUniqueStrRef()); } } char **ResString::GetIndex(int i) { StrLang *s = Items.ItemAt(i); return (s) ? &s->GetStr() : 0; } bool ResString::Test(ErrorCollection *e) { bool Status = true; if (ValidStr(Define) && Id == 0) { ErrorInfo *Err = &e->StrErr.New(); Err->Str = this; Err->Msg.Reset(NewStr("No ctrl id.")); Status = false; } if (Ref == 0) { ErrorInfo *Err = &e->StrErr.New(); Err->Str = this; Err->Msg.Reset(NewStr("No resource ref.")); Status = false; } #if 0 if (!ValidStr(Define)) { ErrorInfo *Err = &e->StrErr.New(); Err->Str = this; Err->Msg.Reset(NewStr("No define name.")); Status = false; } #endif for (auto s: Items) { if (!LIsUtf8(s->GetStr())) { ErrorInfo *Err = &e->StrErr.New(); Err->Str = this; char m[256]; sprintf(m, "Utf-8 error in translation '%s'", s->GetLang()); Err->Msg.Reset(NewStr(m)); Status = false; } } return Status; } bool ResString::Read(LXmlTag *t, SerialiseContext &Ctx) { if (!t || !t->IsTag("string")) { Ctx.Log.Print("%s:%i - Not a string tag (Tag='%s').\n", _FL, t->GetTag()); return false; } char *n = 0; int Ref = t->GetAsInt("Ref"); if (!Ref) { Ctx.Log.Print("%s:%i - String has no Ref number.\n", _FL); return false; } SetRef(Ref); if ((n = t->GetAttr("Define")) && ValidStr(n)) { SetDefine(n); char *c; if ((c = t->GetAttr("Cid")) || (c = t->GetAttr("Id"))) { int Cid = atoi(c); if (!Cid) { Ctx.Log.Print("%s:%i - String missing id (ref=%i)\n", _FL, Ref); Ctx.FixId.Add(this); } else { SetId(Cid); } } } if ((n = t->GetAttr("Tag"))) { DeleteArray(Tag); Tag = NewStr(n); } for (int i=0; iAttr.Length(); i++) { LXmlAttr *v = &t->Attr[i]; if (v->GetName()) { LLanguage *Lang = LFindLang(v->GetName()); if (Lang) { if (Ctx.Format == Lr8File) { LAssert(LIsUtf8(v->GetName())); Set(v->GetValue(), Lang->Id); } else if (Ctx.Format == CodepageFile) { char *Utf8 = (char*)LNewConvertCp("utf-8", v->GetValue(), Lang->Charset); Set(Utf8 ? Utf8 : v->GetValue(), Lang->Id); if (Utf8) { DeleteArray(Utf8); } } else if (Ctx.Format == XmlFile) { char *x = DecodeXml(v->GetValue()); if (x) { Set(x, Lang->Id); DeleteArray(x); } } else LAssert(0); } } } return true; } bool ResString::Write(LXmlTag *t, SerialiseContext &Ctx) { t->SetTag("String"); t->SetAttr("Ref", Ref); if (ValidStr(Define)) { t->SetAttr("Cid", Id); t->SetAttr("Define", Define); } if (Tag) t->SetAttr("Tag", Tag); char *English = 0; for (auto s: Items) { if (s->IsEnglish()) English = s->GetStr(); } for (auto s: Items) { if (ValidStr(s->GetStr())) { char *Mem = 0; char *String = 0; if (Ctx.Format == Lr8File) { // Don't save the string if it's the same as the English if (English && !s->IsEnglish()) { if (strcmp(English, s->GetStr()) == 0) { continue; } } // Save this string String = s->GetStr(); } else if (Ctx.Format == CodepageFile) { LLanguage *Li = LFindLang(s->GetLang()); if (Li) { Mem = String = (char*)LNewConvertCp(Li->Charset, s->GetStr(), "utf-8"); } if (!String) String = s->GetStr(); } else if (Ctx.Format == XmlFile) { // Don't save the string if it's the same as the English if (English && !s->IsEnglish()) { if (strcmp(English, s->GetStr()) == 0) { continue; } } // Save this string String = Mem = EncodeXml(s->GetStr()); } else LAssert(0); if (ValidStr(String)) { t->SetAttr(s->GetLang(), String); } DeleteArray(Mem); } } return true; } StrLang *ResString::GetLang(LLanguageId i) { for (auto s: Items) { if (stricmp(s->GetLang(), i) == 0) { return s; } } return 0; } bool ResString::GetFields(FieldTree &Fields) { // string view [literal] Fields.Insert(this, DATA_INT, 199, VAL_Ref, "Ref"); Fields.Insert(this, DATA_INT, 200, VAL_Id, "Id"); // else dialog view [embeded] Fields.Insert(this, DATA_STR, 201, VAL_Define, "#define"); Fields.Insert(this, DATA_STR, 198, VAL_Tag, "Tag"); if (Group) { for (int i=0; iGetLanguages(); i++) { if (Group->App()->ShowLang(Group->Lang[i]->Id)) { Fields.Insert(this, DATA_STR, 202+i, Group->Lang[i]->Name, Group->Lang[i]->Name); } } } return true; } bool ResString::Serialize(FieldTree &Fields) { Fields.Serialize(this, VAL_Ref, Ref); Fields.Serialize(this, VAL_Id, Id); Fields.Serialize(this, VAL_Define, Define); Fields.Serialize(this, VAL_Tag, Tag); if (Group) { for (int i=0; iGetLanguages(); i++) { LLanguageId Id = Group->Lang[i]->Id; StrLang *s = GetLang(Id); if (Fields.GetMode() == FieldTree::UiToObj && !s) { s = new StrLang; if (s) { Items.Insert(s); s->SetLang(Id); } } if (s) { Fields.Serialize(this, Group->Lang[i]->Name, s->GetStr()); } } Update(); } return true; } int ResString::NewId() { List sl; if (Group && Group->AppWindow) { Group->AppWindow->FindStrings(sl, Define); int NewId = Group->AppWindow->GetUniqueCtrlId(); for (auto s: sl) { s->SetId(NewId); s->Update(); } } else LAssert(!"Invalid ptrs."); return Id; } int ResString::GetCols() { return 3 + Group->GetLanguages(); } const char *ResString::GetText(int i) { switch (i) { case 0: { return (Define)?Define:(char*)""; } case 1: { sprintf(RefStr, "%i", Ref); return RefStr; } case 2: { sprintf(IdStr, "%i", Id); return IdStr; } default: { int LangIdx = i - 3; if (LangIdx < Group->Visible.Length()) { LLanguage *Info = Group->Visible[LangIdx]; if (Info) { for (auto s: Items) { if (*s == Info->Id) { return (s->GetStr()) ? s->GetStr() : (char*)""; } } } } break; } } return NULL; } void ResString::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu *RClick = new LSubMenu; if (RClick) { bool PasteTranslations = false; char *Clip; { LClipBoard c(Parent); Clip = c.Text(); if (Clip) PasteTranslations = strstr(Clip, TranslationStrMagic); } RClick->AppendItem("Copy Text", IDM_COPY_TEXT, true); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, PasteTranslations); RClick->AppendSeparator(); RClick->AppendItem("New Id", IDM_NEW_ID, true); RClick->AppendItem("Make Ref=Id", IDM_REF_EQ_ID, true); RClick->AppendItem("Make Id=Ref", IDM_ID_EQ_REF, true); if (Parent->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick->Float(Parent, m.x, m.y)) { case IDM_COPY_TEXT: { CopyText(); break; } case IDM_PASTE_TEXT: { PasteText(); break; } case IDM_NEW_ID: { List Sel; if (!GetList()->GetSelection(Sel)) break; auto App = Group->AppWindow; for (auto s : Sel) { List sl; App->FindStrings(sl, s->Define); int NewId = App->GetUniqueCtrlId(); for (auto i : sl) { i->SetId(NewId); } } break; } case IDM_REF_EQ_ID: { List a; if (GetList()->GetSelection(a)) { bool Dirty = false; for (auto s: a) { if (s->GetId() != s->GetRef()) { ResString *Existing = Group->App()->GetStrFromRef(s->GetId()); if (!Existing) { s->SetRef(s->GetId()); Dirty = true; } } } if (Dirty) { Group->App()->SetDirty(); } } break; } case IDM_ID_EQ_REF: { List a; if (GetList()->GetSelection(a)) { bool Dirty = false; for (auto s: a) { if (s->GetId() != s->GetRef()) { List Existing; int CtrlId = s->GetRef(); Group->App()->FindStrings(Existing, 0, &CtrlId); if (Existing.Length() == 0) { s->SetId(s->GetRef()); Dirty = true; } } } if (Dirty) { Group->App()->SetDirty(); } } break; } } } DeleteObj(RClick); } } } void ResString::CopyText() { if (Items.Length() > 0) { LStringPipe p; p.Push(TranslationStrMagic); p.Push(EOL_SEQUENCE); for (auto s: Items) { char Str[256]; sprintf(Str, "%s,%s", s->GetLang(), s->GetStr()); p.Push(Str); p.Push(EOL_SEQUENCE); } char *All = p.NewStr(); if (All) { LClipBoard Clip(Parent); Clip.Text(All); char16 *w = Utf8ToWide(All); Clip.TextW(w, false); DeleteArray(w); DeleteArray(All); } } else { LgiMsg(Parent, "No translations to copy.", "ResDialogCtrl::CopyText", MB_OK); } } void ResString::PasteText() { LClipBoard c(Parent); char *Clip = 0; char16 *w = c.TextW(); if (w) { Clip = WideToUtf8(w); } else { Clip = c.Text(); } if (Clip) { LToken Lines(Clip, "\r\n"); if (Lines.Length() > 0 && strcmp(Lines[0], TranslationStrMagic) == 0) { Items.DeleteObjects(); for (int i=1; iId); } } } if (Group && Group->App()) { Group->App()->OnObjSelect(this); } } } if (w) { DeleteArray(Clip); } } //////////////////////////////////////////////////////////////////// ResStringGroup::ResStringGroup(AppWnd *w, int type) : Resource(w, type), LList(90, 0, 0, 200, 200) { Ui = 0; Wnd()->Name(""); Sunken(false); SortAscend = true; SortCol = 0; AppendLanguage("en"); App()->OnLanguagesChange("en", true); } ResStringGroup::~ResStringGroup() { while (Strs.Length()) { delete Strs[0]; } DeleteObj(Ui); } LLanguage *ResStringGroup::GetLanguage(LLanguageId Id) { for (int i=0; iId) return Lang[i]; } return 0; } int ResStringGroup::GetLangIdx(LLanguageId Id) { for (int i=0; iId) == 0) return i; } return -1; } int ResString::Compare(LListItem *li, ssize_t Column) { ResString *r = dynamic_cast(li); static const char *Empty = ""; bool Mod = (Column < 0) ? -1 : 1; Column = abs(Column) - 1; if (r) { switch (Column) { case 0: { return stricmp(Define?Define:Empty, r->Define?r->Define:Empty) * Mod; break; } case 1: { return (Ref - r->Ref) * Mod; break; } case 2: { return (Id - r->Id) * Mod; break; } default: { auto Col = Column - 3; if (Group && Col >= 0 && Col < Group->GetLanguages()) { LLanguageId Lang = Group->Visible[Column - 3]->Id; char *a = Get(Lang); char *b = r->Get(Lang); const char *Empty = ""; return stricmp(a?a:Empty, b?b:Empty) * Mod; } break; } } } return -1; } int ResStringCompareFunc(LListItem *a, LListItem *b, ssize_t Data) { ResString *A = dynamic_cast(a); if (A) { return A->Compare(dynamic_cast(b), Data); } return -1; } void ResStringGroup::OnColumnClick(int Col, LMouse &m) { if (SortCol == Col) { SortAscend = !SortAscend; } else { SortCol = Col; SortAscend = true; } LList::Sort(ResStringCompareFunc, (SortCol + 1) * ((SortAscend) ? 1 : -1)); LListItem *Sel = GetSelected(); if (Sel) { Sel->ScrollTo(); } } void ResStringGroup::OnItemClick(LListItem *Item, LMouse &m) { LList::OnItemClick(Item, m); } void ResStringGroup::OnItemSelect(LArray &Items) { if (IsAttached()) { ResString *s = dynamic_cast(Items[0]); if (s && AppWindow) { AppWindow->OnObjSelect(s); } } } void ResStringGroup::OnShowLanguages() { UpdateColumns(); UpdateAllItems(); } void ResStringGroup::UpdateColumns() { EmptyColumns(); AddColumn("#define", 150); AddColumn("Ref", 70); AddColumn("Id", 70); Visible.Length(0); for (int i=0; iShowLang(Lang[i]->Id)) { Visible.Add(Lang[i]); AddColumn(Lang[i]->Name, 100); } } } void ResStringGroup::DeleteLanguage(LLanguageId Id) { // Check for presence int Index = -1; for (int i=0; iId == Id) { Index = i; break; } } if (Index >= 0) { // Remove lang Lang.DeleteAt(Index); UpdateColumns(); } } void ResStringGroup::AppendLanguage(LLanguageId Id) { // Check for presence int Index = -1; for (int i=0; iId == Id) { Index = i; break; } } if (Index < 0) { LLanguage *n = LFindLang(Id); if (n) { Lang[Lang.Length()] = n; UpdateColumns(); } } } int RefCmp(ResString *a, ResString *b, NativeInt d) { return a->GetRef() - b->GetRef(); } void ResStringGroup::Sort() { Strs.Sort(RefCmp); } void ResStringGroup::RemoveUnReferenced() { for (auto It = Strs.begin(); It != Strs.end(); ) { auto s = *It; if (!s->UpdateWnd) { s->Group = NULL; Strs.Delete(It); DeleteStr(s); } else It++; } } int ResStringGroup::OnCommand(int Cmd, int Event, OsView hWnd) { switch (Cmd) { case IDM_NEW: { CreateStr(true); break; } case IDM_DELETE: { List l; if (GetSelection(l)) { for (auto s: l) { DeleteObj(s); } } break; } case IDM_NEW_LANG: { // List all the languages minus the ones already // being edited List l; for (LLanguage *li = LFindLang(NULL); li->Id; li++) { bool Present = false; for (int i=0; iId == li->Id) { Present = true; break; } } if (!Present) { l.Insert(li); } } // Display the list - LangDlg Dlg(this, l); - if (Dlg.DoModal()) + auto Dlg = new LangDlg(this, l); + Dlg->DoModal([&](auto dlg, auto id) { - if (Dlg.Lang) + if (id && Dlg->Lang) { - AppendLanguage(Dlg.Lang->Id); + AppendLanguage(Dlg->Lang->Id); // Update the global language list - AppWindow->ShowLang(Dlg.Lang->Id, true); + AppWindow->ShowLang(Dlg->Lang->Id, true); } - } + delete dlg; + }); break; } case IDM_DELETE_LANG: { // List all the languages being edited List l; for (int i=0; iDoModal([&](auto dlg, auto id) { - if (Dlg.Lang) + if (id && Dlg->Lang) { - DeleteLanguage(Dlg.Lang->Id); + DeleteLanguage(Dlg->Lang->Id); // CurrentLang = limit(CurrentLang, 0, Lang.Length()-1); } - } + delete dlg; + }); break; } } return 0; } ResString *ResStringGroup::CreateStr(bool User) { int NextRef = App()->GetUniqueStrRef(); ResString *s = new ResString(this, NextRef); if (s) { s->SetId(s->SetRef(NextRef)); if (User) { char Str[256]; sprintf(Str, "IDS_%i", s->Ref); s->SetDefine(Str); s->GetList()->Select(NULL); s->ScrollTo(); s->Select(true); } } return s; } void ResStringGroup::DeleteStr(ResString *Str) { if (Str->Items.Length() == 0) DeleteObj(Str); } ResString *ResStringGroup::FindName(char *Name) { if (Name) { for (auto s: Strs) { if (stricmp(s->Define, Name) == 0) { return s; } } } return NULL; } ResString *ResStringGroup::FindRef(int Ref) { for (auto s: Strs) { if (s->GetRef() == Ref) { return s; } } return NULL; } int ResStringGroup::UniqueRef() { int n = 1; for (auto li: Items) { auto i = dynamic_cast(li); n = MAX(n, i->Ref); } return n + 1; } int ResStringGroup::UniqueId(char *Define) { int n = 1; for (auto li: Items) { auto i = dynamic_cast(li); if (i->Id && i->Define && Define && stricmp(Define, i->Define) == 0) { return i->Id; } n = MAX(n, i->Id); } return n + 1; } int ResStringGroup::FindId(int Id, List &Strs) { return 0; } void ResStringGroup::SetLanguages() { List l; bool EnglishFound = false; for (auto s: Strs) { for (auto sl: s->Items) { LLanguage *li = 0; for (auto i: l) if (*sl == i->Id) { li = i; break; } if (!li) { LLanguage *NewLang = LFindLang(sl->GetLang()); if (NewLang) l.Insert(NewLang); if (*sl == (LLanguageId)"en") EnglishFound = true; } } } // CurrentLang = 0; // LAssert(CurrentLang < Lang.Length()); Lang.Length(l.Length() + ((EnglishFound) ? 0 : 1)); int n = 0; Lang[n++] = LFindLang("en"); for (auto li: l) { if (stricmp(li->Id, "en") != 0) // !English { Lang[n] = LFindLang(li->Id); LAssert(Lang[n]); n++; } } UpdateColumns(); } void ResStringGroup::DeleteSel() { } void ResStringGroup::Copy(bool Delete) { } void ResStringGroup::Paste() { } LView *ResStringGroup::CreateUI() { return Ui = new ResStringUi(this); } bool ResStringGroup::Test(ErrorCollection *e) { bool Status = true; for (auto s: Strs) { if (!s->Test(e)) { Status = false; } } return Status; } bool ResStringGroup::Read(LXmlTag *t, SerialiseContext &Ctx) { bool Status = false; char *p = 0; if ((p = t->GetAttr("Name"))) { if (Ctx.Format == XmlFile) { char *x = DecodeXml(p); if (x) { Name(x); DeleteArray(x); } } else { Name(p); } Empty(); LHashTbl, bool> L; L.Add("en", true); Status = true; for (auto c: t->Children) { ResString *s = new ResString(this); if (s && s->Read(c, Ctx)) { for (auto i: s->Items) { if (!L.Find(i->GetLang())) { L.Add(i->GetLang(), true); AppendLanguage(i->GetLang()); } } } else { // Lets not fail everything here... just keep trying to read DeleteObj(s); } } } if (Item) { Item->Update(); } return Status; } bool ResStringGroup::Write(LXmlTag *t, SerialiseContext &Ctx) { bool Status = true; t->SetTag("string-group"); auto n = Ctx.Format == XmlFile ? EncodeXml(Name()) : NewStr(Name()); t->SetAttr("Name", n); DeleteArray(n); for (auto i: Strs) { ResString *s = dynamic_cast(i); if (s && (s->Define || s->Items.Length() > 0)) { LXmlTag *c = new LXmlTag; if (c && s->Write(c, Ctx)) { t->InsertTag(c); } else { Status = false; DeleteObj(c); } } } return Status; } void ResStringGroup::OnRightClick(LSubMenu *RClick) { if (RClick) { if (Enabled()) { RClick->AppendSeparator(); if (Type() > 0) { auto Export = RClick->AppendSub("Export to..."); if (Export) { Export->AppendItem("Lgi File", IDM_EXPORT, true); Export->AppendItem("Win32 Resource Script", IDM_EXPORT_WIN32, false); } } } } } void ResStringGroup::OnCommand(int Cmd) { switch (Cmd) { case IDM_IMPORT: { - LFileSelect Select; - Select.Parent(AppWindow); - Select.Type("Text", "*.txt"); - if (Select.Open()) + auto Select = new LFileSelect(AppWindow); + Select->Type("Text", "*.txt"); + Select->Open([&](auto dlg, auto status) { - LFile F; - if (F.Open(Select.Name(), O_READ)) + if (status) { - SerialiseContext Ctx; - Resource *Res = AppWindow->NewObject(Ctx, 0, -Type()); - if (Res) + LFile F; + if (F.Open(dlg->Name(), O_READ)) { - // TODO - // Res->Read(); + SerialiseContext Ctx; + Resource *Res = AppWindow->NewObject(Ctx, 0, -Type()); + if (Res) + { + // TODO + // Res->Read(); + } + } + else + { + LgiMsg(AppWindow, "Couldn't open file for reading."); } } - else - { - LgiMsg(AppWindow, "Couldn't open file for reading."); - } - } + delete dlg; + }); break; } case IDM_EXPORT: { - LFileSelect Select; - Select.Parent(AppWindow); - Select.Type("Text", "*.txt"); - if (Select.Save()) + auto Select = new LFileSelect(AppWindow); + Select->Type("Text", "*.txt"); + Select->Save([&](auto dlg, auto status) { - LFile F; - if (F.Open(Select.Name(), O_WRITE)) + if (status) { - F.SetSize(0); - // Serialize(F, true); + LFile F; + if (F.Open(dlg->Name(), O_WRITE)) + { + F.SetSize(0); + // Serialize(F, true); + } + else + { + LgiMsg(AppWindow, "Couldn't open file for writing."); + } } - else - { - LgiMsg(AppWindow, "Couldn't open file for writing."); - } - } + delete dlg; + }); break; } } } void ResStringGroup::Create(LXmlTag *load, SerialiseContext *ctx) { if (load) { if (ctx) Read(load, *ctx); else LAssert(0); } } //////////////////////////////////////////////////////////////////// ResStringUi::ResStringUi(ResStringGroup *Res) { StringGrp = Res; Tools = 0; Status = 0; StatusInfo = 0; } ResStringUi::~ResStringUi() { if (StringGrp) { StringGrp->Ui = 0; } } void ResStringUi::OnPaint(LSurface *pDC) { LRegion Client(0, 0, X()-1, Y()-1); for (auto w: Children) { LRect r = w->GetPos(); Client.Subtract(&r); } pDC->Colour(L_MED); for (LRect *r = Client.First(); r; r = Client.Next()) { pDC->Rectangle(r); } } void ResStringUi::PourAll() { LRegion Client(GetClient()); LRegion Update; for (auto v: Children) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible v->Visible(false); } } for (int i=0; iSetBitmap(FileName, 16, 16)) { Tools->Attach(this); Tools->AppendButton("New String", IDM_NEW, TBT_PUSH, !StringGrp->SystemObject()); Tools->AppendButton("Delete String", IDM_DELETE, TBT_PUSH, !StringGrp->SystemObject()); Tools->AppendSeparator(); Tools->AppendButton("New Language", IDM_NEW_LANG, TBT_PUSH); Tools->AppendButton("Delete Language", IDM_DELETE_LANG, TBT_PUSH); } else { DeleteObj(Tools); } } Status = new LStatusBar; if (Status) { Status->Attach(this); StatusInfo = Status->AppendPane("", 2); } ResFrame *Frame = new ResFrame(StringGrp); if (Frame) { Frame->Attach(this); } } LMessage::Result ResStringUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { StringGrp->OnCommand(Msg->A()&0xffff, (int)(Msg->A()>>16), (OsView) Msg->B()); break; } case M_DESCRIBE: { char *Text = (char*) Msg->B(); if (Text) { StatusInfo->Name(Text); } break; } } return LView::OnEvent(Msg); } diff --git a/ResourceEditor/Code/LgiRes_TableLayout.cpp b/ResourceEditor/Code/LgiRes_TableLayout.cpp --- a/ResourceEditor/Code/LgiRes_TableLayout.cpp +++ b/ResourceEditor/Code/LgiRes_TableLayout.cpp @@ -1,1734 +1,1737 @@ #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "lgi/common/Button.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #define DRAW_CELL_INDEX 0 #define DRAW_TABLE_SIZE 0 enum Cmds { IDM_ALIGN_X_MIN = 100, IDM_ALIGN_X_CTR, IDM_ALIGN_X_MAX, IDM_ALIGN_Y_MIN, IDM_ALIGN_Y_CTR, IDM_ALIGN_Y_MAX, IDM_UNMERGE, IDM_FIX_TABLE, IDM_INSERT_ROW, IDM_INSERT_COL, }; static LColour Blue(0, 30, 222); ///////////////////////////////////////////////////////////////////// struct Pair { int Pos, Size; }; void CalcCell(LArray &p, LArray &s, int Total) { int CurI = 0; for (int i=0; i T SumElements(LArray &a, ssize_t start, ssize_t end) { T Sum = 0; for (auto i=start; i<=end; i++) { Sum += a[i]; } return Sum; } void MakeSumUnity(LArray &a) { double s = SumElements(a, 0, a.Length()-1); double Diff = s - 1.0; if (Diff < 0) Diff *= -1.0; if (Diff > 0.001) { for (int i=0; i Ctrls; TableAlign AlignX; TableAlign AlignY; LAutoString Class; // CSS class for styling LAutoString Style; // CSS styles ResTableCell(CtrlTable *table, ssize_t cx, ssize_t cy) { AlignX = AlignY = AlignMin; Table = table; Selected = false; Cell.ZOff(0, 0); Cell.Offset((int)cx, (int)cy); Pos.ZOff(-1, -1); } ResTableCell() { Ctrls.DeleteObjects(); } void MoveCtrls() { for (int i=0; iView()->GetPos(); if (r.X() > Pos.X()) { r.x2 = r.x1 + Pos.X() - 1; } if (r.Y() > Pos.Y()) { r.y2 = r.y1 + Pos.Y() - 1; } if (r.x2 > Pos.x2) { r.Offset(Pos.x2 - r.x2, 0); } if (r.y2 > Pos.y2) { r.Offset(0, Pos.y2 - r.y2); } c->SetPos(r); } } } void SetPos(LRect &p) { int Dx = p.x1 - Pos.x1; int Dy = p.y1 - Pos.y1; for (int i=0; iView()->GetPos(); r.Offset(Dx, Dy); c->SetPos(r); } } Pos = p; MoveCtrls(); } bool GetVariant(const char *Name, LVariant &Value, const char *Array) { if (stricmp(Name, VAL_Span) == 0) { Value = Cell.Describe(); } else if (stricmp(Name, VAL_Children) == 0) { List c; for (int i=0; iType == GV_VOID_PTR); ResDialogCtrl *Obj = dynamic_cast((ResObject*) v->Value.Ptr); if (Obj) { Table->SetAttachCell(this); LRect r = Obj->View()->GetPos(); Table->AttachCtrl(Obj, &r); Table->SetAttachCell(0); } } } else if (stricmp(Name, VAL_HorizontalAlign) == 0) { if (Value.Str()) { for (int i=0; iAppendSub("Horizontal Align"); if (s) { s->AppendItem("Left", IDM_ALIGN_X_MIN, AlignX != AlignMin); s->AppendItem("Center", IDM_ALIGN_X_CTR, AlignX != AlignCenter); s->AppendItem("Right", IDM_ALIGN_X_MAX, AlignX != AlignMax); } s = RClick->AppendSub("Vertical Align"); if (s) { s->AppendItem("Top", IDM_ALIGN_Y_MIN, AlignY != AlignMin); s->AppendItem("Center", IDM_ALIGN_Y_CTR, AlignY != AlignCenter); s->AppendItem("Bottom", IDM_ALIGN_Y_MAX, AlignY != AlignMax); } RClick->AppendItem("Unmerge", IDM_UNMERGE, Cell.X() > 1 || Cell.Y() > 1); RClick->AppendItem("Fix Missing Cells", IDM_FIX_TABLE, true); RClick->AppendItem("Insert Row", IDM_INSERT_ROW, true); RClick->AppendItem("Insert Column", IDM_INSERT_COL, true); m.ToScreen(); switch (RClick->Float(Table, m.x, m.y)) { case IDM_ALIGN_X_MIN: AlignX = AlignMin; break; case IDM_ALIGN_X_CTR: AlignX = AlignCenter; break; case IDM_ALIGN_X_MAX: AlignX = AlignMax; break; case IDM_ALIGN_Y_MIN: AlignY = AlignMin; break; case IDM_ALIGN_Y_CTR: AlignY = AlignCenter; break; case IDM_ALIGN_Y_MAX: AlignY = AlignMax; break; case IDM_UNMERGE: Table->UnMerge(this); break; case IDM_FIX_TABLE: Table->Fix(); break; case IDM_INSERT_ROW: Table->InsertRow(Cell.y1); break; case IDM_INSERT_COL: Table->InsertCol(Cell.x1); break; } DeleteObj(RClick); } } } }; enum HandleTypes { LAddCol, LAddRow, LDeleteCol, LDeleteRow, LSizeCol, LSizeRow, LJoinCells, }; struct OpHandle : public LRect { bool Over; HandleTypes Type; int Index; ResTableCell *a, *b; OpHandle &Set(HandleTypes type, int x = -1, int y = -1) { Type = type; Over = false; Index = -1; a = b = NULL; if (x > 0 && y > 0) ZOff(x, y); return *this; } void OnPaint(LSurface *pDC) { #define OFF 1 int cx = X() >> 1; int cy = Y() >> 1; pDC->Colour(Over ? LColour::Red : Blue); pDC->Rectangle(this); pDC->Colour(LColour::White); switch (Type) { case LSizeRow: { pDC->Line(x1 + cx, y1 + OFF, x1 + cx, y2 - OFF); pDC->Line(x1 + cx - 1, y1 + OFF + 1, x1 + cx + 1, y1 + OFF + 1); pDC->Line(x1 + cx - 1, y2 - OFF - 1, x1 + cx + 1, y2 - OFF - 1); break; } case LSizeCol: { pDC->Line(x1 + OFF, y1 + cy, x2 - OFF, y1 + cy); pDC->Line(x1 + OFF + 1, y1 + cy - 1, x1 + OFF + 1, y1 + cy + 1); pDC->Line(x2 - OFF - 1, y1 + cy - 1, x2 - OFF - 1, y1 + cy + 1); break; } case LAddCol: case LAddRow: case LJoinCells: { pDC->Line(x1 + cx, y1 + OFF, x1 + cx, y2 - OFF); pDC->Line(x1 + OFF, y1 + cy, x2 - OFF, y1 + cy); break; } case LDeleteCol: case LDeleteRow: { pDC->Line(x1 + OFF, y1 + cy, x2 - OFF, y1 + cy); break; } default: LAssert(!"Impl me."); } } }; class CtrlTablePrivate { public: bool InLayout, LayoutDirty; // The cell container CtrlTable *Table; ssize_t CellX, CellY; LArray Cells; ResTableCell *AttachTo; // Column + Row sizes LArray ColSize; LArray RowSize; // Goobers for editing the cell layout LArray Handles; int DragRowSize; int DragColSize; // Methods CtrlTablePrivate(CtrlTable *t) { InLayout = false; LayoutDirty = false; Table = t; CellX = CellY = 2; AttachTo = 0; Cells[0] = new ResTableCell(t, 0, 0); Cells[1] = new ResTableCell(t, 1, 0); Cells[2] = new ResTableCell(t, 0, 1); Cells[3] = new ResTableCell(t, 1, 1); ColSize[0] = 0.5; ColSize[1] = 0.5; RowSize[0] = 0.5; RowSize[1] = 0.5; DragRowSize = DragColSize = -1; } ~CtrlTablePrivate() { Cells.DeleteObjects(); } bool GetSelected(LArray &s) { for (int i=0; iSelected) s.Add(Cells[i]); } return s.Length(); } ResTableCell *GetCellAt(int cx, int cy) { for (int i=0; iCell.Overlap(cx, cy)) { return Cells[i]; } } return 0; } void Layout(LRect c) { #define ADD_BORDER 10 #define CELL_SPACING 1 if (InLayout) return; InLayout = true; LayoutDirty = false; Handles.Length(0); int x, y; int BtnSize = ADD_BORDER * 2 / 3; auto &AddX = Handles.New().Set(LAddCol, BtnSize, BtnSize); auto &AddY = Handles.New().Set(LAddRow, BtnSize, BtnSize); int BtnX = c.X() - AddX.X() - 2; int BtnY = c.Y() - AddY.Y() - 2; AddX.Offset(BtnX, 0); AddY.Offset(0, BtnY); c.x2 = AddX.x2 - ADD_BORDER; c.y2 = AddY.y2 - ADD_BORDER; if (c.Valid()) { int AvailX = c.X(); int AvailY = c.Y(); LArray XPair, YPair; CalcCell(XPair, ColSize, AvailX); CalcCell(YPair, RowSize, AvailY); XPair[CellX].Pos = AvailX; for (x=0; xCell.x1 == x && Cell->Cell.y1 == y) { LRect Pos = Cell->Pos; // Set cells pixel position, with spanning int Ex = XPair[Cell->Cell.x2 + 1].Pos; int Ey = YPair[Cell->Cell.y2 + 1].Pos; Pos.ZOff(Ex - Px - 1 - CELL_SPACING, Ey - Py - 1 - CELL_SPACING); // Offset the cell into place Pos.Offset(Px, Py); Cell->SetPos(Pos); if (Cell->Selected) { // Add cell joining goobers... ResTableCell *c = GetCellAt(x + Cell->Cell.X(), y); if (c && c->Selected) { auto &j = Handles.New().Set(LJoinCells, BtnSize, BtnSize); j.a = Cell; j.b = c; j.Offset(Cell->Pos.x2 - (j.X()>>1), Cell->Pos.y1 + ((Cell->Pos.Y()-j.Y()) >> 1)); int asd=0; } c = GetCellAt(x, y + Cell->Cell.Y()); // LgiTrace("%s %i,%i+%i = %p\n", Cell->Cell.GetStr(), x, y, Cell->Cell.Y(), c); if (c && c->Selected) { auto &j = Handles.New().Set(LJoinCells, BtnSize, BtnSize); j.a = Cell; j.b = c; j.Offset(Cell->Pos.x1 + ((Cell->Pos.X()-j.X()) >> 1), Cell->Pos.y2 - (j.Y()>>1)); int asd=0; } } } LAssert(Cell->Cell.X()); x += Cell->Cell.X(); Px += Cell->Pos.X() + CELL_SPACING; } else break; } } } InLayout = false; } bool DeleteCol(int x) { bool Status = false; int OldCellX = CellX; // Delete column 'x' for (int y=0; yCell.y2 + 1; if (c->Cell.X() == 1) { Cells.Delete(c); DeleteObj(c); } else { c->Cell.x2--; } Status = true; } else break; } CellX--; double Last = ColSize[x]; ColSize.DeleteAt(x, true); for (int i=0; iCell.x1 == x && c->Cell.y1 == y) { c->Cell.Offset(-1, 0); } } } } Table->Layout(); return Status; } bool DeleteRow(int y) { bool Status = false; int x; int OldCellY = CellY; // Delete row 'y' for (x=0; xCell.x2 + 1; if (c->Cell.Y() == 1) { Cells.Delete(c); DeleteObj(c); } else { c->Cell.y2--; } Status = true; } else break; } CellY--; double Last = RowSize[y]; RowSize.DeleteAt(y, true); for (int i=0; iCell.x1 == x && c->Cell.y1 == y) { c->Cell.Offset(0, -1); } } } } Table->Layout(); return Status; } ResTableCell *GetCell(ResDialogCtrl *Ctrl) { for (int i=0; iCtrls.HasItem(Ctrl)) { return Cells[i]; } } return 0; } }; CtrlTable::CtrlTable(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Table, load) { d = new CtrlTablePrivate(this); AcceptChildren = true; } CtrlTable::~CtrlTable() { DeleteObj(d); } bool CtrlTable::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); LArray s; if (d->GetSelected(s) == 1) { int Id = 150; Fields.Insert(this, DATA_STR, Id++, VAL_CellClass, "Cell Class"); Fields.Insert(this, DATA_STR, Id++, VAL_CellStyle, "Cell Style", -1, true); } return Status; } bool CtrlTable::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); LArray s; ResTableCell *c; if (d->GetSelected(s) == 1 && ((c = s[0])) != NULL) { Fields.Serialize(this, VAL_CellClass, c->Class); Fields.Serialize(this, VAL_CellStyle, c->Style); } return Status; } void CtrlTable::EnumCtrls(List &Ctrls) { // LgiTrace("Tbl Ref=%i\n", Str->GetRef()); for (int i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; for (int n=0; nCtrls.Length(); n++) { // LgiTrace(" Ref=%i\n", c->Ctrls[n]->Str->GetRef()); c->Ctrls[n]->EnumCtrls(Ctrls); } } } bool CtrlTable::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case TableLayoutCols: { LStringPipe p; for (int i=0; iColSize.Length(); i++) { if (i) p.Push(","); p.Print("%.3f", d->ColSize[i]); } Value.OwnStr(p.NewStr()); break; } case TableLayoutRows: { LStringPipe p; for (int i=0; iRowSize.Length(); i++) { if (i) p.Push(","); p.Print("%.3f", d->RowSize[i]); } Value.OwnStr(p.NewStr()); break; } case TableLayoutCell: { auto Coords = LString(Array).SplitDelimit(","); if (Coords.Length() != 2) return false; Value = d->GetCellAt(Coords[0].Int(), Coords[1].Int()); break; } default: { LAssert(!"Invalid property."); return false; } } return true; } bool CtrlTable::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case TableLayoutCols: { d->Cells.DeleteObjects(); LToken t(Value.Str(), ","); d->ColSize.Length(0); for (int i=0; iColSize.Add(atof(t[i])); } MakeSumUnity(d->ColSize); d->CellX = d->ColSize.Length(); break; } case TableLayoutRows: { d->Cells.DeleteObjects(); LToken t(Value.Str(), ","); d->RowSize.Length(0); for (int i=0; iRowSize.Add(atof(t[i])); } MakeSumUnity(d->RowSize); d->CellY = d->RowSize.Length(); break; } case TableLayoutCell: { auto Coords = LString(Array).SplitDelimit(","); if (Coords.Length() != 2) return false; auto Cx = Coords[0].Int(); auto Cy = Coords[1].Int(); ResTableCell *c = new ResTableCell(this, Cx, Cy); if (!c) return false; d->Cells.Add(c); LDom **Ptr = (LDom**)Value.Value.Ptr; if (!Ptr) return false; *Ptr = c; // LgiTrace("Create cell %i,%i = %p\n", (int)Cx, (int)Cy, c); d->LayoutDirty = true; break; } case ObjStyle: { const char *s = Value.Str(); GetCss(true)->Parse(s); break; } default: { LAssert(!"Invalid property."); return false; } } return true; } LRect *CtrlTable::GetPasteArea() { for (int i=0; iCells.Length(); i++) { if (d->Cells[i]->Selected) return &d->Cells[i]->Pos; } return 0; } LRect *CtrlTable::GetChildArea(ResDialogCtrl *Ctrl) { ResTableCell *c = d->GetCell(Ctrl); if (c) return &c->Pos; return NULL; } void CtrlTable::OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (Attaching) return; ResDialogCtrl *Rc = dynamic_cast(Wnd); if (Rc) { ResTableCell *c = d->GetCell(Rc); if (c) c->Ctrls.Delete(Rc); } } void CtrlTable::SetAttachCell(ResTableCell *c) { d->AttachTo = c; } bool CtrlTable::AttachCtrl(ResDialogCtrl *Ctrl, LRect *r) { if (d->AttachTo) { Layout(); if (!d->AttachTo->Ctrls.HasItem(Ctrl)) { d->AttachTo->Ctrls.Add(Ctrl); } LRect b = d->AttachTo->Pos; if (r->X() > b.X()) { r->x1 = b.x1; r->x2 = b.x2; } else if (r->x2 > b.x2) { r->Offset(b.x2 - r->x2, 0); } if (r->Y() > b.Y()) { r->y1 = b.y1; r->y2 = b.y2; } else if (r->y2 > b.y2) { r->Offset(b.y2 - r->y2, 0); } Ctrl->SetPos(*r); } else { for (int i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c) { if (c->Pos.Overlap(r->x1, r->y1)) { if (!c->Ctrls.HasItem(Ctrl)) { c->Ctrls.Add(Ctrl); } Ctrl->SetPos(*r); c->MoveCtrls(); *r = Ctrl->View()->GetPos(); break; } } } } return ResDialogCtrl::AttachCtrl(Ctrl, r); } void CtrlTable::Layout() { d->Layout(GetClient()); } void CtrlTable::OnPosChange() { Layout(); } void CtrlTable::OnPaint(LSurface *pDC) { int i; Client.Set(0, 0, X()-1, Y()-1); if (d->LayoutDirty || d->Handles.Length() == 0) d->Layout(GetClient()); pDC->Colour(Blue); for (i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c) { pDC->Box(&c->Pos); if (c->Selected) { int Op = pDC->Op(GDC_ALPHA); if (pDC->Applicator()) { pDC->Colour(Blue); pDC->Applicator()->SetVar(GAPP_ALPHA_A, 0x20); } else pDC->Colour(LColour(Blue.r(), Blue.g(), Blue.b(), 0x20)); pDC->Rectangle(c->Pos.x1 + 1, c->Pos.y1 + 1, c->Pos.x2 - 1, c->Pos.y2 - 1); pDC->Op(Op); pDC->Colour(Blue); } #if DRAW_CELL_INDEX LSysFont->Colour(Blue, 24); LSysFont->Transparent(true); char s[256]; sprintf(s, "%i,%i-%i,%i", c->Cell.x1, c->Cell.y1, c->Cell.X(), c->Cell.Y()); LSysFont->Text(pDC, c->Pos.x1 + 3, c->Pos.y1 + 1, s); #endif // LgiTrace("Drawing %i,%i = %p @ %s\n", c->Cell.x1, c->Cell.y1, c, c->Pos.GetStr()); } } for (auto &h: d->Handles) h.OnPaint(pDC); #if DRAW_TABLE_SIZE LSysFont->Colour(Blue, 24); LSysFont->Transparent(true); char s[256]; sprintf(s, "Cells: %i,%i", d->CellX, d->CellY); LSysFont->Text(pDC, 3, 24, s); #endif ResDialogCtrl::OnPaint(pDC); } void CtrlTable::OnMouseMove(LMouse &m) { bool Change = false; for (auto &h: d->Handles) { bool Over = h.Overlap(m.x, m.y); if (Over != h.Over) { h.Over = Over; Change = true; } } if (Change) Invalidate(); if (IsCapturing()) { LArray p; int Fudge = 6; if (d->DragRowSize >= 0) { // Adjust RowSize[d->DragRowSize] and RowSize[d->DragRowSize+1] to // center on the mouse y location int AvailY = GetClient().Y(); CalcCell(p, d->RowSize, AvailY); int BothPx = p[d->DragRowSize].Size + p[d->DragRowSize+1].Size; int PxOffset = m.y - p[d->DragRowSize].Pos + Fudge; PxOffset = limit(PxOffset, 2, BothPx - 2); double Frac = (double) PxOffset / BothPx; double Total = d->RowSize[d->DragRowSize] + d->RowSize[d->DragRowSize+1]; d->RowSize[d->DragRowSize] = Frac * Total; d->RowSize[d->DragRowSize+1] = (1.0 - Frac) * Total; // LgiTrace("Int: %i/%i, Frac: %f,%f\n", PxOffset, BothPx, d->RowSize[d->DragRowSize], d->RowSize[d->DragRowSize+1]); Layout(); Invalidate(); return; } else if (d->DragColSize >= 0) { // Adjust ColSize[d->DragColSize] and ColSize[d->DragColSize+1] to // center on the mouse x location int AvailX = GetClient().X(); CalcCell(p, d->ColSize, AvailX); int BothPx = p[d->DragColSize].Size + p[d->DragColSize+1].Size; int PxOffset = m.x - p[d->DragColSize].Pos + Fudge; PxOffset = limit(PxOffset, 2, BothPx - 2); double Frac = (double) PxOffset / BothPx; double Total = d->ColSize[d->DragColSize] + d->ColSize[d->DragColSize+1]; d->ColSize[d->DragColSize] = Frac * Total; d->ColSize[d->DragColSize+1] = (1.0 - Frac) * Total; Layout(); Invalidate(); return; } } ResDialogCtrl::OnMouseMove(m); } void CtrlTable::Fix() { // Fix missing cells for (int y=0; yCellY; y++) { for (int x=0; xCellX; ) { ResTableCell *c = d->GetCellAt(x, y); // LgiTrace("[%i][%i] = %p (%ix%i, %s)\n", x, y, c, c ? c->Cell.X() : -1, c ? c->Cell.Y() : -1, c ? c->Pos.GetStr() : NULL); if (c) { x += c->Cell.X(); } else { c = new ResTableCell(this, x, y); if (c) { d->Cells.Add(c); x++; } else break; } } } // Fix rows/cols size #define absf(i) ((i) < 0.0 ? -(i) : (i)) double Sum = 0.0; int n; for (n=0; nRowSize.Length(); n++) { Sum += d->RowSize[n]; } if (absf(Sum - 1.0) > 0.001) { for (n=0; nRowSize.Length(); n++) { double k = d->RowSize[n]; k /= Sum; d->RowSize[n] = k; } } Sum = 0.0; for (n=0; nRowSize.Length(); n++) { Sum += d->RowSize[n]; } if (absf(Sum - 1.0) > 0.001) { for (n=0; nRowSize.Length(); n++) { double k = d->RowSize[n]; k /= Sum; d->RowSize[n] = k; } } Layout(); Invalidate(); } void CtrlTable::InsertRow(int y) { // Shift existing cells down int i; for (i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c) { if (c->Cell.y1 >= y) c->Cell.Offset(0, 1); else if (c->Cell.y2 >= y) // Make spanned cells taller... c->Cell.y2++; } } // Add new row of 1x1 cells for (i=0; iCellX; i++) { d->Cells.Add(new ResTableCell(this, i, y)); } d->CellY++; // Update rows double Last = d->RowSize[d->RowSize.Length()-1]; d->RowSize.Add(Last); for (i=0; iRowSize.Length(); i++) { d->RowSize[i] = d->RowSize[i] / (1.0 + Last); } // Refresh the screen Layout(); Invalidate(); } void CtrlTable::InsertCol(int x) { auto Par = GetParent(); // Shift existing cells down int i; for (i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c) { if (c->Cell.x1 >= x) c->Cell.Offset(1, 0); else if (c->Cell.x2 >= x) // Make spanned cells wider... c->Cell.x2++; } } // Add new row of 1x1 cells for (i=0; iCellY; i++) { d->Cells.Add(new ResTableCell(this, x, i)); } d->CellX++; // Update rows double Last = d->ColSize.Last(); d->ColSize.Add(Last); for (i=0; iColSize.Length(); i++) { d->ColSize[i] = d->ColSize[i] / (1.0 + Last); } // Refresh the screen Layout(); Invalidate(); } void CtrlTable::UnMerge(ResTableCell *Cell) { if (Cell && (Cell->Cell.X() > 1 || Cell->Cell.Y() > 1)) { for (int y=Cell->Cell.x1; y<=Cell->Cell.y2; y++) { for (int x=Cell->Cell.x1; x<=Cell->Cell.x2; x++) { if (x > Cell->Cell.x1 || y > Cell->Cell.y1) { ResTableCell *n = new ResTableCell(this, x, y); if (n) { d->Cells.Add(n); } } } } Cell->Cell.x2 = Cell->Cell.x1; Cell->Cell.y2 = Cell->Cell.y1; Layout(); Invalidate(); } } void CtrlTable::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { OpHandle *h = NULL; for (auto &i: d->Handles) { if (i.Overlap(m.x, m.y)) { h = &i; break; } } if (h && h->Type == LAddCol) { for (int i=0; iCellY; i++) d->Cells.Add(new ResTableCell(this, d->CellX, i)); d->CellX++; double Last = d->ColSize[d->ColSize.Length()-1]; d->ColSize.Add(Last); for (int i=0; iColSize.Length(); i++) d->ColSize[i] = d->ColSize[i] / (1.0 + Last); Layout(); Invalidate(); return; } else if (h && h->Type == LAddRow) { for (int i=0; iCellX; i++) d->Cells.Add(new ResTableCell(this, i, d->CellY)); d->CellY++; double Total = 0; double Last = d->RowSize[d->RowSize.Length()-1]; d->RowSize.Add(Last); for (int i=0; iRowSize.Length(); i++) { d->RowSize[i] = d->RowSize[i] / (1.0 + Last); Total += d->RowSize[i]; } Layout(); Invalidate(); return; } else { bool Dirty = false; bool EatClick = true; int i; ResTableCell *Over = 0; // Look at cell joins if (h && h->Type == LJoinCells) { // Do a cell merge LRect u = h->a->Cell; u.Union(&h->b->Cell); d->Cells.Delete(h->a); for (int y=u.y1; y<=u.y2; y++) { for (int x=u.x1; x<=u.x2; x++) { ResTableCell *c = d->GetCellAt(x, y); if (c) { d->Cells.Delete(c); DeleteObj(c); } } } h->a->Cell = u; d->Cells.Add(h->a); Dirty = true; } if (!Dirty) { // Select a cell? for (i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c->Pos.Overlap(m.x, m.y)) { Over = c; if (!c->Selected || m.Ctrl()) { Dirty = true; EatClick = false; c->Selected = !c->Selected; } } else if (!m.Ctrl()) { if (c->Selected) { Dirty = true; EatClick = false; c->Selected = false; } } } } // Delete column goobers if (h && h->Type == LDeleteCol) Dirty = d->DeleteCol(h->Index); // Delete row goobers if (!Dirty && h && h->Type == LDeleteRow) Dirty = d->DeleteRow(h->Index); // Size row goobs if (!Dirty && h && h->Type == LSizeRow) { Dirty = true; d->DragRowSize = h->Index; Capture(true); } // Size col goobs if (!Dirty && h && h->Type == LSizeCol) { Dirty = true; d->DragColSize = h->Index; Capture(true); } if (Dirty) { Layout(); Invalidate(); if (EatClick) return; } } } else if (m.IsContextMenu()) { for (int i=0; iCells.Length(); i++) { ResTableCell *c = d->Cells[i]; if (c->Pos.Overlap(m.x, m.y)) { c->OnMouseClick(m); return; } } } } else { Capture(false); d->DragRowSize = -1; d->DragColSize = -1; } ResDialogCtrl::OnMouseClick(m); } //////////////////////////////////////////////////////////////////////////////////////////////// enum { M_FINISHED = M_USER + 1000, }; class TableLayoutTest : public LDialog { LTableLayout *Tbl; LView *Msg; LTree *Tree; class DlgContainer *View; LAutoPtr Worker; LAutoString Base; public: TableLayoutTest(LViewI *par); ~TableLayoutTest(); void OnDialog(LDialogRes *Dlg); int OnNotify(LViewI *Ctrl, LNotification n); LMessage::Param OnEvent(LMessage *m); }; class DlgItem : public LTreeItem { TableLayoutTest *Wnd; LDialogRes *Dlg; public: DlgItem(TableLayoutTest *w, LDialogRes *dlg) { Wnd = w; Dlg = dlg; } const char *GetText(int i) { return Dlg->Str->Str; } void OnSelect() { Wnd->OnDialog(Dlg); } }; static bool HasTableLayout(LXmlTag *t) { if (t->IsTag("TableLayout")) return true; for (auto c: t->Children) { if (HasTableLayout(c)) return true; } return false; } class Lr8Item : public LTreeItem { TableLayoutTest *Wnd; LString File; LAutoPtr Res; public: Lr8Item(TableLayoutTest *w, LAutoPtr res, char *file) { Wnd = w; Res = res; File = file; List::I d = Res->GetDialogs(); for (LDialogRes *dlg = *d; dlg; dlg = *++d) { if (dlg->Str && HasTableLayout(dlg->Dialog)) { Insert(new DlgItem(Wnd, dlg)); } } if (stristr(File, "Scribe-Branches\\v2.00")) { Expanded(true); } } const char *GetText(int i) { return File ? File.Get() : "#error"; } }; class Lr8Search : public LThread { TableLayoutTest *Wnd; char *Base; LTree *Tree; public: Lr8Search(TableLayoutTest *w, char *base, LTree *tree) : LThread("Lr8Search") { Wnd = w; Base = base; Tree = tree; Run(); } ~Lr8Search() { while (!IsExited()) { LSleep(1); } } int Main() { LArray Ext; LArray Files; Ext.Add("*.lr8"); if (LRecursiveFileSearch(Base, &Ext, &Files)) { for (int i=0; i Res; if (Res.Reset(new LResources(Files[i]))) { List::I d = Res->GetDialogs(); bool HasTl = false; for (LDialogRes *dlg = *d; dlg; dlg = *++d) { if (dlg->Str && HasTableLayout(dlg->Dialog)) { HasTl = true; break; } } if (HasTl) { auto r = LMakeRelativePath(Base, Files[i]); Tree->Insert(new Lr8Item(Wnd, Res, r)); } } } } Files.DeleteArrays(); Wnd->PostEvent(M_FINISHED); return 0; } }; class DlgContainer : public LLayout { LDialogRes *Dlg; LRect Size; public: DlgContainer(int id) { Dlg = 0; Sunken(true); SetId(id); Size.Set(0, 0, 100, 100); SetPos(Size); } void OnPaint(LSurface *pDC) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); } void OnDialog(LDialogRes *d) { while (Children.Length()) delete Children[0]; if ((Dlg = d)) { if (Dlg->GetRes()->LoadDialog(Dlg->Str->Id, this, &Size)) { LRect r = GetPos(); r.SetSize(Size.X(), Size.Y()); SetPos(r); AttachChildren(); SendNotify(LNotifyTableLayoutRefresh); } } } }; TableLayoutTest::TableLayoutTest(LViewI *par) { LRect r(0, 0, 1000, 800); SetPos(r); SetParent(par); AddView(Tbl = new LTableLayout); auto *c = Tbl->GetCell(0, 0, true, 2); c->Add(Msg = new LTextLabel(100, 0, 0, -1, -1, "Searching for files...")); c = Tbl->GetCell(0, 1); c->Add(Tree = new LTree(101, 0, 0, 100, 100)); c = Tbl->GetCell(1, 1); c->Add(View = new DlgContainer(102)); c = Tbl->GetCell(0, 2, true, 2); c->TextAlign(LCss::AlignRight); c->Add(new LButton(IDOK, 0, 0, -1, -1, "Close")); char e[MAX_PATH_LEN]; LGetSystemPath(LSP_APP_INSTALL, e, sizeof(e)); LMakePath(e, sizeof(e), e, "../../.."); Base.Reset(NewStr(e)); Worker.Reset(new Lr8Search(this, Base, Tree)); } TableLayoutTest::~TableLayoutTest() { Worker.Reset(); } void TableLayoutTest::OnDialog(LDialogRes *Dlg) { if (View) View->OnDialog(Dlg); } int TableLayoutTest::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: EndModal(1); break; } return LDialog::OnNotify(Ctrl, n); } LMessage::Param TableLayoutTest::OnEvent(LMessage *m) { if (m->Msg() == M_FINISHED) { Tree->Invalidate(); Msg->Name("Finished."); Worker.Reset(); } return LDialog::OnEvent(m); } void OpenTableLayoutTest(LViewI *p) { - TableLayoutTest Dlg(p); - Dlg.DoModal(); + auto Dlg = new TableLayoutTest(p); + Dlg->DoModal([](auto dlg, auto id) + { + delete dlg; + }); } diff --git a/ResourceEditor/Code/ShowLanguages.cpp b/ResourceEditor/Code/ShowLanguages.cpp --- a/ResourceEditor/Code/ShowLanguages.cpp +++ b/ResourceEditor/Code/ShowLanguages.cpp @@ -1,114 +1,112 @@ #include "lgi/common/Lgi.h" #include "LgiResEdit.h" #include "lgi/common/List.h" #include "lgi/common/Button.h" #include "lgi/common/ListItemCheckBox.h" class ShowLanguagesDlgPriv { public: AppWnd *App; LList *Lst; }; class Lang : public LListItem { LLanguage *L; int Index; LListItemCheckBox *Val; public: Lang(AppWnd *App, LLanguage *l, int i) { L = l; Index = i; SetText(l->Id, 1); SetText(l->Name, 2); Val = new LListItemCheckBox(this, 0, App->ShowLang(L->Id)); } LLanguage *GetLang() { return L; } int GetVal() { return (int)Val->Value(); } }; int Cmp(LListItem *a, LListItem *b, NativeInt d) { return stricmp(a->GetText(2), b->GetText(2)); } ShowLanguagesDlg::ShowLanguagesDlg(AppWnd *app) { d = new ShowLanguagesDlgPriv; SetParent(d->App = app); LRect r(0, 0, 300, 500); SetPos(r); MoveToCenter(); Name("Show Languages"); r = GetClient(); Children.Insert(d->Lst = new LList(100, 10, 10, r.X() - 20, r.Y() - 50)); Children.Insert(new LButton(IDOK, r.X() - 140, r.Y() - 30, 60, 20, "Ok")); Children.Insert(new LButton(IDCANCEL, r.X() - 70, r.Y() - 30, 60, 20, "Cancel")); if (d->Lst) { d->Lst->AddColumn("Show", 40); d->Lst->AddColumn("Id", 40); d->Lst->AddColumn("Language", 180); for (int i=0; iApp->GetLanguages()->Length(); i++) { LLanguage *L = (*d->App->GetLanguages())[i]; if (L) { d->Lst->Insert(new Lang(d->App, L, i)); } } d->Lst->Sort(Cmp); d->Lst->Select(*d->Lst->begin()); } - - DoModal(); } ShowLanguagesDlg::~ShowLanguagesDlg() { DeleteObj(d); } int ShowLanguagesDlg::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDOK: { if (d->Lst) { List All; d->Lst->GetAll(All); for (auto L: All) { d->App->ShowLang(L->GetLang()->Id, L->GetVal()); } d->App->OnLanguagesChange(0, 0, true); } } case IDCANCEL: { EndModal(v->GetId() == IDOK); break; } } return false; } diff --git a/ScriptingUnitTests/LgiScript.sln b/ScriptingUnitTests/LgiScript.sln --- a/ScriptingUnitTests/LgiScript.sln +++ b/ScriptingUnitTests/LgiScript.sln @@ -1,46 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2015 -VisualStudioVersion = 12.0.40629.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LgiScript", "LgiScript.vcxproj", "{D21A823F-9A21-43BB-8F2C-FA665D16126B}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lgi", "..\Lgi_vs2015.vcxproj", "{95DF9CA4-6D37-4A85-A648-80C2712E0DA1}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - ReleaseNoOptimize|Win32 = ReleaseNoOptimize|Win32 - ReleaseNoOptimize|x64 = ReleaseNoOptimize|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|Win32.ActiveCfg = Debug|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|Win32.Build.0 = Debug|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|x64.ActiveCfg = Debug|x64 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|x64.Build.0 = Debug|x64 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|Win32.ActiveCfg = Release|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|Win32.Build.0 = Release|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|x64.ActiveCfg = Release|x64 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|x64.Build.0 = Release|x64 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.ReleaseNoOptimize|Win32.ActiveCfg = Release|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.ReleaseNoOptimize|Win32.Build.0 = Release|Win32 - {D21A823F-9A21-43BB-8F2C-FA665D16126B}.ReleaseNoOptimize|x64.ActiveCfg = Release|x64 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|Win32.ActiveCfg = Debug|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|Win32.Build.0 = Debug|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|x64.ActiveCfg = Debug|x64 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|x64.Build.0 = Debug|x64 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|Win32.ActiveCfg = Release|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|Win32.Build.0 = Release|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|x64.ActiveCfg = Release|x64 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|x64.Build.0 = Release|x64 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.ReleaseNoOptimize|Win32.ActiveCfg = ReleaseNoOptimize|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.ReleaseNoOptimize|Win32.Build.0 = ReleaseNoOptimize|Win32 - {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.ReleaseNoOptimize|x64.ActiveCfg = ReleaseNoOptimize|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LgiScript", "LgiScript.vcxproj", "{D21A823F-9A21-43BB-8F2C-FA665D16126B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lgi", "..\Lgi_vs2015.vcxproj", "{95DF9CA4-6D37-4A85-A648-80C2712E0DA1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + ReleaseNoOptimize|x64 = ReleaseNoOptimize|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|x64.ActiveCfg = Debug|x64 + {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Debug|x64.Build.0 = Debug|x64 + {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|x64.ActiveCfg = Release|x64 + {D21A823F-9A21-43BB-8F2C-FA665D16126B}.Release|x64.Build.0 = Release|x64 + {D21A823F-9A21-43BB-8F2C-FA665D16126B}.ReleaseNoOptimize|x64.ActiveCfg = Release|x64 + {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|x64.ActiveCfg = Debug|x64 + {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Debug|x64.Build.0 = Debug|x64 + {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|x64.ActiveCfg = Release|x64 + {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.Release|x64.Build.0 = Release|x64 + {95DF9CA4-6D37-4A85-A648-80C2712E0DA1}.ReleaseNoOptimize|x64.ActiveCfg = ReleaseNoOptimize|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SlogViewer/CMakeLists.txt b/SlogViewer/CMakeLists.txt --- a/SlogViewer/CMakeLists.txt +++ b/SlogViewer/CMakeLists.txt @@ -1,28 +1,28 @@ set(APP_MANIFEST) if (WIN32) set(APP_TYPE WIN32) set(APP_MANIFEST ../src/win/General/Lgi.manifest) elseif (APPLE) set(APP_TYPE MACOSX_BUNDLE) elseif (LINUX) endif() add_executable(SlogViewer ${APP_TYPE} ../src/common/Lgi/DocApp.cpp ../src/common/Lgi/LgiMain.cpp src/SlogViewerMain.cpp ${APP_MANIFEST} ) target_link_libraries(SlogViewer lgi) target_include_directories(SlogViewer PRIVATE - Resources) + resources) if (APPLE) macBundlePostBuild(SlogViewer) endif() set_target_properties(SlogViewer PROPERTIES EXCLUDE_FROM_ALL TRUE) \ No newline at end of file diff --git a/Updater/Main.cpp b/Updater/Main.cpp --- a/Updater/Main.cpp +++ b/Updater/Main.cpp @@ -1,154 +1,155 @@ /* The purpose of this code is to allow various Lgi applications to update their files even when installed in the Program Files tree, where they do not have write access to their own install folder. This util is built with a manifest that requests admin rights, allowing it to write to the target folder. */ #if WINDOWS #include #include #else typedef char TCHAR; #define _T(str) str #endif +#include #include #include "../include/lgi/common/LgiDefs.h" #include "../include/lgi/common/Array.h" const TCHAR *AppName = _T("Updater"); void _lgi_assert(bool b, const char *test, const char *file, int line) { if (!b) assert(!test); } char *NewStr(char *s, size_t len = 0) { if (!s) return NULL; if (len == 0) len = strlen(s); char *n = new char[len]; memcpy(n, s, sizeof(*n)*(len+1)); n[len] = 0; return n; } #if WINDOWS WCHAR *NewStr(WCHAR *s, size_t len = 0) { if (!s) return NULL; if (len == 0) len = wcslen(s); TCHAR *n = new TCHAR[len + 1]; if (n) { memcpy(n, s, sizeof(*n) * len); n[len] = 0; } return n; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { LArray Files; // Parse the command line for the filename(s) to copy.... WCHAR *CmdLine = GetCommandLineW(); const char *WhiteSpace = " \r\t\n"; for (WCHAR *s = CmdLine; s && *s; ) { if (strchr(WhiteSpace, *s)) { s++; } else if (strchr("\'\"", *s)) { int delim = *s++; WCHAR *e = s; while (*e && *e != delim) e++; Files.Add(NewStr(s, e-s)); s = *e ? e + 1 : e; } else { WCHAR *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; Files.Add(NewStr(s, e-s)); s = e; } } // Get the location of the executable... WCHAR Exe[MAX_PATH]; if (GetModuleFileNameW(NULL, Exe, sizeof(Exe)) <= 0) { WCHAR Msg[256]; swprintf_s(Msg, CountOf(Msg), L"GetModuleFileNameW failed with the error 0x%x", GetLastError()); MessageBox(NULL, Msg, AppName, MB_OK); return -1; } // Chop off the filename... WCHAR *e = wcsrchr(Exe, '\\'); if (!e) { MessageBox(NULL, _T("No directory char in the exe file name?"), AppName, MB_OK); return -1; } *e++; *e = 0; // Copy the files to the same location as the exe... int Copied = 0; for (unsigned i=1; iGetProcessId()) /// Returns a pointer to the LApp object. /// /// \warning Don't use this before you have created your LApp object. i.e. in a constructor /// of a global static class which is initialized before the main begins executing. #define LAppInst (LApp::ObjInstance()) /// Process any pending messages in the applications message que and then return. #define LYield() LAppInst->Yield() /// Returns a system font pointer. /// /// \warning Don't use this before you have created your LApp object. i.e. in a constructor /// of a global static class which is initialized before the main begins executing. #define LSysFont (LAppInst->SystemNormal) /// Returns a bold system font pointer. /// /// \warning Don't use this before you have created your LApp object. i.e. in a constructor /// of a global static class which is initialized before the main begins executing. #define LSysBold (LAppInst->SystemBold) /// Exits the application right now! /// /// \warning This will cause data loss if you have any unsaved data. Equivilant to exit(0). LgiFunc void LExitApp(); /// Closes the application gracefully. /// /// This actually causes LApp::Run() to stop processing message and return. #define LCloseApp() LAppInst->Exit(false) #if defined(LINUX) && !defined(LGI_SDL) #define ThreadCheck() LAssert(InThread()) #else #define ThreadCheck() #endif /// Optional arguments to the LApp object struct LAppArguments { /// Don't initialize the skinning engine. bool NoSkin = false; /// Don't do crash handling bool NoCrashHandler = false; LAppArguments(const char *init = NULL) { auto a = LString(init).SplitDelimit(","); for (auto o: a) if (o.Equals("NoCrashHandler")) NoCrashHandler = true; else if (o.Equals("NoSkin")) NoSkin = true; } }; class LAppPrivate; #if LGI_COCOA && defined __OBJC__ #include "LCocoaView.h" @interface LNsApplication : NSApplication { } @property LAppPrivate *d; - (id)init; - (void)setPriv:(nonnull LAppPrivate*)priv; - (void)terminate:(nullable id)sender; - (void)dealloc; - (void)assert:(nonnull LCocoaAssert*)ca; - (void)onUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply; @end ObjCWrapper(LNsApplication, OsApp) #endif /// \brief Singleton class for handling application wide settings and methods /// /// This should be the first class you create, passing in the arguments from the /// operating system. And once your initialization is complete the 'Run' method /// is called to enter the main application loop that processes messages for the /// life time of the application. class LgiClass LApp : virtual public LAppI, public LBase, public OsApplication { friend class LView; friend class LWindow; public: typedef LHashTbl, LWindowsClass*> ClassContainer; protected: // private member vars class LAppPrivate *d = NULL; #if defined LGI_SDL void OnSDLEvent(LMessage *m); #elif defined LGI_COCOA LAutoPtr Default; #elif defined WIN32 CRITICAL_SECTION StackTraceSync; friend LONG __stdcall _ExceptionFilter_Redir(LPEXCEPTION_POINTERS e); LONG __stdcall _ExceptionFilter(LPEXCEPTION_POINTERS e, char *ProductId); friend class LWindowsClass; ClassContainer *GetClasses(); #elif defined LINUX friend class LClipBoard; // virtual void OnEvents(); void DeleteMeLater(LViewI *v); void SetClipBoardContent(OsView Hnd, LVariant &v); bool GetClipBoardContent(OsView Hnd, LVariant &v, LArray &Types); #endif friend class LMouseHook; static LMouseHook *MouseHook; public: // Static publics #ifdef LINUX constexpr static const char *CfgLinuxKeysShift = "Linux.Keys.Shift"; constexpr static const char *CfgLinuxKeysCtrl = "Linux.Keys.Ctrl"; constexpr static const char *CfgLinuxKeysAlt = "Linux.Keys.Alt"; constexpr static const char *CfgLinuxKeysSystem = "Linux.Keys.System"; constexpr static const char *CfgLinuxMouseLeft = "Linux.Mouse.Left"; constexpr static const char *CfgLinuxMouseMiddle = "Linux.Mouse.Middle"; constexpr static const char *CfgLinuxMouseRight = "Linux.Mouse.Right"; constexpr static const char *CfgLinuxMouseBack = "Linux.Mouse.Back"; constexpr static const char *CfgLinuxMouseForward = "Linux.Mouse.Forward"; #endif constexpr static const char *CfgFontsGlyphSub = "Fonts.GlyphSub"; constexpr static const char *CfgFontsPointSizeOffset = "Fonts.PointSizeOffset"; constexpr static const char *CfgFontsSystemFont = "Fonts.SystemFont"; constexpr static const char *CfgFontsBoldFont = "Fonts.BoldFont"; constexpr static const char *CfgFontsMonoFont = "Fonts.MonoFont"; constexpr static const char *CfgFontsSmallFont = "Fonts.SmallFont"; constexpr static const char *CfgFontsCaptionFont = "Fonts.CaptionFont"; constexpr static const char *CfgFontsMenuFont = "Fonts.MenuFont"; /// Use 'LAppInst' to return a pointer to the LApp object static LApp *ObjInstance(); static class LSkinEngine *SkinEngine; // public member vars /// The system font LFont *SystemNormal = NULL; /// The system font in bold LFont *SystemBold = NULL; /// Pointer to the applications main window LWindow *AppWnd = NULL; /// Returns true if the LApp object initialized correctly bool IsOk(); /// Returns this processes ID OsProcessId GetProcessId(); /// Returns the thread currently running the active message loop OsThread _GetGuiThread(); OsThreadId GetGuiThreadId(); bool InThread(); /// Returns the number of CPU cores the machine has int GetCpuCount(); /// Construct the object LApp ( /// The arguments passed in by the OS. OsAppArguments &AppArgs, /// The application's name. const char *AppName, /// Optional args LAppArguments *ObjArgs = 0 ); /// Destroys the object virtual ~LApp(); /// Resets the arguments void SetAppArgs(OsAppArguments &AppArgs); /// Returns the arguemnts OsAppArguments *GetAppArgs(); /// Returns the n'th argument as a heap string. Free with DeleteArray(...). const char *GetArgumentAt(int n); /// Enters the message loop. bool Run ( /// Idle callback OnIdleProc IdleCallback = NULL, /// Param for IdleCallback void *IdleParam = NULL ); /// Processes any messages in the queue and then returns. [[deprecated]] bool Yield(); /// Event called to process the command line void OnCommandLine(); /// Event called to process files dropped on the application void OnReceiveFiles(LArray &Files); /// Event called to process URLs given to the application void OnUrl(const char *Url); /// Exits the event loop with the code specified void Exit ( /// The application exit code. int Code = 0 ); /// \brief Parses the command line for a switch /// \return true if the option exists. bool GetOption ( /// The option to look for. const char *Option, /// String to receive the value (if any) of the option LString &Value ); /// \brief Parses the command line for a switch /// \return true if the option exists. 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 ); /// Return the path to the Lgi config file... (not the same as the application options, more global Lgi apps settings) ::LString GetConfigPath(); /// Gets the application conf stored in lgi.conf ::LString GetConfig(const char *Variable); /// Sets a single tag in the config. (Not written to disk) void SetConfig(const char *Variable, const char *Value); /// Gets the control with the keyboard focus LViewI *GetFocus(); /// Gets the MIME type of a file /// \returns the mime type or NULL if unknown. LString GetFileMimeType ( /// The file to identify const char *File ); /// Gets the applications that can handle a file of a certain mime type - bool GetAppsForMimeType(char *Mime, LArray &Apps); + bool GetAppsForMimeType(const char *Mime, LArray &Apps); /// Get a system metric int32 GetMetric ( /// One of #LGI_MET_DECOR_X, #LGI_MET_DECOR_Y LSystemMetric Metric ); /// Get the mouse hook instance LMouseHook *GetMouseHook(); /// Gets the singleton symbol lookup class class LSymLookup *GetSymLookup(); /// \returns true if the process is running with elevated permissions bool IsElevated(); /// Gets the font cache class LFontCache *GetFontCache(); // OS Specific #if defined(LGI_SDL) /// This keeps track of the dirty rectangle and issues a M_INVALIDATE /// event when needed to keep the screen up to date. bool InvalidateRect(LRect &r); /// Push a new window to the top of the window stack bool PushWindow(LWindow *w); /// Remove the top most window LWindow *PopWindow(); /// Sets up mouse tracking beyond the current window... void CaptureMouse(bool capture); /// Returns the freetype version as a string. LString GetFreetypeVersion(); #elif defined(WIN32) HINSTANCE GetInstance(); int GetShow(); /// \returns true if the application is running under Wine on Linux. This is useful to know /// if you need to work around missing functionality in the Wine implementation. bool IsWine(); #elif defined(LINUX) class LLibrary *GetWindowManagerLib(); class DesktopInfo { friend class LApp; LString File; bool Dirty; struct KeyPair { LString Key, Value; }; struct Section { LString Name; LArray Values; KeyPair *Get(const char *Name, bool Create, bool &Dirty) { for (unsigned i=0; iKey.Equals(Name)) return kp; } if (Create) { KeyPair *kp = &Values.New(); kp->Key = Name; Dirty = true; return kp; } return NULL; } }; LArray
Data; bool Serialize(bool Write); Section *GetSection(const char *Name, bool Create); public: DesktopInfo(const char *file); LString Get(const char *Field, const char *Section = NULL); bool Set(const char *Field, const char *Value, const char *Section = NULL); bool Update() { return Dirty ? Serialize(true) : true; } const char *GetFile() { return File; } }; DesktopInfo *GetDesktopInfo(); bool SetApplicationIcon(const char *FileName); #elif LGI_COCOA && defined(__OBJC__) OsApp &Handle(); #endif #ifdef __GTK_H__ struct KeyModFlags { int Shift, Alt, Ctrl, System; bool Debug; KeyModFlags() { Shift = 0; Alt = 0; Ctrl = 0; System = 0; Debug = false; } const char *FlagName(int Flag); // Single flag to string int FlagValue(const char *Name); // Single name to bitmask ::LString FlagsToString(int Flags); // Turn multiple flags to string }; KeyModFlags *GetKeyModFlags(); void OnDetach(LViewI *View); #endif #if !LGI_VIEW_HANDLE bool PostEvent(LViewI *View, int Msg, LMessage::Param a = 0, LMessage::Param b = 0); #endif }; #endif diff --git a/include/lgi/common/Containers.h b/include/lgi/common/Containers.h --- a/include/lgi/common/Containers.h +++ b/include/lgi/common/Containers.h @@ -1,1148 +1,1148 @@ /** \file \author Matthew Allen \date 27/11/1996 \brief Container class header.\n Copyright (C) 1996-2003, Matthew Allen */ #ifndef _CONTAIN_H_ #define _CONTAIN_H_ #include #include "lgi/common/LgiInc.h" #include "LgiOsDefs.h" #include "lgi/common/Stream.h" #include "lgi/common/Profile.h" #include /// Template for using DLinkList with a type safe API. #define ITEM_PTRS 64 #define LGI_LIST_VALIDATION 0 LgiFunc bool UnitTest_ListClass(); #ifdef _DEBUG #define VALIDATE() Validate() #else #define VALIDATE() #endif template class List { struct LstBlk { LstBlk *Next, *Prev; uint8_t Count; T *Ptr[ITEM_PTRS]; LstBlk() { Next = Prev = NULL; Count = 0; ZeroObj(Ptr); } bool Full() { return Count >= ITEM_PTRS; } int Remaining() { return ITEM_PTRS - Count; } }; public: class Iter { public: - List *Lst; + const List *Lst; LstBlk *i; int Cur, Ver; OsThreadId Thread; bool CheckThread() const { #if 1 return true; #else auto Cur = GetCurrentThreadId(); bool Ok = Thread == Cur; LAssert(Ok); return Ok; #endif } #ifdef _DEBUG #define CHECK_THREAD CheckThread(); #else #define CHECK_THREAD #endif - Iter(List *lst) + Iter(const List *lst) { Lst = lst; i = 0; Cur = 0; Ver = lst->Ver; Thread = GetCurrentThreadId(); } - Iter(List *lst, LstBlk *item, int c) + Iter(const List *lst, LstBlk *item, int c) { Lst = lst; i = item; Cur = c; Ver = lst->Ver; Thread = GetCurrentThreadId(); } bool operator ==(const Iter &it) const { CHECK_THREAD int x = (int)In() + (int)it.In(); if (x == 2) return (i == it.i) && (Cur == it.Cur); return x == 0; } bool operator !=(const Iter &it) const { CHECK_THREAD return !(*this == it); } bool operator <(const Iter &it) const { CHECK_THREAD return Cur < it.Cur; } bool operator <=(const Iter &it) const { CHECK_THREAD return Cur <= it.Cur; } bool operator >(const Iter &it) const { CHECK_THREAD return Cur > it.Cur; } bool operator >=(const Iter &it) const { CHECK_THREAD return Cur >= it.Cur; } bool In() const { CHECK_THREAD if (Ver != Lst->Ver) { if (!Lst->ValidBlock(i)) return false; } return i && Cur >= 0 && Cur < i->Count; } operator T*() const { CHECK_THREAD return In() ? i->Ptr[Cur] : NULL; } T *operator *() const { CHECK_THREAD return In() ? i->Ptr[Cur] : NULL; } Iter &operator =(LstBlk *item) { CHECK_THREAD i = item; if (!i) Cur = 0; return *this; } Iter &operator =(int c) { CHECK_THREAD Cur = c; return *this; } Iter &operator =(Iter *iter) { CHECK_THREAD Lst = iter->Lst; i = iter->i; Cur = iter->Cur; return *this; } int GetIndex(int Base) { CHECK_THREAD if (i) return Base + Cur; return -1; } bool Next() { CHECK_THREAD if (i) { if (!In()) return false; Cur++; if (Cur >= i->Count) { i = i->Next; if (i) { Cur = 0; return i->Count > 0; } } else return true; } return false; } bool Prev() { CHECK_THREAD if (i) { if (!In()) return false; Cur--; if (Cur < 0) { i = i->Prev; if (i && i->Count > 0) { Cur = i->Count - 1; return true; } } else return true; } return false; } bool Delete() { CHECK_THREAD if (i) { LAssert(Lst); i->Delete(Cur, i); return true; } return false; } Iter &operator ++() { Next(); return *this; } Iter &operator --() { Prev(); return *this; } Iter &operator ++(int) { Next(); return *this; } Iter &operator --(int) { Prev(); return *this; } }; typedef Iter I; // typedef int (*CompareFn)(T *a, T *b, NativeInt data); protected: size_t Items; int Ver; LstBlk *FirstObj, *LastObj; - bool ValidBlock(LstBlk *b) + bool ValidBlock(LstBlk *b) const { for (LstBlk *i = FirstObj; i; i = i->Next) if (i == b) return true; return false; } LstBlk *NewBlock(LstBlk *Where) { LstBlk *i = new LstBlk; LAssert(i != NULL); if (!i) return NULL; if (Where) { i->Prev = Where; if (i->Prev->Next) { // Insert i->Next = Where->Next; i->Prev->Next = i->Next->Prev = i; } else { // Append i->Prev->Next = i; LAssert(LastObj == Where); LastObj = i; } } else { // First object LAssert(FirstObj == 0); LAssert(LastObj == 0); FirstObj = LastObj = i; } return i; } bool DeleteBlock(LstBlk *i) { if (!i) { LAssert(!"No ptr."); return false; } if (i->Prev != 0 && i->Next != 0) { LAssert(FirstObj != i); LAssert(LastObj != i); } if (i->Prev) { i->Prev->Next = i->Next; } else { LAssert(FirstObj == i); FirstObj = i->Next; } if (i->Next) { i->Next->Prev = i->Prev; } else { LAssert(LastObj == i); LastObj = i->Prev; } delete i; Ver++; // This forces the iterators to recheck their block ptr return true; } bool Insert(LstBlk *i, T *p, ssize_t Index = -1) { if (!i) return false; if (i->Full()) { if (!i->Next) { // Append a new LstBlk if (!NewBlock(i)) return false; } if (Index < 0) return Insert(i->Next, p, Index); // Push last pointer into Next if (i->Next->Full()) NewBlock(i); // Create an empty Next if (!Insert(i->Next, i->Ptr[ITEM_PTRS-1], 0)) return false; i->Count--; Items--; // We moved the item... not inserted it. // Fall through to the local "non-full" insert... } LAssert(!i->Full()); if (Index < 0) Index = i->Count; else if (Index < i->Count) memmove(i->Ptr+Index+1, i->Ptr+Index, (i->Count-Index) * sizeof(p)); i->Ptr[Index] = p; i->Count++; Items++; LAssert(i->Count <= ITEM_PTRS); return true; } - Iter GetIndex(size_t Index, size_t *Base = NULL) + Iter GetIndex(size_t Index, size_t *Base = NULL) const { size_t n = 0; for (LstBlk *i = FirstObj; i; i = i->Next) { if (Index >= n && Index < n + i->Count) { if (Base) *Base = n; return Iter(this, i, (int) (Index - n)); } n += i->Count; } if (Base) *Base = 0; return Iter(this); } Iter GetPtr(T *Ptr, size_t *Base = NULL) { size_t n = 0; for (LstBlk *i = FirstObj; i; i = i->Next) { for (int k=0; kCount; k++) { if (i->Ptr[k] == Ptr) { if (Base) *Base = n; return Iter(this, i, k); } } n += i->Count; } if (Base) *Base = 0; return Iter(this); } public: List() { FirstObj = LastObj = NULL; Items = 0; Ver = 0; } ~List() { VALIDATE(); Empty(); } size_t Length() const { return Items; } bool Length(size_t Len) { if (Len == 0) return Empty(); else if (Len == Items) return true; VALIDATE(); bool Status = false; if (Len < Items) { // Decrease list size... size_t Base = 0; Iter i = GetIndex(Len, &Base); if (i.i) { size_t Offset = Len - Base; if (!(Offset <= i.i->Count)) { LAssert(!"Offset error"); } i.i->Count = (uint8_t) (Len - Base); LAssert(i.i->Count >= 0 && i.i->Count < ITEM_PTRS); while (i.i->Next) { DeleteBlock(i.i->Next); } Items = Len; } else LAssert(!"Iterator invalid."); } else { // Increase list size... LAssert(!"Impl me."); } VALIDATE(); return Status; } bool Empty() { VALIDATE(); LstBlk *n; for (LstBlk *i = FirstObj; i; i = n) { n = i->Next; delete i; } FirstObj = LastObj = NULL; Items = 0; Ver++; VALIDATE(); return true; } bool DeleteAt(size_t i) { VALIDATE(); Iter p = GetIndex(i); if (!p.In()) return false; bool Status = Delete(p); VALIDATE(); return Status; } bool Delete(Iter &Pos) { if (!Pos.In()) return false; int &Index = Pos.Cur; LstBlk *&i = Pos.i; if (Index < i->Count-1) memmove(i->Ptr+Index, i->Ptr+Index+1, (i->Count-Index-1) * sizeof(T*)); Items--; if (--i->Count == 0) { // This Item is now empty, remove and reset current // into the next Item LstBlk *n = i->Next; bool Status = DeleteBlock(i); Pos.Cur = 0; Pos.i = n; return Status; } else if (Index >= i->Count) { // Carry current item over to next Item Pos.i = Pos.i->Next; Pos.Cur = 0; } return true; } bool Delete(T *Ptr) { VALIDATE(); Iter It = GetPtr(Ptr); if (!It.In()) return false; bool Status = Delete(It); VALIDATE(); return Status; } bool Insert(T *p, ssize_t Index = -1) { VALIDATE(); if (!LastObj) { LstBlk *b = NewBlock(NULL); if (!b) return false; b->Ptr[b->Count++] = p; Items++; VALIDATE(); return true; } bool Status; size_t Base; Iter Pos(this); if (Index < 0) Status = Insert(LastObj, p, Index); else { Pos = GetIndex(Index, &Base); if (Pos.i) Status = Insert(Pos.i, p, (int) (Index - Base)); else Status = Insert(LastObj, p, -1); } VALIDATE(); LAssert(Status); return Status; } bool Add(T *p) { return Insert(p); } - T *operator [](size_t Index) + T *operator [](size_t Index) const { VALIDATE(); auto it = GetIndex(Index); VALIDATE(); return it; } ssize_t IndexOf(T *p) { VALIDATE(); size_t Base = -1; auto It = GetPtr(p, &Base); LAssert(Base != -1); ssize_t Idx = It.In() ? Base + It.Cur : -1; VALIDATE(); return Idx; } bool HasItem(T *p) { VALIDATE(); Iter Pos = GetPtr(p); bool Status = Pos.In(); VALIDATE(); return Status; } T *ItemAt(ssize_t i) { VALIDATE(); Iter It = GetIndex(i); VALIDATE(); return It; } /// Sorts the list template void Sort ( /// The callback function used to compare 2 pointers int (*Compare)(T *a, T *b, User data), /// User data that is passed into the callback User Data = 0 ) { if (Items < 1) return; VALIDATE(); // LProfile prof("ListSort"); // Save the List to an Array LArray a; a.Length(Length()); T **p = a.AddressOf(); for (LstBlk *i = FirstObj; i; i = i->Next) for (int n=0; nCount; n++) *p++ = i->Ptr[n]; // prof.Add("Compare"); struct UserData { int (*Compare)(T *a, T *b, User data); User Data; } ud = {Compare, Data}; #if !defined(WINDOWS) && !defined(HAIKU) && !defined(LINUX) #define USER_DATA_FIRST 1 #else #define USER_DATA_FIRST 0 #endif #if defined(WINDOWS) /* _ACRTIMP void __cdecl qsort_s(void* _Base, rsize_t _NumOfElements, rsize_t _SizeOfElements, int (__cdecl* _PtFuncCompare)(void*, void const*, void const*), void* _Context); */ qsort_s #else qsort_r #endif ( a.AddressOf(), a.Length(), sizeof(T*), #if USER_DATA_FIRST &ud, #endif #if defined(HAIKU) || defined(LINUX) // typedef int (*_compare_function_qsort_r)(const void*, const void*, void*); // extern void qsort_r(void* base, size_t numElements, size_t sizeOfElement, _compare_function_qsort_r, void* cookie); [](const void *a, const void *b, void *ud) -> int #else [](void *ud, const void *a, const void *b) -> int #endif { auto *user = (UserData*)ud; return user->Compare(*(T**)a, *(T**)b, user->Data); } #if !USER_DATA_FIRST , &ud #endif ); // prof.Add("Copy"); // Copy back to the List p = a.AddressOf(); for (LstBlk *i = FirstObj; i; i = i->Next) for (int n=0; nCount; n++) i->Ptr[n] = *p++; VALIDATE(); } /// Delete all pointers in the list as dynamically allocated objects void DeleteObjects() { VALIDATE(); LstBlk *n; for (LstBlk *i = FirstObj; i; i = n) { n = i->Next; for (int n=0; nCount; n++) { if (i->Ptr[n]) { #ifdef _DEBUG size_t Objs = Items; #endif delete i->Ptr[n]; #ifdef _DEBUG if (Objs != Items) LAssert(!"Do you have self deleting objects?"); #endif i->Ptr[n] = NULL; } } delete i; } FirstObj = LastObj = NULL; Items = 0; Ver++; VALIDATE(); } /// Delete all pointers in the list as dynamically allocated arrays void DeleteArrays() { VALIDATE(); LstBlk *n; for (LstBlk *i = FirstObj; i; i = n) { n = i->Next; for (int n=0; nCount; n++) { delete [] i->Ptr[n]; i->Ptr[n] = NULL; } delete i; } FirstObj = LastObj = NULL; Items = 0; Ver++; VALIDATE(); } void Swap(List &other) { LSwap(FirstObj, other.FirstObj); LSwap(LastObj, other.LastObj); LSwap(Items, other.Items); LSwap(Ver, other.Ver); } /// Assign the contents of another list to this one #if 0 List &operator=(const List &lst) { Empty(); for (auto i : lst) Add(i); return *this; } #else List &operator =(const List &lst) { VALIDATE(); // Make sure we have enough blocks allocated size_t i = 0; // Set the existing blocks to empty... for (LstBlk *out = FirstObj; out; out = out->Next) { out->Count = 0; i += ITEM_PTRS; } // If we don't have enough, add more... while (i < lst.Length()) { LstBlk *out = NewBlock(LastObj); if (out) i += ITEM_PTRS; else { LAssert(!"Can't allocate enough blocks?"); return *this; } } // If we have too many, free some... while (LastObj && i > lst.Length() + ITEM_PTRS) { DeleteBlock(LastObj); i -= ITEM_PTRS; } // Now copy over the block's contents. LstBlk *out = FirstObj; Items = 0; for (LstBlk *in = lst.FirstObj; in; in = in->Next) { for (int pos = 0; pos < in->Count; ) { if (!out->Remaining()) { out = out->Next; if (!out) { LAssert(!"We should have pre-allocated everything..."); return *this; } } int Cp = MIN(out->Remaining(), in->Count - pos); LAssert(Cp > 0); memcpy(out->Ptr + out->Count, in->Ptr + pos, Cp * sizeof(T*)); out->Count += Cp; pos += Cp; Items += Cp; } } VALIDATE(); return *this; } #endif Iter begin(ssize_t At = 0) { return GetIndex(At); } Iter rbegin(ssize_t At = 0) { return GetIndex(Length()-1); } Iter end() { return Iter(this, NULL, -1); } bool Validate() const { if (FirstObj == NULL && LastObj == NULL && Items == 0) return true; #if LGI_LIST_VALIDATION size_t n = 0; LstBlk *Prev = NULL; for (LstBlk *i = FirstObj; i; i = i->Next) { for (int k=0; kCount; k++) { if (!i->Ptr[k]) { LAssert(!"NULL pointer in LstBlk."); return false; } else { n++; } } if (i == FirstObj) { if (i->Prev) { LAssert(!"First object's 'Prev' should be NULL."); return false; } } else if (i == LastObj) { if (i->Next) { LAssert(!"Last object's 'Next' should be NULL."); return false; } } else { if (i->Prev != Prev) { LAssert(!"Middle LstBlk 'Prev' incorrect."); return false; } } Prev = i; } if (Items != n) { LAssert(!"Item count cache incorrect."); return false; } #endif return true; } }; /// \brief Data storage class. /// /// Allows data to be buffered in separate memory /// blocks and then written to a continuous block for processing. Works /// as a first in first out que. You can (and should) provide a suitable /// PreAlloc size to the constructor. This can reduce the number of blocks /// of memory being used (and their associated alloc/free time and /// tracking overhead) in high volume situations. class LgiClass LMemQueue : public LStream { protected: /// Data block. These can contain a mix of 3 types of data: /// 1) Bytes that have been read (always at the start of the block) /// 2) Bytes that have been written (always in the middle) /// 3) Unwritten buffer space (always at the end) /// /// Initially a block starts out as completely type 3 bytes, just garbage /// data waiting to be written to. Then something writes into the pipe and bytes /// are stored into this free space, and the 'Used' variable is set to show /// how much of the available buffer is used. At this point we have type 2 and /// type 3 bytes in the block. The buffer can fill up completely in which /// case Used == Size and there are no type 3 bytes left. Also at some point /// something can start reading bytes out of the block which causes the 'Next' /// value to be increased, at which point the block starts with 'Next' bytes /// of type 1. Crystal? struct Block { /// Number of bytes in this block that have been read /// Type 1 or 'read' bytes are in [0,Next-1]. int Next; /// Number of bytes that are used in this block include read bytes. /// Type 2 or 'used' bytes are in [Next,Used-1]. int Used; /// Total size of the memory block /// Type 3 or 'unused' bytes are in [Used,Size-1]. int Size; uint8_t *Ptr() { return (uint8_t*) (this + 1); } Block() { Next = 0; Size = 0; Used = 0; } }; ssize_t PreAlloc; List Mem; public: /// Constructor LMemQueue ( /// Sets the block size, which means allocating ahead and then joining /// together smaller inserts into 1 continuous block. size_t PreAlloc = 0 ); /// Destructor virtual ~LMemQueue(); LMemQueue &operator =(LMemQueue &p); /// Empties the container freeing any memory used. virtual void Empty(); /// Returns a dynamically allocated block that contains all the data in the container /// in a continuous block. virtual void *New ( /// If this is > 0 then the function add's the specified number of bytes containing /// the value 0 on the end of the block. This is useful for NULL terminating strings /// or adding buffer space on the end of the block returned. int AddBytes = 0 ); /// Reads data from the start of the container without removing it from /// the que. Returns the bytes copied. virtual int64 Peek ( /// Buffer for output uchar *Ptr, /// Bytes to look at int Size ); /// Gets the total bytes in the container int64 GetSize() override; /// Reads bytes off the start of the container ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) override; /// Writes bytes to the end of the container ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override; bool Write(const LString &s) { return Write(s.Get(), s.Length()) == s.Length(); } /// Search for a substring and return it's index, or -1 if not found. ssize_t Find(LString str, bool caseSensitive = true); /// Iterate over the data in the container /// Callback should return true to keep iterating... void Iterate(std::function callback, bool reverse = false); /// For processes that read from one source and then write into this container, it is better /// to not have to copy the data into some local buffer and then again into the mem queue. /// So this allows allocating internal blocks and returning them directly to the writer. After /// the source has written into the internal block the client calls Buffer::Commit to tell the /// LMemQueue how much data has been written. class LgiClass Buffer { friend class LMemQueue; LMemQueue *mq = NULL; Block *blk = NULL; public: uint8_t *ptr = NULL; size_t len = 0; Buffer(LMemQueue *memq) : mq(memq) {} // Call this after writing data into the 'ptr' buffer. bool Commit(size_t bytes); }; /// Find a buffer and return it. Call Buffer::Commit after writting data to the start of the block. /// On error Buffer.ptr is NULL. Buffer GetBuffer(); }; /// A version of GBytePipe for strings. Adds some special handling for strings. class LgiClass LStringPipe : public LMemQueue { ssize_t LineChars(); ssize_t SaveToBuffer(char *Str, ssize_t BufSize, ssize_t Chars); public: /// Constructs the object LStringPipe ( /// Number of bytes to allocate per block. int PreAlloc = -1 ) : LMemQueue(PreAlloc) {} ~LStringPipe() {} virtual ssize_t Pop(char *Str, ssize_t Chars); virtual ssize_t Pop(LArray &Buf); virtual LString Pop(); virtual ssize_t Push(const char *Str, ssize_t Chars = -1); virtual ssize_t Push(const char16 *Str, ssize_t Chars = -1); char *NewStr() { return (char*)New(sizeof(char)); } LString NewGStr(); char16 *NewStrW() { return (char16*)New(sizeof(char16)); } LStringPipe &operator +=(const LString &s) { Write(s.Get(), s.Length()); return *this; } #ifdef _DEBUG static bool UnitTest(); #endif }; #define GMEMFILE_BLOCKS 8 class LgiClass GMemFile : public LStream { struct Block { size_t Offset; size_t Used; uint8_t Data[1]; }; // The current file pointer uint64 CurPos; /// Number of blocks in use int Blocks; /// Data payload of blocks int BlockSize; /// Some blocks are built into the struct, no memory alloc needed. Block *Local[GMEMFILE_BLOCKS]; // Buf if they run out we can alloc some more. LArray Extra; Block *Get(int Index); bool FreeBlock(Block *b); Block *GetLast() { return Get(Blocks-1); } Block *Create(); int CurBlock() { return (int) (CurPos / BlockSize); } public: GMemFile(int BlkSize = 256); ~GMemFile(); void Empty(); int64 GetSize() override; int64 SetSize(int64 Size) override; int64 GetPos() override; int64 SetPos(int64 Pos) override; ssize_t Read(void *Ptr, ssize_t Size, int Flags = 0) override; ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) override; }; #endif diff --git a/include/lgi/common/Dialog.h b/include/lgi/common/Dialog.h --- a/include/lgi/common/Dialog.h +++ b/include/lgi/common/Dialog.h @@ -1,227 +1,233 @@ #pragma once +#include + #include "lgi/common/Window.h" #include "lgi/common/LgiRes.h" /// \brief A top level dialog window. /// /// LDialog's can either be modal or modeless. A modal dialog blocks the user from accessing /// the parent LWindow until the dialog has been dismissed. A modeless dialog behaves like /// any other top level window in that it doesn't block the user accessing any other top level /// window while it's open. /// /// LDialog's can be created in two different ways. Firstly you can create code to instantiate /// all the various controls and add them manually to the LView::Children list. Or you can load /// all the controls from a resource file. The resource files are in XML format and there is a /// graphical tool LgiRes. /// /// Manual example: /// \code /// #define IDC_STRING 100 /// /// class Example : public LDialog /// { /// public: /// char *Str; /// /// Example(LView *p) /// { /// Str = 0; /// SetParent(p); /// LRect r(0, 0, 400, 300); /// SetPos(p); /// MoveToCenter(); /// /// Children.Insert(new LEdit(IDC_STRING, 10, 10, 200, 20, "")); /// Children.Insert(new LButton(IDOK, 10, 30, 80, 20, "Ok")); /// Children.Insert(new LButton(IDCANCEL, 100, 30, 80, 20, "Cancel")); /// } /// /// ~Example() /// { /// DeleteArray(Str); /// } /// /// int OnNotify(LViewI *c, int Flags) /// { /// switch (c->GetId()) /// { /// case IDOK: /// { /// Str = NewStr(GetCtrlName(IDC_STRING)); /// // fall thru /// } /// case IDCANCEL: /// { /// EndModal(c->GetId()); /// break; /// } /// } /// /// return 0; /// } /// }; /// \endcode /// /// This example shows how to insert child widgets into the window and then process clicks on the buttons /// and finally return data to the caller via a public member variable. It is desirable to pass data using /// this method because a dialog could be running in it's own thread on some systems and therefor should not /// be accessing data structures outside of itself directly without locking them. If your main application' /// doesn't have thread locking in place for it's main data structures then accessing them from the dialog /// wouldn't be thread safe. This is mainly the case with the BeOS port of Lgi, but is good practise for /// the Windows and Linux ports for cross platform compatibility. /// /// The resource file method of creating dialogs is arguably the easier route to take once your've got it /// set up. Firstly create a resource file using LgiRes with the same name as your programs executable, but /// a different extension ("lr8"). When you call the LResourceLoad::LoadFromResource function Lgi will automatically /// look for a file named after the running executable with the right extension and load it. Then it will /// find the dialog resource and instantiate all the controls specified in the resource. All the built in /// Lgi controls are supported directly as tags in the XML file and you can create your own custom controls /// that the resource loader can instantiate as well using the LViewFactory system. /// /// Resource file example: /// \code /// #include "Resdefs.h" // For the dialog defines /// /// class Example : public LDialog /// { /// public: /// char *Str; /// /// Example(LView *p) /// { /// Str = 0; /// SetParent(p); /// if (LoadFromResource(IDD_EXAMPLE)) /// { /// MoveToCenter(); /// } /// } /// /// ~Example() /// { /// DeleteArray(Str); /// } /// /// int OnNotify(LViewI *c, int Flags) /// { /// switch (c->GetId()) /// { /// case IDOK: /// { /// Str = NewStr(GetCtrlName(1)); /// // fall thru /// } /// case IDCANCEL: /// { /// EndModal(c->GetId()); /// break; /// } /// } /// /// return 0; /// } /// }; /// \endcode /// /// This assumes you have created an lr8 file with the resource named 'IDD_EXAMPLE' in it. /// /// Now to actually call either of these dialogs you would use code like this: /// \code /// Example Dlg(MyWindow); /// if (Dlg.DoModal() == IDOK) /// { /// // Do something with Dlg.Str /// } /// \endcode /// /// The built in controls that you can use are: ///
    ///
  • LButton (Push button) ///
  • LEdit (Edit box for text entry) ///
  • GText (Static label) ///
  • LCheckBox (Independent boolean) ///
  • LCombo (Select one from many) ///
  • LSlider (Select a position) ///
  • LBitmap (Display an image) ///
  • LProgressView (Show a progress) ///
  • GList (List containing LListItem) ///
  • LTree (Hierarchy of LTreeItem) ///
  • LRadioGroup (One of many selection using LRadioButton) ///
  • LTabView (Containing LTabPage) ///
class LgiClass LDialog : public LWindow, public LResourceLoad, public ResObject { friend class LControl; private: struct LDialogPriv *d; public: + typedef std::function OnClose; + /// Constructor - LDialog(); + LDialog(LViewI *Parent = NULL); /// Destructor ~LDialog(); - const char *GetClass() { return "LDialog"; } + const char *GetClass() override { return "LDialog"; } /// Load the dialog from a resource bool LoadFromResource ( /// The resource ID int Resource, /// [Optional] tag list to exclude/include various controls via tag char *TagList = 0 ); /// \brief Run the dialog in modal mode. /// /// The user has to dismiss the dialog to continue. - virtual int DoModal + virtual void DoModal ( + /// Call back to handle post-dialog activity + OnClose Callback, /// Optional override parent window handle OsView ParentHnd = NULL ); /// \brief Run the dialog in modeless mode /// /// It will behave like any other top level window. The user doesn't /// have to dismiss the window to continue, it can just move to the back. virtual int DoModeless(); /// Gets the model status virtual bool IsModal(); /// End a modal window. Typically calling in the OnNotify event of the LDialog. virtual void EndModal(int Code = 0); /// End a modeless window. Typically calling in the OnNotify event of the LDialog. virtual void EndModeless(int Code = 0); LMessage::Result OnEvent(LMessage *Msg); bool OnRequestClose(bool OsClose); void OnPosChange(); void PourAll() {} void Quit(bool DontDelete = false); /// By default the dialog will finish when a button is pressed. To override this /// behavior you'll have to subclass LDialog and handle the OnNotify yourself. int OnNotify(LViewI *Ctrl, LNotification n); /// This returns the ID of the button pressed to close the dialog. int GetButtonId(); #if defined(__GTK_H__) friend Gtk::gboolean GtkDialogDestroy(Gtk::GtkWidget *widget, LDialog *This); bool IsResizeable(); void IsResizeable(bool r); bool SetupDialog(bool Modal); #elif defined(LGI_CARBON) void OnPaint(LSurface *pDC); #endif }; diff --git a/include/lgi/common/DisplayString.h b/include/lgi/common/DisplayString.h --- a/include/lgi/common/DisplayString.h +++ b/include/lgi/common/DisplayString.h @@ -1,303 +1,307 @@ #ifndef _GDISPLAY_STRING_H_ #define _GDISPLAY_STRING_H_ #include "lgi/common/Font.h" #if defined(LGI_SDL) #elif defined(LINUX) namespace Pango { #include "pango/pango.h" #include "pango/pangocairo.h" } #endif #if !defined(_MSC_VER) || defined(__GTK_H__) #define LGI_DSP_STR_CACHE 1 #endif /// \brief Cache for text measuring, glyph substitution and painting /// /// To paint text onto the screen several stages need to be implemented to /// properly support unicode on multiple platforms. This class addresses all /// of those needs and then allows you to cache the results to reduce text /// related workload. /// /// The first stage is converting text into the native format for the /// OS's API. This usually involved converting the text to wide characters for /// Linux or Windows, or Utf-8 for Haiku. Then the text is converted into runs of /// characters that can be rendered in the same font. If glyph substitution is /// required to render the characters a separate run is used with a different /// font ID. Finally you can measure or paint these runs of text. Also tab characters /// are expanded to the current tab size setting. class LgiClass LDisplayString { protected: LSurface *pDC; LFont *Font; uint32_t x, y; int xf, yf; int DrawOffsetF; // Flags uint8_t LaidOut : 1; uint8_t AppendDots : 1; uint8_t VisibleTab : 1; /* String data: Str Wide Windows: utf-16 utf-16 MacCarbon: utf-16 utf-32 MacCocoa: utf-16 utf-32 Gtk3: utf-8 utf-32 + Haiku: utf-8 utf-32 */ OsChar *Str; ssize_t StrWords; #if LGI_DSP_STR_CACHE wchar_t *Wide; ssize_t WideWords; #endif #if defined(LGI_SDL) LAutoPtr Img; #elif defined(__GTK_H__) struct GDisplayStringPriv *d; friend struct GDisplayStringPriv; #elif defined(MAC) #if USE_CORETEXT CTLineRef Hnd; CFAttributedStringRef AttrStr; void CreateAttrStr(); #else ATSUTextLayout Hnd; ATSUTextMeasurement fAscent; ATSUTextMeasurement fDescent; #endif #elif defined(WINNATIVE) || defined(HAIKU) class CharInfo { public: OsChar *Str; int Len; int X; uint8_t FontId; int8 SizeDelta; CharInfo() { Str = 0; Len = 0; X = 0; FontId = 0; SizeDelta = 0; } uint32_t First() { auto s = (const uint16*)Str; ssize_t l = Len * sizeof(*Str); return LgiUtf16To32(s, l); } }; LArray Info; #endif void Layout(bool Debug = false); void DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r); public: + // Turn on debug logging... + bool _debug = false; + /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// Utf-8 input string const char *s, /// Number of bytes in the input string or -1 for NULL terminated. ssize_t l = -1, LSurface *pdc = 0 ); /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// A wide character input string const char16 *s, /// The number of characters in the input string (NOT the number of bytes) or -1 for NULL terminated ssize_t l = -1, LSurface *pdc = 0 ); #ifdef _MSC_VER /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// A wide character input string const uint32_t *s, /// The number of characters in the input string (NOT the number of bytes) or -1 for NULL terminated ssize_t l = -1, LSurface *pdc = 0 ); #endif virtual ~LDisplayString(); LDisplayString &operator=(const LDisplayString &s) { LAssert(0); return *this; } /// \returns the draw offset used to calculate tab spacing (in Px) int GetDrawOffset(); /// Sets the draw offset used to calculate tab spacing (in Px) void SetDrawOffset(int Px); /// Returns the ShowVisibleTab setting. /// Treats Unicode-2192 (left arrow) as a tab char bool ShowVisibleTab(); /// Sets the ShowVisibleTab setting. /// Treats Unicode-2192 (left arrow) as a tab char void ShowVisibleTab(bool i); /// \returns the font used to create the layout LFont *GetFont() { return Font; }; /// Fits string into 'width' pixels, truncating with '...' if it's not going to fit void TruncateWithDots ( /// The number of pixels the string has to fit int Width ); /// Returns true if the string is trucated bool IsTruncated(); /// Returns the words (not chars) in the OsChar string ssize_t Length(); /* /// Sets the number of words in the OsChar string void Length(int NewLen); */ /// Returns the pointer to the native string operator const char16*() { #if LGI_DSP_STR_CACHE return Wide; #else return Str; #endif } /// \returns the number of words in the wide string ssize_t WideLength() { #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif } // API that use full pixel sizes: /// Returns the width of the whole string int X(); /// Returns the height of the whole string int Y(); /// Returns the width and height of the whole string LPoint Size(); /// Returns the number of characters that fit in 'x' pixels. ssize_t CharAt(int x, LPxToIndexType Type = LgiTruncate); /// Draws the string onto a device surface void Draw ( /// The output device LSurface *pDC, /// The x coordinate of the top left corner of the output box int x, /// The y coordinate of the top left corner of the output box int y, /// An optional clipping rectangle. If the font is not transparent this rectangle will be filled with the background colour. LRect *r = NULL, /// Turn on debug logging bool Debug = false ); /// Draws the string onto a device surface void DrawCenter ( /// The output device LSurface *pDC, /// An rectangle to center in. If the font is not transparent this rectangle will be filled with the background colour. LRect *r ) { if (r) Draw(pDC, r->x1 + ((r->X() - X()) >> 1), r->y1 + ((r->Y() - Y()) >> 1), r); } // API that uses fractional pixel sizes. enum { #if defined LGI_SDL FShift = 6, FScale = 1 << FShift, #elif defined __GTK_H__ /// This is the value for 1 pixel. FScale = PANGO_SCALE, /// This is bitwise shift to convert between integer and fractional FShift = 10 #elif defined MAC /// This is the value for 1 pixel. FScale = 0x10000, /// This is bitwise shift to convert between integer and fractional FShift = 16 #else /// This is the value for 1 pixel. FScale = 1, /// This is bitwise shift to convert between integer and fractional FShift = 0 #endif }; /// \returns the draw offset used to calculate tab spacing (in fractional units) int GetDrawOffsetF(); /// Sets the draw offset used to calculate tab spacing (in fractional units) void SetDrawOffsetF(int Fpx); /// \returns the fractional width of the string int FX(); /// \returns the fractional height of the string int FY(); /// \returns both the fractional width and height of the string LPoint FSize(); /// Draws the string using fractional co-ordinates. void FDraw ( /// The output device LSurface *pDC, /// The fractional x coordinate of the top left corner of the output box int fx, /// The fractional y coordinate of the top left corner of the output box int fy, /// [Optional] fractional clipping rectangle. If the font is not transparent /// this rectangle will be filled with the background colour. LRect *frc = NULL, /// [Optional] print debug info bool Debug = false ); }; #endif diff --git a/include/lgi/common/DocView.h b/include/lgi/common/DocView.h --- a/include/lgi/common/DocView.h +++ b/include/lgi/common/DocView.h @@ -1,526 +1,524 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief This is the base data and code for all the text controls (inc. HTML) -#ifndef __GDOCVIEW_H -#define __GDOCVIEW_H +#pragma once + +#include #include "lgi/common/Variant.h" #include "lgi/common/Notifications.h" #include "lgi/common/Thread.h" #include "lgi/common/Layout.h" // Word wrap enum LDocWrapType { /// No word wrapping TEXTED_WRAP_NONE = 0, /// Dynamically wrap line to editor width TEXTED_WRAP_REFLOW = 1, }; // Util macros /// Returns true if 'c' is whitespace #define IsWhiteSpace(c) ((c) < 126 && strchr(LDocView::WhiteSpace, c) != 0) /// Returns true if 'c' is a delimiter #define IsDelimiter(c) ((c) < 126 && strchr(LDocView::Delimiters, c) != 0) /// Returns true if 'c' is a letter or number #define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_') /// Returns true if 'c' is word boundry #define IsWordBoundry(c) (strchr(LDocView::WhiteSpace, c) || strchr(LDocView::Delimiters, c)) /// Returns true if 'c' is alphanumeric or a digit #define AlphaOrDigit(c) (IsDigit(c) || IsAlpha(c)) /// Returns true if 'c' is a valid URL character #define UrlChar(c) ( \ strchr(LDocView::UrlDelim, (c)) || \ AlphaOrDigit((c)) || \ ((c) >= 256) \ ) /// Returns true if 'c' is email address character #define EmailChar(c) (strchr("._-:+", (c)) || AlphaOrDigit((c))) LgiFunc char16 *ConvertToCrLf(char16 *Text); /// This class contains information about a link. /// \sa LDetectLinks struct LLinkInfo { ssize_t Start; ssize_t Len; bool Email; void Set(ssize_t start, ssize_t len, bool email) { Start = start; Len = len; Email = email; } }; // Call back class to handle viewer events class LDocView; /// An environment class to handle requests from the text view to the outside world. class LgiClass LDocumentEnv : public LThreadOwner { LArray Viewers; public: LDocumentEnv(LDocView *v = 0); virtual ~LDocumentEnv(); enum LoadType { LoadError, LoadNotImpl, LoadImmediate, LoadDeferred, }; struct #ifdef MAC LgiClass #endif LoadJob : public LThreadJob { enum PrefFormat { FmtNone, FmtStream, FmtSurface, FmtFilename, }; enum JobStatus { JobInit, JobOk, JobErr_Uri, JobErr_Path, JobErr_FileOpen, JobErr_GetUri, JobErr_NoCachedFile, JobErr_ImageFilter, JobErr_NoMem, }; // View data LDocumentEnv *Env; void *UserData; uint32_t UserUid; PrefFormat Pref; // Input data LAutoString Uri; LAutoString PostData; // Output data LAutoPtr Stream; LAutoPtr pDC; LString Filename; LString Error; JobStatus Status; LString MimeType, ContentId; LoadJob(LThreadTarget *o) : LThreadJob(o) { Env = NULL; UserUid = 0; UserData = NULL; Pref = FmtNone; Status = JobInit; } LStreamI *GetStream() { if (!Stream && Filename) { LFile *file = new LFile; if (file && file->Open(Filename, O_READ)) Stream.Reset(file); else DeleteObj(file); } return Stream; } }; LoadJob *NewJob() { return new LoadJob(this); } bool AttachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(!Viewers.HasItem(v)); Viewers.Add(v); Unlock(); return true; } bool DetachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(Viewers.HasItem(v)); Viewers.Delete(v); Unlock(); return true; } int NextUid(); /// Creating a context menu, usually when the user right clicks on the /// document. virtual bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) { return false; } /// Do something when the menu items created by LDocumentEnv::AppendItems /// are clicked. virtual bool OnMenu(LDocView *View, int Id, void *Context) { return false; } /// Asks the env to get some data linked from the document, e.g. a css file or an iframe source etc. /// If the GetContent implementation takes ownership of the job pointer then it should set 'j' to NULL. virtual LoadType GetContent(LoadJob *&j) { return LoadNotImpl; } /// After the env's thread loads the resource it calls this to pass it to the doc void OnDone(LAutoPtr j); /// Handle a click on URI virtual bool OnNavigate(LDocView *Parent, const char *Uri) { return false; } /// Handle a form post virtual bool OnPostForm(LDocView *Parent, const char *Uri, const char *Data) { return false; } /// Process dynamic content, returning a dynamically allocated string /// for the result of the executed script. Dynamic content is enclosed /// between <? and ?>. virtual char *OnDynamicContent(LDocView *Parent, const char *Code) { return 0; } /// Some script was received, the owner should compile it virtual bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { return false; } /// Some script needs to be executed, the owner should compile it virtual bool OnExecuteScript(LDocView *Parent, char *Script) { return false; } }; /// Default text view environment /// /// This class defines the default behavior of the environment, /// However you will need to instantiate this yourself and call /// SetEnv with your instance. i.e. it's not automatic. class LgiClass LDefaultDocumentEnv : public LDocumentEnv { public: LoadType GetContent(LoadJob *&j); bool OnNavigate(LDocView *Parent, const char *Uri); }; /// Find params class LDocFindReplaceParams { public: virtual ~LDocFindReplaceParams() {} }; /// TextView class is a base for all text controls class LgiClass LDocView : public LLayout, virtual public LDom { friend class LDocumentEnv; protected: LDocumentEnv *Environment = NULL; LString Charset; public: // Static static const char *WhiteSpace; static const char *Delimiters; static const char *UrlDelim; /////////////////////////////////////////////////////////////////////// // Properties #define _TvMenuProp(Type, Name, Default) \ protected: \ Type Name = Default; \ public: \ virtual void Set##Name(Type i) { Name=i; } \ Type Get##Name() { return Name; } _TvMenuProp(uint16, WrapAtCol, 0) _TvMenuProp(bool, UrlDetect, true) _TvMenuProp(bool, ReadOnly, false) _TvMenuProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW) _TvMenuProp(uint8_t, TabSize, 4) _TvMenuProp(uint8_t, IndentSize, 4) _TvMenuProp(bool, HardTabs, true) _TvMenuProp(bool, ShowWhiteSpace, false) _TvMenuProp(bool, ObscurePassword, false) _TvMenuProp(bool, CrLf, false) _TvMenuProp(bool, AutoIndent, true) _TvMenuProp(bool, FixedWidthFont, false) _TvMenuProp(bool, LoadImages, false) _TvMenuProp(bool, OverideDocCharset, false) // This UID is used to match data load events with their source document. // Sometimes data will arrive after the document that asked for it has // already been unloaded. So by assigned each document an UID we can check // the job UID against it and discard old data. _TvMenuProp(int, DocumentUid, 0) #undef _TvMenuProp virtual const char *GetCharset() { return Charset.Get() ? Charset.Get() : "utf-8"; } virtual void SetCharset(const char *s) { Charset = s; } virtual const char *GetMimeType() = 0; /////////////////////////////////////////////////////////////////////// // Object LDocView(LDocumentEnv *e = NULL) { SetEnv(e); } virtual ~LDocView() { - SetEnv(0); + SetEnv(NULL); } const char *GetClass() { return "LDocView"; } /// Open a file handler virtual bool Open(const char *Name, const char *Cs = 0) { return false; } /// Save a file handler virtual bool Save(const char *Name, const char *Cs = 0) { return false; } /////////////////////////////////////////////////////////////////////// /// Find window handler - virtual bool DoFind() { return false; } + virtual void DoFind(std::function Callback) { if (Callback) Callback(false); } /// Replace window handler - virtual bool DoReplace() { return false; } + virtual void DoReplace(std::function Callback) { if (Callback) Callback(false); } virtual LDocFindReplaceParams *CreateFindReplaceParams() { return 0; } virtual void SetFindReplaceParams(LDocFindReplaceParams *Params) { } /////////////////////////////////////////////////////////////////////// /// Get the current environment virtual LDocumentEnv *GetEnv() { return Environment; } /// Set the current environment virtual void SetEnv(LDocumentEnv *e) { if (Environment) Environment->DetachView(this); Environment = e; if (Environment) Environment->AttachView(this); } /// When the env has loaded a resource it can pass it to the doc control via this method. /// It MUST be thread safe. Often an environment will call this function directly from /// it's worker thread. virtual void OnContent(LDocumentEnv::LoadJob *Res) {} /////////////////////////////////////////////////////////////////////// // State / Selection /// Set the cursor position, to select an area, move the cursor with Select=false /// then set the other end of the region with Select=true. virtual void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) {} /// Cursor=false means the other end of the selection if any. The cursor is alwasy /// at one end of the selection. virtual ssize_t GetCaret(bool Cursor = true) { return 0; } /// True if there is a selection virtual bool HasSelection() { return false; } /// Unselect all the text virtual void UnSelectAll() {} /// Select the word from index 'From' virtual void SelectWord(size_t From) {} /// Select all the text in the control virtual void SelectAll() {} /// Get the selection as a dynamicially allocated utf-8 string virtual char *GetSelection() { return 0; } /// Returns the character index at the x,y location virtual ssize_t IndexAt(int x, int y) { return 0; } /// Index=-1 returns the x,y of the cursor, Index >=0 returns the specified x,y virtual bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) { return false; } /// True if the document has changed virtual bool IsDirty() { return false; } /// Gets the number of lines of text virtual size_t GetLines() { return 0; } /// Gets the pixels required to display all the text virtual void GetTextExtent(int &x, int &y) {} /////////////////////////////////////////////////////////////////////// /// Cuts the selection from the document and puts it on the clipboard virtual bool Cut() { return false; } /// Copies the selection from the document to the clipboard virtual bool Copy() { return false; } /// Pastes the current contents of the clipboard into the document virtual bool Paste() { return false; } /////////////////////////////////////////////////////////////////////// /// Called when the user hits the escape key virtual void OnEscape(LKey &K) {} /// Called when the user hits the enter key virtual void OnEnter(LKey &k) {} /// Called when the user clicks a URL virtual void OnUrl(char *Url) {} /// Called to add styling to the document virtual void OnAddStyle(const char *MimeType, const char *Styles) {} /////////////////////////////////////////////////////////////////////// struct ContentMedia { LString Id; LString FileName; LString MimeType; LVariant Data; LAutoPtr Stream; bool Valid() { return MimeType.Get() != NULL && FileName.Get() != NULL && ( (Data.Type == GV_BINARY && Data.Value.Binary.Data != NULL) || (Stream.Get() != NULL) ); } }; /// Gets the document in format of a desired MIME type virtual bool GetFormattedContent ( /// [In] The desired mime type of the content const char *MimeType, /// [Out] The content in the specified mime type LString &Out, /// [Out/Optional] Any attached media files that the content references LArray *Media = NULL ) { return false; } }; /// Detects links in text, returning their location and type template bool LDetectLinks(LArray &Links, T *Text, ssize_t TextCharLen = -1) { if (!Text) return false; if (TextCharLen < 0) TextCharLen = Strlen(Text); T *End = Text + TextCharLen; static T Http[] = {'h', 't', 't', 'p', ':', '/', '/', 0 }; static T Https[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0}; for (int64 i=0; i= 7 && ( Strnicmp(Text+i, Http, 6) == 0 || Strnicmp(Text+i, Https, 7) == 0 ) ) { // find end T *s = Text + i; T *e = s + 6; for ( ; e < End && UrlChar(*e); e++) ; while ( e > s && ! ( IsAlpha(e[-1]) || IsDigit(e[-1]) || e[-1] == '/' ) ) e--; Links.New().Set(s - Text, e - s, false); i = e - Text; } break; } case '@': { // find start T *s = Text + (MAX(i, 1) - 1); for ( ; s > Text && EmailChar(*s); s--) ; if (s < Text + i) { if (!EmailChar(*s)) s++; bool FoundDot = false; T *Start = Text + i + 1; T *e = Start; for ( ; e < End && EmailChar(*e); e++) { if (*e == '.') FoundDot = true; } while (e > Start && e[-1] == '.') e--; if (FoundDot) { Links.New().Set(s - Text, e - s, true); i = e - Text; } } break; } } } return true; } - - -#endif diff --git a/include/lgi/common/Error.h b/include/lgi/common/Error.h --- a/include/lgi/common/Error.h +++ b/include/lgi/common/Error.h @@ -1,96 +1,97 @@ // // LCancel.h // Lgi // // Created by Matthew Allen on 19/10/2017. // // #ifndef _LERROR_H_ #define _LERROR_H_ #include #ifdef POSIX #include "errno.h" #endif -LgiFunc const char *GetErrorName(int e); - // Some cross platform error symbols (by no means a complete list) enum LErrorCodes { #if defined(WINDOWS) LErrorNone = ERROR_SUCCESS, LErrorAccessDenied = ERROR_ACCESS_DENIED, LErrorNoMem = ERROR_NOT_ENOUGH_MEMORY, LErrorInvalidParam = ERROR_INVALID_PARAMETER, LErrorTooManyFiles = ERROR_NO_MORE_FILES, LErrorFileExists = ERROR_FILE_EXISTS, LErrorPathNotFound = ERROR_PATH_NOT_FOUND, #elif defined(POSIX) LErrorNone = 0, LErrorAccessDenied = EACCES, LErrorNoMem = ENOMEM, LErrorInvalidParam = EINVAL, LErrorTooManyFiles = EMFILE, LErrorFileExists = EEXIST, LErrorPathNotFound = ENOTDIR, #else #warning "Impl me." #endif }; +/// Converts an OS error code into a text string +LgiExtern LString LErrorCodeToString(uint32_t ErrorCode); + class LgiClass LError { // This error code is defined by the operating system int Code; LString Msg; public: LString::Array DevNotes; LError(int code = 0, const char *msg = NULL) { Set(code, msg); } LError &operator =(int code) { Code = code; return *this; } void Set(int code, const char *msg = NULL) { Code = code; Msg = msg; } int GetCode() { return Code; } LString GetMsg() { if (!Msg) - Msg = GetErrorName(Code); + Msg = LErrorCodeToString(Code); return Msg; } void AddNote(const char *File, int Line, const char *Fmt, ...) { char buffer[512] = {0}; va_list arg; va_start(arg, Fmt); vsprintf_s(buffer, sizeof(buffer), Fmt, arg); va_end(arg); DevNotes.New().Printf("%s:%i - %s", File, Line, buffer); } LString GetNotes() { return LString("\n").Join(DevNotes); } }; #endif /* _LERROR_H_ */ diff --git a/include/lgi/common/EventTargetThread.h b/include/lgi/common/EventTargetThread.h --- a/include/lgi/common/EventTargetThread.h +++ b/include/lgi/common/EventTargetThread.h @@ -1,435 +1,458 @@ #ifndef _GEVENTTARGETTHREAD_H_ #define _GEVENTTARGETTHREAD_H_ #include "lgi/common/Thread.h" #include "lgi/common/Mutex.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/Cancel.h" #include "lgi/common/HashTable.h" #define PostThreadEvent LEventSinkMap::Dispatch.PostEvent class LgiClass LEventSinkMap : public LMutex { protected: LHashTbl,LEventSinkI*> ToPtr; LHashTbl,int> ToHnd; public: static LEventSinkMap Dispatch; LEventSinkMap(int SizeHint = 0) : LMutex("LEventSinkMap"), ToPtr(SizeHint), ToHnd(SizeHint) { } virtual ~LEventSinkMap() { } int AddSink(LEventSinkI *s) { if (!s || !Lock(_FL)) return ToPtr.GetNullKey(); // Find free handle... int Hnd; while (ToPtr.Find(Hnd = LRand(10000) + 1)) ; // Add the new sink ToPtr.Add(Hnd, s); ToHnd.Add(s, Hnd); Unlock(); return Hnd; } bool RemoveSink(LEventSinkI *s) { if (!s || !Lock(_FL)) return false; bool Status = false; int Hnd = ToHnd.Find(s); if (Hnd > 0) { Status |= ToHnd.Delete(s); Status &= ToPtr.Delete(Hnd); } else LAssert(!"Not a member of this sink."); Unlock(); return Status; } bool RemoveSink(int Hnd) { if (!Hnd || !Lock(_FL)) return false; bool Status = false; void *Ptr = ToPtr.Find(Hnd); if (Ptr) { Status |= ToHnd.Delete(Ptr); Status &= ToPtr.Delete(Hnd); } else LAssert(!"Not a member of this sink."); Unlock(); return Status; } bool PostEvent(int Hnd, int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) { if (!Hnd) return false; - if (!Lock(_FL)) - return false; + + /* + + This can deadlock on Haiku: + + - If the target LEventSinkI is a view processing a message, and therefor locked. + - And it's also locking this LEventSinkMap to try and send a message to it. + - And the LEventTargetThread thread is trying to send a message back to the view. + + Fix is to not keep this locked, but to periodically try to lock and then send. + If it fails, wait and then try again. While waiting, release the lock. + + */ - auto *s = ToPtr.Find(Hnd); - bool Status = s ? s->PostEvent(Cmd, a, b) : false; - #if 0 // _DEBUG - else - // This is not fatal, but we might want to know about it in DEBUG builds: - LgiTrace("%s:%i - Sink associated with handle '%i' doesn't exist.\n", _FL, Hnd); - #endif + bool Done = false, Status = false; + while (!Done) + { + if (LockWithTimeout(10, _FL)) + { + auto *s = ToPtr.Find(Hnd); + Status = s ? s->PostEvent(Cmd, a, b) : false; + #if 0 // _DEBUG + else + // This is not fatal, but we might want to know about it in DEBUG builds: + LgiTrace("%s:%i - Sink associated with handle '%i' doesn't exist.\n", _FL, Hnd); + #endif + Done = true; - Unlock(); + Unlock(); + break; + } + + LSleep(10); + } + return Status; } bool CancelThread(int Hnd) { if (!Hnd) return false; if (!Lock(_FL)) return false; LEventSinkI *s = (LEventSinkI*)ToPtr.Find(Hnd); bool Status = false; if (s) { LCancel *c = dynamic_cast(s); if (c) { Status = c->Cancel(true); } else { LgiTrace("%s:%i - LEventSinkI is not an LCancel object.\n", _FL); } } Unlock(); return Status; } }; class LgiClass LMappedEventSink : public LEventSinkI { protected: int Handle = 0; LEventSinkMap *Map = NULL; public: LMappedEventSink() { SetMap(&LEventSinkMap::Dispatch); } virtual ~LMappedEventSink() { SetMap(NULL); } bool SetMap(LEventSinkMap *m) { if (Map) { if (!Map->RemoveSink(this)) return false; Map = 0; Handle = 0; } Map = m; if (Map) { Handle = Map->AddSink(this); return Handle > 0; } return true; } int GetHandle() { LAssert(Handle != 0); return Handle; } }; /// This class is a worker thread that accepts messages on it's LEventSinkI interface. /// To use, sub class and implement the OnEvent handler. class LgiClass LEventTargetThread : public LThread, public LMutex, public LMappedEventSink, public LCancel, public LEventTargetI // Sub-class has to implement OnEvent { LArray Msgs; LThreadEvent Event; // This makes the event name unique on windows to // prevent multiple instances clashing. LString ProcessName(LString obj, const char *desc) { OsProcessId process = LGetCurrentProcess(); OsThreadId thread = GetCurrentThreadId(); LString s; s.Printf("%s.%s.%i.%i", obj.Get(), desc, process, thread); return s; } protected: int PostTimeout; size_t Processing; uint32_t TimerMs; // Milliseconds between timer ticks. uint64 TimerTs; // Time for next tick public: LEventTargetThread(LString Name, bool RunImmediately = true) : LThread(Name + ".Thread"), LMutex(Name + ".Mutex"), Event(ProcessName(Name, "Event")) { PostTimeout = 1000; Processing = 0; TimerMs = 0; TimerTs = 0; if (RunImmediately) Run(); } virtual ~LEventTargetThread() { EndThread(); } /// Set or clear a timer. Every time the timer expires, the function /// OnPulse is called. Until SetPulse() is called. bool SetPulse(uint32_t Ms = 0) { TimerMs = Ms; TimerTs = Ms ? LCurrentTime() + Ms : 0; return Event.Signal(); } /// Called roughly every 'TimerMs' milliseconds. /// Be aware however that OnPulse is called from the worker thread, not your main /// GUI thread. So best to send a message or something thread safe. virtual void OnPulse() {} void EndThread() { // We can't be locked here, because LEventTargetThread::Main needs // to lock to check for messages... Cancel(); if (GetCurrentThreadId() == LThread::GetId()) { // Being called from within the thread, in which case we can't signal // the event because we'll be stuck in this loop and not waitin on it. #ifdef _DEBUG LgiTrace("%s:%i - EndThread called from inside thread.\n", _FL); #endif } else { Event.Signal(); } uint64 Start = LCurrentTime(); while (!IsExited()) { LSleep(10); uint64 Now = LCurrentTime(); if (Now - Start > 2000) { #ifdef LINUX int val = 1111; int r = sem_getvalue(Event.Handle(), &val); printf("%s:%i - EndThread() hung waiting for %s to exit (caller.thread=%i, worker.thread=%i, event=%i, r=%i, val=%i).\n", _FL, LThread::GetName(), GetCurrentThreadId(), GetId(), Event.Handle(), r, val); #else printf("%s:%i - EndThread() hung waiting for %s to exit (caller.thread=0x%x, worker.thread=0x%x, event=%p).\n", _FL, LThread::GetName(), (int)GetCurrentThreadId(), (int)GetId(), (void*)(ssize_t)Event.Handle()); #endif Start = Now; } } } size_t GetQueueSize() { return Msgs.Length() + Processing; } - bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) + bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t timeout = -1) { if (IsCancelled()) return false; if (!Lock(_FL)) return false; auto Msg = new LMessage(Cmd, a, b); if (Msg) Msgs.Add(Msg); Unlock(); // printf("%x: PostEvent and sig %i\n", GetCurrentThreadId(), (int)Msgs.Length()); return Event.Signal(); } int Main() { while (!IsCancelled()) { int WaitLength = -1; if (TimerTs != 0) { uint64 Now = LCurrentTime(); if (TimerTs > Now) { WaitLength = (int) (TimerTs - Now); } else { OnPulse(); if (TimerMs) { TimerTs = Now + TimerMs; WaitLength = (int) TimerTs; } else WaitLength = -1; } } LThreadEvent::WaitStatus s = Event.Wait(WaitLength); if (s == LThreadEvent::WaitSignaled) { LArray m; if (Lock(_FL)) { if (Msgs.Length()) m.Swap(Msgs); Unlock(); } Processing = m.Length(); for (unsigned i=0; !IsCancelled() && i < m.Length(); i++) { Processing--; auto Msg = m[i]; if (Msg) OnEvent(Msg); else LAssert(!"NULL Msg?"); } m.DeleteObjects(); } else if (s == LThreadEvent::WaitError) { LgiTrace("%s:%i - Event.Wait failed.\n", _FL); break; } } return 0; } template bool PostObject(int Hnd, int Cmd, LAutoPtr A) { uint64 Start = LCurrentTime(); bool Status; while (!(Status = LEventSinkMap::Dispatch.PostEvent(Hnd, Cmd, (LMessage::Param) A.Get()))) { LSleep(2); if (LCurrentTime() - Start >= PostTimeout) break; } if (Status) A.Release(); return Status; } template bool PostObject(int Hnd, int Cmd, LMessage::Param A, LAutoPtr B) { uint64 Start = LCurrentTime(); bool Status; while (!(Status = LEventSinkMap::Dispatch.PostEvent(Hnd, Cmd, A, (LMessage::Param) B.Get()))) { LSleep(2); if (LCurrentTime() - Start >= PostTimeout) break; } if (Status) B.Release(); return Status; } template bool PostObject(int Hnd, int Cmd, LAutoPtr A, LAutoPtr B) { uint64 Start = LCurrentTime(); bool Status; while (!(Status = LEventSinkMap::Dispatch.PostEvent(Hnd, Cmd, (LMessage::Param) A.Get(), (LMessage::Param) B.Get()))) { LSleep(2); if (LCurrentTime() - Start >= PostTimeout) break; } if (Status) { A.Release(); B.Release(); } return Status; } template bool ReceiveA(LAutoPtr &Obj, LMessage *m) { return Obj.Reset((T*)m->A()); } template bool ReceiveB(LAutoPtr &Obj, LMessage *m) { return Obj.Reset((T*)m->B()); } }; #endif diff --git a/include/lgi/common/FileSelect.h b/include/lgi/common/FileSelect.h --- a/include/lgi/common/FileSelect.h +++ b/include/lgi/common/FileSelect.h @@ -1,128 +1,130 @@ -#ifndef __GFILE_SELECT_H -#define __GFILE_SELECT_H +#pragma once + +#include LgiFunc bool LgiGetUsersLinks(LArray &Links); /////////////////////////////////////////////////////////////////////////////////////////////////// // File select dialog class LgiClass LFileType : public LBase { char *Ext; int _Data; public: LFileType() { Ext = 0; _Data = 0; } ~LFileType() { DeleteArray(Ext); } char *Extension() { return Ext; } bool Extension(const char *e) { return (Ext = NewStr(e)) != 0; } const char *Description() { return Name(); } bool Description(const char *d) { return Name(d); } int Data() { return _Data; } void Data(int i) { _Data = i; } char *DefaultExtension(); }; /// \brief File selector dialog /// /// Handles the UI for selecting files for opening and saving and selecting directories. /// Uses the Win32 system dialogs on Windows and has it's own native window on Linux. /// /// A simple example of this class in action: /// \code /// LFileSelect s; /// s.Parent(MyWindow); /// s.Type("PNG Files", "*.png"); /// if (s.Open()) /// { /// LgiMsg(MyWindow, "The file selected is '%s'", "Example", MB_OK, s.Name()); /// } /// \endcode class LgiClass LFileSelect : public LBase { class LFileSelectPrivate *d; public: - LFileSelect(); + typedef std::function SelectCb; + + LFileSelect(LViewI *Window = NULL); ~LFileSelect(); // Properties /// Returns the first file name selected. const char *Name() override; /// Sets the file name bool Name(const char *n) override; /// Returns the n'th file name selected const char *operator [](size_t i); /// Returns the number of file names selected size_t Length(); /// Returns the parent window LViewI *Parent(); /// Sets the parent window void Parent(LViewI *Window); /// Returns whether the use can select multiple files bool MultiSelect(); /// Sets whether the use can select multiple files void MultiSelect(bool Multi); /// Returns whether read only was selected bool ReadOnly(); /// Sets whether the user sees a read only option void ShowReadOnly(bool ro); /// Gets the initial directory to open in const char *InitialDir(); /// Sets the initial directory to open in void InitialDir(const char *InitDir); /// Gets the title of the dialog box const char *Title(); /// Sets the title of the dialog box void Title(const char *Title); /// Gets the default extension to append to files selected without an extension const char *DefaultExtension(); /// Sets the default extension to append to files selected without an extension void DefaultExtension(const char *DefExt); // File types /// Returns the number of types in the type list size_t Types(); /// Returns the 0 based index of the type selected in the type list ssize_t SelectedType(); /// Returns the type into at a given index LFileType *TypeAt(ssize_t n); /// Adds a file type to the type filters list. bool Type ( /// This full description of the file type const char *Description, /// The extension(s) of the file type: e.g: '*.png;*.gif;*.jpg' const char *Extension, /// Application defined 32-bit value. int Data = 0 ); /// Empties the type list void ClearTypes(); // Methods /// Shows the open file dialog /// \returns true if the user selected a file, otherwise false - bool Open(); + void Open(SelectCb Cb); /// Shows the save file dialog /// \returns true if the user selected a file, otherwise false - bool Save(); + void Save(SelectCb Cb); /// Shows the open folder dialog /// \returns true if the user selected a folder, otherwise false - bool OpenFolder(); + void OpenFolder(SelectCb Cb); }; -#endif diff --git a/include/lgi/common/FindReplaceDlg.h b/include/lgi/common/FindReplaceDlg.h --- a/include/lgi/common/FindReplaceDlg.h +++ b/include/lgi/common/FindReplaceDlg.h @@ -1,89 +1,87 @@ /// \file /// \author Matthew Allen (fret@memecode.com) #pragma once +#include + /// Common find/replace window parameters class LgiClass LFindReplaceCommon : public LDialog { // bool OnViewKey(LView *v, LKey &k); public: + typedef std::function Callback; + /// The string to find LString Find; /// Whether to match a whole word bool MatchWord; /// Whether to match the case bool MatchCase; /// Whether to search only in the selection bool SelectionOnly; /// Whether to search only in the selection bool SearchUpwards; LFindReplaceCommon(); }; -typedef bool (*LFindReplaceCallback)(LFindReplaceCommon *Dlg, bool Replace, void *User); - /// Find dialog class LgiClass LFindDlg : public LFindReplaceCommon { class LFindDlgPrivate *d; public: /// Constructor LFindDlg ( /// The parent window LView *Parent, + /// Callback + Callback Cb, /// The initial string for the find argument - char *Init = 0, - /// Callback - LFindReplaceCallback Callback = 0, - /// User defined data for callback - void *UserData = 0 + const char *Init = NULL ); ~LFindDlg(); void OnCreate(); void OnPosChange(); int OnNotify(LViewI *Ctrl, LNotification n); }; /// The find command on the Replace dialog #define IDC_FR_FIND 21002 /// The replace command on the Replace dialog #define IDC_FR_REPLACE 21007 /// Replace dialog class LgiClass LReplaceDlg : public LFindReplaceCommon { class LReplaceDlgPrivate *d; public: LString Replace; /// Constructor LReplaceDlg ( /// The parent window LView *Parent, + /// Callback + Callback Cb, /// The initial value to find - char *InitFind = 0, + char *InitFind = NULL, /// The initial value to replace with - char *InitReplace = 0, - /// Callback - LFindReplaceCallback Callback = 0, - /// User defined data for callback - void *UserData = 0 + char *InitReplace = NULL ); ~LReplaceDlg(); void OnCreate(); void OnPosChange(); /// \returns DoModal will return one of #IDC_FR_FIND, /// #IDC_FR_REPLACE, #IDCANCEL or #IDOK (which means 'Replace All', the default action) int OnNotify(LViewI *Ctrl, LNotification n); }; diff --git a/include/lgi/common/Font.h b/include/lgi/common/Font.h --- a/include/lgi/common/Font.h +++ b/include/lgi/common/Font.h @@ -1,399 +1,401 @@ /// \file /// \author Matthew Allen #ifndef _GDCFONT_H_ #define _GDCFONT_H_ #include "string.h" #include "lgi/common/Rect.h" #include "LgiOsClasses.h" #include "lgi/common/Colour.h" #include "lgi/common/Capabilities.h" #include "lgi/common/Css.h" +#include + ////////////////////////////////////////////////////////////// // Defines #ifndef WIN32 // font weights #define FW_DONTCARE 0 #define FW_THIN 100 #define FW_EXTRALIGHT 200 #define FW_ULTRALIGHT 200 #define FW_LIGHT 300 /// The default font weight #define FW_NORMAL 400 #define FW_REGULAR 400 #define FW_MEDIUM 500 #define FW_SEMIBOLD 600 #define FW_DEMIBOLD 600 /// Bold font weight #define FW_BOLD 700 #define FW_EXTRABOLD 800 #define FW_ULTRABOLD 800 #define FW_HEAVY 900 #define FW_BLACK 900 // quality /// Default font quality #define DEFAULT_QUALITY 0 /// Specifically anti-aliased font #define ANTIALIASED_QUALITY 1 /// Specifically not anti-alias font #define NONANTIALIASED_QUALITY 2 #elif defined WIN32 #define WESTEUROPE_CHARSET BALTIC_CHARSET // ??? don't know #endif // OS specific font type #if defined(LGI_SDL) #define PrevOsChar(Ptr) Ptr-- #define NextOsChar(Ptr) Ptr++ #elif defined __GTK_H__ #include "LgiOsClasses.h" #define PrevOsChar(Ptr) Ptr-- #define NextOsChar(Ptr) Ptr++ #elif defined(WIN32) #define PrevOsChar(Ptr) Ptr-- #define NextOsChar(Ptr) Ptr++ #endif #define LGI_WHITESPACE_WEIGHT 0.15f // amount of foreground colour in visible whitespace #define MAX_UNICODE 0x10FFFF // maximum unicode char I can handle #define _HasUnicodeGlyph(map, u) ( (map[(u)>>3] & (1 << ((u) & 7))) != 0 ) enum LPxToIndexType { LgiTruncate, LgiNearest }; ////////////////////////////////////////////////////////////// // Classes class LFontType; /// Font parameters collection class LgiClass LTypeFace { protected: class LTypeFacePrivate *d; // Methods virtual void _OnPropChange(bool c) {} // if false then it's just a property change public: LTypeFace(); virtual ~LTypeFace(); /// Sets the font face name void Face(const char *s); /// Sets the font size void Size(LCss::Len); /// Sets the point size void PointSize(int i); /// Sets the tab size in device units (pixels) void TabSize(int i); /// Sets the quality resquested, use one of #DEFAULT_QUALITY, #ANTIALIASED_QUALITY or #NONANTIALIASED_QUALITY. void Quality(int i); /// Sets the foreground colour as a 24 bit RGB value void Fore(LSystemColour c); void Fore(LColour c); void Back(LSystemColour c); void Back(LColour c); /// Get the whitespace rendering colour LColour WhitespaceColour(); /// Sets the rendering colour of whitespace characters when LDisplayString::ShowVisibleTab() is true. void WhitespaceColour(LColour c); /// Sets the font's weight, use one the weight defines in LFont.h, e.g. #FW_NORMAL, #FW_BOLD void SetWeight(int Weight); /// Set a bold font void Bold(bool i) { SetWeight(i ? FW_BOLD : FW_NORMAL); } /// Sets the font to italic void Italic(bool i); /// Draws with an underline void Underline(bool i); /// Makes the text have no background. void Transparent(bool i); /// Turns glyph substitution on or off void SubGlyphs(bool i); /// \returns the font face const char *Face() const; /// \returns the point size (avoid, use 'Size' instead) int PointSize() const; /// \returns the size LCss::Len Size() const; /// \returns the tabsize in pixels int TabSize() const; /// \returns the quality setting int Quality() const; /// \returns the foreground colour. LColour Fore() const; /// \returns the background colour. LColour Back() const; /// \returns the font weight. int GetWeight() const; /// \returns true if this is a bold font. bool Bold() const { return GetWeight() >= FW_BOLD; } /// \returns true if this is a italic font. bool Italic() const; /// \returns true if this font is drawn with an underline. bool Underline() const; /// \returns true if no background will be drawn. bool Transparent() const; /// \returns true if glyph substitution will be done. bool SubGlyphs() const; /// \returns the amount of space above the baseline. double Ascent() const; /// \returns the amount of space below the baseline. double Descent() const; /// \returns the amount of normally unused space at the top of the Ascent. double Leading() const; /// \returns true if the font types are the same bool operator ==(const LTypeFace &t); /// Set the foreground and background in 24-bit colour. /// \sa LTypeFace::Fore() and LTypeFace::Back() virtual void Colour(LSystemColour Fore, LSystemColour Back = L_TRANSPARENT); /// Set the foreground and background colour. virtual void Colour(LColour Fore, LColour Back); }; /// \brief Font class. class LgiClass LFont : public LTypeFace { friend class LFontSystem; friend class LDisplayString; protected: class LFontPrivate *d; // Methods bool IsValid(); void _OnPropChange(bool Change); char16 *_ToUnicode(char *In, ssize_t &Len); bool GetOwnerUnderline(); virtual void _Measure(int &x, int &y, OsChar *Str, int Len); virtual int _CharAt(int x, OsChar *Str, int Len, LPxToIndexType Type); virtual void _Draw(LSurface *pDC, int x, int y, OsChar *Str, int Len, LRect *r, LColour &fore); public: /// Construct from face/pt size. LFont ( /// Font face name const char *face = 0, /// Size of the font LCss::Len size = LCss::LenInherit ); /// Construct from OS font handle LFont(OsFont Handle); /// Construct from type information LFont(LFontType &Type); /// Copy constructor LFont(LFont &Fnt); virtual ~LFont(); /// Creates a new font handle with the specified face / pt size virtual bool Create ( /// The new font face const char *Face = 0, /// The pt size LCss::Len Size = LCss::LenInherit, /// Creating a font for a particular surface (e.g. printing). LSurface *pSurface = 0 ); /// Creates a new font from type infomation bool Create(LFontType *Type, LSurface *pSurface = NULL); /// Creates the font from CSS defs. bool CreateFromCss(const char *Css); /// Creates the font from a CSS object. bool CreateFromCss(LCss *Css); /// Returns CSS styles to describe font LString FontToCss(); /// Clears any handles and memory associated with the object. virtual bool Destroy(); void WarnOnDelete(bool w); /// Returns the OS font handle virtual OsFont Handle(); /// Copies the font virtual LFont &operator =(const LFont &f); /// Returns the pixel height of the font virtual int GetHeight(); /// Gets the creation parameter passed in (0 by default). LSurface *GetSurface(); /// Get supported glyphs uchar *GetGlyphMap(); /// Converts printable characters to unicode. LAutoString ConvertToUnicode(char16 *Input, ssize_t Len = -1); #if USE_CORETEXT CFDictionaryRef GetAttributes(); #endif }; /// Font type information and system font query tools. class LgiClass LFontType { friend class LFont; friend class LTypeFace; protected: #if WINNATIVE LOGFONTW Info; #else LTypeFace Info; #endif LString Buf; public: LFontType(const char *face = 0, int pointsize = 0); virtual ~LFontType(); #ifdef WINNATIVE LOGFONTW *Handle() { return &Info; } #else LTypeFace *Handle() { return &Info; } #endif /// Gets the type face name const char *GetFace(); /// Sets the type face name void SetFace(const char *Face); /// Sets the point size int GetPointSize(); /// Sets the point size void SetPointSize(int PointSize); /// Put a user interface for the user to edit the font def - bool DoUI(LView *Parent); + void DoUI(LView *Parent, std::function Callback); /// Describe the font to the user as a string bool GetDescription(char *Str, int SLen); /// Read/Write the font def to storage // bool Serialize(ObjProperties *Options, char *OptName, bool Write); /// Read/Write the font def to storage bool Serialize(LDom *Options, const char *OptName, bool Write); /// Read the font from the LGI config bool GetConfigFont(const char *Tag); /// Read the font from LGI config (if there) and then the system settings bool GetSystemFont(const char *Which); /// Read from OS reference bool GetFromRef(OsFont Handle); /// Create a font based on this font def virtual LFont *Create(LSurface *pSurface = NULL); }; #ifndef LGI_STATIC /// Overall font system class class LgiClass LFontSystem : public LCapabilityClient { friend class LApp; friend class LDisplayString; static LFontSystem *Me; private: // System Font List LString::Array AllFonts; LString::Array SubFonts; // Fonts yet to be scanned for substitution // Missing Glyph Substitution uchar Lut[MAX_UNICODE+1]; LFont *Font[256]; class LFontSystemPrivate *d; public: /// Get a pointer to the font system. static LFontSystem *Inst(); // Object LFontSystem(); ~LFontSystem(); /// Enumerate all installed fonts bool EnumerateFonts(LString::Array &Fonts); /// Returns whether the current Lgi implementation supports glyph sub bool GetGlyphSubSupport(); /// Returns whether glyph sub is currently turned on bool GetDefaultGlyphSub(); /// Turns the glyph sub feature on or off void SetDefaultGlyphSub(bool i); /// Returns a font that can render the specified unicode code point LFont *GetGlyph ( /// A utf-32 character uint32_t u, /// The base font used for rendering LFont *UserFont ); /// This looks for a font that can contains the most glyphs for a given string, /// ideally it can render the whole thing. But the next best alternative is returned /// when no font matches all characters in the string. LFont *GetBestFont(char *Str); /// Add a custom font to the glyph lookup table bool AddFont(LAutoPtr Fnt); #ifdef __GTK_H__ // Pango stuff Gtk::PangoFontMap *GetFontMap(); Gtk::PangoContext *GetContext(); #endif void ResetLibCheck(); /// \returns true if iconv services are available. bool HasIconv(bool Quiet); /// Converts a string into a buffer using iconv ssize_t IconvConvert(const char *OutCs, char *Out, ssize_t OutLen, const char *InCs, const char *&In, ssize_t InLen); /// Converts a string into a stream using iconv ssize_t IconvConvert(const char *OutCs, LStreamI *Out, const char *InCs, const char *&In, ssize_t InLen); }; #endif #ifdef LINUX -extern bool _GetSystemFont(char *FontType, char *Font, int FontBufSize, int &PointSize); +extern bool _GetSystemFont(const char *FontType, char *Font, int FontBufSize, int &PointSize); #endif #endif diff --git a/include/lgi/common/Gdc2.h b/include/lgi/common/Gdc2.h --- a/include/lgi/common/Gdc2.h +++ b/include/lgi/common/Gdc2.h @@ -1,1461 +1,1474 @@ /** \file \author Matthew Allen \date 20/2/1997 \brief GDC v2.xx header */ #ifndef __GDC2_H_ #define __GDC2_H_ #include #include "LgiOsDefs.h" // Platform specific // Alpha Bliting #ifdef WINNATIVE #include #include #pragma warning(disable:4263) #include #pragma warning(error:4263) #pragma comment (lib,"Gdiplus.lib") #endif // sub-system headers #include "lgi/common/LgiInc.h" #include "lgi/common/LgiUiBase.h" #include "lgi/common/File.h" #include "lgi/common/Mem.h" #include "lgi/common/Core.h" #include "lgi/common/Containers.h" #include "lgi/common/Capabilities.h" #include "lgi/common/RefCount.h" #include "lgi/common/Palette.h" #include "lgi/common/ColourSpace.h" #include "lgi/common/Rect.h" #include "lgi/common/Point.h" #include "lgi/common/Colour.h" #ifndef AC_SRC_OVER #define AC_SRC_OVER 0 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 1 #endif #include "lgi/common/Library.h" // Defines /// The default gamma curve (none) used by the gamma LUT GdcDevice::GetGamma #define LGI_DEFAULT_GAMMA 1.0 #ifndef LGI_PI /// The value of PI #define LGI_PI 3.141592654 #endif /// Converts degrees to radians #define LGI_DegToRad(i) ((i)*LGI_PI/180) /// Converts radians to degrees #define LGI_RadToDeg(i) ((i)*180/LGI_PI) #if defined(WIN32) && !defined(_WIN64) /// Use intel assembly instructions, comment out for porting #define GDC_USE_ASM #endif /// Blending mode: overwrite #define GDC_SET 0 /// Blending mode: bitwise AND with background #define GDC_AND 1 /// Blending mode: bitwise OR with background #define GDC_OR 2 /// Blending mode: bitwise XOR with background #define GDC_XOR 3 /// Blending mode: alpha blend with background #define GDC_ALPHA 4 #define GDC_REMAP 5 #define GDC_MAXOP 6 #define GDC_CACHE_SIZE 4 // Channel types #define GDCC_MONO 0 #define GDCC_GREY 1 #define GDCC_INDEX 2 #define GDCC_R 3 #define GDCC_G 4 #define GDCC_B 5 #define GDCC_ALPHA 6 // Native data formats #define GDC_8BIT 0 #define GDC_16BIT 1 #define GDC_24BIT 2 #define GDC_32BIT 3 #define GDC_MAXFMT 4 // Colour spaces #define GDC_8I 0 // 8 bit paletted #define GDC_5R6G5B 1 // 16 bit #define GDC_8B8G8B 2 // 24 bit #define GDC_8A8R8G8B 3 // 32 bit #define GDC_MAXSPACE 4 // Update types #define GDC_PAL_CHANGE 0x1 #define GDC_BITS_CHANGE 0x2 // Flood fill types /// LSurface::FloodFill to a different colour #define GDC_FILL_TO_DIFFERENT 0 /// LSurface::FloodFill to a certain colour #define GDC_FILL_TO_BORDER 1 /// LSurface::FloodFill while colour is near to the seed colour #define GDC_FILL_NEAR 2 // Gdc options /// Used in GdcApp8Set::Blt when doing colour depth reduction to 8 bit. #define GDC_REDUCE_TYPE 0 /// No conversion #define REDUCE_NONE 0 /// Nearest colour pixels #define REDUCE_NEAREST 1 /// Halftone the pixels #define REDUCE_HALFTONE 2 /// Error diffuse the pixels #define REDUCE_ERROR_DIFFUSION 3 /// Not used. #define REDUCE_DL1 4 /// Not used. #define GDC_HALFTONE_BASE_INDEX 1 /// When in 8-bit defined the behaviour of GdcDevice::GetColour #define GDC_PALETTE_TYPE 2 /// Allocate colours from the palette #define PALTYPE_ALLOC 0 /// Use an RGB cube #define PALTYPE_RGB_CUBE 1 /// Use a HSL palette #define PALTYPE_HSL 2 /// Converts images to the specified bit-depth on load, does nothing if 0 #define GDC_PROMOTE_ON_LOAD 3 #define GDC_MAX_OPTION 4 // LSurface Flags #define GDC_ON_SCREEN 0x0002 #define GDC_ALPHA_CHANNEL 0x0004 #define GDC_UPDATED_PALETTE 0x0008 #define GDC_CAPTURE_CURSOR 0x0010 #define GDC_OWN_APPLICATOR 0x0020 #define GDC_CACHED_APPLICATOR 0x0040 #define GDC_OWN_PALETTE 0x0080 #define GDC_DRAW_ON_ALPHA 0x0100 #define GDC_ANTI_ALIAS 0x0200 // Region types #define GDC_RGN_NONE 0 // No clipping #define GDC_RGN_SIMPLE 1 // Single rectangle #define GDC_RGN_COMPLEX 2 // Many rectangles // Error codes #define GDCERR_NONE 0 #define GDCERR_ERROR 1 #define GDCERR_CANT_SET_SCAN_WIDTH 2 #define GDC_INVALIDMODE -1 // Font display flags #define GDCFNT_TRANSPARENT 0x0001 // not set - SOLID #define GDCFNT_UNDERLINE 0x0002 // Palette file types #define GDCPAL_JASC 1 #define GDCPAL_MICROSOFT 2 // Misc #define BMPWIDTH(bits) ((((bits)+31)/32)<<2) // Look up tables #define Div255Lut (GdcDevice::GetInst()->GetDiv255()) // Classes class LFilter; class LSurface; class LgiClass LBmpMem { public: enum GdcMemFlags { BmpOwnMemory = 0x1, BmpPreMulAlpha = 0x2, }; uchar *Base; int x, y; ssize_t Line; LColourSpace Cs; int Flags; LBmpMem(); ~LBmpMem(); + int GetBits() + { + return LColourSpaceToBits(Cs); + } + + int BytesPerPx() + { + return LColourSpaceToBits(Cs) >> 3; + } + + uchar *AddressOf(int ox, int oy) + { + LAssert(ox >= 0 && ox < x && + oy >= 0 && oy < y); + + return Base + (oy * Line) + (ox * BytesPerPx()); + } + bool PreMul() { return (Flags & BmpPreMulAlpha) != 0; } bool PreMul(bool set) { if (set) Flags |= BmpPreMulAlpha; else Flags &= ~BmpPreMulAlpha; return PreMul(); } bool OwnMem() { return (Flags & BmpOwnMemory) != 0; } bool OwnMem(bool set) { if (set) Flags |= BmpOwnMemory; else Flags &= ~BmpOwnMemory; return OwnMem(); } - int GetBits() - { - return LColourSpaceToBits(Cs); - } - void GetMemoryExtents(uchar *&Start, uchar *&End) { if (Line < 0) { Start = Base + (y * Line); End = Base + Line; } else { Start = Base; End = Base + (y * Line); } } bool Overlap(LBmpMem *Mem) { uchar *ThisStart, *ThisEnd; GetMemoryExtents(ThisStart, ThisEnd); uchar *MemStart, *MemEnd; GetMemoryExtents(MemStart, MemEnd); if (ThisEnd < MemStart) return false; if (ThisStart > MemEnd) return false; return true; } }; #define GAPP_ALPHA_A 1 #define GAPP_ALPHA_PAL 2 #define GAPP_BACKGROUND 3 #define GAPP_ANGLE 4 #define GAPP_BOUNDS 5 /// \brief Class to draw onto a memory bitmap /// /// This class assumes that all clipping is done by the layer above. /// It can then implement very simple loops to do the work of filling /// pixels class LgiClass LApplicator { protected: LBmpMem *Dest; LBmpMem *Alpha; LPalette *Pal; int Op; public: union { COLOUR c; // main colour System24BitPixel p24; System32BitPixel p32; }; LApplicator() { c = 0; Dest = NULL; Alpha = NULL; Pal = NULL; } LApplicator(COLOUR Colour) { c = Colour; } virtual ~LApplicator() { } virtual const char *GetClass() { return "LApplicator"; } /// Get a parameter virtual int GetVar(int Var) { return 0; } /// Set a parameter virtual int SetVar(int Var, NativeInt Value) { return 0; } LColourSpace GetColourSpace() { return Dest ? Dest->Cs : CsNone; } /// Sets the operator void SetOp(int o, int Param = -1) { Op = o; } /// Gets the operator int GetOp() { return Op; } /// Gets the bit depth int GetBits() { return (Dest) ? LColourSpaceToBits(Dest->Cs) : 0; } /// Gets the flags in operation int GetFlags() { return (Dest) ? Dest->Flags : 0; } /// Gets the palette LPalette *GetPal() { return Pal; } /// Sets the bitmap to write onto virtual bool SetSurface(LBmpMem *d, LPalette *p = 0, LBmpMem *a = 0) = 0; // sets Dest, returns FALSE on error /// Sets the current position to an x,y virtual void SetPtr(int x, int y) = 0; // calculates Ptr from x, y /// Moves the current position one pixel left virtual void IncX() = 0; /// Moves the current position one scanline down virtual void IncY() = 0; /// Offset the current position virtual void IncPtr(int X, int Y) = 0; /// Sets the pixel at the current location with the current colour virtual void Set() = 0; /// Gets the colour of the pixel at the current location virtual COLOUR Get() = 0; /// Draws a vertical line from the current position down 'height' scanlines virtual void VLine(int height) = 0; /// Draws a rectangle starting from the current position, 'x' pixels across and 'y' pixels down virtual void Rectangle(int x, int y) = 0; /// Copies bitmap data to the current position virtual bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha = 0) = 0; }; /// Creates applications from parameters. class LgiClass LApplicatorFactory { public: LApplicatorFactory(); virtual ~LApplicatorFactory(); /// Find the application factory and create the appropriate object. static LApplicator *NewApp(LColourSpace Cs, int Op); virtual LApplicator *Create(LColourSpace Cs, int Op) = 0; }; class LgiClass LApp15 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp16 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp24 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp32 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp8 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LAlphaFactory : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; #define OrgX(x) x -= OriginX #define OrgY(y) y -= OriginY #define OrgXy(x, y) x -= OriginX; y -= OriginY #define OrgPt(p) p.x -= OriginX; p.y -= OriginY #define OrgRgn(r) r.Offset(-OriginX, -OriginY) /// Base class API for graphics operations class LgiClass LSurface : public LRefCount, public LDom { friend class LFilter; friend class LView; friend class LWindow; friend class LVariant; friend class LRegionClipDC; friend class LMemDC; void Init(); protected: int Flags; int PrevOp; LRect Clip; LColourSpace ColourSpace; LBmpMem *pMem; LSurface *pAlphaDC; LPalette *pPalette; LApplicator *pApp; LApplicator *pAppCache[GDC_CACHE_SIZE]; int OriginX, OriginY; // Protected functions LApplicator *CreateApplicator(int Op, LColourSpace Cs = CsNone); uint32_t LineBits; uint32_t LineMask; uint32_t LineReset; #if WINNATIVE OsPainter hDC; OsBitmap hBmp; LAutoPtr GdiplusGfx; #endif public: LSurface(); LSurface(LSurface *pDC); virtual ~LSurface(); // Win32 #if defined(__GTK_H__) /// Gets the drawable size, regardless of clipping or client rect virtual LPoint GetSize() { LPoint p; return p; } virtual Gtk::GtkPrintContext *GetPrintContext() { return NULL; } virtual Gtk::GdkPixbuf *CreatePixBuf() { return NULL; } #elif defined(WINNATIVE) virtual HDC StartDC() { return hDC; } virtual void EndDC() {} Gdiplus::Graphics *GetGfx(); Gdiplus::Color GdiColour(); #elif defined MAC virtual CGColorSpaceRef GetColourSpaceRef() { return 0; } #endif virtual const char *GetClass() { return "LSurface"; } virtual OsBitmap GetBitmap(); virtual OsPainter Handle(); virtual void SetClient(LRect *c) {} virtual bool GetClient(LRect *c) { return false; } // Creation enum SurfaceCreateFlags { SurfaceCreateNone, SurfaceRequireNative, SurfaceRequireExactCs, }; virtual bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone) { return false; } virtual void Update(int Flags) {} // Alpha channel /// Returns true if this Surface has an alpha channel virtual bool HasAlpha() { return pAlphaDC != 0; } /// Creates or destroys the alpha channel for this surface virtual bool HasAlpha(bool b); /// Returns true if we are drawing on the alpha channel bool DrawOnAlpha() { return ((Flags & GDC_DRAW_ON_ALPHA) != 0); } /// True if you want to edit the alpha channel rather than the colour bits bool DrawOnAlpha(bool Draw); /// Returns the surface of the alpha channel. LSurface *AlphaDC() { return pAlphaDC; } /// \returns the anti-alias setting bool AntiAlias(); /// Set the anti-alias setting bool AntiAlias(bool antiAlias); /// Lowers the alpha of the whole image to Alpha/255.0. /// Only works on bitmaps with an alpha channel (i.e. CsRgba32 or it's variants) bool SetConstantAlpha(uint8_t Alpha); // Create sub-images (that reference the memory of this object) LSurface *SubImage(LRect r); LSurface *SubImage(int x1, int y1, int x2, int y2) { LRect r(x1, y1, x2, y2); return SubImage(r); } // Applicator virtual bool Applicator(LApplicator *pApp); virtual LApplicator *Applicator(); // Palette virtual LPalette *Palette(); virtual void Palette(LPalette *pPal, bool bOwnIt = true); // Clip region virtual LRect ClipRgn(LRect *Rgn); virtual LRect ClipRgn(); /// Gets the current colour virtual COLOUR Colour() { return pApp->c; } /// Sets the current colour virtual COLOUR Colour ( /// The new colour COLOUR c, /// The bit depth of the new colour or 0 to indicate the depth is the same as the current Surface int Bits = 0 ); /// Sets the current colour virtual LColour Colour ( /// The new colour LColour c ); /// Sets the colour to a system colour virtual LColour Colour(LSystemColour SysCol); /// Gets the current blending mode in operation virtual int Op() { return (pApp) ? pApp->GetOp() : GDC_SET; } /// Sets the current blending mode in operation /// \sa GDC_SET, GDC_AND, GDC_OR, GDC_XOR and GDC_ALPHA virtual int Op(int Op, NativeInt Param = -1); /// Gets the width in pixels virtual int X() { return (pMem) ? pMem->x : 0; } /// Gets the height in pixels virtual int Y() { return (pMem) ? pMem->y : 0; } /// Gets the bounds of the image as a LRect LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } /// Gets the length of a scanline in bytes virtual ssize_t GetRowStep() { return (pMem) ? pMem->Line : 0; } /// Returns the resolution of the device virtual LPoint GetDpi() { return LPoint(96,96); } /// Gets the bits per pixel virtual int GetBits() { return (pMem) ? LColourSpaceToBits(pMem->Cs) : 0; } /// Gets the colour space of the pixels virtual LColourSpace GetColourSpace() { return ColourSpace; } /// Gets any flags associated with the surface virtual int GetFlags() { return Flags; } /// Returns true if the surface is on the screen virtual class LScreenDC *IsScreen() { return 0; } /// Returns true if the surface is for printing virtual bool IsPrint() { return false; } /// Returns a pointer to the start of a scanline, or NULL if not available virtual uchar *operator[](int y); /// Dumps various debug information virtual LString Dump() { return LString("Not implemented."); } /// Returns true if this surface supports alpha compositing when using Blt virtual bool SupportsAlphaCompositing() { return false; } /// \returns whether if pixel data is pre-multiplied alpha virtual bool IsPreMultipliedAlpha(); /// Converts the pixel data between pre-mul alpha or non-pre-mul alpha virtual bool ConvertPreMulAlpha(bool ToPreMul); /// Makes the alpha channel opaque virtual bool MakeOpaque(); /// Gets the surface origin virtual void GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; } /// Gets the surface origin LPoint GetOrigin() { int x, y; GetOrigin(x, y); return LPoint(x, y); } /// Sets the surface origin virtual void SetOrigin(int x, int y) { OriginX = x; OriginY = y; } /// Sets the surface origin void SetOrigin(LPoint p) { SetOrigin(p.x, p.y); } /// Sets a pixel with the current colour virtual void Set(int x, int y); /// Gets a pixel (doesn't work on some types of image, i.e. LScreenDC) virtual COLOUR Get(int x, int y); // Line /// Draw a horizontal line in the current colour virtual void HLine(int x1, int x2, int y); /// Draw a vertical line in the current colour virtual void VLine(int x, int y1, int y2); /// Draw a line in the current colour virtual void Line(int x1, int y1, int x2, int y2); /// Some surfaces only support specific line styles (e.g. GDI/Win) enum LineStyles { LineNone = 0x0, LineSolid = 0xffffffff, LineAlternate = 0xaaaaaaaa, LineDash = 0xf0f0f0f0, LineDot = 0xcccccccc, LineDashDot = 0xF33CCF30, LineDashDotDot = 0xf0ccf0cc, }; virtual uint LineStyle(uint32_t Bits, uint32_t Reset = 0x80000000) { uint32_t B = LineBits; LineBits = Bits; LineMask = LineReset = Reset; return B; } virtual uint LineStyle() { return LineBits; } // Curve /// Stroke a circle in the current colour virtual void Circle(double cx, double cy, double radius); /// Fill a circle in the current colour virtual void FilledCircle(double cx, double cy, double radius); /// Stroke an arc in the current colour virtual void Arc(double cx, double cy, double radius, double start, double end); /// Fill an arc in the current colour virtual void FilledArc(double cx, double cy, double radius, double start, double end); /// Stroke an ellipse in the current colour virtual void Ellipse(double cx, double cy, double x, double y); /// Fill an ellipse in the current colour virtual void FilledEllipse(double cx, double cy, double x, double y); // Rectangular /// Stroke a rectangle in the current colour virtual void Box(int x1, int y1, int x2, int y2); /// Stroke a rectangle in the current colour virtual void Box ( /// The rectangle, or NULL to stroke the edge of the entire surface LRect *a = NULL ); /// Fill a rectangle in the current colour virtual void Rectangle(int x1, int y1, int x2, int y2); /// Fill a rectangle in the current colour virtual void Rectangle ( /// The rectangle, or NULL to fill the entire surface LRect *a = NULL ); /// Copy an image onto the surface virtual void Blt ( /// The destination x coord int x, /// The destination y coord int y, /// The source surface LSurface *Src, /// The optional area of the source to use, if not specified the whole source is used LRect *a = NULL ); void Blt(int x, int y, LSurface *Src, LRect a) { Blt(x, y, Src, &a); } /// Not implemented virtual void StretchBlt(LRect *d, LSurface *Src, LRect *s); // Other /// Fill a polygon in the current colour virtual void Polygon(int Points, LPoint *Data); /// Stroke a bezier in the current colour virtual void Bezier(int Threshold, LPoint *Pt); /// Flood fill in the current colour (doesn't work on a LScreenDC) virtual void FloodFill ( /// Start x coordinate int x, /// Start y coordinate int y, /// Use #GDC_FILL_TO_DIFFERENT, #GDC_FILL_TO_BORDER or #GDC_FILL_NEAR int Mode, /// Fill colour COLOUR Border = 0, /// The bounds of the filled area or NULL if you don't care LRect *Bounds = NULL ); /// Describes the image virtual LString GetStr(); // LDom interface bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args); }; #if defined(MAC) && !defined(__GTK_H__) struct LPrintDcParams { #if LGI_COCOA #else PMRect Page; CGContextRef Ctx; PMResolution Dpi; #endif }; #endif #if defined(__GTK_H__) typedef Gtk::GdkWindow OsDrawable; #endif /// \brief An implemenation of LSurface to draw onto the screen. /// /// This is the class given to LView::OnPaint() most of the time. Which most of /// the time doesn't matter unless your doing something unusual. class LgiClass LScreenDC : public LSurface { class LScreenPrivate *d; public: LScreenDC(); virtual ~LScreenDC(); const char *GetClass() override { return "LScreenDC"; } // OS Sepcific #if WINNATIVE LScreenDC(LViewI *view); LScreenDC(HWND hwnd); LScreenDC(HDC hdc, HWND hwnd, bool Release = false); LScreenDC(HBITMAP hBmp, int Sx, int Sy); bool CreateFromHandle(HDC hdc); void SetSize(int x, int y); #else /// Construct a wrapper to draw on a window LScreenDC(LView *view, void *Param = 0); #if defined(LGI_SDL) #elif defined(__GTK_H__) /// Constructs a server size pixmap LScreenDC(int x, int y, int bits); /// Constructs a wrapper around a drawable LScreenDC(OsDrawable *Drawable); /// Constructs a DC for drawing on a cairo context LScreenDC(Gtk::cairo_t *cr, int x, int y); // Gtk::cairo_surface_t *GetSurface(bool Render); LPoint GetSize(); #elif defined(MAC) LScreenDC(LWindow *wnd, void *Param = 0); LScreenDC(LPrintDcParams *Params); // Used by LPrintDC LRect GetPos(); void PushState(); void PopState(); #endif OsPainter Handle() override; LView *GetView(); int GetFlags() override; LRect *GetClient(); #endif // Properties bool GetClient(LRect *c) override; void SetClient(LRect *c) override; int X() override; int Y() override; LPalette *Palette() override; void Palette(LPalette *pPal, bool bOwnIt = true) override; uint LineStyle() override; uint LineStyle(uint Bits, uint32_t Reset = 0x80000000) override; int GetBits() override; LScreenDC *IsScreen() override { return this; } bool SupportsAlphaCompositing() override; LPoint GetDpi() override; #ifndef LGI_SDL uchar *operator[](int y) override { return NULL; } void GetOrigin(int &x, int &y) override; void SetOrigin(int x, int y) override; LRect ClipRgn() override; LRect ClipRgn(LRect *Rgn) override; COLOUR Colour() override; COLOUR Colour(COLOUR c, int Bits = 0) override; LColour Colour(LColour c) override; LString Dump() override; int Op() override; int Op(int Op, NativeInt Param = -1) override; // Primitives void Set(int x, int y) override; COLOUR Get(int x, int y) override; void HLine(int x1, int x2, int y) override; void VLine(int x, int y1, int y2) override; void Line(int x1, int y1, int x2, int y2) override; void Circle(double cx, double cy, double radius) override; void FilledCircle(double cx, double cy, double radius) override; void Arc(double cx, double cy, double radius, double start, double end) override; void FilledArc(double cx, double cy, double radius, double start, double end) override; void Ellipse(double cx, double cy, double x, double y) override; void FilledEllipse(double cx, double cy, double x, double y) override; void Box(int x1, int y1, int x2, int y2) override; void Box(LRect *a) override; void Rectangle(int x1, int y1, int x2, int y2) override; void Rectangle(LRect *a = NULL) override; void Blt(int x, int y, LSurface *Src, LRect *a = NULL) override; void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL) override; void Polygon(int Points, LPoint *Data) override; void Bezier(int Threshold, LPoint *Pt) override; void FloodFill(int x, int y, int Mode, COLOUR Border = 0, LRect *Bounds = NULL) override; #endif }; /// \brief Blitting region helper class, can calculate the right source and dest rectangles /// for a blt operation including propagating clipping back to the source rect. class LBlitRegions { // Raw image bounds LRect SrcBounds; LRect DstBounds; // Unclipped blit regions LRect SrcBlt; LRect DstBlt; public: /// Clipped blit region in destination co-ords LRect SrcClip; /// Clipped blit region in source co-ords LRect DstClip; /// Calculate the rectangles. LBlitRegions ( /// Destination surface LSurface *Dst, /// Destination blt x offset int x1, /// Destination blt y offset int y1, /// Source surface LSurface *Src, /// [Optional] Crop the source surface first, else whole surface is blt LRect *SrcRc = 0 ) { // Calc full image bounds if (Src) SrcBounds.Set(0, 0, Src->X()-1, Src->Y()-1); else SrcBounds.ZOff(-1, -1); if (Dst) { DstBounds = Dst->Bounds(); int x = 0, y = 0; Dst->GetOrigin(x, y); DstBounds.Offset(x, y); } else DstBounds.ZOff(-1, -1); // Calc full sized blt regions if (SrcRc) { SrcBlt = *SrcRc; SrcBlt.Bound(&SrcBounds); } else SrcBlt = SrcBounds; DstBlt = SrcBlt; DstBlt.Offset(x1-DstBlt.x1, y1-DstBlt.y1); // Dest clipped to dest bounds DstClip = DstBlt; DstClip.Bound(&DstBounds); // Now map the dest clipping back to the source SrcClip = SrcBlt; SrcClip.x1 += DstClip.x1 - DstBlt.x1; SrcClip.y1 += DstClip.y1 - DstBlt.y1; SrcClip.x2 -= DstBlt.x2 - DstClip.x2; SrcClip.y2 -= DstBlt.y2 - DstClip.y2; } /// Returns non-zero if both clipped rectangles are valid. bool Valid() { return DstClip.Valid() && SrcClip.Valid(); } void Dump() { printf("SrcBounds: %s\n", SrcBounds.GetStr()); printf("DstBounds: %s\n", DstBounds.GetStr()); printf("SrcBlt: %s\n", SrcBlt.GetStr()); printf("DstBlt: %s\n", DstBlt.GetStr()); printf("SrcClip: %s\n", SrcClip.GetStr()); printf("DstClip: %s\n", DstClip.GetStr()); } }; #if defined(MAC) && !defined(__GTK_H__) class CGImg { class CGImgPriv *d; void Create(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r); public: CGImg(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r, int Debug = 0); CGImg(LSurface *pDC); ~CGImg(); operator CGImageRef(); void Release(); }; #endif #ifdef __GTK_H__ #include "lgi/common/CairoSurface.h" #endif /// \brief An implemenation of LSurface to draw into a memory bitmap. /// /// This class uses a block of memory to represent an image. You have direct /// pixel access as well as higher level functions to manipulate the bits. class LgiClass LMemDC : public LSurface { protected: class LMemDCPrivate *d; #if defined WINNATIVE PBITMAPINFO GetInfo(); #endif // This is called between capturing the screen and overlaying the // cursor in LMemDC::Blt(x, y, ScreenDC, Src). It can be used to // overlay effects between the screen and cursor layers. virtual void OnCaptureScreen() {} public: /// Creates a memory bitmap LMemDC ( /// The width int x = 0, /// The height int y = 0, /// The colour space to use. CsNone will default to the /// current screen colour space. LColourSpace cs = CsNone, /// Optional creation flags int Flags = SurfaceCreateNone ); LMemDC(LSurface *pDC); virtual ~LMemDC(); const char *GetClass() { return "LMemDC"; } #if WINNATIVE HDC StartDC(); void EndDC(); void Update(int Flags); void UpsideDown(bool upsidedown); #else LRect ClipRgn() { return Clip; } #if defined(__GTK_H__) LPoint GetSize(); /// This returns the surface owned by the LMemDC Gtk::cairo_surface_t *GetSurface(); /// This returns a sub-image, caller is responsible to free via /// calling cairo_surface_destroy LCairoSurfaceT GetSubImage(LRect &r); LColourSpace GetCreateCs(); Gtk::GdkPixbuf *CreatePixBuf(); #elif defined MAC OsBitmap GetBitmap(); #if LGI_COCOA && defined(__OBJC__) LMemDC(NSImage *img); NSImage *NsImage(LRect *rc = NULL); #endif #if !defined(LGI_SDL) CGColorSpaceRef GetColourSpaceRef(); CGImg *GetImg(LRect *Sub = 0, int Debug = 0); #endif #elif defined(LGI_SDL) || defined(HAIKU) OsBitmap GetBitmap(); #endif OsPainter Handle(); #endif // Set new clipping region LRect ClipRgn(LRect *Rgn); void SetClient(LRect *c); /// Locks the bits for access. LMemDC's start in the locked state. bool Lock(); /// Unlocks the bits to optimize for display. While the bitmap is unlocked you /// can't access the data for read or write. On linux this converts the XImage /// to pixmap. On other systems it doesn't do much. As a general rule if you /// don't need access to a bitmap after creating / loading it then unlock it. bool Unlock(); #if !WINNATIVE && !LGI_CARBON && !LGI_COCOA void GetOrigin(int &x, int &y); #endif void SetOrigin(int x, int y); void Empty(); bool SupportsAlphaCompositing(); bool SwapRedAndBlue(); bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL); void HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b); void VertLine(int x, int y1, int y2, COLOUR a, COLOUR b); }; /// \brief An implemenation of LSurface to print to a printer. /// /// This class redirects standard graphics calls to print a page. /// /// \sa GPrinter class LgiClass LPrintDC #if defined(WIN32) || defined(MAC) : public LScreenDC #else : public LSurface #endif { class LPrintDCPrivate *d; public: LPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName = NULL); ~LPrintDC(); const char *GetClass() override { return "LPrintDC"; } bool IsPrint() override { return true; } const char *GetOutputFileName(); int X() override; int Y() override; int GetBits() override; /// Returns the DPI of the printer or 0,0 on error LPoint GetDpi() override; #if defined __GTK_H__ Gtk::GtkPrintContext *GetPrintContext(); int Op() { return GDC_SET; } int Op(int Op, NativeInt Param = -1) { return GDC_SET; } LRect ClipRgn(LRect *Rgn); LRect ClipRgn(); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); LColour Colour(LColour c); void Set(int x, int y); void HLine(int x1, int x2, int y); void VLine(int x, int y1, int y2); void Line(int x1, int y1, int x2, int y2); void Circle(double cx, double cy, double radius); void FilledCircle(double cx, double cy, double radius); void Arc(double cx, double cy, double radius, double start, double end); void FilledArc(double cx, double cy, double radius, double start, double end); void Ellipse(double cx, double cy, double x, double y); void FilledEllipse(double cx, double cy, double x, double y); void Box(int x1, int y1, int x2, int y2); void Box(LRect *a = NULL); void Rectangle(int x1, int y1, int x2, int y2); void Rectangle(LRect *a = NULL); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s); void Polygon(int Points, LPoint *Data); void Bezier(int Threshold, LPoint *Pt); #endif }; ////////////////////////////////////////////////////////////////////////////// class LgiClass LGlobalColour { class LGlobalColourPrivate *d; public: LGlobalColour(); ~LGlobalColour(); // Add all the colours first COLOUR AddColour(COLOUR c24); bool AddBitmap(LSurface *pDC); bool AddBitmap(LImageList *il); // Then call this bool MakeGlobalPalette(); // Which will give you a palette that // includes everything LPalette *GetPalette(); // Convert a bitmap to the global palette COLOUR GetColour(COLOUR c24); bool RemapBitmap(LSurface *pDC); }; /// This class is useful for double buffering in an OnPaint handler... class LDoubleBuffer { LSurface **In; LSurface *Screen; LMemDC Mem; LRect Rgn; bool Valid; public: LDoubleBuffer(LSurface *&pDC, LRect *Sub = NULL) : In(&pDC) { Rgn = Sub ? *Sub : pDC->Bounds(); Screen = pDC; Valid = pDC && Mem.Create(Rgn.X(), Rgn.Y(), pDC->GetColourSpace()); if (Valid) { *In = &Mem; if (Sub) pDC->SetOrigin(Sub->x1, Sub->y1); } } ~LDoubleBuffer() { if (Valid) { #if WINDOWS if (Mem.Handle()) Mem.EndDC(); #endif Mem.SetOrigin(0, 0); Screen->Blt(Rgn.x1, Rgn.y1, &Mem); } // Restore state *In = Screen; } LMemDC *GetMem() { return &Mem; } }; #ifdef WIN32 typedef int (__stdcall *MsImg32_AlphaBlend)(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION); #endif /// Main singleton graphics device class. Holds all global data for graphics rendering. class LgiClass GdcDevice : public LCapabilityClient { friend class LScreenDC; friend class LMemDC; friend class LImageList; static GdcDevice *pInstance; class GdcDevicePrivate *d; #ifdef WIN32 MsImg32_AlphaBlend AlphaBlend; #endif public: GdcDevice(); ~GdcDevice(); static GdcDevice *GetInst() { return pInstance; } /// Returns the colour space of the screen LColourSpace GetColourSpace(); /// Returns the current screen bit depth int GetBits(); /// Returns the current screen width int X(); /// Returns the current screen height int Y(); /// Returns the size of the screen as a rectangle. LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } LGlobalColour *GetGlobalColour(); /// Set a global graphics option int GetOption(int Opt); /// Get a global graphics option int SetOption(int Opt, int Value); /// 256 lut for squares ulong *GetCharSquares(); /// Divide by 255 lut, 64k entries long. uchar *GetDiv255(); // Palette/Colour void SetGamma(double Gamma); double GetGamma(); // Palette void SetSystemPalette(int Start, int Size, LPalette *Pal); LPalette *GetSystemPalette(); void SetColourPaletteType(int Type); // Type = PALTYPE_xxx define COLOUR GetColour(COLOUR Rgb24, LSurface *pDC = NULL); // File I/O /// \brief Loads a image from a file /// /// This function uses the compiled in codecs, some of which require external /// shared libraries / DLL's to function. Other just need the right source to /// be compiled in. /// /// Lgi comes with the following image codecs: ///
    ///
  • Windows or OS/2 Bitmap: GdcBmp (LFilter.cpp) ///
  • PCX: GdcPcx (Pcx.cpp) ///
  • GIF: GdcGif (Gif.cpp and Lzw.cpp) ///
  • JPEG: GdcJpeg (Jpeg.cpp + libjpeg library) ///
  • PNG: GdcPng (Png.cpp + libpng library) ///
/// LSurface *Load ( /// The full path of the file const char *FileName, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// The stream version of the file loader... LSurface *Load ( /// The full path of the file LStream *In, /// [Optional] File name hint for selecting a filter const char *Name = NULL, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// Save an image to a stream. bool Save ( /// The file to write to LStream *Out, /// The pixels to store LSurface *In, /// Dummy file name to determine the file type, eg: "img.jpg" const char *FileType ); /// Save an image to a file. bool Save ( /// The file to write to const char *Name, /// The pixels to store LSurface *pDC ); #if LGI_SDL SDL_Surface *Handle(); #endif }; /// \brief Defines a bitmap inline in C++ code. /// /// The easiest way I know of create the raw data for an LInlineBmp /// is to use i.Mage to /// load a file or create a image and then use the Edit->Copy As Code /// menu. Then paste into your C++ and put a uint32 array declaration /// around it. Then point the Data member to the uint32 array. Just be /// sure to get the dimensions right. /// /// I use this for embeding resource images directly into the code so /// that a) they load instantly and b) they can't get lost as a separate file. class LgiClass LInlineBmp { public: /// The width of the image. int X; /// The height of the image. int Y; /// The bitdepth of the image (8, 15, 16, 24, 32). int Bits; /// Pointer to the raw data. uint32_t *Data; /// Creates a memory DC of the image. LSurface *Create(uint32_t TransparentPx = 0xffffffff); }; // file filter support #include "lgi/common/Filter.h" // globals #define GdcD GdcDevice::GetInst() /// Converts a context to a different bit depth LgiFunc LSurface *ConvertDC ( /// The source image LSurface *pDC, /// The destination bit depth int Bits ); /// Converts a colour to a different bit depth LgiFunc COLOUR CBit(int DstBits, COLOUR c, int SrcBits = 24, LPalette *Pal = 0); #ifdef __cplusplus /// blends 2 colours by the amount specified LgiClass LColour GdcMixColour(LColour a, LColour b, float HowMuchA = 0.5); #endif /// Colour reduction option to define what palette to go to enum LColourReducePalette { CR_PAL_NONE = -1, CR_PAL_CUBE = 0, CR_PAL_OPT, CR_PAL_FILE }; /// Colour reduction option to define how to deal with reduction error enum LColourReduceMatch { CR_MATCH_NONE = -1, CR_MATCH_NEAR = 0, CR_MATCH_HALFTONE, CR_MATCH_ERROR }; /// Colour reduction options class LReduceOptions { public: /// Type of palette LColourReducePalette PalType; /// Reduction error handling LColourReduceMatch MatchType; /// 1-256 int Colours; /// Specific palette to reduce to LPalette *Palette; LReduceOptions() { Palette = 0; Colours = 256; PalType = CR_PAL_NONE; MatchType = CR_MATCH_NONE; } }; /// Reduces a images colour depth LgiFunc bool LReduceBitDepth(LSurface *pDC, int Bits, LPalette *Pal = 0, LReduceOptions *Reduce = 0); struct LColourStop { LColour Colour; float Pos; void Set(float p, LColour c) { Pos = p; Colour = c; } }; /// Draws a horizontal or vertical gradient LgiFunc void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray &Stops); #ifdef WIN32 /// Draws a windows HICON onto a surface at Dx, Dy LgiFunc void LDrawIcon(LSurface *pDC, int Dx, int Dy, HICON ico); #endif /// Row copy operator for full RGB (8 bit components) LgiFunc bool LRopRgb ( // Pointer to destination pixel buffer uint8_t *Dst, // Destination colour space (must be 8bit components) LColourSpace DstCs, // Pointer to source pixel buffer (if this overlaps 'Dst', set 'Overlap' to true) uint8_t *Src, // Source colour space (must be 8bit components) LColourSpace SrcCs, // Number of pixels to convert int Px, // Whether to composite using alpha or copy blt bool Composite ); /// Universal bit blt method LgiFunc bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite); /// Gets the screens DPI LgiClass LPoint LScreenDpi(); /// Find the bounds of an image. /// \return true if there is some non-transparent image in 'rc' LgiFunc bool LFindBounds ( /// [in] The image LSurface *pDC, /// [in/out] Starts off as the initial bounds to search. /// Returns the non-background area. LRect *rc ); #if defined(LGI_SDL) LgiFunc LColourSpace PixelFormat2ColourSpace(SDL_PixelFormat *pf); #endif #endif diff --git a/include/lgi/common/Html.h b/include/lgi/common/Html.h --- a/include/lgi/common/Html.h +++ b/include/lgi/common/Html.h @@ -1,169 +1,169 @@ /// \file /// \author Matthew Allen #pragma once #include "lgi/common/DocView.h" #include "lgi/common/HtmlCommon.h" #include "lgi/common/HtmlParser.h" #include "lgi/common/Capabilities.h" #include "lgi/common/ToolTip.h" #include "lgi/common/FindReplaceDlg.h" namespace Html1 { class LTag; class LFontCache; /// A lightwight scripting safe HTML control. It has limited CSS support, renders /// most tables, even when nested. You can provide support for loading external /// images by implementing the LDocumentEnv::GetImageUri method of the /// LDocumentEnv interface and passing it into LHtml2::SetEnv. /// All attempts to open URL's are passed into LDocumentEnv::OnNavigate method of the /// environment if set. Likewise any content inside active scripting tags, e.g. <? ?> /// will be striped out of the document and passed to LDocumentEnv::OnDynamicContent, which /// should return the relevant HTML that the script resolves to. A reasonable default /// implementation of the LDocumentEnv interface is the LDefaultDocumentEnv object. /// /// You can set the content of the control through the LHtml2::Name method. /// /// Retreive any selected content through LHtml2::GetSelection. class LHtml : public LDocView, public ResObject, public LHtmlParser, public LCapabilityClient { friend class LTag; friend class LFlowRegion; class LHtmlPrivate *d; protected: // Data LFontCache *FontCache; LTag *Tag; // Tree root LTag *Cursor; // Cursor location.. LTag *Selection; // Edge of selection or NULL char IsHtml; int ViewWidth; uint64_t PaintStart; LToolTip Tip; LCss::Store CssStore; LHashTbl, bool> CssHref; // Display LAutoPtr MemDC; // This lock is separate from the window lock to avoid deadlocks. struct GJobSem : public LMutex { // Data that has to be accessed under Lock LArray Jobs; GJobSem() : LMutex("GJobSem") {} } JobSem; // Methods void _New(); void _Delete() override; LFont *DefFont(); void CloseTag(LTag *t); void ParseDocument(const char *Doc); void OnAddStyle(const char *MimeType, const char *Styles) override; int ScrollY(); void SetCursorVis(bool b); bool GetCursorVis(); LRect *GetCursorPos(); bool IsCursorFirst(); bool CompareTagPos(LTag *a, ssize_t AIdx, LTag *b, ssize_t BIdx); int GetTagDepth(LTag *Tag); LTag *PrevTag(LTag *t); LTag *NextTag(LTag *t); LTag *GetLastChild(LTag *t); public: LHtml(int Id, int x, int y, int cx, int cy, LDocumentEnv *system = 0); ~LHtml(); // Html const char *GetClass() override { return "LHtml"; } bool GetFormattedContent(const char *MimeType, LString &Out, LArray *Media = 0) override; /// Get the tag at an x,y location LTag *GetTagByPos( int x, int y, ssize_t *Index, LPoint *LocalCoords = NULL, bool DebugLog = false); /// Layout content and return size. LPoint Layout(bool ForceLayout = false); // Options bool GetLinkDoubleClick(); void SetLinkDoubleClick(bool b); void SetLoadImages(bool i) override; bool GetEmoji(); void SetEmoji(bool i); void SetMaxPaintTime(int Ms); bool GetMaxPaintTimeout(); // LDocView bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; /// Copy the selection to the clipboard bool Copy() override; /// Returns true if there is a selection bool HasSelection() override; /// Unselect all the text in the control void UnSelectAll() override; /// Select all the text in the control (not impl) void SelectAll() override; /// Return the selection in a dynamically allocated string char *GetSelection() override; // Prop // Window /// Sets the HTML content of the control bool Name(const char *s) override; /// Returns the HTML content const char *Name() override; /// Sets the HTML content of the control bool NameW(const char16 *s) override; /// Returns the HTML content const char16 *NameW() override; // Impl void OnPaint(LSurface *pDC) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; LCursor GetCursor(int x, int y) override; bool OnMouseWheel(double Lines) override; bool OnKey(LKey &k) override; int OnNotify(LViewI *c, LNotification n) override; void OnPosChange() override; void OnPulse() override; LMessage::Result OnEvent(LMessage *Msg) override; const char *GetMimeType() override { return "text/html"; } void OnContent(LDocumentEnv::LoadJob *Res) override; bool GotoAnchor(char *Name); LHtmlElement *CreateElement(LHtmlElement *Parent) override; bool EvaluateCondition(const char *Cond) override; bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; - bool DoFind() override; + void DoFind(std::function Callback) override; LPointF GetDpiScale(); void SetVScroll(int64 v); // Javascript handlers LDom *getElementById(char *Id); // Events bool OnFind(LFindReplaceCommon *Params); virtual bool OnSubmitForm(LTag *Form); virtual void OnCursorChanged() {} virtual void OnLoad(); virtual bool OnContextMenuCreate(struct LTagHit &Hit, LSubMenu &RClick) { return true; } virtual void OnContextMenuCommand(struct LTagHit &Hit, int Cmd) {} }; } diff --git a/include/lgi/common/LgiClasses.h b/include/lgi/common/LgiClasses.h --- a/include/lgi/common/LgiClasses.h +++ b/include/lgi/common/LgiClasses.h @@ -1,194 +1,197 @@ /** \file \author Matthew Allen \date 19/12/1997 \brief Gui class definitions Copyright (C) 1997-2004, Matthew Allen */ ///////////////////////////////////////////////////////////////////////////////////// // Includes #ifndef _LGICLASSES_H_ #define _LGICLASSES_H_ #include "lgi/common/Mutex.h" #include "LgiOsClasses.h" #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/DragAndDrop.h" ///////////////////////////////////////////////////////////////////////////////////// LgiFunc bool LPostEvent(OsView Wnd, int Event, LMessage::Param a = 0, LMessage::Param b = 0); LgiFunc LViewI *GetNextTabStop(LViewI *v, bool Back); -class LgiClass LCommand : public LBase //, public GFlags +class LgiClass LCommand : public LBase { - int Flags; - bool PrevValue; + int Flags = GWF_VISIBLE; + bool PrevValue = false; public: - int Id; - LToolButton *ToolButton; - LMenuItem *MenuItem; - LKey *Accelerator; - char *TipHelp; + int Id = 0; + LToolButton *ToolButton = NULL; + LMenuItem *MenuItem = NULL; + LAutoPtr Accelerator; + LString TipHelp; LCommand(); ~LCommand(); bool Enabled(); void Enabled(bool e); bool Value(); void Value(bool v); }; #include "lgi/common/TrayIcon.h" #include "lgi/common/Input.h" #ifndef LGI_STATIC /// \brief A BeOS style alert window, kinda like a Win32 MessageBox /// /// The best thing about this class is you can name the buttons very specifically. /// It's always non-intuitive to word a question to the user in such a way so thats /// it's obvious to answer with "Ok" or "Cancel". But if the user gets a question /// with customized "actions" as buttons they'll love you. /// /// The button pressed is returned as a index from the DoModal() function. Starting /// at '1'. i.e. Btn2 -> returns 2. class LgiClass LAlert : public LDialog { + LArray> Callbacks; + public: /// Constructor LAlert ( /// The parent view LViewI *parent, /// The dialog title const char *Title, /// The body of the message const char *Text, /// The first button text const char *Btn1, /// The [optional] 2nd buttons text const char *Btn2 = 0, /// The [optional] 3rd buttons text const char *Btn3 = 0 ); void SetAppModal(); int OnNotify(LViewI *Ctrl, LNotification n); + void SetButtonCallback(int ButtonIdx, std::function Callback); }; #endif /// Timer class to help do something every so often class LgiClass DoEvery { int64 LastTime; int64 Period; public: /// Constructor DoEvery ( /// Timeout in ms int p = 1000 ); /// Reset the timer void Init ( /// Timeout in ms int p = -1 ); /// Returns true when the time has expired. Resets automatically. bool DoNow(); }; ////////////////////////////////////////////////////////// // Graphics LgiFunc void LDrawBox(LSurface *pDC, LRect &r, bool Sunken, bool Fill); LgiFunc void LWideBorder(LSurface *pDC, LRect &r, LEdge Type); LgiFunc void LThinBorder(LSurface *pDC, LRect &r, LEdge Type); LgiFunc void LFlatBorder(LSurface *pDC, LRect &r, int Width = -1); // Helpers #ifdef __GTK_H__ extern Gtk::gboolean GtkViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *This); #endif #ifdef LINUX /// Ends a x windows startup session LgiFunc void LFinishXWindowsStartup(class LViewI *Wnd); #endif /// \brief Displays a message box /// \returns The button clicked. The return value is one of #IDOK, #IDCANCEL, #IDYES or #IDNO. LgiFunc int LgiMsg ( /// The parent view or NULL if none available LViewI *Parent, /// The message's text. This is a printf format string that you can pass arguments to const char *Msg, /// The title of the message box window const char *Title = 0, /// The type of buttons below the message. Can be one of: /// #MB_OK, #MB_OKCANCEL, #MB_YESNO or #MB_YESNOCANCEL. int Type = MB_OK, ... ); /// This is like LgiMsg but displays the text in a scrollable view. LgiFunc void LDialogTextMsg(LViewI *Parent, const char *Title, LString Txt); /// Contains all the information about a display/monitor attached to the system. /// \sa LGetDisplays struct GDisplayInfo { /// The position and dimensions of the display. On windows the left/upper /// most display will be positioned at 0,0 and each further display will have /// co-ordinates that join to one edge of that initial rectangle. LRect r; /// The number of bits per pixel int BitDepth; /// The refresh rate int Refresh; /// The device's path, system specific LString Device; /// A descriptive name of the device, usually the video card LString Name; /// The name of any attached monitor LString Monitor; /// The dots per inch of the display LPoint Dpi; GDisplayInfo() { r.ZOff(-1, -1); BitDepth = 0; Refresh = 0; } LPointF Scale(); }; /// Returns infomation about the displays attached to the system. /// \returns non-zero on success. LgiFunc bool LGetDisplays ( /// [out] The array of display info structures. The caller should free these /// objects using Displays.DeleteObjects(). LArray &Displays, /// [out] Optional bounding rectangle of all displays. Can be NULL if your don't /// need that information. LRect *AllDisplays = 0 ); #include "lgi/common/Profile.h" #endif diff --git a/include/lgi/common/LgiCommon.h b/include/lgi/common/LgiCommon.h --- a/include/lgi/common/LgiCommon.h +++ b/include/lgi/common/LgiCommon.h @@ -1,420 +1,417 @@ /** \file \author Matthew Allen \date 27/3/2000 \brief Common LGI include Copyright (C) 2000-2004, Matthew Allen */ /** * \defgroup Base Foundation tools * \ingroup Lgi */ /** * \defgroup Text Text handling * \ingroup Lgi */ #ifndef _LGI_COMMON_H #define _LGI_COMMON_H #if defined LINUX #include #endif #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/LgiString.h" #include "lgi/common/StringClass.h" #include "lgi/common/CurrentTime.h" #include "lgi/common/LgiUiBase.h" /// Returns the system path specified /// \ingroup Base LgiExtern LString LGetSystemPath( /// Which path to retreive LSystemPath Which, int WordSize = 0 ); /// Gets the path of the currently running executable /// \ingroup Base LgiExtern LString LGetExePath(); /// Gets the file of the currently running executable /// \ingroup Base LgiExtern LString LGetExeFile(); /// Returns the mime type of the file /// \ingroup Mime LgiExtern LString LGetFileMimeType ( /// File to find mime type for const char *File ); /// Finds a file in the applications directory or nearby /// \ingroup Base LgiExtern LString LFindFile(const char *Name); /// Returns the application associated with the mime type /// \ingroup Mime LgiExtern LString LGetAppForMimeType ( /// Type of the file to find and app for const char *Mime ); /// \return a formatted file size LgiExtern LString LFormatSize(int64_t Size); /// URL encode a string LgiExtern LString LUrlEncode(const char *s, const char *delim); /// URL decode a string LgiExtern LString LUrlDecode(const char *s); /// Gets the current user LgiExtern LString LCurrentUserName(); /// Returns an environment variable. LgiExtern LString LGetEnv(const char *Var); /// Gets the system path.. LgiExtern LString::Array LGetPath(); /// Check for a valid email string LgiExtern bool LIsValidEmail(LString Email); /// Finds an application to handle a protocol request (e.g. 'mailto', 'http' etc) LgiExtern LString LGetAppForProtocol(const char *Protocol); /// Converts a string to the native 8bit charset of the OS from utf-8 /// \ingroup Text LgiExtern LString LToNativeCp(const char *In, ssize_t InLen = -1); /// Converts a string from the native 8bit charset of the OS to utf-8 /// \ingroup Text LgiExtern LString LFromNativeCp(const char *In, ssize_t InLen = -1); LgiExtern LString LStrConvertCp ( /// Output charset const char *OutCp, /// Input buffer const void *In, /// The input data's charset const char *InCp, /// Bytes of valid data in the input ssize_t InLen = -1 ); -/// Converts an OS error code into a text string -LgiExtern LString LErrorCodeToString(uint32_t ErrorCode); - #ifdef __cplusplus extern "C" { #endif ///////////////////////////////////////////////////////////// // Externs // Codepages /// Converts a buffer of text to a different charset /// \ingroup Text /// \returns the bytes written to the location pointed to by 'Out' LgiFunc ssize_t LBufConvertCp(void *Out, const char *OutCp, ssize_t OutLen, const void *&In, const char *InCp, ssize_t &InLen); /// \brief Converts a string to a new charset /// \return A dynamically allocate, null terminated string in the new charset /// \ingroup Text LgiFunc void *LNewConvertCp ( /// Output charset const char *OutCp, /// Input buffer const void *In, /// The input data's charset const char *InCp, /// Bytes of valid data in the input ssize_t InLen = -1 ); /// Return true if Lgi support the charset /// \ingroup Text LgiFunc bool LIsCpImplemented(const char *Cp); /// Converts the ANSI code page to a charset name /// \ingroup Text LgiFunc const char *LAnsiToLgiCp(int AnsiCodePage = -1); /// Calculate the number of characters in a string /// \ingroup Text LgiFunc int LCharLen(const void *Str, const char *Cp, int Bytes = -1); /// Move a pointer along a utf-8 string by characters /// \ingroup Text LgiFunc char *LSeekUtf8 ( /// Pointer to the current character const char *Ptr, /// The number of characters to move forward or back ssize_t D, /// The start of the memory buffer if you known char *Start = 0 ); /// Return true if the string is valid utf-8 /// \ingroup Text LgiFunc bool LIsUtf8(const char *s, ssize_t len = -1); /// Returns the next token in a string, leaving the argument pointing to the end of the token /// \ingroup Text LgiFunc char *LTokStr(const char *&s); /// Formats a data size into appropriate units /// \ingroup Base LgiFunc void LFormatSize ( /// Output string char *Str, /// Output string buffer length int SLen, /// Input size in bytes int64_t Size ); /// \returns true if the path is a volume root. LgiFunc bool LIsVolumeRoot(const char *Path); /// Converts a string from URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LDecodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); /// Converts a string to URI encoding (ala %20 -> ' ') /// \returns a dynamically allocated string or NULL on error /// \ingroup Text LgiFunc char *LEncodeUri ( /// The URI const char *uri, /// The length or -1 if NULL terminated int len = -1 ); // Path #if LGI_COCOA || defined(__GTK_H__) || defined(HAIKU) LgiExtern LString LgiArgsAppPath; #endif /// Returns the system path specified /// \ingroup Base LgiFunc bool LGetSystemPath ( /// Which path to retreive LSystemPath Which, /// The buffer to receive the path into char *Dst, /// The size of the receive buffer in bytes ssize_t DstSize ); /// \brief Recursively search for files /// \return Non zero if something was found /// \ingroup Base LgiFunc bool LRecursiveFileSearch ( /// Start search in this dir const char *Root, /// Extensions to match LArray *Ext = NULL, /// [optional] Output filenames LArray *Files = NULL, /// [optional] Output total size uint64 *Size = NULL, /// [optional] File count uint64 *Count = NULL, /// [optional] Callback for match std::function Callback = NULL, /// [optional] Cancel object LCancel *Cancel = NULL ); // Resources /// Gets the currently selected language /// \ingroup Resources LgiFunc struct LLanguage *LGetLanguageId(); // Os version functions /// Gets the current operating system and optionally it's version. /// \returns One of the defines starting with #LGI_OS_UNKNOWN in LgiDefs.h /// \ingroup Base LgiFunc int LGetOs(LArray *Ver = 0); /// Gets the current operation systems name. /// \ingroup Base LgiFunc const char *LGetOsName(); // System /// \brief Opens a file or directory. /// /// If the input is an executable then it is run. If the input file /// is a document then an appropriate application is found to open the /// file and the file is passed to that application. If the input is /// a directory then the OS's file manager is openned to browse the /// directory. /// /// \ingroup Base LgiFunc bool LExecute ( /// The file to open const char *File, /// The arguments to pass to the program const char *Arguments="", /// The directory to run in const char *Dir = 0, /// An error message LString *ErrorMsg = NULL ); /// Initializes the random number generator /// \ingroup Base LgiFunc void LRandomize(uint Seed); /// Returns a random number between 0 and Max-1 /// \ingroup Base LgiFunc uint LRand(uint Max = 0); LgiFunc bool _lgi_read_colour_config(const char *Tag, uint32_t *c); #ifndef SND_ASYNC #define SND_ASYNC 0x0001 #endif /// Plays a sound /// \ingroup Base LgiFunc bool LPlaySound ( /// File name of the sound to play const char *FileName, /// 0 or SND_ASYNC. If 0 the function blocks till the sound finishes. int Flags ); /** * \defgroup Mime Mime handling support. * \ingroup Lgi */ /// Returns the file extensions associated with the mimetype /// \ingroup Mime LgiExtern bool LGetMimeTypeExtensions ( /// The returned mime type const char *Mime, /// The extensions LArray &Ext ); inline bool LGetAppForMimeType(const char *Mime, char *AppPath, int BufSize) { LString p = LGetAppForMimeType(Mime); if (AppPath && p) strcpy_s(AppPath, BufSize, p); return p.Length() > 0; } /// Returns the all applications that can open a given mime type. /// \ingroup Mime LgiFunc bool LGetAppsForMimeType ( /// The type of files to match apps to. /// /// Two special cases exist: /// - application/email gets the default email client /// - application/browser get the default web browser const char *Mime, /// The applications that can handle the - LArray &Apps, + LArray &Apps, /// Limit the length of the results, i.e. stop looking after 'Limit' matches. /// -1 means return all matches. int Limit = -1 ); // Debug /// Returns true if the build is for release. /// \ingroup Base LgiFunc int LIsReleaseBuild(); #if defined WIN32 /// Registers an active x control LgiFunc bool RegisterActiveXControl(const char *Dll); enum HWBRK_TYPE { HWBRK_TYPE_CODE, HWBRK_TYPE_READWRITE, HWBRK_TYPE_WRITE, }; enum HWBRK_SIZE { HWBRK_SIZE_1, HWBRK_SIZE_2, HWBRK_SIZE_4, HWBRK_SIZE_8, }; /// Set a hardware breakpoint. LgiFunc HANDLE SetHardwareBreakpoint ( /// Use GetCurrentThread() HANDLE hThread, /// Type of breakpoint HWBRK_TYPE Type, /// Size of breakpoint HWBRK_SIZE Size, /// The pointer to the data to break on void *s ); /// Deletes a hardware breakpoint LgiFunc bool RemoveHardwareBreakpoint(HANDLE hBrk); #elif defined LINUX /// Window managers enum WindowManager { WM_Unknown, WM_Kde, WM_Gnome }; /// Returns the currently running window manager WindowManager LGetWindowManager(); #elif defined(__OBJC__) LgiFunc NSCursor *LCocoaCursor(LCursor lc); #endif #ifdef __cplusplus } #endif #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) = 0; + 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 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() = 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/LgiUiBase.h b/include/lgi/common/LgiUiBase.h --- a/include/lgi/common/LgiUiBase.h +++ b/include/lgi/common/LgiUiBase.h @@ -1,421 +1,421 @@ #ifndef _LGI_CLASS_H_ #define _LGI_CLASS_H_ #include #include "lgi/common/LgiInc.h" #include "lgi/common/LgiDefs.h" #include "lgi/common/AutoPtr.h" #include "lgi/common/StringClass.h" #include "lgi/common/Point.h" #if defined(__OBJC__) && !defined(__GTK_H__) #include #endif #include // Virtual input classes class LKey; class LMouse; // General GUI classes class LApp; class LWindow; class LWindowsClass; class LView; class LLayout; class LFileSelect; class LSubMenu; class LMenuItem; class LMenu; class LToolBar; class LToolButton; class LSplitter; class LStatusPane; class LStatusBar; class LScrollBar; class LImageList; class LDialog; // Tracing: /// Sets the output stream for the LgiTrace statement. By default the stream output /// is to .txt in the executables folder or $LSP_APP_ROOT\.txt if /// that is not writable. If the stream is set to something then normal file output is /// directed to the specified stream instead. LgiFunc void LgiTraceSetStream(class LStreamI *stream); /// Gets the log file path LgiFunc bool LgiTraceGetFilePath(char *LogPath, int BufLen); /// Writes a debug statement to a output stream, or if not defined with LgiTraceSetStream /// then to a log file (see LgiTraceSetStream for details) /// /// Default path is ./.txt relative to the executable. /// Fall back path is LgiGetSystemPath(LSP_APP_ROOT). LgiFunc void LgiTrace(const char *Format, ...); #ifndef LGI_STATIC /// Same as LgiTrace but writes a stack trace as well. LgiFunc void LStackTrace(const char *Format, ...); #endif // Template hash function: template RESULT LHash(const CHAR *v, ssize_t l, bool Case) { RESULT h = 0; if (Case) { // case sensitive if (l > 0) { while (l--) h = (h << 5) - h + *v++; } else { for (; *v; v ++) h = (h << 5) - h + *v; } } else { // case insensitive CHAR c; if (l > 0) { while (l--) { c = tolower(*v); v++; h = (h << 5) - h + c; } } else { for (; *v; v++) { c = tolower(*v); h = (h << 5) - h + c; } } } return h; } // Template sort function: template void LSort(T *v, ssize_t left, ssize_t right, std::function comp) { ssize_t i, last; if (left >= right) return; LSwap(v[left], v[(left + right)>>1]); last = left; for (i = left+1; i <= right; i++) if (comp(v[i], v[left]) < 0) /* Here's the function call */ LSwap(v[++last], v[i]); LSwap(v[left], v[last]); LSort(v, left, last-1, comp); LSort(v, last+1, right, comp); } #define AssignFlag(f, bit, to) if (to) f |= bit; else f &= ~(bit) // OsEvent is defined here because the LUiEvent is the primary user. // And this header can be included independently of LgiOsDefs.h where // this would otherwise live. #if defined __GTK_H__ typedef Gtk::GdkEvent *OsEvent; #elif defined _SDL_H typedef SDL_Event *OsEvent; #elif defined _WIN32 // No native type here, but LMessage can encapsulate the message code, WPARAM and LPARAM. typedef class LMessage *OsEvent; #elif LGI_COCOA #include "ObjCWrapper.h" ObjCWrapper(NSEvent, OsEvent); #elif LGI_CARBON typedef EventRef OsEvent; #elif LGI_HAIKU - typedef BMessage OsEvent; + typedef BMessage *OsEvent; #else #error "Impl me." #endif /// General user interface event class LgiClass LUiEvent { public: int Flags; OsEvent Event; LUiEvent() { Flags = 0; } virtual ~LUiEvent() {} virtual void Trace(const char *Msg) const {} /// The key or mouse button was being pressed. false on the up-click. bool Down() const { return TestFlag(Flags, LGI_EF_DOWN); } /// The mouse button was double clicked. bool Double() const { return TestFlag(Flags, LGI_EF_DOUBLE); } /// A ctrl button was held down during the event bool Ctrl() const { return TestFlag(Flags, LGI_EF_CTRL); } /// A alt button was held down during the event bool Alt() const { return TestFlag(Flags, LGI_EF_ALT); } /// A shift button was held down during the event bool Shift() const { return TestFlag(Flags, LGI_EF_SHIFT); } /// The system key was held down (windows key / apple key etc) bool System() const { return TestFlag(Flags, LGI_EF_SYSTEM); } // Set void Down(bool i) { AssignFlag(Flags, LGI_EF_DOWN, i); } void Double(bool i) { AssignFlag(Flags, LGI_EF_DOUBLE, i); } void Ctrl(bool i) { AssignFlag(Flags, LGI_EF_CTRL, i); } void Alt(bool i) { AssignFlag(Flags, LGI_EF_ALT, i); } void Shift(bool i) { AssignFlag(Flags, LGI_EF_SHIFT, i); } void System(bool i) { AssignFlag(Flags, LGI_EF_SYSTEM, i); } #if defined(MAC) bool CtrlCmd() const { return System(); } static const char *CtrlCmdName() { return "\xE2\x8C\x98"; } bool AltCmd() const { return System(); } static const char *AltCmdName() { return "\xE2\x8C\x98"; } + #elif defined(HAIKU) + bool CtrlCmd() const { return System(); } + static const char *CtrlCmdName() { return "System"; } + bool AltCmd() const { return System(); } + static const char *AltCmdName() { return "System"; } #else // win32 and linux bool CtrlCmd() const { return Ctrl(); } static const char *CtrlCmdName() { return "Ctrl"; } bool AltCmd() const { return Alt(); } static const char *AltCmdName() { return "Alt"; } #endif #if LGI_COCOA void SetModifer(uint32_t modifierKeys); #else void SetModifer(uint32_t modifierKeys) { #if defined(__GTK_H__) System((modifierKeys & Gtk::GDK_MOD4_MASK) != 0); Shift((modifierKeys & Gtk::GDK_SHIFT_MASK) != 0); Alt((modifierKeys & Gtk::GDK_MOD1_MASK) != 0); Ctrl((modifierKeys & Gtk::GDK_CONTROL_MASK) != 0); #elif LGI_CARBON System(modifierKeys & cmdKey); Shift(modifierKeys & shiftKey); Alt(modifierKeys & optionKey); Ctrl(modifierKeys & controlKey); #endif } #endif }; #ifdef WINDOWS struct LKeyWinBits { unsigned Repeat : 16; unsigned Scan : 8; unsigned Extended : 1; unsigned Reserved : 4; unsigned Context : 1; unsigned Previous : 1; unsigned Pressed : 1; }; #endif /// All the information related to a keyboard event class LgiClass LKey : public LUiEvent { public: /// The virtual code for key /// Rule: Only compare with LK_??? symbols - char16 vkey; + char16 vkey = 0; /// The unicode character for the key /// Rule: Never compare with LK_??? symbols - char16 c16; + char16 c16 = 0; /// OS Specific #ifdef WINDOWS union { #endif - uint32_t Data; + uint32_t Data = 0; #ifdef WINDOWS LKeyWinBits WinBits; }; #endif /// True if this is a standard character (ie not a control key) - bool IsChar; + bool IsChar = false; - LKey() - { - vkey = 0; - c16 = 0; - Data = 0; - IsChar = 0; - } - + LKey() {} LKey(int vkey, uint32_t flags); void Trace(const char *Msg) const { LgiTrace("%s LKey vkey=%i(0x%x) c16=%i(%c) IsChar=%i down=%i ctrl=%i alt=%i sh=%i sys=%i\n", Msg ? Msg : (char*)"", vkey, vkey, c16, c16 >= ' ' && c16 < 127 ? c16 : '.', IsChar, Down(), Ctrl(), Alt(), Shift(), System()); } bool CapsLock() const { return TestFlag(Flags, LGI_EF_CAPS_LOCK); } /// Returns the character in the right case... char16 GetChar() const { if (Shift() ^ CapsLock()) { return (c16 >= 'a' && c16 <= 'z') ? c16 - 'a' + 'A' : c16; } else { return (c16 >= 'A' && c16 <= 'Z') ? c16 - 'A' + 'a' : c16; } } /// \returns true if this event should show a context menu bool IsContextMenu() const; }; /// \brief All the parameters of a mouse click event /// /// The parent class LUiEvent keeps information about whether it was a Down() /// or Double() click. You can also query whether the Alt(), Ctrl() or Shift() /// keys were pressed at the time the event occurred. /// /// To get the position of the mouse in screen co-ordinates you can either use /// LView::GetMouse() and pass true in the 'ScreenCoords' parameter. Or you can /// construct a LPoint out of the x,y fields of this class and use LView::PointToScreen() /// to map the point to screen co-ordinates. class LgiClass LMouse : public LUiEvent, public LPoint { public: /// Receiving view class LViewI *Target; /// True if specified in view coordinates, false if in screen coords bool ViewCoords; LMouse(LViewI *target = NULL) { Target = target; ViewCoords = true; } LMouse operator -(LPoint p) { LMouse m = *this; m.x -= p.x; m.y -= p.y; return m; } LMouse operator +(LPoint p) { LMouse m = *this; m.x += p.x; m.y += p.y; return m; } bool operator !=(const LMouse &m) { return x != m.x || y != m.y || Flags != m.Flags; } LString ToString() const; void Trace(const char *Msg) const { LgiTrace("%s %s\n", Msg ? Msg : (char*)"", ToString().Get()); } bool Left() const { return TestFlag(Flags, LGI_EF_LEFT); } bool Middle() const { return TestFlag(Flags, LGI_EF_MIDDLE); } bool Right() const { return TestFlag(Flags, LGI_EF_RIGHT); } bool Button1() const { return TestFlag(Flags, LGI_EF_XBTN1); } bool Button2() const { return TestFlag(Flags, LGI_EF_XBTN2); } bool IsMove() const { return TestFlag(Flags, LGI_EF_MOVE); } void Left(bool i) { AssignFlag(Flags, LGI_EF_LEFT, i); } void Middle(bool i) { AssignFlag(Flags, LGI_EF_MIDDLE, i); } void Right(bool i) { AssignFlag(Flags, LGI_EF_RIGHT, i); } void Button1(bool i) { AssignFlag(Flags, LGI_EF_XBTN1, i); } void Button2(bool i) { AssignFlag(Flags, LGI_EF_XBTN2, i); } void IsMove(bool i) { AssignFlag(Flags, LGI_EF_MOVE, i); } /// Converts to screen coordinates bool ToScreen(); /// Converts to local coordinates bool ToView(); /// \returns true if this event should show a context menu bool IsContextMenu() const; #if defined(__OBJC__) && !defined(__GTK_H__) void SetFromEvent(NSEvent *ev, NSView *view); #else void SetButton(uint32_t Btn) { #if defined(MAC) && defined(__CARBONEVENTS__) Left(Btn == kEventMouseButtonPrimary); Right(Btn == kEventMouseButtonSecondary); Middle(Btn == kEventMouseButtonTertiary); #elif defined(__GTK_H__) if (Btn == 1) Left(true); else if (Btn == 2) Middle(true); else if (Btn == 3) Right(true); #endif } #endif }; /// Holds information pertaining to an application class LAppInfo { public: + /// Mime type for the app + LString MimeType; /// The path to the executable for the app LString Path; /// Plain text name for the app LString Name; /// A path to an icon to display for the app LString Icon; /// The params to call the app with LString Params; }; // Base class for GUI objects class LgiClass LBase { char *_Name8; char16 *_Name16; public: LBase(); virtual ~LBase(); virtual const char *Name(); virtual bool Name(const char *n); virtual const char16 *NameW(); virtual bool NameW(const char16 *n); }; #endif diff --git a/include/lgi/common/Mdi.h b/include/lgi/common/Mdi.h --- a/include/lgi/common/Mdi.h +++ b/include/lgi/common/Mdi.h @@ -1,102 +1,104 @@ #ifndef _GMDI_H_ #define _GMDI_H_ #include "lgi/common/Layout.h" #define MDI_TAB_STYLE 1 class LMdiChild : public LLayout { friend class LMdiParent; class LMdiChildPrivate *d; public: LMdiChild(); ~LMdiChild(); const char *GetClass() { return "LMdiChild"; } #if MDI_TAB_STYLE int GetOrder(); #else void OnPaint(LSurface *pDC); void OnMouseClick(LMouse &m); void OnMouseMove(LMouse &m); LCursor GetCursor(int x, int y); LRect &GetClient(bool InClientSpace = true); #endif bool Attach(LViewI *p); bool Detach(); bool PourAll(); const char *Name(); bool Name(const char *n); virtual void Raise(); virtual void Lower(); virtual void OnTitleClick(LMouse &m); virtual void OnButtonClick(LMouse &m); virtual void OnPaintButton(LSurface *pDC, LRect &rc); }; class LMdiParent : public LLayout { friend class LMdiChild; class LMdiParentPrivate *d; #if MDI_TAB_STYLE int GetNextOrder(); #endif LMdiChild *IsChild(LViewI *v); ::LArray &PrivChildren(); + + bool SetScrollBars(bool x, bool y); public: LMdiParent(); ~LMdiParent(); const char *GetClass() { return "LMdiParent"; } bool HasButton(); void HasButton(bool b); void OnPaint(LSurface *pDC); bool Attach(LViewI *p); bool OnViewMouse(LView *View, LMouse &m); bool OnViewKey(LView *View, LKey &Key); void OnChildrenChanged(LViewI *Wnd, bool Attaching); LRect NewPos(); LViewI *GetTop(); template bool GetChildren(::LArray &Views) { for (auto c : PrivChildren()) { T *t = dynamic_cast(c); if (t) Views.Add(t); } #if MDI_TAB_STYLE Views.Sort ( [] (T **a, T **b) { return (*a)->GetOrder() - (*b)->GetOrder(); } ); #endif return Views.Length() > 0; } #if MDI_TAB_STYLE void OnPosChange(); void OnMouseClick(LMouse &m); bool Detach(); #endif }; #endif diff --git a/include/lgi/common/Menu.h b/include/lgi/common/Menu.h --- a/include/lgi/common/Menu.h +++ b/include/lgi/common/Menu.h @@ -1,563 +1,572 @@ /** \file \author Matthew Allen */ - + #ifndef _LMENU_H_ #define _LMENU_H_ // Os specific declarations #if defined __GTK_H__ typedef Gtk::GtkMenuShell *OsSubMenu; typedef Gtk::GtkMenuItem *OsMenuItem; #elif defined WINNATIVE typedef HMENU OsSubMenu; typedef MENUITEMINFO OsMenuItem; #ifndef COLOR_MENUHILIGHT #define COLOR_MENUHILIGHT 29 #endif #ifndef COLOR_MENUBAR #define COLOR_MENUBAR 30 #endif #elif defined(MAC) && !defined(LGI_SDL) #if LGI_COCOA #ifdef __OBJC__ #include #endif ObjCWrapper(NSMenu, OsSubMenu) ObjCWrapper(NSMenuItem, OsMenuItem) #else typedef MenuRef OsSubMenu; typedef MenuItemIndex OsMenuItem; #endif #elif defined(HAIKU) typedef class BMenu *OsSubMenu; typedef class BMenuItem *OsMenuItem; #else #include "LMenuImpl.h" typedef class MenuClickImpl *OsSubMenu; typedef class MenuItemImpl *OsMenuItem; #endif #include "lgi/common/XmlTree.h" #include "lgi/common/Res.h" #include "lgi/common/ImageList.h" /////////////////////////////////////////////////////////////////////////////////////////////// // Menu wrappers class LgiClass LMenuLoader { friend class LMenuItem; friend class LMenu; friend class LSubMenu; friend class MenuImpl; friend class SubMenuImplPrivate; protected: #ifdef WIN32 OsSubMenu Info; #endif List Items; public: LMenuLoader() { #ifdef WIN32 Info = 0; #endif } bool Load( class LMenuRes *MenuRes, LXmlTag *Tag, ResFileFormat Format, class TagHash *TagList); virtual LMenuItem *AppendItem(const char *Str, int Id, bool Enabled = true, int Where = -1, const char *Shortcut = 0) = 0; virtual LSubMenu *AppendSub(const char *Str, int Where = -1) = 0; virtual LMenuItem *AppendSeparator(int Where = -1) = 0; }; /// Sub menu. class LMenu; class LgiClass LSubMenu : public LBase, public LMenuLoader, public LImageListOwner, public LDom { friend class LMenuItem; friend class LMenu; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; friend class LMouseHookPrivate; // This is not called in the GUI thread static void SysMouseClick(LMouse &m); #if !WINNATIVE && !defined(__GTK_H__) OsSubMenu Info; #endif #if LGI_COCOA LAutoPtr FloatResult; virtual void OnActivate(LMenuItem *item); #endif #if defined(__GTK_H__) GlibWrapper Info; friend void GtkDeactivate(Gtk::GtkMenuShell *widget, LSubMenu *Sub); friend Gtk::gboolean LSubMenuClick(LMouse *m); friend void SubMenuDestroy(LSubMenu *Item); - int *_ContextMenuId; - bool InLoop; - uint64 ActiveTs; + int *_ContextMenuId = NULL; + bool InLoop = false; + uint64 ActiveTs = 0; bool IsContext(LMenuItem *Item); void OnActivate(bool a); #elif defined(WINNATIVE) - HWND TrackHandle; + HWND TrackHandle = NULL; #else bool OnKey(LKey &k); #endif protected: /// The parent menu item or NULL if the root menu - LMenuItem *Parent; + LMenuItem *Parent = NULL; /// The top level window this sub menu belongs to or NULL - LMenu *Menu; + LMenu *Menu = NULL; /// The window that the menu belongs to or NULL. - LViewI *Window; + LViewI *Window = NULL; void OnAttach(bool Attach); void ClearHandle(); public: + constexpr static int ItemId_Submenu = -1; + constexpr static int ItemId_Separator = -2; + LSubMenu(OsSubMenu Hnd); /// Constructor LSubMenu ( /// Name of the menu const char *name = "", /// True if it's popup bool Popup = true ); virtual ~LSubMenu(); /// Returns the OS handle OsSubMenu Handle() { return Info; } /// Detachs the OS handle and returns it OsSubMenu Release() { OsSubMenu Hnd = Info; Info = NULL; return Hnd; } /// Add a new item LMenuItem *AppendItem ( /// The text of the item. /// /// If you put a tab control in the text, anything after the tab is considered /// to be the keyboard shortcut for the menu item. const char *Str, /// Command ID to post to the OnCommand() handler int Id, /// True if the item should be enabled bool Enabled = true, /// The index into the list to insert at, or -1 to insert at the end int Where = -1, /// Shortcut if not embedded in the "Str" parameter. /// /// The shortcut can be a combination of modifiers and keys added together with '+'. /// /// Available modifier names are: /// - Ctrl/Control /// - Alt/Option /// - Shift /// - System/Cmd /// - CtrlCmd (Ctrl on Windows/Linux, Cmd on OSX) /// - AltCmd (Alt on Windows/Linux, Cmd on OSX) /// /// Available key names are: /// - Del/Delete /// - Home/End /// - PageUp/Page Up/Page-Up /// - PageDown/Page Down/Page-Down /// - Backspace /// - Up/Right/Down/Left /// - Esc /// - Space /// - Numbers or letters /// - Some punctuation: ,./\\[]`;\' /// /// Examples: /// - Ctrl+S /// - Shift+Modifier+F /// - Alt+Left const char *Shortcut = NULL ); /// Add a submenu LSubMenu *AppendSub ( /// The text of the item const char *Str, /// The index to insert the item, or -1 to insert on the end int Where = -1 ); /// Add a separator LMenuItem *AppendSeparator(int Where = -1); /// Delete all items void Empty(); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// The index of the item to remove int i ); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// Pointer of the item to remove LMenuItem *Item ); /// Returns numbers of items in menu size_t Length(); /// Return a pointer to an item LMenuItem *ItemAt ( /// The index of the item to return int i ); /// Returns a pointer to an item LMenuItem *FindItem ( /// The ID of the item to return int Id ); /// Returns a pointer to an sub menu LSubMenu *FindSubMenu ( /// The ID of the sub menu to return int Id ); enum { BtnLeft = 1, BtnMiddle = 2, BtnRight = 3, }; /// Floats the submenu anywhere on the screen int Float ( /// The parent view LView *Parent, /// The x coord of the top-left corner int x, /// The y coord of the top-left corner int y, /// True if the menu is tracking the left button, else it tracks the right button int Button = BtnRight ); int Float(LView *Parent, LMouse m) { int Btn = 0; if (m.Left()) Btn = BtnLeft; else if (m.Middle()) Btn = BtnMiddle; else if (m.Right()) Btn = BtnRight; m.ToScreen(); return Float(Parent, m.x, m.y, Btn); } /// Returns the parent menu item LMenuItem *GetParent() { return Parent; } /// Returns the menu that this belongs to LMenu *GetMenu() { return Menu; } // Dom impl bool GetVariant(const char *Name, LVariant &Value, const char *Arr = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Arr = NULL); bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args); }; /// An item an a menu class LgiClass LMenuItem : public LBase, public LDom { friend class LSubMenu; friend class LMenu; friend class LView; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; friend class SubMenuImplPrivate; + friend class LMenuPrivate; private: #ifdef WIN32 bool Insert(int Pos); bool Update(); #endif #if defined(__GTK_H__) || defined(LGI_SDL) LString ShortCut; #endif protected: - LMenu *Menu; - LSubMenu *Parent; - LSubMenu *Child; - int Position; - int _Icon; + LMenu *Menu = NULL; + LSubMenu *Parent = NULL; + LSubMenu *Child = NULL; + int Position = -1; + int _Icon = -1; #ifdef __GTK_H__ GlibWrapper Info; #else OsMenuItem Info; #endif - class LMenuItemPrivate *d; + class LMenuItemPrivate *d = NULL; - int _Id; - int _Flags; + int _Id = 0; + int _Flags = 0; #if defined(__GTK_H__) - bool InSetCheck; + bool InSetCheck = false; LAutoPtr IconImg; bool Replace(Gtk::GtkWidget *newWid); public: void Handle(Gtk::GtkMenuItem *mi); void OnGtkEvent(LString Event); void PaintIcon(Gtk::cairo_t *cr); protected: #else virtual void _Measure(LPoint &Size); virtual void _Paint(LSurface *pDC, int Flags); virtual void _PaintText(LSurface *pDC, int x, int y, int Width); #endif void OnAttach(bool Attach); void ClearHandle(); public: LMenuItem(); - LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int Id, int Pos, const char *Shortcut = 0); + LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int Id, int Pos, const char *Shortcut = NULL); virtual ~LMenuItem(); LMenuItem &operator =(const LMenuItem &m) { LAssert(!"This shouldn't be used anywhere") ; return *this; } /// Creates a sub menu off the item LSubMenu *Create(); /// Removes the item from it's place in the menu but doesn't delete it bool Remove(); /// Returns the parent sub menu LSubMenu *GetParent(); /// Returns the parent sub menu LMenu *GetMenu() { return Menu; } /// Scans the text of the item for a keyboard shortcut bool ScanForAccel(); /// Returns the OS handle for the menuitem OsMenuItem Handle() { return Info; } /// Set the id void Id(int i); /// Turn the item into a separator void Separator(bool s); /// Put a check mark on the item void Checked(bool c); /// \brief Set the text of the item /// \sa LSubMenu::AppendItem() bool Name(const char *n) override; /// Enable or disable the item void Enabled(bool e); void Visible(bool v); void Focus(bool f); /// Attach a sub menu to the item void Sub(LSubMenu *s); /// Set the icon for the item. The icon is stored in the LMenu's image list. void Icon(int i); /// Get the id int Id(); /// Get the text of the item const char *Name() override; /// Return whether this item is a separator bool Separator(); /// Return whether this item has a check mark bool Checked(); /// Return whether this item is enabled bool Enabled(); bool Visible(); bool Focus(); /// Return whether this item's submenu LSubMenu *Sub(); /// Return the icon of this this int Icon(); LImageList *GetImageList(); #if LGI_COCOA void OnActivate(LMenuItem *item); #endif }; /// Encapsulates a keyboard shortcut class LgiClass LAccelerator : public LUiEvent { int Vkey = 0; int Chr = 0; int Id = 0; public: LAccelerator(int flags, int vkey, int chr, int id); int GetId() { return Id; } /// See if the accelerator matchs a keyboard event bool Match(LKey &k); }; /** \brief Top level window menu This class contains LMenuItem's and LSubMenu's. A basic menu can be constructed inside a LWindow like this: \code Menu = new LMenu; if (Menu) { Menu->Attach(this); LSubMenu *File = Menu->AppendSub("&File"); if (File) { File->AppendItem("&Open\tCtrl+O", IDM_OPEN, true); File->AppendItem("&Save All\tCtrl+S", IDM_SAVE_ALL, true); File->AppendItem("Save &As", IDM_SAVEAS, true); File->AppendSeparator(); File->AppendItem("&Options", IDM_OPTIONS, true); File->AppendSeparator(); File->AppendItem("E&xit", IDM_EXIT, true); } LSubMenu *Help = Menu->AppendSub("&Help"); if (Help) { Help->AppendItem("&Help", IDM_HELP, true); Help->AppendItem("&About", IDM_ABOUT, true); } } \endcode Or you can load a menu from a resource like this: \code Menu = new LMenu; if (Menu) { Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } \endcode */ class LgiClass LMenu : public LSubMenu { friend class LSubMenu; friend class LMenuItem; friend class LWindow; + friend class LMenuPrivate; class LMenuPrivate *d; #if defined WIN32 - void OnChange(); + void OnChange(); #else - void OnChange() {} + void OnChange() {} #endif protected: /// List of keyboard shortcuts in the menu items attached List Accel; #ifdef __GTK_H__ - Gtk::GtkAccelGroup *AccelGrp; + Gtk::GtkAccelGroup *AccelGrp; #endif #if LGI_COCOA - void OnActivate(LMenuItem *item); + void OnActivate(LMenuItem *item); + #endif + + #ifdef HAIKU + bool PostMessage(BMessage *m); #endif public: /// Constructor LMenu(const char *AppName = NULL); /// Destructor virtual ~LMenu(); /// Returns the font used by the menu items static LFont *GetFont(); /// Returns the top level window that this menu is attached to LViewI *WindowHandle() { return Window; } /// Attach the menu to a window bool Attach(LViewI *p); /// Detact the menu from the window bool Detach(); /// Load the menu from a resource file bool Load ( /// The parent view for any error message boxes LView *p, /// The resource to load. Will probably change to an int sometime. const char *Res, /// Optional list of comma or space separated tags const char *Tags = 0 ); /// \brief See if any of the accelerators match the key event /// \return true if none of the accelerators match bool OnKey ( /// The view that will eventually receive the key event LView *v, /// The keyboard event details LKey &k ); /// This creates copies of the preference and about menu items in the /// application menu. On other platforms it's a NOP. bool SetPrefAndAboutItems(int PrefId, int AboutId); #if defined(WIN32) - static int _OnEvent(LMessage *Msg); + static int _OnEvent(LMessage *Msg); #elif defined(MAC) - int GetIdForCommand(uint32_t Cmd); + int GetIdForCommand(uint32_t Cmd); #endif }; #endif diff --git a/include/lgi/common/Message.h b/include/lgi/common/Message.h --- a/include/lgi/common/Message.h +++ b/include/lgi/common/Message.h @@ -1,297 +1,314 @@ + #ifndef _GMESSAGE_H_ #define _GMESSAGE_H_ +#ifdef HAIKU +#include +#endif + enum LgiMessages { #if defined(__GTK_H__) /// Base point for system messages. M_SYSTEM = 900, /// Message that indicates the user is trying to close a top level window. M_CLOSE, /// Implemented to handle paint requests in the GUI thread. M_X11_REPARENT, /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEENTER, /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, M_MENU, M_COMMAND, M_DRAG_DROP, M_TRAY_NOTIFY, M_CUT, M_COPY, M_PASTE, M_GTHREADWORK_COMPELTE, /// Implemented to handle timer events in the GUI thread. M_PULSE, M_SET_VISIBLE, M_CAPTURE_PULSE, /// Sent from a worker thread when calling LTextLabel::Name M_TEXT_UPDATE_NAME, #elif defined(WINNATIVE) // [WM_APP:WM_APP+200] is reserved for LGI itself. // [WM_APP+200:0xBFFF] is reserved for LGI applications. M_CUT = WM_CUT, M_COPY = WM_COPY, M_PASTE = WM_PASTE, M_COMMAND = WM_COMMAND, M_CLOSE = WM_CLOSE, // wParam = bool Inside; // is the mouse inside the client area? // lParam = MAKELONG(x, y); // mouse location M_MOUSEENTER = WM_APP, M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, // wParam = void // lParam = (MSG*) Msg; M_TRANSLATE_ACCELERATOR, // None M_TRAY_NOTIFY, // lParam = Style M_SET_WND_STYLE, // lParam = LScrollBar *Obj M_SCROLL_BAR_CHANGED, // lParam = HWND of window under mouse // This is only sent for non-LGI window in our process // because we'd get WM_MOUSEMOVE's for our own windows M_HANDLEMOUSEMOVE, // Calls SetWindowPlacement... M_SET_WINDOW_PLACEMENT, // Set the visibility of the window M_SET_VISIBLE, /// Sent from a worker thread when calling LTextLabel::Name M_TEXT_UPDATE_NAME, /// Send when a window is losing it's mouse capture. Usually // because something else has requested it. M_LOSING_CAPTURE, + + // A: code + M_DIALOG_END_MODAL, #elif defined(LGI_SDL) || defined(HAIKU) /// Minimum value for application defined message ID's M_MOUSEENTER = 900, M_MOUSEEXIT, M_COMMAND, M_CUT, M_COPY, M_PASTE, M_PULSE, M_SET_VISIBLE, M_MOUSE_CAPTURE_POLL, M_TEXT_UPDATE_NAME, M_ASSERT_DLG, M_CLOSE, + + #if defined(HAIKU) + M_HANDLE_IN_THREAD, // A = (LMessage::InThreadCb*)Cb; + M_LWINDOW_DELETE, + M_LMENUITEM_ENABLE, + M_LSUBMENU_APPENDITEM, + #endif #elif defined(MAC) /// Base point for system messages. M_SYSTEM = 0, /// Message that indicates the user is trying to close a top level window. M_CLOSE = (M_SYSTEM+92), /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEENTER = (M_SYSTEM+900), /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, M_MENU, M_COMMAND, M_DRAG_DROP, M_TRAY_NOTIFY, M_CUT, M_COPY, M_PASTE, M_PULSE, M_MOUSE_TRACK_UP, M_GTHREADWORK_COMPELTE, M_TEXT_UPDATE_NAME, M_SET_VISIBLE, M_SETPOS, // A=(LRect*)Rectangle, B=(LView*)this M_ASSERT_DLG, M_QUIT, M_ABOUT, M_PERFERENCES, M_HIDE, M_DESTROY, #endif M_DESCRIBE, M_CHANGE, M_DELETE, M_TABLE_LAYOUT, M_URL, M_LOG_TEXT, - M_ASSERT_UI, + M_ASSERT_UI, // A=(LString*)Msg M_INVALIDATE, // A=(LRect*)Rectangle, B=(LView*)this M_RESIZE_TO_CONTENT, // A=(int)Border (used by LItemContainer) M_SCROLL_TO, // Sent from LTreeItem to LTree + M_SET_SCROLL, // LScrollBar M_JOBS_LOADED, // LHtml M_THREAD_COMPLETED, // A=(LThread*)Thread M_SET_CTRL_NAME, // A=(int)CtrlId, B=(LString*)Name M_SET_CTRL_ENABLE, // A=(int)CtrlId, B=(bool)Enabled M_SET_CTRL_VISIBLE, // A=(int)CtrlId, B=(bool)Visible #ifdef WINDOWS M_USER = WM_APP + 200 #else M_USER = 1000 #endif }; class LgiClass LMessage #ifdef HAIKU : public BMessage #endif { public: #if defined(WINNATIVE) typedef LPARAM Param; typedef LRESULT Result; #elif defined(LGI_SDL) typedef void *Param; typedef NativeInt Result; #else typedef NativeInt Param; typedef NativeInt Result; #endif #if !defined(LGI_SDL) && !defined(HAIKU) int m; #endif #if defined(LGI_SDL) SDL_Event Event; struct EventParams { Param a, b; EventParams(Param A, Param B) { a = A; b = B; } }; #elif defined(WINNATIVE) HWND hWnd; WPARAM a; LPARAM b; #elif !defined(HAIKU) Param a; Param b; #endif #if LGI_COCOA #if __OBJC__ NSEvent *event; #else void *event; #endif #endif #if defined(HAIKU) static constexpr char *PropA = "lgiA"; static constexpr char *PropB = "lgiB"; static constexpr char *PropView = "lgiView"; static constexpr char *PropCallback = "lgiCallback"; + static constexpr char *PropNames[2] = {"lgi_a", "lgi_b"}; typedef std::function InThreadCb; #endif LMessage() { #if defined(LGI_SDL) Set(0, 0, 0); #else #if defined(WINNATIVE) hWnd = 0; #endif #if !defined(LGI_SDL) && !defined(HAIKU) m = 0; a = 0; b = 0; #endif #endif #if LGI_COCOA event = NULL; #endif } LMessage ( int M, #if defined(WINNATIVE) WPARAM A = 0, LPARAM B = 0 #else Param A = 0, Param B = 0 #endif ) { #if defined(WINNATIVE) hWnd = 0; #endif #if LGI_COCOA event = NULL; #endif Set(M, A, B); } #if defined(LGI_SDL) || defined(HAIKU) int Msg(); Param A(); Param B(); #else int Msg() { return m; } Param A() { return a; } Param B() { return b; } #endif void Set(int m, Param a, Param b); bool Send(class LViewI *View); }; #ifdef LINUX extern LMessage CreateMsg(int m, int a = 0, int b = 0); #else #define CreateMsg(m, a, b) LMessage(m, a, b) #endif #endif diff --git a/include/lgi/common/Mru.h b/include/lgi/common/Mru.h --- a/include/lgi/common/Mru.h +++ b/include/lgi/common/Mru.h @@ -1,68 +1,70 @@ #ifndef __GMRU_H #define __GMRU_H +#include + #ifdef HAS_PROPERTIES #include "GProperties.h" #endif #include "lgi/common/FileSelect.h" // Message defines #define IDM_OPEN 15000 #define IDM_SAVEAS 15001 // Classes class LgiClass LMru { private: class LMruPrivate *d; void _Update(); protected: virtual bool _OpenFile(const char *File, bool ReadOnly); virtual bool _SaveFile(const char *File); virtual const char *_GetCurFile(); virtual void GetFileTypes(LFileSelect *Dlg, bool Write); virtual LFileType *GetSelectedType(); - bool DoFileDlg(LFileSelect &Select, bool Open); + void DoFileDlg(LFileSelect &Select, bool Open, std::function OnSelect); /// This method converts the storage reference (which can contain user/pass credentials) /// between the display, raw and stored forms. Display and Raw are used at runtime and /// the stored form is written to disk. /// /// The implementor should fill in any NULL strings by converting from the supplied forms. /// The caller must supply either the Raw or Stored form. virtual bool SerializeEntry ( /// The displayable version of the reference (this should have any passwords blanked out) LString *Display, /// The form passed to the client software to open/save. (passwords NOT blanked) LString *Raw, /// The form safe to write to disk, if a password is present it must be encrypted. LString *Stored ); public: LMru(); virtual ~LMru(); // Impl bool Set(LSubMenu *parent, int size = -1); const char *AddFile(const char *FileName, bool Update = true); void RemoveFile(const char *FileName, bool Update = true); LMessage::Result OnEvent(LMessage *Msg); - bool OnCommand(int Cmd); + void OnCommand(int Cmd, std::function OnStatus); // Serialization bool Serialize(LDom *Store, const char *Prefix, bool Write); #ifdef HAS_PROPERTIES bool Serialize(ObjProperties *Store, char *Prefix, bool Write); #endif // Events virtual bool OpenFile(const char *FileName, bool ReadOnly) = 0; virtual bool SaveFile(const char *FileName) = 0; }; #endif diff --git a/include/lgi/common/Net.h b/include/lgi/common/Net.h --- a/include/lgi/common/Net.h +++ b/include/lgi/common/Net.h @@ -1,554 +1,554 @@ /** \file \author Matthew Allen \date 28/5/1998 \brief Network sockets classes Copyright (C) 1998, Matthew Allen */ #ifndef __INET_H #define __INET_H #include "lgi/common/LgiNetInc.h" #include "lgi/common/LgiInterfaces.h" #include "lgi/common/Mem.h" #include "lgi/common/Containers.h" #include "lgi/common/Stream.h" #include "lgi/common/LgiString.h" #include "lgi/common/Cancel.h" #include "lgi/common/HashTable.h" #if defined WIN32 #include "ws2ipdef.h" #elif defined POSIX #include #elif defined BEOS #include #include #else typedef int SOCKET; #endif #define DEFAULT_TIMEOUT 30 // Standard ports #define FTP_PORT 21 #define SSH_PORT 22 #define SMTP_PORT 25 #define HTTP_PORT 80 #define HTTPS_PORT 443 #define SMTP_SSL_PORT 465 #define POP3_PORT 110 #define POP3_SSL_PORT 995 #define IMAP_PORT 143 #define IMAP_SSL_PORT 993 // Parameters for passing to LSocket::SetVariant /// Turn on/off logging. Used with LSocket::SetParameter. #define LSocket_Log "Log" /// Set the progress object. Used with LSocket::SetParameter. Value = (LProgressView*)Prog #define LSocket_Progress "Progress" /// Set the size of the transfer. Used with LSocket::SetParameter. Value = (int)Size #define LSocket_TransferSize "TransferSize" /// Set the type of protocol. Used with LSocket::SetParameter. Value = (char*)Protocol #define LSocket_Protocol "Protocol" #define LSocket_SetDelete "SetDelete" // Functions LgiNetFunc bool HaveNetConnection(); LgiNetFunc bool WhatsMyIp(LAutoString &Ip); LgiExtern LString LIpToStr(uint32_t ip); LgiExtern uint32_t LIpToInt(LString str); // Convert IP as string to host order int LgiExtern uint32_t LHostnameToIp(const char *HostName); // Hostname lookup (DNS), returns IP in host order or 0 on error /// Make md5 hash LgiNetFunc void MDStringToDigest ( /// Buffer to receive md5 hash unsigned char digest[16], /// Input string char *Str, /// Length of input string or -1 for null terminated int Len = -1 ); /// Implementation of a network socket class LgiNetClass LSocket : public LSocketI, public LStream { protected: class LSocketImplPrivate *d; // Methods void Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len); bool CreateUdpSocket(); public: ssize_t BytesRead, BytesWritten; /// Creates the class LSocket(LStreamI *logger = 0, void *unused_param = 0); /// Destroys the class ~LSocket(); /// Gets the active cancellation object LCancel *GetCancel() override; /// Sets the active cancellation object void SetCancel(LCancel *c) override; /// Returns the operating system handle to the socket. OsSocket Handle(OsSocket Set = INVALID_SOCKET) override; OsSocket ReleaseHandle(); /// Returns true if the internal state of the class is ok bool IsOK(); /// Returns the IP address at this end of the socket. bool GetLocalIp(char *IpAddr) override; /// Returns the port at this end of the socket. int GetLocalPort() override; /// Gets the IP address at the remote end of the socket. bool GetRemoteIp(uint32_t *IpAddr); bool GetRemoteIp(char *IpAddr) override; /// Gets the IP address at the remote end of the socket. int GetRemotePort() override; /// Gets the current timeout for operations in ms int GetTimeout() override; /// Sets the current timeout for operations in ms void SetTimeout(int ms) override; /// Returns whether there is data available for reading. bool IsReadable(int TimeoutMs = 0) override; /// Returns whether there is data available for reading. bool IsWritable(int TimeoutMs = 0) override; /// Returns if the socket is ready to accept a connection bool CanAccept(int TimeoutMs = 0) override; /// Returns if the socket is set to block bool IsBlocking() override; /// Set the socket to block void IsBlocking(bool block) override; /// Get the send delay setting bool IsDelayed() override; /// Set the send delay setting void IsDelayed(bool Delay) override; /// Opens a connection. int Open ( /// The name of the remote host. const char *HostAddr, /// The port on the remote host. int Port ) override; /// Returns true if the socket is connected. bool IsOpen() override; /// Closes the connection to the remote host. int Close() override; /// Binds on a given port. bool Bind(int Port, bool reuseAddr = true); /// Binds and listens on a given port for an incomming connection. bool Listen(int Port = 0) override; /// Accepts an incomming connection and connects the socket you pass in to the remote host. bool Accept ( /// The socket to handle the connection. LSocketI *c ) override; /// \brief Sends data to the remote host. /// \return the number of bytes written or <= 0 on error. ssize_t Write ( /// Pointer to the data to write const void *Data, /// Numbers of bytes to write ssize_t Len, /// Flags to pass to send int Flags = 0 ) override; inline ssize_t Write(const LString s) { return LStreamI::Write(s); } /// \brief Reads data from the remote host. /// \return the number of bytes read or <= 0 on error. /// /// Generally the number of bytes returned is less than the buffer size. Depending on how much data /// you are expecting you will need to keep reading until you get and end of field marker or the number /// of bytes your looking for. ssize_t Read ( /// Pointer to the buffer to write output to void *Data, /// The length of the receive buffer. ssize_t Len, /// The flags to pass to recv int Flags = 0 ) override; /// Returns the last error or 0. int Error(void *Param = 0) override; const char *GetErrorString() override; /// Not supported int64 GetSize() override { return -1; } /// Not supported int64 SetSize(int64 Size) override { return -1; } /// Not supported int64 GetPos() override { return -1; } /// Not supported int64 SetPos(int64 Pos) override { return -1; } /// Gets called when the connection is disconnected void OnDisconnect() override; /// Gets called when data is received. void OnRead(char *Data, ssize_t Len) override {} /// Gets called when data is sent. void OnWrite(const char *Data, ssize_t Len) override {} /// Gets called when an error occurs. void OnError(int ErrorCode, const char *ErrorDescription) override; /// Gets called when some information is available. void OnInformation(const char *Str) override {} /// Parameter change handler. int SetParameter ( /// e.g. #LSocket_Log int Param, int Value ) { return false; } /// Get UPD mode bool GetUdp() override; /// Set UPD mode void SetUdp(bool isUdp = true) override; /// Makes the socket able to broadcast void SetBroadcast(bool isBroadcast = true); /// Read UPD packet int ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip = 0, uint16_t *Port = 0) override; /// Write UPD packet int WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) override; bool AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface); bool SetMulticastInterface(uint32_t Interface); // Impl LStreamI *Clone() override { LSocket *s = new LSocket; if (s) s->SetCancel(GetCancel()); return s; } // Statics /// Enumerates the current interfaces struct Interface { LString Name; uint32_t Ip4; // Host order... uint32_t Netmask4; bool IsLoopBack() { return Ip4 == 0x7f000001; } bool IsPrivate() { uint8_t h1 = (Ip4 >> 24) & 0xff; if (h1 == 192) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 == 168; } else if (h1 == 10) { return true; } else if (h1 == 172) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 >= 16 && h2 <= 31; } return false; } bool IsLinkLocal() { uint8_t h1 = (Ip4 >> 24) & 0xff; if (h1 == 169) { uint8_t h2 = ((Ip4 >> 16) & 0xff); return h2 == 254; } return false; } }; static bool EnumInterfaces(LArray &Out); }; class LgiNetClass LSocks5Socket : public LSocket { LAutoString Proxy; int Port; LAutoString UserName; LAutoString Password; protected: bool Socks5Connected; public: LSocks5Socket(); LSocks5Socket &operator=(const LSocks5Socket &s) { Proxy.Reset(NewStr(s.Proxy)); UserName.Reset(NewStr(s.UserName)); Password.Reset(NewStr(s.Password)); Port = s.Port; return *this; } // Connection void SetProxy(char *proxy, int port, char *username, char *password); void SetProxy(const LSocks5Socket *s); int Open(const char *HostAddr, int port); // Server bool Listen(int Port) { return false; } }; /// Uri parser class LgiNetClass LUri { public: LString sProtocol; LString sUser; LString sPass; LString sHost; int Port; LString sPath; LString sAnchor; /// Parser for URI's. LUri ( /// Optional URI to start parsing const char *uri = 0 ); ~LUri(); bool IsProtocol(const char *p) { return sProtocol.Equals(p); } bool IsHttp() { return sProtocol.Equals("http") || sProtocol.Equals("https"); } bool IsFile() { return sProtocol.Equals("file"); } void SetFile(LString Path) { Empty(); sProtocol = "file"; sPath = Path; } const char *LocalPath(); operator bool(); /// Parse a URI into it's sub fields... bool Set(const char *uri); /// Re-constructs the URI LString ToString(); /// Empty this object... void Empty(); /// URL encode LString EncodeStr ( /// The string to encode const char *s, /// [Optional] Any extra characters you want encoded const char *ExtraCharsToEncode = 0 ); /// URL decode LString DecodeStr(const char *s); /// Separate args into map typedef LHashTbl,LString> StrMap; StrMap Params(); LUri &operator =(const LUri &u); LUri &operator =(const char *s) { Set(s); return *this; } LUri &operator +=(const char *s); }; /// Proxy settings lookup class LgiNetClass LProxyUri : public LUri { public: LProxyUri(); }; #define MAX_UDP_SIZE 512 class LUdpListener : public LSocket { LStream *Log; LString Context; public: bool Status = false; /* If this isn't working on Linux, most likely it's a firewall issue. For instance if you are running 'ufw' as your firewall you could allow packets through with: sudo ufw allow ${port}/udp */ LUdpListener(LArray interface_ips, uint32_t mc_ip, uint16_t port, LStream *log = NULL) : Log(log) { SetUdp(true); struct sockaddr_in addr; ZeroObj(addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); #ifdef WINDOWS addr.sin_addr.S_un.S_addr = INADDR_ANY; #elif defined(MAC) addr.sin_addr.s_addr = htonl(mc_ip); #else addr.sin_addr.s_addr = INADDR_ANY; #endif Context = "LUdpListener.bind"; Status = bind(Handle(), (struct sockaddr*)&addr, sizeof(addr)) == 0; if (!Status) { #ifdef WIN32 int err = WSAGetLastError(); #else int err = errno; #endif - OnError(err, GetErrorName(err)); + OnError(err, LErrorCodeToString(err)); LgiTrace("Error: Bind on %s:%i\n", LIpToStr(ntohl(addr.sin_addr.s_addr)).Get(), port); } if (mc_ip) { Context = "LUdpListener.AddMulticastMember"; for (auto ip: interface_ips) AddMulticastMember(mc_ip, ip); } } bool ReadPacket(LString &d, uint32_t &Ip, uint16_t &Port) { if (!IsReadable(10)) return false; char Data[MAX_UDP_SIZE]; int Rd = ReadUdp(Data, sizeof(Data), 0, &Ip, &Port); if (Rd <= 0) return false; d.Set(Data, Rd); return true; } void OnError(int ErrorCode, const char *ErrorDescription) { LString s; if (Context) s.Printf("Error: %s - %i, %s\n", Context.Get(), ErrorCode, ErrorDescription); else s.Printf("Error: %i, %s\n", ErrorCode, ErrorDescription); if (Log) Log->Print("%s", s.Get()); else LgiTrace("%s", s.Get()); } }; class LUdpBroadcast : public LSocket { // LArray Intf; uint32_t SelectIf; public: LUdpBroadcast(uint32_t selectIf) : SelectIf(selectIf) { SetBroadcast(); SetUdp(true); // EnumInterfaces(Intf); } bool BroadcastPacket(LString Data, uint32_t Ip, uint16_t Port) { return BroadcastPacket(Data.Get(), Data.Length(), Ip, Port); } bool BroadcastPacket(void *Ptr, size_t Size, uint32_t Ip, uint16_t Port) { if (Size > MAX_UDP_SIZE) return false; if (SelectIf) { struct in_addr addr; addr.s_addr = htonl(SelectIf); auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (char*)&addr, sizeof(addr)); if (r) LgiTrace("%s:%i - set IP_MULTICAST_IF for '%s' failed: %i\n", _FL, LIpToStr(SelectIf).Get(), r); SelectIf = 0; } uint32_t BroadcastIp = Ip; #if 0 LgiTrace("Broadcast %i.%i.%i.%i\n", (BroadcastIp >> 24) & 0xff, (BroadcastIp >> 16) & 0xff, (BroadcastIp >> 8) & 0xff, (BroadcastIp) & 0xff); #endif int wr = WriteUdp(Ptr, (int)Size, 0, BroadcastIp, Port); return wr == Size; } }; #endif diff --git a/include/lgi/common/Password.h b/include/lgi/common/Password.h --- a/include/lgi/common/Password.h +++ b/include/lgi/common/Password.h @@ -1,34 +1,34 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A password manager #ifndef __GPASSWORD_H #define __GPASSWORD_H class LgiClass GPassword { char *Data; // this is binary data... may contain NULL's ssize_t Len; - void Process(char *Out, const char *In, ssize_t Len); + void Process(char *Out, const char *In, ssize_t Len) const; public: GPassword(GPassword *p = 0); virtual ~GPassword(); bool IsValid() { return Data && Len > 0; } - void Get(char *Buf); - LString Get(); + void Get(char *Buf) const; + LString Get() const; void Set(const char *Buf); // bool Serialize(ObjProperties *Options, char *Option, int Write); bool Serialize(LDom *Options, const char *Option, int Write); void Serialize(char *Password, int Write); void Delete(LDom *Options, char *Option); GPassword &operator =(GPassword &p); bool operator ==(GPassword &p); }; #endif diff --git a/include/lgi/common/Popup.h b/include/lgi/common/Popup.h --- a/include/lgi/common/Popup.h +++ b/include/lgi/common/Popup.h @@ -1,114 +1,114 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A popup window #pragma once #include "lgi/common/Layout.h" -#if defined(LGI_CARBON) || defined(__GTK_H__) -#define LGI_POPUP_LWINDOW 1 +#if defined(LGI_CARBON) || defined(__GTK_H__) || defined(HAIKU) + #define LGI_POPUP_LWINDOW 1 #else -#define LGI_POPUP_LWINDOW 0 + #define LGI_POPUP_LWINDOW 0 #endif #if LGI_COCOA -ObjCWrapper(NSPanel, OsPanel) + ObjCWrapper(NSPanel, OsPanel) #endif /// A popup window: closes when the user clicks off-window. class LgiClass LPopup : #if LGI_POPUP_LWINDOW - public LWindow + public LWindow #else - public LView + public LView #endif { friend class _QPopup; friend class LWindow; friend class LDropDown; friend class LMouseHook; friend class LMouseHookPrivate; friend class LView; static LArray CurrentPopups; protected: class LPopupPrivate *d; bool Cancelled; LView *Owner; uint64 Start; LRect ScreenPos; #if LGI_COCOA OsPanel Panel; #endif public: LPopup(LView *owner); ~LPopup(); #if LGI_COCOA - OsPanel Handle() { return Panel; } - LRect &GetPos() override; - bool SetPos(LRect &r, bool repaint = false) override; + OsPanel Handle() { return Panel; } + LRect &GetPos() override; + bool SetPos(LRect &r, bool repaint = false) override; #endif /// Sets whether the popup should take the focus when it's shown. /// The default is 'true' void TakeFocus(bool Take); const char *GetClass() override { return "LPopup"; } bool GetCancelled() { return Cancelled; } bool Attach(LViewI *p) override; void Visible(bool i) override; bool Visible() override; LMessage::Result OnEvent(LMessage *Msg) override; }; /// Drop down menu, UI widget for opening a popup. class LgiClass LDropDown : public LLayout { friend class LPopup; LPopup *Popup; public: LDropDown(int Id, int x, int y, int cx, int cy, LPopup *popup); ~LDropDown(); // Properties bool IsOpen(); void SetPopup(LPopup *popup); LPopup *GetPopup(); // Window events void OnFocus(bool f); void OnPaint(LSurface *pDC); bool OnKey(LKey &k); void OnMouseClick(LMouse &m); int OnNotify(LViewI *c, LNotification n); // Override virtual void Activate(); virtual void OnPopupClose() {} }; /// Mouse hook grabs mouse events from the OS class LPopup; class LMouseHook { class LMouseHookPrivate *d; public: LMouseHook(); ~LMouseHook(); void RegisterPopup(LPopup *p); void UnregisterPopup(LPopup *p); bool OnViewKey(LView *v, LKey &k); void TrackClick(LView *v); #ifdef WIN32 static LRESULT CALLBACK MouseProc(int Code, WPARAM a, LPARAM b); #endif }; diff --git a/include/lgi/common/PopupList.h b/include/lgi/common/PopupList.h --- a/include/lgi/common/PopupList.h +++ b/include/lgi/common/PopupList.h @@ -1,286 +1,285 @@ #ifndef _POPUP_LIST_H_ #define _POPUP_LIST_H_ #include "lgi/common/List.h" #include "lgi/common/Css.h" #include "lgi/common/CssTools.h" /// This class displays a list of search results in a /// popup connected to an editbox. To use override: /// - ToString /// - OnSelect /// /// 'T' is the type of record that maps to one list item. /// The user can select an item with [Enter] or click on it. /// [Escape] cancels the popup. template class LPopupList : public LPopup { public: enum { IDC_BROWSE_LIST = 50, }; enum PositionType { PopupAbove, PopupBelow, }; class Item : public LListItem { public: T *Value; Item(T *val = NULL) { Value = val; } }; protected: LList *Lst; LViewI *Edit; bool Registered; PositionType PosType; LWindow *HookWnd() { #if defined(LGI_CARBON)// || defined(__GTK_H__) return GetWindow(); #else return Edit->GetWindow(); #endif } public: LPopupList(LViewI *edit, PositionType pos, int width = 200, int height = 300) : LPopup(edit->GetGView()) { Registered = false; PosType = pos; LRect r(width - 1, height - 1); Edit = edit; SetPos(r); AddView(Lst = new LList(IDC_BROWSE_LIST, r.x1+1, r.y1+1, r.X()-3, r.Y()-3)); Lst->Sunken(false); Lst->AddColumn("Name", r.X()); Lst->ShowColumnHeader(false); Lst->MultiSelect(false); // Set default border style... LCss *Css = GetCss(true); if (Css) { LCss::BorderDef b(Css, "1px solid #888;"); Css->Border(b); } Attach(Edit); } ~LPopupList() { auto Wnd = HookWnd(); if (Wnd && Registered) Wnd->UnregisterHook(this); } const char *GetClass() { return "LPopupList"; } // Events: // ------------------------------------------------------------------------ /// Override this to convert an object to a string for the list items virtual LString ToString(T *Obj) = 0; /// Override this to handle the selection of an object virtual void OnSelect(T *Obj) = 0; // Implementation: // ------------------------------------------------------------------------ void SetPosType(PositionType t) { PosType = t; } void AdjustPosition() { // Set position relative to editbox LRect r = GetPos(); LPoint p(0, PosType == PopupAbove ? 0 : Edit->Y()); Edit->PointToScreen(p); if (PosType == PopupAbove) r.Offset(p.x - r.x1, (p.y - r.Y()) - r.y1); else r.Offset(p.x - r.x1, p.y - r.y1); SetPos(r); } bool SetItems(LArray &a) { Lst->Empty(); for (unsigned i=0; iSetText(s); Lst->Insert(li); } else { LAssert(!"ToString failed."); return false; } } else LAssert(!"Alloc failed."); } else LAssert(!"Null array entry."); } return true; } void OnPaint(LSurface *pDC) { // Draw the CSS border... (the default value is set in the constructor) LCssTools t(GetCss(true), GetFont()); LRect c = GetClient(); c = t.PaintBorder(pDC, c); // Move Lst if needed... LRect r = Lst->GetPos(); if (r != c) Lst->SetPos(c); } bool Visible() { return LPopup::Visible(); } void Visible(bool i) { if (i) AdjustPosition(); LPopup::Visible(i); if (i) { AttachChildren(); OnPosChange(); auto Wnd = HookWnd(); if (Wnd && !Registered) { Registered = true; Wnd->RegisterHook(this, LKeyEvents); } #ifdef WINNATIVE Edit->Focus(true); #else Lst->Focus(true); #endif } } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { auto Str = Edit->Name(); - Name(Str); bool Has = ValidStr(Str) && Lst->Length(); bool Vis = Visible(); if (Has ^ Vis) { // printf("%s:%i - PopupLst, has=%i vis=%i\n", _FL, Has, Vis); Visible(Has); } } else if (Ctrl == Lst) { if (n.Type == LNotifyReturnKey || n.Type == LNotifyItemClick) { Item *Sel = dynamic_cast(Lst->GetSelected()); if (Sel) OnSelect(Sel->Value); Visible(false); } } return 0; } bool OnViewKey(LView *v, LKey &k) { if (!Visible()) return false; #if 0 LString s; s.Printf("%s:%i - OnViewKey vis=%i", _FL, Visible()); k.Trace(s); #endif switch (k.vkey) { case LK_TAB: { Visible(false); return false; } case LK_ESCAPE: { if (!k.Down()) { Visible(false); } return true; break; } case LK_RETURN: { if (Lst) Lst->OnKey(k); return true; break; } case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { if (!k.IsChar) { if (Lst) Lst->OnKey(k); return true; } break; } } #if defined(MAC) && !defined(__GTK_H__) return Edit->OnKey(k); #else return false; #endif } }; #endif diff --git a/include/lgi/common/Printer.h b/include/lgi/common/Printer.h --- a/include/lgi/common/Printer.h +++ b/include/lgi/common/Printer.h @@ -1,98 +1,114 @@ /** \file \author Matthew Allen */ #pragma once struct LPrintPageRanges : public LArray { LPrintPageRanges(LString PageRanges) { for (auto r: PageRanges.SplitDelimit(",")) { auto p = r.SplitDelimit("-"); if (p.Length() == 1) { auto &range = New(); range.Start = (ssize_t)p[0].Int(); range.Len = 1; } else if (p.Length() == 2) { auto &range = New(); range.Start = (ssize_t)p[0].Int(); range.Len = (ssize_t)(p[1].Int() - range.Start + 1); } } } bool InRanges(int Page) { if (Length() == 0) return true; for (auto &r: *this) if (r.Overlap(Page)) return true; return false; } }; class LPrintEvents { public: + constexpr static int OnBeginPrintError = -1; + constexpr static int OnBeginPrintCancel = 0; + virtual ~LPrintEvents() {} /* /// Get the number of pages in the document int GetPages(); /// Get the range of pages to print that was selected by the user bool GetPageRange ( /// You'll get pairs of ints, each pair is the start and end page /// number of a range to print. i.e. [5, 10], [14, 16] LArray &p ); */ - virtual int OnBeginPrint(LPrintDC *pDC) { return 1; } + virtual void OnBeginPrint( + /// The print device context to print with + LPrintDC *pDC, + /// Callback for the status of the operation. Will return one of: + /// - OnBeginPrintError + /// - OnBeginPrintCancel + /// - A positive number of pages + std::function callback + ) + { + if (callback) callback(OnBeginPrintError); + } virtual bool OnPrintPage(LPrintDC *pDC, int PageIndex) = 0; virtual LPrintPageRanges *GetPageRanges() { return NULL; } }; /// Class to connect to a printer and start printing pages. class LgiClass LPrinter { class LPrinterPrivate *d; public: LPrinter(); virtual ~LPrinter(); /// Browse to a printer bool Browse(LView *Parent); /// Start a print job - int Print + void Print ( /// The event callback for pagination and printing of pages LPrintEvents *Events, + /// The status callback + std::function callback, /// [Optional] The name of the print job const char *PrintJobName = NULL, /// [Optional] The maximum number of pages to print int Pages = -1, /// [Optional] The parent window for the printer selection dialog LView *Parent = NULL ); /// Gets any available error message... LString GetErrorMsg(); /// Write the user selected printer to a string for storage bool Serialize(LString &Str, bool Write); }; diff --git a/include/lgi/common/Rect.h b/include/lgi/common/Rect.h --- a/include/lgi/common/Rect.h +++ b/include/lgi/common/Rect.h @@ -1,447 +1,447 @@ /* \file \author Matthew Allen \date 22/8/1997 */ #ifndef __GDCREGION_H #define __GDCREGION_H #if defined __GTK_H__ #define CornerOffset 1 typedef Gtk::GdkRectangle OsRect; #elif defined WIN32 #define CornerOffset 1 typedef RECT OsRect; #elif defined HAIKU - #define CornerOffset 1 + #define CornerOffset 0 typedef BRect OsRect; #elif defined MAC #define CornerOffset 1 #if LGI_COCOA #ifdef __OBJC__ #include typedef NSRect OsRect; #else typedef CGRect OsRect; #endif #else typedef Rect OsRect; #endif #else // Implement as required #define CornerOffset 0 struct OsRect { int left, right, top, bottom; }; #endif #include "lgi/common/Point.h" /// Rectangle class class LgiClass LRect { public: int x1, y1, x2, y2; LRect() {} LRect(int X1, int Y1, int X2, int Y2) { x1 = X1; x2 = X2; y1 = Y1; y2 = Y2; } LRect(int Width, int Height) { x1 = 0; x2 = Width - 1; y1 = 0; y2 = Height - 1; } LRect(LRect *r) { x1 = r->x1; x2 = r->x2; y1 = r->y1; y2 = r->y2; } LPoint Center() { return LPoint(x1 + (X()/2), y1 + (Y()/2)); } LPoint GetSize() { return LPoint(X(), Y()); } /// Returns the width int X() const { return x2 - x1 + 1; } /// Returns the height int Y() const { return y2 - y1 + 1; } /// Sets the rectangle void Set(int X1, int Y1, int X2, int Y2) { x1 = X1; x2 = X2; y1 = Y1; y2 = Y2; } /// Zero offset, sets the top left to 0,0 and the bottom right to x,y void ZOff(int x, int y); /// Normalizes the rectangle so that left is less than the right and so on void Normal(); /// Returns true if the rectangle is valid bool Valid() const; /// Moves the rectangle by an offset void Offset(int x, int y); void Offset(LPoint *p); /// Moves the edges by an offset void Offset(LRect *a); LRect Move(int x, int y) { LRect r = *this; r.Offset(x, y); return r; } /// Zooms the rectangle void Inset(int x, int y); /// Zooms the rectangle void Inset(LRect *a); /// Sets the width and height void SetSize(int x, int y); /// Sets the width and height void SetSize(LRect *a); /// Sets the rectangle to the intersection of this object and 'b' void Bound(LRect *b); /// Returns true if the point 'x,y' is in this rectangle bool Overlap(int x, int y) const; /// Returns true if the point 'pt' is in this rectangle bool Overlap(const LPoint &pt) const; /// Returns true if the rectangle 'b' overlaps this rectangle bool Overlap(const LRect *b) const; /// Enlarges this rectangle to include the point 'x,y' void Union(int x, int y); /// Enlarges this rectangle to include all points in 'a' void Union(LRect *a); /// Makes this rectangle include all points in 'a' OR 'b' void Union(LRect *a, LRect *b); /// Makes this rectangle the intersection of 'this' AND 'a' void Intersection(LRect *a); /// Makes this rectangle the intersection of 'a' AND 'b' void Intersection(LRect *a, LRect *b); /// Returns a static string formated to include the points in the order: x1,y1,x2,y2 char *GetStr() const; char *Describe() { return GetStr(); } /// Sets the rect from a string containing: x1,y1,x2,y2 bool SetStr(const char *s); /// Returns how near a point is to a rectangle int Near(int x, int y); /// Returns how near a point is to a rectangle int Near(LRect &r); LRect ZeroTranslate() { return LRect(0, 0, X()-1, Y()-1); } bool operator ==(const LRect &r) { return x1 == r.x1 && y1 == r.y1 && x2 == r.x2 && y2 == r.y2; } LRect &operator =(const LRect &r); LRect operator +(const LPoint &p) { LRect r = *this; r.Offset(p.x, p.y); return r; } LRect &operator +=(const LPoint &p) { Offset(p.x, p.y); return *this; } LRect operator -(const LPoint &p) { LRect r = *this; r.Offset(-p.x, -p.y); return r; } LRect &operator -=(const LPoint &p) { Offset(-p.x, -p.y); return *this; } LRect &operator *=(int factor) { x1 *= factor; y1 *= factor; x2 *= factor; y2 *= factor; return *this; } LRect &operator /=(int factor) { x1 /= factor; y1 /= factor; x2 /= factor; y2 /= factor; return *this; } #if LGI_COCOA operator OsRect() { OsRect r; r.origin.x = x1; r.origin.y = y1; r.size.width = X(); r.size.height = Y(); return r; } LRect &operator =(OsRect &r) { if (isinf(r.origin.x) || isinf(r.origin.y)) { ZOff(-1, -1); } else { x1 = r.origin.x; y1 = r.origin.y; x2 = x1 + r.size.width - 1; y2 = y1 + r.size.height - 1; } return *this; } LRect(OsRect r) { if (isinf(r.origin.x) || isinf(r.origin.y)) { ZOff(-1, -1); } else { x1 = r.origin.x; y1 = r.origin.y; x2 = x1 + r.size.width - 1; y2 = y1 + r.size.height - 1; } } #elif defined(__GTK_H__) operator Gtk::GdkRectangle() { Gtk::GdkRectangle r; r.x = x1; r.y = y1; r.width = X(); r.height = Y(); return r; } LRect &operator =(const Gtk::GdkRectangle r) { x1 = r.x; y1 = r.y; x2 = x1 + r.width - 1; y2 = y1 + r.height - 1; return *this; } LRect(const Gtk::GtkAllocation &a) { x1 = a.x; y1 = a.y; x2 = a.x + a.width - 1; y2 = a.y + a.height - 1; } #elif defined(LGI_SDL) operator SDL_Rect() { SDL_Rect s = {(Sint16)x1, (Sint16)y1, (Uint16)X(), (Uint16)Y()}; return s; } #else /// Returns an operating system specific rectangle operator OsRect() { OsRect r; r.left = x1; r.top = y1; - r.right = x2+CornerOffset; - r.bottom = y2+CornerOffset; + r.right = x2 + CornerOffset; + r.bottom = y2 + CornerOffset; return r; } LRect &operator =(OsRect &r) { x1 = (int) r.left; y1 = (int) r.top; x2 = (int) r.right - CornerOffset; y2 = (int) r.bottom - CornerOffset; return *this; } LRect(OsRect r) { x1 = (int) r.left; y1 = (int) r.top; x2 = (int) r.right - CornerOffset; y2 = (int) r.bottom - CornerOffset; } #if defined(MAC) LRect(const CGRect &r) { x1 = (int)r.origin.x; y1 = (int)r.origin.y; x2 = x1 + (int)r.size.width - CornerOffset; y2 = y1 + (int)r.size.height - CornerOffset; } LRect &operator =(const CGRect &r) { x1 = (int)r.origin.x; y1 = (int)r.origin.y; x2 = x1 + (int)r.size.width - CornerOffset; y2 = y1 + (int)r.size.height - CornerOffset; return *this; } operator CGRect() { CGRect r; r.origin.x = x1; r.origin.y = y1; r.size.width = x2 - x1 + CornerOffset; r.size.height = y2 - y1 + CornerOffset; return r; } #endif #endif }; LgiClass bool operator ==(LRect &a, LRect &b); LgiClass bool operator !=(LRect &a, LRect &b); /// A region is a list of non-overlapping rectangles that can describe any shape class LgiClass LRegion : public LRect { /// Current number of stored rectangles int Size; /// The size of the memory allocated for rectangle int Alloc; /// The current index (must be >= 0 and < Size) int Current; /// The array of rectangles LRect *a; bool SetLength(int s); LRect *NewOne() { return (SetLength(Size+1)) ? a+(Size-1) : 0; } bool Delete(int i); public: LRegion(); LRegion(int X1, int Y1, int X2, int Y2); LRegion(const LRect &r); LRegion(OsRect &r); LRegion(LRegion &c); ~LRegion(); int X() { return x2 - x1 + 1; } int Y() { return y2 - y1 + 1; } int Length() { return Size; } LRect *operator [](int i) { return (i >= 0 && i < Size) ? a+i : 0; } LRegion &operator =(const LRect &r); LRect *First(); LRect *Last(); LRect *Next(); LRect *Prev(); void Empty(); void ZOff(int x, int y); void Normal(); bool Valid(); void Offset(int x, int y); void Bound(LRect *b); LRect Bound(); bool Overlap(LRect *b); bool Overlap(int x, int y); bool Overlap(LPoint &p); void Union(LRect *a); void Intersect(LRect *a); void Subtract(LRect *a); /// This joins adjacent blocks into one rect where possible void Simplify ( /// If this is set only blocks that are align perfectly will be /// merged. Otherwise any adjacent block will be merged as a union /// of the 2 blocks. Resulting in a larger region than before. bool PerfectlyAlignOnly ); friend bool operator ==(LRegion &a, LRegion &b); friend bool operator !=(LRegion &a, LRegion &b); }; #endif diff --git a/include/lgi/common/RichTextEdit.h b/include/lgi/common/RichTextEdit.h --- a/include/lgi/common/RichTextEdit.h +++ b/include/lgi/common/RichTextEdit.h @@ -1,238 +1,238 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef _RICH_TEXT_EDIT_H_ #define _RICH_TEXT_EDIT_H_ #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Capabilities.h" #include "lgi/common/FindReplaceDlg.h" #if _DEBUG #include "lgi/common/Tree.h" #endif enum RichEditMsgs { M_BLOCK_MSG = M_USER + 0x1000, M_IMAGE_LOAD_FILE, M_IMAGE_SET_SURFACE, M_IMAGE_ERROR, M_IMAGE_COMPONENT_MISSING, M_IMAGE_PROGRESS, M_IMAGE_RESAMPLE, M_IMAGE_FINISHED, M_IMAGE_COMPRESS, M_IMAGE_ROTATE, M_IMAGE_FLIP, M_IMAGE_LOAD_STREAM, M_COMPONENT_INSTALLED, // A = LString *ComponentName }; extern char Delimiters[]; /// Styled unicode text editor control. class #if defined(MAC) LgiClass #endif LRichTextEdit : public LDocView, public ResObject, public LDragDropTarget, public LCapabilityClient { friend bool RichText_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum GTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; protected: class LRichTextPriv *d; friend class LRichTextPriv; bool IndexAt(int x, int y, ssize_t &Off, int &LineHint); // Overridables virtual void PourText(ssize_t Start, ssize_t Length); virtual void PourStyle(ssize_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); public: // Construction LRichTextEdit( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = 0); ~LRichTextEdit(); const char *GetClass() { return "LRichTextEdit"; } // Data const char *Name(); bool Name(const char *s); const char16 *NameW(); bool NameW(const char16 *s); int64 Value(); void Value(int64 i); const char *GetMimeType() { return "text/html"; } int GetSize(); const char *GetCharset(); void SetCharset(const char *s); ssize_t HitTest(int x, int y); bool DeleteSelection(char16 **Cut = 0); bool SetSpellCheck(class LSpellCheck *sp); bool GetFormattedContent(const char *MimeType, LString &Out, LArray *Media = NULL); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); // Font LFont *GetFont(); void SetFont(LFont *f, bool OwnIt = false); void SetFixedWidthFont(bool i); // Options void SetTabSize(uint8_t i); void SetReadOnly(bool i); bool ShowStyleTools(); void ShowStyleTools(bool b); enum RectType { ContentArea, ToolsArea, // CapabilityArea, // CapabilityBtn, FontFamilyBtn, FontSizeBtn, BoldBtn, ItalicBtn, UnderlineBtn, ForegroundColourBtn, BackgroundColourBtn, MakeLinkBtn, RemoveLinkBtn, RemoveStyleBtn, EmojiBtn, HorzRuleBtn, MaxArea }; LRect GetArea(RectType Type); /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW void SetWrapType(LDocWrapType i); // State / Selection void SetCursor(int i, bool Select, bool ForceFullUpdate = false); ssize_t IndexAt(int x, int y); bool IsDirty(); void IsDirty(bool d); bool HasSelection(); void UnSelectAll(); void SelectWord(size_t From); void SelectAll(); ssize_t GetCaret(bool Cursor = true); bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1); size_t GetLines(); void GetTextExtent(int &x, int &y); char *GetSelection(); void SetStylePrefix(LString s); bool IsBusy(bool Stop = false); // File IO bool Open(const char *Name, const char *Cs = 0); bool Save(const char *Name, const char *Cs = 0); // Clipboard IO bool Cut(); bool Copy(); bool Paste(); // Undo/Redo void Undo(); void Redo(); bool GetUndoOn(); void SetUndoOn(bool b); // Action UI - virtual bool DoGoto(); - virtual bool DoCase(bool Upper); - virtual bool DoFind(); - virtual bool DoFindNext(); - virtual bool DoReplace(); + virtual void DoGoto(std::function Callback); + virtual void DoCase(std::function Callback, bool Upper); + virtual void DoFind(std::function Callback); + virtual void DoFindNext(std::function Callback); + virtual void DoReplace(std::function Callback); // Action Processing bool ClearDirty(bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); int GetLine(); void SetLine(int Line); LDocFindReplaceParams *CreateFindReplaceParams(); void SetFindReplaceParams(LDocFindReplaceParams *Params); void OnAddStyle(const char *MimeType, const char *Styles); // Object Events bool OnFind(LFindReplaceCommon *Params); bool OnReplace(LFindReplaceCommon *Params); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange(); void OnCreate(); void OnEscape(LKey &K); bool OnMouseWheel(double Lines); // Capability target stuff // bool NeedsCapability(const char *Name, const char *Param = NULL); // void OnInstall(CapsHash *Caps, bool Status); // void OnCloseInstaller(); // Window Events void OnFocus(bool f); void OnMouseClick(LMouse &m); void OnMouseMove(LMouse &m); bool OnKey(LKey &k); void OnPaint(LSurface *pDC); LMessage::Result OnEvent(LMessage *Msg); int OnNotify(LViewI *Ctrl, LNotification n); void OnPulse(); int OnHitTest(int x, int y); bool OnLayout(LViewLayoutInfo &Inf); // D'n'd target int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); // Virtuals virtual bool Insert(int At, char16 *Data, int Len); virtual bool Delete(int At, int Len); virtual void OnEnter(LKey &k); virtual void OnUrl(char *Url); virtual void DoContextMenu(LMouse &m); #if _DEBUG void DumpNodes(LTree *Root); void SelectNode(LString Param); #endif }; #endif diff --git a/include/lgi/common/TextView3.h b/include/lgi/common/TextView3.h --- a/include/lgi/common/TextView3.h +++ b/include/lgi/common/TextView3.h @@ -1,437 +1,439 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef __GTEXTVIEW3_H #define __GTEXTVIEW3_H +#include + #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Css.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/FindReplaceDlg.h" // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str extern char Delimiters[]; class LTextView3; /// Unicode text editor control. class LgiClass LTextView3 : public LDocView, public ResObject, public LDragDropTarget { friend struct LTextView3Undo; friend bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum Messages { M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421, M_TEXTVIEW_FIND, M_TEXTVIEW_REPLACE, M_TEXT_POUR_CONTINUE, }; enum StyleOwners { STYLE_NONE, STYLE_IDE, STYLE_SPELLING, STYLE_FIND_MATCHES, STYLE_ADDRESS, STYLE_URL, }; class LStyle { protected: void RefreshLayout(size_t Start, ssize_t Len); public: /// The view the style is for LTextView3 *View; /// When you write several bits of code to do styling assign them /// different owner id's so that they can manage the lifespan of their /// own styles. LTextView3::PourStyle is owner '0', anything else it /// will leave alone. StyleOwners Owner; /// The start index into the text buffer of the region to style. ssize_t Start; /// The length of the styled region ssize_t Len; /// The font to draw the styled text in LFont *Font; /// The colour to draw with. If transparent, then the default /// line colour is used. LColour Fore, Back; /// Cursor LCursor Cursor; /// Optional extra decor not supported by the fonts LCss::TextDecorType Decor; /// Colour for the optional decor. LColour DecorColour; /// Application base data LVariant Data; LStyle(StyleOwners owner = STYLE_NONE) { Owner = owner; View = NULL; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; } LStyle(const LStyle &s) { Owner = s.Owner; View = s.View; Font = s.Font; Start = s.Start; Len = s.Len; Decor = s.Decor; DecorColour = s.DecorColour; Fore = s.Fore; Back = s.Back; Data = s.Data; Cursor = s.Cursor; } LStyle &Construct(LTextView3 *view, StyleOwners owner) { View = view; Owner = owner; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; return *this; } void Empty() { Start = -1; Len = 0; } bool Valid() { return Start >= 0 && Len > 0; } size_t End() const { return Start + Len; } /// \returns true if style is the same bool operator ==(const LStyle &s) { return Owner == s.Owner && Start == s.Start && Len == s.Len && Fore == s.Fore && Back == s.Back && Decor == s.Decor; } /// Returns true if this style overlaps the position of 's' bool Overlap(LStyle &s) { return Overlap(s.Start, s.Len); } /// Returns true if this style overlaps the position of 's' bool Overlap(ssize_t sStart, ssize_t sLen) { if (sStart + sLen - 1 < Start || sStart >= Start + Len) return false; return true; } void Union(const LStyle &s) { if (Start < 0) { Start = s.Start; Len = s.Len; } else { Start = MIN(Start, s.Start); Len = MAX(End(), s.End()) - Start; } } }; friend class LTextView3::LStyle; protected: // Internal classes enum GTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; class LTextLine : public LRange { public: LRect r; // Screen location LColour c; // Colour of line... transparent = default colour LColour Back; // Background colour or transparent LTextLine() { Start = -1; Len = 0; r.ZOff(-1, -1); } virtual ~LTextLine() {} bool Overlap(ssize_t i) { return i>=Start && i<=Start+Len; } size_t CalcLen(char16 *Text) { char16 *c = Text + Start, *e = c; while (*e && *e != '\n') e++; return Len = e - c; } }; class LTextView3Private *d; friend class LTextView3Private; // Options bool Dirty = false; bool CanScrollX = false; // Display LFont *Font = NULL; LFont *Bold = NULL; // Bold variant of 'Font' LFont *Underline = NULL; // Underline variant of 'Font' LFont *FixedFont = NULL; int LineY = 1; ssize_t SelStart = -1, SelEnd = -1; int DocOffset = 0; int MaxX = 0; bool Blink = true; uint64 BlinkTs = 0; int ScrollX = 0; LRect CursorPos; /// true if the text pour process is still ongoing bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots // of related edits at the same time. And then manually pour once // finished. bool PartialPour = false; // True if the pour is happening in the background. It's not threaded // but taking place in the GUI thread via timer. size_t PartialPourLines = 0;// Partial pour max lines, if we restart this tracks the max we saw... bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true) List Line; LUnrolledList Style; // sorted in 'Start' order typedef LUnrolledList::Iter StyleIter; // For ::Name(...) char *TextCache = NULL; // Data char16 *Text; ssize_t Cursor; ssize_t Size; ssize_t Alloc; // Undo stuff bool UndoOn = true; LUndo UndoQue; struct LTextView3Undo *UndoCur = NULL; // private methods List::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0); LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { return *GetTextLineIt(Offset, Index); } ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where); int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin); bool ScrollToOffset(size_t Off); int ScrollYLine(); int ScrollYPixel(); LRect DocToScreen(LRect r); ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); void InternalPulse(); // styles bool InsertStyle(LAutoPtr s); LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1); LStyle *HitStyle(ssize_t i); int GetColumn(); int SpaceDepth(char16 *Start, char16 *End); int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false); // Overridables virtual void PourText(size_t Start, ssize_t Length); virtual void PourStyle(size_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false); #ifdef _DEBUG // debug uint64 _PourTime; uint64 _StyleTime; uint64 _PaintTime; #endif void LogLines(); bool ValidateLines(bool CheckBox = false); public: // Construction LTextView3( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = NULL); ~LTextView3(); const char *GetClass() override { return "LTextView3"; } // Data const char *Name() override; bool Name(const char *s) override; const char16 *NameW() override; bool NameW(const char16 *s) override; int64 Value() override; void Value(int64 i) override; const char *GetMimeType() override { return "text/plain"; } size_t Length() { return Size; } LString operator[](ssize_t LineIdx); const char16 *TextAtLine(size_t Index); ssize_t HitText(int x, int y, bool Nearest); void DeleteSelection(char16 **Cut = 0); // Font LFont *GetFont() override; LFont *GetBold(); void SetFont(LFont *f, bool OwnIt = false) override; void SetFixedWidthFont(bool i) override; // Options void SetTabSize(uint8_t i) override; void SetBorder(int b); void SetReadOnly(bool i) override; void SetCrLf(bool crlf) override; /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW void SetWrapType(LDocWrapType i) override; // State / Selection ssize_t GetCaret(bool Cursor = true) override; virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override; ssize_t IndexAt(int x, int y) override; bool IsDirty() override { return Dirty; } void IsDirty(bool d) { Dirty = d; } bool HasSelection() override; void UnSelectAll() override; void SelectWord(size_t From) override; void SelectAll() override; bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override; size_t GetLines() override; void GetTextExtent(int &x, int &y) override; char *GetSelection() override; LRange GetSelectionRange(); // File IO bool Open(const char *Name, const char *Cs = NULL) override; bool Save(const char *Name, const char *Cs = NULL) override; const char *GetLastError(); // Clipboard IO bool Cut() override; bool Copy() override; bool Paste() override; // Undo/Redo void Undo(); void Redo(); bool GetUndoOn() { return UndoOn; } void SetUndoOn(bool b) { UndoOn = b; } // Action UI - virtual bool DoGoto(); - virtual bool DoCase(bool Upper); - virtual bool DoFind() override; - virtual bool DoFindNext(); - virtual bool DoReplace() override; + virtual void DoGoto(std::function Callback); + virtual void DoCase(std::function Callback, bool Upper); + virtual void DoFind(std::function Callback) override; + virtual void DoFindNext(std::function Callback); + virtual void DoReplace(std::function Callback) override; // Action Processing - bool ClearDirty(bool Ask, const char *FileName = 0); + void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); ssize_t GetLine(); - void SetLine(int Line, bool select = false); + void SetLine(int64_t Line, bool select = false); LDocFindReplaceParams *CreateFindReplaceParams() override; void SetFindReplaceParams(LDocFindReplaceParams *Params) override; // Object Events virtual bool OnFind( const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); virtual bool OnReplace( const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange() override; void OnCreate() override; void OnEscape(LKey &K) override; bool OnMouseWheel(double Lines) override; // Window Events void OnFocus(bool f) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPulse() override; int OnHitTest(int x, int y) override; bool OnLayout(LViewLayoutInfo &Inf) override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; LCursor GetCursor(int x, int y) override; // Virtuals virtual bool Insert(size_t At, const char16 *Data, ssize_t Len); virtual bool Delete(size_t At, ssize_t Len); virtual void OnEnter(LKey &k) override; virtual void OnUrl(char *Url) override; virtual void DoContextMenu(LMouse &m); virtual bool OnStyleClick(LStyle *style, LMouse *m); virtual bool OnStyleMenu(LStyle *style, LSubMenu *m); virtual void OnStyleMenuClick(LStyle *style, int i); }; #endif diff --git a/include/lgi/common/TextView4.h b/include/lgi/common/TextView4.h --- a/include/lgi/common/TextView4.h +++ b/include/lgi/common/TextView4.h @@ -1,434 +1,434 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef _GTEXTVIEW4_H #define _GTEXTVIEW4_H #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Css.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/FindReplaceDlg.h" // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str extern char Delimiters[]; class LTextView4; /// Unicode text editor control. class LgiClass LTextView4 : public LDocView, public ResObject, public LDragDropTarget { friend struct LTextView4Undo; friend bool Text4_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum Messages { M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421, M_TEXTVIEW_FIND, M_TEXTVIEW_REPLACE, M_TEXT_POUR_CONTINUE, }; enum StyleOwners { STYLE_NONE, STYLE_IDE, STYLE_SPELLING, STYLE_FIND_MATCHES, STYLE_ADDRESS, STYLE_URL, }; class LStyle { protected: void RefreshLayout(size_t Start, ssize_t Len); public: /// The view the style is for LTextView4 *View; /// When you write several bits of code to do styling assign them /// different owner id's so that they can manage the lifespan of their /// own styles. LTextView4::PourStyle is owner '0', anything else it /// will leave alone. StyleOwners Owner; /// The start index into the text buffer of the region to style. ssize_t Start; /// The length of the styled region ssize_t Len; /// The font to draw the styled text in LFont *Font; /// The colour to draw with. If transparent, then the default /// line colour is used. LColour Fore, Back; /// Cursor LCursor Cursor; /// Optional extra decor not supported by the fonts LCss::TextDecorType Decor; /// Colour for the optional decor. LColour DecorColour; /// Application base data LVariant Data; LStyle(StyleOwners owner = STYLE_NONE) { Owner = owner; View = NULL; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; } LStyle(const LStyle &s) { Owner = s.Owner; View = s.View; Font = s.Font; Start = s.Start; Len = s.Len; Decor = s.Decor; DecorColour = s.DecorColour; Fore = s.Fore; Back = s.Back; Data = s.Data; Cursor = s.Cursor; } LStyle &Construct(LTextView4 *view, StyleOwners owner) { View = view; Owner = owner; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; return *this; } void Empty() { Start = -1; Len = 0; } bool Valid() { return Start >= 0 && Len > 0; } size_t End() const { return Start + Len; } /// \returns true if style is the same bool operator ==(const LStyle &s) { return Owner == s.Owner && Start == s.Start && Len == s.Len && Fore == s.Fore && Back == s.Back && Decor == s.Decor; } /// Returns true if this style overlaps the position of 's' bool Overlap(LStyle &s) { return Overlap(s.Start, s.Len); } /// Returns true if this style overlaps the position of 's' bool Overlap(ssize_t sStart, ssize_t sLen) { if (sStart + sLen - 1 < Start || sStart >= Start + Len) return false; return true; } void Union(const LStyle &s) { if (Start < 0) { Start = s.Start; Len = s.Len; } else { Start = MIN(Start, s.Start); Len = MAX(End(), s.End()) - Start; } } }; friend class LTextView4::LStyle; protected: // Internal classes enum GTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; class LTextLine : public LRange { public: LRect r; // Screen location LColour c; // Colour of line... transparent = default colour LColour Back; // Background colour or transparent LTextLine() { Start = -1; Len = 0; r.ZOff(-1, -1); } virtual ~LTextLine() {} size_t CalcLen(char16 *Text) { char16 *c = Text + Start, *e = c; while (*e && *e != '\n') e++; return Len = e - c; } }; class LTextView4Private *d; friend class LTextView4Private; // Options bool Dirty; bool CanScrollX; // Display LFont *Font; LFont *Bold; // Bold variant of 'Font' LFont *Underline; // Underline variant of 'Font' LFont *FixedFont; int LineY; ssize_t SelStart, SelEnd; int DocOffset; int MaxX; bool Blink; uint64 BlinkTs; int ScrollX; LRect CursorPos; /// true if the text pour process is still ongoing bool PourEnabled; // True if pouring the text happens on edit. Turn off if doing lots // of related edits at the same time. And then manually pour once // finished. bool PartialPour; // True if the pour is happening in the background. It's not threaded // but taking place in the GUI thread via timer. bool AdjustStylePos; // Insert/Delete moved styles automatically to match (default: true) LArray Line; LUnrolledList Style; // sorted in 'Start' order typedef LUnrolledList::Iter StyleIter; // For ::Name(...) char *TextCache; // Data char16 *Text; ssize_t Cursor; ssize_t Size; ssize_t Alloc; // Undo stuff bool UndoOn; LUndo UndoQue; struct LTextView4Undo *UndoCur; // private methods LArray::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0); LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { auto it = GetTextLineIt(Offset, Index); return it != Line.end() ? *it : NULL; } ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where); int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin); bool ScrollToOffset(size_t Off); int ScrollYLine(); int ScrollYPixel(); LRect DocToScreen(LRect r); ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); void InternalPulse(); // styles bool InsertStyle(LAutoPtr s); LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1); LStyle *HitStyle(ssize_t i); int GetColumn(); int SpaceDepth(char16 *Start, char16 *End); int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false); // Overridables virtual void PourText(size_t Start, ssize_t Length); virtual void PourStyle(size_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false); #ifdef _DEBUG // debug uint64 _PourTime; uint64 _StyleTime; uint64 _PaintTime; #endif void LogLines(); bool ValidateLines(bool CheckBox = false); public: // Construction LTextView4( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = NULL); ~LTextView4(); const char *GetClass() override { return "LTextView4"; } // Data const char *Name() override; bool Name(const char *s) override; const char16 *NameW() override; bool NameW(const char16 *s) override; int64 Value() override; void Value(int64 i) override; const char *GetMimeType() override { return "text/plain"; } size_t Length() const { return Size; } LString operator[](ssize_t LineIdx); ssize_t HitText(int x, int y, bool Nearest); void DeleteSelection(char16 **Cut = 0); // Font LFont *GetFont() override; LFont *GetBold(); void SetFont(LFont *f, bool OwnIt = false) override; void SetFixedWidthFont(bool i) override; // Options void SetTabSize(uint8_t i) override; void SetBorder(int b); void SetReadOnly(bool i) override; void SetCrLf(bool crlf) override; /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW void SetWrapType(LDocWrapType i) override; // State / Selection ssize_t GetCaret(bool Cursor = true) override; virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override; ssize_t IndexAt(int x, int y) override; bool IsDirty() override { return Dirty; } void IsDirty(bool d) { Dirty = d; } bool HasSelection() override; void UnSelectAll() override; void SelectWord(size_t From) override; void SelectAll() override; bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override; size_t GetLines() override; void GetTextExtent(int &x, int &y) override; char *GetSelection() override; LRange GetSelectionRange(); // File IO bool Open(const char *Name, const char *Cs = 0) override; bool Save(const char *Name, const char *Cs = 0) override; const char *GetLastError(); // Clipboard IO bool Cut() override; bool Copy() override; bool Paste() override; // Undo/Redo void Undo(); void Redo(); bool GetUndoOn() { return UndoOn; } void SetUndoOn(bool b) { UndoOn = b; } // Action UI - virtual bool DoGoto(); - virtual bool DoCase(bool Upper); - virtual bool DoFind() override; - virtual bool DoFindNext(); - virtual bool DoReplace() override; + virtual void DoGoto(std::function Callback); + virtual void DoCase(std::function Callback, bool Upper); + virtual void DoFind(std::function Callback) override; + virtual void DoFindNext(std::function Callback); + virtual void DoReplace(std::function Callback) override; // Action Processing - bool ClearDirty(bool Ask, const char *FileName = 0); + void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); ssize_t GetLine(); - void SetLine(int Line); + void SetLine(int64_t Line); LDocFindReplaceParams *CreateFindReplaceParams() override; void SetFindReplaceParams(LDocFindReplaceParams *Params) override; // Object Events virtual bool OnFind( const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); virtual bool OnReplace( const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange() override; void OnCreate() override; void OnEscape(LKey &K) override; bool OnMouseWheel(double Lines) override; // Window Events void OnFocus(bool f) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPulse() override; int OnHitTest(int x, int y) override; bool OnLayout(LViewLayoutInfo &Inf) override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; LCursor GetCursor(int x, int y) override; // Virtuals virtual bool Insert(size_t At, const char16 *Data, ssize_t Len); virtual bool Delete(size_t At, ssize_t Len); virtual void OnEnter(LKey &k) override; virtual void OnUrl(char *Url) override; virtual void DoContextMenu(LMouse &m); virtual bool OnStyleClick(LStyle *style, LMouse *m); virtual bool OnStyleMenu(LStyle *style, LSubMenu *m); virtual void OnStyleMenuClick(LStyle *style, int i); }; #endif // _GTEXTVIEW4_H diff --git a/include/lgi/common/Tree.h b/include/lgi/common/Tree.h --- a/include/lgi/common/Tree.h +++ b/include/lgi/common/Tree.h @@ -1,307 +1,305 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A tree/heirarchy control #ifndef __GTREE2_H #define __GTREE2_H #include "lgi/common/ItemContainer.h" #include enum LTreeItemRect { TreeItemPos, TreeItemThumb, TreeItemText, TreeItemIcon }; class LTreeItem; class LgiClass LTreeNode { protected: class LTree *Tree; LTreeItem *Parent; List Items; virtual LTreeItem *Item() { return 0; } virtual LRect *Pos() { return 0; } virtual void _ClearDs(int Col); void _Visible(bool v); void SetLayoutDirty(); public: LTreeNode(); virtual ~LTreeNode(); /// Inserts a tree item as a child at 'Pos' LTreeItem *Insert(LTreeItem *Obj = NULL, ssize_t Pos = -1); /// Removes this node from it's parent, for permanent separation. void Remove(); /// Detachs the item from the tree so it can be re-inserted else where. void Detach(); /// Gets the node after this one at the same level. LTreeItem *GetNext(); /// Gets the node before this one at the same level. LTreeItem *GetPrev(); /// Gets the first child node. LTreeItem *GetChild(); /// Gets the parent of this node. LTreeItem *GetParent() { return Parent; } /// Gets the owning tree. May be NULL if not attached to a tree. LTree *GetTree() { return Tree; } /// Returns true if this is the root node. bool IsRoot(); /// Returns the index of this node in the list of item owned by it's parent. ssize_t IndexOf(); /// \returns number of child. size_t Length(); /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); List::I begin() { return Items.begin(); } List::I end() { return Items.end(); } /// Sorts the child items template bool Sort(int (*Compare)(LTreeItem*, LTreeItem*, T user_param), T user_param = 0) { if (!Compare) return false; Items.Sort(Compare, user_param); SetLayoutDirty(); return true; } /// Calls a f(n) for each int ForEach(std::function Fn); virtual bool Expanded() { return false; } virtual void Expanded(bool b) {} virtual void OnVisible(bool v) {} }; /// The item class for a tree. This defines a node in the heirarchy. class LgiClass LTreeItem : public LItem, public LTreeNode { friend class LTree; friend class LTreeNode; protected: class LTreeItemPrivate *d; // Private methods void _RePour(); void _Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible); void _Remove(); void _MouseClick(LMouse &m); void _SetTreePtr(LTree *t); LTreeItem *_HitTest(int x, int y, bool Debug = false); LRect *_GetRect(LTreeItemRect Which); LPoint _ScrollPos(); LTreeItem *Item() override { return this; } LRect *Pos() override; virtual void _PourText(LPoint &Size); virtual void _PaintText(LItem::ItemPaintCtx &Ctx); void _ClearDs(int Col) override; virtual void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c); int GetColumnSize(int Col); protected: LString::Array Str; int Sys_Image = -1; public: LTreeItem(); virtual ~LTreeItem(); LItemContainer *GetContainer() override; /// \brief Get the text for the node /// /// You can either return a string stored internally to your /// object by implementing this function in your item class /// or use the SetText function to store the string in this /// class. const char *GetText(int i = 0) override; /// \brief Sets the text for the node. /// /// This will allocate and store the string in this class. bool SetText(const char *s, int i=0) override; /// Returns the icon index into the parent tree's LImageList. int GetImage(int Flags = 0) override; /// Sets the icon index into the parent tree's LImageList. void SetImage(int i) override; /// Tells the item to update itself on the screen when the /// LTreeItem::GetText data has changed. void Update() override; /// Returns true if the tree item is currently selected. bool Select() override; /// Selects or deselects the tree item. void Select(bool b) override; /// Returns true if the node has children and is open. bool Expanded() override; /// Opens or closes the node to show or hide the children. void Expanded(bool b) override; /// Scrolls the tree view so this node is visible. void ScrollTo() override; /// Gets the bounding box of the item. LRect *GetPos(int Col = -1) override; /// True if the node is the drop target bool IsDropTarget(); /// Called when the node expands/contracts to show or hide it's children. virtual void OnExpand(bool b); /// Paints the item void OnPaint(ItemPaintCtx &Ctx) override; void OnPaint(LSurface *pDC) override { LAssert(0); } }; /// A tree control. class LgiClass LTree : public LItemContainer, public ResObject, public LTreeNode { friend class LTreeItem; friend class LTreeNode; class LTreePrivate *d; // Private methods void _Pour(); void _OnSelect(LTreeItem *Item); void _Update(LRect *r = 0, bool Now = false); void _UpdateBelow(int y, bool Now = false); void _UpdateScrollBars(); List *GetSelLst(); protected: // Options bool Lines; bool Buttons; bool LinesAtRoot; bool EditLabels; LRect rItems; LPoint _ScrollPos(); LTreeItem *GetAdjacent(LTreeItem *From, bool Down); void OnDragEnter(); void OnDragExit(); void ClearDs(int Col) override; public: LTree(int id, int x = 0, int y = 0, int cx = 100, int cy = 100, const char *name = NULL); ~LTree(); const char *GetClass() override { return "LTree"; } /// Called when an item is clicked virtual void OnItemClick(LTreeItem *Item, LMouse &m); /// Called when an item is dragged from it's position virtual void OnItemBeginDrag(LTreeItem *Item, LMouse &m); /// Called when an item is expanded/contracted to show or hide it's children virtual void OnItemExpand(LTreeItem *Item, bool Expand); /// Called when an item is selected virtual void OnItemSelect(LTreeItem *Item); // Implementation void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnMouseWheel(double Lines) override; void OnPaint(LSurface *pDC) override; void OnFocus(bool b) override; void OnPosChange() override; bool OnKey(LKey &k) override; int OnNotify(LViewI *Ctrl, LNotification n) override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPulse() override; int GetContentSize(int ColumnIdx) override; LCursor GetCursor(int x, int y) override; - bool Lock(const char *file, int line, int TimeOut = -1) override; - void Unlock() override; /// Add a item to the tree LTreeItem *Insert(LTreeItem *Obj = 0, ssize_t Pos = -1); /// Remove and delete an item bool Delete(LTreeItem *Obj); /// Remove but don't delete an item bool Remove(LTreeItem *Obj); /// Gets the item at an index LTreeItem *ItemAt(size_t Pos) { return Items[Pos]; } /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); /// Select the item 'Obj' bool Select(LTreeItem *Obj); /// Returns the first selected item LTreeItem *Selection(); /// Gets the whole selection and puts it in 'n' template bool GetSelection(LArray &n) { n.Empty(); auto s = GetSelLst(); for (auto i : *s) { T *ptr = dynamic_cast(i); if (ptr) n.Add(ptr); } return n.Length() > 0; } /// Gets an array of all items template bool GetAll(LArray &n) { n.Empty(); return ForAllItems([&n](LTreeItem *item) { T *t = dynamic_cast(item); if (t) n.Add(t); }); } /// Call a function for every item bool ForAllItems(std::function Callback); /// Returns the item at an x,y location LTreeItem *ItemAtPoint(int x, int y, bool Debug = false); /// Temporarily selects one of the items as the drop target during a /// drag and drop operation. Call SelectDropTarget(0) when done. void SelectDropTarget(LTreeItem *Item); /// Delete all items (frees the items) void Empty(); /// Remove reference to items (doesn't free the items) void RemoveAll(); /// Call 'Update' on all tree items void UpdateAllItems() override; // Visual style enum ThumbStyle { TreePlus, TreeTriangle }; void SetVisualStyle(ThumbStyle Btns, bool JoiningLines); }; #endif diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h --- a/include/lgi/common/Variant.h +++ b/include/lgi/common/Variant.h @@ -1,437 +1,437 @@ /** \file \author Matthew Allen \brief Variant class.\n Copyright (C), Matthew Allen */ #ifndef __LVariant_H__ #define __LVariant_H__ #include "lgi/common/Dom.h" #undef Bool #include "lgi/common/DateTime.h" #include "lgi/common/Containers.h" #include "lgi/common/HashTable.h" #include "lgi/common/LgiString.h" class LCompiledCode; #if !defined(_MSC_VER) && !defined(LINUX) && (defined(LGI_64BIT) || defined(MAC)) && !defined(HAIKU) #define LVARIANT_SIZET 1 #define LVARIANT_SSIZET 1 #endif /// The different types the varient can be. /// \sa LVariant::TypeToString to convert to string. enum LVariantType { // Main types /// Null type GV_NULL, /// 32-bit integer GV_INT32, /// 64-bit integer GV_INT64, /// true or false boolean. GV_BOOL, /// C++ double GV_DOUBLE, /// Null terminated string value GV_STRING, /// Block of binary data GV_BINARY, /// List of LVariant GV_LIST, /// Pointer to LDom object GV_DOM, /// DOM reference, ie. a variable in a DOM object GV_DOMREF, /// Untyped pointer GV_VOID_PTR, /// LDateTime class. GV_DATETIME, /// Hash table class, containing pointers to LVariants GV_HASHTABLE, // Scripting language operator GV_OPERATOR, // Custom scripting lang type GV_CUSTOM, // Wide string GV_WSTRING, // LSurface ptr GV_LSURFACE, /// Pointer to LView GV_GVIEW, /// Pointer to LMouse GV_LMOUSE, /// Pointer to LKey GV_LKEY, /// Pointer to LStream GV_STREAM, /// The maximum value for the variant type. /// (This is used by the scripting engine to refer to a LVariant itself) GV_MAX, }; /// Language operators enum LOperator { OpNull, OpAssign, OpPlus, OpUnaryPlus, OpMinus, OpUnaryMinus, OpMul, OpDiv, OpMod, OpLessThan, OpLessThanEqual, OpGreaterThan, OpGreaterThanEqual, OpEquals, OpNotEquals, OpPlusEquals, OpMinusEquals, OpMulEquals, OpDivEquals, OpPostInc, OpPostDec, OpPreInc, OpPreDec, OpAnd, OpOr, OpNot, }; class LgiClass LCustomType : public LDom { protected: struct CustomField : public LDom { ssize_t Offset; ssize_t Bytes; ssize_t ArrayLen; LVariantType Type; LString Name; LCustomType *Nested; ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address; int FrameSize; Method() { Address = -1; FrameSize = -1; } }; protected: // Global vars int Pack; size_t Size; LString Name; // Fields LArray Flds; LHashTbl, int> FldMap; // Methods LArray Methods; LHashTbl, Method*> MethodMap; // Private methods ssize_t PadSize(); public: LCustomType(const char *name, int pack = 1); LCustomType(const char16 *name, int pack = 1); ~LCustomType(); size_t Sizeof(); const char *GetName() { return Name; } ssize_t Members() { return Flds.Length(); } int AddressOf(const char *Field); int IndexOf(const char *Field); bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1); bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1); Method *DefineMethod(const char *Name, LArray &Params, size_t Address); Method *GetMethod(const char *Name); // Field access. You can't use the LDom interface to get/set member variables because // there is no provision for the 'This' pointer. bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0); bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0); // Dom access. However the DOM can be used to access information about the type itself. // Which doesn't need a 'This' pointer. bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args); }; /// A class that can be different types class LgiClass LVariant { public: typedef LHashTbl,LVariant*> LHash; /// The type of the variant LVariantType Type; /// The value of the variant union { /// Valid when Type == #GV_INT32 int Int; /// Valid when Type == #GV_BOOL bool Bool; /// Valid when Type == #GV_INT64 int64 Int64; /// Valid when Type == #GV_DOUBLE double Dbl; /// Valid when Type == #GV_STRING char *String; /// Valid when Type == #GV_WSTRING char16 *WString; /// Valid when Type == #GV_DOM LDom *Dom; /// Valid when Type is #GV_VOID_PTR, #GV_GVIEW, #GV_LMOUSE or #GV_LKEY void *Ptr; /// Valid when Type == #GV_BINARY struct _Binary { ssize_t Length; void *Data; } Binary; /// Valid when Type == #GV_LIST List *Lst; /// Valid when Type == #GV_HASHTABLE LHash *Hash; /// Valid when Type == #GV_DATETIME LDateTime *Date; /// Valid when Type == #GV_CUSTOM struct _Custom { LCustomType *Dom; uint8_t *Data; bool operator == (_Custom &c) { return Dom == c.Dom && Data == c.Data; } } Custom; /// Valid when Type == #GV_DOMREF struct _DomRef { /// The pointer to the dom object LDom *Dom; /// The name of the variable to set/get in the dom object char *Name; } DomRef; /// Valid when Type == #GV_OPERATOR LOperator Op; /// Valid when Type == #GV_LSURFACE struct { class LSurface *Ptr; bool Own; LSurface *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Surface; /// Valid when Type == #GV_STREAM struct { class LStreamI *Ptr; bool Own; LStreamI *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Stream; /// Valid when Type == #GV_GVIEW class LView *View; /// Valid when Type == #GV_LMOUSE class LMouse *Mouse; /// Valid when Type == #GV_LKEY class LKey *Key; } Value; /// Constructor to null LVariant(); /// Constructor for integers LVariant(int32_t i); LVariant(uint32_t i); LVariant(int64_t i); LVariant(uint64_t i); #if LVARIANT_SIZET LVariant(size_t i); #endif #if LVARIANT_SSIZET LVariant(ssize_t i); #endif /// Constructor for double LVariant(double i); /// Constructor for string LVariant(const char *s); /// Constructor for wide string LVariant(const char16 *s); /// Constructor for ptr LVariant(void *p); /// Constructor for DOM ptr LVariant(LDom *p); /// Constructor for DOM variable reference LVariant(LDom *p, char *name); /// Constructor for date LVariant(const LDateTime *d); /// Constructor for variant LVariant(LVariant const &v); /// Constructor for operator LVariant(LOperator Op); /// Destructor ~LVariant(); /// Assign bool value LVariant &operator =(bool i); /// Assign an integer value LVariant &operator =(int32_t i); LVariant &operator =(uint32_t i); LVariant &operator =(int64_t i); LVariant &operator =(uint64_t i); #if LVARIANT_SIZET LVariant &operator =(size_t i); #endif #if LVARIANT_SSIZET LVariant &operator =(ssize_t i); #endif /// Assign double value LVariant &operator =(double i); /// Assign string value (makes a copy) LVariant &operator =(const char *s); /// Assign a wide string value (makes a copy) LVariant &operator =(const char16 *s); /// Assign another variant value LVariant &operator =(LVariant const &i); /// Assign value to a void ptr LVariant &operator =(void *p); /// Assign value to DOM ptr LVariant &operator =(LDom *p); /// Assign value to be a date/time LVariant &operator =(const LDateTime *d); LVariant &operator =(class LView *p); LVariant &operator =(class LMouse *p); LVariant &operator =(class LKey *k); LVariant &operator =(class LStream *s); bool operator ==(LVariant &v); bool operator !=(LVariant &v) { return !(*this == v); } /// Sets the value to a DOM variable reference bool SetDomRef(LDom *obj, char *name); /// Sets the value to a copy of block of binary data bool SetBinary(ssize_t Len, void *Data, bool Own = false); /// Sets the value to a copy of the list bool SetList(List *Lst = NULL); /// Sets the value to a hashtable bool SetHashTable(LHash *Table = NULL, bool Copy = true); /// Set the value to a surface bool SetSurface(class LSurface *Ptr, bool Own); /// Set the value to a stream bool SetStream(class LStreamI *Ptr, bool Own); /// Returns the string if valid (will convert a GV_WSTRING to utf) char *Str(); /// Returns the value as an LString LString LStr(); /// Returns a wide string if valid (will convert a GV_STRING to wide) char16 *WStr(); /// Returns the string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char *ReleaseStr(); /// Returns the wide string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char16 *ReleaseWStr(); /// Sets the variant to a heap string and takes ownership of it bool OwnStr(char *s); /// Sets the variant to a wide heap string and takes ownership of it bool OwnStr(char16 *s); /// Sets the variant to NULL void Empty(); /// Returns the byte length of the data int64 Length(); /// True if currently a int bool IsInt(); /// True if currently a bool bool IsBool(); /// True if currently a double bool IsDouble(); /// True if currently a string bool IsString(); /// True if currently a binary block bool IsBinary(); /// True if currently null bool IsNull(); /// Changes the variant's type, maintaining the value where possible. If /// no conversion is available then nothing happens. LVariant &Cast(LVariantType NewType); /// Casts the value to int, from whatever source type. The /// LVariant type does not change after calling this. - int32 CastInt32(); + int32 CastInt32() const; /// Casts the value to a 64 bit int, from whatever source type. The /// LVariant type does not change after calling this. - int64 CastInt64(); + int64 CastInt64() const; /// Casts the value to double, from whatever source type. The /// LVariant type does not change after calling this. - double CastDouble(); + double CastDouble() const; /// Cast to a string from whatever source type, the LVariant will /// take the type GV_STRING after calling this. This is because /// returning a static string is not thread safe. char *CastString(); /// Casts to a DOM ptr - LDom *CastDom(); + LDom *CastDom() const; /// Casts to a boolean. You probably DON'T want to use this function. The /// behaviour for strings -> bool is such that if the string is value it /// always evaluates to true, and false if it's not a valid string. Commonly /// what you want is to evaluate whether the string is zero or non-zero in /// which cast you should use "CastInt32() != 0" instead. - bool CastBool(); + bool CastBool() const; /// Returns the pointer if available. - void *CastVoidPtr(); + void *CastVoidPtr() const; /// Returns a LView - LView *CastView() { return Type == GV_GVIEW ? Value.View : NULL; } + LView *CastView() const { return Type == GV_GVIEW ? Value.View : NULL; } /// List insert bool Add(LVariant *v, int Where = -1); /// Converts the varient type to a string static const char *TypeToString(LVariantType t); /// Converts an operator to a string static const char *OperatorToString(LOperator op); /// Converts the value to a string description include type. LString ToString(); }; #endif diff --git a/include/lgi/common/View.h b/include/lgi/common/View.h --- a/include/lgi/common/View.h +++ b/include/lgi/common/View.h @@ -1,792 +1,796 @@ #pragma once #if defined(LGI_CARBON) LgiFunc void DumpHnd(HIViewRef v, int depth = 0); #elif LGI_COCOA && defined(__OBJC__) #include "LCocoaView.h" #endif /// \brief The base class for all windows in the GUI. /// /// This is the core object that all on screen windows inherit from. It encapsulates /// a HWND on Win32, a GtkWidget on Linux, and a NSView for Mac OS X. Used by /// itself it's not a top level window, for that see the LWindow class. /// /// To create a top level window see LWindow or LDialog. /// /// For a LView with scroll bars use LLayout. /// class LgiClass LView : virtual public LViewI, virtual public LBase { friend class LWindow; friend class LLayout; friend class LControl; friend class LMenu; friend class LSubMenu; friend class LScrollBar; friend class LDialog; friend class LDragDropTarget; friend class LPopup; friend class LWindowPrivate; friend class LViewPrivate; friend bool SysOnKey(LView *w, LMessage *m); #if defined(__GTK_H__) friend Gtk::gboolean lgi_widget_draw(Gtk::GtkWidget *widget, Gtk::cairo_t *cr); friend Gtk::gboolean lgi_widget_click(Gtk::GtkWidget *widget, Gtk::GdkEventButton *ev); friend Gtk::gboolean lgi_widget_motion(Gtk::GtkWidget *widget, Gtk::GdkEventMotion *ev); friend Gtk::gboolean GViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *view); friend Gtk::gboolean PopupEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, class LPopup *This); friend Gtk::gboolean GtkViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *This); virtual Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); public: virtual void OnGtkRealize(); virtual void OnGtkDelete(); private: #endif #if defined WIN32 friend class LWindowsClass; friend class LCombo; friend LRESULT CALLBACK DlgRedir(OsView hWnd, UINT m, WPARAM a, LPARAM b); static void CALLBACK TimerProc(OsView hwnd, UINT uMsg, UINT_PTR idEvent, uint32_t dwTime); #elif defined MAC #if LGI_COCOA #elif LGI_CARBON friend OSStatus LgiWindowProc(EventHandlerCallRef, EventRef, void *); friend OSStatus LgiRootCtrlProc(EventHandlerCallRef, EventRef, void *); friend OSStatus CarbonControlProc(EventHandlerCallRef, EventRef, void *); friend OSStatus GViewProc(EventHandlerCallRef, EventRef, void *); friend OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #elif defined HAIKU - friend class LBView; + template friend class LBView; #endif #if defined(LGI_SDL) friend Uint32 SDL_PulseCallback(Uint32 interval, LView *v); friend class LApp; #endif LRect Pos; - int _InLock; + int _InLock = 0; protected: class LViewPrivate *d = NULL; #if LGI_VIEW_HANDLE && !defined(HAIKU) OsView _View; // OS specific handle to view object #endif - LView *_Window; - LMutex *_Lock; - uint16 _BorderSize; - uint16 _IsToolBar; - int WndFlags; + LView *_Window = NULL; + #ifndef HAIKU + LMutex *_Lock = NULL; + #endif + uint16 _BorderSize = 0; + uint16 _IsToolBar = 0; + int WndFlags = 0; static LViewI *_Capturing; static LViewI *_Over; #ifndef LGI_VIEW_HASH #error "Define LGI_VIEW_HASH to 0 or 1" #endif #if LGI_VIEW_HASH public: enum LockOp { OpCreate, OpDelete, OpExists, }; static bool LockHandler(LViewI *v, LockOp Op); #endif protected: #if defined WINNATIVE uint32_t GetStyle(); void SetStyle(uint32_t i); uint32_t GetExStyle(); void SetExStyle(uint32_t i); uint32_t GetDlgCode(); void SetDlgCode(uint32_t i); /// \brief Gets the win32 class passed to CreateWindowEx() const char *GetClassW32(); /// \brief Sets the win32 class passed to CreateWindowEx() void SetClassW32(const char *s); /// \brief Creates a class to pass to CreateWindowEx(). If this methed is not /// explicitly called then the string from GetClass() is used to create a class, /// which is usually the name of the object. LWindowsClass *CreateClassW32(const char *Class = 0, HICON Icon = 0, int AddStyles = 0); virtual int SysOnNotify(int Msg, int Code) { return 0; } #elif defined MAC bool _Attach(LViewI *parent); #if LGI_COCOA public: LPoint Flip(LPoint p); LRect Flip(LRect p); virtual void OnDealloc(); #elif LGI_CARBON OsView _CreateCustomView(); virtual bool _OnGetInfo(HISize &size, HISize &line, HIRect &bounds, HIPoint &origin) { return false; } virtual void _OnScroll(HIPoint &origin) {} #endif #endif #if !WINNATIVE LView *&PopupChild(); virtual bool _Mouse(LMouse &m, bool Move); void _Focus(bool f); #endif #if LGI_COCOA protected: #endif // Complex Region searches /// Finds the largest rectangle in the region LRect *FindLargest(LRegion &r); /// Finds the smallest rectangle that would fit a window 'Sx' by 'Sy' LRect *FindSmallestFit(LRegion &r, int Sx, int Sy); /// Finds the largest rectangle on the specified LRect *FindLargestEdge ( /// The region to search LRegion &r, /// The edge to look at: /// \sa GV_EDGE_TOP, GV_EDGE_RIGHT, GV_EDGE_BOTTOM or GV_EDGE_LEFT int Edge ); virtual void _Delete(); LViewI *FindReal(LPoint *Offset = 0); bool HandleCapture(LView *Wnd, bool c); virtual bool OnViewMouse(LView *v, LMouse &m) override { return true; } virtual bool OnViewKey(LView *v, LKey &k) override { return false; } virtual void OnNcPaint(LSurface *pDC, LRect &r); /// List of children views. List Children; #if defined(LGI_SDL) || defined(LGI_COCOA) public: #endif virtual void _Paint(LSurface *pDC = NULL, LPoint *Offset = NULL, LRect *Update = NULL); public: /// \brief Creates a view/window. /// /// On non-Win32 platforms the default argument is the class that redirects the /// C++ virtual event handlers to the LView handlers. Which is usually the /// 'DefaultOsView' class. If you pass NULL in a DefaultOsView will be created to /// do the job. LView ( /// The handle that the OS knows the window by OsView wnd = NULL ); /// Destructor virtual ~LView(); /// Returns the OS handle of the view #if defined(HAIKU) OsView Handle() const; #elif LGI_VIEW_HANDLE OsView Handle() const { return _View; } #endif /// Returns the ptr to a LView LView *GetGView() override { return this; } /// Returns the OS handle of the top level window OsWindow WindowHandle() override; // Attaching windows / heirarchy bool AddView(LViewI *v, int Where = -1) override; bool DelView(LViewI *v) override; bool HasView(LViewI *v) override; LArray IterateViews() override; /// \brief Attaches the view to a parent view. /// /// Each LView starts in an un-attached state. When you attach it to a Parent LView /// the view gains a OS-specific handle and becomes visible on the screen (if the /// Visible() property is TRUE). However if a view is inserted into the Children list /// of a LView and it's parent pointer is set correctly it will still paint on the /// screen without the OS knowing about it. This is known in Lgi as a "virtual window" /// and is primarily used to cut down on windowing resources. Mouse clicks are handled /// by the parent window and passed down to the virtual children. Virtual children /// are somewhat limited. They can't receive focus, or participate in drag and drop /// operations. If you want to see an example have a look at the LToolBar code. virtual bool Attach ( /// The parent view or NULL for a top level window LViewI *p ) override; /// Attachs all the views in the Children list if not already attached. virtual bool AttachChildren() override; /// Detachs a window from it's parent. virtual bool Detach() override; /// Returns true if the window is attached virtual bool IsAttached() override; /// Destroys the window async virtual void Quit(bool DontDelete = false) override; // Properties /// Gets the top level window that this view belongs to LWindow *GetWindow() override; /// Gets the parent view. LViewI *GetParent() override; /// \brief Sets the parent view. /// /// This doesn't attach the window so that it will display. You should use LView::Attach for that. virtual void SetParent(LViewI *p) override; /// Sends a notification to the notify target or the parent chain void SendNotify(LNotification n = LNotifyValueChanged) override; /// Gets the window that receives event notifications LViewI *GetNotify() override; /// \brief Sets the view to receive event notifications. /// /// The notify window will receive events when this view changes. By /// default the parent view receives the events. virtual void SetNotify(LViewI *n) override; /// \brief Each top level window (LWindow) has a lock. By calling this function /// you lock the whole LWindow and all it's children. bool Lock ( /// The file name of the caller const char *file, /// The line number of the caller int line, /// The timeout in milli-seconds or -1 to block until locked. int TimeOut = -1 ) override; /// Unlocks the LWindow and that this view belongs to. void Unlock() override; /// Add this view to the event target sink dispatch hash table. /// This allows you to use PostThreadEvent with a handle. Which /// is safe even if the object is deleted (unlike the PostEvent /// member function). /// /// Calling this multiple times only adds the view once, but it /// returns the same handle each time. /// The view is automatically removed from the dispatch on /// deletion. /// /// \returns the handle for PostThreadEvent. int AddDispatch() override; /// Called to process every message received by this window. LMessage::Result OnEvent(LMessage *Msg) override; /// true if the view is enabled bool Enabled() override; /// Sets the enabled state void Enabled(bool e) override; /// true if the view is visible bool Visible() override; /// Hides/Shows the view void Visible ( /// True if you want to show the view, False to hide the view/ bool v ) override; /// true if the view has keyboard focus bool Focus() override; /// Sets the keyboard focus state on the view. void Focus(bool f) override; /// Get/Set the drop source LDragDropSource *DropSource(LDragDropSource *Set = NULL) override; /// Get/Set the drop target LDragDropTarget *DropTarget(LDragDropTarget *Set = NULL) override; /// Sets the drop target state of this view bool DropTarget(bool t) override; /// \brief Gives this view a 1 or 2 px sunken border. /// /// The size is set by the _BorderSize member variable. This border is /// not considered part of the client area. Mouse and drawing coordinates /// do not take it into account. bool Sunken() override; /// Sets a sunken border around the control void Sunken(bool i) override; /// true if the view has a flat border bool Flat() override; /// Sets the flat border state void Flat(bool i) override; /// \brief true if the view has a raised border /// /// The size is set by the _BorderSize member variable. This border is /// not considered part of the client area. Mouse and drawing coordinates /// do not take it into account. bool Raised() override; /// Sets the raised border state void Raised(bool i) override; /// Draws an OS themed border void DrawThemeBorder(LSurface *pDC, LRect &r); /// \brief true if the control is currently executing in the GUI thread /// /// Some OS functions are not thread safe, and can only be called in the GUI /// thread. In the Linux implementation the GUI thread can change from time /// to time. On Win32 it stays the same. In any case if this function returns /// true it's safe to do just about anything. bool InThread() override; /// \brief Asyncronously posts an event to be received by this view virtual bool PostEvent ( /// The command ID. /// \sa Should be M_USER or higher for custom events. int Cmd, /// The first 32-bits of data. Equivalent to wParam on Win32. LMessage::Param a = 0, /// The second 32-bits of data. Equivalent to lParam on Win32. - LMessage::Param b = 0 + LMessage::Param b = 0, + /// Optional timeout in milliseconds + int64_t TimeoutMs = -1 ) override; template bool PostEvent(int Cmd, T *Ptr) { return PostEvent(Cmd, (LMessage::Param)Ptr, 0); } /// \brief Sets the utf-8 text associated with this view /// /// Name and NameW are interchangable. Using them in any order will convert the /// text between utf-8 and wide to satify any requirement. Generally once the opposing /// version of the string is required both the utf-8 and wide copies of the string /// remain cached in RAM until the Name is changed. bool Name(const char *n) override; /// Returns the utf-8 text associated with this view const char *Name() override; /// Sets the wide char text associated with this view bool NameW(const char16 *n) override; /// \brief Returns the wide char text associated with this view /// /// On Win32 the wide characters are 16 bits, on unix systems they are 32-bit /// characters. const char16 *NameW() override; /// \brief Gets the font this control should draw with. /// /// The default font is the system font, owned by the LApp object. virtual LFont *GetFont() override; /// \brief Sets the font for this control /// /// The lifetime of the font passed in is the responsibility of the caller. /// The LView object assumes the pointer will be valid at all times. virtual void SetFont(LFont *Fnt, bool OwnIt = false) override; /// Returns the cursor that should be displayed for the given location /// \returns a cursor type. i.e. LCUR_Normal from LgiDefs.h LCursor GetCursor(int x, int y) override; /// \brief Get the position of the view relitive to it's parent. virtual LRect &GetPos() override { return Pos; } /// Get the client region of the window relitive to itself (ie always 0,0-x,y) virtual LRect &GetClient(bool InClientSpace = true) override; /// Set the position of the view in terms of it's parent virtual bool SetPos(LRect &p, bool Repaint = false) override; /// Gets the width of the view in pixels int X() override { return Pos.X(); } /// Gets the height of the view in pixels. int Y() override { return Pos.Y(); } /// Gets the minimum size of the view LPoint GetMinimumSize() override; /// \brief Set the minimum size of the view. /// /// Only works for top level windows. void SetMinimumSize(LPoint Size) override; /// Gets the style of the control class LCss *GetCss(bool Create = false) override; /// Resolve a CSS colour, e.g.: /// auto Back = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); LColour StyleColour(int CssPropType, LColour Default, int Depth = 5); /// Sets the style of the control (will take ownership of 'css') void SetCss(LCss *css) override; /// Sets the CSS foreground or background colour bool SetColour(LColour &c, bool Fore) override; /// The class' name. Should be overriden in child classes to return the /// right class name. Mostly used for debugging, but in the win32 port it /// is also the default WIN32 class name passed to RegisterClass() in /// LView::CreateClass(). /// /// \returns the Class' name for debugging const char *GetClass() override; /// The array of CSS class names. LString::Array *CssClasses() override; /// Any element level styles LString CssStyles(const char *Set = NULL) override; /// \brief Captures all mouse events to this view /// /// Once you have mouse capture all mouse events will be passed to this /// view. i.e. during a mouse click. bool Capture(bool c) override; /// true if this view is capturing mouse events. bool IsCapturing() override; /// \brief Gets the current mouse location /// \return true on success bool GetMouse ( /// The mouse location information returned LMouse &m, /// Get the location in screen coordinates bool ScreenCoords = false ) override; /// \brief Gets the ID associated with the view /// /// The ID of a view is designed to associate controls defined in resource /// files with a object at runtime via a C header file define. int GetId() override; /// Sets the view's ID. void SetId(int i) override; /// true if this control is a tab stop. bool GetTabStop() override; /// \brief Sets whether this control is a tab stop. /// /// A top stop is a control that receives focus if the user scrolls through the controls /// with the tab key. void SetTabStop(bool b) override; /// Gets the integer representation of the view's contents virtual int64 Value() override { return 0; } /// Sets the integer representation of the view's contents virtual void Value(int64 i) override {} #if LGI_VIEW_HANDLE /// Find a view by it's os handle virtual LViewI *FindControl(OsView hnd); #endif /// Returns the view by it's ID virtual LViewI *FindControl ( // The ID to look for int Id ) override; /// Gets the value of the control identified by the ID int64 GetCtrlValue(int Id) override; /// Sets the value of the control identified by the ID void SetCtrlValue(int Id, int64 i) override; /// Gets the name (text) of the control identified by the ID const char *GetCtrlName(int Id) override; /// Sets the name (text) of the control identified by the ID void SetCtrlName(int Id, const char *s) override; /// Gets the enabled state of the control identified by the ID bool GetCtrlEnabled(int Id) override; /// Sets the enabled state of the control identified by the ID void SetCtrlEnabled(int Id, bool Enabled) override; /// Gets the visible state of the control identified by the ID bool GetCtrlVisible(int Id) override; /// Sets the visible state of the control identified by the ID void SetCtrlVisible(int Id, bool Visible) override; /// Causes the given area of the view to be repainted to update the screen bool Invalidate ( /// The rectangle of the view to repaint, or NULL for the entire view LRect *r = NULL, /// true if you want to wait for the update to happen bool Repaint = false, /// false to update in client coordinates, true to update the non client region bool NonClient = false ) override; /// Causes the given area of the view to be repainted to update the screen bool Invalidate ( /// The region of the view to repaint LRegion *r, /// true if you want to wait for the update to happen bool Repaint = false, /// false to update in client coordinates, true to update the non client region bool NonClient = false ) override; /// true if the mouse event is over the view bool IsOver(LMouse &m) override; /// returns the sub window located at the point x,y LViewI *WindowFromPoint(int x, int y, int DebugDepth = 0) override; /// Sets a timer to call the OnPulse() event void SetPulse ( /// The milliseconds between calls to OnPulse() or -1 to disable int Ms = -1 ) override; /// Convert a point form view coordinates to screen coordinates bool PointToScreen(LPoint &p) override; /// Convert a point form screen coordinates to view coordinates bool PointToView(LPoint &p) override; /// Get the x,y offset from the virtual window to the first real view in the parent chain bool WindowVirtualOffset(LPoint *Offset) override; /// Get the size of the window borders LPoint &GetWindowBorderSize() override; /// Layout all the child views virtual bool Pour ( /// The available space to lay out the views into LRegion &r ) override { return false; } /// The mouse was clicked over this view void OnMouseClick ( /// The event parameters LMouse &m ) override; /// Mouse moves into the area over the control void OnMouseEnter ( /// The event parameters LMouse &m ) override; /// Mouse leaves the area over the control void OnMouseExit ( /// The event parameters LMouse &m ) override; /// The mouse moves over the control void OnMouseMove ( /// The event parameters LMouse &m ) override; /// The mouse wheel was scrolled. bool OnMouseWheel ( /// The amount scrolled double Lines ) override; /// A key was pressed while this view has focus bool OnKey(LKey &k) override; /// The view is attached void OnCreate() override; /// The view is detached void OnDestroy() override; /// The view gains or loses the keyboard focus void OnFocus ( /// True if the control is receiving focus bool f ) override; /// \brief Called every so often by the timer system. /// \sa SetPulse() void OnPulse() override; /// Called when the view position changes void OnPosChange() override; /// Called on a top level window when something requests to close the window /// \returns true if it's ok to continue shutting down. bool OnRequestClose ( /// True if the operating system is shutting down. bool OsShuttingDown ) override; /// Return the type of cursor that should be visible when the mouse is at x,y /// e.g. #LCUR_Normal int OnHitTest ( /// The x coordinate in view coordinates int x, /// The y coordinate in view coordinates int y ) override; /// Called when the contents of the Children list have changed. void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; /// Called to paint the on screen representation of the view void OnPaint(LSurface *pDC) override; /// \brief Called when a child view or view with it's SetNotify() set to this window changes. /// /// The event by default will bubble up to the LWindow at the top of the window hierarchy visiting /// each LView on the way. If it reaches a LView that processes it then the event stops propagating /// up the hierarchy. int OnNotify(LViewI *Ctrl, LNotification n) override; /// Called when a menu command is activated by the user. int OnCommand(int Cmd, int Event, OsView Wnd) override; /// Called after the view is attached to a new parent void OnAttach() override; /// Called to get layout information for the control. It's called /// up to 3 times to collect various dimensions: /// 1) PreLayout: Get the maximum width, and optionally the minimum width. /// Called with both widths set to zero. /// Must fill out Inf.Width.Max. Use -1 to fill all available space. /// Optionally fill out the min width. /// 2) Layout: Called to work out row height. /// Called with: /// - Width.Min unchanged from previous call. /// - Width.Max is limited to Cell size. /// Must fill out Inf.Height.Max. /// Min height currently not used. /// 3) PostLayout: Called to position view in cell. /// Not called. bool OnLayout(LViewLayoutInfo &Inf) override { return false; } #if defined(_DEBUG) bool _Debug; void Debug(); void _Dump(int Depth = 0); #endif }; LgiFunc LView *LViewFromHandle(OsView hWnd); /// \brief Factory for creating view's by name. /// /// Inherit from this to add a new factory to create objects. Override /// NewView() to create your control. class LgiClass LViewFactory { /** \brief Create a view by name \code if (strcmp(Class, "MyControl") == 0) { return new MyControl; } \endcode */ virtual LView *NewView ( /// The name of the class to create const char *Class, /// The initial position of the view LRect *Pos, /// The initial text of the view const char *Text ) = 0; public: LViewFactory(); virtual ~LViewFactory(); /// Create a view by name. static LView *Create(const char *Class, LRect *Pos = 0, const char *Text = 0); }; #define DeclFactory(CLS) \ class CLS ## Factory : public LViewFactory \ { \ LView *NewView(const char *Name, LRect *Pos, const char *Text) \ { \ if (!_stricmp(Name, #CLS)) return new CLS; \ return NULL; \ } \ } CLS ## FactoryInst; #define DeclFactoryParam1(CLS, Param1) \ class CLS ## Factory : public LViewFactory \ { \ LView *NewView(const char *Name, LRect *Pos, const char *Text) \ { \ if (!_stricmp(Name, #CLS)) return new CLS(Param1); \ return NULL; \ } \ } CLS ## FactoryInst; /// Control widget base class class LgiClass LControl : public LView { friend class LDialog; protected: #if defined BEOS bigtime_t Sys_LastClick; void MouseClickEvent(bool Down); #elif WINNATIVE bool *SetOnDelete; LWindowsClass *SubClass; #endif LPoint SizeOfStr(const char *Str); public: #if WINNATIVE LControl(char *SubClassName = 0); #else LControl(OsView view = NULL); #endif ~LControl(); LMessage::Result OnEvent(LMessage *Msg); }; 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,308 +1,315 @@ #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) + #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(); void SetParent(LViewI *p) override; #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/include/lgi/haiku/LgiOsClasses.h b/include/lgi/haiku/LgiOsClasses.h --- a/include/lgi/haiku/LgiOsClasses.h +++ b/include/lgi/haiku/LgiOsClasses.h @@ -1,79 +1,21 @@ #ifndef __OS_CLASS_H #define __OS_CLASS_H class LLocker { - BHandler *hnd; - bool locked = false; +protected: + BHandler *hnd = NULL; const char *file = NULL; int line = 0; + bool locked = false; public: - LLocker(BHandler *h, const char *File, int Line) - { - hnd = h; - file = File; - line = Line; - } - - ~LLocker() - { - Unlock(); - } - - bool Lock() - { - LAssert(!locked); - LAssert(hnd != NULL); + LLocker(BHandler *h, const char *File, int Line); + ~LLocker(); - while (!locked) - { - status_t result = hnd->LockLooperWithTimeout(1000 * 1000); - if (result == B_OK) - { - locked = true; - break; - } - else if (result == B_TIMED_OUT) - { - // Warn about failure to lock... - thread_id Thread = hnd->Looper()->LockingThread(); - printf("%s:%i - Warning: can't lock. Myself=%i\n", _FL, LGetCurrentThread(), Thread); - } - else if (result == B_BAD_VALUE) - { - break; - } - else - { - // Warn about error - printf("%s:%i - Error from LockLooperWithTimeout = 0x%x\n", _FL, result); - } - } - - return locked; - } - - status_t LockWithTimeout(int64 time) - { - LAssert(!locked); - LAssert(hnd != NULL); - - status_t result = hnd->LockLooperWithTimeout(time); - if (result == B_OK) - locked = true; - - return result; - } - - void Unlock() - { - if (locked) - { - hnd->UnlockLooper(); - locked = false; - } - } + bool Lock(); + status_t LockWithTimeout(int64 time); + void Unlock(); }; #endif diff --git a/include/lgi/haiku/LgiOsDefs.h b/include/lgi/haiku/LgiOsDefs.h --- a/include/lgi/haiku/LgiOsDefs.h +++ b/include/lgi/haiku/LgiOsDefs.h @@ -1,527 +1,518 @@ /** \file \author Matthew Allen \brief Haiku defs. + + Debugging memory issues in Haiku: + LD_PRELOAD=/boot/system/lib/libroot_debug.so MALLOC_DEBUG=g ./my_app */ #ifndef __LGI_OS_DEFS_H #define __LGI_OS_DEFS_H #include #include #include #include #include #include #include #include #include #include #include #include #include "lgi/common/LgiDefs.h" #include #define XP_CTRLS 1 #define POSIX 1 #define LGI_64BIT 1 #define LGI_VIEW_HANDLE 1 #define LGI_VIEW_HASH 1 #define LGI_HAIKU 1 #undef stricmp #include "lgi/common/LgiInc.h" class LgiClass OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; const char **Arg; OsAppArguments(int args, const char **arg); ~OsAppArguments(); void Set(char *CmdLine); bool Get(const char *Name, const char **Val = NULL); OsAppArguments &operator =(OsAppArguments &a); }; // Process typedef int OsProcess; typedef int OsProcessId; typedef BView *OsView; typedef BWindow *OsWindow; typedef char OsChar; typedef BView *OsPainter; typedef BFont *OsFont; typedef BBitmap *OsBitmap; #define LGetCurrentProcess getpid class OsApplication { class OsApplicationPriv *d; protected: static OsApplication *Inst; public: OsApplication(int Args, const char **Arg); ~OsApplication(); static OsApplication *GetInst() { LAssert(Inst != NULL); return Inst; } }; // Threads typedef pthread_t OsThread; typedef pid_t OsThreadId; typedef pthread_mutex_t OsSemaphore; -#define LGetCurrentThread() pthread_self() +#define LGetCurrentThread() find_thread(NULL) LgiFunc OsThreadId GetCurrentThreadId(); #include "lgi/common/Message.h" // Sockets #define ValidSocket(s) ((s)>=0) #ifndef WIN32 #define INVALID_SOCKET -1 #endif typedef int OsSocket; /// Sleep the current thread for i milliseconds. #ifdef WIN32 LgiFunc void LSleep(DWORD i); #else LgiFunc void LSleep(uint32_t i); #endif #ifndef WIN32 #define atoi64 atoll #else #define atoi64 _atoi64 #endif #define _snprintf snprintf #define _vsnprintf vsnprintf #define wcscpy_s(dst, len, src) wcsncpy(dst, src, len) /// Drag and drop format for a file #define LGI_FileDropFormat "text/uri-list" #define LGI_StreamDropFormat "application/x-file-stream" // FIXME #define LGI_IllegalFileNameChars "\t\r\n/\\:*?\"<>|" #ifdef WINDOWS #define LGI_WideCharset "ucs-2" #else #define LGI_WideCharset "utf-32" #endif #ifdef _MSC_VER #define _MSC_VER_VS2019 2000 // MSVC++ 16.0 (speculative) #define _MSC_VER_VS2017 1910 // MSVC++ 15.0 #define _MSC_VER_VS2015 1900 // MSVC++ 14.0 #define _MSC_VER_VS2013 1800 // MSVC++ 12.0 #define _MSC_VER_VS2012 1700 // MSVC++ 11.0 #define _MSC_VER_VS2010 1600 // MSVC++ 10.0 #define _MSC_VER_VS2008 1500 // MSVC++ 9.0 #define _MSC_VER_VS2005 1400 // MSVC++ 8.0 #define _MSC_VER_VS2003 1310 // MSVC++ 7.1 #define _MSC_VER_VC7 1300 // MSVC++ 7.0 #define _MSC_VER_VC6 1200 // MSVC++ 6.0 #define _MSC_VER_VC5 1100 // MSVC++ 5.0 #if _MSC_VER >= _MSC_VER_VS2015 #define _MSC_VER_STR "14" #elif _MSC_VER >= _MSC_VER_VS2013 #define _MSC_VER_STR "12" #elif _MSC_VER >= _MSC_VER_VS2012 #define _MSC_VER_STR "11" #elif _MSC_VER >= _MSC_VER_VS2010 #define _MSC_VER_STR "10" #else #define _MSC_VER_STR "9" #endif #define LPrintfInt64 "%I64i" #define LPrintfHex64 "%I64x" #if LGI_64BIT #define LPrintfSizeT "%I64u" #define LPrintfSSizeT "%I64d" #else #define LPrintfSizeT "%u" #define LPrintfSSizeT "%d" #endif #else #define LPrintfInt64 "%lld" #define LPrintfHex64 "%llx" #define LPrintfSizeT "%zu" #define LPrintfSSizeT "%zi" #endif #ifndef SND_ASYNC #define SND_ASYNC 0x0001 #endif #define DOUBLE_CLICK_THRESHOLD 5 #define DOUBLE_CLICK_TIME 400 // Window flags #define GWF_VISIBLE 0x00000001 #define GWF_DISABLED 0x00000002 #define GWF_FOCUS 0x00000004 #define GWF_OVER 0x00000008 #define GWF_DROP_TARGET 0x00000010 #define GWF_SUNKEN 0x00000020 #define GWF_FLAT 0x00000040 #define GWF_RAISED 0x00000080 #define GWF_BORDER 0x00000100 #define GWF_DIALOG 0x00000200 #define GWF_DESTRUCTOR 0x00000400 #define GWF_QUIT_WND 0x00000800 // Menu flags #ifndef WIN32 #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 #endif /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 #ifdef WIN32 /// The directory separator character on Linux as a char #define DIR_CHAR '\\' /// The directory separator character on Linux as a string #define DIR_STR "\\" /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ";" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*.*" /// The stardard extension for dynamically linked code #define LGI_LIBRARY_EXT "dll" /// The standard executable extension #define LGI_EXECUTABLE_EXT ".exe" #else /// The directory separator character on Linux as a char #define DIR_CHAR '/' /// The directory separator character on Linux as a string #define DIR_STR "/" /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ":" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*" /// The stardard extension for dynamically linked code #ifdef MAC #define LGI_LIBRARY_EXT "dylib" #else #define LGI_LIBRARY_EXT "so" #endif /// The standard executable extension #define LGI_EXECUTABLE_EXT "" #endif /// The standard end of line string for Linux #define EOL_SEQUENCE "\n" /// Tests a char for being a slash #define IsSlash(c) (((c)=='/')||((c)=='\\')) /// Tests a char for being a quote #define IsQuote(c) (((c)=='\"')||((c)=='\'')) #ifndef WIN32 /// ID's returned by LgiMsg. /// \sa LgiMsg enum MessageBoxResponse { IDOK = 1, IDCANCEL = 2, IDABORT = 3, IDRETRY = 4, IDIGNORE = 5, IDYES = 6, IDNO = 7, IDTRYAGAIN = 10, IDCONTINUE = 11, }; /// Standard message box types. /// \sa LgiMsg enum MessageBoxType { MB_OK = 0, MB_OKCANCEL = 1, MB_ABORTRETRYIGNORE = 2, MB_YESNOCANCEL = 3, MB_YESNO = 4, MB_RETRYCANCEL = 5, MB_CANCELTRYCONTINUE = 6 }; #define MB_SYSTEMMODAL 0x1000 #endif /// The CTRL key is pressed /// \sa LKey #define LGI_VKEY_CTRL 0x001 /// The ALT key is pressed /// \sa LKey #define LGI_VKEY_ALT 0x002 /// The SHIFT key is pressed /// \sa LKey #define LGI_VKEY_SHIFT 0x004 /// The left mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_LEFT 0x008 /// The middle mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_MIDDLE 0x010 /// The right mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_RIGHT 0x020 /// The ctrl key is pressed /// \sa LMouse #define LGI_VMOUSE_CTRL 0x040 /// The alt key is pressed /// \sa LMouse #define LGI_VMOUSE_ALT 0x080 /// The shift key is pressed /// \sa LMouse #define LGI_VMOUSE_SHIFT 0x100 /// The mouse event is a down click /// \sa LMouse #define LGI_VMOUSE_DOWN 0x200 /// The mouse event is a double click /// \sa LMouse #define LGI_VMOUSE_DOUBLE 0x400 -#if !defined(WINNATIVE) - - typedef enum { - /* The keyboard syms have been cleverly chosen to map to ASCII */ - LK_UNKNOWN = 0, - LK_FIRST = 0, - LK_BACKSPACE = 8, - LK_TAB = 9, - LK_RETURN = 13, - LK_ESCAPE = 27, - LK_SPACE = 32, - LK_EXCLAIM = 33, - LK_QUOTEDBL = 34, - LK_HASH = 35, - LK_DOLLAR = 36, - LK_AMPERSAND = 38, - LK_QUOTE = 39, - LK_LEFTPAREN = 40, - LK_RIGHTPAREN = 41, - LK_ASTERISK = 42, - LK_PLUS = 43, - LK_COMMA = 44, - LK_MINUS = 45, - LK_PERIOD = 46, - LK_SLASH = 47, - LK_0 = 48, - LK_1 = 49, - LK_2 = 50, - LK_3 = 51, - LK_4 = 52, - LK_5 = 53, - LK_6 = 54, - LK_7 = 55, - LK_8 = 56, - LK_9 = 57, - LK_COLON = 58, - LK_SEMICOLON = 59, - LK_LESS = 60, - LK_EQUALS = 61, - LK_GREATER = 62, - LK_QUESTION = 63, - LK_AT = 64, - /* - Skip uppercase letters - */ - LK_LEFTBRACKET = '[', - LK_BACKSLASH = '\\', - LK_RIGHTBRACKET = ']', - LK_CARET = '^', - LK_UNDERSCORE = '_', - LK_BACKQUOTE = 96, - LK_a = 'a', - LK_b = 'b', - LK_c = 'c', - LK_d = 'd', - LK_e = 'e', - LK_f = 'f', - LK_g = 'g', - LK_h = 'h', - LK_i = 'i', - LK_j = 'j', - LK_k = 'k', - LK_l = 'l', - LK_m = 'm', - LK_n = 'n', - LK_o = 'o', - LK_p = 'p', - LK_q = 'q', - LK_r = 'r', - LK_s = 's', - LK_t = 't', - LK_u = 'u', - LK_v = 'v', - LK_w = 'w', - LK_x = 'x', - LK_y = 'y', - LK_z = 'z', - /* End of ASCII mapped keysyms */ +enum LgiKey +{ + /* The keyboard syms have been cleverly chosen to map to ASCII */ + LK_UNKNOWN = 0, + LK_FIRST = 0, + LK_BACKSPACE = B_BACKSPACE, + LK_TAB = B_TAB, + LK_RETURN = B_RETURN, + LK_ESCAPE = B_ESCAPE, + LK_SPACE = B_SPACE, + LK_EXCLAIM = '!', + LK_QUOTEDBL = '\"', + LK_HASH = '#', + LK_DOLLAR = '$', + LK_AMPERSAND = '&', + LK_QUOTE = '\'', + LK_LEFTPAREN = '(', + LK_RIGHTPAREN = ')', + LK_ASTERISK = '*', + LK_PLUS = '+', + LK_COMMA = ',', + LK_MINUS = '-', + LK_PERIOD = '.', + LK_SLASH = '/', + LK_0 = '0', + LK_1 = '1', + LK_2 = '2', + LK_3 = '3', + LK_4 = '4', + LK_5 = '5', + LK_6 = '6', + LK_7 = '7', + LK_8 = '8', + LK_9 = '9', + LK_COLON = ':', + LK_SEMICOLON = ';', + LK_LESS = '<', + LK_EQUALS = '=', + LK_GREATER = '>', + LK_QUESTION = '?', + LK_AT = '@', + /* + Skip uppercase letters + */ + LK_LEFTBRACKET = '[', + LK_BACKSLASH = '\\', + LK_RIGHTBRACKET = ']', + LK_CARET = '^', + LK_UNDERSCORE = '_', + LK_BACKQUOTE = 96, + LK_a = 'a', + LK_b = 'b', + LK_c = 'c', + LK_d = 'd', + LK_e = 'e', + LK_f = 'f', + LK_g = 'g', + LK_h = 'h', + LK_i = 'i', + LK_j = 'j', + LK_k = 'k', + LK_l = 'l', + LK_m = 'm', + LK_n = 'n', + LK_o = 'o', + LK_p = 'p', + LK_q = 'q', + LK_r = 'r', + LK_s = 's', + LK_t = 't', + LK_u = 'u', + LK_v = 'v', + LK_w = 'w', + LK_x = 'x', + LK_y = 'y', + LK_z = 'z', + /* End of ASCII mapped keysyms */ - /* Numeric keypad */ - LK_KEYPADENTER , - LK_KP0 , - LK_KP1 , - LK_KP2 , - LK_KP3 , - LK_KP4 , - LK_KP5 , - LK_KP6 , - LK_KP7 , - LK_KP8 , - LK_KP9 , - LK_KP_PERIOD , - LK_KP_DELETE , - LK_KP_MULTIPLY , - LK_KP_PLUS , - LK_KP_MINUS , - LK_KP_DIVIDE , - LK_KP_EQUALS , + /* Arrows + Home/End pad */ + LK_HOME = B_HOME, + LK_LEFT = B_LEFT_ARROW, + LK_UP = B_UP_ARROW, + LK_RIGHT = B_RIGHT_ARROW, + LK_DOWN = B_DOWN_ARROW, + LK_PAGEUP = B_PAGE_UP, + LK_PAGEDOWN = B_PAGE_DOWN, + LK_END = B_END, + LK_INSERT = B_INSERT, + LK_DELETE = B_DELETE, - /* Arrows + Home/End pad */ - LK_HOME , - LK_LEFT , - LK_UP , - LK_RIGHT , - LK_DOWN , - LK_PAGEUP , - LK_PAGEDOWN , - LK_END , - LK_INSERT , + /* Numeric keypad */ + LK_KEYPADENTER , + LK_KP0 , + LK_KP1 , + LK_KP2 , + LK_KP3 , + LK_KP4 , + LK_KP5 , + LK_KP6 , + LK_KP7 , + LK_KP8 , + LK_KP9 , + LK_KP_PERIOD , + LK_KP_DELETE , + LK_KP_MULTIPLY , + LK_KP_PLUS , + LK_KP_MINUS , + LK_KP_DIVIDE , + LK_KP_EQUALS , - /* Function keys */ - LK_F1 , - LK_F2 , - LK_F3 , - LK_F4 , - LK_F5 , - LK_F6 , - LK_F7 , - LK_F8 , - LK_F9 , - LK_F10 , - LK_F11 , - LK_F12 , - LK_F13 , - LK_F14 , - LK_F15 , + /* Function keys */ + LK_F1, + LK_F2 , + LK_F3 , + LK_F4 , + LK_F5 , + LK_F6 , + LK_F7 , + LK_F8 , + LK_F9 , + LK_F10 , + LK_F11 , + LK_F12 , + LK_F13 , + LK_F14 , + LK_F15 , - /* Key state modifier keys */ - LK_NUMLOCK , - LK_CAPSLOCK , - LK_SCROLLOCK , - LK_LSHIFT , - LK_RSHIFT , - LK_LCTRL , - LK_RCTRL , - LK_LALT , - LK_RALT , - LK_LMETA , - LK_RMETA , - LK_LSUPER , /* "Windows" key */ - LK_RSUPER , + /* Key state modifier keys */ + LK_NUMLOCK , + LK_CAPSLOCK , + LK_SCROLLOCK , + LK_LSHIFT , + LK_RSHIFT , + LK_LCTRL , + LK_RCTRL , + LK_LALT , + LK_RALT , + LK_LMETA , + LK_RMETA , + LK_LSUPER , /* "Windows" key */ + LK_RSUPER , - /* Miscellaneous function keys */ - LK_HELP , - LK_PRINT , - LK_SYSREQ , - LK_BREAK , - LK_MENU , - LK_UNDO , - LK_REDO , - LK_EURO , /* Some european keyboards */ - LK_COMPOSE , /* Multi-key compose key */ - LK_MODE , /* "Alt Gr" key (could be wrong) */ - LK_DELETE , - LK_POWER , /* Power Macintosh power key */ - LK_CONTEXTKEY , + /* Miscellaneous function keys */ + LK_HELP , + LK_PRINT , + LK_SYSREQ , + LK_BREAK , + LK_MENU , + LK_UNDO , + LK_REDO , + LK_EURO , /* Some european keyboards */ + LK_COMPOSE , /* Multi-key compose key */ + LK_MODE , /* "Alt Gr" key (could be wrong) */ + LK_POWER , /* Power Macintosh power key */ + LK_CONTEXTKEY , - /* Add any other keys here */ - LK_LAST - } LgiKey; - -#else + /* Add any other keys here */ + LK_LAST - #define VK_BACKSPACE VK_BACK - #define VK_PAGEUP VK_PRIOR - #define VK_PAGEDOWN VK_NEXT - #define VK_RALT VK_MENU - #define VK_LALT VK_MENU - #define VK_RCTRL VK_CONTROL - #define VK_LCTRL VK_CONTROL - -#endif +}; ///////////////////////////////////////////////////////////////////////////////////// // Externs #define swprintf_s swprintf #ifndef _MSC_VER #define vsprintf_s vsnprintf #endif #ifndef WINNATIVE // __CYGWIN__ #ifdef _MSC_VER #else // LgiFunc char *strnistr(char *a, char *b, int n); #define _strnicmp strncasecmp // LgiFunc int _strnicmp(char *a, char *b, int i); LgiFunc char *strupr(char *a); LgiFunc char *strlwr(char *a); LgiFunc int stricmp(const char *a, const char *b); #define _stricmp strcasecmp // LgiFunc int _stricmp(const char *a, const char *b); #endif #define sprintf_s snprintf #if __BIG_ENDIAN__ #define htonll(x) (x) #define ntohll(x) (x) #else #define htonll(x) ( ((uint64)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32) ) #define ntohll(x) ( ((uint64)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32) ) #endif #else LgiFunc class LViewI *GWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt, HDC hDC = NULL); LgiFunc int WinHeightToPoint(int Ht, HDC hDC = NULL); #if _MSC_VER >= 1400 #define snprintf sprintf_s #endif /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(char *s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); #endif #endif diff --git a/private/common/FontPriv.h b/private/common/FontPriv.h --- a/private/common/FontPriv.h +++ b/private/common/FontPriv.h @@ -1,121 +1,98 @@ #ifndef _GFONT_PRIV_H_ #define _GFONT_PRIV_H_ #if defined USE_CORETEXT #include typedef ATSUTextMeasurement OsTextSize; #else typedef double OsTextSize; #endif class LTypeFacePrivate { public: // Type LString _Face; // type face LString _CodePage; LCss::Len _Size; // size int _Weight; int _Quality; bool _Italic; bool _Underline; // Output LColour _Fore; LColour _Back; LColour WhiteSpace; // Can be empty (if so it's calculated) int _TabSize; bool _Transparent; bool _SubGlyphs; // Backs bool IsSymbol; // Props double _Ascent, _Descent, _Leading; LTypeFacePrivate() { IsSymbol = false; _Ascent = _Descent = _Leading = 0.0; _Size = LCss::Len(LCss::LenPt, 8.0f); _Weight = FW_NORMAL; _Italic = false; _Underline = false; _CodePage = "utf-8"; _Fore.Rgb(0, 0, 0); _Back.Rgb(255, 255, 255); _TabSize = 32; // px _Transparent = false; _Quality = DEFAULT_QUALITY; _SubGlyphs = LFontSystem::Inst()->GetDefaultGlyphSub(); } }; class LFontPrivate { public: #ifdef WINDOWS static LAutoPtr Gdi32; #endif // Data - OsFont hFont; - int Height; - bool Dirty; - char *Cp; - LSurface *pSurface; - bool OwnerUnderline; - bool WarnOnDelete; + OsFont hFont = NULL; + int Height = 0; + bool Dirty = true; + char *Cp = NULL; + LSurface *pSurface = NULL; + bool OwnerUnderline = false; + bool WarnOnDelete = false; // Glyph substitution - uchar *GlyphMap; + uchar *GlyphMap = NULL; static class GlyphCache *Cache; - #ifdef BEOS - // Beos glyph sizes - uint16 CharX[128]; // Widths of ascii - LHashTbl,int> UnicodeX; // Widths of any other characters + #ifdef USE_CORETEXT + CFDictionaryRef Attributes = NULL; #endif - - #ifdef USE_CORETEXT - CFDictionaryRef Attributes; - #endif - #ifdef __GTK_H__ - Gtk::PangoContext *PangoCtx; + Gtk::PangoContext *PangoCtx = NULL; #endif LFontPrivate() { - hFont = 0; - pSurface = NULL; - #if defined WIN32 - OwnerUnderline = false; - #endif - #ifdef __GTK_H__ - PangoCtx = NULL; - #elif defined(MAC) - Attributes = NULL; - #endif - - GlyphMap = 0; - Dirty = true; - Height = 0; - Cp = 0; - WarnOnDelete = false; } ~LFontPrivate() { #ifdef __GTK_H__ if (PangoCtx) g_object_unref(PangoCtx); #elif defined(MAC) if (Attributes) CFRelease(Attributes); #endif } }; #endif diff --git a/private/common/ViewPriv.h b/private/common/ViewPriv.h --- a/private/common/ViewPriv.h +++ b/private/common/ViewPriv.h @@ -1,232 +1,249 @@ // Private LView definations #pragma once #if WINNATIVE #if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x501 #undef _WIN32_WINNT #define _WIN32_WINNT 0x501 #endif #include "commctrl.h" #include "Uxtheme.h" #define GViewFlags d->WndStyle #else #define GViewFlags WndFlags #endif #if defined(__GTK_H__) struct LCaptureThread : public LThread, public LCancel { int view = -1; LString name; public: constexpr static int EventMs = 150; LCaptureThread(LView *v); ~LCaptureThread(); int Main(); }; #elif defined(MAC) extern OsThread LgiThreadInPaint; #elif defined(HAIKU) #include - + extern void *IsAttached(BView *v); + #endif -#define PAINT_VIRTUAL_CHILDREN 1 +#define PAINT_VIRTUAL_CHILDREN 0 extern bool In_SetWindowPos; extern LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing = false, bool Debug = false); #ifdef __GTK_H__ extern LPoint GtkAbsPos(Gtk::GtkWidget *w); extern LRect GtkGetPos(Gtk::GtkWidget *w); #endif #if !WINNATIVE #include "lgi/common/ThreadEvent.h" class LPulseThread : public LThread, public LCancel { LView *View = NULL; + LString ViewClass; int Length = 0; LThreadEvent Event; + uint64_t WarnTs = 0; LString MakeName(LView *v, const char *Type) { LString s; s.Printf("LPulseThread.%s.%s", v->GetClass(), Type); return s; } public: - static int PulseThreadCount; - LPulseThread(LView *view, int len) : View(view), LThread(MakeName(view, "Thread")), Event(MakeName(view, "Event")) { LAssert(View); Length = len; - PulseThreadCount++; + ViewClass = View->GetClass(); // printf("PulseThread=%i, %s, %i\n", PulseThreadCount, View->GetClass(), Length); Run(); } ~LPulseThread() { + Cancel(); View = NULL; Cancel(); Event.Signal(); WaitForExit(); PulseThreadCount--; + LSleep(1); } int Main() { while (!IsCancelled() && LAppInst) { auto s = Event.Wait(Length); if (IsCancelled() || s == LThreadEvent::WaitError) break; if (View && !View->PostEvent(M_PULSE)) Cancel(); + auto r = View->PostEvent(M_PULSE, 0, 0, 50/*milliseconds*/); + #if 0 + if (!r) + { + auto now = LCurrentTime(); + if (now - WarnTs >= 5000) + { + WarnTs = now; + printf("%s:%i - PulseThread::PostEvent failed for %p/%s.\n", _FL, View, ViewClass.Get()); + } + } + #endif } return 0; } }; #endif enum LViewFontType { /// The LView has a pointer to an externally owned font. GV_FontPtr, /// The LView owns the font object, and must free it. GV_FontOwned, /// The LApp's font cache owns the object. In this case, /// calling GetCssStyle on the LView will invalidate the /// font ptr causing it to be re-calculated. GV_FontCached, }; class LViewPrivate { public: // General LView *View = NULL; // Owning view LDragDropSource *DropSource = NULL; LDragDropTarget *DropTarget = NULL; bool IsThemed = false; int CtrlId = -1; int WantsPulse = -1; // Hierarchy LViewI *ParentI = NULL; LView *Parent = NULL; LViewI *Notify = NULL; // Size LPoint MinimumSize; // Font LFont *Font = NULL; LViewFontType FontOwnType = GV_FontPtr; // Style LAutoPtr Css; bool CssDirty = false; // This is set when 'Styles' changes, the next call to GetCss(...) parses // the styles into the 'Css' object. LString Styles; // Somewhat temporary object to store unparsed styles particular to // this view until runtime, where the view heirarchy is known. LString::Array Classes; // Event dispatch handle int SinkHnd = -1; // OS Specific #if WINNATIVE static OsView hPrevCapture; int WndStyle = 0; // Windows hWnd Style int WndExStyle = 0; // Windows hWnd ExStyle int WndDlgCode = 0; // Windows Dialog Code (WM_GETDLGCODE) LString WndClass; UINT_PTR TimerId = 0; HTHEME hTheme = NULL; #else // Cursor LPulseThread *PulseThread = NULL; LView *Popup = NULL; bool TabStop = false; bool WantsFocus = false; #if defined __GTK_H__ bool InPaint = false; bool GotOnCreate = false; #elif defined(MAC) && !defined(LGI_COCOA) static HIObjectClassRef BaseClass; #endif #endif #if defined(__GTK_H__) static LCaptureThread *CaptureThread; LMouse PrevMouse; #elif defined(MAC) #ifdef LGI_COCOA LString ClassName; bool AttachEvent; #elif defined LGI_CARBON EventHandlerRef DndHandler; LAutoString AcceptedDropFormat; #endif #elif defined(LGI_SDL) SDL_TimerID PulseId; int PulseLength = -1; #elif defined(HAIKU) BView *Hnd = NULL; + LArray MsgQue; // For before the window is attached... #endif LViewPrivate(LView *view); ~LViewPrivate(); LView *GetParent() { if (Parent) return Parent; if (ParentI) return ParentI->GetGView(); return 0; } }; + 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,1157 +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; } } 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; + LFileSelect *s = new LFileSelect; + Args.GetReturn()->Empty(); if (Args.Length() > 0) - s.Parent(CastLView(*Args[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); + 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(Type, c); } } } - s.Type("All Files", LGI_ALL_FILES); + 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); + 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; - if (SaveAs ? s.Save() : s.Open()) + auto Process = [&Args](LFileSelect *s, bool ok) { - Args.GetReturn()->SetList(); - for (unsigned i=0; iValue.Lst->Insert(new LVariant(s[i])); + 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; + }; - return true; + if (SaveAs) + s->Save(Process); + else + s->Open(Process); + + return WaitForReturn(Args); } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { - LFileSelect s; + LFileSelect *s = new LFileSelect; if (Args.Length() > 0) - s.Parent(CastLView(*Args[0])); - s.InitialDir(Args.Length() > 1 ? Args[1]->CastString() : 0); + s->Parent(CastLView(*Args[0])); + s->InitialDir(Args.Length() > 1 ? Args[1]->CastString() : 0); + + Args.GetReturn()->Empty(); - if (s.OpenFolder()) - *Args.GetReturn() = s.Name(); - else - Args.GetReturn()->Empty(); + s->OpenFolder([&Args](LFileSelect *s, bool ok) + { + if (ok) + *Args.GetReturn() = s->Name(); + else + *Args.GetReturn() = false; + delete s; + }); - return true; + 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->IncRef(); 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->IncRef(); } 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(Parent, InitVal, Msg, Title, Pass); - if (Dlg.DoModal()) - *Args.GetReturn() = Dlg.GetStr(); - return true; + 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/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,561 +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 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 ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } 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; 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/Alpha.cpp b/src/common/Gdc2/Alpha.cpp --- a/src/common/Gdc2/Alpha.cpp +++ b/src/common/Gdc2/Alpha.cpp @@ -1,1820 +1,1822 @@ /** \file \author Matthew Allen \date 4/3/1998 \brief Draw mode effects */ #include #include #include #include #include "lgi/common/Gdc2.h" #include "lgi/common/Path.h" #include "lgi/common/PixelRops.h" #include "lgi/common/Palette.h" // #define Div255(a) DivLut[a] #define Div255(a) ((a)/255) template void CreatePaletteLut(T *c, LPalette *Pal, int Scale = 255) { if (Scale < 255) { uchar *DivLut = Div255Lut; for (int i=0; i<256; i++) { GdcRGB *p = (Pal) ? (*Pal)[i] : 0; if (p) { c[i].r = DivLut[p->r * Scale]; c[i].g = DivLut[p->g * Scale]; c[i].b = DivLut[p->b * Scale]; } else { c[i].r = DivLut[i * Scale]; c[i].g = c[i].r; c[i].b = c[i].r; } } } else if (Scale) { for (int i=0; i<256; i++) { GdcRGB *p = (Pal) ? (*Pal)[i] : 0; if (p) { c[i].r = p->r; c[i].g = p->g; c[i].b = p->b; } else { c[i].r = i; c[i].g = i; c[i].b = i; } } } else { memset(c, 0, sizeof(*c) * 256); } } /// Alpha blending applicators class LAlphaApp : public LApplicator { protected: uchar alpha, oma; int Bits, Bytes; uchar *Ptr; uchar *APtr; const char *GetClass() { return "LAlphaApp"; } public: LAlphaApp() { Op = GDC_ALPHA; alpha = 0xFF; oma = 0; Bits = 8; Bytes = 1; Ptr = 0; APtr = 0; } int GetVar(int Var) { switch (Var) { case GAPP_ALPHA_A: return alpha; } return 0; } int SetVar(int Var, NativeInt Value) { switch (Var) { case GAPP_ALPHA_A: { int Old = alpha; alpha = (uchar)Value; oma = 0xFF - alpha; + + printf("GAPP_ALPHA_A=%i\n", alpha); return Old; } case GAPP_ALPHA_PAL: { } } return 0; } bool SetSurface(LBmpMem *d, LPalette *p, LBmpMem *a) { if (d && d->GetBits() == Bits) { Dest = d; Pal = p; Ptr = d->Base; Alpha = a; APtr = Alpha ? Alpha->Base : 0; return true; } return false; } void SetPtr(int x, int y) { LAssert(Dest && Dest->Base); Ptr = Dest->Base + ((y * Dest->Line) + (x * Bytes)); if (APtr) APtr = Alpha->Base + ((y * Alpha->Line) + x); } void IncX() { Ptr += Bytes; if (APtr) APtr++; } void IncY() { Ptr += Dest->Line; if (APtr) APtr += Alpha->Line; } void IncPtr(int X, int Y) { Ptr += (Y * Dest->Line) + (X * Bytes); if (APtr) APtr += (Y * Dest->Line) + X; } COLOUR Get() { switch (Bytes) { case 1: return *((uint8_t*)Ptr); case 2: return *((uint16*)Ptr); case 3: return Rgb24(Ptr[2], Ptr[1], Ptr[0]); case 4: return *((uint32_t*)Ptr); } return 0; } }; class GdcApp8Alpha : public LAlphaApp { char Remap[256]; uchar *DivLut; public: GdcApp8Alpha(); int SetVar(int Var, NativeInt Value); void Set(); void VLine(int height); void Rectangle(int x, int y); bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha); template void AlphaBlt15(LBmpMem *Src, LPalette *DPal, uchar *Lut) { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, oma); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { System24BitPixel *dst = dc + *d; int r = dst->r + DivLut[G5bitTo8bit(s->r) * alpha]; int g = dst->g + DivLut[G5bitTo8bit(s->g) * alpha]; int b = dst->b + DivLut[G5bitTo8bit(s->b) * alpha]; *d++ = Lut[Rgb15(r, g, b)]; s++; } Ptr += Dest->Line; } } template void AlphaBlt16(LBmpMem *Src, LPalette *DPal, uchar *Lut) { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, oma); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { System24BitPixel *dst = dc + *d; int r = dst->r + DivLut[G5bitTo8bit(s->r) * alpha]; int g = dst->g + DivLut[G6bitTo8bit(s->g) * alpha]; int b = dst->b + DivLut[G5bitTo8bit(s->b) * alpha]; *d++ = Lut[Rgb15(r, g, b)]; s++; } Ptr += Dest->Line; } } template void AlphaBlt24(LBmpMem *Src, LPalette *DPal, uchar *Lut) { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, oma); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { System24BitPixel *dst = dc + *d; int r = dst->r + DivLut[s->r * alpha]; int g = dst->g + DivLut[s->g * alpha]; int b = dst->b + DivLut[s->b * alpha]; *d++ = Lut[Rgb15(r, g, b)]; s++; } Ptr += Dest->Line; } } template void AlphaBlt32(LBmpMem *Src, LPalette *DPal, uchar *Lut) { GdcRGB *dc = (*DPal)[0]; if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { GdcRGB dst = dc[*d]; OverNpm32toNpm24(s, &dst); *d++ = Lut[Rgb15(dst.r, dst.g, dst.b)]; s++; } Ptr += Dest->Line; } } template void AlphaBlt48(LBmpMem *Src, LPalette *DPal, uchar *Lut) { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, oma); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { System24BitPixel dst = dc[*d]; LRgba32 src = { (uint8_t)(s->r >> 8), (uint8_t)(s->g >> 8), (uint8_t)(s->b >> 8), alpha }; OverNpm32toNpm24(&src, &dst); *d++ = Lut[Rgb15(dst.r, dst.g, dst.b)]; s++; } Ptr += Dest->Line; } } template void AlphaBlt64(LBmpMem *Src, LPalette *DPal, uchar *Lut) { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, 0xff); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *d = Ptr; uchar *e = d + Src->x; while (d < e) { System24BitPixel dst = dc[*d]; OverNpm64toNpm24(s, &dst); *d++ = Lut[Rgb15(dst.r, dst.g, dst.b)]; s++; } Ptr += Dest->Line; } } }; template class GdcAlpha : public LApplicator { protected: union { uint8_t *u8; uint16 *u16; uint32_t *u32; Pixel *p; }; uint8_t alpha, one_minus_alpha; public: GdcAlpha() { p = NULL; alpha = 0xff; one_minus_alpha = 0; } const char *GetClass() { return "GdcAlpha"; } int GetVar(int Var) { switch (Var) { case GAPP_ALPHA_A: return alpha; } return 0; } int SetVar(int Var, NativeInt Value) { switch (Var) { case GAPP_ALPHA_A: { int Old = alpha; alpha = (uchar)Value; one_minus_alpha = 0xFF - alpha; return Old; } } return 0; } bool SetSurface(LBmpMem *d, LPalette *p = 0, LBmpMem *a = 0) { if (d && d->Cs == ColourSpace) { Dest = d; Pal = p; u8 = d->Base; Alpha = a; return true; } else LAssert(0); return false; } void SetPtr(int x, int y) { u8 = Dest->Base + (Dest->Line * y); p += x; } void IncX() { p++; } void IncY() { u8 += Dest->Line; } void IncPtr(int X, int Y) { p += X; u8 += Dest->Line * Y; } COLOUR Get() { return Rgb24(p->r, p->g, p->b); } }; template class GdcAlpha15 : public GdcAlpha { public: const char *GetClass() { return "GdcAlpha15"; } #define Div2040(c) ((c) / 2040) #define Setup15() \ REG uint8_t r = Rc15(this->c); \ REG uint8_t g = Gc15(this->c); \ REG uint8_t b = Bc15(this->c); \ REG uint8_t a = this->alpha; \ REG uint8_t oma = 255 - a; \ REG Pixel *d = this->p #define Comp15() \ d->r = Div2040((G5bitTo8bit(d->r) * oma) + (r * a)); \ d->g = Div2040((G5bitTo8bit(d->g) * oma) + (g * a)); \ d->b = Div2040((G5bitTo8bit(d->b) * oma) + (b * a)) void Set() { Setup15(); Comp15(); } void VLine(int height) { Setup15(); REG ssize_t line = this->Dest->Line; while (height--) { Comp15(); d = (Pixel*)(((uint8_t*)d) + line); } this->p = d; } void Rectangle(int x, int y) { Setup15(); REG ssize_t line = this->Dest->Line; while (y--) { d = this->p; REG Pixel *e = d + x; while (d < e) { Comp15(); d++; } this->u8 += line; } } bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha) { if (!Src) return false; if (Src->Cs == CsIndex8) { Pixel map[256]; if (SPal) { GdcRGB *p = (*SPal)[0]; for (int i=0; i<256; i++, p++) { map[i].r = G8bitTo5bit(p->r); map[i].g = G8bitTo5bit(p->g); map[i].b = G8bitTo5bit(p->b); } } else { for (int i=0; i<256; i++) { map[i].r = G8bitTo5bit(i); map[i].g = G8bitTo5bit(i); map[i].b = G8bitTo5bit(i); } } for (int y=0; yy; y++) { uchar *s = ((uchar*)Src->Base) + (y * Src->Line); Pixel *d = this->p; for (int x=0; xx; x++) { *d++ = map[*s++]; } this->u8 += this->Dest->Line; } return true; } else { LBmpMem Dst; Dst.Base = this->u8; Dst.x = Src->x; Dst.y = Src->y; Dst.Cs = this->Dest->Cs; Dst.Line = this->Dest->Line; return LRopUniversal(&Dst, Src, true); } } }; template class GdcAlpha16 : public GdcAlpha { public: const char *GetClass() { return "GdcAlpha16"; } #define Div2040(c) ((c) / 2040) #define Div1020(c) ((c) / 1020) #define Setup16() \ REG uint8_t r = Rc16(this->c); \ REG uint8_t g = Gc16(this->c); \ REG uint8_t b = Bc16(this->c); \ REG uint8_t a = this->alpha; \ REG uint8_t oma = 255 - a; \ REG Pixel *d = this->p #define Comp16() \ d->r = Div2040((G5bitTo8bit(d->r) * oma) + (r * a)); \ d->g = Div1020((G6bitTo8bit(d->g) * oma) + (g * a)); \ d->b = Div2040((G5bitTo8bit(d->b) * oma) + (b * a)) void Set() { Setup16(); Comp16(); } void VLine(int height) { Setup16(); REG ssize_t line = this->Dest->Line; while (height--) { Comp16(); d = (Pixel*)(((uint8_t*)d) + line); } this->p = d; } void Rectangle(int x, int y) { Setup16(); REG ssize_t line = this->Dest->Line; while (y--) { d = this->p; REG Pixel *e = d + x; while (d < e) { Comp16(); d++; } this->u8 += line; } } bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha) { if (!Src) return false; if (Src->Cs == CsIndex8) { Pixel map[256]; if (SPal) { GdcRGB *p = (*SPal)[0]; for (int i=0; i<256; i++, p++) { map[i].r = G8bitTo5bit(p->r); map[i].g = G8bitTo6bit(p->g); map[i].b = G8bitTo5bit(p->b); } } else { for (int i=0; i<256; i++) { map[i].r = G8bitTo5bit(i); map[i].g = G8bitTo6bit(i); map[i].b = G8bitTo5bit(i); } } for (int y=0; yy; y++) { uchar *s = ((uchar*)Src->Base) + (y * Src->Line); Pixel *d = this->p; for (int x=0; xx; x++) { *d++ = map[*s++]; } this->u8 += this->Dest->Line; } return true; } else { LBmpMem Dst; Dst.Base = this->u8; Dst.x = Src->x; Dst.y = Src->y; Dst.Cs = this->Dest->Cs; Dst.Line = this->Dest->Line; return LRopUniversal(&Dst, Src, true); } } }; template class GdcAlpha24 : public GdcAlpha { public: const char *GetClass() { return "GdcAlpha24"; } #define InitComposite24() \ uchar *DivLut = Div255Lut; \ REG uint8_t a = this->alpha; \ REG uint8_t oma = this->one_minus_alpha; \ REG int r = this->p24.r * a; \ REG int g = this->p24.g * a; \ REG int b = this->p24.b * a #define InitFlat24() \ Pixel px; \ px.r = this->p24.r; \ px.g = this->p24.g; \ px.b = this->p24.b #define Composite24(ptr) \ ptr->r = DivLut[(oma * ptr->r) + r]; \ ptr->g = DivLut[(oma * ptr->g) + g]; \ ptr->b = DivLut[(oma * ptr->b) + b] void Set() { InitComposite24(); Composite24(this->p); } void VLine(int height) { if (this->alpha == 255) { InitFlat24(); while (height-- > 0) { *this->p = px; this->u8 += this->Dest->Line; } } else if (this->alpha > 0) { InitComposite24(); while (height-- > 0) { Composite24(this->p); this->u8 += this->Dest->Line; } } } void Rectangle(int x, int y) { if (this->alpha == 0xff) { InitFlat24(); while (y-- > 0) { REG Pixel *s = this->p; REG Pixel *e = s + x; while (s < e) { *s++ = px; } this->u8 += this->Dest->Line; } } else if (this->alpha > 0) { InitComposite24(); while (y-- > 0) { REG Pixel *s = this->p; REG Pixel *e = s + x; while (s < e) { Composite24(s); s++; } this->u8 += this->Dest->Line; } } } template void CompositeBlt24(LBmpMem *Src) { uchar *Lut = Div255Lut; REG uint8_t a = this->alpha; REG uint8_t oma = this->one_minus_alpha; for (int y=0; yy; y++) { Pixel *dst = this->p; Pixel *dst_end = dst + Src->x; SrcPx *src = (SrcPx*)(Src->Base + (y * Src->Line)); if (a == 0xff) { while (dst < dst_end) { // No source alpha, just copy blt dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; src++; } } else if (a > 0) { while (dst < dst_end) { // No source alpha, but apply our local alpha dst->r = Lut[(dst->r * oma) + (src->r * a)]; dst->g = Lut[(dst->g * oma) + (src->g * a)]; dst->b = Lut[(dst->b * oma) + (src->b * a)]; dst++; src++; } } this->u8 += this->Dest->Line; } } template void CompositeBlt32(LBmpMem *Src) { uchar *Lut = Div255Lut; REG uint8_t a = this->alpha; if (a == 0xff) { // Apply the source alpha only for (int y=0; yy; y++) { Pixel *dst = this->p; Pixel *dst_end = dst + Src->x; SrcPx *src = (SrcPx*)(Src->Base + (y * Src->Line)); while (dst < dst_end) { REG uint8_t sa = src->a; REG uint8_t soma = 0xff - sa; dst->r = Lut[(dst->r * soma) + (src->r * sa)]; dst->g = Lut[(dst->g * soma) + (src->g * sa)]; dst->b = Lut[(dst->b * soma) + (src->b * sa)]; dst++; src++; } this->u8 += this->Dest->Line; } } else if (a > 0) { // Apply source alpha AND our local alpha for (int y=0; yy; y++) { Pixel *dst = this->p; Pixel *dst_end = dst + Src->x; SrcPx *src = (SrcPx*)(Src->Base + (y * Src->Line)); while (dst < dst_end) { REG uint8_t sa = Lut[a * src->a]; REG uint8_t soma = 0xff - sa; dst->r = Lut[(dst->r * soma) + (src->r * sa)]; dst->g = Lut[(dst->g * soma) + (src->g * sa)]; dst->b = Lut[(dst->b * soma) + (src->b * sa)]; dst++; src++; } this->u8 += this->Dest->Line; } } } bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha = 0) { if (!Src) return false; if (SrcAlpha) { LAssert(!"Impl me."); } else { switch (Src->Cs) { case CsIndex8: { Pixel map[256]; for (int i=0; i<256; i++) { GdcRGB *p = (SPal) ? (*SPal)[i] : NULL; if (p) { map[i].r = p->r; map[i].g = p->g; map[i].b = p->b; } else { map[i].r = i; map[i].g = i; map[i].b = i; } } for (int y=0; yy; y++) { uchar *s = ((uchar*)Src->Base) + (y * Src->Line); Pixel *d = this->p; for (int x=0; xx; x++) { *d++ = map[*s++]; } this->u8 += this->Dest->Line; } return true; } #define Blt24Case(name, size) \ case Cs##name: \ CompositeBlt##size(Src); \ break Blt24Case(Rgb24, 24); Blt24Case(Bgr24, 24); Blt24Case(Rgbx32, 24); Blt24Case(Bgrx32, 24); Blt24Case(Xrgb32, 24); Blt24Case(Xbgr32, 24); Blt24Case(Rgba32, 32); Blt24Case(Bgra32, 32); Blt24Case(Argb32, 32); Blt24Case(Abgr32, 32); #undef Blt24Case default: { LBmpMem Dst; Dst.Base = this->u8; Dst.x = Src->x; Dst.y = Src->y; Dst.Cs = this->Dest->Cs; Dst.Line = this->Dest->Line; return LRopUniversal(&Dst, Src, true); break; } } } return false; } }; template class GdcAlpha32 : public GdcAlpha { public: #define InitComposite32() \ uchar *DivLut = Div255Lut; \ REG int a = DivLut[this->alpha * this->p32.a]; \ REG int r = this->p32.r * a; \ REG int g = this->p32.g * a; \ REG int b = this->p32.b * a; \ REG uint8_t oma = 0xff - a #define InitFlat32() \ Pixel px; \ px.r = this->p32.r; \ px.g = this->p32.g; \ px.b = this->p32.b; \ px.a = this->p32.a #define Composite32(ptr) \ ptr->r = DivLut[(oma * ptr->r) + r]; \ ptr->g = DivLut[(oma * ptr->g) + g]; \ ptr->b = DivLut[(oma * ptr->b) + b]; \ ptr->a = (a + ptr->a) - DivLut[a * ptr->a] const char *GetClass() { return "GdcAlpha32"; } void Set() { InitComposite32(); Composite32(this->p); } void VLine(int height) { int sa = Div255Lut[this->alpha * this->p32.a]; if (sa == 0xff) { InitFlat32(); while (height-- > 0) { *this->p = px; this->u8 += this->Dest->Line; } } else if (sa > 0) { InitComposite32(); while (height-- > 0) { Composite32(this->p); this->u8 += this->Dest->Line; } } } void Rectangle(int x, int y) { int sa = Div255Lut[this->alpha * this->p32.a]; if (sa == 0xff) { // Fully opaque InitFlat32(); while (y--) { Pixel *d = this->p; Pixel *e = d + x; while (d < e) { *d++ = px; } this->u8 += this->Dest->Line; } } else if (sa > 0) { // Translucent InitComposite32(); while (y--) { Pixel *d = this->p; Pixel *e = d + x; while (d < e) { Composite32(d); d++; } this->u8 += this->Dest->Line; } } } template void PmBlt32(LBmpMem *Src) { REG uchar *DivLut = Div255Lut; for (int y=0; yy; y++) { REG Pixel *d = this->p, *e = d + Src->x; REG SrcPx *s = (SrcPx*)(Src->Base + (Src->Line * y)); while (d < e) { OverPm32toPm32(s, d); d++; s++; } this->u8 += this->Dest->Line; } } bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha = 0) { if (!Src) return 0; REG uchar *DivLut = Div255Lut; uchar lookup[256]; REG uint8_t a = this->alpha; REG uint8_t oma = this->one_minus_alpha; for (int i=0; i<256; i++) { lookup[i] = DivLut[i * this->alpha]; } if (SrcAlpha) { switch (Src->Cs) { case CsIndex8: case CsAlpha8: { System24BitPixel c[256]; CreatePaletteLut(c, SPal); for (int y=0; yy; y++) { uchar *s = (uchar*) (Src->Base + (y * Src->Line)); uchar *sa = (uchar*) (SrcAlpha->Base + (y * SrcAlpha->Line)); System24BitPixel *sc; Pixel *d = this->p; uchar a, o; for (int x=0; xx; x++) { a = lookup[*sa++]; if (a == 255) { sc = c + *s; d->r = sc->r; d->g = sc->g; d->b = sc->b; d->a = 255; } else if (a) { sc = c + *s; o = 0xff - a; d->r = DivLut[(d->r * o) + (sc->r * a)]; d->g = DivLut[(d->g * o) + (sc->g * a)]; d->b = DivLut[(d->b * o) + (sc->b * a)]; d->a = (a + d->a) - DivLut[a * d->a]; } s++; d++; } this->u8 += this->Dest->Line; } break; } case System15BitColourSpace: { for (int y=0; yy; y++) { ushort *s = (ushort*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); Pixel *d = this->p; for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); } else if (a) { uchar o = 255 - a; d->r = DivLut[(a * Rc15(*s)) + (o * d->r)]; d->g = DivLut[(a * Gc15(*s)) + (o * d->g)]; d->b = DivLut[(a * Bc15(*s)) + (o * d->b)]; } s++; d++; } this->u8 += this->Dest->Line; } break; } case System16BitColourSpace: { for (int y=0; yy; y++) { ushort *s = (ushort*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); Pixel *d = this->p; for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); } else if (a) { uchar o = 255 - a; d->r = DivLut[(a * Rc16(*s)) + (o * d->r)]; d->g = DivLut[(a * Gc16(*s)) + (o * d->g)]; d->b = DivLut[(a * Bc16(*s)) + (o * d->b)]; } s++; d++; } this->u8 += this->Dest->Line; } break; } case System24BitColourSpace: { for (int y=0; yy; y++) { uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); Pixel *d = this->p; System24BitPixel *s = (System24BitPixel*) (Src->Base + (y * Src->Line)); for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { d->r = s->r; d->g = s->g; d->b = s->b; } else if (a) { uchar o = 255 - a; d->r = DivLut[(a * s->r) + (o * d->r)]; d->g = DivLut[(a * s->g) + (o * d->g)]; d->b = DivLut[(a * s->b) + (o * d->b)]; } d++; s++; } this->u8 += this->Dest->Line; } break; } case System32BitColourSpace: { for (int y=0; yy; y++) { Pixel *d = this->p; Pixel *s = (Pixel*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = 255; } else if (a) { uchar o = 255 - a; d->r = DivLut[(a * s->r) + (o * d->r)]; d->g = DivLut[(a * s->g) + (o * d->g)]; d->b = DivLut[(a * s->b) + (o * d->b)]; d->a = (s->a + d->a) - DivLut[s->a * d->a]; } d++; s++; } this->u8 += this->Dest->Line; } break; } default: return false; } return true; } if (this->Dest->PreMul() || Src->PreMul()) { switch (Src->Cs) { case CsRgba32: PmBlt32(Src); return true; case CsBgra32: PmBlt32(Src); return true; case CsArgb32: PmBlt32(Src); return true; case CsAbgr32: PmBlt32(Src); return true; default: break; } } switch (Src->Cs) { default: { LBmpMem Dst; Dst.Base = this->u8; Dst.x = Src->x; Dst.y = Src->y; Dst.Cs = this->Dest->Cs; Dst.Line = this->Dest->Line; if (!LRopUniversal(&Dst, Src, true)) { return false; } break; } case CsIndex8: { System24BitPixel c[256]; if (SPal && SPal->GetSize() == 0) return false; CreatePaletteLut(c, SPal, this->alpha); for (int y=0; yy; y++) { uchar *s = (uchar*) (Src->Base + (y * Src->Line)); System24BitPixel *sc; Pixel *d = this->p; if (this->alpha == 255) { for (int x=0; xx; x++) { sc = c + *s++; d->r = sc->r; d->g = sc->g; d->b = sc->b; d->a = 255; d++; } } else if (this->alpha) { for (int x=0; xx; x++) { sc = c + *s++; d->r = sc->r + DivLut[d->r * oma]; d->g = sc->g + DivLut[d->g * oma]; d->b = sc->b + DivLut[d->b * oma]; d->a = (a + d->a) - DivLut[a * d->a]; d++; } } this->u8 += this->Dest->Line; } break; } } return true; } }; LApplicator *LAlphaFactory::Create(LColourSpace Cs, int Op) { if (Op != GDC_ALPHA) return NULL; switch (Cs) { #define Case(name, px) \ case Cs##name: \ return new GdcAlpha##px() Case(Rgb15, 15); Case(Bgr15, 15); Case(Rgb16, 16); Case(Bgr16, 16); Case(Rgb24, 24); Case(Bgr24, 24); Case(Rgbx32, 24); Case(Bgrx32, 24); Case(Xrgb32, 24); Case(Xbgr32, 24); Case(Rgba32, 32); Case(Bgra32, 32); Case(Argb32, 32); Case(Abgr32, 32); #undef Case case CsIndex8: return new GdcApp8Alpha; default: LgiTrace("%s:%i - Unknown colour space: 0x%x %s\n", _FL, Cs, LColourSpaceToString(Cs)); // LAssert(0); break; } return 0; } GdcApp8Alpha::GdcApp8Alpha() { Bits = 8; Bytes = 1; Op = GDC_ALPHA; ZeroObj(Remap); DivLut = Div255Lut; } int GdcApp8Alpha::SetVar(int Var, NativeInt Value) { int Status = LAlphaApp::SetVar(Var, Value); switch (Var) { case GAPP_ALPHA_PAL: { LPalette *Pal = (LPalette*)Value; if (Pal && alpha < 255) { GdcRGB *p = (*Pal)[0]; GdcRGB *Col = (*Pal)[c&0xFF]; for (int i=0; iGetSize(); i++) { COLOUR Rgb = Rgb24( Div255((oma * p[i].r) + (alpha * Col->r)), Div255((oma * p[i].g) + (alpha * Col->g)), Div255((oma * p[i].b) + (alpha * Col->b))); Remap[i] = Pal->MatchRgb(Rgb); } } else { for (int i=0; i<256; i++) { Remap[i] = c; } } break; } } return Status; } void GdcApp8Alpha::Set() { *Ptr = Remap[*Ptr]; if (APtr) { *APtr += DivLut[(255 - *APtr) * alpha]; } } void GdcApp8Alpha::VLine(int y) { while (y--) { *Ptr = Remap[*Ptr]; Ptr += Dest->Line; if (APtr) { *APtr += DivLut[(255 - *APtr) * alpha]; APtr += Alpha->Line; } } } void GdcApp8Alpha::Rectangle(int x, int y) { while (y--) { uchar *p = Ptr; uchar *e = Ptr + x; while (p < e) { *p = Remap[*p]; p++; } Ptr += Dest->Line; if (APtr) { uchar *a = APtr; e = a + x; while (a < e) { *a += DivLut[(255 - *a) * alpha]; a++; } APtr += Alpha->Line; } } } bool GdcApp8Alpha::Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha) { if (!Src) return false; if (!SPal) SPal = Pal; LPalette Grey; LPalette *DPal; if (Pal) { DPal = Pal; } else { Grey.CreateGreyScale(); DPal = &Grey; } uchar *DivLut = Div255Lut; uchar *Lut = 0; uchar lookup[256]; for (int i=0; i<256; i++) { lookup[i] = (i * (int)alpha) / 255; } if (SrcAlpha) { // Per pixel source alpha GdcRGB *SRgb = (*SPal)[0]; GdcRGB *DRgb = (*DPal)[0]; if (!SRgb || !DRgb) return false; switch (Src->Cs) { default: { LAssert(!"Not impl."); break; } case CsIndex8: { System24BitPixel sc[256]; CreatePaletteLut(sc, SPal); System24BitPixel dc[256]; CreatePaletteLut(dc, DPal); for (int y=0; yy; y++) { uchar *s = Src->Base + (y * Src->Line); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); uchar *d = Ptr; int r = 0, g = 0, b = 0; Lut = DPal->MakeLut(15); for (int x=0; xx; x++) { uchar a = lookup[*sa]; System24BitPixel *src = sc + *s; if (a == 255) { r = src->r; g = src->g; b = src->b; } else if (a) { uchar o = 0xff - a; System24BitPixel *dst = dc + *d; r = DivLut[(dst->r * o) + (src->r * a)]; g = DivLut[(dst->g * o) + (src->g * a)]; b = DivLut[(dst->b * o) + (src->b * a)]; } *d++ = Lut[Rgb15(r, g, b)]; sa++; s++; } Ptr += Dest->Line; } break; } case System15BitColourSpace: { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { ushort *s = (ushort*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); uchar *d = Ptr; uchar *end = d + Src->x; while (d < end) { uchar a = lookup[*sa++]; if (a == 255) { *d = Lut[*s]; } else if (a) { uchar o = 255 - a; System24BitPixel *dst = dc + *d; int r = DivLut[(dst->r * o) + (Rc15(*s) * a)]; int g = DivLut[(dst->g * o) + (Gc15(*s) * a)]; int b = DivLut[(dst->b * o) + (Bc15(*s) * a)]; *d = Lut[Rgb15(r, g, b)]; } d++; s++; } Ptr += Dest->Line; } break; } case System16BitColourSpace: { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { ushort *s = (ushort*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); uchar *d = Ptr; uchar *end = d + Src->x; while (d < end) { uchar a = lookup[*sa++]; if (a == 255) { *d = Lut[Rgb16To15(*s)]; } else if (a) { uchar o = 255 - a; System24BitPixel *dst = dc + *d; int r = DivLut[(dst->r * o) + (Rc16(*s) * a)]; int g = DivLut[(dst->g * o) + (Gc16(*s) * a)]; int b = DivLut[(dst->b * o) + (Bc16(*s) * a)]; *d = Lut[Rgb15(r, g, b)]; } d++; s++; } Ptr += Dest->Line; } break; } case CsBgr24: { LBgr24 dc[256]; CreatePaletteLut(dc, DPal, 255); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { LBgr24 *s = (LBgr24*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); uchar *d = Ptr; for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { *d = Lut[Rgb15(s->r, s->g, s->b)]; } else if (a) { uchar o = 255 - a; LBgr24 *dst = dc + *d; int r = DivLut[(dst->r * o) + (s->r * a)]; int g = DivLut[(dst->g * o) + (s->g * a)]; int b = DivLut[(dst->b * o) + (s->b * a)]; *d = Lut[Rgb15(r, g, b)]; } d++; s++; } Ptr += Dest->Line; } break; } case System32BitColourSpace: { System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, 255); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { System32BitPixel *s = (System32BitPixel*) (Src->Base + (y * Src->Line)); uchar *sa = SrcAlpha->Base + (y * SrcAlpha->Line); uchar *d = Ptr; for (int x=0; xx; x++) { uchar a = lookup[*sa++]; if (a == 255) { *d = Lut[Rgb15(s->r, s->g, s->b)]; } else if (a) { uchar o = 255 - a; System24BitPixel *dst = dc + *d; int r = DivLut[(dst->r * o) + (s->r * a)]; int g = DivLut[(dst->g * o) + (s->g * a)]; int b = DivLut[(dst->b * o) + (s->b * a)]; *d = Lut[Rgb15(r, g, b)]; } d++; s++; } Ptr += Dest->Line; } break; } } } else { // Global alpha level GdcRGB *SRgb = (*SPal)[0]; GdcRGB *DRgb = (*DPal)[0]; if (!SRgb || !DRgb) return false; switch (Src->Cs) { default: { LgiTrace("%s:%i - Not impl.\n", _FL); break; } case CsIndex8: { if (alpha == 255) { // do a straight blt for (int y=0; yy; y++) { uchar *s = Src->Base + (y * Src->Line); uchar *d = Ptr; memcpy(d, s, Src->x); Ptr += Dest->Line; } } else if (alpha) { System24BitPixel sc[256]; CreatePaletteLut(sc, SPal, alpha); System24BitPixel dc[256]; CreatePaletteLut(dc, DPal, oma); if (!Lut) Lut = DPal->MakeLut(15); for (int y=0; yy; y++) { uchar *s = Src->Base + (y * Src->Line); uchar *d = Ptr; for (int x=0; xx; x++, s++, d++) { System24BitPixel *src = sc + *s; System24BitPixel *dst = dc + *d; int r = src->r + dst->r; int g = src->g + dst->g; int b = src->b + dst->b; *d = Lut[Rgb15(r, g, b)]; } Ptr += Dest->Line; } } break; } #define Case(Px, Sz) \ case Cs##Px: \ AlphaBlt##Sz(Src, DPal, Lut); \ break Case(Rgb15, 15); Case(Bgr15, 15); Case(Rgb16, 16); Case(Bgr16, 16); Case(Rgb24, 24); Case(Bgr24, 24); Case(Rgbx32, 24); Case(Bgrx32, 24); Case(Xrgb32, 24); Case(Xbgr32, 24); Case(Rgba32, 32); Case(Bgra32, 32); Case(Argb32, 32); Case(Abgr32, 32); Case(Rgb48, 48); Case(Bgr48, 48); Case(Rgba64, 64); Case(Bgra64, 64); Case(Argb64, 64); Case(Abgr64, 64); #undef Case } } return false; } diff --git a/src/common/Gdc2/Colour.cpp b/src/common/Gdc2/Colour.cpp --- a/src/common/Gdc2/Colour.cpp +++ b/src/common/Gdc2/Colour.cpp @@ -1,930 +1,936 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Json.h" const LColour LColour::Black(0, 0, 0); const LColour LColour::White(255, 255, 255); const LColour LColour::Red(255, 0, 0); const LColour LColour::Green(0, 192, 0); const LColour LColour::Blue(0, 0, 255); LColour::LColour() { space = CsNone; flat = 0; pal = NULL; } LColour::LColour(const char *Str) { space = CsNone; flat = 0; pal = NULL; SetStr(Str); } LColour::LColour(uint8_t idx8, LPalette *palette) { pal = NULL; c8(idx8, palette); } LColour::LColour(int r, int g, int b, int a) { pal = NULL; space = System32BitColourSpace; rgb.r = limit(r, 0, 255); rgb.g = limit(g, 0, 255); rgb.b = limit(b, 0, 255); rgb.a = limit(a, 0, 255); } LColour::LColour(uint32_t c, int bits, LPalette *palette) { pal = NULL; Set(c, bits, palette); } #ifdef __GTK_H__ LColour::LColour(Gtk::GdkRGBA gtk) { pal = NULL; Rgb(gtk.red * 255.0, gtk.green * 255.0, gtk.blue * 255.0, gtk.alpha * 255.0); } #endif int LColour::HlsValue(double fN1, double fN2, double fHue) const { if (fHue > 360.0) fHue -= 360.0; else if (fHue < 0.0) fHue += 360.0; if (fHue < 60.0) return (int) ((fN1 + (fN2 - fN1) * fHue / 60.0) * 255.0 + 0.5); else if (fHue < 180.0) return (int) ((fN2 * 255.0) + 0.5); else if (fHue < 240.0) return (int) ((fN1 + (fN2 - fN1) * (240.0 - fHue) / 60.0) * 255.0 + 0.5); return (int) ((fN1 * 255.0) + 0.5); } LColourSpace LColour::GetColourSpace() { return space; } bool LColour::SetColourSpace(LColourSpace cs) { if (space == CsNone) { space = cs; rgb.a = 255; return true; } if (space == cs) return true; LAssert(!"Impl conversion."); return false; } bool LColour::IsValid() const { return space != CsNone; } void LColour::Empty() { space = CsNone; } bool LColour::IsTransparent() { if (space == System32BitColourSpace) return rgb.a == 0; else if (space == CsIndex8) return !pal || index >= pal->GetSize(); return space == CsNone; } void LColour::Rgb(int r, int g, int b, int a) { c32(Rgba32(r, g, b, a)); } void LColour::Set(LSystemColour c) { *this = LColour(c); } void LColour::Set(uint32_t c, int bits, LPalette *palette) { pal = 0; switch (bits) { case 8: { index = c; space = CsIndex8; pal = palette; break; } case 15: { space = System32BitColourSpace; rgb.r = Rc15(c); rgb.g = Gc15(c); rgb.b = Bc15(c); rgb.a = 255; break; } case 16: { space = System32BitColourSpace; rgb.r = Rc16(c); rgb.g = Gc16(c); rgb.b = Bc16(c); rgb.a = 255; break; } case 24: case 48: { space = System32BitColourSpace; rgb.r = R24(c); rgb.g = G24(c); rgb.b = B24(c); rgb.a = 255; break; } case 32: case 64: { space = System32BitColourSpace; rgb.r = R32(c); rgb.g = G32(c); rgb.b = B32(c); rgb.a = A32(c); break; } default: { space = System32BitColourSpace; flat = 0; LgiTrace("Error: Unable to set colour %x, %i\n", c, bits); LAssert(!"Not a known colour depth."); } } } uint32_t LColour::Get(int bits) { switch (bits) { case 8: if (space == CsIndex8) return index; LAssert(!"Not supported."); break; case 24: return c24(); case 32: return c32(); } return 0; } uint8_t LColour::r() const { return R32(c32()); } void LColour::r(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.r = i; else LAssert(0); } uint8_t LColour::g() const { return G32(c32()); } void LColour::g(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.g = i; else LAssert(0); } uint8_t LColour::b() const { return B32(c32()); } void LColour::b(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.b = i; else LAssert(0); } uint8_t LColour::a() const { return A32(c32()); } void LColour::a(uint8_t i) { if (SetColourSpace(System32BitColourSpace)) rgb.a = i; else LAssert(0); } uint8_t LColour::c8() const { return index; } void LColour::c8(uint8_t c, LPalette *p) { space = CsIndex8; pal = p; index = c; } uint32_t LColour::c24() const { if (space == System32BitColourSpace) { return Rgb24(rgb.r, rgb.g, rgb.b); } else if (space == CsIndex8) { if (pal) { // colour palette lookup if (index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return Rgb24(c->r, c->g, c->b); } } return 0; } return Rgb24(index, index, index); // monochome } else if (space == CsHls32) { return Rgb32To24(c32()); } // Black... return 0; } void LColour::c24(uint32_t c) { space = System32BitColourSpace; rgb.r = R24(c); rgb.g = G24(c); rgb.b = B24(c); rgb.a = 255; pal = NULL; } uint32_t LColour::c32() const { if (space == System32BitColourSpace) { return Rgba32(rgb.r, rgb.g, rgb.b, rgb.a); } else if (space == CsIndex8) { if (pal) { // colour palette lookup if (index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return Rgb32(c->r, c->g, c->b); } } return 0; } return Rgb32(index, index, index); // monochome } else if (space == CsHls32) { // Convert from HLS back to RGB LColour tmp = *this; tmp.ToRGB(); return tmp.c32(); } // Transparent? return 0; } void LColour::c32(uint32_t c) { space = System32BitColourSpace; pal = NULL; rgb.r = R32(c); rgb.g = G32(c); rgb.b = B32(c); rgb.a = A32(c); } LColour LColour::Invert() { LColour i(255-r(), 255-g(), 255-b()); return i; } LColour LColour::Mix(LColour Tint, float RatioOfTint) const { COLOUR c1 = c32(); COLOUR c2 = Tint.c32(); float RatioThis = 1.0f - RatioOfTint; int r = (int) ((RatioThis * R32(c1)) + (RatioOfTint * R32(c2)) + 0.5f); int g = (int) ((RatioThis * G32(c1)) + (RatioOfTint * G32(c2)) + 0.5f); int b = (int) ((RatioThis * B32(c1)) + (RatioOfTint * B32(c2)) + 0.5f); int a = (int) ((RatioThis * A32(c1)) + (RatioOfTint * A32(c2)) + 0.5f); return LColour(r, g, b, a); } uint32_t LColour::GetH() { ToHLS(); return hls.h; } bool LColour::HueIsUndefined() { ToHLS(); return hls.h == HUE_UNDEFINED; } uint32_t LColour::GetL() { ToHLS(); return hls.l; } uint32_t LColour::GetS() { ToHLS(); return hls.s; } bool LColour::ToHLS() { if (space == CsHls32) return true; uint32_t nMax, nMin, nDelta, c = c32(); int R = R32(c), G = G32(c), B = B32(c); double fHue; nMax = MAX(R, MAX(G, B)); nMin = MIN(R, MIN(G, B)); if (nMax == nMin) return false; hls.l = (nMax + nMin) / 2; if (hls.l < 128) hls.s = (uchar) ((255.0 * ((double)(nMax - nMin)) / (double)(nMax + nMin)) + 0.5); else hls.s = (uchar) ((255.0 * ((double)(nMax - nMin)) / (double)(511 - nMax - nMin)) + 0.5); nDelta = nMax - nMin; if (R == nMax) fHue = ((double) (G - B)) / (double) nDelta; else if (G == nMax) fHue = 2.0 + ((double) (B - R)) / (double) nDelta; else fHue = 4.0 + ((double) (R - G)) / (double) nDelta; fHue *= 60; if (fHue < 0.0) fHue += 360.0; hls.h = (uint16) (fHue + 0.5); space = CsHls32; pal = NULL; return true; } void LColour::SetHLS(uint16 h, uint8_t l, uint8_t s) { space = CsHls32; hls.h = h; hls.l = l; hls.s = s; } void LColour::ToRGB() { LHls32 Hls = hls; if (Hls.s == 0) { Rgb(0, 0, 0); } else { while (Hls.h >= 360) Hls.h -= 360; while (hls.h < 0) Hls.h += 360; double fHue = (double) Hls.h, fM1, fM2; double fLightness = ((double) Hls.l) / 255.0; double fSaturation = ((double) Hls.s) / 255.0; if (Hls.l < 128) fM2 = fLightness * (1 + fSaturation); else fM2 = fLightness + fSaturation - (fLightness * fSaturation); fM1 = 2.0 * fLightness - fM2; Rgb(HlsValue(fM1, fM2, fHue + 120.0), HlsValue(fM1, fM2, fHue), HlsValue(fM1, fM2, fHue - 120.0)); } } int LColour::GetGray(int BitDepth) const { if (BitDepth == 8) { int R = r() * 77; int G = g() * 151; int B = b() * 28; return (R + G + B) >> 8; } double Scale = 1 << BitDepth; int R = (int) ((double) r() * (0.3 * Scale) + 0.5); int G = (int) ((double) g() * (0.59 * Scale) + 0.5); int B = (int) ((double) b() * (0.11 * Scale) + 0.5); return (R + G + B) >> BitDepth; } uint32_t LColour::GetNative() { #ifdef WIN32 if (space == CsIndex8) { if (pal && index < pal->GetSize()) { GdcRGB *c = (*pal)[index]; if (c) { return RGB(c->r, c->g, c->b); } } return RGB(index, index, index); } else if (space == System32BitColourSpace) { return RGB(rgb.r, rgb.g, rgb.b); } else if (space == CsHls32) { LColour c(*this); c.ToRGB(); return RGB(c.r(), c.g(), c.b()); } else { LAssert(0); } #else LAssert(0); #endif return c32(); } char *LColour::GetStr() { static char Buf[4][32]; static int Idx = 0; int b = Idx++; if (Idx >= 4) Idx = 0; switch (space) { case System32BitColourSpace: if (rgb.a == 0xff) { sprintf_s( Buf[b], 32, "rgb(%i,%i,%i)", rgb.r, rgb.g, rgb.b); } else { sprintf_s( Buf[b], 32, "rgba(%i,%i,%i,%i)", rgb.r, rgb.g, rgb.b, rgb.a); } break; case CsIndex8: sprintf_s( Buf[b], 32, "index(%i)", index); break; case CsHls32: sprintf_s( Buf[b], 32, "hls(%i,%i,%i)", hls.h, hls.l, hls.s); break; default: sprintf_s( Buf[b], 32, "unknown(%i)", space); break; } return Buf[b]; } bool LColour::SetStr(const char *str) { if (!str) return false; LString Str = str; if (*str == '#') { // Web colour Str = Str.Strip("# \t\r\n"); if (Str.Length() == 3) { auto h = htoi(Str.Get()); uint8_t r = (h >> 8) & 0xf; uint8_t g = (h >> 4) & 0xf; uint8_t b = (h) & 0xf; Rgb(r | (r << 4), g | (g << 4), b | (b << 4)); } else if (Str.Length() == 6) { auto h = htoi(Str.Get()); uint8_t r = (h >> 16) & 0xff; uint8_t g = (h >> 8) & 0xff; uint8_t b = (h) & 0xff; Rgb(r, g, b); } else return false; return true; } char *s = strchr(Str, '('); if (!s) return false; char *e = strchr(s + 1, ')'); if (!s) return false; *s = 0; *e = 0; LString::Array Comp = LString(s+1).Split(","); if (!Stricmp(Str.Get(), "rgb")) { if (Comp.Length() == 3) Rgb((int)Comp[0].Int(), (int)Comp[1].Int(), (int)Comp[2].Int()); else if (Comp.Length() == 4) Rgb((int)Comp[0].Int(), (int)Comp[1].Int(), (int)Comp[2].Int(), (int)Comp[3].Int()); else return false; } else if (!Stricmp(Str.Get(), "hls")) { if (Comp.Length() == 3) SetHLS((uint16) Comp[0].Int(), (uint8_t) Comp[1].Int(), (uint8_t) Comp[2].Int()); else return false; } else if (!Stricmp(Str.Get(), "index")) { if (Comp.Length() == 1) { index = (uint8_t) Comp[0].Int(); space = CsIndex8; } else return false; } else return false; return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static LColour _LgiColours[L_MAXIMUM]; #define ReadColourConfig(def) LColour::GetConfigColour("Colour."#def, _LgiColours[def]) bool LColour::GetConfigColour(const char *Tag, LColour &c) { #ifdef LGI_STATIC return false; #else if (!Tag) return false; auto Col = LAppInst->GetConfig(Tag); if (!Col) return false; auto n = (int)Col.Strip("#").Int(16); c.Rgb( n>>16, n>>8, n ); return true; #endif } //////////////////////////////////////////////////////////////////////////// #ifdef __GTK_H__ COLOUR ColTo24(Gtk::GdkColor &c) { return Rgb24(c.red >> 8, c.green >> 8, c.blue >> 8); } #endif #if defined(WINDOWS) static LColour ConvertWinColour(uint32_t c) { return LColour(GetRValue(c), GetGValue(c), GetBValue(c)); } #endif void LColour::OnChange() { // Basic colours _LgiColours[L_BLACK].Rgb(0, 0, 0); // LC_BLACK _LgiColours[L_DKGREY].Rgb(0x40, 0x40, 0x40); // LC_DKGREY _LgiColours[L_MIDGREY].Rgb(0x80, 0x80, 0x80); // LC_MIDGREY _LgiColours[L_LTGREY].Rgb(0xc0, 0xc0, 0xc0); // LC_LTGREY _LgiColours[L_WHITE].Rgb(0xff, 0xff, 0xff); // LC_WHITE // Variable colours #if defined _XP_CTRLS - _LgiColours[L_SHADOW] = Rgb24(0x42, 0x27, 0x63); // LC_SHADOW - _LgiColours[L_LOW] = Rgb24(0x7a, 0x54, 0xa9); // LC_LOW - _LgiColours[L_MED] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MED - _LgiColours[L_HIGH] = Rgb24(0xdd, 0xd4, 0xe9); // LC_HIGH - _LgiColours[L_LIGHT] = Rgb24(0xff, 0xff, 0xff); // LC_LIGHT - _LgiColours[L_DIALOG] = Rgb24(0xbc, 0xa9, 0xd4); // LC_DIALOG - _LgiColours[L_WORKSPACE] = Rgb24(0xeb, 0xe6, 0xf2); // LC_WORKSPACE - _LgiColours[L_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_TEXT - _LgiColours[L_FOCUS_SEL_BACK] = Rgb24(0xbf, 0x67, 0x93); // LC_FOCUS_SEL_BACK - _LgiColours[L_FOCUS_SEL_FORE] = Rgb24(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE - _LgiColours[L_ACTIVE_TITLE] = Rgb24(0x70, 0x3a, 0xec); // LC_ACTIVE_TITLE - _LgiColours[L_ACTIVE_TITLE_TEXT] = Rgb24(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT - _LgiColours[L_INACTIVE_TITLE] = Rgb24(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE - _LgiColours[L_INACTIVE_TITLE_TEXT] = Rgb24(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT - _LgiColours[L_MENU_BACKGROUND] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MENU_BACKGROUND - _LgiColours[L_MENU_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_MENU_TEXT - _LgiColours[L_NON_FOCUS_SEL_BACK] = Rgb24(0xbc, 0xa9, 0xd4); // LC_NON_FOCUS_SEL_BACK - _LgiColours[L_NON_FOCUS_SEL_FORE] = Rgb24(0x35, 0x1f, 0x4f); // LC_NON_FOCUS_SEL_FORE - LAssert(i == LC_MAXIMUM); + _LgiColours[L_SHADOW] = Rgb24(0x42, 0x27, 0x63); // LC_SHADOW + _LgiColours[L_LOW] = Rgb24(0x7a, 0x54, 0xa9); // LC_LOW + _LgiColours[L_MED] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MED + _LgiColours[L_HIGH] = Rgb24(0xdd, 0xd4, 0xe9); // LC_HIGH + _LgiColours[L_LIGHT] = Rgb24(0xff, 0xff, 0xff); // LC_LIGHT + _LgiColours[L_DIALOG] = Rgb24(0xbc, 0xa9, 0xd4); // LC_DIALOG + _LgiColours[L_WORKSPACE] = Rgb24(0xeb, 0xe6, 0xf2); // LC_WORKSPACE + _LgiColours[L_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_TEXT + _LgiColours[L_FOCUS_SEL_BACK] = Rgb24(0xbf, 0x67, 0x93); // LC_FOCUS_SEL_BACK + _LgiColours[L_FOCUS_SEL_FORE] = Rgb24(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE + _LgiColours[L_ACTIVE_TITLE] = Rgb24(0x70, 0x3a, 0xec); // LC_ACTIVE_TITLE + _LgiColours[L_ACTIVE_TITLE_TEXT] = Rgb24(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT + _LgiColours[L_INACTIVE_TITLE] = Rgb24(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE + _LgiColours[L_INACTIVE_TITLE_TEXT] = Rgb24(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT + _LgiColours[L_MENU_BACKGROUND] = Rgb24(0xbc, 0xa9, 0xd4); // LC_MENU_BACKGROUND + _LgiColours[L_MENU_TEXT] = Rgb24(0x35, 0x1f, 0x4f); // LC_MENU_TEXT + _LgiColours[L_NON_FOCUS_SEL_BACK] = Rgb24(0xbc, 0xa9, 0xd4); // LC_NON_FOCUS_SEL_BACK + _LgiColours[L_NON_FOCUS_SEL_FORE] = Rgb24(0x35, 0x1f, 0x4f); // LC_NON_FOCUS_SEL_FORE + LAssert(i == LC_MAXIMUM); #elif defined __GTK_H__ - Gtk::GtkSettings *set = Gtk::gtk_settings_get_default(); - if (!set) - { - printf("%s:%i - gtk_settings_get_for_screen failed.\n", _FL); - return; - } - - char PropName[] = "gtk-color-scheme"; - Gtk::gchararray Value = 0; - Gtk::g_object_get(set, PropName, &Value, NULL); - LString::Array Lines = LString(Value).SplitDelimit("\n"); - Gtk::g_free(Value); - g_object_unref(set); + Gtk::GtkSettings *set = Gtk::gtk_settings_get_default(); + if (!set) + { + printf("%s:%i - gtk_settings_get_for_screen failed.\n", _FL); + return; + } + + char PropName[] = "gtk-color-scheme"; + Gtk::gchararray Value = 0; + Gtk::g_object_get(set, PropName, &Value, NULL); + LString::Array Lines = LString(Value).SplitDelimit("\n"); + Gtk::g_free(Value); + g_object_unref(set); - LHashTbl, int> Colours(0, -1); - auto ScreenBits = GdcD->GetBits(); - for (int i=0; i, int> Colours(0, -1); + auto ScreenBits = GdcD->GetBits(); + for (int i=0; i> 8) & 0xff) | - ((c >> 16) & 0xff00) | - ((c >> 24) & 0xff0000); - } + *col++ = 0; + + char *val = col; + if (*val == ' ') val++; + if (*val == '#') val++; + uint64 c = htoi64(val); + COLOUR c24 = c; + if (ScreenBits == 32) + { + c24 = ((c >> 8) & 0xff) | + ((c >> 16) & 0xff00) | + ((c >> 24) & 0xff0000); + } - Colours.Add(var, c24); - // printf("Color %s = %x\n", var, c24); + Colours.Add(var, c24); + // printf("Color %s = %x\n", var, c24); + } } - } - #define LookupColour(name, default) ((Colours.Find(name) >= 0) ? LColour(Colours.Find(name),24) : default) + #define LookupColour(name, default) ((Colours.Find(name) >= 0) ? LColour(Colours.Find(name),24) : default) - LColour Med = LookupColour("bg_color", LColour(0xe8, 0xe8, 0xe8)); - LColour White(255, 255, 255); - LColour Black(0, 0, 0); - LColour Sel(0x33, 0x99, 0xff); - _LgiColours[L_SHADOW] = GdcMixColour(Med, Black, 0.25); // LC_SHADOW - _LgiColours[L_LOW] = GdcMixColour(Med, Black, 0.5); // LC_LOW - _LgiColours[L_MED] = Med; // LC_MED - _LgiColours[L_HIGH] = GdcMixColour(Med, White, 0.5); // LC_HIGH - _LgiColours[L_LIGHT] = GdcMixColour(Med, White, 0.25); // LC_LIGHT - _LgiColours[L_DIALOG] = Med; // LC_DIALOG - _LgiColours[L_WORKSPACE] = LookupColour("base_color", White); // LC_WORKSPACE - _LgiColours[L_TEXT] = LookupColour("text_color", Black); // LC_TEXT - _LgiColours[L_FOCUS_SEL_BACK] = LookupColour("selected_bg_color", Sel); // LC_FOCUS_SEL_BACK - _LgiColours[L_FOCUS_SEL_FORE] = LookupColour("selected_fg_color", White); // LC_FOCUS_SEL_FORE - _LgiColours[L_ACTIVE_TITLE] = LookupColour("selected_bg_color", Sel); // LC_ACTIVE_TITLE - _LgiColours[L_ACTIVE_TITLE_TEXT] = LookupColour("selected_fg_color", White); // LC_ACTIVE_TITLE_TEXT - _LgiColours[L_INACTIVE_TITLE].Rgb(0xc0, 0xc0, 0xc0); // LC_INACTIVE_TITLE - _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE_TEXT - _LgiColours[L_MENU_BACKGROUND] = LookupColour("bg_color", White); // LC_MENU_BACKGROUND - _LgiColours[L_MENU_TEXT] = _LgiColours[L_TEXT]; // LC_MENU_TEXT - _LgiColours[L_NON_FOCUS_SEL_BACK] = _LgiColours[L_FOCUS_SEL_BACK].Mix(_LgiColours[L_WORKSPACE]); // LC_NON_FOCUS_SEL_BACK - _LgiColours[L_NON_FOCUS_SEL_FORE] = _LgiColours[L_TEXT]; // LC_NON_FOCUS_SEL_FORE - - // printf("_LgiColours[L_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_FOCUS_SEL_BACK].GetStr()); - // printf("_LgiColours[L_NON_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_NON_FOCUS_SEL_BACK].GetStr()); + LColour Med = LookupColour("bg_color", LColour(0xe8, 0xe8, 0xe8)); + LColour White(255, 255, 255); + LColour Black(0, 0, 0); + LColour Sel(0x33, 0x99, 0xff); + _LgiColours[L_SHADOW] = GdcMixColour(Med, Black, 0.25); // LC_SHADOW + _LgiColours[L_LOW] = GdcMixColour(Med, Black, 0.5); // LC_LOW + _LgiColours[L_MED] = Med; // LC_MED + _LgiColours[L_HIGH] = GdcMixColour(Med, White, 0.5); // LC_HIGH + _LgiColours[L_LIGHT] = GdcMixColour(Med, White, 0.25); // LC_LIGHT + _LgiColours[L_DIALOG] = Med; // LC_DIALOG + _LgiColours[L_WORKSPACE] = LookupColour("base_color", White); // LC_WORKSPACE + _LgiColours[L_TEXT] = LookupColour("text_color", Black); // LC_TEXT + _LgiColours[L_FOCUS_SEL_BACK] = LookupColour("selected_bg_color", Sel); // LC_FOCUS_SEL_BACK + _LgiColours[L_FOCUS_SEL_FORE] = LookupColour("selected_fg_color", White); // LC_FOCUS_SEL_FORE + _LgiColours[L_ACTIVE_TITLE] = LookupColour("selected_bg_color", Sel); // LC_ACTIVE_TITLE + _LgiColours[L_ACTIVE_TITLE_TEXT] = LookupColour("selected_fg_color", White); // LC_ACTIVE_TITLE_TEXT + _LgiColours[L_INACTIVE_TITLE].Rgb(0xc0, 0xc0, 0xc0); // LC_INACTIVE_TITLE + _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE_TEXT + _LgiColours[L_MENU_BACKGROUND] = LookupColour("bg_color", White); // LC_MENU_BACKGROUND + _LgiColours[L_MENU_TEXT] = _LgiColours[L_TEXT]; // LC_MENU_TEXT + _LgiColours[L_NON_FOCUS_SEL_BACK] = _LgiColours[L_FOCUS_SEL_BACK].Mix(_LgiColours[L_WORKSPACE]); // LC_NON_FOCUS_SEL_BACK + _LgiColours[L_NON_FOCUS_SEL_FORE] = _LgiColours[L_TEXT]; // LC_NON_FOCUS_SEL_FORE + + // printf("_LgiColours[L_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_FOCUS_SEL_BACK].GetStr()); + // printf("_LgiColours[L_NON_FOCUS_SEL_BACK]=%s\n", _LgiColours[L_NON_FOCUS_SEL_BACK].GetStr()); #elif defined(WINDOWS) - /* - for (int i=0; i<30; i++) - { - auto c = GetSysColor(i); - auto r = GetRValue(c); - auto g = GetGValue(c); - auto b = GetBValue(c); - LgiTrace("[%i]=%i,%i,%i %x,%x,%x\n", i, r, g, b, r, g, b); - } - */ + /* + for (int i=0; i<30; i++) + { + auto c = GetSysColor(i); + auto r = GetRValue(c); + auto g = GetGValue(c); + auto b = GetBValue(c); + LgiTrace("[%i]=%i,%i,%i %x,%x,%x\n", i, r, g, b, r, g, b); + } + */ - _LgiColours[L_SHADOW] = ConvertWinColour(GetSysColor(COLOR_3DDKSHADOW)); // LC_SHADOW - _LgiColours[L_LOW] = ConvertWinColour(GetSysColor(COLOR_3DSHADOW)); // LC_LOW - _LgiColours[L_MED] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_MED - _LgiColours[L_HIGH] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_HIGH - _LgiColours[L_LIGHT] = ConvertWinColour(GetSysColor(COLOR_3DHIGHLIGHT)); // LC_LIGHT - _LgiColours[L_DIALOG] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_DIALOG - _LgiColours[L_WORKSPACE] = ConvertWinColour(GetSysColor(COLOR_WINDOW)); // LC_WORKSPACE - _LgiColours[L_TEXT] = ConvertWinColour(GetSysColor(COLOR_WINDOWTEXT)); // LC_TEXT - _LgiColours[L_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHT)); // LC_FOCUS_SEL_BACK - _LgiColours[L_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHTTEXT)); // LC_FOCUS_SEL_FORE - _LgiColours[L_ACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_ACTIVECAPTION)); // LC_ACTIVE_TITLE - _LgiColours[L_ACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_CAPTIONTEXT)); // LC_ACTIVE_TITLE_TEXT - _LgiColours[L_INACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTION)); // LC_INACTIVE_TITLE - _LgiColours[L_INACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTIONTEXT)); // LC_INACTIVE_TITLE_TEXT - _LgiColours[L_MENU_BACKGROUND] = ConvertWinColour(GetSysColor(COLOR_MENU)); // LC_MENU_BACKGROUND - _LgiColours[L_MENU_TEXT] = ConvertWinColour(GetSysColor(COLOR_MENUTEXT)); // LC_MENU_TEXT - _LgiColours[L_NON_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_NON_FOCUS_SEL_BACK - _LgiColours[L_NON_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_BTNTEXT)); // LC_NON_FOCUS_SEL_FORE + _LgiColours[L_SHADOW] = ConvertWinColour(GetSysColor(COLOR_3DDKSHADOW)); // LC_SHADOW + _LgiColours[L_LOW] = ConvertWinColour(GetSysColor(COLOR_3DSHADOW)); // LC_LOW + _LgiColours[L_MED] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_MED + _LgiColours[L_HIGH] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_HIGH + _LgiColours[L_LIGHT] = ConvertWinColour(GetSysColor(COLOR_3DHIGHLIGHT)); // LC_LIGHT + _LgiColours[L_DIALOG] = ConvertWinColour(GetSysColor(COLOR_3DFACE)); // LC_DIALOG + _LgiColours[L_WORKSPACE] = ConvertWinColour(GetSysColor(COLOR_WINDOW)); // LC_WORKSPACE + _LgiColours[L_TEXT] = ConvertWinColour(GetSysColor(COLOR_WINDOWTEXT)); // LC_TEXT + _LgiColours[L_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHT)); // LC_FOCUS_SEL_BACK + _LgiColours[L_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_HIGHLIGHTTEXT)); // LC_FOCUS_SEL_FORE + _LgiColours[L_ACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_ACTIVECAPTION)); // LC_ACTIVE_TITLE + _LgiColours[L_ACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_CAPTIONTEXT)); // LC_ACTIVE_TITLE_TEXT + _LgiColours[L_INACTIVE_TITLE] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTION)); // LC_INACTIVE_TITLE + _LgiColours[L_INACTIVE_TITLE_TEXT] = ConvertWinColour(GetSysColor(COLOR_INACTIVECAPTIONTEXT)); // LC_INACTIVE_TITLE_TEXT + _LgiColours[L_MENU_BACKGROUND] = ConvertWinColour(GetSysColor(COLOR_MENU)); // LC_MENU_BACKGROUND + _LgiColours[L_MENU_TEXT] = ConvertWinColour(GetSysColor(COLOR_MENUTEXT)); // LC_MENU_TEXT + _LgiColours[L_NON_FOCUS_SEL_BACK] = ConvertWinColour(GetSysColor(COLOR_3DLIGHT)); // LC_NON_FOCUS_SEL_BACK + _LgiColours[L_NON_FOCUS_SEL_FORE] = ConvertWinColour(GetSysColor(COLOR_BTNTEXT)); // LC_NON_FOCUS_SEL_FORE #else // defaults for non-windows, plain grays - #if defined(LINUX) && !defined(LGI_SDL) - WmColour c; - Proc_LgiWmGetColour WmGetColour = 0; - LLibrary *WmLib = LAppInst->GetWindowManagerLib(); - if (WmLib) - { - WmGetColour = (Proc_LgiWmGetColour) WmLib->GetAddress("LgiWmGetColour"); - } + #if defined(LINUX) && !defined(LGI_SDL) + + WmColour c; + Proc_LgiWmGetColour WmGetColour = 0; + LLibrary *WmLib = LAppInst->GetWindowManagerLib(); + if (WmLib) + { + WmGetColour = (Proc_LgiWmGetColour) WmLib->GetAddress("LgiWmGetColour"); + } - #define SetCol(def) \ - if (WmGetColour && WmGetColour(i, &c)) \ - _LgiColours[i++] = Rgb24(c.r, c.g, c.b); \ - else \ - _LgiColours[i++] = def; + #define SetCol(def) \ + if (WmGetColour && WmGetColour(i, &c)) \ + _LgiColours[i++] = Rgb24(c.r, c.g, c.b); \ + else \ + _LgiColours[i++] = def; - #else // MAC + #else // MAC - #define SetCol(def) \ - _LgiColours[i++] = def; + #define SetCol(def) \ + _LgiColours[i++] = def; + + #endif - #endif + _LgiColours[L_SHADOW].Rgb(96, 96, 96); // LC_SHADOW + _LgiColours[L_LOW].Rgb(150, 150, 150); // LC_LOW + #ifdef HAIKU + _LgiColours[L_MED].Rgb(216, 216, 216); // LC_MED + #else + _LgiColours[L_MED].Rgb(230, 230, 230); // LC_MED + #endif + _LgiColours[L_HIGH].Rgb(240, 240, 240); // LC_HIGH + _LgiColours[L_LIGHT].Rgb(255, 255, 255); // LC_LIGHT + _LgiColours[L_DIALOG].Rgb(216, 216, 216); // LC_DIALOG + _LgiColours[L_WORKSPACE].Rgb(0xff, 0xff, 0xff); // LC_WORKSPACE + _LgiColours[L_TEXT].Rgb(0, 0, 0); // LC_TEXT + _LgiColours[L_FOCUS_SEL_BACK].Rgb(0x4a, 0x59, 0xa5); // LC_FOCUS_SEL_BACK + _LgiColours[L_FOCUS_SEL_FORE].Rgb(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE + _LgiColours[L_ACTIVE_TITLE].Rgb(0, 0, 0x80); // LC_ACTIVE_TITLE + _LgiColours[L_ACTIVE_TITLE_TEXT].Rgb(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT + _LgiColours[L_INACTIVE_TITLE].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE + _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT + _LgiColours[L_MENU_BACKGROUND].Rgb(222, 222, 222); // LC_MENU_BACKGROUND + _LgiColours[L_MENU_TEXT].Rgb(0, 0, 0); // LC_MENU_TEXT + _LgiColours[L_NON_FOCUS_SEL_BACK].Rgb(222, 222, 222); // LC_NON_FOCUS_SEL_BACK + _LgiColours[L_NON_FOCUS_SEL_FORE].Rgb(0, 0, 0); // LC_NON_FOCUS_SEL_FORE - _LgiColours[L_SHADOW].Rgb(96, 96, 96); // LC_SHADOW - _LgiColours[L_LOW].Rgb(150, 150, 150); // LC_LOW - _LgiColours[L_MED].Rgb(230, 230, 230); // LC_MED - _LgiColours[L_HIGH].Rgb(240, 240, 240); // LC_HIGH - _LgiColours[L_LIGHT].Rgb(255, 255, 255); // LC_LIGHT - _LgiColours[L_DIALOG].Rgb(216, 216, 216); // LC_DIALOG - _LgiColours[L_WORKSPACE].Rgb(0xff, 0xff, 0xff); // LC_WORKSPACE - _LgiColours[L_TEXT].Rgb(0, 0, 0); // LC_TEXT - _LgiColours[L_FOCUS_SEL_BACK].Rgb(0x4a, 0x59, 0xa5); // LC_FOCUS_SEL_BACK - _LgiColours[L_FOCUS_SEL_FORE].Rgb(0xff, 0xff, 0xff); // LC_FOCUS_SEL_FORE - _LgiColours[L_ACTIVE_TITLE].Rgb(0, 0, 0x80); // LC_ACTIVE_TITLE - _LgiColours[L_ACTIVE_TITLE_TEXT].Rgb(0xff, 0xff, 0xff); // LC_ACTIVE_TITLE_TEXT - _LgiColours[L_INACTIVE_TITLE].Rgb(0x80, 0x80, 0x80); // LC_INACTIVE_TITLE - _LgiColours[L_INACTIVE_TITLE_TEXT].Rgb(0x40, 0x40, 0x40); // LC_INACTIVE_TITLE_TEXT - _LgiColours[L_MENU_BACKGROUND].Rgb(222, 222, 222); // LC_MENU_BACKGROUND - _LgiColours[L_MENU_TEXT].Rgb(0, 0, 0); // LC_MENU_TEXT - _LgiColours[L_NON_FOCUS_SEL_BACK].Rgb(222, 222, 222); // LC_NON_FOCUS_SEL_BACK - _LgiColours[L_NON_FOCUS_SEL_FORE].Rgb(0, 0, 0); // LC_NON_FOCUS_SEL_FORE #endif // Tweak if (LGetOs() == LGI_OS_WIN32 || LGetOs() == LGI_OS_WIN64) { // Win32 doesn't seem to get this right, so we just tweak it here _LgiColours[L_LIGHT] = _LgiColours[L_MED].Mix(_LgiColours[L_LIGHT]); } _LgiColours[L_DEBUG_CURRENT_LINE].Rgb(0xff, 0xe0, 0x00); #ifdef MAC _LgiColours[L_TOOL_TIP].Rgb(0xef, 0xef, 0xef); #else _LgiColours[L_TOOL_TIP].Rgb(255, 255, 231); #endif } LColour::LColour(LSystemColour sc) { if (sc < L_MAXIMUM) *this = _LgiColours[sc]; } LColour LSysColour(LSystemColour Colour) { return Colour < L_MAXIMUM ? _LgiColours[Colour] : LColour(); } bool LColourLoad(const char *Json) { #ifdef LGI_STATIC // Not supported due to no CSS in static build return false; #else if (!Json) return false; LHashTbl,LSystemColour> Map; #define _(name) Map.Add("L_" #name, L_##name); _SystemColour(); #undef _ LFile f(Json, O_READ); if (!f.IsOpen()) return false; LJson j(f.Read()); for (auto i: j.GetArray("colors")) { auto p = i.Get(); auto c = Map.Find(p.key); if (c > L_TRANSPARENT && c < L_MAXIMUM) { LCss::ColorDef cd(p.value); if (cd.Type == LCss::ColorRgb) _LgiColours[c].Set(cd.Rgb32, 32); else LAssert(!"Invalid colour type."); } } return true; #endif } diff --git a/src/common/Gdc2/Filters/Gif.cpp b/src/common/Gdc2/Filters/Gif.cpp --- a/src/common/Gdc2/Filters/Gif.cpp +++ b/src/common/Gdc2/Filters/Gif.cpp @@ -1,1054 +1,1092 @@ /*hdr ** FILE: Gif.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Gif file filter ** ** Copyright (C) 1997-8, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Lzw.h" #include "lgi/common/Variant.h" #include "lgi/common/Palette.h" #ifdef FILTER_UI // define the symbol FILTER_UI to access the gif save options dialog #include "TransparentDlg.h" #endif #define MAX_CODES 4095 class GdcGif : public LFilter { LSurface *pDC; LStream *s; int ProcessedScanlines; + uint8_t BackgroundColour = 0; + bool Transparent = false; + // Old GIF coder stuff short linewidth; int lines; int pass; short curr_size; /* The current code size */ short clearc; /* Value for a clear code */ short ending; /* Value for a ending code */ short newcodes; /* First available code */ short top_slot; /* Highest code for current size */ short slot; /* Last read code */ short navail_bytes; /* # bytes left in block */ short nbits_left; /* # bits left in current byte */ uchar b1; /* Current byte */ uchar byte_buff[257]; /* Current block */ uchar *pbytes; /* Pointer to next byte in block */ uchar stack[MAX_CODES+1]; /* Stack for storing pixels */ uchar suffix[MAX_CODES+1]; /* Suffix table */ ushort prefix[MAX_CODES+1]; /* Prefix linked list */ int bad_code_count; int get_byte(); int out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth); short init_exp(short size); short get_next_code(); short decoder(int BitDepth, uchar interlaced); public: GdcGif(); Format GetFormat() { return FmtGif; } int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In); IoStatus WriteImage(LStream *Out, LSurface *pDC); bool GetVariant(const char *n, LVariant &v, const char *a) { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Gif"; } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "GIF"; } else return false; return true; } }; // Filter factory // tells the application we're here class GdcGifFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { if (Hint[0] == 'G' && Hint[1] == 'I' && Hint[2] == 'F' && Hint[3] == '8' && Hint[4] == '9') { return true; } } return (File) ? stristr(File, ".gif") != 0 : false; } LFilter *NewObject() { return new GdcGif; } } GifFactory; // gif error codes #define OUT_OF_MEMORY -10 #define BAD_CODE_SIZE -20 #define READ_ERROR -1 #define WRITE_ERROR -2 #define OPEN_ERROR -3 #define CREATE_ERROR -4 long code_mask[13] = { 0, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF}; int GdcGif::get_byte() { uchar c; if (s->Read(&c, 1) == 1) return c; return READ_ERROR; } int GdcGif::out_line(uchar *pixels, int linewidth, int interlaced, int BitDepth) { - // static int p; - // if (lines == 0) p = 0; - if (lines >= pDC->Y()) return -1; + auto pal = pDC->Palette(); + + /* + static bool first = true; + if (first) + { + first = false; + printf("cs=%s BackgroundColour=%i\n", LColourSpaceToString(pDC->GetColourSpace()), BackgroundColour); + for (int i=0; pal && i<3; i++) + { + auto *p = (*pal)[pixels[i]]; + printf("px=%i, %i,%i,%i\n", pixels[i], p->r, p->g, p->b); + } + } + */ + switch (pDC->GetColourSpace()) { case CsIndex8: case CsAlpha8: { memcpy((*pDC)[lines], pixels, pDC->X()); break; } case CsBgr16: { LBgr16 *s = (LBgr16*) (*pDC)[lines]; LBgr16 *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case CsRgb16: { LRgb16 *s = (LRgb16*) (*pDC)[lines]; LRgb16 *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; while (s < e) { pix = p + *pixels++; s->r = pix->r >> 3; s->g = pix->g >> 2; s->b = pix->b >> 3; s++; } break; } case System32BitColourSpace: { System32BitPixel *s = (System32BitPixel*) (*pDC)[lines]; System32BitPixel *e = s + pDC->X(); - LPalette *pal = pDC->Palette(); GdcRGB *p = (*pal)[0], *pix; - while (s < e) - { - pix = p + *pixels++; - s->r = pix->r; - s->g = pix->g; - s->b = pix->b; - s->a = 255; - s++; - } + if (Transparent) + { + while (s < e) + { + if (*pixels != BackgroundColour) + { + pix = p + *pixels; + s->r = pix->r; + s->g = pix->g; + s->b = pix->b; + s->a = 255; + } + else + { + s->r = 0; + s->g = 0; + s->b = 0; + s->a = 0; + } + + pixels++; + s++; + } + } + else // no transparent colour + { + while (s < e) + { + pix = p + *pixels++; + s->r = pix->r; + s->g = pix->g; + s->b = pix->b; + s->a = 255; + s++; + } + } break; } default: { LAssert(!"Unsupported colour space"); break; } } ProcessedScanlines++; if (interlaced) { switch (pass) { case 0: lines += 8; if (lines >= pDC->Y()) { lines = 4; pass++; } break; case 1: lines += 8; if (lines >= pDC->Y()) { lines = 2; pass++; } break; case 2: lines += 4; if (lines >= pDC->Y()) { lines = 1; pass++; } break; case 3: lines += 2; break; } } else { lines++; } if (Meter) { int a = (int)Meter->Value() * 100 / pDC->Y(); int b = lines * 100 / pDC->Y(); if (abs(a-b) > 5) { Meter->Value(lines); } } return 0; } short GdcGif::init_exp(short size) { curr_size = size + 1; top_slot = 1 << curr_size; clearc = 1 << size; ending = clearc + 1; slot = newcodes = ending + 1; navail_bytes = nbits_left = 0; return 0; } short GdcGif::get_next_code() { short i, x; ulong ret; if (nbits_left == 0) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; nbits_left = 8; --navail_bytes; } ret = b1 >> (8 - nbits_left); while (curr_size > nbits_left) { if (navail_bytes <= 0) { /* Out of bytes in current block, so read next block */ pbytes = byte_buff; if ((navail_bytes = get_byte()) < 0) { return(navail_bytes); } else if (navail_bytes) { for (i = 0; i < navail_bytes; ++i) { if ((x = get_byte()) < 0) { return(x); } byte_buff[i] = (uchar)x; } } } b1 = *pbytes++; ret |= b1 << nbits_left; nbits_left += 8; --navail_bytes; } nbits_left -= curr_size; ret &= code_mask[curr_size]; return ((short) ret); } short GdcGif::decoder(int BitDepth, uchar interlaced) { uchar *sp, *bufptr; uchar *buf; short code, fc, oc, bufcnt; short c, size, ret; lines = 0; pass = 0; /* Initialize for decoding a new image... */ if ((size = get_byte()) < 0) { return(size); } if (size < 2 || 9 < size) { return(BAD_CODE_SIZE); } init_exp(size); /* Initialize in case they forgot to put in a clearc code. * (This shouldn't happen, but we'll try and decode it anyway...) */ oc = fc = 0; /* Allocate space for the decode buffer */ buf = new uchar[linewidth+1]; if (buf == NULL) { return (OUT_OF_MEMORY); } /* Set up the stack pointer and decode buffer pointer */ uchar *EndOfStack = stack + sizeof(stack); sp = stack; bufptr = buf; bufcnt = linewidth; /* This is the main loop. For each code we get we pass through the * linked list of prefix codes, pushing the corresponding "character" for * each code onto the stack. When the list reaches a single "character" * we push that on the stack too, and then start unstacking each * character for output in the correct order. Special handling is * included for the clearc code, and the whole thing ends when we get * an ending code. */ while ((c = get_next_code()) != ending) { /* If we had a file error, return without completing the decode */ if (c < 0) { DeleteArray(buf); return(0); } /* If the code is a clearc code, reinitialize all necessary items. */ if (c == clearc) { curr_size = size + 1; slot = newcodes; top_slot = 1 << curr_size; /* Continue reading codes until we get a non-clearc code * (Another unlikely, but possible case...) */ while ((c = get_next_code()) == clearc) ; /* If we get an ending code immediately after a clearc code * (Yet another unlikely case), then break out of the loop. */ if (c == ending) { break; } /* Finally, if the code is beyond the range of already set codes, * (This one had better !happen... I have no idea what will * result from this, but I doubt it will look good...) then set it * to color zero. */ if (c >= slot) { c = 0; } oc = fc = c; /* And let us not forget to put the char into the buffer... And * if, on the off chance, we were exactly one pixel from the end * of the line, we have to send the buffer to the out_line() * routine... */ *bufptr++ = (uchar)c; if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return (ret); } bufptr = buf; bufcnt = linewidth; } } else { /* In this case, it's not a clearc code or an ending code, so * it must be a code code... So we can now decode the code into * a stack of character codes. (Clear as mud, right?) */ code = c; /* Here we go again with one of those off chances... If, on the * off chance, the code we got is beyond the range of those already * set up (Another thing which had better !happen...) we trick * the decoder into thinking it actually got the last code read. * (Hmmn... I'm not sure why this works... But it does...) */ if (code >= slot) { if (code > slot) { ++bad_code_count; } code = oc; *sp++ = (uchar)fc; } /* Here we scan back along the linked list of prefixes, pushing * helpless characters (ie. suffixes) onto the stack as we do so. */ while (code >= newcodes) { if (sp >= EndOfStack || code >= MAX_CODES + 1) { return -1; } *sp++ = suffix[code]; code = prefix[code]; } /* Push the last character on the stack, and set up the new * prefix and suffix, and if the required slot number is greater * than that allowed by the current bit size, increase the bit * size. (NOTE - If we are all full, we *don't* save the new * suffix and prefix... I'm not certain if this is correct... * it might be more proper to overwrite the last code... */ *sp++ = (uchar)code; if (slot < top_slot) { suffix[slot] = (uchar)(fc = code); prefix[slot++] = oc; oc = c; } if (slot >= top_slot) { if (curr_size < 12) { top_slot <<= 1; ++curr_size; } } /* Now that we've pushed the decoded string (in reverse order) * onto the stack, lets pop it off and put it into our decode * buffer... And when the decode buffer is full, write another * line... */ while (sp > stack) { *bufptr++ = *(--sp); if (--bufcnt == 0) { if ((ret = out_line(buf, linewidth, interlaced, BitDepth)) < 0) { DeleteArray(buf); return(ret); } bufptr = buf; bufcnt = linewidth; } } } } ret = 0; if (bufcnt != linewidth) { ret = out_line(buf, (linewidth - bufcnt), interlaced, BitDepth); } DeleteArray(buf); return(ret); } union LogicalScreenBits { uint8_t u8; struct { uint8_t TableSize : 3; uint8_t SortFlag : 1; uint8_t ColourRes : 3; uint8_t GlobalColorTable : 1; }; }; union LocalColourBits { uint8_t u8; struct { uint8_t TableBits : 3; uint8_t Reserved : 2; uint8_t SortFlag : 1; uint8_t Interlaced : 1; uint8_t LocalColorTable : 1; }; }; union GfxCtrlExtBits { uint8_t u8; struct { uint8_t Transparent : 1; uint8_t UserInput : 1; uint8_t DisposalMethod : 3; uint8_t Reserved : 3; }; }; bool GifLoadPalette(LStream *s, LSurface *pDC, int TableBits) { LRgb24 Rgb[256]; int Colours = 1 << (TableBits + 1); int Bytes = Colours * sizeof(Rgb[0]); memset(Rgb, 0xFF, sizeof(Rgb)); if (s->Read(Rgb, Bytes) != Bytes) return false; LPalette *Pal = new LPalette((uint8_t*)Rgb, 256); if (!Pal) return false; pDC->Palette(Pal); return true; } LFilter::IoStatus GdcGif::ReadImage(LSurface *pdc, LStream *in) { LFilter::IoStatus Status = IoError; pDC = pdc; s = in; ProcessedScanlines = 0; if (pDC && s) { bad_code_count = 0; if (!FindHeader(0, "GIF8?a", s)) { // not a gif file } else { - bool Transparent = false; + Transparent = false; LogicalScreenBits LogBits; uchar interlace = false; uint16 LogicalX = 0; uint16 LogicalY = 0; - uint8_t BackgroundColour = 0; + BackgroundColour = 0; uint8_t PixelAspectRatio = 0; // read header Read(s, &LogicalX, sizeof(LogicalX)); Read(s, &LogicalY, sizeof(LogicalY)); Read(s, &LogBits.u8, sizeof(LogBits.u8)); int Bits = LogBits.ColourRes + 1; Read(s, &BackgroundColour, sizeof(BackgroundColour)); Read(s, &PixelAspectRatio, sizeof(PixelAspectRatio)); if (LogBits.GlobalColorTable) { GifLoadPalette(s, pDC, LogBits.TableSize); } // Start reading the block stream bool Done = false; uchar BlockCode = 0; uchar BlockLabel = 0; uchar BlockSize = 0; while (!Done) { #define Rd(Var) \ if (!Read(s, &Var, sizeof(Var))) \ { \ Done = true; \ LgiTrace("%s:%i - Failed to read %i (" LPrintfInt64 " of " LPrintfInt64 ")\n", \ _FL, (int)sizeof(Var), in->GetPos(), in->GetSize()); \ break; \ } Rd(BlockCode); switch (BlockCode) { case 0x2C: { // Image Descriptor uint16 x1, y1, sx, sy; LocalColourBits LocalBits; Rd(x1); Rd(y1); Rd(sx); Rd(sy); Rd(LocalBits.u8); linewidth = sx; interlace = LocalBits.Interlaced != 0; if (pDC->Create(sx, sy, CsIndex8)) { if (LocalBits.LocalColorTable) { GifLoadPalette(s, pDC, LocalBits.TableBits); } // Progress if (Meter) { Meter->SetDescription("scanlines"); Meter->SetRange(sy); } // Decode image decoder(Bits, interlace); if (ProcessedScanlines == pDC->Y()) Status = IoSuccess; - if (Transparent) + if (Transparent && !LColourSpaceHasAlpha(pDC->GetColourSpace())) { // Setup alpha channel pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { for (int y=0; yY(); y++) { uchar *C = (*pDC)[y]; uchar *A = (*Alpha)[y]; for (int x=0; xX(); x++) - { A[x] = C[x] == BackgroundColour ? 0x00 : 0xff; - } } } } } else { LgiTrace("%s:%i - Failed to create output surface.\n", _FL); } Done = true; break; } case 0x21: { uint8_t GraphicControlLabel; uint8_t BlockSize; GfxCtrlExtBits ExtBits; uint16 Delay; Rd(GraphicControlLabel); Rd(BlockSize); switch (GraphicControlLabel) { case 0xF9: { Rd(ExtBits.u8); Rd(Delay); Rd(BackgroundColour); Transparent = ExtBits.Transparent != 0; break; } default: { s->SetPos(s->GetPos() + BlockSize); break; } } Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } default: { // unknown block Rd(BlockLabel); Rd(BlockSize); while (BlockSize) { int64 NewPos = s->GetPos() + BlockSize; if (s->SetPos(NewPos) != NewPos || !Read(s, &BlockSize, sizeof(BlockSize))) break; } break; } } } } } return Status; } LFilter::IoStatus GdcGif::WriteImage(LStream *Out, LSurface *pDC) { LVariant Transparent; int Back = -1; LVariant v; if (!Out || !pDC) return LFilter::IoError; if (pDC->GetBits() > 8) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "The GIF format only supports 1 to 8 bit graphics."); return LFilter::IoUnsupportedFormat; } #ifdef FILTER_UI LVariant Parent; if (Props) { Props->GetValue(LGI_FILTER_PARENT_WND, Parent); if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } if (Parent.Type == GV_GVIEW) { // If the source document has an alpha channel then we use // that to create transparent pixels in the output, otherwise // we ask the user if they want the background transparent... if (pDC->AlphaDC()) { Transparent = true; // However we have to pick an unused colour to set as the // "background" pixel value bool Used[256]; ZeroObj(Used); for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; uint8_t *a = (*pDC->AlphaDC())[y]; LAssert(p && a); if (!p || !a) break; uint8_t *e = p + pDC->X(); while (p < e) { if (*a) Used[*p] = true; a++; p++; } } Back = -1; for (int i=0; i<256; i++) { if (!Used[i]) { Back = i; break; } } if (Back < 0) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "No unused colour for transparent pixels??"); return IoError; } } else { // put up a dialog to ask about transparent colour LTransparentDlg Dlg((LView*)Parent.Value.Ptr, &Transparent); if (!Dlg.DoModal()) { Props->SetValue("Cancel", v = 1); return IoCancel; } } if (Transparent.CastInt32() && Back < 0) { LAssert(!"No background colour available??"); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Transparency requested, but no background colour set."); return IoError; } } } #endif LPalette *Pal = pDC->Palette(); // Intel byte ordering Out->SetSize(0); // Header Out->Write((void*)"GIF89a", 6); // Logical screen descriptor int16 s = pDC->X(); Write(Out, &s, sizeof(s)); s = pDC->Y(); Write(Out, &s, sizeof(s)); bool Ordered = false; uint8_t c = ((Pal != 0) ? 0x80 : 0) | // global colour table/transparent (pDC->GetBits() - 1) | // bits per pixel ((Ordered) ? 0x08 : 0) | // colours are sorted (pDC->GetBits() - 1); Out->Write(&c, 1); c = 0; Out->Write(&c, 1); // background colour c = 0; Out->Write(&c, 1); // aspect ratio // global colour table if (Pal) { uchar Buf[768]; uchar *d = Buf; int Colours = 1 << pDC->GetBits(); for (int i=0; ir; *d++ = s->g; *d++ = s->b; } else { *d++ = i; *d++ = i; *d++ = i; } } Out->Write(Buf, Colours * 3); } if (Transparent.CastInt32()) { // Graphic Control Extension uchar gce[] = {0x21, 0xF9, 4, 1, 0, 0, (uchar)Back, 0 }; Out->Write(gce, sizeof(gce)); } // Image descriptor c = 0x2c; Out->Write(&c, 1); // Image Separator s = 0; Write(Out, &s, sizeof(s)); // Image left position s = 0; Write(Out, &s, sizeof(s)); // Image top position s = pDC->X(); Write(Out, &s, sizeof(s)); // Image width s = pDC->Y(); Write(Out, &s, sizeof(s)); // Image height c = 0; Out->Write(&c, 1); // Flags // Image data c = 8; Out->Write(&c, 1); // Min code size LMemQueue Encode, Pixels; // Get input ready int Len = (pDC->X() * pDC->GetBits() + 7) / 8; uint8_t *buf = pDC->AlphaDC() ? new uint8_t[Len] : 0; for (int y=0; yY(); y++) { uint8_t *p = (*pDC)[y]; if (!p) continue; if (pDC->AlphaDC()) { // Preprocess pixels to make the alpha channel into the // transparent colour. uint8_t *a = (*pDC->AlphaDC())[y]; uint8_t *e = p + pDC->X(); uint8_t *o = buf; while (p < e) { if (*a++) *o++ = *p; else *o++ = Back; p++; } LAssert(o == buf + Len); p = buf; } Pixels.Write(p, Len); } DeleteArray(buf); // Compress Lzw Encoder; Encoder.Meter = Meter; if (Encoder.Compress(&Encode, &Pixels)) { uchar Buf[256]; // write data out while ((Len = (int)Encode.GetSize()) > 0) { int l = MIN(Len, 255); if (Encode.Read(Buf, l)) { c = l; Out->Write(&c, 1); // Sub block size Out->Write(Buf, l); } } c = 0; Out->Write(&c, 1); // Terminator sub block } // Trailer c = 0x3b; Out->Write(&c, 1); return LFilter::IoSuccess; } GdcGif::GdcGif() { ProcessedScanlines = 0; } diff --git a/src/common/Gdc2/Filters/Png.cpp b/src/common/Gdc2/Filters/Png.cpp --- a/src/common/Gdc2/Filters/Png.cpp +++ b/src/common/Gdc2/Filters/Png.cpp @@ -1,1599 +1,1597 @@ /*hdr ** FILE: Png.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Png file filter ** ** Copyright (C) 2002, Matthew Allen ** fret@memecode.com */ // // 'png.h' comes from libpng, which you can get from: // http://www.libpng.org/pub/png/libpng.html // // You will also need zlib, which pnglib requires. zlib // is available here: // http://www.gzip.org/zlib // // If you don't want to build with PNG support then set // the define HAS_LIBPNG_ZLIB to '0' in Lgi.h // #ifndef __CYGWIN__ #include "math.h" #include "png.h" #endif #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" #ifdef __CYGWIN__ #include "png.h" #endif #include #include #include #include "lgi/common/LibraryUtils.h" #ifdef FILTER_UI #include "TransparentDlg.h" #endif #include "lgi/common/Variant.h" // Pixel formats typedef uint8_t Png8; typedef LRgb24 Png24; typedef LRgba32 Png32; typedef LRgb48 Png48; typedef LRgba64 Png64; #if PNG_LIBPNG_VER_MAJOR <= 1 && PNG_LIBPNG_VER_MINOR <= 2 #define png_const_infop png_infop #define png_const_bytep png_bytep #endif #ifdef LINUX const char *LinuxLibName() { static char lib[64]; sprintf_s(lib, sizeof(lib), "libpng%i", PNG_LIBPNG_VER_SONUM); // printf("png lib name = '%s'\n", lib); return lib; } #endif #if LIBPNG_SHARED #define LIBPNG Lib-> const char *sLibrary = - #if defined(MAC) + #if defined(MAC) || defined(HAIKU) "libpng16" - #elif defined(HAIKU) - "libpng16.so.16" #elif defined(LINUX) LinuxLibName() #else #if defined(__CYGWIN__) "cygpng12" #else "libpng16" #ifdef _MSC_VER_STR "_" #if _MSC_VER >= _MSC_VER_VS2019 _MSC_YEAR_STR #else _MSC_VER_STR #endif #if defined(LGI_64BIT) "x64" #else "x32" #endif #ifdef _DEBUG "d" #endif #endif #endif #endif ; // Library interface class LibPng : public LLibrary { public: LibPng() : LLibrary(sLibrary) { static bool First = true; if (First) { First = false; auto Loaded = IsLoaded(); if (Loaded) { LgiTrace("%s:%i - PNG: %s\n", _FL, GetFullPath().Get()); } else { #if defined(WINDOWS) && defined(_DEBUG) auto ReleaseLib = LString(sLibrary)(0, -2); if (!Load(ReleaseLib)) #endif LgiTrace("%s:%i - Failed to load '%s'.\n", _FL, sLibrary); } } } DynFunc4( png_structp, png_create_read_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc4( png_structp, png_create_write_struct, png_const_charp, user_png_ver, png_voidp, error_ptr, png_error_ptr, error_fn, png_error_ptr, warn_fn); DynFunc1( png_infop, png_create_info_struct, png_structp, png_ptr); DynFunc2( int, png_destroy_info_struct, png_structp, png_ptr, png_infopp, info_ptr); DynFunc3( int, png_destroy_read_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr, png_infopp, end_info_ptr_ptr); DynFunc2( int, png_destroy_write_struct, png_structpp, png_ptr_ptr, png_infopp, info_ptr_ptr); DynFunc3( int, png_set_read_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, read_data_fn); DynFunc4( int, png_set_write_fn, png_structp, png_ptr, png_voidp, io_ptr, png_rw_ptr, write_data_fn, png_flush_ptr, output_flush_fn); DynFunc4( int, png_read_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc4( int, png_write_png, png_structp, png_ptr, png_infop, info_ptr, int, transforms, png_voidp, params); DynFunc2( png_bytepp, png_get_rows, png_structp, png_ptr, png_infop, info_ptr); DynFunc3( int, png_set_rows, png_structp, png_ptr, png_infop, info_ptr, png_bytepp, row_pointers); DynFunc6( png_uint_32, png_get_iCCP, png_structp, png_ptr, png_const_infop, info_ptr, png_charpp, name, int*, compression_type, png_bytepp, profile, png_uint_32*, proflen); DynFunc6( int, png_set_iCCP, png_structp, png_ptr, png_infop, info_ptr, png_charp, name, int, compression_type, png_const_bytep, profile, png_uint_32, proflen); DynFunc5( png_uint_32, png_get_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep*, trans_alpha, int*, num_trans, png_color_16p*, trans_color); DynFunc3( png_uint_32, png_get_valid, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, flag); DynFunc4( png_uint_32, png_get_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp*, palette, int*, num_palette); DynFunc2( png_uint_32, png_get_image_width, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_uint_32, png_get_image_height, png_structp, png_ptr, png_infop, info_ptr); DynFunc2( png_byte, png_get_channels, png_structp, png_ptr, png_infop, info_ptr); #if 1 // PNG_LIBPNG_VER <= 10250 DynFunc2( png_byte, png_get_color_type, png_structp, png_ptr, png_infop, info_ptr); #else DynFunc2( png_byte, png_get_color_type, png_const_structp, png_ptr, png_const_infop, info_ptr); #endif DynFunc2( png_byte, png_get_bit_depth, png_structp, png_ptr, png_infop, info_ptr); DynFunc1( png_voidp, png_get_error_ptr, png_structp, png_ptr); DynFunc1( png_voidp, png_get_io_ptr, png_structp, png_ptr); DynFunc9( int, png_set_IHDR, png_structp, png_ptr, png_infop, info_ptr, png_uint_32, width, png_uint_32, height, int, bit_depth, int, color_type, int, interlace_method, int, compression_method, int, filter_method); DynFunc4( int, png_set_PLTE, png_structp, png_ptr, png_infop, info_ptr, png_colorp, palette, int, num_palette); DynFunc5( int, png_set_tRNS, png_structp, png_ptr, png_infop, info_ptr, png_bytep, trans_alpha, int, num_trans, png_color_16p, trans_color); /* DynFunc2( png_byte, png_get_interlace_type, png_const_structp, png_ptr, png_const_infop, info_ptr); */ }; class InitLibPng : public LMutex { LAutoPtr Png; public: LibPng *Get() { if (Lock(_FL)) { if (!Png) Png.Reset(new LibPng); Unlock(); } return Png; } InitLibPng() : LMutex("InitLibPng") { } } CurrentLibPng; #else #define LIBPNG #endif class GdcPng : public LFilter { static char PngSig[]; friend void PNGAPI LibPngError(png_structp Png, png_const_charp Msg); friend void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg); #if LIBPNG_SHARED LibPng *Lib; #endif int Pos; uchar *PrevScanLine; LSurface *pDC; LMemQueue DataPipe; LView *Parent; jmp_buf Here; public: GdcPng ( #if LIBPNG_SHARED LibPng *lib #endif ); ~GdcPng(); const char *GetComponentName() override { return "libpng"; } Format GetFormat() override { return FmtPng; } void SetMeter(int i) { if (Meter) Meter->Value(i); } int GetCapabilites() override { return FILTER_CAP_READ | FILTER_CAP_WRITE; } IoStatus ReadImage(LSurface *pDC, LStream *In) override; IoStatus WriteImage(LStream *Out, LSurface *pDC) override; bool GetVariant(const char *n, LVariant &v, const char *a = NULL) override { if (!_stricmp(n, LGI_FILTER_TYPE)) { v = "Png"; // Portable Network Graphic } else if (!_stricmp(n, LGI_FILTER_EXTENSIONS)) { v = "PNG"; } else return false; return true; } }; // Object Factory class GdcPngFactory : public LFilterFactory { bool CheckFile(const char *File, int Access, const uchar *Hint) { if (Hint) { return Hint[1] == 'P' && Hint[2] == 'N' && Hint[3] == 'G'; } else { return (File) ? stristr(File, ".png") != 0 : false; } } LFilter *NewObject() { return new GdcPng ( #if LIBPNG_SHARED CurrentLibPng.Get() #endif ); } } PngFactory; // Class impl char GdcPng::PngSig[] = { (char)137, 'P', 'N', 'G', '\r', '\n', (char)26, '\n', 0 }; GdcPng::GdcPng( #if LIBPNG_SHARED LibPng *lib #endif ) { #if LIBPNG_SHARED Lib = lib; #endif Parent = 0; Pos = 0; PrevScanLine = 0; } GdcPng::~GdcPng() { DeleteArray(PrevScanLine); } void PNGAPI LibPngError(png_structp Png, png_const_charp Msg) { GdcPng *This = (GdcPng*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_error_ptr(Png); if (This) { printf("Libpng Error Message='%s'\n", Msg); if (This->Props) { LVariant v; This->Props->SetValue(LGI_FILTER_ERROR, v = (char*)Msg); } longjmp(This->Here, -1); } } void PNGAPI LibPngWarning(png_structp Png, png_const_charp Msg) { LgiTrace("LibPng Warning: %s\n", Msg); } void PNGAPI LibPngRead(png_structp Png, png_bytep Ptr, png_size_t Size) { LStream *s = (LStream*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (s) { s->Read(Ptr, Size); } else { LgiTrace("%s:%i - No this ptr? (%p)\n", __FILE__, __LINE__, Ptr); LAssert(0); } } struct PngWriteInfo { LStream *s; Progress *m; }; void PNGAPI LibPngWrite(png_structp Png, png_bytep Ptr, png_size_t Size) { PngWriteInfo *i = (PngWriteInfo*) #if LIBPNG_SHARED CurrentLibPng.Get()-> #endif png_get_io_ptr(Png); if (i) { i->s->Write(Ptr, Size); /* if (i->m) i->m->Value(Png->flush_rows); */ } else { LgiTrace("%s:%i - No this ptr?\n", __FILE__, __LINE__); LAssert(0); } } template void Read32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void Read64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void Read32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void Read64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void Read32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = 255; o++; i++; } } template void Read64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = 255; o++; i++; } } template void ReadAlpha32_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 3; o->g = i->g >> 2; o->b = i->b >> 3; o++; i++; } } template void ReadAlpha64_16(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 11; o->g = i->g >> 10; o->b = i->b >> 11; o++; i++; } } template void ReadAlpha32_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o++; i++; } } template void ReadAlpha64_24(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o++; i++; } } template void ReadAlpha32_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r; o->g = i->g; o->b = i->b; o->a = i->a; o++; i++; } } template void ReadAlpha64_32(Out *o, In *i, int Len) { In *e = i + Len; while (i < e) { o->r = i->r >> 8; o->g = i->g >> 8; o->b = i->b >> 8; o->a = i->a >> 8; o++; i++; } } LFilter::IoStatus GdcPng::ReadImage(LSurface *pDeviceContext, LStream *In) { LFilter::IoStatus Status = IoError; Pos = 0; pDC = pDeviceContext; DeleteArray(PrevScanLine); if (!pDC) { LAssert(!"No DC."); return Status; } LVariant v; if (Props && Props->GetValue(LGI_FILTER_PARENT_WND, v) && v.Type == GV_GVIEW) { Parent = (LView*)v.Value.Ptr; } #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { LString s; s.Printf("libpng is missing (%s.%s)", sLibrary, LGI_LIBRARY_EXT); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = s); else LgiTrace("%s:%i - %s\n", _FL, s.Get()); static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unable to load libpng (%s.%s).\n", _FL, sLibrary, LGI_LIBRARY_EXT); Warn = false; } return LFilter::IoComponentMissing; } #endif png_structp png_ptr = NULL; if (setjmp(Here)) { return Status; } png_ptr = LIBPNG png_create_read_struct(PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (!png_ptr) { if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "png_create_read_struct failed."); } else { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { LIBPNG png_set_read_fn(png_ptr, In, LibPngRead); #if 0 // What was this for again? int off = (char*)&png_ptr->io_ptr - (char*)png_ptr; if (!png_ptr->io_ptr) { printf("io_ptr offset = %i\n", off); LAssert(0); CurrentLibPng = 0; return false; } #endif LIBPNG png_read_png(png_ptr, info_ptr, 0, 0); png_bytepp Scan0 = LIBPNG png_get_rows(png_ptr, info_ptr); if (Scan0) { int BitDepth = LIBPNG png_get_bit_depth(png_ptr, info_ptr); int FinalBits = BitDepth == 16 ? 8 : BitDepth; int ColourType = LIBPNG png_get_color_type(png_ptr, info_ptr); int Channels = LIBPNG png_get_channels(png_ptr, info_ptr); int RequestBits = FinalBits * Channels; LColourSpace InCs = ColourType == PNG_COLOR_TYPE_GRAY_ALPHA ? CsIndex8 : LBitsToColourSpace(MAX(RequestBits, 8)); if (!pDC->Create( LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), InCs, LSurface::SurfaceRequireExactCs)) { printf("%s:%i - LMemDC::Create(%i, %i, %i) failed.\n", _FL, LIBPNG png_get_image_width(png_ptr, info_ptr), LIBPNG png_get_image_height(png_ptr, info_ptr), RequestBits); } else { bool Error = false; #if 1 if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { pDC->HasAlpha(true); // Setup alpha channel } /* printf("PngRead %s->%s\n", LColourSpaceToString(InCs), LColourSpaceToString(pDC->GetColourSpace())); */ #endif // Copy in the scanlines int ActualBits = pDC->GetBits(); int ScanLen = LIBPNG png_get_image_width(png_ptr, info_ptr) * ActualBits / 8; LColourSpace OutCs = pDC->GetColourSpace(); for (int y=0; yY() && !Error; y++) { uchar *Scan = (*pDC)[y]; LAssert(Scan != NULL); switch (RequestBits) { case 1: { uchar *o = Scan; uchar *e = Scan + pDC->X(); uchar *i = Scan0[y]; uchar Mask = 0x80; while (o < e) { *o++ = (*i & Mask) ? 1 : 0; Mask >>= 1; if (!Mask) { i++; Mask = 0x80; } } break; } case 2: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { switch (x & 3) { case 0: *o++ = (*i >> 6) & 0x3; break; case 1: *o++ = (*i >> 4) & 0x3; break; case 2: *o++ = (*i >> 2) & 0x3; break; case 3: *o++ = (*i++ >> 0) & 0x3; break; } } break; } case 4: { uchar *i = Scan0[y]; uchar *o = Scan; for (int x=0; xX(); x++) { if (x & 1) *o++ = *i++ & 0xf; else *o++ = (*i >> 4) & 0xf; } break; } case 8: { memcpy(Scan, Scan0[y], ScanLen); break; } case 16: { if (ColourType == PNG_COLOR_TYPE_GRAY_ALPHA) { uint8_t *grey = Scan; uint8_t *alpha = (*(pDC->AlphaDC()))[y]; LAssert(grey && alpha); uint8_t *end = grey + pDC->X(); uint8_t *in = Scan0[y]; while (grey < end) { *grey++ = *in++; *alpha++ = *in++; } } else { memcpy(Scan, Scan0[y], ScanLen); } break; } case 24: { switch (OutCs) { #define Read24Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ Read64_##bits((L##name*)Scan, (Png48*)Scan0[y], pDC->X()); \ else \ Read32_##bits((L##name*)Scan, (Png24*)Scan0[y], pDC->X()); \ break; \ } Read24Case(Rgb16, 16); Read24Case(Bgr16, 16); Read24Case(Rgb24, 24); Read24Case(Bgr24, 24); Read24Case(Xrgb32, 24); Read24Case(Rgbx32, 24); Read24Case(Xbgr32, 24); Read24Case(Bgrx32, 24); Read24Case(Rgba32, 32); Read24Case(Bgra32, 32); Read24Case(Argb32, 32); Read24Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); break; } break; } case 32: { switch (pDC->GetColourSpace()) { #define Read32Case(name, bits) \ case Cs##name: \ { \ if (LIBPNG png_get_bit_depth(png_ptr, info_ptr) == 16) \ ReadAlpha64_##bits((L##name*)Scan, (Png64*)Scan0[y], pDC->X()); \ else \ ReadAlpha32_##bits((L##name*)Scan, (Png32*)Scan0[y], pDC->X()); \ break; \ } Read32Case(Rgb16, 16); Read32Case(Bgr16, 16); Read32Case(Rgb24, 24); Read32Case(Bgr24, 24); Read32Case(Xrgb32, 24); Read32Case(Rgbx32, 24); Read32Case(Xbgr32, 24); Read32Case(Bgrx32, 24); Read32Case(Rgba32, 32); Read32Case(Bgra32, 32); Read32Case(Argb32, 32); Read32Case(Abgr32, 32); default: LgiTrace("%s:%i - Unsupported colour space: 0x%x (%s)\n", _FL, pDC->GetColourSpace(), LColourSpaceToString(pDC->GetColourSpace())); LAssert(!"Not impl."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; break; } break; } default: { if (ActualBits == RequestBits) { memcpy(Scan, Scan0[y], ScanLen); } else { LAssert(!"Yeah you need to impl a convertor here."); if (Props) Props->SetValue(LGI_FILTER_ERROR, v = "Missing scan convertor"); Error = true; } break; } } } if (RequestBits == 32) { // bool IsPreMul = pDC->IsPreMultipliedAlpha(); pDC->ConvertPreMulAlpha(true); } if (ActualBits <= 8) { // Copy in the palette png_colorp pal; int num_pal = 0; if (LIBPNG png_get_PLTE(png_ptr, info_ptr, &pal, &num_pal) == PNG_INFO_PLTE) { LPalette *Pal = new LPalette(0, num_pal); if (Pal) { for (int i=0; ir = pal[i].red; Rgb->g = pal[i].green; Rgb->b = pal[i].blue; } } pDC->Palette(Pal, true); } } if (LIBPNG png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_bytep trans_alpha = NULL; png_color_16p trans_color; int num_trans; if (LIBPNG png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color)) { pDC->HasAlpha(true); LSurface *Alpha = pDC->AlphaDC(); if (Alpha) { if (trans_alpha) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { if (p[x] < num_trans) { a[x] = trans_alpha[p[x]]; } else { a[x] = 0xff; } } } } else if (trans_color) { for (int y=0; yY(); y++) { uchar *a = (*Alpha)[y]; uchar *p = (*pDC)[y]; for (int x=0; xX(); x++) { a[x] = p[x] == trans_color->index ? 0x00 : 0xff; } } } } else { printf("%s:%i - No alpha channel.\n", _FL); } } else { printf("%s:%i - Bad trans ptr.\n", _FL); } } } Status = Error ? IoError : IoSuccess; } } else LgiTrace("%s:%i - png_get_rows failed.\n", _FL); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } png_charp ProfName = 0; int CompressionType = 0; png_bytep ColProf = 0; png_uint_32 ColProfLen = 0; if (LIBPNG png_get_iCCP(png_ptr, info_ptr, &ProfName, &CompressionType, &ColProf, &ColProfLen) && Props) { v.SetBinary(ColProfLen, ColProf); Props->SetValue(LGI_FILTER_COLOUR_PROF, v); } LIBPNG png_destroy_read_struct(&png_ptr, 0, 0); } return Status; } LFilter::IoStatus GdcPng::WriteImage(LStream *Out, LSurface *pDC) { LFilter::IoStatus Status = IoError; LVariant Transparent; bool HasTransparency = false; COLOUR Back = 0; LVariant v; if (!pDC) return LFilter::IoError; #if LIBPNG_SHARED if (!Lib->IsLoaded() && !Lib->Load(sLibrary)) { static bool Warn = true; if (Warn) { LgiTrace("%s:%i - Unabled to load libpng.\n", _FL); Warn = false; } return LFilter::IoComponentMissing; } #endif // Work out whether the image has transparency if (pDC->GetBits() == 32) { // Check alpha channel for (int y=0; yY() && !HasTransparency; y++) { System32BitPixel *p = (System32BitPixel*)(*pDC)[y]; if (!p) break; System32BitPixel *e = p + pDC->X(); while (p < e) { if (p->a < 255) { HasTransparency = true; break; } p++; } } } else if (pDC->AlphaDC()) { LSurface *a = pDC->AlphaDC(); if (a) { for (int y=0; yY() && !HasTransparency; y++) { uint8_t *p = (*a)[y]; if (!p) break; uint8_t *e = p + a->X(); while (p < e) { if (*p < 255) { HasTransparency = true; break; } p++; } } } } if (Props) { if (Props->GetValue(LGI_FILTER_PARENT_WND, v) && v.Type == GV_GVIEW) { Parent = (LView*)v.Value.Ptr; } if (Props->GetValue(LGI_FILTER_BACKGROUND, v)) { Back = v.CastInt32(); } Props->GetValue(LGI_FILTER_TRANSPARENT, Transparent); } #ifdef FILTER_UI if (Parent && Transparent.IsNull()) { // put up a dialog to ask about transparent colour LTransparentDlg Dlg(Parent, &Transparent); if (!Dlg.DoModal()) { if (Props) Props->SetValue("Cancel", v = 1); return IoCancel; } } #endif if (setjmp(Here) == 0 && pDC && Out) { LVariant ColProfile; if (Props) { Props->GetValue(LGI_FILTER_COLOUR_PROF, ColProfile); } // setup png_structp png_ptr = LIBPNG png_create_write_struct( PNG_LIBPNG_VER_STRING, (void*)this, LibPngError, LibPngWarning); if (png_ptr) { png_infop info_ptr = LIBPNG png_create_info_struct(png_ptr); if (info_ptr) { Out->SetSize(0); PngWriteInfo WriteInfo; WriteInfo.s = Out; WriteInfo.m = Meter; LIBPNG png_set_write_fn(png_ptr, &WriteInfo, LibPngWrite, 0); // png_set_write_status_fn(png_ptr, write_row_callback); bool KeyAlpha = false; bool ChannelAlpha = false; LMemDC *pTemp = 0; if (pDC->AlphaDC() && HasTransparency) { pTemp = new LMemDC(pDC->X(), pDC->Y(), System32BitColourSpace); if (pTemp) { pTemp->Colour(0); pTemp->Rectangle(); pTemp->Op(GDC_ALPHA); pTemp->Blt(0, 0, pDC); pTemp->Op(GDC_SET); pDC = pTemp; ChannelAlpha = true; } } else { if (Transparent.CastInt32() && Props && Props->GetValue(LGI_FILTER_BACKGROUND, v)) { KeyAlpha = true; } } int Ar = R32(Back); int Ag = G32(Back); int Ab = B32(Back); if (pDC->GetBits() == 32) { if (!ChannelAlpha && !KeyAlpha) { for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; for (int x=0; xX(); x++) { if (s[x].a < 0xff) { ChannelAlpha = true; y = pDC->Y(); break; } } } } } bool ExtraAlphaChannel = ChannelAlpha || (pDC->GetBits() > 8 ? KeyAlpha : 0); int ColourType; if (pDC->GetBits() <= 8) { if (pDC->Palette()) ColourType = PNG_COLOR_TYPE_PALETTE; else ColourType = PNG_COLOR_TYPE_GRAY; } else if (ExtraAlphaChannel) { ColourType = PNG_COLOR_TYPE_RGB_ALPHA; } else { ColourType = PNG_COLOR_TYPE_RGB; } LIBPNG png_set_IHDR(png_ptr, info_ptr, pDC->X(), pDC->Y(), 8, ColourType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); if (ColProfile.Type == GV_BINARY) { LIBPNG png_set_iCCP(png_ptr, info_ptr, (png_charp)"ColourProfile", 0, (png_const_bytep) ColProfile.Value.Binary.Data, (png_uint_32) ColProfile.Value.Binary.Length); } int TempLine = pDC->X() * ((pDC->GetBits() <= 8 ? 1 : 3) + (ExtraAlphaChannel ? 1 : 0)); uchar *TempBits = new uchar[pDC->Y() * TempLine]; if (Meter) Meter->SetRange(pDC->Y()); switch (pDC->GetBits()) { case 8: { // Output the palette LPalette *Pal = pDC->Palette(); if (Pal) { int Colours = Pal->GetSize(); LAutoPtr PngPal(new png_color[Colours]); if (PngPal) { for (int i=0; ir; PngPal[i].green = Rgb->g; PngPal[i].blue = Rgb->b; } } LIBPNG png_set_PLTE(png_ptr, info_ptr, PngPal, Colours); } } // Copy the pixels for (int y=0; yY(); y++) { uchar *s = (*pDC)[y]; Png8 *d = (Png8*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { *d++ = *s++; } } // Setup the transparent palette entry if (KeyAlpha) { static png_byte Trans[256]; for (uint n=0; nbit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { uint16 *s = (uint16*) (*pDC)[y]; if (pDC->GetBits() == 15) { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc15(*s); d->g = Gc15(*s); d->b = Bc15(*s); s++; d++; } } } else { if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); d->a = (d->r == Ar && d->g == Ag && d->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = Rc16(*s); d->g = Gc16(*s); d->b = Bc16(*s); s++; d++; } } } } break; } case 24: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (KeyAlpha ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (KeyAlpha ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System24BitPixel *s = (System24BitPixel*) (*pDC)[y]; if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = (s->r == Ar && s->g == Ag && s->b == Ab) ? 0 : 0xff; s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } case 32: { //info_ptr->bit_depth = 8; //info_ptr->channels = 3 + (ExtraAlphaChannel ? 1 : 0); //info_ptr->color_type = PNG_COLOR_TYPE_RGB | (ExtraAlphaChannel ? PNG_COLOR_MASK_ALPHA : 0); for (int y=0; yY(); y++) { System32BitPixel *s = (System32BitPixel*) (*pDC)[y]; if (ChannelAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; s++; d++; } } else if (KeyAlpha) { Png32 *d = (Png32*) (TempBits + (TempLine * y)); Png32 *e = d + pDC->X(); while (d < e) { if (s->a == 0 || (s->r == Ar && s->g == Ag && s->b == Ab) ) { d->r = 0; d->g = 0; d->b = 0; d->a = 0; } else { d->r = s->r; d->g = s->g; d->b = s->b; d->a = s->a; } s++; d++; } } else { Png24 *d = (Png24*) (TempBits + (TempLine * y)); for (int x=0; xX(); x++) { d->r = s->r; d->g = s->g; d->b = s->b; s++; d++; } } } break; } default: { goto CleanUp; } } LArray row; if (row.Length(pDC->Y())) { for (int y=0; yY(); y++) { row[y] = TempBits + (TempLine * y); } LIBPNG png_set_rows(png_ptr, info_ptr, row.AddressOf()); LIBPNG png_write_png(png_ptr, info_ptr, 0, 0); Status = IoSuccess; } DeleteArray(TempBits); DeleteObj(pTemp); LIBPNG png_destroy_info_struct(png_ptr, &info_ptr); } CleanUp: LIBPNG png_destroy_write_struct(&png_ptr, NULL); } } return Status; } 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,2362 +1,2293 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include +#ifdef HAIKU +#include +#endif + #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(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; - } - - auto &l = Info[0]; - l.Str = Str; - l.Len = Str ? strlen(Str) : 0; - - const char *strArr[] = { Str }; - const int32 strLen[] = { l.Len }; - float width[1] = { 0 }; - fnt->GetStringWidths(strArr, strLen, 1, width); - - x = l.X = (int)(width[0] + 0.5); - - font_height height = {0}; - fnt->GetHeight(&height); - y = height.ascent + height.descent + height.leading; - - // printf("Layout str=%s sz=%i,%i\n", Str, x, y); - - #elif defined(LGI_SDL) + #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) { 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__ + #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); 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 MAC && !defined(LGI_SDL) + #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; - #endif } } 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; } -/* If this is only impl for windows either, impl for everything or don't use. -void LDisplayString::Length(int New) -{ - Layout(); - - #if WINNATIVE - - if (New < len) - { - LFontSystem *Sys = LFontSystem::Inst(); - - int CurX = 0; - int CurLen = 0; - for (int i=0; i= CurLen && New < CurLen + Info[i].Len ) - { - // In this block - int Offset = New - CurLen; - Info[i].Len = Offset; - Info[i].Str[Info[i].Len] = 0; - - // Get the font for this block of characters - LFont *f = 0; - if (Info[i].FontId) - { - f = Sys->Font[Info[i].FontId]; - f->PointSize(Font->PointSize()); - f->Transparent(Font->Transparent()); - if (!f->Handle()) - { - f->Create(); - } - } - else - { - f = Font; - } - - // Chop the current block and re-measure - int ChoppedX, Unused; - f->_Measure(ChoppedX, Unused, Info[i].Str, Info[i].Len); - Info[i].X = ChoppedX; - x = CurX + Info[i].X; - Info.Length(i + 1); - - // Leave the loop - break; - } - - CurX += Info[i].X; - CurLen += Info[i].Len; - } - } - else - { - printf("%s:%i - New>=Len (%i>=" LPrintfSSizeT" )\n", _FL, New, len); - } - - #endif -} -*/ - 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); + // 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; - } - - view->SetHighColor(Font->Fore()); - - font_height height = {0}; - fnt->GetHeight(&height); + 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; + } + + font_height height = {0}; + fnt->GetHeight(&height); + + /* + if (_debug) + printf(" trans=%i height=%g,%g,%g\n", + Font->Transparent(), + height.ascent, height.descent, height.leading); + */ - auto &i = Info[0]; - view->SetFont(fnt); - view->DrawString(i.Str, i.Len, BPoint(px, py + height.ascent)); + if (!Font->Transparent()) + { + view->SetHighColor(Font->Back()); + if (r) + view->FillRect(*r); + else + view->FillRect(BRect(px, py, px+x, py+y)); + } + + auto locked = view->LockLooper(); + view->SetFont(fnt); + view->SetHighColor(Font->Fore()); + + int cx = px; + for (auto &i: Info) + { + /* + if (_debug) + printf(" info=%.*s c=%i,%i\n", i.Len, i.Str, cx, py); + */ + + view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); + + cx += i.X; + } + + BRegion region; + view->GetClippingRegion(®ion); + + if (locked) + view->UnlockLooper(); #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) + if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { - #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) + 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 + 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"); + 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) + if (Info.Length() && pDC && Font) { - cWhitespace = Font->WhitespaceColour(); - LAssert(cWhitespace.IsValid()); - } - - for (int i=0; iColour(); + int TabSize = Font->TabSize() ? Font->TabSize() : 32; + int Ox = px; + LColour cFore = Font->Fore(); + LColour cBack = Font->Back(); + LColour cWhitespace; - // Get the font for this block of characters - if (Info[i].FontId) + if (VisibleTab) { - f = Sys->Font[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; + cWhitespace = Font->WhitespaceColour(); + LAssert(cWhitespace.IsValid()); } - if (f) + 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 = i ? px : r->x1; + b.x1 = px; b.y1 = r->y1; - b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; + b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; - b.x2 = px + Info[i].X - 1; + b.x2 = px + Sx - 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++; - } - } - } - } + LColour Fg = Font->Fore(); + Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } - // Inc my position - px += Info[i].X; + pDC->Colour(Old); } - - if (AppendDots) + else if (r && + Font && + !Font->Transparent()) { - 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(Font->Back()); + pDC->Rectangle(r); } - - 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 y; + 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) { 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,1381 +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 { auto Dpi = LScreenDpi(); int PtSize = PointSize(); 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/Gdc2/Font/FontType.cpp b/src/common/Gdc2/Font/FontType.cpp --- a/src/common/Gdc2/Font/FontType.cpp +++ b/src/common/Gdc2/Font/FontType.cpp @@ -1,789 +1,793 @@ #include "lgi/common/Lgi.h" #include "lgi/common/FontSelect.h" LFontType::LFontType(const char *face, int pointsize) { #if defined WINNATIVE ZeroObj(Info); if (face) { swprintf_s(Info.lfFaceName, CountOf(Info.lfFaceName), L"%S", face); } if (pointsize) { Info.lfHeight = WinHeightToPoint(pointsize); } #else if (face) Info.Face(face); if (pointsize) Info.PointSize(pointsize); #endif } LFontType::~LFontType() { } const char *LFontType::GetFace() { #ifdef WINNATIVE Buf = Info.lfFaceName; return Buf; #else return Info.Face(); #endif } void LFontType::SetFace(const char *Face) { #ifdef WINNATIVE if (Face) { swprintf_s(Info.lfFaceName, CountOf(Info.lfFaceName), L"%S", Face); } else { Info.lfFaceName[0] = 0; } #else Info.Face(Face); #endif } int LFontType::GetPointSize() { #ifdef WINNATIVE return WinHeightToPoint(Info.lfHeight); #else return Info.PointSize(); #endif } void LFontType::SetPointSize(int PointSize) { #ifdef WINNATIVE Info.lfHeight = WinPointToHeight(PointSize); #else Info.PointSize(PointSize); #endif } -bool LFontType::DoUI(LView *Parent) +void LFontType::DoUI(LView *Parent, std::function Callback) { bool Status = false; #if WINNATIVE - void *i = &Info; int bytes = sizeof(Info); #else - char i[128]; - int bytes = sprintf_s(i, sizeof(i), "%s,%i", Info.Face(), Info.PointSize()); + char str[256]; + int bytes = sprintf_s(str, sizeof(str), "%s,%i", Info.Face(), Info.PointSize()); #endif - LFontSelect Dlg(Parent, i, bytes); - if (Dlg.DoModal() == IDOK) + LFontSelect *Dlg = new LFontSelect(Parent, &Info, bytes); + Dlg->DoModal([Dlg, this, Callback](auto dlg, auto id) { - #if WINNATIVE - Dlg.Serialize(i, sizeof(Info), true); - #else - if (Dlg.Face) Info.Face(Dlg.Face); - Info.PointSize(Dlg.Size); - #endif - - Status = true; - } - - return Status; + if (id == IDOK) + { + #if WINNATIVE + Dlg->Serialize(&this->Info, sizeof(this->Info), true); + #else + if (Dlg->Face) + Info.Face(Dlg->Face); + Info.PointSize(Dlg->Size); + #endif + + if (Callback) + Callback(this); + } + + delete Dlg; + }); } bool LFontType::GetDescription(char *Str, int SLen) { if (Str && GetFace()) { sprintf_s(Str, SLen, "%s, %i pt", GetFace(), GetPointSize()); return true; } return false; } bool LFontType::Serialize(LDom *Options, const char *OptName, bool Write) { bool Status = false; if (Options && OptName) { LVariant v; #if defined WINNATIVE if (Write) { v.SetBinary(sizeof(Info), &Info); Status = Options->SetValue(OptName, v); } else { if (Options->GetValue(OptName, v)) { if (v.Type == GV_BINARY && v.Value.Binary.Length == sizeof(Info)) { memcpy(&Info, v.Value.Binary.Data, sizeof(Info)); Status = ValidStrW(Info.lfFaceName); } } } #else if (Write) { char Temp[128]; sprintf_s(Temp, sizeof(Temp), "%s,%i pt", Info.Face(), Info.PointSize()); Status = Options->SetValue(OptName, v = Temp); } else { if (Options->GetValue(OptName, v) && ValidStr(v.Str())) { char *Comma = strchr(v.Str(), ','); if (Comma) { *Comma++ = 0; int PtSize = atoi(Comma); if (stricmp(v.Str(), "(null)")) { Info.Face(v.Str()); Info.PointSize(PtSize); // printf("FontTypeSer getting '%s' = '%s' pt %i\n", OptName, v.Str(), PtSize); Status = true; } } } } #endif } return Status; } bool LFontType::GetConfigFont(const char *Tag) { // read from config file auto Font = LAppInst->GetConfig(Tag); if (!Font) return false; if (Font == "-") return false; // default string added for discoverability auto p = Font.Split(":"); if (p.Length() != 2) { LgiTrace("%s:%i - Font specification '%s' should have the format: :\n", _FL, Font.Get()); return false; } SetFace(p[0]); SetPointSize((int)p[1].Int()); return true; } class LFontTypeCache { char DefFace[64]; int DefSize; char Face[64]; int Size; public: LFontTypeCache(char *defface, int defsize) { ZeroObj(Face); Size = -1; strcpy_s(DefFace, sizeof(DefFace), defface); DefSize = defsize; } char *GetFace(char *Type = 0) { #ifdef LINUX if (!IsInit()) { char f[256]; int s; if (_GetSystemFont(Type, f, sizeof(f), s)) { strcpy_s(Face, sizeof(Face), f); Size = s; } else { Face[0] = 0; Size = 0; } } #endif return ValidStr(Face) ? Face : DefFace; } int GetSize() { return Size > 0 ? Size : DefSize; } bool IsInit() { return Size >= 0; } }; #if defined USE_CORETEXT bool MacGetSystemFont(LTypeFace &Info, CTFontUIFontType Which) { CTFontRef ref = CTFontCreateUIFontForLanguage(Which, 0.0, NULL); if (!ref) return false; bool Status = false; CFStringRef name = CTFontCopyFamilyName(ref); if (name) { CGFloat sz = CTFontGetSize(ref); LString face(name); Info.Face(face); Info.PointSize((int)sz); CFRelease(name); Status = true; } CFRelease(ref); return Status; } #endif bool LFontType::GetSystemFont(const char *Which) { bool Status = false; if (!Which) { LAssert(!"No param supplied."); return false; } #if LGI_SDL #elif defined WINNATIVE // Get the system settings NONCLIENTMETRICS info; info.cbSize = sizeof(info); #if (WINVER >= 0x0600) LArray Ver; if (LGetOs(&Ver) == LGI_OS_WIN32 && Ver[0] <= 5) info.cbSize -= 4; #endif BOOL InfoOk = SystemParametersInfo( SPI_GETNONCLIENTMETRICS, info.cbSize, &info, 0); if (!InfoOk) { LgiTrace("%s:%i - SystemParametersInfo failed with 0x%x (info.cbSize=%i, os=%i, %i)\n", _FL, GetLastError(), info.cbSize, LGetOs(), LGI_OS_WIN9X); } // Convert windows font height into points int Height = WinHeightToPoint(info.lfMessageFont.lfHeight); #elif defined __GTK_H__ // Define some defaults.. in case the system settings aren't there static char DefFont[64] = #ifdef __CYGWIN__ "Luxi Sans"; #else "Sans"; #endif int DefSize = 10; // int Offset = 0; static bool First = true; if (First) { bool ConfigFontUsed = false; char p[MAX_PATH_LEN]; LGetSystemPath(LSP_HOME, p, sizeof(p)); LMakePath(p, sizeof(p), p, ".lgi.conf"); if (LFileExists(p)) { LAutoString a(LReadTextFile(p)); if (a) { LString s; s = a.Get(); LString::Array Lines = s.Split("\n"); for (int i=0; i 1) { strcpy_s(DefFont, sizeof(DefFont), d[0]); int PtSize = d[1].Int(); if (PtSize > 0) { DefSize = PtSize; ConfigFontUsed = true; printf("Config font %s : %i\n", DefFont, DefSize); } } } } } } else printf("Can't read '%s'\n", p); } if (!ConfigFontUsed) { Gtk::GtkStyle *s = Gtk::gtk_style_new(); if (s) { const char *fam = Gtk::pango_font_description_get_family(s->font_desc); if (fam) { strcpy_s(DefFont, sizeof(DefFont), fam); } else printf("%s:%i - pango_font_description_get_family failed.\n", _FL); if (Gtk::pango_font_description_get_size_is_absolute(s->font_desc)) { float Px = Gtk::pango_font_description_get_size(s->font_desc) / PANGO_SCALE; float Dpi = (float)LScreenDpi().x; DefSize = (Px * 72.0) / Dpi; printf("pango px=%f, Dpi=%f\n", Px, Dpi); } else { DefSize = Gtk::pango_font_description_get_size(s->font_desc) / PANGO_SCALE; } g_object_unref(s); } else printf("%s:%i - gtk_style_new failed.\n", _FL); } First = false; } #endif int PtSizeOffset = 0; auto Offset = LAppInst->GetConfig(LApp::CfgFontsPointSizeOffset); if (Offset) { auto i = Offset.Int(); if (i != 0 && std::abs(i) < 20) PtSizeOffset = (int)i; } if (!_stricmp(Which, "System")) { Status = GetConfigFont(LApp::CfgFontsSystemFont); if (!Status) { // read from system #if LGI_SDL #if defined(WIN32) Info.Face("Tahoma"); Info.PointSize(11); Status = true; #elif defined(MAC) Info.Face("LucidaGrande"); Info.PointSize(11); Status = true; #elif defined(LINUX) Info.Face("Sans"); Info.PointSize(11); Status = true; #else #error fix me #endif #elif defined WINNATIVE if (InfoOk) { // Copy the font metrics memcpy(&Info, &info.lfMessageFont, sizeof(Info)); Status = true; } else LgiTrace("%s:%i - Info not ok.\n", _FL); #elif defined(HAIKU) font_family family = {0}; font_style style = {0}; be_plain_font->GetFamilyAndStyle(&family, &style); Info.PointSize(be_plain_font->Size()); Info.Face(family); Status = true; #elif defined __GTK_H__ Info.Face(DefFont); Info.PointSize(DefSize); Status = true; #elif defined MAC #ifdef USE_CORETEXT Status = MacGetSystemFont(Info, kCTFontUIFontControlContent); #else Str255 Name; SInt16 Size; Style St; OSStatus e = GetThemeFont( kThemeSmallSystemFont, smSystemScript, Name, &Size, &St); if (e) printf("%s:%i - GetThemeFont failed with %i\n", __FILE__, __LINE__, (int)e); else { Info.Face(p2c(Name)); Info.PointSize(Size); Status = true; // printf("System=%s,%i\n", Info.Face(), Size); } #endif #endif } } else if (!stricmp(Which, "Menu")) { Status = GetConfigFont(LApp::CfgFontsMenuFont); if (!Status) { #if LGI_SDL LAssert(!"Impl me."); #elif defined WINNATIVE if (InfoOk) { // Copy the font metrics memcpy(&Info, &info.lfMenuFont, sizeof(Info)); Status = true; } #elif defined __GTK_H__ Info.Face(DefFont); Info.PointSize(DefSize); Status = true; #elif defined MAC && !LGI_COCOA #if USE_CORETEXT Status = MacGetSystemFont(Info, kCTFontUIFontMenuItem); #else Str255 Name; SInt16 Size; Style St; OSStatus e = GetThemeFont( kThemeMenuItemFont, smSystemScript, Name, &Size, &St); if (e) printf("%s:%i - GetThemeFont failed with %i\n", __FILE__, __LINE__, (int)e); else { Info.Face(p2c(Name)); Info.PointSize(Size); Status = true; } #endif #endif } } else if (!stricmp(Which, "Caption")) { Status = GetConfigFont(LApp::CfgFontsCaptionFont); if (!Status) { #if LGI_SDL LAssert(!"Impl me."); #elif defined WINNATIVE if (InfoOk) { // Copy the font metrics memcpy(&Info, &info.lfCaptionFont, sizeof(Info)); Status = true; } #elif defined LINUX #elif defined __GTK_H__ Info.Face(DefFont); Info.PointSize(DefSize-1); Status = true; #elif defined MAC && !LGI_COCOA #if USE_CORETEXT Status = MacGetSystemFont(Info, kCTFontUIFontToolbar); #else Str255 Name; SInt16 Size; Style St; OSStatus e = GetThemeFont( kThemeToolbarFont, smSystemScript, Name, &Size, &St); if (e) printf("%s:%i - GetThemeFont failed with %i\n", __FILE__, __LINE__, (int)e); else { Info.Face(p2c(Name)); Info.PointSize(Size); Status = true; } #endif #endif } } else if (!stricmp(Which, "Status")) { Status = GetConfigFont("Font-Status"); if (!Status) { #if LGI_SDL LAssert(!"Impl me."); #elif defined WINNATIVE if (InfoOk) { // Copy the font metrics memcpy(&Info, &info.lfStatusFont, sizeof(Info)); Status = true; } #elif defined __GTK_H__ Info.Face(DefFont); Info.PointSize(DefSize); Status = true; #elif defined MAC && !LGI_COCOA #if USE_CORETEXT Status = MacGetSystemFont(Info, kCTFontUIFontSystemDetail); #else Str255 Name; SInt16 Size; Style St; OSStatus e = GetThemeFont( kThemeToolbarFont, smSystemScript, Name, &Size, &St); if (e) printf("%s:%i - GetThemeFont failed with %i\n", __FILE__, __LINE__, (int)e); else { Info.Face(p2c(Name)); Info.PointSize(Size); Status = true; } #endif #endif } } else if (!stricmp(Which, "Small")) { Status = GetConfigFont(LApp::CfgFontsSmallFont); if (!Status) { #if LGI_SDL LAssert(!"Impl me."); #elif defined WINNATIVE if (InfoOk) { // Copy the font metrics memcpy(&Info, &info.lfSmCaptionFont, sizeof(Info)); if (LGetOs() == LGI_OS_WIN9X && _wcsicmp(Info.lfFaceName, L"ms sans serif") == 0) { SetFace("Arial"); } // Make it a bit smaller than the system font Info.lfHeight = WinPointToHeight(WinHeightToPoint(info.lfMessageFont.lfHeight)-1); Info.lfWeight = FW_NORMAL; Status = true; } #elif defined __GTK_H__ Info.Face(DefFont); Info.PointSize(DefSize-1); Status = true; #elif defined MAC #if USE_CORETEXT Status = MacGetSystemFont(Info, kCTFontUIFontSmallSystem); #else Str255 Name; SInt16 Size; Style St; OSStatus e = GetThemeFont( kThemeSmallSystemFont, smSystemScript, Name, &Size, &St); if (e) printf("%s:%i - GetThemeFont failed with %i\n", __FILE__, __LINE__, (int)e); else { Info.Face(p2c(Name)); Info.PointSize(Size - 2); Status = true; } #endif #endif } } else if (!stricmp(Which, "Fixed")) { Status = GetConfigFont(LApp::CfgFontsMonoFont); if (!Status) { #if LGI_SDL LAssert(!"Impl me."); #elif defined WINNATIVE // SetFace("Courier New"); SetFace("Consolas"); Info.lfHeight = WinPointToHeight(10); Status = true; #elif defined(HAIKU) font_family family = {0}; font_style style = {0}; be_fixed_font->GetFamilyAndStyle(&family, &style); Info.PointSize(be_fixed_font->Size()); Info.Face(family); Status = true; #elif defined __GTK_H__ Info.Face("Courier New"); Info.PointSize(DefSize); Status = true; #elif defined MAC Status = MacGetSystemFont(Info, kCTFontUIFontUserFixedPitch); #else #warning "Impl me" #endif } } else { LAssert(!"Invalid param supplied."); } if (Status && PtSizeOffset) SetPointSize(GetPointSize() + PtSizeOffset); // printf("GetSystemFont(%s)=%i %s,%i\n", Which, Status, Info.Face(), Info.PointSize()); return Status; } bool LFontType::GetFromRef(OsFont Handle) { #if defined WIN32 return GetObject(Handle, sizeof(Info), &Info) == sizeof(Info); #else // FIXME return false; #endif } LFont *LFontType::Create(LSurface *pSurface) { LFont *New = new LFont; if (New) { if (!New->Create(this, pSurface)) { DeleteObj(New); } if (New) LAssert(New->GetHeight() > 0); } return New; } diff --git a/src/common/Gdc2/Font/StringLayout.cpp b/src/common/Gdc2/Font/StringLayout.cpp --- a/src/common/Gdc2/Font/StringLayout.cpp +++ b/src/common/Gdc2/Font/StringLayout.cpp @@ -1,627 +1,633 @@ #include "lgi/common/Lgi.h" #include "lgi/common/StringLayout.h" #define DEBUG_PROFILE_LAYOUT 0 #define DEBUG_LAYOUT 0 static char White[] = " \t\r\n"; //////////////////////////////////////////////////////////////////////////////////// void LLayoutString::Set(int LineIdx, int FixX, int YPx, LLayoutRun *Lr, ssize_t Start) { Line = LineIdx; Src = Lr; Fx = FixX; y = YPx; Offset = Start; LCss::ColorDef Fill = Src->Color(); if (Fill.Type == LCss::ColorRgb) Fore.Set(Fill.Rgb32, 32); else if (Fill.Type != LCss::ColorTransparent) Fore = LColour(L_TEXT); Fill = Src->BackgroundColor(); if (Fill.Type == LCss::ColorRgb) Back.Set(Fill.Rgb32, 32); } //////////////////////////////////////////////////////////////////////////////////// LStringLayout::LStringLayout(LFontCache *fc) { FontCache = fc; Wrap = false; AmpersandToUnderline = false; Debug = false; Empty(); } LStringLayout::~LStringLayout() { Empty(); } void LStringLayout::Empty() { Min.Zero(); Max.Zero(); MinLines = 0; Bounds.ZOff(-1, -1); Strs.DeleteObjects(); Text.DeleteObjects(); } bool LStringLayout::Add(const char *Str, LCss *Style) { if (!Str) return false; if (AmpersandToUnderline) { LLayoutRun *r; for (const char *s = Str; *s; ) { const char *e = s; // Find '&' or end of string while (*e) { if (e[0] == '&') break; e++; } if (e > s) { // Add text before '&' r = new LLayoutRun(Style); r->Text.Set(s, e - s); Text.Add(r); } if (!*e) break; // End of string if (e[0] == '&' && e[1] == '&') { // '&' literal... r = new LLayoutRun(Style); r->Text.Set("&", 1); Text.Add(r); s = e + 2; } else { // Add '&'ed char r = new LLayoutRun(Style); r->TextDecoration(LCss::TextDecorUnderline); s = e + 1; // Skip the '&' itself LUtf8Ptr p(s); // Find the end of the next Unicode char p++; if ((const char*)p.GetPtr() == s) break; // No more text: exit r->Text.Set(s, (const char*)p.GetPtr()-s); Text.Add(r); s = (const char*) p.GetPtr(); } } } else // No parsing required { LLayoutRun *r = new LLayoutRun(Style); r->Text = Str; Text.Add(r); } return true; } uint32_t LStringLayout::NextChar(char *s) { ssize_t Len = 0; while (s[Len] && Len < 6) Len++; return LgiUtf8To32((uint8_t*&)s, Len); } uint32_t LStringLayout::PrevChar(char *s) { if (IsUtf8_Lead(*s) || IsUtf8_1Byte(*s)) { s--; ssize_t Len = 1; while (IsUtf8_Trail(*s) && Len < 6) { s--; Len++; } return LgiUtf8To32((uint8_t*&)s, Len); } return 0; } LFont *LStringLayout::GetBaseFont() { return FontCache && FontCache->GetDefaultFont() ? FontCache->GetDefaultFont() : LSysFont; } void LStringLayout::SetBaseFont(LFont *f) { if (FontCache) FontCache->SetDefaultFont(f); } LFont *LStringLayout::GetFont() { if (Strs.Length() == 0) return NULL; return Strs[0]->GetFont(); } typedef LArray LayoutArray; // Pre-layout min/max calculation void LStringLayout::DoPreLayout(int32 &MinX, int32 &MaxX) { MinX = 0; MaxX = 0; LFont *f = GetBaseFont(); if (!Text.Length() || !f) return; LArray Lines; int Line = 0; for (auto Run: Text) { char *s = Run->Text; LFont *f = FontCache ? FontCache->GetFont(Run) : LSysFont; LAssert(f != NULL); char *Start = s; while (*s) { if (*s == '\n') { Lines[Line++].Add(new LLayoutString(f, Start, s - Start)); Start = s + 1; } s++; } if (s > Start) { Lines[Line].Add(new LLayoutString(f, Start, s - Start)); } s = Run->Text; while (*s) { while (*s && strchr(White, *s)) s++; char *e = s; while (*e) { uint32_t c = NextChar(e); if (c == 0) break; if (e > s && LGI_BreakableChar(c)) break; char *cur = e; e = LSeekUtf8(e, 1); if (e == cur) // sanity check... { LAssert(!"LSeekUtf8 broke."); break; } } if (e == s) break; LDisplayString d(f, s, (int) (e - s)); MinX = MAX(d.X(), MinX); s = e; } } for (unsigned i=0; iFX(); } int LineX = Fx >> LDisplayString::FShift; MaxX = MAX(MaxX, LineX); Ln.DeleteObjects(); } } struct Break { LLayoutRun *Run; ssize_t Bytes; void Set(LLayoutRun *r, ssize_t b) { Run = r; Bytes = b; } }; #define GotoNextLine() \ LineFX = 0; \ MinLines++; \ y += LineHeight; \ StartLine = Strs.Length(); \ Breaks.Empty(); // Create the lines from text bool LStringLayout::DoLayout(int Width, int MinYSize, bool DebugLog) { #if DEBUG_PROFILE_LAYOUT LProfile Prof("LStringLayout::DoLayout"); char Buf[1024]; int Ch = 0; // Prof.HideResultsIfBelow(100); #endif // Empty Min.x = Max.x = 0; Min.y = Max.y = 0; MinLines = 0; Strs.DeleteObjects(); // Param validation LFont *f = GetBaseFont(); if (!f || !Text.Length() || Width <= 0) { auto fnt = f ? f : LSysFont; Min.y = Max.y = MAX(fnt ? fnt->GetHeight() : 0, MinYSize); return false; } // Loop over input int y = 0; int LineFX = 0; int LineHeight = 0; int Shift = LDisplayString::FShift; // By definition these potential break points are all // on the same line. Clear the array on each new line. LArray Breaks; /// Index of the display string starting the line ssize_t StartLine = 0; /// There is alway one line of text... even if it's empty MinLines = 1; #if DEBUG_LAYOUT if (Debug) LgiTrace("Text.Len=%i\n", Text.Length()); #endif for (auto Run: Text) { char *Start = Run->Text; + if (!Start) + { + LgiTrace("%s:%i - Empty text.\n", _FL); + break; + } + LUtf8Ptr s(Start); #if DEBUG_LAYOUT if (Debug) LgiTrace(" Run='%s' %p\n", s.GetPtr(), Run); #endif #if DEBUG_PROFILE_LAYOUT Prof.Add(Buf+Ch); Ch += sprintf(Buf+Ch, "[%i] Run = '%.*s'\n", (int) (Run - Text.AddressOf()), (int) max(20, Run->Text.Length()), s.GetPtr()); #endif while (s) { LUtf8Ptr e(s); ssize_t StartOffset = (char*)s.GetPtr() - Start; #if DEBUG_LAYOUT if (Debug) LgiTrace(" Breaks: "); #endif for (uint32_t Ch; (Ch = e); e++) { if (Ch == '\n') break; if ((char*)e.GetPtr() > Start && LGI_BreakableChar(Ch)) { ssize_t Pos = (char*)e.GetPtr() - Start; #if DEBUG_LAYOUT if (Debug) LgiTrace("%i, ", (int)Pos); #endif Breaks.New().Set(Run, Pos); } } #if DEBUG_LAYOUT if (Debug) LgiTrace("\n"); #endif ssize_t Bytes = e - s; LFont *Fnt = NULL; if (FontCache) Fnt = FontCache->GetFont(Run); if (!Fnt) Fnt = f; #if DEBUG_PROFILE_LAYOUT Prof.Add("CreateStr"); #endif // Create a display string for the segment LLayoutString *n = new LLayoutString(Fnt, Bytes ? (char*)s.GetPtr() : (char*)"", Bytes ? (int)Bytes : 1); if (n) { n->Set(MinLines - 1, LineFX, y, Run, StartOffset); LineFX += n->FX(); // Do min / max size calculation Min.x = Min.x ? MIN(Min.x, LineFX) : LineFX; Max.x = MAX(Max.x, LineFX); if (Wrap && (LineFX >> Shift) > Width) { // If wrapping, work out the split point and the text is too long int PosPx = Width - (n->Fx >> Shift); ssize_t Chars = n->CharAt(PosPx); #if DEBUG_LAYOUT if (Debug) LgiTrace(" CharAt(%i)=%i\n", PosPx, (int)Chars); #endif if (Breaks.Length() > 0) { #if DEBUG_PROFILE_LAYOUT Prof.Add("WrapWithBreaks"); #endif // Break at previous word break Strs.Add(n); // Roll back till we get a break point that fits for (ssize_t i = Breaks.Length() - 1; i >= 0; i--) { Break &b = Breaks[i]; #if DEBUG_LAYOUT if (Debug) LgiTrace(" Testing Break %i: %p %i\n", i, b.Run, b.Bytes); #endif // Calc line width from 'StartLine' to 'Break[i]' int FixX = 0; LAutoPtr Broken; size_t k; for (k=StartLine; k(Strs[k]); if (!Ls) return false; if (b.Run == Ls->Src && b.Bytes >= Ls->Offset) { // Add just the part till the break if (Broken.Reset(new LLayoutString(Ls, b.Run->Text.Get() + Ls->Offset, b.Bytes - Ls->Offset))) FixX += Broken->FX(); break; } else // Add whole string FixX += Ls->FX(); } #if DEBUG_LAYOUT if (Debug) LgiTrace(" Len=%i of %i\n", FixX, Width); #endif if ((FixX >> Shift) <= Width || i == 0) { // Found a good fit... #if DEBUG_LAYOUT if (Debug) LgiTrace(" Found a fit\n"); #endif // So we want to keep 'StartLine' to 'k-1' as is... while (Strs.Length() > k) { delete Strs.Last(); Strs.PopLast(); } if (Broken) { // Then change out from 'k' onwards for 'Broken' LineHeight = MAX(LineHeight, Broken->Y()); Strs.Add(Broken.Release()); } LineFX = FixX; s = b.Run->Text.Get() + b.Bytes; ssize_t Idx = Text.IndexOf(b.Run); if (Idx < 0) { LAssert(0); return false; } Run = Text[Idx]; break; } } // Start pouring from the break pos... while (s && strchr(White, s)) s++; // Move to the next line... GotoNextLine(); continue; } else { #if DEBUG_PROFILE_LAYOUT Prof.Add("WrapNoBreaks"); #endif // Break at next word break e = s; e += Chars; for (uint32_t Ch; (Ch = e); e++) { if (LGI_BreakableChar(Ch)) break; } } LineFX -= n->FX(); DeleteObj(n); n = new LLayoutString(Fnt, (char*)s.GetPtr(), e - s); n->Set(MinLines - 1, LineFX, y, Run, (char*)s.GetPtr() - Start); LineHeight = MAX(LineHeight, n->Y()); GotoNextLine(); } #if DEBUG_PROFILE_LAYOUT Prof.Add("Bounds"); #endif LRect Sr(0, 0, n->X()-1, n->Y()-1); Sr.Offset(n->Fx >> Shift, n->y); if (Strs.Length()) Bounds.Union(&Sr); else Bounds = Sr; LineHeight = MAX(LineHeight, Sr.Y()); Strs.Add(n); } if (e == '\n') { s = ++e; #if DEBUG_PROFILE_LAYOUT Prof.Add("NewLine"); #endif GotoNextLine(); } else s = e; } } #if DEBUG_PROFILE_LAYOUT Prof.Add("Post"); #endif Min.x = (Min.x + LDisplayString::FScale - 1) >> LDisplayString::FShift; Min.y = LineHeight * MinLines; if (Min.y < MinYSize) Min.y = MinYSize; Max.x = (Max.x + LDisplayString::FScale - 1) >> LDisplayString::FShift; Max.y = y + LineHeight; if (Max.y < MinYSize) Max.y = MinYSize; if (DebugLog) LgiTrace("CreateTxtLayout(%i) min=%i,%i max=%i,%i\n", Width, Min.x, Min.y, Max.x, Min.y); return true; } void LStringLayout::Paint( LSurface *pDC, LPoint pt, LColour Back, LRect &rc, bool Enabled, bool Focused) { if (!pDC) return; #ifdef WINNATIVE LRegion Rgn(rc); #else // Fill the background... if (!Back.IsTransparent()) { pDC->Colour(Back); pDC->Rectangle(&rc); } int Shift = LDisplayString::FShift; #endif LColour FocusFore = LColour(L_FOCUS_SEL_FORE); // Draw all the text for (auto ds: Strs) { LLayoutString *s = dynamic_cast(ds); LFont *f = s->GetFont(); LColour Bk = s->Back.IsTransparent() ? Back : s->Back; #ifdef WINNATIVE int y = pt.y + s->y; LRect r(pt.x + s->Fx, y, pt.x + s->Fx + s->FX() - 1, y + s->Y() - 1); Rgn.Subtract(&r); f->Transparent(Bk.IsTransparent()); #else f->Transparent(true); #endif // LgiTrace("'%S' @ %i,%i\n", (const char16*)(**ds), r.x1, r.y1); if (Enabled) { f->Colour(Focused ? FocusFore : s->Fore, Bk); #ifdef WINNATIVE s->Draw(pDC, r.x1, r.y1, &r); #else LPoint k((pt.x << Shift) + s->Fx, (pt.y + s->y) << Shift); s->FDraw(pDC, k.x, k.y); #endif } else { f->Transparent(Bk.IsTransparent()); f->Colour(LColour(L_LIGHT), Bk); #ifdef WINNATIVE s->Draw(pDC, r.x1+1, r.y1+1, &r); #else LPoint k(((pt.x+1) << Shift) + s->Fx, (pt.y + 1 + s->y) << Shift); s->FDraw(pDC, k.x, k.y); #endif f->Transparent(true); f->Colour(LColour(L_LOW), LColour(L_MED)); #ifdef WINNATIVE s->Draw(pDC, r.x1, r.y1, &r); #else s->FDraw(pDC, (pt.x << Shift) + s->Fx, (pt.y + s->y) << Shift); #endif } } #ifdef WINNATIVE // Fill any remaining area with background... if (!Back.IsTransparent()) { pDC->Colour(Back); for (LRect *r=Rgn.First(); r; r=Rgn.Next()) pDC->Rectangle(r); } #endif } diff --git a/src/common/Gdc2/GdcCommon.cpp b/src/common/Gdc2/GdcCommon.cpp --- a/src/common/Gdc2/GdcCommon.cpp +++ b/src/common/Gdc2/GdcCommon.cpp @@ -1,1217 +1,1219 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" +#include "ControlLook.h" + ///////////////////////////////////////////////////////////////////////////// // Mem ops void Add64(ulong *DestH, ulong *DestL, ulong SrcH, ulong SrcL) { *DestH += SrcH + (*DestL & SrcL & 0x80000000) ? 1 : 0; *DestL += SrcL; } void MemSet(void *d, int c, uint s) { if (d && s > 0) memset(d, c, s); } void MemCpy(void *d, void *s, uint l) { if (d && s && l > 0) memcpy(d, s, l); } void MemAnd(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ &= *S++; } } } void MemXor(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ ^= *S++; } } } void MemOr(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ |= *S++; } } } ////////////////////////////////////////////////////////////////////// bool LFindBounds(LSurface *pDC, LRect *rc) { if (!pDC || ! rc) return false; LAssert(pDC->GetColourSpace() == System32BitColourSpace); // Move top border down to image while (rc->y1 < rc->y2) { System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y1]; if (!p) return false; p += rc->x1; System32BitPixel *e = p + rc->X(); bool IsTrans = true; while (p < e) { if (p->a != 0) { IsTrans = false; break; } p++; } if (IsTrans) rc->y1++; else break; } // Move bottom border up to image while (rc->y2 >= rc->y1) { System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y2]; if (!p) return false; p += rc->x1; System32BitPixel *e = p + rc->X(); bool IsTrans = true; while (p < e) { if (p->a != 0) { IsTrans = false; break; } p++; } if (IsTrans) rc->y2--; else break; } // Do the left and right edges too int x1 = rc->x2; int x2 = rc->x1; for (int y=rc->y1; y<=rc->y2; y++) { System32BitPixel *p = (System32BitPixel*)(*pDC)[y]; if (!p) return false; System32BitPixel *px1 = p + rc->x1; System32BitPixel *px2 = p + rc->x2; while (px1 < px2 && px1->a == 0) px1++; x1 = MIN(x1, (int) (px1 - p)); while (px2 >= px1 && px2->a == 0) px2--; x2 = MAX(x2, (int) (px2 - p)); } rc->x1 = x1; rc->x2 = x2; if (rc->Valid()) return true; // No data? rc->ZOff(-1, -1); return false; } ////////////////////////////////////////////////////////////////////// // Drawing functions void LDrawBox(LSurface *pDC, LRect &r, bool Sunken, bool Fill) { if (Fill) { pDC->Colour(LColour(L_MED)); pDC->Rectangle(r.x1+1, r.y1+1, r.x2-1, r.y2-1); } pDC->Colour((Sunken) ? LColour(L_LIGHT) : LColour(L_LOW)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour((Sunken) ? LColour(L_LOW) : LColour(L_LIGHT)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); } void LWideBorder(LSurface *pDC, LRect &r, LEdge Type) { if (!pDC) return; COLOUR Old = pDC->Colour(); LColour VLow = LColour(L_SHADOW); LColour Low = LColour(L_LOW); LColour High = LColour(L_HIGH); LColour VHigh = LColour(L_LIGHT); switch (Type) { case EdgeXpSunken: { // XP theme pDC->Colour(Low); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(VLow); pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2); pDC->Colour(High); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeXpRaised: { pDC->Colour(VHigh); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(High); pDC->Line(r.x1+1, r.y1+1, r.x2-1, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-1); pDC->Colour(Low); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VLow); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeXpChisel: { pDC->Colour(Low); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2); pDC->Colour(Low); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeWin7Sunken: case EdgeWin7FocusSunken: { bool Focus = Type == EdgeWin7FocusSunken; // Win7 theme LColour Med(L_MED), Ws(L_WORKSPACE); LColour Mixer = Med.GetGray() >= 128 ? LColour::Black : LColour::White; LColour TopLeft, RightBottom; if (Focus) { LColour Focus(L_FOCUS_SEL_BACK); TopLeft = Med.Mix(Focus, 0.4f); RightBottom = Ws.Mix(Focus, 0.2f); } else { TopLeft = Med.Mix(Mixer, 0.4f); RightBottom = Ws.Mix(Mixer, 0.2f); } // Top corners pDC->Colour(Med); pDC->Set(r.x1, r.y1); pDC->Set(r.x2, r.y1); pDC->Colour(Ws.Mix(TopLeft)); pDC->Set(r.x1+1, r.y1+1); pDC->Set(r.x2-1, r.y1+1); pDC->Set(r.x1+1, r.y2-1); // Edges pDC->Colour(TopLeft); pDC->Line(r.x1, r.y1+1, r.x1, r.y2-1); // left pDC->Line(r.x1+1, r.y1, r.x2-1, r.y1); // top // Bottom edge pDC->Colour(RightBottom); pDC->Line(r.x1+1, r.y2, r.x2-1, r.y2); // bottom pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1); // right // Inner workspace rect pDC->Colour(Ws); pDC->Line(r.x1+2, r.y1+1, r.x2-2, r.y1+1); // top pDC->Line(r.x1+1, r.y1+2, r.x1+1, r.y2-2); // left pDC->Line(r.x2-1, r.y1+2, r.x2-1, r.y2-2); // right pDC->Line(r.x1+2, r.y2-1, r.x2-1, r.y2-1); // bottom break; } default: { return; } } r.Inset(2, 2); pDC->Colour(Old); } void LThinBorder(LSurface *pDC, LRect &r, LEdge Type) { if (!pDC) return; COLOUR Old = pDC->Colour(); switch (Type) { case EdgeXpSunken: case EdgeWin7FocusSunken: case EdgeWin7Sunken: { pDC->Colour(LColour(L_LIGHT)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour(LColour(L_LOW)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); r.Inset(1, 1); break; } case EdgeXpRaised: { pDC->Colour(LColour(L_LOW)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour(LColour(L_LIGHT)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); r.Inset(1, 1); break; } default: { LAssert(0); break; } } pDC->Colour(Old); } void LFlatBorder(LSurface *pDC, LRect &r, int Width) { pDC->Colour(LColour(L_MED)); if (Width < 1 || r.X() < (2 * Width) || r.Y() < (2 * Width)) { pDC->Rectangle(&r); r.ZOff(-1, -1); } else { pDC->Rectangle(r.x1, r.y1, r.x2, r.y1+Width-1); pDC->Rectangle(r.x1, r.y2-Width+1, r.x2, r.y2); pDC->Rectangle(r.x1, r.y1+Width, r.x1+Width-1, r.y2-Width); pDC->Rectangle(r.x2-Width+1, r.y1+Width, r.x2, r.y2-Width); r.Inset(Width, Width); } } void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray &Stops) { int CurStop = 0; LColourStop *This = Stops.Length() > CurStop ? &Stops[CurStop] : 0; CurStop++; LColourStop *Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0; int Limit = Vert ? r.Y() : r.X(); for (int n=0; nPos) { // Just this c = This->Colour; } else if (p > This->Pos && p < Next->Pos) { // Interpolate between this and next float d = Next->Pos - This->Pos; float t = (Next->Pos - p) / d; float n = (p - This->Pos) / d; uint8_t r = (uint8_t) ((This->Colour.r() * t) + (Next->Colour.r() * n)); uint8_t g = (uint8_t) ((This->Colour.g() * t) + (Next->Colour.g() * n)); uint8_t b = (uint8_t) ((This->Colour.b() * t) + (Next->Colour.b() * n)); uint8_t a = (uint8_t) ((This->Colour.a() * t) + (Next->Colour.a() * n)); c.Rgb(r, g, b, a); } else if (p >= Next->Pos) { // Get next colour stops This = Stops.Length() > CurStop ? &Stops[CurStop] : 0; CurStop++; Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0; goto DoStop; } pDC->Colour(c); if (Vert) pDC->Line(r.x1, r.y1 + n, r.x2, r.y1 + n); else pDC->Line(r.x1 + n, r.y1, r.x1 + n, r.y2); } } } ////////////////////////////////////////////////////////////////////////////////// // Other Gdc Stuff LSurface *ConvertDC(LSurface *pDC, int Bits) { LSurface *pNew = new LMemDC; if (pNew && pNew->Create(pDC->X(), pDC->Y(), LBitsToColourSpace(Bits))) { pNew->Blt(0, 0, pDC); DeleteObj(pDC); return pNew; } return pDC; } LColour GdcMixColour(LColour c1, LColour c2, float HowMuchC1) { float HowMuchC2 = 1.0f - HowMuchC1; uint8_t r = (uint8_t) ((c1.r()*HowMuchC1) + (c2.r()*HowMuchC2)); uint8_t g = (uint8_t) ((c1.g()*HowMuchC1) + (c2.g()*HowMuchC2)); uint8_t b = (uint8_t) ((c1.b()*HowMuchC1) + (c2.b()*HowMuchC2)); uint8_t a = (uint8_t) ((c1.a()*HowMuchC1) + (c2.a()*HowMuchC2)); return LColour(r, g, b, a); } COLOUR CBit(int DstBits, COLOUR c, int SrcBits, LPalette *Pal) { if (SrcBits == DstBits) { return c; } else { switch (SrcBits) { case 8: { GdcRGB Grey, *p = 0; if (!Pal || !(p = (*Pal)[c])) { Grey.r = Grey.g = Grey.b = c & 0xFF; p = &Grey; } switch (DstBits) { case 16: { return Rgb16(p->r, p->g, p->b); } case 24: { return Rgb24(p->r, p->g, p->b); } case 32: { return Rgb32(p->r, p->g, p->b); } } break; } case 15: { int R = R15(c); int G = G15(c); int B = B15(c); R = R | (R >> 5); G = G | (G >> 5); B = B | (B >> 5); switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb24(R, G, B)); } break; } case 16: { return Rgb16(R, G, B); } case 24: { return Rgb24(R, G, B); } case 32: { return Rgb32(R, G, B); } } break; } case 16: { /* int R = ((c>>8)&0xF8) | ((c>>13)&0x7); int G = ((c>>3)&0xFC) | ((c>>9)&0x3); int B = ((c<<3)&0xF8) | ((c>>2)&0x7); */ int R = (c >> 8) & 0xF8; int G = (c >> 3) & 0xFC; int B = (c << 3) & 0xF8; switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb24(R, G, B)); } break; } case 15: { return Rgb16To15(c); } case 24: { return Rgb24(R, G, B); } case 32: { return Rgb32(R, G, B); } } break; } case 24: { switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(c); } break; } case 15: { return Rgb24To15(c); } case 16: { if (LGetOs() == LGI_OS_WIN32 || LGetOs() == LGI_OS_WIN64) { return Rgb24To16(c); } int r = MAX(R24(c) - 1, 0) >> 3; int g = MAX(G24(c) - 1, 0) >> 2; int b = MAX(B24(c) - 1, 0) >> 3; return (r << 11) | (g << 5) | (b); } case 24: case 48: { return c; } case 32: case 64: { return Rgb24To32(c); } default: LAssert(0); break; } break; } case 32: { switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb32To24(c)); } break; } case 15: { return Rgb32To15(c); } case 16: { return Rgb32To16(c); } case 24: { return Rgb32To24(c); } } break; } } } return c; } ///////////////////////////////////////////////////////////////////////////// const char *LColourSpaceToString(LColourSpace cs) { #define CS_STR_BUF 4 static int Cur = 0; static char Buf[CS_STR_BUF][16]; static const char *CompTypes[] = { "N", // None "I", // Index "R", // Red "G", // Green "B", // Blue "A", // Alpha "X", // Pad "H", // Hue "S", // Sat "L", // Lum "C", // Cyan "M", // Magenta "Y", // Yellow "B", // Black "?", "?", }; char *start = Buf[Cur++], *s = start; int total = 0; bool first = true; if (Cur >= CS_STR_BUF) Cur = 0; *s++ = 'C'; *s++ = 's'; // printf("Converting Cs to String: 0x%x\n", cs); for (int i=3; i>=0; i--) { int c = (((uint32_t)cs) >> (i << 3)) & 0xff; // printf(" c[%i] = 0x%x\n", i, c); if (c) { LComponentType type = (LComponentType)(c >> 4); int size = c & 0xf; if (first) { *s++ = CompTypes[type][0]; first = false; } else { *s++ = tolower(CompTypes[type][0]); } total += size ? size : 16; } } s += sprintf_s(s, 4, "%i", total); return start; } int LColourSpaceChannels(LColourSpace Cs) { int Channels = 0; while (Cs) { uint8_t c = Cs & 0xff; if (!c) break; Channels++; ((int&)Cs) >>= 8; } return Channels; } bool LColourSpaceHasAlpha(LColourSpace Cs) { while (Cs) { uint8_t c = Cs & 0xff; LComponentType type = (LComponentType)(c >> 4); if (type == CtAlpha) return true; ((int&)Cs) >>= 8; } return false; } int LColourSpaceToBits(LColourSpace ColourSpace) { uint32_t c = ColourSpace; int bits = 0; while (c) { if (c & 0xf0) { int n = c & 0xf; bits += n ? n : 16; } c >>= 8; } return bits; } LColourSpace LStringToColourSpace(const char *c) { if (!c) return CsNone; if (!_strnicmp(c, "Cs", 2)) { // "Cs" style colour space c += 2; const char *n = c; while (*n && !IsDigit(*n)) n++; int Depth = ::atoi(n); if (!_strnicmp(c, "Index", 5)) { if (Depth == 8) return CsIndex8; } else { LArray Comp; while (*c) { switch (tolower(*c)) { case 'r': Comp.Add(CtRed); break; case 'g': Comp.Add(CtGreen); break; case 'b': Comp.Add(CtBlue); break; case 'a': Comp.Add(CtAlpha); break; case 'x': Comp.Add(CtPad); break; case 'c': Comp.Add(CtCyan); break; case 'm': Comp.Add(CtMagenta); break; case 'y': Comp.Add(CtYellow); break; case 'k': Comp.Add(CtBlack); break; case 'h': Comp.Add(CtHue); break; case 'l': Comp.Add(CtLuminance); break; case 's': Comp.Add(CtSaturation); break; } c++; } LColourSpaceBits a; ZeroObj(a); if (Comp.Length() == 3) { a[0].Type(Comp[0]); a[1].Type(Comp[1]); a[2].Type(Comp[2]); if (Depth == 24) { a[0].Size(8); a[1].Size(8); a[2].Size(8); } else if (Depth == 16) { a[0].Size(5); a[1].Size(6); a[2].Size(5); } else if (Depth == 15) { a[0].Size(5); a[0].Size(5); a[0].Size(5); } else if (Depth == 48) { a[0].Size(0); a[1].Size(0); a[2].Size(0); } else return CsNone; } else if (Comp.Length() == 4) { a[0].Type(Comp[0]); a[1].Type(Comp[1]); a[2].Type(Comp[2]); a[3].Type(Comp[3]); if (Depth == 32) { a[0].Size(8); a[1].Size(8); a[2].Size(8); a[3].Size(8); } else if (Depth == 64) { a[0].Size(0); a[1].Size(0); a[2].Size(0); a[3].Size(0); } else return CsNone; } LColourSpace Cs = (LColourSpace)a.All; return Cs; } } else if (!_strnicmp(c, "System", 6)) { // "System" colour space c += 6; int Depth = ::atoi(c); if (Depth) return LBitsToColourSpace(Depth); } return CsNone; } LColourSpace LBitsToColourSpace(int Bits) { switch (Bits) { case 8: return CsIndex8; case 15: return System15BitColourSpace; case 16: return System16BitColourSpace; case 24: return System24BitColourSpace; case 32: return System32BitColourSpace; default: LAssert(!"Unknown colour space."); break; } return CsNone; } bool LColourSpaceTest() { union { uint8_t b4[4]; uint32_t u32; LBgrx32 bgrx32; LXrgb32 xrgb32; LRgba32 rgba32; }; b4[0] = 1; b4[1] = 2; b4[2] = 3; b4[3] = 4; #if 0 // def LGI_SDL printf("LeastSigBit=%i, LeastSigByte=%i, u32=%08x\n", LeastSigBit, LeastSigByte, u32); printf("bgrx32=%i,%i,%i,%i\n", bgrx32.b, bgrx32.g, bgrx32.r, bgrx32.pad); printf("xrgb32=%i,%i,%i,%i\n", xrgb32.pad, xrgb32.r, xrgb32.g, xrgb32.b); #endif if (bgrx32.b != 1 || bgrx32.pad != 4 || xrgb32.pad != 1 || xrgb32.b != 4) { LAssert(!"32bit colour space byte ordering wrong. Flip the value of LEAST_SIG_BYTE_FIRST"); return false; } union { uint16 u16; LRgb16 rgb16; }; rgba32.r = 0xff; rgba32.g = 0; rgba32.b = 0; rgba32.a = 0; u16 = 0xf800; #if 0 // def LGI_SDL printf("rgba32=%i,%i,%i,%i or %08x\n", rgba32.r, rgba32.g, rgba32.b, rgba32.a, u32); printf("rgb16=%i,%i,%i or %04x\n", rgb16.r, rgb16.g, rgb16.b, u16); #endif if (rgb16.r != 0x1f || rgb16.b != 0x0) { LAssert(!"16bit colour space bit ordering wrong. Flip the value of LEAST_SIG_BIT_FIRST"); return false; } return true; } //////////////////////////////////////////////////////////////////////// LSurface *LInlineBmp::Create(uint32_t TransparentPx) { LSurface *pDC = new LMemDC; if (pDC->Create(X, Y, System32BitColourSpace, LSurface::SurfaceRequireExactCs)) { LBmpMem Src, Dst; Src.Base = (uint8_t*)Data; Src.Line = X * Bits >> 3; Src.x = X; Src.y = Y; switch (Bits) { case 8: Src.Cs = CsIndex8; break; case 15: Src.Cs = CsRgb15; break; case 16: Src.Cs = CsRgb16; break; case 24: Src.Cs = CsRgb24; break; case 32: Src.Cs = CsRgba32; break; default: Src.Cs = CsNone; break; } Dst.Base = (*pDC)[0]; Dst.Line = pDC->GetRowStep(); Dst.x = pDC->X(); Dst.y = pDC->Y(); Dst.Cs = pDC->GetColourSpace(); LRopUniversal(&Dst, &Src, false); if (TransparentPx != 0xffffffff) { for (int y=0; y> 3) { case 1: { for (int x=0; xr == r && px->g == g && px->b == b) { *d = 0; } d++; } break; } case 4: { for (int x=0; x %s\n", _FL, LColourSpaceToString(SrcCs), LColourSpaceToString(DstCs)); LAssert(!"Unsupported pixel conversion."); return false; } } return true; } /// Universal bit blt method bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite) { if (!Dst || !Src) return false; // Work out conversion area... int Cx = MIN(Dst->x, Src->x); int Cy = MIN(Dst->y, Src->y); // Size of src and dst pixels: int SrcBits = LColourSpaceToBits(Src->Cs); int DstBits = LColourSpaceToBits(Dst->Cs); if (Dst->Cs == Src->Cs && !Composite) { // No conversion idiom uint8_t *d = Dst->Base; uint8_t *s = Src->Base; int BytesPerPx = (SrcBits + 7) / 8; int Bytes = BytesPerPx * Cx; for (int y=0; yLine; s += Src->Line; } return true; } if (SrcBits > 8 && DstBits > 8) { uint8_t *d = Dst->Base; uint8_t *s = Src->Base; for (int y=0; yCs, s, Src->Cs, Cx, Composite)) return false; d += Dst->Line; s += Src->Line; } return true; } else { } // LAssert(!"Unsupported pixel conversion."); return false; } LPoint LScreenDpi() { static LPoint Dpi; #if LGI_COCOA if (!Dpi.x) { NSScreen *screen = [NSScreen mainScreen]; NSDictionary *description = [screen deviceDescription]; NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; CGSize displayPhysicalSize = CGDisplayScreenSize( [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); Dpi.x = (int) ((displayPixelSize.width / displayPhysicalSize.width) * 25.4f); Dpi.y = (int) ((displayPixelSize.height / displayPhysicalSize.height) * 25.4f); } #elif defined(__GTK_H__) if (!Dpi.x) { auto dpi = Gtk::gdk_screen_get_resolution(Gtk::gdk_screen_get_default()); if (dpi > 0) Dpi.Set((int)dpi, (int)dpi); } #elif defined(HAIKU) if (!Dpi.x) { - BScreen screen(Window()); - monitor_info info; - if (screen.GetMonitorInfo(&info) == B_OK) - { - Dpi.x = info.width / 2.54; - Dpi.y = info.height / 2.54; - } + double scaling = std::max(1.0f, be_plain_font->Size() / 12.0f); + Dpi.x = (int)(96.0 * scaling); + Dpi.y = Dpi.x; + // printf("scaling=%g dpi=%i\n", scaling, Dpi.x); } #elif defined(WINDOWS) if (!Dpi.x) { + auto dpi = GetDpiForWindow(GetDesktopWindow()); + Dpi.Set(dpi, dpi); + } SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); auto dpi = GetDpiForSystem(); Dpi.Set(dpi, dpi); } #endif if (Dpi.x <= 0) Dpi.Set(96, 96); return Dpi; } bool LMemDC::SwapRedAndBlue() { switch (GetColourSpace()) { #define ROP(cs) \ case Cs##cs: \ { \ for (int y=0; yr, p->b); \ p++; \ } \ break; \ } \ break; \ } ROP(Rgb24) ROP(Bgr24) ROP(Rgbx32) ROP(Bgrx32) ROP(Xrgb32) ROP(Xbgr32) ROP(Argb32) ROP(Abgr32) ROP(Rgba32) ROP(Bgra32) default: return false; } return true; } diff --git a/src/common/Gdc2/Surface.cpp b/src/common/Gdc2/Surface.cpp --- a/src/common/Gdc2/Surface.cpp +++ b/src/common/Gdc2/Surface.cpp @@ -1,2183 +1,2181 @@ /*hdr ** FILE: GdcPrim.cpp ** AUTHOR: Matthew Allen ** DATE: 1/3/97 ** DESCRIPTION: GDC v2.xx device independent primitives ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include #include "lgi/common/Gdc2.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Palette.h" #include "lgi/common/Variant.h" #define LINE_SOLID 0xFFFFFFFF #ifdef MAC #define REGISTER #else #define REGISTER register #endif void LSurface::Init() { OriginX = OriginY = 0; pMem = NULL; pAlphaDC = 0; Flags = 0; Clip.ZOff(-1, -1); pPalette = NULL; pApp = NULL; ColourSpace = CsNone; for (int i=0; iX(), pDC->Y(), pDC->GetColourSpace())) { Blt(0, 0, pDC); if (pDC->Palette()) { LPalette *Pal = new LPalette(pDC->Palette()); if (Pal) { Palette(Pal, true); } } } } LSurface::~LSurface() { #if defined(LINUX) && !defined(LGI_SDL) /* if (Cairo) { Gtk::cairo_destroy(Cairo); Cairo = 0; } */ #endif DrawOnAlpha(false); DeleteObj(pMem); DeleteObj(pAlphaDC); if (pPalette && (Flags & GDC_OWN_PALETTE)) { DeleteObj(pPalette); } if ( (Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; iSetSmoothingMode(AntiAlias() ? Gdiplus::SmoothingModeAntiAlias : Gdiplus::SmoothingModeNone); return GdiplusGfx; } Gdiplus::Color LSurface::GdiColour() { COLOUR col = Colour(); switch (GetBits()) { default: LAssert(!"Impl me."); // fall through case 32: return Gdiplus::Color(A32(col), R32(col), G32(col), B32(col)); case 24: return Gdiplus::Color(R24(col), G24(col), B24(col)); } } #endif LString LSurface::GetStr() { LString::Array s; s.SetFixedLength(false); s.New().Printf("Size(%ix%i)", X(), Y()); s.New().Printf("ColourSpace(%s)", LColourSpaceToString(ColourSpace)); s.New().Printf("IsScreen(%i)", IsScreen()); if (pAlphaDC) s.New().Printf("pAlphaDC(%ix%i,%s)", pAlphaDC->X(), pAlphaDC->Y(), LColourSpaceToString(pAlphaDC->GetColourSpace())); if (Clip.Valid()) s.New().Printf("Clip(%s)", Clip.GetStr()); s.New().Printf("Op(%i)", Op()); s.New().Printf("GetRowStep(" LPrintfSizeT ")", GetRowStep()); return LString(" ").Join(s); } LSurface *LSurface::SubImage(LRect r) { if (!pMem || !pMem->Base) return NULL; LRect clip = r; LRect bounds = Bounds(); clip.Intersection(&bounds); if (!clip.Valid()) return NULL; LAutoPtr s(new LSurface); if (!s) return NULL; int BytePx = pMem->GetBits() >> 3; s->pMem = new LBmpMem; s->pMem->Base = pMem->Base + (pMem->Line * clip.y1) + (BytePx * clip.x1); s->pMem->x = clip.X(); s->pMem->y = clip.Y(); s->pMem->Line = pMem->Line; s->pMem->Cs = pMem->Cs; s->pMem->Flags = 0; // Don't own memory. s->Clip = s->Bounds(); s->ColourSpace = pMem->Cs; s->Op(GDC_SET); return s.Release(); } template void SetAlphaPm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->r = Lut[s->r * a]; s->g = Lut[s->g * a]; s->b = Lut[s->b * a]; s->a = Lut[s->a * a]; s++; } } template void SetAlphaNpm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->a = Lut[s->a * a]; s++; } } bool LSurface::SetConstantAlpha(uint8_t Alpha) { bool HasAlpha = LColourSpaceHasAlpha(GetColourSpace()); if (!HasAlpha) return false; if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (pMem->PreMul()) { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaPm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } else { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaNpm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } } return true; } OsBitmap LSurface::GetBitmap() { #if WINNATIVE return hBmp; #else return NULL; #endif } OsPainter LSurface::Handle() { #if WINNATIVE return hDC; #elif defined(__GTK_H__) return NULL; #else return 0; #endif } uchar *LSurface::operator[](int y) { if (pMem && pMem->Base && y >= 0 && y < pMem->y) { return pMem->Base + (pMem->Line * y); } return 0; } void LSurface::Set(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); pApp->Set(); Update(GDC_BITS_CHANGE); } } COLOUR LSurface::Get(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); return pApp->Get(); } return (COLOUR)-1; } void LSurface::HLine(int x1, int x2, int y) { OrgXy(x1, y); OrgX(x2); if (x1 > x2) LSwap(x1, x2); if (x1 < Clip.x1) x1 = Clip.x1; if (x2 > Clip.x2) x2 = Clip.x2; if (x1 <= x2 && y >= Clip.y1 && y <= Clip.y2) { pApp->SetPtr(x1, y); if (LineBits == LINE_SOLID) { pApp->Rectangle(x2 - x1 + 1, 1); Update(GDC_BITS_CHANGE); } else { for (; x1 <= x2; x1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncX(); } Update(GDC_BITS_CHANGE); } } } void LSurface::VLine(int x, int y1, int y2) { OrgXy(x, y1); OrgY(y2); if (y1 > y2) LSwap(y1, y2); if (y1 < Clip.y1) y1 = Clip.y1; if (y2 > Clip.y2) y2 = Clip.y2; if (y1 <= y2 && x >= Clip.x1 && x <= Clip.x2) { pApp->SetPtr(x, y1); if (LineBits == LINE_SOLID) { pApp->VLine(y2 - y1 + 1); Update(GDC_BITS_CHANGE); } else { for (; y1 <= y2; y1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncY(); } Update(GDC_BITS_CHANGE); } } } void LSurface::Line(int x1, int y1, int x2, int y2) { if (x1 == x2) { VLine(x1, y1, y2); } else if (y1 == y2) { HLine(x1, x2, y1); } else if (Clip.Valid()) { OrgXy(x1, y1); OrgXy(x2, y2); // angled if (y1 > y2) { LSwap(y1, y2); LSwap(x1, x2); } LRect Bound(x1, y1, x2, y2); Bound.Normal(); if (!Bound.Overlap(&Clip)) return; double m = (double) (y2-y1) / (x2-x1); double b = (double) y1 - (m*x1); int xt = (int) (((double)Clip.y1-b)/m); int xb = (int) (((double)Clip.y2-b)/m); if (y1 < Clip.y1) { x1 = xt; y1 = Clip.y1; } if (y2 > Clip.y2) { x2 = xb; y2 = Clip.y2; } int X1, X2; if (x1 < x2) { X1 = x1; X2 = x2; } else { X1 = x2; X2 = x1; } if (X1 < Clip.x1) { if (X2 < Clip.x1) return; if (x1 < Clip.x1) { y1 = (int) (((double) Clip.x1 * m) + b); x1 = Clip.x1; } else { y2 = (int) (((double) Clip.x1 * m) + b); x2 = Clip.x1; } } if (X2 > Clip.x2) { if (X1 > Clip.x2) return; if (x1 > Clip.x2) { y1 = (int) (((double) Clip.x2 * m) + b); x1 = Clip.x2; } else { y2 = (int) (((double) Clip.x2 * m) + b); x2 = Clip.x2; } } int dx = abs(x2-x1); int dy = abs(y2-y1); int EInc, ENoInc, E, Inc = 1; if (dy < dx) { // flat if (x1 > x2) { LSwap(x1, x2); LSwap(y1, y2); } if (y1 > y2) { Inc = -1; } EInc = dy + dy; E = EInc - dx; ENoInc = E - dx; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; x1<=x2; x1++) { pApp->Set(); if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } else { for (; x1<=x2; x1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } } else { if (x1 > x2) { Inc = -1; } // steep EInc = dx + dx; E = EInc - dy; ENoInc = E - dy; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; y1<=y2; y1++) { pApp->Set(); if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } else { for (; y1<=y2; y1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } } Update(GDC_BITS_CHANGE); } } void LSurface::Circle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0)-1, (Gdiplus::REAL)(radius*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - (2 * radius)); int x = 0; int y = (int) radius; Set(cx, cy + y); Set(cx, cy - y); Set(cx + y, cy); Set(cx - y, cy); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; while (x < y) { Set(cx + x, cy + y); Set(cx - x, cy + y); Set(cx + x, cy - y); Set(cx - x, cy - y); Set(cx + y, cy + x); Set(cx - y, cy + x); Set(cx + y, cy - x); Set(cx - y, cy - x); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; } if (x == y) { Set(cx + x, cy + x); Set(cx - x, cy + x); Set(cx + x, cy - x); Set(cx - x, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::FilledCircle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0-1.0), (Gdiplus::REAL)(radius*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - 2 * radius); int x = 0; int y = (int) radius; // HLine(cx + y, cx - y, cy); if (d < 0) { d += 4 * x + 6; } else { HLine(cx, cx, cy + y); d += 4 * (x - y) + 10; y--; } while (x < y) { HLine(cx + y, cx - y, cy + x); if (x != 0) HLine(cx + y, cx - y, cy - x); if (d < 0) { d += 4 * x + 6; } else { HLine(cx + x, cx - x, cy + y); HLine(cx + x, cx - x, cy - y); d += 4 * (x - y) + 10; y--; } x++; } if (x == y) { HLine(cx + y, cx - y, cy + x); HLine(cx + y, cx - y, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::Box(LRect *a) { if (a) { LRect b = *a; b.Normal(); if (b.x1 != b.x2 && b.y1 != b.y2) { HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } else { Set(b.x1, b.y1); } } else { LRect b(0, 0, X()-1, Y()-1); HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } } void LSurface::Box(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Box(&a); } void LSurface::Rectangle(LRect *a) { LRect b; if (a) b = a; else b.ZOff(pMem->x-1, pMem->y-1); OrgRgn(b); b.Normal(); b.Bound(&Clip); if (b.Valid()) { pApp->SetPtr(b.x1, b.y1); pApp->Rectangle(b.X(), b.Y()); Update(GDC_BITS_CHANGE); } } void LSurface::Rectangle(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Rectangle(&a); } void LSurface::Ellipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0)-1, (Gdiplus::REAL)(radiusY*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif #define incx() x++, dxt += d2xt, t += dxt #define incy() y--, dyt += d2yt, t += dyt int x = 0, y = (int)radiusY; int a = (int)radiusX; if (a % 2 == 0) a--; int b = (int)radiusY; int xc = (int)Cx; int yc = (int)Cy; long a2 = (long)(a*a), b2 = (long)(b*b); long crit1 = -(a2/4 + (int)a%2 + b2); long crit2 = -(b2/4 + b%2 + a2); long crit3 = -(b2/4 + b%2); long t = -a2*y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */ long dxt = 2*b2*x, dyt = -2*a2*y; long d2xt = 2*b2, d2yt = 2*a2; if (a % 2) { // Odd version while (y>=0 && x<=a) { Set(xc+x, yc+y); if (x!=0 || y!=0) Set(xc-x, yc-y); if (x!=0 && y!=0) { Set(xc+x, yc-y); Set(xc-x, yc+y); } if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } else // even version { while (y>=0 && x<=a) { Set(xc+x+1, yc+y); Set(xc+x+1, yc-y); Set(xc-x, yc-y); Set(xc-x, yc+y); if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } Update(GDC_BITS_CHANGE); } void LSurface::FilledEllipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0-1.0), (Gdiplus::REAL)(radiusY*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif // TODO: fix this primitive for odd widths and heights int cx = (int)Cx; int cy = (int)Cy; /* a = floor(a); b = floor(b); */ long aSq = (long) (radiusX * radiusX); long bSq = (long) (radiusY * radiusY); long two_aSq = aSq+aSq; long two_bSq = bSq+bSq; long x=0, y=(long)radiusY, two_xBsq = 0, two_yAsq = y * two_aSq, error = -y * aSq; if (aSq && bSq && error) { while (two_xBsq <= two_yAsq) if (Op() == GDC_SET) { x++; two_xBsq += two_bSq; error += two_xBsq - bSq; if (error >= 0) { Line((int) (cx+x-1), (int) (cy+y), (int) (cx-x+1), (int) (cy+y)); if (y != 0) Line((int) (cx+x-1), (int) (cy-y), (int) (cx-x+1), (int) (cy-y)); y--; two_yAsq -= two_aSq; error -= two_yAsq; } } x=(long)radiusX; y=0; two_xBsq = x * two_bSq; two_yAsq = 0; error = -x * bSq; while (two_xBsq > two_yAsq) { Line( (int) (cx+x), (int) (cy+y), (int) (cx-x), (int) (cy+y)); if (y != 0) Line((int) (cx+x), (int) (cy-y), (int) (cx-x), (int) (cy-y)); y++; two_yAsq += two_aSq; error += two_yAsq - aSq; if (error >= 0) { x--; two_xBsq -= two_bSq; error -= two_xBsq; } } } Update(GDC_BITS_CHANGE); } struct EDGE { int yMin, yMax, x, dWholeX, dX, dY, frac; }; int nActive, nNextEdge; void LSurface::Polygon(int nPoints, LPoint *aPoints) { LPoint p0, p1; int i, j, gap, x0, x1, y, nEdges; EDGE *ET, **GET, **AET; /******************************************************************** * Add entries to the global edge table. The global edge table has a * bucket for each scan line in the polygon. Each bucket contains all * the edges whose yMin == yScanline. Each bucket contains the yMax, * the x coordinate at yMax, and the denominator of the slope (dX) */ // allocate the tables ET = new EDGE[nPoints]; GET = new EDGE*[nPoints]; AET = new EDGE*[nPoints]; if (ET && GET && AET) { for ( i = 0, nEdges = 0; i < nPoints; i++) { p0 = aPoints[i]; p1 = aPoints[(i+1) % nPoints]; // ignore if this is a horizontal edge if (p0.y == p1.y) continue; // swap points if necessary to ensure p0 contains yMin if (p0.y > p1.y) { p0 = p1; p1 = aPoints[i]; } // create the new edge ET[nEdges].yMin = p0.y; ET[nEdges].yMax = p1.y; ET[nEdges].x = p0.x; ET[nEdges].dX = p1.x - p0.x; ET[nEdges].dY = p1.y - p0.y; ET[nEdges].frac = 0; GET[nEdges] = &ET[nEdges]; nEdges++; } // sort the GET on yMin for (gap = 1; gap < nEdges; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nEdges; i++) for (j = i-gap; j >= 0; j -= gap) { if (GET[j]->yMin <= GET[j+gap]->yMin) break; EDGE *t = GET[j]; GET[j] = GET[j+gap]; GET[j+gap] = t; } // initialize the active edge table, and set y to first entering edge nActive = 0; nNextEdge = 0; y = GET[nNextEdge]->yMin; /* Now process the edges using the scan line algorithm. Active edges will be added to the Active Edge Table (AET), and inactive edges will be deleted. X coordinates will be updated with incremental integer arithmetic using the slope (dY / dX) of the edges. */ while (nNextEdge < nEdges || nActive) { /* Move from the ET bucket y to the AET those edges whose yMin == y (entering edges) */ while (nNextEdge < nEdges && GET[nNextEdge]->yMin == y) AET[nActive++] = GET[nNextEdge++]; /* Remove from the AET those entries for which yMax == y (leaving edges) */ i = 0; while (i < nActive) { if (AET[i]->yMax == y) memmove(&AET[i], &AET[i+1], sizeof(AET[0]) * (--nActive - i)); else i++; } /* Now sort the AET on x. Since the list is usually quite small, the sort is implemented as a simple non-recursive shell sort */ for (gap = 1; gap < nActive; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nActive; i++) for (j = i-gap; j >= 0; j -= gap) { if (AET[j]->x <= AET[j+gap]->x) break; EDGE *t = AET[j]; AET[j] = AET[j+gap]; AET[j+gap] = t; } /* Fill in desired pixels values on scan line y by using pairs of x coordinates from the AET */ for (i = 0; i < nActive; i += 2) { x0 = AET[i]->x; x1 = AET[i+1]->x; /* Left edge adjustment for positive fraction. 0 is interior. */ if (AET[i]->frac > 0) x0++; // Right edge adjustment for negative fraction. 0 is exterior. */ if (AET[i+1]->frac <= 0) x1--; // Draw interior spans if (x1 >= x0) HLine(x0, x1, y); } /* Update all the x coordinates. Edges are scan converted using a modified midpoint algorithm (Bresenham's algorithm reduces to the midpoint algorithm for two dimensional lines) */ for (i = 0; i < nActive; i++) { EDGE *e = AET[i]; // update the fraction by dX e->frac += e->dX; if (e->dX < 0) while ( -(e->frac) >= e->dY) { e->frac += e->dY; e->x--; } else while (e->frac >= e->dY) { e->frac -= e->dY; e->x++; } } y++; } } // Release tables DeleteArray(ET); DeleteArray(GET); DeleteArray(AET); } void LSurface::Blt(int x, int y, LSurface *Src, LRect *a) { OrgXy(x, y); - if (Src && Src->pMem && Src->pMem->Base) - { - LRect S; - if (a) S = *a; - else S.ZOff(Src->X()-1, Src->Y()-1); - S.Offset(Src->OriginX, Src->OriginY); + if (!Src || !Src->pMem || !Src->pMem->Base) + return; - LRect SClip = S; - SClip.Bound(&Src->Clip); + LRect S; + if (a) S = *a; + else S.ZOff(Src->X()-1, Src->Y()-1); + S.Offset(Src->OriginX, Src->OriginY); + + LRect SClip = S; + SClip.Bound(&Src->Clip); + + if (!SClip.Valid()) + return; - if (SClip.Valid()) - { - LRect D = SClip; - D.Offset(x-S.x1, y-S.y1); + LRect D = SClip; + D.Offset(x-S.x1, y-S.y1); - LRect DClip = D; - DClip.Bound(&Clip); + LRect DClip = D; + DClip.Bound(&Clip); - LRect Re = DClip; - Re.Offset(S.x1-x, S.y1-y); - SClip.Bound(&Re); + LRect Re = DClip; + Re.Offset(S.x1-x, S.y1-y); + SClip.Bound(&Re); - if (DClip.Valid() && SClip.Valid()) - { - LBmpMem Bits, Alpha; + if (!DClip.Valid() || !SClip.Valid()) + return; + + LBmpMem Bits, Alpha; - int PixelBytes = LColourSpaceToBits(Src->pMem->Cs) >> 3; - - Bits.Base = Src->pMem->Base + - (SClip.y1 * Src->pMem->Line) + - (SClip.x1 * PixelBytes); - Bits.x = MIN(SClip.X(), DClip.X()); - Bits.y = MIN(SClip.Y(), DClip.Y()); - Bits.Line = Src->pMem->Line; - Bits.Cs = Src->GetColourSpace(); - Bits.PreMul(Src->pMem->PreMul()); + Bits.Base = Src->pMem->AddressOf(SClip.x1, SClip.y1); + Bits.x = MIN(SClip.X(), DClip.X()); + Bits.y = MIN(SClip.Y(), DClip.Y()); + Bits.Line = Src->pMem->Line; + Bits.Cs = Src->GetColourSpace(); + Bits.PreMul(Src->pMem->PreMul()); - if (Src->pAlphaDC && !Src->DrawOnAlpha()) - { - LBmpMem *ASurface = Src->pAlphaDC->pMem; - Alpha = Bits; - Alpha.Cs = CsIndex8; - Alpha.Line = ASurface->Line; - Alpha.Base = ASurface->Base + - (SClip.y1 * ASurface->Line) + - (SClip.x1); - } + if (Src->pAlphaDC && !Src->DrawOnAlpha()) + { + auto ASurface = Src->pAlphaDC->pMem; + Alpha = Bits; + Alpha.Cs = CsIndex8; + Alpha.Line = ASurface->Line; + Alpha.Base = ASurface->Base + + (SClip.y1 * ASurface->Line) + + (SClip.x1); + } - pApp->SetPtr(DClip.x1, DClip.y1); - LPalette *SrcPal = Src->DrawOnAlpha() ? NULL : Src->Palette(); - // printf("\t%p::Blt pApp=%p, %s\n", this, pApp, pApp->GetClass()); - pApp->Blt(&Bits, SrcPal, Alpha.Base ? &Alpha : NULL); - Update(GDC_BITS_CHANGE); + pApp->SetPtr(DClip.x1, DClip.y1); + LPalette *SrcPal = Src->DrawOnAlpha() ? NULL : Src->Palette(); + + // printf("\t%p::Blt pApp=%p, %s\n", this, pApp, pApp->GetClass()); + + pApp->Blt(&Bits, SrcPal, Alpha.Base ? &Alpha : NULL); + Update(GDC_BITS_CHANGE); - if (pApp->GetFlags() & GDC_UPDATED_PALETTE) - { - Palette(new LPalette(pApp->GetPal())); - } - } - } + if (pApp->GetFlags() & GDC_UPDATED_PALETTE) + { + Palette(new LPalette(pApp->GetPal())); } } #define sqr(a) ((a)*(a)) #define Distance(a, b) (sqrt(sqr(a.x-b.x)+sqr(a.y-b.y))) class FPt2 { public: double x, y; }; typedef FPt2 BPt; void LSurface::Bezier(int Threshold, LPoint *Pt) { if (Pt) { int OldPts = 3; int NewPts = 0; BPt *BufA = new BPt[1024]; BPt *BufB = new BPt[1024]; BPt *Old = BufA; BPt *New = BufB; Threshold = MAX(Threshold, 1); if (!Old || !New) return; for (int n=0; n Threshold); if (Threshold > 1) { for (int i=0; i= Size) { SetSize(Size+1024); } if (Stack) { Stack[Used].x = x; Stack[Used].y = y; Used++; } } void Pop(int &x, int &y) { if (Stack && Used > 0) { Used--; x = Stack[Used].x; y = Stack[Used].y; } } }; // This should return true if 'Pixel' is in the region being filled. typedef bool (*FillMatchProc)(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits); bool FillMatch_Diff(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { return Seed == Pixel; } bool FillMatch_Near(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { COLOUR s24 = CBit(24, Seed, Bits); COLOUR p24 = CBit(24, Pixel, Bits); int Dr = R24(s24) - R24(p24); int Dg = G24(s24) - G24(p24); int Db = B24(s24) - B24(p24); return ((unsigned)abs(Dr) < Border) && ((unsigned)abs(Dg) < Border) && ((unsigned)abs(Db) < Border); } void LSurface::FloodFill(int StartX, int StartY, int Mode, COLOUR Border, LRect *FillBounds) { COLOUR Seed = Get(StartX, StartY); if (Seed == 0xffffffff) return; // Doesn't support get pixel PointStack Ps; LRect Bounds; FillMatchProc Proc = 0; int Bits = GetBits(); Bounds.x1 = X(); Bounds.y1 = Y(); Bounds.x2 = 0; Bounds.y2 = 0; Ps.Push(StartX, StartY); switch (Mode) { case GDC_FILL_TO_DIFFERENT: { Proc = FillMatch_Diff; break; } case GDC_FILL_TO_BORDER: { break; } case GDC_FILL_NEAR: { Proc = FillMatch_Near; break; } } if (Proc) { COLOUR Start = Colour(); if (!Proc(Seed, Start, Border, Bits)) { while (Ps.GetSize() > 0) { bool Above = true; bool Below = true; int Ox, Oy; Ps.Pop(Ox, Oy); int x = Ox, y = Oy; // move right loop COLOUR c = Get(x, y); while (x < X() && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x++; c = Get(x, y); } // move left loop x = Ox; Above = !((y > 0) && (Get(x, y - 1) == Seed)); Below = !((y < Y() - 1) && (Get(x, y + 1) == Seed)); x--; c = Get(x, y); while (x >= 0 && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x--; c = Get(x, y); } } } } if (FillBounds) { *FillBounds = Bounds; } Update(GDC_BITS_CHANGE); } void LSurface::Arc(double cx, double cy, double radius, double start, double end) {} void LSurface::FilledArc(double cx, double cy, double radius, double start, double end) {} void LSurface::StretchBlt(LRect *d, LSurface *Src, LRect *s) {} bool LSurface::AntiAlias() { return (Flags & GDC_ANTI_ALIAS) != 0; } bool LSurface::AntiAlias(bool antiAlias) { if (antiAlias) Flags |= GDC_ANTI_ALIAS; else Flags &= ~GDC_ANTI_ALIAS; return true; } bool LSurface::HasAlpha(bool b) { DrawOnAlpha(false); if (b) { if (!pAlphaDC) { pAlphaDC = new LMemDC; } if (pAlphaDC && pMem) { if (!pAlphaDC->Create(pMem->x, pMem->y, CsIndex8)) { DeleteObj(pAlphaDC); } else { ClearFlag(Flags, GDC_DRAW_ON_ALPHA); } } } else { DeleteObj(pAlphaDC); } return (b == HasAlpha()); } bool LSurface::DrawOnAlpha(bool Draw) { bool Prev = DrawOnAlpha(); bool Swap = false; if (Draw) { if (!Prev && pAlphaDC && pMem) { Swap = true; SetFlag(Flags, GDC_DRAW_ON_ALPHA); PrevOp = Op(GDC_SET); } } else { if (Prev && pAlphaDC && pMem) { Swap = true; ClearFlag(Flags, GDC_DRAW_ON_ALPHA); Op(PrevOp); } } if (Swap) { LBmpMem *Temp = pMem; pMem = pAlphaDC->pMem; pAlphaDC->pMem = Temp; #if WINNATIVE OsBitmap hTmp = hBmp; hBmp = pAlphaDC->hBmp; pAlphaDC->hBmp = hTmp; OsPainter hP = hDC; hDC = pAlphaDC->hDC; pAlphaDC->hDC = hP; #endif } return Prev; } LApplicator *LSurface::CreateApplicator(int Op, LColourSpace Cs) { LApplicator *pA = NULL; if (!Cs) { if (pMem) { if (DrawOnAlpha()) { Cs = CsIndex8; } else if (pMem->Cs) { Cs = pMem->Cs; } else { LAssert(!"Memory context has no colour space..."); } } else if (IsScreen()) { Cs = GdcD->GetColourSpace(); } else { LAssert(!"No memory context to read colour space from."); } } pA = LApplicatorFactory::NewApp(Cs, Op); if (pA) { if (DrawOnAlpha()) { pA->SetSurface(pMem); } else { pA->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } pA->SetOp(Op); } else { LApplicatorFactory::NewApp(Cs, Op); const char *CsStr = LColourSpaceToString(Cs); LgiTrace("Error: GDeviceContext::CreateApplicator(%i, %x, %s) failed.\n", Op, Cs, CsStr); LAssert(!"No applicator"); } return pA; } bool LSurface::Applicator(LApplicator *pApplicator) { bool Status = false; if (pApplicator) { if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp) Flags &= ~GDC_OWN_APPLICATOR; } Flags &= ~GDC_CACHED_APPLICATOR; pApp = pApplicator; if (DrawOnAlpha()) { pApp->SetSurface(pMem); } else { pApp->SetSurface(pMem, pPalette, pAlphaDC->pMem); } pApp->SetPtr(0, 0); Status = true; } return Status; } LApplicator *LSurface::Applicator() { return pApp; } LRect LSurface::ClipRgn(LRect *Rgn) { LRect Old = Clip; if (Rgn) { Clip.x1 = MAX(0, Rgn->x1 - OriginX); Clip.y1 = MAX(0, Rgn->y1 - OriginY); Clip.x2 = MIN(X()-1, Rgn->x2 - OriginX); Clip.y2 = MIN(Y()-1, Rgn->y2 - OriginY); } else { Clip.x1 = 0; Clip.y1 = 0; Clip.x2 = X()-1; Clip.y2 = Y()-1; } return Old; } LRect LSurface::ClipRgn() { return Clip; } LColour LSurface::Colour(LSystemColour SysCol) { return Colour(LColour(SysCol)); } LColour LSurface::Colour(LColour c) { LAssert(pApp != NULL); LColour cPrev; uint32_t c32 = c.c32(); LColourSpace Cs = pApp->GetColourSpace(); switch (Cs) { case CsIndex8: { cPrev.Set(pApp->c, 8); if (c.GetColourSpace() == CsIndex8) { pApp->c = c.c8(); } else { LPalette *p = Palette(); if (p) // Colour pApp->c = p->MatchRgb(Rgb32To24(c32)); else // Grey scale pApp->c = c.GetGray(); } break; } case CsRgb15: case CsBgr15: { cPrev.Set(pApp->c, 15); pApp->c = Rgb32To15(c32); break; } case CsRgb16: case CsBgr16: { cPrev.Set(pApp->c, 16); pApp->c = Rgb32To16(c32); break; } case CsRgba32: case CsBgra32: case CsArgb32: case CsAbgr32: case CsRgbx32: case CsBgrx32: case CsXrgb32: case CsXbgr32: case CsRgba64: case CsBgra64: case CsArgb64: case CsAbgr64: { cPrev.Rgb(pApp->p32.r, pApp->p32.g, pApp->p32.b, pApp->p32.a); pApp->p32.r = R32(c32); pApp->p32.g = G32(c32); pApp->p32.b = B32(c32); pApp->p32.a = A32(c32); break; } case CsRgb24: case CsBgr24: case CsRgb48: case CsBgr48: { cPrev.Rgb(pApp->p24.r, pApp->p24.g, pApp->p24.b); pApp->p24.r = R32(c32); pApp->p24.g = G32(c32); pApp->p24.b = B32(c32); break; } default: LAssert(0); break; } #if defined LGI_CARBON { // Update the current colour of the drawing context if present. OsPainter Hnd = Handle(); if (Hnd) { float r = (float)c.r()/255.0; float g = (float)c.g()/255.0; float b = (float)c.b()/255.0; float a = (float)c.a()/255.0; CGContextSetRGBFillColor(Hnd, r, g, b, a); CGContextSetRGBStrokeColor(Hnd, r, g, b, a); } } #endif return cPrev; } COLOUR LSurface::Colour(COLOUR c, int Bits) { LColour n(c, Bits ? Bits : GetBits()); LColour Prev = Colour(n); switch (GetBits()) { case 8: return Prev.c8(); case 24: return Prev.c24(); case 32: return Prev.c32(); } return Prev.c32(); } int LSurface::Op(int NewOp, NativeInt Param) { int PrevOp = (pApp) ? pApp->GetOp() : GDC_SET; if (!pApp || PrevOp != NewOp) { COLOUR cCurrent = (pApp) ? Colour() : 0; if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp); } if (NewOp < GDC_CACHE_SIZE && !DrawOnAlpha()) { pApp = (pAppCache[NewOp]) ? pAppCache[NewOp] : pAppCache[NewOp] = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_OWN_APPLICATOR; Flags |= GDC_CACHED_APPLICATOR; } else { pApp = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_CACHED_APPLICATOR; Flags |= GDC_OWN_APPLICATOR; } if (pApp) { Colour(cCurrent); if (Param >= 0) pApp->SetVar(GAPP_ALPHA_A, Param); } else { printf("Error: Couldn't create applicator, Op=%i\n", NewOp); LAssert(0); } } return PrevOp; } LPalette *LSurface::Palette() { if (!pPalette && pMem && (pMem->Flags & GDC_ON_SCREEN) && pMem->Cs == CsIndex8) { pPalette = GdcD->GetSystemPalette(); // printf("Setting sys palette: %p\n", pPalette); if (pPalette) { Flags &= ~GDC_OWN_PALETTE; } } return pPalette; } void LSurface::Palette(LPalette *pPal, bool bOwnIt) { if (pPal == pPalette) { LgiTrace("%s:%i - Palette setting itself.\n", _FL); return; } // printf("LSurface::Palette %p %i\n", pPal, bOwnIt); if (pPalette && (Flags & GDC_OWN_PALETTE) != 0) { // printf("\tdel=%p\n", pPalette); delete pPalette; } pPalette = pPal; if (pPal && bOwnIt) { Flags |= GDC_OWN_PALETTE; // printf("\tp=%p own\n", pPalette); } else { Flags &= ~GDC_OWN_PALETTE; // printf("\tp=%p not-own\n", pPalette); } if (pApp) { pApp->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } } bool LSurface::GetVariant(const char *Name, LVariant &Dst, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceX: // Type: Int32 { Dst = X(); break; } case SurfaceY: // Type: Int32 { Dst = Y(); break; } case SurfaceBits: // Type: Int32 { Dst = GetBits(); break; } case SurfaceColourSpace: // Type: Int32 { Dst = GetColourSpace(); break; } case SurfaceIncludeCursor: // Type: Int32 { Dst = TestFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: { return false; } } return true; } bool LSurface::SetVariant(const char *Name, LVariant &Value, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceIncludeCursor: { if (Value.CastInt32()) SetFlag(Flags, GDC_CAPTURE_CURSOR); else ClearFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: break; } return false; } bool LSurface::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { return false; } bool LSurface::IsPreMultipliedAlpha() { return pMem ? pMem->PreMul() : false; } template void ConvertToPreMul(Px *src, int x) { REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { s->r = DivLut[s->r * s->a]; s->g = DivLut[s->g * s->a]; s->b = DivLut[s->b * s->a]; s++; } } template void ConvertFromPreMul(Px *src, int x) { // REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { if (s->a > 0 && s->a < 255) { s->r = (int) s->r * 255 / s->a; s->g = (int) s->g * 255 / s->a; s->b = (int) s->b * 255 / s->a; } s++; } } bool LSurface::ConvertPreMulAlpha(bool ToPreMul) { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (ToPreMul) { switch (pMem->Cs) { #define ToPreMulCase(px) \ case Cs##px: ConvertToPreMul((L##px*)src, pMem->x); break ToPreMulCase(Rgba32); ToPreMulCase(Bgra32); ToPreMulCase(Argb32); ToPreMulCase(Abgr32); #undef ToPreMulCase default: break; } } else { switch (pMem->Cs) { #define FromPreMulCase(px) \ case Cs##px: ConvertFromPreMul((L##px*)src, pMem->x); break FromPreMulCase(Rgba32); FromPreMulCase(Bgra32); FromPreMulCase(Argb32); FromPreMulCase(Abgr32); #undef FromPreMulCase default: break; } } } pMem->PreMul(ToPreMul); return true; } template void MakeOpaqueRop32(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xff; s++; } } template void MakeOpaqueRop64(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xffff; s++; } } bool LSurface::MakeOpaque() { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); switch (pMem->Cs) { #define OpaqueCase(px, sz) \ case Cs##px: MakeOpaqueRop##sz((L##px*)src, pMem->x); break OpaqueCase(Rgba32, 32); OpaqueCase(Bgra32, 32); OpaqueCase(Argb32, 32); OpaqueCase(Abgr32, 32); OpaqueCase(Rgba64, 64); OpaqueCase(Bgra64, 64); OpaqueCase(Argb64, 64); OpaqueCase(Abgr64, 64); #undef OpaqueCase default: break; } } return true; } diff --git a/src/common/General/Password.cpp b/src/common/General/Password.cpp --- a/src/common/General/Password.cpp +++ b/src/common/General/Password.cpp @@ -1,173 +1,173 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Password.h" #include "lgi/common/Variant.h" ////////////////////////////////////////////////////////////////////////////// // Password encryption for storing the passwords for accounts on the HD // // This isn't intended to be secure, but merely a obfusication of the // password to hide it from casual observers. char HashString[] = "KLWEHGF)AS^*_GPYIOShakl;sjfhas0w89725l3jkhL:KHASFQ_DF_AS+_F"; GPassword::GPassword(GPassword *p) { Data = 0; Len = 0; if (p) { *this = *p; } } GPassword::~GPassword() { DeleteArray(Data); } GPassword &GPassword::operator =(GPassword &p) { if (p.Data) { Data = new char[p.Len]; if (Data) { memcpy(Data, p.Data, p.Len); Len = p.Len; } } return *this; } bool GPassword::operator ==(GPassword &p) { if (Data && p.Data && Len == p.Len) { return memcmp(Data, p.Data, Len) == 0; } else if (Data == 0 && p.Data == 0) { return true; } return false; } -void GPassword::Process(char *Out, const char *In, ssize_t Len) +void GPassword::Process(char *Out, const char *In, ssize_t Len) const { if (Out && In && Len > 0) { size_t HashLen = strlen(HashString); int i; for (i=0; i 28) Len = 28; *p++ = (int)Len; if (Data) { memcpy(p, Data, Len); } } else // read { Len = *p++; DeleteArray(Data); if (Len > 0) { Data = new char[Len]; if (Data) { memcpy(Data, p, Len); } } } } bool GPassword::Serialize(LDom *Options, const char *Prop, int Write) { bool Status = false; if (Options && Prop) { LVariant v; if (Write) { v.SetBinary(Len, Data); Status = Options->SetValue(Prop, v); } else // read from prop list { if (Options->GetValue(Prop, v) && v.Type == GV_BINARY) { DeleteArray(Data); Len = v.Value.Binary.Length; Data = new char[v.Value.Binary.Length]; if (Data) { memcpy(Data, v.Value.Binary.Data, Len); Status = true; } } } } return Status; } void GPassword::Delete(LDom *Options, char *Option) { if (Options && Option) { LVariant v; Options->SetValue(Option, v); } } diff --git a/src/common/General/SoftwareUpdate.cpp b/src/common/General/SoftwareUpdate.cpp --- a/src/common/General/SoftwareUpdate.cpp +++ b/src/common/General/SoftwareUpdate.cpp @@ -1,454 +1,454 @@ #include "lgi/common/Lgi.h" #include "lgi/common/SoftwareUpdate.h" #include "lgi/common/Net.h" #include "lgi/common/Http.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Thread.h" static char sHttpDownloadFailed[] = "HTTP download failed."; static char sSocketConnectFailed[] = "Socket connect failed."; static char sNoUpdateUri[] = "No update URI."; static char sXmlParsingFailed[] = "XML parsing failed."; static char sUnexpectedXml[] = "Unexpected XML."; static char sUpdateError[] = "Update script error: %s"; struct LSoftwareUpdatePriv { LString Name; LString UpdateUri; LString Proxy; LString Error; LString TempPath; void SetError(int Id, const char *Def = 0) { Error = LLoadString(Id, Def); } class UpdateThread : public LThread { LSoftwareUpdatePriv *d; LSocket *s; LSoftwareUpdate::UpdateInfo *Info; LHttp Http; bool IncBetas; public: bool Status; UpdateThread(LSoftwareUpdatePriv *priv, LSoftwareUpdate::UpdateInfo *info, bool betas) : LThread("SoftwareUpdateThread") { Info = info; d = priv; s = 0; IncBetas = betas; Status = false; Run(); } ~UpdateThread() { LAssert(IsExited()); } void Cancel() { Http.Close(); Info->Cancel = true; } int Main() { if (d->UpdateUri) { LUri Uri(d->UpdateUri); char Dir[256]; int WordSize = sizeof(size_t) << 3; LString OsName = LGetOsName(); int Os = LGetOs(); if (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 || Os == LGI_OS_WIN9X) { OsName.Printf("Win%i", WordSize); } sprintf_s(Dir, sizeof(Dir), "%s?name=%s&os=%s&betas=%i", Uri.sPath.Get(), (char*)d->Name, OsName.Get(), IncBetas); Uri.sPath = Dir; LString GetUri = Uri.ToString(); #ifdef _DEBUG LgiTrace("UpdateURI=%s\n", GetUri.Get()); #endif if (d->Proxy) { LUri Proxy(d->Proxy); if (Proxy.sHost) Http.SetProxy(Proxy.sHost, Proxy.Port?Proxy.Port:HTTP_PORT); } LStringPipe RawXml; int ProtocolStatus = 0; LAutoPtr s(new LSocket); if (Http.Open(s, Uri.sHost, Uri.Port)) { LHttp::ContentEncoding Enc; if (Http.Get(GetUri, NULL, &ProtocolStatus, &RawXml, &Enc)) { auto Xml = RawXml.NewGStr(); LMemStream XmlStream(Xml.Get(), Xml.Length(), false); LXmlTree Tree; LXmlTag Root; if (Tree.Read(&Root, &XmlStream)) { LXmlTag *StatusCode; if (Root.IsTag("software") && (StatusCode = Root.GetChildTag("status"))) { if (StatusCode->GetContent() && atoi(StatusCode->GetContent()) > 0) { LXmlTag *t; if ((t = Root.GetChildTag("version"))) Info->Version = t->GetContent(); if ((t = Root.GetChildTag("revision"))) Info->Build = t->GetContent(); if ((t = Root.GetChildTag("uri"))) Info->Uri = t->GetContent(); if ((t = Root.GetChildTag("date"))) { Info->Date.SetFormat(GDTF_YEAR_MONTH_DAY); Info->Date.Set(t->GetContent()); } Status = true; } else { LXmlTag *Msg = Root.GetChildTag("msg"); LStringPipe p; p.Print(LLoadString(L_ERROR_UPDATE, sUpdateError), Msg?Msg->GetContent():(char*)"Unknown"); d->Error = p.NewGStr(); LgiTrace("UpdateURI=%s\n", GetUri.Get()); } } else { d->SetError(L_ERROR_UNEXPECTED_XML, sUnexpectedXml); LgiTrace("%s:%i - Bad XML: %s\n", _FL, Xml.Get()); } } else { d->SetError(L_ERROR_XML_PARSE, sXmlParsingFailed); LgiTrace("%s:%i - Bad XML: %s\n", _FL, Xml.Get()); } } else { d->SetError(L_ERROR_HTTP_FAILED, sHttpDownloadFailed); LgiTrace("%s:%i - Bad URI: %s\n", _FL, GetUri.Get()); } } else { d->SetError(L_ERROR_CONNECT_FAILED, sSocketConnectFailed); LgiTrace("%s:%i - Bad connect: %s:%i\n", _FL, Uri.sHost.Get(), Uri.Port); } } else d->SetError(L_ERROR_NO_URI, sNoUpdateUri); return 0; } }; class Spinner : public LDialog { UpdateThread *Watch; public: Spinner(LViewI *par, UpdateThread *watch) { Watch = watch; SetParent(par); LRect r(0, 0, 330, 400); SetPos(r); Name("Software Update"); LRect c = GetClient(); LTextLabel *t = new LTextLabel( -1, 10, 10, c.X()-20, -1, LLoadString(L_SOFTUP_CHECKING, "Checking for software update...")); if (t) { AddView(t); LButton *btn = new LButton( IDCANCEL, (c.X()-70)/2, t->GetPos().y2 + 10, 70, -1, LLoadString(L_BTN_CANCEL, "Cancel")); if (btn) { AddView(btn); r.y2 = (r.Y()-c.Y()) + 30 + t->Y() + btn->Y(); SetPos(r); MoveToCenter(); - DoModal(); } } } void OnCreate() { SetPulse(100); } void OnPulse() { if (Watch->IsExited()) { EndModal(0); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDCANCEL: { Watch->Cancel(); break; } } return LDialog::OnNotify(c, n); } }; class UpdateDownload : public LThread, public LProxyStream { const LSoftwareUpdate::UpdateInfo *Info; LUri *Uri; LUri *Proxy; LStream *Local; LString *Err; int *Status; public: int64 Progress, Total; UpdateDownload( const LSoftwareUpdate::UpdateInfo *info, LUri *uri, LUri *proxy, LStream *local, LString *err, int *status) : LThread("UpdateDownload"), LProxyStream(local) { Info = info; Uri = uri; Proxy = proxy; Local = local; Err = err; Status = status; Progress = Total = 0; Run(); } int64 SetSize(int64 Size) override { Total = Size; return s->SetSize(Size); } ssize_t Write(const void *b, ssize_t l, int f = 0) override { auto ret = s->Write(b, l, f); Progress = s->GetPos(); return ret; } int Main() override { LHttp Http; if (Proxy->sHost) Http.SetProxy(Proxy->sHost, Proxy->Port?Proxy->Port:HTTP_PORT); LAutoPtr s(new LSocket); LHttp::ContentEncoding Enc; if (!Http.Open(s, Uri->sHost, Uri->Port)) { *Err = LLoadString(L_ERROR_CONNECT_FAILED, sSocketConnectFailed); } else if (!Http.Get(Info->Uri, 0, Status, this, &Enc)) { *Err = LLoadString(L_ERROR_HTTP_FAILED, sHttpDownloadFailed); } return 0; } }; }; LSoftwareUpdate::LSoftwareUpdate(const char *SoftwareName, const char *UpdateUri, const char *ProxyUri, const char *OptionalTempPath) { d = new LSoftwareUpdatePriv; d->Name = SoftwareName; d->UpdateUri = UpdateUri; d->Proxy = ProxyUri; d->TempPath = OptionalTempPath; } LSoftwareUpdate::~LSoftwareUpdate() { DeleteObj(d); } void LSoftwareUpdate::CheckForUpdate(UpdateInfo &Info, LViewI *WithUi, bool IncBetas, std::function callback) { LSoftwareUpdatePriv::UpdateThread Update(d, &Info, IncBetas); if (WithUi) { - LSoftwareUpdatePriv::Spinner s(WithUi, &Update); + auto s = new LSoftwareUpdatePriv::Spinner(WithUi, &Update); + s->DoModal(NULL); } else { while (!Update.IsExited()) { LYield(); LSleep(10); } } if (callback) callback(Update.Status, GetErrorMessage()); } bool LSoftwareUpdate::ApplyUpdate(const UpdateInfo &Info, bool DownloadOnly, LViewI *WithUi) { if (!Info.Uri) { d->SetError(L_ERROR_NO_URI, sNoUpdateUri); return false; } LUri Uri(Info.Uri); if (!Uri.sPath) { d->SetError(L_ERROR_URI_ERROR, "No path in URI."); return false; } char *File = strrchr(Uri.sPath, '/'); if (!File) File = Uri.sPath; else File++; char Tmp[MAX_PATH_LEN]; if (d->TempPath) LMakePath(Tmp, sizeof(Tmp), d->TempPath, File); else LMakePath(Tmp, sizeof(Tmp), LGetSystemPath(LSP_TEMP), File); LFile Local; if (!Local.Open(Tmp, O_WRITE)) { d->SetError(L_ERROR_OPENING_TEMP_FILE, "Can't open local temp file."); return false; } Local.SetSize(0); LProgressDlg *Dlg = new LProgressDlg; Dlg->SetDescription(LLoadString(L_SOFTUP_DOWNLOADING, "Downloading...")); Dlg->SetType("KiB"); Dlg->SetScale(1.0 / 1024.0); int HttpStatus = 0; int64 Size = 0; LUri Proxy(d->Proxy); LSoftwareUpdatePriv::UpdateDownload Thread(&Info, &Uri, &Proxy, &Local, &d->Error, &HttpStatus); while (!Thread.IsExited()) { LYield(); LSleep(50); if (!Size) { if (Thread.Total) Dlg->SetRange(Size = Thread.Total); } else { if (Thread.Progress > Dlg->Value()) Dlg->Value(Thread.Progress); } } Local.Close(); Dlg->EndModeless(); if (HttpStatus != 200) { FileDev->Delete(Tmp); d->SetError(L_ERROR_HTTP_FAILED, sHttpDownloadFailed); return false; } if (!DownloadOnly) { char *Ext = LGetExtension(Tmp); if (Ext) { if (!_stricmp(Ext, "exe")) { // Execute to install... LExecute(Tmp); return true; } else { // Calculate the local path... char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), LGetExePath(), File); if (!_stricmp(Ext, "dll")) { // Copy to local folder... LError Err; if (!FileDev->Copy(Tmp, Path, &Err)) { d->SetError(L_ERROR_COPY_FAILED, "Failed to copy file from temp folder to local folder."); } } else if (!_stricmp(Ext, "gz")) { // Unpack to local folder... } // Cleanup FileDev->Delete(Tmp); } } } return false; } const char *LSoftwareUpdate::GetErrorMessage() { return d->Error; } diff --git a/src/common/Lgi/About.cpp b/src/common/Lgi/About.cpp --- a/src/common/Lgi/About.cpp +++ b/src/common/Lgi/About.cpp @@ -1,120 +1,118 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/About.h" #include "lgi/common/DocView.h" #include "lgi/common/Bitmap.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/TableLayout.h" ////////////////////////////////////////////////////////////////////////////// enum Ctrls { IDC_WEB = 100, IDC_TABLE, IDC_BMP, IDC_MESSAGE, }; LAbout::LAbout( LView *parent, const char *AppName, const char *Ver, const char *Text, const char *AboutGraphic, const char *Url, const char *Email) { SetParent(parent); if (!AppName) AppName = "Application"; LString n; n.Printf("About %s", AppName); Name(n); #ifdef _DEBUG const char *Build = "Debug"; #else const char *Build = "Release"; #endif LStringPipe p; const char *OsName = LGetOsName(); #if defined(_WIN64) OsName = "Win64"; #elif defined(WIN32) OsName = "Win32"; #endif p.Print("%s v%s (%s %s)\n", AppName, Ver, OsName, Build); p.Print("Build: %s, %s\n", __DATE__, __TIME__); #ifdef __GTK_H__ p.Print("GTK v%i.%i.%i\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); #endif p.Print("\n"); if (Url) p.Print("Homepage:\n\t%s\n", Url); if (Email) p.Print("Email:\n\t%s\n", Email); if (Text) p.Write((char*)Text, strlen(Text)); LColour cBack(L_MED); LTableLayout *Tbl = new LTableLayout(IDC_TABLE); AddView(Tbl); Tbl->GetCss(true)->Padding("0.5em"); int x = 0; if (AboutGraphic) { LString FileName = LFindFile(AboutGraphic); if (FileName) { auto c = Tbl->GetCell(x++, 0, true); auto Img = new LBitmap(IDC_BMP, 0, 0, FileName, true); c->Add(Img); Img->GetCss(true)->BackgroundColor(cBack); } } LView *Ctrl = LViewFactory::Create("LTextView3"); if (Ctrl) { auto c = Tbl->GetCell(x++, 0, true); c->Add(Ctrl); Ctrl->SetId(IDC_MESSAGE); Ctrl->Name(p.NewGStr()); Ctrl->GetCss(true)->BackgroundColor(cBack); Ctrl->SetFont(LSysFont); } auto c = Tbl->GetCell(0, 1, true, x); c->TextAlign(LCss::AlignRight); c->Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); LRect r(0, 0, 400, 260); SetPos(r); MoveSameScreen(parent); - - DoModal(); } int LAbout::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ctrl) return 0; switch (Ctrl->GetId()) { case IDC_BMP: { break; } case IDOK: { EndModal(0); break; } } return 0; } diff --git a/src/common/Lgi/Alert.cpp b/src/common/Lgi/Alert.cpp --- a/src/common/Lgi/Alert.cpp +++ b/src/common/Lgi/Alert.cpp @@ -1,97 +1,111 @@ // Lgi.cpp #include "lgi/common/Lgi.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TableLayout.h" ////////////////////////////////////////////////////////////////////////////// #define CMD_BASE 10000 #ifdef LGI_SDL #define BTN_SCALE 2.0f #else #define BTN_SCALE 1.0f #endif LAlert::LAlert( LViewI *parent, const char *Title, const char *Text, const char *Btn1, const char *Btn2, const char *Btn3) { LArray Names; if (Btn1) Names.Add(Btn1); if (Btn2) Names.Add(Btn2); if (Btn3) Names.Add(Btn3); // Setup dialog SetParent(parent); Name((char*)Title); LTableLayout *Tbl = new LTableLayout(100); Tbl->GetCss(true)->Padding("10px"); AddView(Tbl); LLayoutCell *c = Tbl->GetCell(0, 0, true); c->Add(new LTextLabel(-1, 8, 8, -1, -1, Text)); c->PaddingBottom(LCss::Len("10px")); c = Tbl->GetCell(0, 1, true); for (unsigned i=0; iTextAlign(LCss::Len(LCss::AlignCenter)); c->Add(new LButton(CMD_BASE + i, 0, 0, -1, -1, Names[i])); } LRect r(0, 0, 1000, 1000); Tbl->SetPos(r); r = Tbl->GetUsedArea(); r.Inset(-10, -10); int x = LAppInst->GetMetric(LGI_MET_DECOR_X); int y = LAppInst->GetMetric(LGI_MET_DECOR_Y) + LAppInst->GetMetric(LGI_MET_DECOR_CAPTION); r.x2 += x + 20; r.y2 += y; SetPos(r); if (parent && parent->GetPos().Valid()) MoveSameScreen(parent); else MoveToCenter(); } void LAlert::SetAppModal() { #if WINNATIVE SetExStyle(GetExStyle() | WS_EX_TOPMOST); #elif defined(LGI_CARBON) if (Handle()) { OSStatus e = HIWindowChangeClass(WindowHandle(), kMovableModalWindowClass); if (e) printf("%s:%i - Error: HIWindowChangeClass=%i\n", _FL, (int)e); } #endif } +void LAlert::SetButtonCallback(int ButtonIdx, std::function Callback) +{ + Callbacks[ButtonIdx] = Callback; +} + int LAlert::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case CMD_BASE+0: case CMD_BASE+1: case CMD_BASE+2: { if (n.Type != LNotifyTableLayoutChanged) { - EndModal(Ctrl->GetId() - CMD_BASE + 1); + auto Index = Ctrl->GetId() - CMD_BASE + 1; + if (Callbacks.IdxCheck(Index) && + Callbacks[Index] != NULL) + { + Callbacks[Index](Index); + } + else + { + EndModal(Index); + } } break; } } return 0; } diff --git a/src/common/Lgi/DocApp.cpp b/src/common/Lgi/DocApp.cpp --- a/src/common/Lgi/DocApp.cpp +++ b/src/common/Lgi/DocApp.cpp @@ -1,674 +1,681 @@ /*hdr ** FILE: LDocApp.cpp ** AUTHOR: Matthew Allen ** DATE: 20/4/01 ** DESCRIPTION: Generic Document Application Impl ** */ #include #include "lgi/common/DocApp.h" #include "lgi/common/Token.h" #include "lgi/common/OptionsFile.h" #ifdef HAS_PROPERTIES #include "GProperties.h" #endif #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" ////////////////////////////////////////////////////////////////////////////////////////// class LDocAppPrivate { public: // Data LWindow *App; LString OptionsFile; LString OptionsParam; char *AppName; LString CurFile; bool Dirty; GDocAppInstallMode Mode; LDocAppPrivate(LWindow *app, char *param) { App = app; OptionsParam = param; AppName = 0; Dirty = 0; Mode = InstallPortable; } ~LDocAppPrivate() { // Release memory DeleteArray(AppName); } LString GetOptionsFile(const char *Ext) { // Get options file LString Status; char Opt[MAX_PATH_LEN]; if (LAppInst->GetOption("o", Opt, sizeof(Opt))) { if (LFileExists(Opt)) Status = Opt; } if (!Status) { auto Exe = LGetExeFile(); char *File = strrchr(Exe, DIR_CHAR); if (File) { File++; Status = File; #ifdef WIN32 auto Dot = Status.RFind("."); if (Dot >= 0) { Status = Status(0, Dot+1) + Ext; } else #endif { // unix apps have no '.' in their name Status += LString(".") + Ext; } char p[MAX_PATH_LEN]; if (Mode == InstallPortable) { LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); } else { if (LGetSystemPath(LSP_APP_ROOT, p, sizeof(p)) && !LDirExists(p)) { FileDev->CreateFolder(p); } } LMakePath(p, sizeof(p), p, Status); Status = p; } } return Status; } /////////////////////////////////////////////////////////////////////////// // Xml Options const char *GetExtension(LOptionsFile *p) { return "xml"; } bool SetOpt(LOptionsFile *p, const char *opt, char *str) { LVariant v = str; return p ? p->SetValue(opt, v) : false; } bool GetOpt(LOptionsFile *p, const char *opt, LVariant &v) { return p ? p->GetValue(opt, v) : false; } bool SerializeOpts(LOptionsFile *p, bool Write) { if (!p) return false; p->SetFile(OptionsFile); bool Result = p->SerializeFile(Write); if (Write && !Result) { // Probably because we don't have write access to the install folder? LgiMsg( App, "Failed to write options to '%s' (mode=%s)", App->Name(), MB_OK, OptionsFile.Get(), Mode == InstallPortable ? "Portable" : "Desktop"); } return Result; } //////////////////////////////////////////////////////////////////////////// // Prop List Options #ifdef __PROP_H char *GetExtension(ObjProperties *p) { return "r"; } bool SetOpt(ObjProperties *p, char *opt, char *str) { return p ? p->Set(opt, str) : false; } bool GetOpt(ObjProperties *p, char *opt, LVariant &v) { char *str; if (p && p->Get(opt, str)) { v = str; return true; } return false; } bool SerializeOpts(ObjProperties *p, bool Write) { bool Status = false; if (p) { LFile f; if (f.Open(OptionsFile, Write?O_WRITE:O_READ)) { if (Write) f.SetSize(0); Status = p->Serialize(f, Write); } } return Status; } #endif }; ////////////////////////////////////////////////////////////////////////////////////////// template LDocApp::LDocApp(const char *appname, LIcon icon, char *optsname) { Options = 0; _LangOptsName = 0; d = new LDocAppPrivate(this, optsname); LRect r(0, 0, 800, 600); SetPos(r); MoveToCenter(); _FileMenu = 0; d->AppName = NewStr(appname?appname:(char*)"Lgi.LDocApp"); char p[MAX_PATH_LEN]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) { LMakePath(p, sizeof(p), p, "_write_test.txt"); LFile f; if (!f.Open(p, O_WRITE)) { d->Mode = InstallDesktop; } else { f.Close(); FileDev->Delete(p, false); } } SetQuitOnClose(true); // Setup class if (icon) { #if defined WIN32 LWindowsClass *c = LWindowsClass::Create(d->AppName); if (c) { if (icon < 0x10000) c->Class.hIcon = LoadIcon(LProcessInst(), MAKEINTRESOURCE(icon)); else c->Class.hIcon = LoadIcon(LProcessInst(), (TCHAR*)(size_t)icon); } #else SetIcon(icon); #endif } } template LDocApp::~LDocApp() { DeleteObj(d); DeleteObj(Options); } template GDocAppInstallMode LDocApp::GetInstallMode() { return d->Mode; } template bool LDocApp::SetLanguage(char *LangId) { if (!_LangOptsName) return false; if (LgiMsg( this, LLoadString(L_DOCAPP_RESTART_APP, "Changing the language requires restarting the application.\nDo you want to restart?"), d->AppName, MB_YESNO) != IDYES) return false; LVariant v; GetOptions()->SetValue(_LangOptsName, v = LangId); GetOptions()->SerializeFile(true); LCloseApp(); LExecute(LGetExeFile()); return true; } template char *LDocApp::GetOptionsFileName() { return d->OptionsFile; } template void LDocApp::_Close() { Empty(); SetCurFile(0); } template bool LDocApp::_DoSerialize(bool Write) { bool Status = false; if (!d->OptionsFile) { const char *Ext = d->GetExtension(Options); d->OptionsFile = d->GetOptionsFile(Ext); } if (!Options) { if (LFileExists(d->OptionsFile)) Options = new OptionsFmt(d->OptionsFile); else Options = new OptionsFmt(LOptionsFile::PortableMode, d->OptionsParam); } if (Write) { // Save window position SerializeState(Options, "Pos", false); // save misc options SerializeOptions(Options, true); LOptionsFile *Of = dynamic_cast(Options); if (Of) Of->CreateTag("Mru"); LMru::Serialize(Options, "Mru", true); } // do the work Status = _SerializeFile(Write); if (!Write) { LVariant Lang; if (_LangOptsName && Options->GetValue(_LangOptsName, Lang)) { LAppInst->SetConfig("language", Lang.Str()); } // read misc options LMru::Serialize(Options, "Mru", false); SerializeOptions(Options, false); // window pos SerializeState(Options, "Pos", true); } return Status; } template bool LDocApp::_SerializeFile(bool Write) { return d->SerializeOpts(Options, Write); } template bool LDocApp::_Create(LIcon IconResource) { // Load options _DoSerialize(false); // Create and setup the main application window #ifdef WIN32 HICON hIcon = NULL; if (IconResource) hIcon = LoadIcon(LProcessInst(), MAKEINTRESOURCE(IconResource)); CreateClassW32(d->AppName, hIcon); #endif if (Attach(0)) { Name(d->AppName); if (X() < 1) { LRect r(100, 100, 600, 500); SetPos(r); } } else { LAssert(!"Window create failed."); return false; } return true; } template bool LDocApp::_Destroy() { // Save options _DoSerialize(true); LAppInst->AppWnd = 0; return true; } template bool LDocApp::_LoadMenu(const char *Resource, const char *Tags, int FileMenuId, int RecentMenuId) { if ((Menu = new LMenu)) { Menu->Attach(this); if (Resource) { Menu->Load(this, Resource, Tags); if (FileMenuId >= 0) _FileMenu = Menu->FindSubMenu(FileMenuId); else _FileMenu = Menu->AppendSub("&File", 0); } else { _FileMenu = Menu->AppendSub("&File"); } if (_FileMenu) { int Idx = 0; if (!_FileMenu->FindItem(IDM_OPEN)) _FileMenu->AppendItem("&Open", IDM_OPEN, true, Idx++, "CtrlCmd+O"); if (!_FileMenu->FindItem(IDM_SAVE)) _FileMenu->AppendItem("&Save", IDM_SAVE, true, Idx++, "CtrlCmd+S"); if (!_FileMenu->FindItem(IDM_SAVEAS)) _FileMenu->AppendItem("Save &As", IDM_SAVEAS, true, Idx++); if (!_FileMenu->FindItem(IDM_CLOSE)) { _FileMenu->AppendItem("Close", IDM_CLOSE, true, Idx++, "CtrlCmd+W"); _FileMenu->AppendSeparator(Idx++); } LSubMenu *Recent = NULL; if (RecentMenuId >= 0) Recent = _FileMenu->FindSubMenu(RecentMenuId); else Recent = _FileMenu->AppendSub("Recent...", Idx++); if (Recent) { Set(Recent); //_FileMenu->AppendSeparator(); LMru::Serialize(Options, "Mru", false); } if (!_FileMenu->FindItem(IDM_EXIT)) _FileMenu->AppendItem("&Quit", IDM_EXIT, true, -1, "CtrlCmd+Q"); } } return Menu != 0; } template bool LDocApp::_OpenFile(const char *File, bool ReadOnly) { bool Status = false; if (SetDirty(false)) { char RealPath[256]; if (LResolveShortcut(File, RealPath, sizeof(RealPath))) { File = RealPath; } Status = LMru::_OpenFile(File, ReadOnly); if (Status) { d->Dirty = false; SetCurFile(File); } } return Status; } template bool LDocApp::_SaveFile(const char *File) { char RealPath[256]; if (LResolveShortcut(File, RealPath, sizeof(RealPath))) { File = RealPath; } else { strcpy_s(RealPath, sizeof(RealPath), File); } bool Status = LMru::_SaveFile(RealPath); if (Status) { d->Dirty = false; SetCurFile(RealPath); OnDirty(d->Dirty); } return Status; } template char *LDocApp::GetAppName() { return d->AppName; } template void LDocApp::SetCurFile(const char *f) { if (!d->CurFile.Equals(f)) { d->CurFile = f; if (f) AddFile(f); } LString Display; if (SerializeEntry(&Display, &d->CurFile, NULL)) { char s[MAX_PATH_LEN + 100]; if (Display) { sprintf_s(s, sizeof(s), "%s [%s%s]", d->AppName, Display.Get(), d->Dirty ? " changed" : ""); } else if (d->Dirty) { sprintf_s(s, sizeof(s), "%s [changed]", d->AppName); } else { strcpy_s(s, sizeof(s), d->AppName); } Name(s); } } template const char *LDocApp::GetCurFile() { return d->CurFile; } template bool LDocApp::SetDirty(bool Dirty) { if (IsAttached() && (d->Dirty ^ Dirty)) { // Changing... if (Dirty) { // Setting dirty d->Dirty = true; SetCurFile(d->CurFile); } else { // Clearing dirty int Result = LgiMsg(this, LLoadString(L_DOCAPP_SAVE_CHANGE, "Do you want to save your changes?"), d->AppName, MB_YESNOCANCEL); if (Result == IDYES) { if (!ValidStr(d->CurFile)) { - if (!LMru::OnCommand(IDM_SAVEAS)) - return false; + LMru::OnCommand(IDM_SAVEAS, [&](auto status) + { + if (status) + { + d->Dirty = false; + SetCurFile(d->CurFile); + } + }); + return false; } else { _SaveFile(d->CurFile); } } else if (Result == IDCANCEL) { return false; } d->Dirty = false; SetCurFile(d->CurFile); } OnDirty(d->Dirty); } return true; } template bool LDocApp::GetDirty() { return d->Dirty; } template OptionsFmt *LDocApp::GetOptions() { return Options; } template void LDocApp::OnReceiveFiles(LArray &Files) { const char *f = Files.Length() ? Files[0] : 0; if (f && _OpenFile(f, false)) { LMru::AddFile(f); } } template bool LDocApp::OnRequestClose(bool OsShuttingDown) { if (SetDirty(false)) { return LWindow::OnRequestClose(OsShuttingDown); } return false; } template int LDocApp::OnCommand(int Cmd, int Event, OsView Window) { switch (Cmd) { case IDM_SAVE: { if (!GetCurFile()) { - LMru::OnCommand(IDM_SAVEAS); + LMru::OnCommand(IDM_SAVEAS, NULL); return 0; } else { _SaveFile(GetCurFile()); return 0; } break; } case IDM_CLOSE: { if (SetDirty(false)) _Close(); break; } case IDM_EXIT: { LCloseApp(); break; } } - LMru::OnCommand(Cmd); + LMru::OnCommand(Cmd, NULL); return 0; } template LMessage::Result LDocApp::OnEvent(LMessage *m) { LMru::OnEvent(m); /* switch (MsgCode(m)) { #ifdef WIN32 case WM_CLOSE: { if (!SetDirty(false)) { return 0; } break; } #endif } */ return LWindow::OnEvent(m); } template class LDocApp; #ifdef __PROP_H template class LDocApp; #endif diff --git a/src/common/Lgi/FileSelect.cpp b/src/common/Lgi/FileSelect.cpp --- a/src/common/Lgi/FileSelect.cpp +++ b/src/common/Lgi/FileSelect.cpp @@ -1,2222 +1,2256 @@ /*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 = NULL; LFileSelect *Select = NULL; DlgType Type = TypeNone; LString Title; LString DefExt; bool MultiSelect = false; List Files; int CurrentType = -1; List Types; List History; bool ShowReadOnly = false; bool ReadOnly = false; bool EatClose = false; public: static LImageList *BtnIcons, *TreeIcons; static LString InitPath; static bool InitShowHiddenFiles; static LRect InitSize; LFileSelectPrivate(LFileSelect *select) { Select = select; 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() { Types.DeleteObjects(); Files.DeleteArrays(); History.DeleteArrays(); } }; LImageList *LFileSelectPrivate::BtnIcons = NULL; LImageList *LFileSelectPrivate::TreeIcons = NULL; 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); void OnFolder(); bool OnKey(LKey &k); void SetFilterKey(LString s) { FilterKey = s; OnFolder(); } }; 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)) 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"); - if (Dlg.DoModal()) + Dlg.DoModal([&](auto d, auto code) { - char New[256]; + 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(Dlg, File, "New name:", Dlg->Name()); - if (Inp.DoModal()) + LInput *Inp = new LInput(Dlg, File, "New name:", Dlg->Name()); + Inp->DoModal([&](auto d, auto code) { - char Old[256]; - strcpy(Old, Path); + if (!code) + return; + + char Old[MAX_PATH_LEN]; + strcpy_s(Old, sizeof(Old), Path); - char New[256]; + char New[MAX_PATH_LEN]; File[0] = 0; - LMakePath(New, sizeof(New), Path, Inp.GetStr()); + 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 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 (size_t 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(LFolderItemCompare); // Display items... Insert(New); } ////////////////////////////////////////////////////////////////////////// -LFileSelect::LFileSelect() +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) \ { \ Var = i; \ } CharPropImpl(InitialDir, d->InitPath); CharPropImpl(Title, d->Title); CharPropImpl(DefaultExtension, d->DefExt); -bool LFileSelect::Open() +void LFileSelect::Open(SelectCb Cb) { - LFileSelectDlg Dlg(d); + LFileSelectDlg *Dlg = new LFileSelectDlg(d); d->Type = TypeOpenFile; - Dlg.Name("Open"); - if (Dlg.SaveBtn) - Dlg.SaveBtn->Name("Open"); + Dlg->Name("Open"); + if (Dlg->SaveBtn) + Dlg->SaveBtn->Name("Open"); - return Dlg.DoModal() == IDOK; + // 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"); + }); } -bool LFileSelect::OpenFolder() +void LFileSelect::OpenFolder(SelectCb Cb) { - LFileSelectDlg Dlg(d); + 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->SaveBtn->Enabled(true); + Dlg->FileNameEdit->Enabled(false); + Dlg->Name("Open Folder"); + Dlg->SaveBtn->Name("Open"); - return Dlg.DoModal() == IDOK; + printf("LFileSelect::OpenFolder domodal...\n"); + Dlg->DoModal([this,Cb](auto d, auto code) + { + printf("LFileSelect::OpenFolder cb, code=%i\n", code); + if (Cb) + Cb(this, code != IDOK); + delete d; + }); } -bool LFileSelect::Save() +void LFileSelect::Save(SelectCb Cb) { - LFileSelectDlg Dlg(d); + auto *Dlg = new LFileSelectDlg(d); d->Type = TypeSaveFile; - Dlg.Name("Save As"); - Dlg.SaveBtn->Name("Save As"); + Dlg->Name("Save As"); + Dlg->SaveBtn->Name("Save As"); - return Dlg.DoModal() == IDOK; + // 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; - LFindReplaceCallback Callback; + LFindReplaceCommon::Callback Callback; void *CallbackData; }; -LFindDlg::LFindDlg(LView *Parent, char *Init, LFindReplaceCallback Callback, void *UserData) +LFindDlg::LFindDlg(LView *Parent, Callback Cb, const char *Init) { d = new LFindDlgPrivate; if (Init) Find = Init; - d->Callback = Callback; - d->CallbackData = UserData; + d->Callback = Cb; SetParent(Parent); Name(LLoadString(L_FR_FIND, "Find")); LRect r(0, 0, 450, 370); 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, false, d->CallbackData); + 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: - LFindReplaceCallback Callback; + LFindReplaceCommon::Callback Callback; void *CallbackData; }; -LReplaceDlg::LReplaceDlg(LView *Parent, char *InitFind, char *InitReplace, LFindReplaceCallback Callback, void *UserData) +LReplaceDlg::LReplaceDlg(LView *Parent, Callback Cb, char *InitFind, char *InitReplace) { d = new LReplaceDlgPrivate; - d->Callback = Callback; - d->CallbackData = UserData; + 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); 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() == IDC_FR_REPLACE, d->CallbackData); + 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/LMsg.cpp b/src/common/Lgi/LMsg.cpp --- a/src/common/Lgi/LMsg.cpp +++ b/src/common/Lgi/LMsg.cpp @@ -1,423 +1,423 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/TextLog.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #if defined(HAIKU) #include #endif #if defined(__GTK_H__) #include "gtk/gtkdialog.h" void MsgCb(Gtk::GtkDialog *dialog, Gtk::gint response_id, Gtk::gpointer user_data) { *((int*)user_data) = response_id; } #endif #if defined(__GTK_H__) || defined(MAC) || defined(LGI_SDL) || defined(HAIKU) #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #if LGI_COCOA #import #endif class LMsgDlg : public LDialog { public: LMsgDlg() { RegisterHook(this, LKeyEvents); } bool OnViewKey(LView *v, LKey &k) { if (k.Down()) { int Id = -1; switch (k.c16) { case 'y': case 'Y': { Id = IDYES; break; } case 'n': case 'N': { Id = IDNO; break; } case 'c': case 'C': { Id = IDCANCEL; break; } case 'o': case 'O': { Id = IDOK; break; } } if (Id >= 0) { LViewI *c = FindControl(Id); if (c) { EndModal(c->GetId()); } } } return LDialog::OnViewKey(v, k); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: case IDCANCEL: case IDYES: case IDNO: { EndModal(Ctrl->GetId()); break; } } return 0; } }; #endif int LgiMsg(LViewI *Parent, const char *Str, const char *Title, int Type, ...) { int Res = 0; va_list Arg; va_start(Arg, Type); LString Msg; Msg.Printf(Arg, Str); va_end(Arg); #if WINNATIVE if (Str) { if (LGetOs() == LGI_OS_WIN9X) { auto t = LToNativeCp(Title ? Title : (char*)"Message"); auto m = LToNativeCp(Msg); Res = MessageBoxA(Parent ? Parent->Handle() : 0, m?m:"", t?t:"", Type); if (Res == 0) { auto Err = GetLastError(); LAssert(!"MessageBoxA failed."); } } else { char16 *t = Utf8ToWide(Title ? Title : (char*)"Message"); char16 *m = Utf8ToWide(Msg); Res = MessageBoxW(Parent ? Parent->Handle() : 0, m?m:L"", t?t:L"", Type); if (Res == 0) { auto Err = GetLastError(); LAssert(!"MessageBoxW failed."); } DeleteArray(t); DeleteArray(m); } } #elif defined(HAIKU) int32 result = 0; BAlert *a = NULL; switch (Type) { default: case MB_OK: a = new BAlert(Title, Msg, "Ok", NULL, NULL); break; case MB_OKCANCEL: a = new BAlert(Title, Msg, "Ok", "Cancel", NULL); break; case MB_YESNO: a = new BAlert(Title, Msg, "Yes", "No", NULL); break; case MB_YESNOCANCEL: a = new BAlert(Title, Msg, "Yes", "No", "Cancel"); break; } if (a) { printf("DoModal(%s,%s)\n", Title, Msg.Get()); result = a->Go(); printf("result=%i\n", result); } if (result < 0) return IDCANCEL; switch (Type) { default: case MB_OK: return IDOK; case MB_OKCANCEL: return result ? IDCANCEL : IDOK; case MB_YESNO: return result ? IDNO : IDYES; case MB_YESNOCANCEL: if (result == 0) return IDYES; if (result == 1) return IDNO; return IDCANCEL; } #elif LGI_COCOA NSAlert *alert = [[NSAlert alloc] init]; auto msg = Msg.NsStr(); auto title = LString(Title).NsStr(); [alert setMessageText:msg]; [alert setInformativeText:title]; switch (Type & ~MB_SYSTEMMODAL) { default: case MB_OK: { [alert addButtonWithTitle:@"Ok"]; break; } case MB_OKCANCEL: { [alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"Ok"]; break; } case MB_YESNO: { [alert addButtonWithTitle:@"No"]; [alert addButtonWithTitle:@"Yes"]; break; } case MB_YESNOCANCEL: { [alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"No"]; [alert addButtonWithTitle:@"Yes"]; break; } } auto r = [alert runModal]; [msg release]; [title release]; Res = IDOK; switch (Type & ~MB_SYSTEMMODAL) { default: case MB_OK: break; case MB_OKCANCEL: { if (r == NSAlertFirstButtonReturn) return IDCANCEL; else if (r == NSAlertSecondButtonReturn) return IDOK; else LAssert(0); break; } case MB_YESNO: { if (r == NSAlertFirstButtonReturn) return IDNO; else if (r == NSAlertSecondButtonReturn) return IDYES; else LAssert(0); break; } case MB_YESNOCANCEL: { if (r == NSAlertFirstButtonReturn) return IDCANCEL; else if (r == NSAlertSecondButtonReturn) return IDNO; else if (r == NSAlertThirdButtonReturn) return IDYES; else LAssert(0); break; } } #elif defined(__GTK_H__) using namespace Gtk; GtkButtonsType GtkType; switch (Type & ~MB_SYSTEMMODAL) { default: case MB_OK: GtkType = GTK_BUTTONS_OK; break; case MB_OKCANCEL: GtkType = GTK_BUTTONS_OK_CANCEL; break; case MB_YESNO: case MB_YESNOCANCEL: // ugh, soz GtkType = GTK_BUTTONS_YES_NO; break; } GtkWidget *dlg = gtk_message_dialog_new( Parent ? Parent->WindowHandle() : NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GtkType, "%s", Msg.Get()); if (dlg) { if (Type == MB_YESNOCANCEL) gtk_dialog_add_button(GTK_DIALOG(dlg), "Cancel", GTK_RESPONSE_CANCEL); int Response = 0; g_signal_connect(GTK_DIALOG(dlg), "response", G_CALLBACK(MsgCb), &Response); gtk_dialog_run(GTK_DIALOG(dlg)); gtk_widget_destroy(dlg); switch (Response) { case GTK_RESPONSE_NONE: case GTK_RESPONSE_REJECT: case GTK_RESPONSE_ACCEPT: case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CLOSE: case GTK_RESPONSE_APPLY: case GTK_RESPONSE_HELP: break; case GTK_RESPONSE_OK: Res = IDOK; break; case GTK_RESPONSE_CANCEL: Res = IDCANCEL; break; case GTK_RESPONSE_YES: Res = IDYES; break; case GTK_RESPONSE_NO: Res = IDNO; break; } } #else // Lgi only controls (used for Linux + Mac) if (Str && LAppInst) { LMsgDlg Dlg; Dlg.SetParent(Parent); Dlg.Name((char*)(Title ? Title : "Message")); LTextLabel *Text = new LTextLabel(-1, 10, 10, -1, -1, Msg); Dlg.AddView(Text); List Btns; #ifdef LGI_TOUCHSCREEN float Scale = 1.6f; #else float Scale = 1.0f; #endif int BtnY = (int) (Scale * 20.0f); int BtnX = (int) (Scale * 70.0f); switch (Type & ~MB_SYSTEMMODAL) { default: case MB_OK: { Btns.Insert(new LButton(IDOK, 10, 40, BtnX, BtnY, "Ok")); break; } case MB_OKCANCEL: { Btns.Insert(new LButton(IDOK, 10, 40, BtnX, BtnY, "Ok")); Btns.Insert(new LButton(IDCANCEL, 10, 40, BtnX, BtnY, "Cancel")); break; } case MB_YESNO: { Btns.Insert(new LButton(IDYES, 10, 40, BtnX, BtnY, "Yes")); Btns.Insert(new LButton(IDNO, 10, 40, BtnX, BtnY, "No")); break; } case MB_YESNOCANCEL: { Btns.Insert(new LButton(IDYES, 10, 40, BtnX, BtnY, "Yes")); Btns.Insert(new LButton(IDNO, 10, 40, BtnX, BtnY, "No")); Btns.Insert(new LButton(IDCANCEL, 10, 40, BtnX, BtnY, "Cancel")); break; } } int BtnsX = (int) ((Btns.Length() * BtnX) + ((Btns.Length()-1) * 10)); int MaxX = MAX(BtnsX, Text->X()); LRect p(0, 0, MaxX + 30, Text->Y() + 30 + BtnY + LAppInst->GetMetric(LGI_MET_DECOR_Y) ); Dlg.SetPos(p); Dlg.MoveToCenter(); int x = (p.X() - BtnsX) / 2; int y = Text->Y() + 20; for (auto b: Btns) { LRect r(x, y, x+BtnX-1, y+BtnY-1); b->SetPos(r); Dlg.AddView(b); x += r.X() + 10; } return Dlg.DoModal(); } #endif return Res; } #if !LGI_STATIC void LDialogTextMsg(LViewI *Parent, const char *Title, LString Txt) { LAutoPtr d(new LDialog); if (d) { LTextLog *Log = NULL; d->SetParent(Parent); d->Name(Title); LRect r(0, 0, 600, 500); d->SetPos(r); d->MoveSameScreen(Parent); LTableLayout *t = new LTableLayout(100); auto c = t->GetCell(0, 0); c->Add(Log = new LTextLog(101)); Log->Name(Txt); c = t->GetCell(0, 1); c->Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); c->TextAlign(LCss::AlignCenter); d->AddView(t); - d->DoModal(); + d->DoModal(NULL); } } #endif diff --git a/src/common/Lgi/LgiCommon.cpp b/src/common/Lgi/LgiCommon.cpp --- a/src/common/Lgi/LgiCommon.cpp +++ b/src/common/Lgi/LgiCommon.cpp @@ -1,2802 +1,2841 @@ // // Cross platform LGI functions // #if LGI_COCOA #import #endif #define _WIN32_WINNT 0x501 #include #include #include #include #ifdef WINDOWS #include #include "lgi/common/RegKey.h" #include #include #else #include #define _getcwd getcwd #endif #include "lgi/common/Lgi.h" #include "lgi/common/Capabilities.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #elif defined(WINDOWS) #include "lgi/common/RegKey.h" #endif #if defined POSIX #include #include #include #include #include "lgi/common/SubProcess.h" #endif #ifdef HAIKU #include #include #else #include "SymLookup.h" #endif #include "lgi/common/Library.h" #include "lgi/common/Net.h" #if defined(__GTK_H__) namespace Gtk { #include "LgiWidget.h" } #endif ////////////////////////////////////////////////////////////////////////// // Misc stuff #if LGI_COCOA || defined(__GTK_H__) LString LgiArgsAppPath; #endif #if defined MAC #import #if defined LGI_CARBON bool _get_path_FSRef(FSRef &fs, LStringPipe &a) { HFSUniStr255 Name; ZeroObj(Name); FSRef Parent; FSCatalogInfo Cat; ZeroObj(Cat); OSErr e = FSGetCatalogInfo(&fs, kFSCatInfoVolume|kFSCatInfoNodeID, &Cat, &Name, NULL, &Parent); if (!e) { if (_get_path_FSRef(Parent, a)) { LAutoString u((char*)LNewConvertCp("utf-8", Name.unicode, "utf-16", Name.length * sizeof(Name.unicode[0]) )); // printf("CatInfo = '%s' %x %x\n", u.Get(), Cat.nodeID, Cat.volume); if (u && Cat.nodeID > 2) { a.Print("%s%s", DIR_STR, u.Get()); } } return true; } return false; } LAutoString FSRefPath(FSRef &fs) { LStringPipe a; if (_get_path_FSRef(fs, a)) { return LAutoString(a.NewStr()); } return LAutoString(); } #endif #endif bool LPostEvent(OsView Wnd, int Event, LMessage::Param a, LMessage::Param b) { #if LGI_SDL SDL_Event e; e.type = SDL_USEREVENT; e.user.code = Event; e.user.data1 = Wnd; e.user.data2 = a || b ? new LMessage::EventParams(a, b) : NULL; /* printf("LPostEvent Wnd=%p, Event=%i, a/b: %i/%i\n", Wnd, Event, (int)a, (int)b); */ return SDL_PushEvent(&e) == 0; #elif WINNATIVE return PostMessage(Wnd, Event, a, b) != 0; #elif defined(__GTK_H__) LAssert(Wnd); LViewI *View = (LViewI*) g_object_get_data(GtkCast(Wnd, g_object, GObject), "LViewI"); if (View) { LMessage m(0); m.Set(Event, a, b); return m.Send(View); } else printf("%s:%i - Error: LPostEvent can't cast OsView to LViewI\n", _FL); #elif defined(MAC) && !LGI_COCOA #if 0 int64 Now = LCurrentTime(); static int64 Last = 0; static int Count = 0; Count++; if (Now > Last + 1000) { printf("Sent %i events in the last %ims\n", Count, (int)(Now-Last)); Last = Now; Count = 0; } #endif EventRef Ev; OSStatus e = CreateEvent(NULL, kEventClassUser, kEventUser, 0, // EventTime kEventAttributeNone, &Ev); if (e) { printf("%s:%i - CreateEvent failed with %i\n", _FL, (int)e); } else { EventTargetRef t = GetControlEventTarget(Wnd); e = SetEventParameter(Ev, kEventParamLgiEvent, typeUInt32, sizeof(Event), &Event); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiA, typeUInt32, sizeof(a), &a); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiB, typeUInt32, sizeof(b), &b); if (e) printf("%s:%i - error %i\n", _FL, (int)e); bool Status = false; EventQueueRef q = GetMainEventQueue(); e = SetEventParameter(Ev, kEventParamPostTarget, typeEventTargetRef, sizeof(t), &t); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = PostEventToQueue(q, Ev, kEventPriorityStandard); if (e) printf("%s:%i - error %i\n", _FL, (int)e); else Status = true; // printf("PostEventToQueue %i,%i,%i -> %p\n", Event, a, b, q); ReleaseEvent(Ev); return Status; } #else LAssert(!"Not impl."); #endif return false; } void LExitApp() { exit(0); } ////////////////////////////////////////////////////////////////////////// #ifdef WIN32 bool RegisterActiveXControl(char *Dll) { LLibrary Lib(Dll); if (Lib.IsLoaded()) { #ifdef _MSC_VER typedef HRESULT (STDAPICALLTYPE *p_DllRegisterServer)(void); p_DllRegisterServer DllRegisterServer = (p_DllRegisterServer)Lib.GetAddress("DllRegisterServer"); if (DllRegisterServer) { return DllRegisterServer() == S_OK; } #else LAssert(!"Not impl."); #endif } return false; } #endif ////////////////////////////////////////////////////////////////////////// #ifdef WINDOWS #include #pragma comment(lib, "netapi32.lib") #endif /// \brief Returns the operating system that Lgi is running on. /// \sa Returns one of the defines starting with LGI_OS_UNKNOWN in LgiDefs.h int LGetOs ( /// Returns the version of the OS or NULL if you don't care LArray *Ver ) { #if defined(WIN32) || defined(WIN64) - static int Os = LGI_OS_UNKNOWN; - static int Version = 0, Revision = 0; + static int Os = LGI_OS_UNKNOWN; + static int Version = 0, Revision = 0; - if (Os == LGI_OS_UNKNOWN) - { - #if defined(WIN64) - BOOL IsWow64 = TRUE; - #elif defined(WIN32) - BOOL IsWow64 = FALSE; - IsWow64Process(GetCurrentProcess(), &IsWow64); - #endif - - SERVER_INFO_101 *v = NULL; - auto r = NetServerGetInfo(NULL, 101, (LPBYTE*)&v); - if (r == NERR_Success) + if (Os == LGI_OS_UNKNOWN) { - Version = v->sv101_version_major; - Revision = v->sv101_version_minor; - Os = (v->sv101_version_major >= 6) - ? - #ifdef WIN32 - (IsWow64 ? LGI_OS_WIN64 : LGI_OS_WIN32) - #else - LGI_OS_WIN64 - #endif - : - LGI_OS_WIN9X; + #if defined(WIN64) + BOOL IsWow64 = TRUE; + #elif defined(WIN32) + BOOL IsWow64 = FALSE; + IsWow64Process(GetCurrentProcess(), &IsWow64); + #endif - NetApiBufferFree(v); - } - else LAssert(0); - } + SERVER_INFO_101 *v = NULL; + auto r = NetServerGetInfo(NULL, 101, (LPBYTE*)&v); + if (r == NERR_Success) + { + Version = v->sv101_version_major; + Revision = v->sv101_version_minor; + Os = (v->sv101_version_major >= 6) + ? + #ifdef WIN32 + (IsWow64 ? LGI_OS_WIN64 : LGI_OS_WIN32) + #else + LGI_OS_WIN64 + #endif + : + LGI_OS_WIN9X; - if (Ver) - { - Ver->Add(Version); - Ver->Add(Revision); - } + NetApiBufferFree(v); + } + else LAssert(0); + } - return Os; + if (Ver) + { + Ver->Add(Version); + Ver->Add(Revision); + } + + return Os; #elif defined LINUX - if (Ver) - { - utsname Buf; - if (!uname(&Buf)) + if (Ver) { - auto t = LString(Buf.release).SplitDelimit("."); - for (int i=0; iAdd(atoi(t[i])); + auto t = LString(Buf.release).SplitDelimit("."); + for (int i=0; iAdd(atoi(t[i])); + } } } - } - return LGI_OS_LINUX; + return LGI_OS_LINUX; #elif defined MAC - #if !defined(__GTK_H__) - if (Ver) - { - NSOperatingSystemVersion v = [[NSProcessInfo processInfo] operatingSystemVersion]; - Ver->Add((int)v.majorVersion); - Ver->Add((int)v.minorVersion); - Ver->Add((int)v.patchVersion); - } - #endif + #if !defined(__GTK_H__) + if (Ver) + { + NSOperatingSystemVersion v = [[NSProcessInfo processInfo] operatingSystemVersion]; + Ver->Add((int)v.majorVersion); + Ver->Add((int)v.minorVersion); + Ver->Add((int)v.patchVersion); + } + #endif + + return LGI_OS_MAC_OS_X; + + #elif defined HAIKU - return LGI_OS_MAC_OS_X; + return LGI_OS_HAIKU; #else - return LGI_OS_UNKNOWN; + #error "Impl Me" + return LGI_OS_UNKNOWN; #endif } const char *LGetOsName() { const char *Str[LGI_OS_MAX] = { "Unknown", "Win9x", "Win32", "Win64", "Haiku", "Linux", "MacOSX", }; - return Str[LGetOs()]; + auto Os = LGetOs(); + if (Os > 0 && Os < CountOf(Str)) + return Str[Os]; + + LAssert(!"Invalid OS index."); + return "error"; } #ifdef WIN32 #define RecursiveFileSearch_Wildcard "*.*" #else // unix'ish OS #define RecursiveFileSearch_Wildcard "*" #endif bool LRecursiveFileSearch(const char *Root, LArray *Ext, LArray *Files, uint64 *Size, uint64 *Count, std::function Callback, LCancel *Cancel) { // validate args if (!Root) return false; // get directory enumerator LDirectory Dir; bool Status = true; // enumerate the directory contents for (auto Found = Dir.First(Root); Found && (!Cancel || !Cancel->IsCancelled()); Found = Dir.Next()) { char Name[300]; if (!Dir.Path(Name, sizeof(Name))) continue; if (Callback && !Callback(Name, &Dir)) continue; if (Dir.IsDir()) { // dir LRecursiveFileSearch( Name, Ext, Files, Size, Count, Callback, Cancel); } else { // process file bool Match = true; // if no Ext's then default to match if (Ext) { for (int i=0; iLength(); i++) { const char *e = (*Ext)[i]; char *RawFile = strrchr(Name, DIR_CHAR); if (RawFile && (Match = MatchStr(e, RawFile+1))) { break; } } } if (Match) { // file matched... process: if (Files) Files->Add(NewStr(Name)); if (Size) *Size += Dir.GetSize(); if (Count) (*Count)++; Status = true; } } } return Status; } #define LGI_TRACE_TO_FILE // #include #ifndef WIN32 #define _vsnprintf vsnprintf #endif static LStreamI *_LgiTraceStream = NULL; void LgiTraceSetStream(LStreamI *stream) { _LgiTraceStream = stream; } bool LgiTraceGetFilePath(char *LogPath, int BufLen) { if (!LogPath) return false; auto Exe = LGetExeFile(); if (Exe) { #ifdef MAC char *Dir = strrchr(Exe, DIR_CHAR); if (Dir) { char Part[256]; strcpy_s(Part, sizeof(Part), Dir+1); LMakePath(LogPath, BufLen, "~/Library/Logs", Dir+1); strcat_s(LogPath, BufLen, ".txt"); } else #endif { char *Dot = strrchr(Exe, '.'); if (Dot && !strchr(Dot, DIR_CHAR)) sprintf_s(LogPath, BufLen, "%.*s.txt", (int)(Dot - Exe.Get()), Exe.Get()); else sprintf_s(LogPath, BufLen, "%s.txt", Exe.Get()); } LFile f; if (f.Open(LogPath, O_WRITE)) { f.Close(); } else { char Leaf[64]; char *Dir = strrchr(LogPath, DIR_CHAR); if (Dir) { strcpy_s(Leaf, sizeof(Leaf), Dir + 1); LGetSystemPath(LSP_APP_ROOT, LogPath, BufLen); if (!LDirExists(LogPath)) FileDev->CreateFolder(LogPath); LMakePath(LogPath, BufLen, LogPath, Leaf); } else goto OnError; } #if 0 LFile tmp; if (tmp.Open("c:\\temp\\log.txt", O_WRITE)) { tmp.SetSize(0); tmp.Write(LogPath, strlen(LogPath)); } #endif } else { // Well what to do now? I give up OnError: strcpy_s(LogPath, BufLen, "trace.txt"); return false; } return true; } void LgiTrace(const char *Msg, ...) { #if defined _INC_MALLOC && WINNATIVE if (_heapchk() != _HEAPOK) return; #endif if (!Msg) return; #ifdef WIN32 static LMutex Sem("LgiTrace"); Sem.Lock(_FL, true); #endif char Buffer[2049] = ""; #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream && LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); #endif va_list Arg; va_start(Arg, Msg); #ifdef LGI_TRACE_TO_FILE int Ch = #endif vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); #ifdef LGI_TRACE_TO_FILE LStreamI *Output = NULL; if (_LgiTraceStream) Output = _LgiTraceStream; else { if (!f.IsOpen() && f.Open(LogPath, O_WRITE)) f.Seek(0, SEEK_END); Output = &f; } if (Output && Ch > 0) { Output->ChangeThread(); Output->Write(Buffer, Ch); } if (!_LgiTraceStream) { #ifdef WINDOWS // Windows can take AGES to close a file when there is anti-virus on, like 100ms. // We can't afford to wait here so just keep the file open but flush the // buffers if we can. FlushFileBuffers(f.Handle()); #else f.Close(); #endif } #endif #if defined WIN32 OutputDebugStringA(Buffer); Sem.Unlock(); #else printf("%s", Buffer); #endif } #ifndef LGI_STATIC #define STACK_SIZE 12 void LStackTrace(const char *Msg, ...) { #ifndef HAIKU LSymLookup::Addr Stack[STACK_SIZE]; ZeroObj(Stack); LSymLookup *Lu = LAppInst ? LAppInst->GetSymLookup() : NULL; if (!Lu) { printf("%s:%i - Failed to get sym lookup object.\n", _FL); return; } int Frames = Lu ? Lu->BackTrace(0, 0, Stack, STACK_SIZE) : 0; if (Msg) { #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream) { if (LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); f.Open(LogPath, O_WRITE); } #endif va_list Arg; va_start(Arg, Msg); char Buffer[2049] = ""; int Len = vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); Lu->Lookup(Buffer+Len, sizeof(Buffer)-Len-1, Stack, Frames); #ifdef LGI_TRACE_TO_FILE if (f.IsOpen()) { f.Seek(0, SEEK_END); f.Write(Buffer, (int)strlen(Buffer)); f.Close(); } #endif #if defined WIN32 OutputDebugStringA(Buffer); #else printf("Trace: %s", Buffer); #endif } #endif } #endif bool LTrimDir(char *Path) { if (!Path) return false; char *p = strrchr(Path, DIR_CHAR); if (!p) return false; if (p[1] == 0) // Trailing DIR_CHAR doesn't count... do it again. { *p = 0; p = strrchr(Path, DIR_CHAR); if (!p) return false; } *p = 0; return true; } LString LMakeRelativePath(const char *Base, const char *Path) { LStringPipe Status; if (Base && Path) { #ifdef WIN32 bool SameNs = strnicmp(Base, Path, 3) == 0; #else bool SameNs = true; #endif if (SameNs) { auto b = LString(Base + 1).SplitDelimit(":\\/"); auto p = LString(Path + 1).SplitDelimit(":\\/"); int Same = 0; while (Same < b.Length() && Same < p.Length() && stricmp(b[Same], p[Same]) == 0) { Same++; } for (int i = Same; i= b.Length()) { Status.Print(".%s", DIR_STR); } for (int n = Same; n Same) { Status.Push(DIR_STR); } Status.Push(p[n]); } } } return Status.NewGStr(); } bool LIsRelativePath(const char *Path) { if (!Path) return false; if (*Path == '.') return true; #ifdef WIN32 if (IsAlpha(Path[0]) && Path[1] == ':') // Drive letter path return false; #endif if (*Path == DIR_CHAR) return false; if (strstr(Path, "://")) // Protocol def return false; return true; // Correct or not??? } bool LMakePath(char *Str, int StrSize, const char *Path, const char *File) { if (!Str || StrSize <= 0 || !Path || !File) { printf("%s:%i - Invalid LMakePath(%p,%i,%s,%s) param\n", _FL, Str, StrSize, Path, File); return false; } if (StrSize <= 4) { printf("%s:%i - LgiMakeFile buf size=%i?\n", _FL, StrSize); } if (Str && Path && File) { char Dir[] = { '/', '\\', 0 }; if (Path[0] == '~') { auto Parts = LString(Path).SplitDelimit(Dir, 2); char *s = Str, *e = Str + StrSize; for (auto p: Parts) { if (p.Equals("~")) { LGetSystemPath(LSP_HOME, s, e - s); s += strlen(s); } else s += sprintf_s(s, e - s, "%s%s", DIR_STR, p.Get()); } } else if (Str != Path) { strcpy_s(Str, StrSize, Path); } #define EndStr() Str[strlen(Str)-1] #define EndDir() if (!strchr(Dir, EndStr())) strcat(Str, DIR_STR); size_t Len = strlen(Str); char *End = Str + (Len ? Len - 1 : 0); if (strchr(Dir, *End) && End > Str) { *End = 0; } auto T = LString(File).SplitDelimit(Dir); for (int i=0; i= StrSize - 1) return false; Str[Len++] = DIR_CHAR; Str[Len] = 0; } size_t SegLen = strlen(T[i]); if (Len + SegLen + 1 > StrSize) { return false; } strcpy_s(Str + Len, StrSize - Len, T[i]); } } } return true; } bool LgiGetTempPath(char *Dst, int DstSize) { return LGetSystemPath(LSP_TEMP, Dst, DstSize); } bool LGetSystemPath(LSystemPath Which, char *Dst, ssize_t DstSize) { if (!Dst || DstSize <= 0) return false; LFile::Path p; LString s = p.GetSystem(Which, 0); if (!s) return false; strcpy_s(Dst, DstSize, s); return true; } LString LGetSystemPath(LSystemPath Which, int WordSize) { LFile::Path p; return p.GetSystem(Which, WordSize); } LFile::Path::State LFile::Path::Exists() { if (Length() == 0) return TypeNone; #ifdef WINDOWS struct _stat64 s; int r = _stat64(GetFull(), &s); if (r) return TypeNone; if (s.st_mode & _S_IFDIR) return TypeFolder; if (s.st_mode & _S_IFREG) return TypeFile; #else struct stat s; int r = stat(GetFull(), &s); if (r) return TypeNone; if (S_ISDIR(s.st_mode)) return TypeFolder; if (S_ISREG(s.st_mode)) return TypeFile; #endif return TypeNone; } LString LFile::Path::PrintAll() { LStringPipe p; #define _(name) \ p.Print(#name ": '%s'\n", GetSystem(name).Get()); _(LSP_OS) _(LSP_OS_LIB) _(LSP_TEMP) _(LSP_COMMON_APP_DATA) _(LSP_USER_APP_DATA) _(LSP_LOCAL_APP_DATA) _(LSP_DESKTOP) _(LSP_HOME) _(LSP_USER_APPS) _(LSP_EXE) _(LSP_TRASH) _(LSP_APP_INSTALL) _(LSP_APP_ROOT) _(LSP_USER_DOCUMENTS) _(LSP_USER_MUSIC) _(LSP_USER_VIDEO) _(LSP_USER_DOWNLOADS) _(LSP_USER_LINKS) _(LSP_USER_PICTURES) #undef _ #if LGI_COCOA int Domains[] = {NSUserDomainMask, NSSystemDomainMask}; const char *DomainName[] = {"User", "System"}; for (int i=0; i 0) \ { \ LString s = [paths objectAtIndex:0]; \ p.Print("%s." #name ": '%s'\n", DomainName[i], s.Get()); \ } \ else p.Print("%s." #name ": null\n", DomainName[i]); \ } \ } _(NSApplicationDirectory) _(NSDemoApplicationDirectory) _(NSDeveloperApplicationDirectory) _(NSAdminApplicationDirectory) _(NSLibraryDirectory) _(NSDeveloperDirectory) _(NSUserDirectory) _(NSDocumentationDirectory) _(NSDocumentDirectory) _(NSCoreServiceDirectory) _(NSAutosavedInformationDirectory) _(NSDesktopDirectory) _(NSCachesDirectory) _(NSApplicationSupportDirectory) _(NSDownloadsDirectory) _(NSInputMethodsDirectory) _(NSMoviesDirectory) _(NSMusicDirectory) _(NSPicturesDirectory) _(NSPrinterDescriptionDirectory) _(NSSharedPublicDirectory) _(NSPreferencePanesDirectory) _(NSApplicationScriptsDirectory) _(NSItemReplacementDirectory) _(NSAllApplicationsDirectory) _(NSAllLibrariesDirectory) _(NSTrashDirectory) #undef _ } #endif return p.NewGStr(); } LString LFile::Path::GetSystem(LSystemPath Which, int WordSize) { LString Path; #if defined(WIN32) #ifndef CSIDL_PROFILE #define CSIDL_PROFILE 0x0028 #endif #if !defined(CSIDL_MYDOCUMENTS) #define CSIDL_MYDOCUMENTS 0x0005 #endif #if !defined(CSIDL_MYMUSIC) #define CSIDL_MYMUSIC 0x000d #endif #if !defined(CSIDL_MYVIDEO) #define CSIDL_MYVIDEO 0x000e #endif #if !defined(CSIDL_LOCAL_APPDATA) #define CSIDL_LOCAL_APPDATA 0x001c #endif #if !defined(CSIDL_COMMON_APPDATA) #define CSIDL_COMMON_APPDATA 0x0023 #endif #if !defined(CSIDL_APPDATA) #define CSIDL_APPDATA 0x001a #endif #endif /* #if defined(LINUX) && !defined(LGI_SDL) // Ask our window manager add-on if it knows the path LLibrary *WmLib = LAppInst ? LAppInst->GetWindowManagerLib() : NULL; if (WmLib) { Proc_LgiWmGetPath WmGetPath = (Proc_LgiWmGetPath) WmLib->GetAddress("LgiWmGetPath"); char Buf[MAX_PATH_LEN]; if (WmGetPath && WmGetPath(Which, Buf, sizeof(Buf))) { Path = Buf; return Path; } } #endif */ switch (Which) { default: break; case LSP_USER_DOWNLOADS: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOWNLOAD); Path = p; #elif defined(WIN32) && defined(_MSC_VER) // OMG!!!! Really? #ifndef REFKNOWNFOLDERID typedef GUID KNOWNFOLDERID; #define REFKNOWNFOLDERID const KNOWNFOLDERID & GUID FOLDERID_Downloads = {0x374DE290,0x123F,0x4565,{0x91,0x64,0x39,0xC4,0x92,0x5E,0x46,0x7B}}; #endif LLibrary Shell("Shell32.dll"); typedef HRESULT (STDAPICALLTYPE *pSHGetKnownFolderPath)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); pSHGetKnownFolderPath SHGetKnownFolderPath = (pSHGetKnownFolderPath)Shell.GetAddress("SHGetKnownFolderPath"); if (SHGetKnownFolderPath) { PWSTR ptr = NULL; HRESULT r = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &ptr); if (SUCCEEDED(r)) { LAutoString u8(WideToUtf8(ptr)); if (u8) Path = u8; CoTaskMemFree(ptr); } } if (!Path.Get()) { LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); char *p = k.GetStr("{374DE290-123F-4565-9164-39C4925E467B}"); if (LDirExists(p)) Path = p; } if (!Path.Get()) { LString MyDoc = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); if (MyDoc) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), MyDoc, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } if (!Path.Get()) { LString Profile = WinGetSpecialFolderPath(CSIDL_PROFILE); if (Profile) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), Profile, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDownloadsDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } + + #elif defined(HAIKU) + + #else LAssert(!"Not implemented"); #endif break; } case LSP_USER_LINKS: { LString Home = LGetSystemPath(LSP_HOME); #if defined(WIN32) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, "Links")) Path = p; #elif defined(LINUX) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, ".config/gtk-3.0")) Path = p; #endif break; } case LSP_USER_PICTURES: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); Path = p; #elif defined(WIN32) Path = WinGetSpecialFolderPath(CSIDL_MYPICTURES); if (Path) return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSPicturesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif // Default to ~/Pictures char hm[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(hm, sizeof(hm), Home, "Pictures")) Path = hm; break; } case LSP_USER_DOCUMENTS: { + char path[MAX_PATH_LEN]; + #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); - Path = p; + if (p) + Path = p; #elif defined(WIN32) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); - if (Path) - return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } + + #elif defined(HAIKU) + + if( find_directory + ( + B_SYSTEM_DOCUMENTATION_DIRECTORY, + dev_for_path("/boot"), + true, + path, sizeof(path) + ) == B_OK) + Path = path; #endif - // Default to ~/Documents - char hm[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(hm, sizeof(hm), Home, "Documents")) - Path = hm; + if (!Path) + { + // Default to ~/Documents + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Documents")) + Path = path; + } break; } case LSP_USER_MUSIC: { + char path[MAX_PATH_LEN]; + #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYMUSIC); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_MUSIC); - Path = p; + if (p) + Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMusicDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMusicDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } + #elif defined(HAIKU) + + if( find_directory + ( + B_USER_SOUNDS_DIRECTORY, + dev_for_path("/boot"), + true, + path, sizeof(path) + ) == B_OK) + Path = path; + #endif if (!Path) { // Default to ~/Music - char p[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(p, sizeof(p), Home, "Music")) - Path = p; + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Music")) + Path = path; } break; } case LSP_USER_VIDEO: { + char path[MAX_PATH_LEN]; + #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYVIDEO); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_VIDEOS); - Path = p; + if (p) + Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMovieDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMoviesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif if (!Path) { // Default to ~/Video - char p[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(p, sizeof(p), Home, "Video")) - Path = p; + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Video")) + Path = path; } break; } case LSP_USER_APPS: { #if defined WIN32 int Id = #ifdef WIN64 CSIDL_PROGRAM_FILES #else CSIDL_PROGRAM_FILESX86 #endif ; if (WordSize == 32) Id = CSIDL_PROGRAM_FILESX86; else if (WordSize == 64) Id = CSIDL_PROGRAM_FILES; Path = WinGetSpecialFolderPath(Id); #elif defined(HAIKU) - dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; - if (find_directory(B_SYSTEM_APPS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) + if (find_directory(B_USER_APPS_DIRECTORY, dev_for_path("/boot"), true, path, sizeof(path)) == B_OK) Path = path; #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationDirectory, NSSystemDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #elif defined MAC Path = "/Applications"; #elif defined LINUX Path = "/usr/bin"; #else LAssert(!"Impl me."); #endif break; } case LSP_APP_INSTALL: { Path = LGetSystemPath(LSP_EXE); if (Path) { size_t Last = Path.RFind(DIR_STR); if (Last > 0) { LString s = Path(Last, -1); if ( stristr(s, #ifdef _DEBUG "Debug" #else "Release" #endif ) ) Path = Path(0, Last); } } break; } case LSP_APP_ROOT: { #ifndef LGI_STATIC const char *Name = NULL; // Try and get the configured app name: if (LAppInst) Name = LAppInst->LBase::Name(); if (!Name) { // Use the exe name? LString Exe = LGetExeFile(); char *l = LGetLeaf(Exe); if (l) { #ifdef WIN32 char *d = strrchr(l, '.'); *d = NULL; #endif Name = l; // printf("%s:%i - name '%s'\n", _FL, Name); } } if (!Name) { LAssert(0); break; } #if defined MAC #if LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); if (paths) Path = [[paths objectAtIndex:0] UTF8String]; #elif LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) { printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); LAssert(0); } else { LAutoString Base = FSRefPath(Ref); Path = Base.Get(); } #else struct passwd *pw = getpwuid(getuid()); if (!pw) return false; Path.Printf("%s/Library", pw->pw_dir); #endif #elif defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LINUX char Dot[128]; snprintf(Dot, sizeof(Dot), ".%s", Name); Name = Dot; struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; else LAssert(0); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY , volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(0); #endif if (Path) { Path += DIR_STR; Path += Name; } #endif break; } case LSP_OS: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetWindowsDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kSystemFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; LTrimDir(Path); } #elif defined LINUX Path = "/boot"; // it'll do for now... #endif break; } case LSP_OS_LIB: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetSystemDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_LIB_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined MAC Path = "/Library"; #elif defined LINUX Path = "/lib"; // it'll do for now... #endif break; } case LSP_TEMP: { #if defined WIN32 char16 t[MAX_PATH_LEN]; if (GetTempPathW(CountOf(t), t) > 0) { LAutoString utf(WideToUtf8(t)); if (utf) Path = utf; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTemporaryFolderType, kCreateFolder, &Ref); if (e) LgiTrace("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) { Path = u.Get(); } else LgiTrace("%s:%i - FSRefPath failed.\n", _FL); } #elif defined LGI_COCOA NSString *tempDir = NSTemporaryDirectory(); if (tempDir) Path = tempDir; else LAssert(!"No tmp folder?"); #elif defined LINUX Path = "/tmp"; // it'll do for now... #else LAssert(!"Impl me."); #endif break; } case LSP_COMMON_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_COMMON_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnSystemDisk, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #else LAssert(!"Impl me."); #endif break; } case LSP_USER_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #elif defined HAIKU dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_SETTINGS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(!"Impl me."); #endif break; } case LSP_LOCAL_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_LOCAL_APPDATA); #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #else LAssert(!"Impl me."); #endif break; } case LSP_DESKTOP: { #if defined(WINDOWS) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_DESKTOPDIRECTORY); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_DESKTOP_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DESKTOP); Path = p; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDesktopDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kDesktopFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) { #ifdef LINUX WindowManager wm = LGetWindowManager(); if (wm == WM_Gnome) { Path.Printf("%s/.gnome-desktop", pw->pw_dir); } #endif if (!LDirExists(Path)) { Path.Printf("%s/Desktop", pw->pw_dir); } } #else #error "Impl me." #endif break; } case LSP_HOME: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_PROFILE); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_COCOA NSString *home = NSHomeDirectory(); if (home) Path = home; else LAssert(!"No home path?"); #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; #else #error "Impl me." #endif break; } case LSP_EXE: { Path = LGetExeFile(); if (!Path) break; auto p = Path.RFind(DIR_STR); if (p > 0) Path.Length(p); break; } case LSP_TRASH: { #if defined LINUX switch (LGetWindowManager()) { case WM_Kde: { static char KdeTrash[256] = ""; if (!ValidStr(KdeTrash)) { // Ask KDE where the current trash is... LStringPipe o; LSubProcess p("kde-config", "--userpath trash"); if (p.Start()) { p.Communicate(&o); char *s = o.NewStr(); if (s) { // Store it.. strcpy_s(KdeTrash, sizeof(KdeTrash), s); DeleteArray(s); // Clear out any crap at the end... char *e = KdeTrash + strlen(KdeTrash) - 1; while (e > KdeTrash && strchr(" \r\n\t/", *e)) { *e-- = 0; } } else { printf("%s:%i - No output from 'kde-config'.\n", _FL); } } else { printf("%s:%i - Run 'kde-config' failed.\n", _FL); } } if (ValidStr(KdeTrash)) Path = KdeTrash; break; } default: { LString Home = LGetSystemPath(LSP_HOME); if (!Home) { LgiTrace("%s:%i - Can't get LSP_HOME.\n", _FL); break; } char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), Home, ".local/share/Trash/files") || !LDirExists(p)) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, p); break; } Path = p; break; } } #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_TRASH_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTrashFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSTrashDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined(WIN32) LAssert(0); #endif break; } } return Path; } LString LGetExeFile() { #if defined WIN32 char16 Exe[MAX_PATH_LEN]; if (GetModuleFileNameW(NULL, Exe, CountOf(Exe)) > 0) { LString e = Exe; if (e) { return e; } else { LgiMsg(0, "LgiFromNativeCp returned 0, ANSI CodePage=%i (%s)", "LgiGetExeFile Error", MB_OK, GetACP(), LAnsiToLgiCp()); return LString(); } } LString m; m.Printf("GetModuleFileName failed err: %08.8X", GetLastError()); MessageBoxA(0, m, "LgiGetExeFile Error", MB_OK); LExitApp(); #elif defined HAIKU // Copy the string so as to not allow callers to change it return LgiArgsAppPath.Get(); #elif defined LINUX static char ExePathCache[MAX_PATH_LEN] = ""; bool Status = false; // this is _REALLY_ lame way to do it... but hey there aren't any // other better ways to get the full path of the running executable if (!ExePathCache[0]) { // First try the self method int Len = readlink("/proc/self/exe", ExePathCache, sizeof(ExePathCache)); Status = LFileExists(ExePathCache); // printf("readlink=%i Status=%i Exe='%s'\n", Len, Status, ExePathCache); if (!Status) { ExePathCache[0] = 0; // Next try the map file method char ProcFile[256]; sprintf_s(ProcFile, sizeof(ProcFile), "/proc/%i/maps", getpid()); int fd = open(ProcFile, O_RDONLY); if (fd >= 0) { int Len = 16 << 10; // is this enough? // no better way of determining the length of proc info? char *Buf = new char[Len+1]; if (Buf) { int r = read(fd, Buf, Len); Buf[r] = 0; char *s = strchr(Buf, '/'); if (s) { char *e = strchr(s, '\n'); if (e) { *e = 0; strcpy_s(ExePathCache, sizeof(ExePathCache), s); Status = true; } } DeleteArray(Buf); } close(fd); } else { // Non proc system (like cygwin for example) // char Cmd[256]; // sprintf_s(Cmd, sizeof(Cmd), "ps | grep \"%i\"", getpid()); LStringPipe Ps; LSubProcess p("ps"); if (p.Start()) { p.Communicate(&Ps); char *PsOutput = Ps.NewStr(); if (PsOutput) { auto n = LString(PsOutput).SplitDelimit("\r\n"); for (int i=0; !Status && i 7) { int LinePid = 0; for (int i=0; iReset(NewStr(Path)); return; } #ifdef WIN32 if (PathLen < sizeof(Path) - 4) { strcat(Path, ".lnk"); if (LResolveShortcut(Path, Path, sizeof(Path))) { if (GStr) *GStr = Path; else if (AStr) AStr->Reset(NewStr(Path)); return; } } #endif } // General search fall back... LArray Ext; LArray Files; Ext.Add((char*)Name); if (LRecursiveFileSearch(Exe, &Ext, &Files) && Files.Length()) { if (GStr) *GStr = Files[0]; else { AStr->Reset(Files[0]); Files.DeleteAt(0); } } Files.DeleteArrays(); } LString LFindFile(const char *Name) { LString s; _LFindFile(Name, &s, NULL); return s; } #if defined WIN32 static LARGE_INTEGER Freq = {0}; static bool CurTimeInit = false; #endif uint64_t LCurrentTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000 / Freq.QuadPart; } // Now what?? Give up and go back to tick count I guess. Freq.QuadPart = 0; } // Fall back for systems without a performance counter. return GetTickCount(); #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); uint64 i = ((uint64)t.hi << 32) | t.lo; return i / 1000; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); #endif } uint64_t LgiMicroTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000000 / Freq.QuadPart; } } return 0; #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); return ((uint64)t.hi << 32) | t.lo; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000000) + tv.tv_usec; #endif } int LIsReleaseBuild() { #ifdef _DEBUG return 0; #else return 1; #endif } bool LIsVolumeRoot(const char *Path) { if (!Path) return false; #ifdef WIN32 if ( IsAlpha(Path[0]) && Path[1] == ':' && ( (Path[2] == 0) || (Path[2] == '\\' && Path[3] == 0) ) ) { return true; } #else auto t = LString(Path).SplitDelimit(DIR_STR); if (t.Length() == 0) return true; #ifdef MAC if (!stricmp(t[0], "Volumes") && t.Length() == 2) return true; #else if (!stricmp(t[0], "mnt") && t.Length() == 2) return true; #endif #endif return false; } LString LFormatSize(int64_t Size) { char Buf[64]; LFormatSize(Buf, sizeof(Buf), Size); return Buf; } void LFormatSize(char *Str, int SLen, int64_t Size) { int64_t K = 1024; int64_t M = K * K; int64_t G = K * M; int64_t T = K * G; if (Size == 1) { strcpy_s(Str, SLen, "1 byte"); } else if (Size < K) { sprintf_s(Str, SLen, "%u bytes", (int)Size); } else if (Size < 10 * K) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f KiB", d / K); } else if (Size < M) { sprintf_s(Str, SLen, "%u KiB", (int) (Size / K)); } else if (Size < G) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f MiB", d / M); } else if (Size < T) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f GiB", d / G); } else { double d = (double)Size; sprintf_s(Str, SLen, "%.2f TiB", d / T); } } char *LTokStr(const char *&s) { char *Status = 0; if (s && *s) { // Skip whitespace static char Delim[] = ", \t\r\n"; while (*s && strchr(Delim, *s)) s++; if (*s) { if (strchr("\'\"", *s)) { char Delim = *s++; const char *e = strchr(s, Delim); if (!e) { // error, no end delimiter e = s; while (*e) e++; } Status = NewStr(s, e - s); s = *e ? e + 1 : e; } else { const char *e = s; while (*e && !strchr(Delim, *e)) e++; Status = NewStr(s, e - s); s = e; } } } return Status; } LString LGetEnv(const char *Var) { #ifdef _MSC_VER char *s = NULL; size_t sz; errno_t err = _dupenv_s(&s, &sz, Var); if (err) return LString(); LString ret(s); free(s); return ret; #else return getenv("PATH"); #endif } LString::Array LGetPath() { LString::Array Paths; #ifdef MAC // OMG, WHY?! Seriously? // // The GUI application path is NOT the same as what is configured for the terminal. // At least in 10.12. And I don't know how to make them the same. This works around // that for the time being. #if 1 LFile EctPaths("/etc/paths", O_READ); Paths = EctPaths.Read().Split("\n"); #else LFile::Path Home(LSP_HOME); Home += ".profile"; if (!Home.Exists()) { Home--; Home += ".zprofile"; } auto Profile = LFile(Home, O_READ).Read().Split("\n"); for (auto Ln : Profile) { auto p = Ln.SplitDelimit(" =", 2); if (p.Length() == 3 && p[0].Equals("export") && p[1].Equals("PATH")) { Paths = p[2].Strip("\"").SplitDelimit(LGI_PATH_SEPARATOR); Paths.SetFixedLength(false); for (auto &p : Paths) { if (p.Equals("$PATH")) { auto SysPath = LGetEnv("PATH").SplitDelimit(LGI_PATH_SEPARATOR); for (unsigned i=0; i 0) { Period = p; } } bool DoEvery::DoNow() { int64 Now = LCurrentTime(); if (LastTime + Period < Now) { LastTime = Now; return true; } return false; } ////////////////////////////////////////////////////////////////////// bool LCapabilityClient::NeedsCapability(const char *Name, const char *Param) { for (int i=0; iNeedsCapability(Name, Param); return Targets.Length() > 0; } LCapabilityClient::~LCapabilityClient() { for (int i=0; iClients.Delete(this); } void LCapabilityClient::Register(LCapabilityTarget *t) { if (t && !Targets.HasItem(t)) { Targets.Add(t); t->Clients.Add(this); } } LCapabilityTarget::~LCapabilityTarget() { for (int i=0; iTargets.Delete(this); } ///////////////////////////////////////////////////////////////////// #define BUF_SIZE (4 << 10) #define PROFILE_MICRO 1 LProfile::LProfile(const char *Name, int HideMs) { MinMs = HideMs; Used = 0; Buf = NULL; Add(Name); } LProfile::~LProfile() { Add("End"); uint64 TotalMs = s.Last().Time - s[0].Time; if (MinMs > 0) { if (TotalMs < MinMs #if PROFILE_MICRO * 1000 #endif ) { return; } } uint64 accum = 0; for (int i=0; i BUF_SIZE - 64) { LAssert(0); return; } char *Name = Buf + Used; Used += sprintf_s(Name, BUF_SIZE - Used, "%s:%i", File, Line) + 1; s.Add(Sample( #if PROFILE_MICRO LgiMicroTime(), #else LCurrentTime(), #endif Name)); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// bool LIsValidEmail(LString Email) { // Local part char buf[321]; char *o = buf; char *e = Email; if (!Email || *e == '.') return false; #define OutputChar() \ if (o - buf >= sizeof(buf) - 1) \ return false; \ *o++ = *e++ // Local part while (*e) { if (strchr("!#$%&\'*+-/=?^_`.{|}~", *e) || IsAlpha((uchar)*e) || IsDigit((uchar)*e)) { OutputChar(); } else if (*e == '\"') { // Quoted string OutputChar(); bool quote = false; while (*e && !quote) { quote = *e == '\"'; OutputChar(); } } else if (*e == '\\') { // Quoted character e++; if (*e < ' ' || *e >= 0x7f) return false; OutputChar(); } else if (*e == '@') { break; } else { // Illegal character return false; } } // Process the '@' if (*e != '@' || o - buf > 64) return false; OutputChar(); // Domain part... if (*e == '[') { // IP addr OutputChar(); // Initial char must by a number if (!IsDigit(*e)) return false; // Check the rest... char *Start = e; while (*e) { if (IsDigit(*e) || *e == '.') { OutputChar(); } else { return false; } } // Not a valid IP if (e - Start > 15) return false; if (*e != ']') return false; OutputChar(); } else { // Hostname, check initial char if (!IsAlpha(*e) && !IsDigit(*e)) return false; // Check the rest. while (*e) { if (IsAlpha(*e) || IsDigit(*e) || strchr(".-", *e)) { OutputChar(); } else { return false; } } } // Remove any trailing dot/dash while (strchr(".-", o[-1])) o--; // Output *o = 0; LAssert(o - buf <= sizeof(buf)); if (strcmp(Email, buf)) Email.Set(buf, o - buf); return true; } ////////////////////////////////////////////////////////////////////////// LString LGetAppForProtocol(const char *Protocol) { LString App; if (!Protocol) return App; #ifdef WINDOWS LRegKey k(false, "HKEY_CLASSES_ROOT\\%s\\shell\\open\\command", Protocol); if (k.IsOk()) { const char *p = k.GetStr(); if (p) { LAutoString a(LTokStr(p)); App = a.Get(); } } #elif defined(LINUX) const char *p = NULL; if (stricmp(Protocol, "mailto")) p = "xdg-email"; else p = "xdg-open"; LString Path = LGetEnv("PATH"); LString::Array a = Path.SplitDelimit(LGI_PATH_SEPARATOR); for (auto i : a) { LFile::Path t(i); t += p; if (t.Exists()) { App = t.GetFull(); break; } } #elif defined(__GTK_H__) LAssert(!"What to do?"); #elif defined(MAC) // Get the handler type LString s; s.Printf("%s://domain/path", Protocol); auto str = s.NsStr(); auto type = [NSURL URLWithString:str]; [str release]; auto handlerUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:type]; [type release]; if (handlerUrl) { // Convert to app path s = [handlerUrl absoluteString]; LUri uri(s); if (uri.sProtocol.Equals("file")) App = uri.sPath.RStrip("/"); else LgiTrace("%s:%i - Error: unknown protocol '%s'\n", _FL, uri.sProtocol.Get()); } [handlerUrl release]; #else #warning "Impl me." #endif return App; } diff --git a/src/common/Lgi/Library.cpp b/src/common/Lgi/Library.cpp --- a/src/common/Lgi/Library.cpp +++ b/src/common/Lgi/Library.cpp @@ -1,359 +1,357 @@ #define __USE_GNU #include #define DEBUG_LIB_MSGS 0 #define ALLOW_FALLBACK_PATH 0 #include "lgi/common/Lgi.h" #if defined(POSIX) #include #endif LLibrary::LLibrary(const char *File, bool Quiet) { FileName = 0; hLib = 0; if (File) Load(File, Quiet); } LLibrary::~LLibrary() { Unload(); } bool LLibrary::Load(const char *File, bool Quiet) { Unload(); if (File) { char f[MAX_PATH_LEN]; int ch = sprintf_s(f, sizeof(f), "%s", File); - #ifndef HAIKU - // check for default extension.. - // if you specify no extension in the application this will correctly set - // it for the OS your running thus removing the need for #ifdef's in your - // app. sweet! - auto Parts = LString(LGetLeaf(File)).SplitDelimit("."); - if (Parts.Length() >= 3 && - Parts.Last().Int() >= 0 && - Parts[Parts.Length()-2].Equals(LGI_LIBRARY_EXT)) - { - // Is normal linux "library.so.version" form. - } - else if (Parts.Length() == 1 || - !Parts.Last().Equals(LGI_LIBRARY_EXT)) - { - ch += sprintf_s(f+ch, sizeof(f)-ch, ".%s", LGI_LIBRARY_EXT); - } - #endif + // check for default extension.. + // if you specify no extension in the application this will correctly set + // it for the OS your running thus removing the need for #ifdef's in your + // app. sweet! + auto Parts = LString(LGetLeaf(File)).SplitDelimit("."); + if (Parts.Length() >= 3 && + Parts.Last().Int() >= 0 && + Parts[Parts.Length()-2].Equals(LGI_LIBRARY_EXT)) + { + // Is normal linux "library.so.version" form. + } + else if (Parts.Length() == 1 || + !Parts.Last().Equals(LGI_LIBRARY_EXT)) + { + ch += sprintf_s(f+ch, sizeof(f)-ch, ".%s", LGI_LIBRARY_EXT); + } size_t Len = strlen(f) + 32; FileName = new char[Len]; if (FileName) { memset(FileName, 0, Len); strcpy_s(FileName, Len, f); // FileName is always longer then 'f' #if defined WIN32 SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); LAutoWString w(Utf8ToWide(f)); if (w) { hLib = LoadLibraryW(w); #if defined(_DEBUG) && DEBUG_LIB_MSGS if (!hLib) LgiTrace("LoadLibraryW(%S) failed with %i\n", w.Get(), GetLastError()); #endif } else LgiTrace("%s:%i - Failed to convert string to wide.\n", _FL); SetErrorMode(0); #elif defined(POSIX) char *f = strrchr(FileName, DIR_CHAR); if (f) f++; else f = FileName; bool IsLgi = stricmp(f, "lgilgi." LGI_LIBRARY_EXT) == 0 || stricmp(f, "lgilgid." LGI_LIBRARY_EXT) == 0; if (!IsLgi) // Bad things happen when we load LGI again. { #if DEBUG_LIB_MSGS LgiTrace("%s:%i - calling dlopen('%s')\n", _FL, FileName); #endif hLib = dlopen(FileName, RTLD_NOW); #if DEBUG_LIB_MSGS LgiTrace("%s:%i - dlopen('%s') = %p\n", _FL, FileName, hLib); #endif #ifdef LINUX if (!hLib) { char *err = dlerror(); if (!Quiet && !stristr(err, "No such file or directory")) { LgiTrace("%s:%i - dlopen(%s) failed: %s\n", _FL, File, err); Quiet = true; } // Try with an extra ".0"... just for fun. char *end = FileName + strlen(FileName); strcpy(end, ".0"); hLib = dlopen(FileName, RTLD_NOW); #if DEBUG_LIB_MSGS LgiTrace("%s:%i - dlopen('%s') = %p\n", _FL, FileName, hLib); #endif *end = 0; } #endif if (hLib) { #if 0 // defined(LINUX) && defined(__USE_GNU) char Path[MAX_PATH_LEN]; int r = dlinfo(hLib, RTLD_DI_ORIGIN, Path); if (r == 0) { LMakePath(Path, sizeof(Path), Path, File); printf("LLibrary loaded: '%s'\n", Path); } else printf("%s:%i - dlinfo failed.\n", _FL); #endif } else { if (LIsRelativePath(FileName)) { // Explicitly try the full path to the executable folder char p[MAX_PATH_LEN]; #if LGI_COCOA || defined(__GTK_H__) LMakePath(p, sizeof(p), LgiArgsAppPath, "../../Frameworks"); #else strcpy_s(p, sizeof(p), LGetExePath()); #endif LMakePath(p, sizeof(p), p, FileName); hLib = dlopen(p, RTLD_NOW); #if DEBUG_LIB_MSGS printf("%s:%i - Trying '%s' = %p\n", _FL, p, hLib); #endif } if (!hLib) { #if DEBUG_LIB_MSGS char *e = dlerror(); if ( !stristr(e, "No such file or directory") && !stristr(e, "image not found") && !Quiet ) LgiTrace("%s:%i - dlopen(%s) failed: %s\n", _FL, File, e); #endif #if ALLOW_FALLBACK_PATH LToken t("/opt/local/lib", ":"); for (int i=0; i #import #import #import #import #ifdef __LP64__ typedef struct mach_header_64 mach_header_t; typedef struct segment_command_64 segment_command_t; typedef struct nlist_64 nlist_t; #else typedef struct mach_header mach_header_t; typedef struct segment_command segment_command_t; typedef struct nlist nlist_t; #endif static const char * first_external_symbol_for_image(const mach_header_t *header) { Dl_info info; if (dladdr(header, &info) == 0) return NULL; segment_command_t *seg_linkedit = NULL; segment_command_t *seg_text = NULL; struct symtab_command *symtab = NULL; struct load_command *cmd = (struct load_command *)((intptr_t)header + sizeof(mach_header_t)); for (uint32_t i = 0; i < header->ncmds; i++, cmd = (struct load_command *)((intptr_t)cmd + cmd->cmdsize)) { switch(cmd->cmd) { case LC_SEGMENT: case LC_SEGMENT_64: if (!strcmp(((segment_command_t *)cmd)->segname, SEG_TEXT)) seg_text = (segment_command_t *)cmd; else if (!strcmp(((segment_command_t *)cmd)->segname, SEG_LINKEDIT)) seg_linkedit = (segment_command_t *)cmd; break; case LC_SYMTAB: symtab = (struct symtab_command *)cmd; break; } } if ((seg_text == NULL) || (seg_linkedit == NULL) || (symtab == NULL)) return NULL; intptr_t file_slide = ((intptr_t)seg_linkedit->vmaddr - (intptr_t)seg_text->vmaddr) - seg_linkedit->fileoff; intptr_t strings = (intptr_t)header + (symtab->stroff + file_slide); nlist_t *sym = (nlist_t *)((intptr_t)header + (symtab->symoff + file_slide)); for (uint32_t i = 0; i < symtab->nsyms; i++, sym++) { if ((sym->n_type & N_EXT) != N_EXT || !sym->n_value) continue; return (const char *)strings + sym->n_un.n_strx; } return NULL; } const char * pathname_for_handle(void *handle) { for (int32_t i = _dyld_image_count(); i >= 0 ; i--) { const char *first_symbol = first_external_symbol_for_image((const mach_header_t *)_dyld_get_image_header(i)); if (first_symbol && strlen(first_symbol) > 1) { handle = (void *)((intptr_t)handle | 1); // in order to trigger findExportedSymbol instead of findExportedSymbolInImageOrDependentImages. See `dlsym` implementation at http://opensource.apple.com/source/dyld/dyld-239.3/src/dyldAPIs.cpp first_symbol++; // in order to remove the leading underscore void *address = dlsym(handle, first_symbol); Dl_info info; if (dladdr(address, &info)) return info.dli_fname; } } return NULL; } #elif defined(LINUX) #include #endif LString LLibrary::GetFullPath() { #if defined(MAC) if (hLib) return pathname_for_handle(hLib); #elif defined(WINDOWS) if (hLib) { wchar_t File[MAX_PATH_LEN] = L""; GetModuleFileNameW(Handle(), File, sizeof(File)); return File; } #elif defined(LINUX) if (hLib) { struct link_map *map = NULL; dlinfo(hLib, RTLD_DI_LINKMAP, &map); if (map) return realpath(map->l_name, NULL); } #elif defined(HAIKU) // No method to get full path? #endif return FileName; } diff --git a/src/common/Lgi/Mdi.cpp b/src/common/Lgi/Mdi.cpp --- a/src/common/Lgi/Mdi.cpp +++ b/src/common/Lgi/Mdi.cpp @@ -1,997 +1,1042 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Mdi.h" #include #include "lgi/common/DisplayString.h" + #define DEBUG_MDI 0 -enum GMdiDrag + +enum LMdiDrag { DragNone = 0, DragMove = 1 << 1, DragLeft = 1 << 2, DragTop = 1 << 3, DragRight = 1 << 4, DragBottom = 1 << 5, DragClose = 1 << 6, DragSystem = 1 << 7 }; + class LMdiChildPrivate { public: LMdiChild *Child; LRect Title; #if MDI_TAB_STYLE LRect Tab, Btn; int Order; #else LRect Close; LRect System; #endif int Fy; - GMdiDrag Drag; + LMdiDrag Drag; int Ox, Oy; bool CloseDown; bool CloseOnUp; LMdiChildPrivate(LMdiChild *c) { CloseOnUp = false; Child = c; CloseDown = false; Fy = LSysFont->GetHeight() + 1; Title.ZOff(-1, -1); #if MDI_TAB_STYLE Tab.ZOff(-1, -1); Order = -1; #else Close.ZOff(-1, -1); System.ZOff(-1, -1); #endif } #if !MDI_TAB_STYLE - GMdiDrag HitTest(int x, int y) + LMdiDrag HitTest(int x, int y) { - GMdiDrag Hit = DragNone; + LMdiDrag Hit = DragNone; if (Child->WindowFromPoint(x, y) == Child) { if (!Child->GetClient().Overlap(x, y)) { if (System.Overlap(x, y)) { Hit = DragSystem; } else if (Close.Overlap(x, y)) { Hit = DragClose; } else if (Title.Overlap(x, y)) { Hit = DragMove; } else { LRect c = Child->LLayout::GetClient(); int Corner = 24; if (x < c.x1 + Corner) { (int&)Hit |= DragLeft; } if (x > c.x2 - Corner) { (int&)Hit |= DragRight; } if (y < c.y1 + Corner) { (int&)Hit |= DragTop; } if (y > c.y2 - Corner) { (int&)Hit |= DragBottom; } } } } return Hit; } #endif }; + class LMdiParentPrivate { public: bool InOnPosChange; bool Btn; LRect Tabs; LRect Content; ::LArray Children; LMdiParentPrivate() { Btn = false; InOnPosChange = false; Tabs.ZOff(-1, -1); Content.ZOff(-1, -1); } }; + //////////////////////////////////////////////////////////////////////////////////////////// LMdiChild::LMdiChild() { d = new LMdiChildPrivate(this); LPoint m(100, d->Fy + 8); SetMinimumSize(m); #if WINNATIVE SetStyle(GetStyle() | WS_CLIPSIBLINGS); #endif } + LMdiChild::~LMdiChild() { Detach(); DeleteObj(d); } + bool LMdiChild::Attach(LViewI *p) { #if MDI_TAB_STYLE LMdiParent *par = dynamic_cast(p); if (!par) return false; if (par->d->Children.HasItem(this)) { LgiTrace("%s:%i - Already attached:\n", _FL); #ifdef BEOS for (unsigned i=0; id->Children.Length(); i++) { LMdiChild *c = par->d->Children[i]; printf("[%i]=%p %p %p %s\n", i, c, c->Handle(), c->Handle()?c->Handle()->Window():0, c->Name()); } #endif return false; } par->d->Children.Add(this); d->Order = par->GetNextOrder(); #endif bool s = LLayout::Attach(p); if (s) AttachChildren(); return s; } + bool LMdiChild::Detach() { #if MDI_TAB_STYLE LMdiParent *par = dynamic_cast(GetParent()); if (par) { LAssert(par->d->Children.HasItem(this)); par->d->Children.Delete(this); } #endif return LLayout::Detach(); } + const char *LMdiChild::Name() { return LView::Name(); } + bool LMdiChild::Name(const char *n) { bool s = LView::Name(n); #if MDI_TAB_STYLE if (GetParent()) GetParent()->Invalidate(); #else Invalidate((LRect*)0, false, true); #endif return s; } + bool LMdiChild::PourAll() { LRect c = GetClient(); #if !MDI_TAB_STYLE c.Size(2, 2); #endif LRegion Client(c); LRegion Update; #if DEBUG_MDI LgiTrace(" LMdiChild::Pour() '%s', cli=%s, vis=%i, children=%i\n", Name(), c.GetStr(), Visible(), Children.Length()); #endif for (auto w: Children) { if (Visible()) { LRect OldPos = w->GetPos(); Update.Union(&OldPos); if (w->Pour(Client)) { if (!w->IsAttached()) w->Attach(this); if (!w->Visible()) w->Visible(true); Client.Subtract(&w->GetPos()); Update.Subtract(&w->GetPos()); } else { // non-pourable } } else { w->Visible(false); } #if DEBUG_MDI LgiTrace(" %s, vis=%i\n", w->GetClass(), w->Visible()); #endif } return true; } + void LMdiChild::OnTitleClick(LMouse &m) { if (!m.IsContextMenu() && m.Down()) { Raise(); } } + void LMdiChild::OnButtonClick(LMouse &m) { if (m.Down()) { if (OnRequestClose(false)) { Quit(); } } } + void LMdiChild::OnPaintButton(LSurface *pDC, LRect &rc) { // Default: Draw little 'x' for closing the MDI child LRect r = rc; pDC->Colour(LColour(192,192,192)); r.Inset(3, 3); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x1+1, r.y1, r.x2, r.y2-1); pDC->Line(r.x1, r.y1+1, r.x2-1, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); pDC->Line(r.x2-1, r.y1, r.x1, r.y2-1); pDC->Line(r.x2, r.y1+1, r.x1+1, r.y2); } + #if MDI_TAB_STYLE + int LMdiChild::GetOrder() { return d->Order; } + #else + LRect &LMdiChild::GetClient(bool ClientSpace) { static LRect r; r = LLayout::GetClient(ClientSpace); r.Size(3, 3); r.y1 += d->Fy + 2; r.Size(1, 1); return r; } + void LMdiChild::OnPaint(LSurface *pDC) { LRect p = LLayout::GetClient(); // Border LWideBorder(pDC, p, RAISED); pDC->Colour(LC_MED, 24); pDC->Box(&p); p.Size(1, 1); // Title bar char *n = Name(); if (!n) n = ""; d->Title.ZOff(p.X()-1, d->Fy + 1); d->Title.Offset(p.x1, p.y1); d->System = d->Title; d->System.x2 = d->System.x1 + d->System.Y() - 1; // Title text bool Top = false; if (GetParent()) { GViewIterator *it = GetParent()->IterateViews(); Top = it->Last() == (LViewI*)this; DeleteObj(it); } LSysFont->Colour(Top ? LC_ACTIVE_TITLE_TEXT : LC_INACTIVE_TITLE_TEXT, Top ? LC_ACTIVE_TITLE : LC_INACTIVE_TITLE); LSysFont->Transparent(false); LDisplayString ds(LSysFont, n); ds.Draw(pDC, d->System.x2 + 1, d->Title.y1 + 1, &d->Title); // System button LRect r = d->System; r.Size(2, 1); pDC->Colour(LC_BLACK, 24); pDC->Box(&r); r.Size(1, 1); pDC->Colour(LC_WHITE, 24); pDC->Rectangle(&r); pDC->Colour(LC_BLACK, 24); for (int k=r.y1 + 1; k < r.y2 - 1; k += 2) { pDC->Line(r.x1 + 1, k, r.x2 - 1, k); } // Close button d->Close = d->Title; d->Close.x1 = d->Close.x2 - d->Close.Y() + 1; d->Close.Size(2, 2); r = d->Close; LWideBorder(pDC, r, d->CloseDown ? SUNKEN : RAISED); pDC->Colour(LC_MED, 24); pDC->Rectangle(&r); int Cx = d->Close.x1 + (d->Close.X() >> 1) - 1 + d->CloseDown; int Cy = d->Close.y1 + (d->Close.Y() >> 1) - 1 + d->CloseDown; int s = 3; pDC->Colour(Rgb24(108,108,108), 24); pDC->Line(Cx-s, Cy-s+1, Cx+s-1, Cy+s); pDC->Line(Cx-s+1, Cy-s, Cx+s, Cy+s-1); pDC->Line(Cx-s, Cy+s-1, Cx+s-1, Cy-s); pDC->Line(Cx-s+1, Cy+s, Cx+s, Cy-s+1); pDC->Colour(LC_BLACK, 24); pDC->Line(Cx-s, Cy-s, Cx+s, Cy+s); pDC->Line(Cx-s, Cy+s, Cx+s, Cy-s); // Client LRect Client = GetClient(); Client.Size(-1, -1); pDC->Colour(LC_MED, 24); pDC->Box(&Client); if (!Children.First()) { Client.Size(1, 1); pDC->Colour(LC_WORKSPACE, 24); pDC->Rectangle(&Client); } Pour(); } + void LMdiChild::OnMouseClick(LMouse &m) { if (m.Left()) { Raise(); if (m.Down()) { LRect c = LLayout::GetClient(); d->Drag = DragNone; d->Ox = d->Oy = -1; d->Drag = d->HitTest(m.x, m.y); if (d->Drag) { Capture(true); } if (d->Drag == DragSystem) { if (m.Double()) { d->CloseOnUp = true; } else { /* LPoint p(d->System.x1, d->System.y2+1); PointToScreen(p); LSubMenu *Sub = new LSubMenu; if (Sub) { Sub->AppendItem("Close", 1, true); Close = Sub->Float(this, p.x, p.y, true) == 1; DeleteObj(Sub); } */ } } else if (d->Drag == DragClose) { d->CloseDown = true; Invalidate(&d->Close); } else if (d->Drag == DragMove) { d->Ox = m.x - c.x1; d->Oy = m.y - c.y1; } else { if (d->Drag & DragLeft) { d->Ox = m.x - c.x1; } if (d->Drag & DragRight) { d->Ox = m.x - c.x2; } if (d->Drag & DragTop) { d->Oy = m.y - c.y1; } if (d->Drag & DragBottom) { d->Oy = m.y - c.y2; } } } else { if (IsCapturing()) { Capture(false); } if (d->CloseOnUp) { d->Drag = DragNone; if (OnRequestClose(false)) { Quit(); return; } } else if (d->Drag == DragClose) { if (d->Close.Overlap(m.x, m.y)) { d->Drag = DragNone; d->CloseDown = false; Invalidate(&d->Close); if (OnRequestClose(false)) { Quit(); return; } } else printf("%s:%i - Outside close btn.\n", _FL); } else { d->Drag = DragNone; } } } } + LCursor LMdiChild::GetCursor(int x, int y) { - GMdiDrag Hit = d->HitTest(x, y); + LMdiDrag Hit = d->HitTest(x, y); if (Hit & DragLeft) { if (Hit & DragTop) { return LCUR_SizeFDiag; } else if (Hit & DragBottom) { return LCUR_SizeBDiag; } else { return LCUR_SizeHor; } } else if (Hit & DragRight) { if (Hit & DragTop) { return LCUR_SizeBDiag; } else if (Hit & DragBottom) { return LCUR_SizeFDiag; } else { return LCUR_SizeHor; } } else if ((Hit & DragTop) || (Hit & DragBottom)) { return LCUR_SizeVer; } return LCUR_Normal; } + void LMdiChild::OnMouseMove(LMouse &m) { if (IsCapturing()) { LRect p = GetPos(); LPoint Min = GetMinimumSize(); if (d->Drag == DragClose) { bool Over = d->Close.Overlap(m.x, m.y); if (Over ^ d->CloseDown) { d->CloseDown = Over; Invalidate(&d->Close); } } else { // GetMouse(m, true); LPoint n(m.x, m.y); n.x += GetPos().x1; n.y += GetPos().y1; // GetParent()->PointToView(n); if (d->Drag == DragMove) { p.Offset(n.x - d->Ox - p.x1, n.y - d->Oy - p.y1); if (p.x1 < 0) p.Offset(-p.x1, 0); if (p.y1 < 0) p.Offset(0, -p.y1); } else { if (d->Drag & DragLeft) { p.x1 = min(p.x2 - Min.x, n.x - d->Ox); if (p.x1 < 0) p.x1 = 0; } if (d->Drag & DragRight) { p.x2 = max(p.x1 + Min.x, n.x - d->Ox); } if (d->Drag & DragTop) { p.y1 = min(p.y2 - Min.y, n.y - d->Oy); if (p.y1 < 0) p.y1 = 0; } if (d->Drag & DragBottom) { p.y2 = max(p.y1 + Min.y, n.y - d->Oy); } } } SetPos(p); } } #endif + #if defined __GTK_H__ using namespace Gtk; #endif void LMdiChild::Raise() { LMdiParent *p = dynamic_cast(GetParent()); if (p) { #if MDI_TAB_STYLE #else #if defined __GTK_H__ GtkWidget *wid = Handle(); GdkWindow *wnd = wid ? GDK_WINDOW(wid->window) : NULL; if (wnd) gdk_window_raise(wnd); else LAssert(0); #elif WINNATIVE SetWindowPos(Handle(), HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); #else #error "Impl me." #endif #endif /* printf("%s:%i %p=%s %p=%s\n", _FL, p->d->Children.Last(), p->d->Children.Last()->Name(), this, Name()); */ if (p->d->Children.Last() != this) { p->d->Children.Delete(this); p->d->Children.Add(this); p->d->Tabs.ZOff(-1, -1); p->OnPosChange(); Focus(true); } #if DEBUG_MDI LgiTrace("LMdiChild::Raise() '%s'\n", Name()); #endif } } + void LMdiChild::Lower() { LMdiParent *p = dynamic_cast(GetParent()); if (p) { #if MDI_TAB_STYLE #else #if defined __GTK_H__ GtkWidget *wid = Handle(); GdkWindow *wnd = wid ? GDK_WINDOW(wid->window) : NULL; if (wnd) gdk_window_lower(wnd); else LAssert(0); #elif WINNATIVE SetWindowPos(Handle(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); #else #error "Impl me." #endif #endif if (p->d->Children[0] != this) { p->d->Children.Delete(this); p->d->Children.AddAt(0, this); p->d->Tabs.ZOff(-1, -1); p->OnPosChange(); } #if DEBUG_MDI LgiTrace("LMdiChild::Lower() '%s'\n", Name()); #endif } } + /////////////////////////////////////////////////////////////////////////////////////////// LMdiParent::LMdiParent() { d = new LMdiParentPrivate; SetPourLargest(true); } + LMdiParent::~LMdiParent() { if (GetWindow()) { GetWindow()->UnregisterHook(this); } DeleteObj(d); } + bool LMdiParent::HasButton() { return d->Btn; } + void LMdiParent::HasButton(bool b) { d->Btn = b; if (IsAttached()) Invalidate(); } + ::LArray &LMdiParent::PrivChildren() { return d->Children; } -/* -#if MDI_TAB_STYLE -int ViewCmp(LMdiChild **a, LMdiChild **b) -{ - return (*a)->GetOrder() - (*b)->GetOrder(); -} -#endif -*/ + void LMdiParent::OnPaint(LSurface *pDC) { #if MDI_TAB_STYLE if (d->Children.Length() == 0) #endif { pDC->Colour(L_LOW); pDC->Rectangle(); return; } #if MDI_TAB_STYLE // Draw tabs... LFont *Fnt = GetFont(); LColour Back(L_LOW), Black(L_BLACK); int Cx = 5; pDC->Colour(Back); pDC->Rectangle(d->Tabs.x1, d->Tabs.y1, d->Tabs.x1 + Cx - 1, d->Tabs.y2 - 1); pDC->Colour(Black); pDC->Line(d->Tabs.x1, d->Tabs.y2, d->Tabs.x2, d->Tabs.y2); int MarginPx = 10; ::LArray Views; LMdiChild *Last = dynamic_cast(d->Children.Last()); GetChildren(Views); LColour cActive(L_WORKSPACE); LColour cInactive(LColour(L_MED).Mix(cActive)); for (int Idx=0; IdxName()); int BtnWidth = d->Btn ? ds.Y()-4 : 0; c->d->Tab.ZOff(ds.X()+BtnWidth+1, d->Tabs.y2-1); c->d->Tab.Offset(d->Tabs.x1 + Cx, d->Tabs.y1); c->d->Tab.x2 += MarginPx * 2 - 2; if (d->Btn) { c->d->Btn = c->d->Tab; c->d->Btn.Inset(2, 2); c->d->Btn.x1 = c->d->Btn.x2 - BtnWidth + 1; c->d->Btn.Offset(-2, 0); } else c->d->Btn.ZOff(-1, -1); c->Visible(Active); LColour Bk(Active ? cActive : cInactive); LColour Edge(L_BLACK); LColour Txt(L_TEXT); LRect r = c->d->Tab; if (Active) r.y2 += 1; int OffX = 0, OffY = 0; for (; OffY < r.Y(); OffX++, OffY += 2) { pDC->Colour(Edge); pDC->VLine(r.x1 + OffX, r.y2 - OffY - 1, r.y2 - OffY); pDC->Colour(Back); pDC->VLine(r.x1 + OffX, r.y1, r.y2 - OffY - 2); if (OffY > 0) { pDC->Colour(Bk); pDC->VLine(r.x1 + OffX, r.y2 - OffY + 1, r.y2); } } pDC->Colour(Edge); pDC->HLine(r.x1 + OffX - 1, r.x2, r.y1); pDC->VLine(r.x2, r.y1, r.y2); r.Inset(1, 1); r.y2 += 1; Fnt->Fore(Txt); Fnt->Back(Bk); Fnt->Transparent(false); LRect txt = r; txt.x1 += OffX - 1; ds.Draw(pDC, r.x1 + MarginPx + 2, r.y1, &txt); c->OnPaintButton(pDC, c->d->Btn); Cx += c->d->Tab.X(); } pDC->Colour(Back); pDC->Rectangle(d->Tabs.x1 + Cx, d->Tabs.y1, d->Tabs.x2, d->Tabs.y2 - 1); #endif } + bool LMdiParent::Attach(LViewI *p) { bool s = LLayout::Attach(p); if (s) { GetWindow()->RegisterHook(this, LKeyAndMouseEvents); OnPosChange(); } return s; } + LMdiChild *LMdiParent::IsChild(LViewI *View) { for (LViewI *v=View; v; v=v->GetParent()) { if (v->GetParent() == this) { LMdiChild *c = dynamic_cast(v); if (c) - { return c; - } - LAssert(0); + + printf("%s:%i - Unexpected view '%s' in IsChild.\n", _FL, v->GetClass()); break; } } - return 0; + return NULL; } + bool LMdiParent::OnViewMouse(LView *View, LMouse &m) { - if (m.Down()) + if (m.Down() && Children.Length() > 0) { LMdiChild *v = IsChild(View); LMdiChild *l = IsChild(*Children.rbegin()); if (v != NULL && v != l) { v->Raise(); } } return true; } + bool LMdiParent::OnViewKey(LView *View, LKey &Key) { if (Key.Down() && Key.Ctrl() && Key.c16 == '\t') { LMdiChild *Child = IsChild(View); #if DEBUG_MDI LgiTrace("Child=%p %s view=%s\n", Child, Child ? Child->Name() : 0, View->GetClass()); #endif if (Child) { ::LArray Views; GetChildren(Views); auto Idx = Views.IndexOf(Child); int Inc = Key.Shift() ? -1 : 1; auto NewIdx = (Idx + Inc) % Views.Length(); LMdiChild *NewFront = Views[NewIdx]; if (NewFront) { NewFront->Raise(); } return true; } } return false; } + #if MDI_TAB_STYLE + void LMdiParent::OnMouseClick(LMouse &m) { ::LArray Views; if (GetChildren(Views)) { for (int i=0; id->Btn.Overlap(m.x, m.y)) { c->OnButtonClick(m); break; } else if (c->d->Tab.Overlap(m.x, m.y)) { c->OnTitleClick(m); break; } } } } + void LMdiParent::OnPosChange() { if (!IsAttached()) return; LFont *Fnt = GetFont(); LRect Client = GetClient(); LRect Tabs, Content; d->InOnPosChange = true; Tabs = Client; Tabs.y2 = Tabs.y1 + Fnt->GetHeight() + 1; Content = Client; Content.y1 = Tabs.y2 + 1; if (Tabs != d->Tabs || Content != d->Content) { d->Tabs = Tabs; d->Content = Content; LMdiChild *Last = d->Children.Length() ? d->Children.Last() : NULL; #if DEBUG_MDI LgiTrace("OnPosChange: Tabs=%s, Content=%s, Children=%i, Last=%s\n", d->Tabs.GetStr(), d->Content.GetStr(), Children.Length(), Last ? Last->Name() : 0); #endif for (int Idx=0; IdxChildren.Length(); Idx++) { LMdiChild *c = d->Children[Idx]; if (c == Last) { #if DEBUG_MDI LgiTrace(" [%p/%s], vis=%i, attached=%i\n", c, c->Name(), c == Last, c->IsAttached()); #endif if (!c->IsAttached()) c->LLayout::Attach(this); c->SetPos(d->Content); c->Visible(true); c->PourAll(); } else { #if DEBUG_MDI LgiTrace(" [%p/%s], vis=%i, attached=%i\n", c, c->Name(), c == Last, c->IsAttached()); #endif #ifdef BEOS if (c->IsAttached()) { #if DEBUG_MDI LgiTrace(" detaching %s\n", c->GetClass()); #endif c->LLayout::Detach(); c->SetParent(this); } #else c->Visible(false); #endif } } Invalidate(); } d->InOnPosChange = false; } + #endif + LRect LMdiParent::NewPos() { LRect Status(0, 0, (int)(X()*0.75), (int)(Y()*0.75)); int Block = 5; for (int y=0; y>Block; y++) { for (int x=0; x>Block; x++) { bool Has = false; for (auto c: Children) { LRect p = c->GetPos(); if (p.x1 >> Block == x && p.y1 >> Block == y) { Has = true; break; } } if (!Has) { Status.Offset(x << Block, y << Block); return Status; } } } return Status; } + bool LMdiParent::Detach() { d->InOnPosChange = true; return LLayout::Detach(); } + +bool LMdiParent::SetScrollBars(bool x, bool y) +{ + LAssert(!"Attempt to set scroll bars on LMdiParent.\n"); + return false; +} + void LMdiParent::OnChildrenChanged(LViewI *Wnd, bool Attaching) { #if MDI_TAB_STYLE + if (Attaching && Stricmp(Wnd->GetClass(), "LMdiChild")) + { + LAssert(0); + } + if (!d->InOnPosChange /*&& Attaching*/) { d->Tabs.ZOff(-1, -1); OnPosChange(); Invalidate(); } #else for (LViewI *v=Children.First(); v; ) { LMdiChild *c = dynamic_cast(v); v = Children.Next(); if (c) { c->Invalidate(&c->d->Title); if (!v) { LViewI *n = c->Children.First(); if (n) { n->Focus(true); } } } } #endif } + LViewI *LMdiParent::GetTop() { return d->Children.Length() > 0 ? d->Children.Last() : NULL; } + #if MDI_TAB_STYLE int LMdiParent::GetNextOrder() { int m = 0; for (int i=0; iChildren.Length(); i++) { m = MAX(m, d->Children[i]->GetOrder()); } return m + 1; } #endif \ No newline at end of file diff --git a/src/common/Lgi/Mru.cpp b/src/common/Lgi/Mru.cpp --- a/src/common/Lgi/Mru.cpp +++ b/src/common/Lgi/Mru.cpp @@ -1,436 +1,445 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Mru.h" #include "lgi/common/Variant.h" #include "lgi/common/Menu.h" //////////////////////////////////////////////////////////////////// #define DEBUG_LOG 0 #define M_MRU_BASE (M_USER+0x3500) struct LMruEntry { LString Display; LString Raw; }; class LMruPrivate { public: int Size = 10; LArray Items; LSubMenu *Parent = NULL; LFileType *SelectedType = NULL; LMruPrivate() { } ~LMruPrivate() { Items.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////// LMru::LMru() { d = new LMruPrivate; } LMru::~LMru() { DeleteObj(d); } bool LMru::SerializeEntry ( /// The displayable version of the reference (this should have any passwords blanked out) LString *Display, /// The form passed to the client software to open/save. (passwords NOT blanked) LString *Raw, /// The form safe to write to disk, if a password is present it must be encrypted. LString *Stored ) { if (Raw && Raw->Get()) { if (Stored) *Stored = *Raw; if (Display) *Display = *Raw; } else if (Stored && Stored->Get()) { if (Display) *Display = *Stored; if (Raw) *Raw = *Stored; } return true; } void LMru::GetFileTypes(LFileSelect *Dlg, bool Write) { Dlg->Type("All Files", LGI_ALL_FILES); } const char *LMru::_GetCurFile() { if (d->Items.Length()) return d->Items[0]->Raw; return NULL; } LFileType *LMru::GetSelectedType() { return d->SelectedType; } bool LMru::_OpenFile(const char *File, bool ReadOnly) { bool Status = OpenFile(File, ReadOnly); if (Status) AddFile(File, true); else RemoveFile(File); return Status; } bool LMru::_SaveFile(const char *FileName) { if (!FileName) return false; char File[MAX_PATH_LEN]; strcpy_s(File, sizeof(File), FileName); LFileType *st; if (!LFileExists(File) && (st = GetSelectedType()) && st->Extension()) { char *Cur = LGetExtension(File); if (!Cur) { // extract extension LString::Array a = LString(st->Extension()).Split(LGI_PATH_SEPARATOR); for (auto e: a) { LString::Array p = e.RSplit(".", 1); if (!p.Last().Equals("*")) { // bung the extension from the file type if not there char *Dot = strrchr(File, '.'); if (Dot) Dot++; else { Dot = File + strlen(File); *Dot++ = '.'; } strcpy_s(Dot, File+sizeof(File)-Dot, p.Last()); break; } } } } auto Status = SaveFile(File); if (Status) AddFile(File); else RemoveFile(File); return Status; } void LMru::_Update() { if (d->Items.Length() > d->Size) d->Items.Length(d->Size); if (d->Parent) { // remove existing items. d->Parent->Empty(); // add current items if (d->Items.Length() > 0) { for (int i=0; iItems.Length(); i++) { LMruEntry *c = d->Items[i]; d->Parent->AppendItem(c->Display ? c->Display : c->Raw, M_MRU_BASE + i, true); } } else { d->Parent->AppendItem("(none)", -1, false); } } } bool LMru::Set(LSubMenu *parent, int size) { d->Parent = parent; if (size > 0) d->Size = size; _Update(); return true; } const char *LMru::AddFile(const char *FileName, bool Update) { #if DEBUG_LOG LgiTrace("%s:%i - AddFile(%s,%i)\n", _FL, FileName, Update); #endif if (!FileName) return NULL; auto Status = FileName; LMruEntry *c = NULL; for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; #if DEBUG_LOG LgiTrace("[%i] cmp '%s' '%s'\n", i, e->Raw.Get(), FileName); #endif if (!LFileCompare(e->Raw, FileName)) { // exact string being added.. just move to the top // no need to reallocate if (strcmp(e->Raw, FileName) && e->Raw.Length() == strlen(FileName)) { e->Raw = FileName; // This fixes any changes in case... #if DEBUG_LOG LgiTrace("Updating raw case\n"); #endif } else { #if DEBUG_LOG LgiTrace("Moving to the top\n"); #endif } d->Items.DeleteAt(i, true); d->Items.AddAt(0, e); c = e; break; } } if (!c) { c = new LMruEntry; c->Raw = FileName; if (SerializeEntry(&c->Display, &c->Raw, NULL)) { #if DEBUG_LOG LgiTrace("Adding new entry %s %s\n", c->Raw.Get(), c->Display.Get()); #endif d->Items.AddAt(0, c); } else { LAssert(0); } } if (Update) { // update _Update(); } return Status; } void LMru::RemoveFile(const char *FileName, bool Update) { // remove from list if there for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; if (stricmp(e->Raw, FileName) == 0) { d->Items.DeleteAt(i); DeleteObj(e); break; } } if (Update) { _Update(); } } -bool LMru::DoFileDlg(LFileSelect &Select, bool Open) +void LMru::DoFileDlg(LFileSelect &Select, bool Open, std::function OnSelect) { GetFileTypes(&Select, false); Select.ShowReadOnly(Open); - if (Open ? Select.Open() : Select.Save()) + + auto Cb = [&](auto s, bool ok) { - d->SelectedType = Select.TypeAt(Select.SelectedType()); - if (Open) - _OpenFile(Select.Name(), Select.ReadOnly()); - else - _SaveFile(Select.Name()); - } - else return false; + if (ok) + { + d->SelectedType = s->TypeAt(s->SelectedType()); + if (Open) + _OpenFile(s->Name(), s->ReadOnly()); + else + _SaveFile(s->Name()); + } + + if (OnSelect) + OnSelect(ok); + }; - return true; + if (Open) + Select.Open(Cb); + else + Select.Save(Cb); } -bool LMru::OnCommand(int Cmd) +void LMru::OnCommand(int Cmd, std::function OnStatus) { bool Status = false; LViewI *Wnd = d->Parent->GetMenu() ? d->Parent->GetMenu()->WindowHandle() : 0; if (Wnd) { LFileSelect Select; Select.Parent(Wnd); Select.ClearTypes(); d->SelectedType = 0; if (_GetCurFile()) { if (LFileExists(_GetCurFile())) Select.Name(_GetCurFile()); char Path[256]; strcpy_s(Path, sizeof(Path), _GetCurFile()); LTrimDir(Path); if (LDirExists(Path)) Select.InitialDir(Path); } - if (Cmd == IDM_OPEN) + auto Process = [Cmd, OnStatus, this](bool ok) { - Status = DoFileDlg(Select, true); - } - else if (Cmd == IDM_SAVEAS) - { - Status = DoFileDlg(Select, false); - } - } + if (Cmd >= M_MRU_BASE && + Cmd < M_MRU_BASE + d->Items.Length()) + { + int Index = Cmd - M_MRU_BASE; + auto c = d->Items[Index]; + if (c) + ok &= _OpenFile(c->Raw, false); + } + + if (OnStatus) + OnStatus(ok); + }; - if (Cmd >= M_MRU_BASE && - Cmd < M_MRU_BASE + d->Items.Length()) - { - auto Index = Cmd - M_MRU_BASE; - auto c = d->Items[Index]; - if (c) - Status &= _OpenFile(c->Raw, false); + if (Cmd == IDM_OPEN) + DoFileDlg(Select, true, Process); + else if (Cmd == IDM_SAVEAS) + DoFileDlg(Select, false, Process); } - - return Status; } LMessage::Result LMru::OnEvent(LMessage *Msg) { /* if (d->Parent && MsgCode(Msg) == M_COMMAND) { #ifdef BEOS int32 Cmd = 0; int32 Event = 0; Msg->FindInt32("Cmd", &Cmd); Msg->FindInt32("Event", &Event); #else int Cmd = MsgA(Msg) & 0xffff; #endif OnCommand(Cmd); } */ return false; } bool LMru::Serialize(LDom *Store, const char *Prefix, bool Write) { bool Status = false; LVariant v; if (Store && Prefix) { if (Write) { // add our keys int Idx = 0; char Key[64]; LHashTbl, bool> Saved; for (int i=0; iItems.Length(); i++) { LMruEntry *e = d->Items[i]; LAssert(e->Raw.Get() != NULL); if (!Saved.Find(e->Raw)) { Saved.Add(e->Raw, true); LString Stored; if (SerializeEntry(NULL, &e->Raw, &Stored)) // Convert Raw -> Stored { sprintf_s(Key, sizeof(Key), "%s.Item%i", Prefix, Idx++); Store->SetValue(Key, v = Stored.Get()); } else LAssert(0); } } sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); Store->SetValue(Key, v = (int)Idx); } else { // clear ourself d->Items.DeleteObjects(); // read our keys in char Key[64]; sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); LVariant i; if (Store->GetValue(Key, i)) { for (int n=0; nGetValue(Key, File)) { LString Stored = File.Str(); LAssert(Stored.Get() != NULL); LAutoPtr e(new LMruEntry); if (SerializeEntry(&e->Display, &e->Raw, &Stored)) // Convert Stored -> Raw { d->Items.Add(e.Release()); } } } } _Update(); } } return Status; } diff --git a/src/common/Lgi/Variant.cpp b/src/common/Lgi/Variant.cpp --- a/src/common/Lgi/Variant.cpp +++ b/src/common/Lgi/Variant.cpp @@ -1,2345 +1,2345 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" const char *LVariant::TypeToString(LVariantType t) { switch (t) { case GV_NULL: return "NULL"; case GV_INT32: return "int32"; case GV_INT64: return "int64"; case GV_BOOL: return "bool"; case GV_DOUBLE: return "double"; case GV_STRING: return "String"; case GV_BINARY: return "Binary"; case GV_LIST: return "List"; case GV_DOM: return "Dom"; case GV_DOMREF: return "DomReference"; case GV_VOID_PTR: return "VoidPtr"; case GV_DATETIME: return "DateTime"; case GV_HASHTABLE: return "HashTable"; case GV_OPERATOR: return "Operator"; case GV_CUSTOM: return "Custom"; case GV_WSTRING: return "WString"; case GV_GVIEW: return "View"; case GV_STREAM: return "Stream"; case GV_LSURFACE: return "Surface"; case GV_LMOUSE: return "MouseEvent"; case GV_LKEY: return "KeyboardEvent"; default: return "Unknown"; } return NULL; } const char *LVariant::OperatorToString(LOperator op) { switch (op) { case OpNull: return "OpNull"; case OpAssign: return "OpAssign"; case OpPlus: return "OpPlus"; case OpUnaryPlus: return "OpUnaryPlus"; case OpMinus: return "OpMinus"; case OpUnaryMinus: return "OpUnaryMinus"; case OpMul: return "OpMul"; case OpDiv: return "OpDiv"; case OpMod: return "OpMod"; case OpLessThan: return "OpLessThan"; case OpLessThanEqual: return "OpLessThanEqual"; case OpGreaterThan: return "OpGreaterThan"; case OpGreaterThanEqual: return "OpGreaterThanEqual"; case OpEquals: return "OpEquals"; case OpNotEquals: return "OpNotEquals"; case OpPlusEquals: return "OpPlusEquals"; case OpMinusEquals: return "OpMinusEquals"; case OpMulEquals: return "OpMulEquals"; case OpDivEquals: return "OpDivEquals"; case OpPostInc: return "OpPostInc"; case OpPostDec: return "OpPostDec"; case OpPreInc: return "OpPreInc"; case OpPreDec: return "OpPreDec"; case OpAnd: return "OpAnd"; case OpOr: return "OpOr"; case OpNot: return "OpNot"; } return NULL; } LVariant::LVariant() { Type = GV_NULL; ZeroObj(Value); } LVariant::LVariant(LVariant const &v) { Type = GV_NULL; ZeroObj(Value); *this = v; } #if LVARIANT_SIZET LVariant::LVariant(size_t i) { Type = GV_NULL; *this = i; } #endif #if LVARIANT_SSIZET LVariant::LVariant(ssize_t i) { Type = GV_NULL; *this = i; } #endif LVariant::LVariant(int32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(uint32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(int64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(uint64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(double i) { Type = GV_DOUBLE; Value.Dbl = i; } LVariant::LVariant(const char *s) { Value.String = NewStr(s); Type = Value.String ? GV_STRING : GV_NULL; } LVariant::LVariant(const char16 *s) { Value.WString = NewStrW(s); Type = Value.WString ? GV_WSTRING : GV_NULL; } LVariant::LVariant(void *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p, char *name) { Type = GV_NULL; SetDomRef(p, name); } LVariant::LVariant(const LDateTime *d) { Type = GV_NULL; *this = d; } LVariant::LVariant(LOperator Op) { Type = GV_OPERATOR; Value.Op = Op; } LVariant::~LVariant() { Empty(); } bool LVariant::operator ==(LVariant &v) { switch (Type) { default: case GV_NULL: return v.Type == Type; case GV_INT32: return Value.Int == v.CastInt32(); case GV_INT64: return Value.Int64 == v.CastInt64(); case GV_BOOL: return Value.Bool == v.CastBool(); case GV_DOUBLE: return Value.Dbl == v.CastDouble(); case GV_STRING: { char *s = v.Str(); if (Value.String && s) return !strcmp(Value.String, s); break; } case GV_WSTRING: { char16 *w = v.WStr(); if (Value.WString && w) return !StrcmpW(Value.WString, w); break; } case GV_BINARY: { if (v.Type == Type && Value.Binary.Data == v.Value.Binary.Data && Value.Binary.Length == v.Value.Binary.Length) { return true; } break; } case GV_LIST: { if (!Value.Lst || !v.Value.Lst) return false; if (Value.Lst->Length() != v.Value.Lst->Length()) return false; auto ValIt = Value.Lst->begin(); auto VIt = v.Value.Lst->begin(); LVariant *a, *b; while ( (a = *ValIt) && (b = *VIt) ) { if (!(*a == *b)) return false; ValIt++; VIt++; } return true; } case GV_DOMREF: { return Value.DomRef.Dom == v.Value.DomRef.Dom && Value.DomRef.Name != 0 && v.Value.DomRef.Name != 0 && !stricmp(Value.DomRef.Name, v.Value.DomRef.Name); } case GV_DATETIME: { if (Value.Date && v.Value.Date) { return Value.Date->Compare(v.Value.Date) == 0; } break; } case GV_DOM: return Value.Dom == v.Value.Dom; case GV_OPERATOR: return Value.Op == v.Value.Op; case GV_CUSTOM: return Value.Custom == v.Value.Custom; case GV_LSURFACE: return Value.Surface.Ptr == v.Value.Surface.Ptr; case GV_GVIEW: return Value.View == v.Value.View; /* case GV_GFILE: return Value.File.Ptr == v.Value.File.Ptr; */ case GV_STREAM: return Value.Stream.Ptr == v.Value.Stream.Ptr; case GV_LMOUSE: return Value.Mouse == v.Value.Mouse; case GV_LKEY: return Value.Key == v.Value.Key; case GV_VOID_PTR: return Value.Ptr == v.Value.Ptr; case GV_HASHTABLE: { LAssert(0); break; } } return false; } LVariant &LVariant::operator =(const LDateTime *d) { Empty(); if (d) { Type = GV_DATETIME; Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *d; // if (Dirty) *Dirty = true; } } return *this; } LVariant &LVariant::operator =(bool i) { Empty(); Type = GV_BOOL; Value.Bool = i; // if (Dirty) *Dirty = true; return *this; } #if LVARIANT_SIZET LVariant &LVariant::operator =(size_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif #if LVARIANT_SSIZET LVariant &LVariant::operator =(ssize_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif LVariant &LVariant::operator =(int32 i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(uint32_t i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(int64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(uint64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(double i) { Empty(); Type = GV_DOUBLE; Value.Dbl = i; // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(const char *s) { Empty(); if (s) { Type = GV_STRING; Value.String = NewStr(s); } return *this; } LVariant &LVariant::operator =(const char16 *s) { Empty(); if (s) { Type = GV_WSTRING; Value.WString = NewStrW(s); } // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(void *p) { Empty(); if (p) { Type = GV_VOID_PTR; Value.Ptr = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LDom *p) { Empty(); if (p) { Type = GV_DOM; Value.Dom = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LView *p) { Empty(); if (p) { Type = GV_GVIEW; Value.View = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LMouse *p) { Empty(); if (p) { Type = GV_LMOUSE; Value.Mouse = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LKey *p) { Empty(); if (p) { Type = GV_LKEY; Value.Key = p; } return *this; } LVariant &LVariant::operator =(LStream *s) { Empty(); if (s) { Type = GV_STREAM; Value.Stream.Ptr = s; Value.Stream.Own = false; } return *this; } LVariant &LVariant::operator =(LVariant const &i) { if (&i == this) return *this; Empty(); Type = i.Type; switch (Type) { case GV_NULL: { break; } case GV_INT32: { Value.Int = i.Value.Int; break; } case GV_BOOL: { Value.Bool = i.Value.Bool; break; } case GV_INT64: { Value.Int64 = i.Value.Int64; break; } case GV_DOUBLE: { Value.Dbl = i.Value.Dbl; break; } case GV_STRING: { Value.String = NewStr(((LVariant&)i).Str()); break; } case GV_WSTRING: { Value.WString = NewStrW(i.Value.WString); break; } case GV_BINARY: { SetBinary(i.Value.Binary.Length, i.Value.Binary.Data); break; } case GV_LIST: { SetList(i.Value.Lst); break; } case GV_DOM: { Value.Dom = i.Value.Dom; break; } case GV_VOID_PTR: case GV_GVIEW: case GV_LMOUSE: case GV_LKEY: { Value.Ptr = i.Value.Ptr; break; } case GV_DATETIME: { if (i.Value.Date) { Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *i.Value.Date; } } break; } case GV_HASHTABLE: { if ((Value.Hash = new LHash)) { if (i.Value.Hash) { // const char *k; // for (LVariant *var = i.Value.Hash->First(&k); var; var = i.Value.Hash->Next(&k)) for (auto it : *i.Value.Hash) { Value.Hash->Add(it.key, new LVariant(*it.value)); } } } break; } case GV_CUSTOM: { Value.Custom.Data = i.Value.Custom.Data; Value.Custom.Dom = i.Value.Custom.Dom; break; } case GV_LSURFACE: { Value.Surface = i.Value.Surface; if (Value.Surface.Own && Value.Surface.Ptr) Value.Surface.Ptr->IncRef(); break; } /* case GV_GFILE: { Value.File = i.Value.File; if (Value.File.Own && Value.File.Ptr) Value.File.Ptr->AddRef(); break; } */ case GV_STREAM: { Value.Stream.Ptr = i.Value.Stream.Ptr; Value.Stream.Own = false; break; } default: { printf("%s:%i - Unknown variant type '%i'\n", _FL, Type); LAssert(0); break; } } // if (Dirty) *Dirty = true; return *this; } bool LVariant::SetDomRef(LDom *obj, char *name) { Empty(); Type = GV_DOMREF; Value.DomRef.Dom = obj; Value.DomRef.Name = NewStr(name); return Value.DomRef.Name != 0; } bool LVariant::SetBinary(ssize_t Len, void *Data, bool Own) { bool Status = false; Empty(); Type = GV_BINARY; Value.Binary.Length = Len; if (Own) { Value.Binary.Data = Data; Status = true; } else { if ((Value.Binary.Data = new uchar[Value.Binary.Length])) { if (Data) memcpy(Value.Binary.Data, Data, Value.Binary.Length); else memset(Value.Binary.Data, 0, Value.Binary.Length); Status = true; } } return Status; } bool LVariant::SetList(List *Lst) { Empty(); Type = GV_LIST; if ((Value.Lst = new List) && Lst) { for (auto s: *Lst) { LVariant *New = new LVariant; if (New) { *New = *s; Value.Lst->Insert(New); } } } return Value.Lst != 0; } bool LVariant::SetHashTable(LHash *Table, bool Copy) { Empty(); Type = GV_HASHTABLE; if (Copy && Table) { if ((Value.Hash = new LHash)) { // const char *k; // for (LVariant *p = Table->First(&k); p; p = Table->Next(&k)) for (auto i : *Table) { Value.Hash->Add(i.key, i.value); } } } else { Value.Hash = Table ? Table : new LHash; } return Value.Hash != 0; } bool LVariant::SetSurface(class LSurface *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_LSURFACE; Value.Surface.Ptr = Ptr; if ((Value.Surface.Own = Own)) Value.Surface.Ptr->IncRef(); return true; } bool LVariant::SetStream(class LStreamI *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_STREAM; Value.Stream.Ptr = Ptr; Value.Stream.Own = Own; return true; } bool LVariant::OwnStr(char *s) { Empty(); if (!s) return false; Type = GV_STRING; Value.String = s; return true; } bool LVariant::OwnStr(char16 *w) { Empty(); if (!w) return false; Type = GV_WSTRING; Value.WString = w; return true; } char *LVariant::ReleaseStr() { char *Ret = Str(); if (Ret) { Value.String = 0; Type = GV_NULL; } return Ret; } LString LVariant::LStr() { return Str(); } char *LVariant::Str() { if (Type == GV_STRING) return Value.String; if (Type == GV_WSTRING) { char *u = WideToUtf8(Value.WString); DeleteArray(Value.WString); Type = GV_STRING; return Value.String = u; } return 0; } char16 *LVariant::ReleaseWStr() { char16 *Ret = WStr(); if (Ret) { Value.WString = 0; Type = GV_NULL; } return Ret; } char16 *LVariant::WStr() { if (Type == GV_WSTRING) return Value.WString; if (Type == GV_STRING) { char16 *w = Utf8ToWide(Value.String); DeleteArray(Value.String); Type = GV_WSTRING; return Value.WString = w; } return 0; } void LVariant::Empty() { switch (Type) { default: break; case GV_CUSTOM: { Value.Custom.Data = 0; Value.Custom.Dom = 0; break; } case GV_DOMREF: { DeleteArray(Value.DomRef.Name); Value.DomRef.Dom = 0; break; } case GV_STRING: { DeleteArray(Value.String); break; } case GV_WSTRING: { DeleteArray(Value.WString); break; } case GV_BINARY: { char *d = (char*) Value.Binary.Data; DeleteArray(d); Value.Binary.Data = 0; break; } case GV_DATETIME: { DeleteObj(Value.Date); break; } case GV_LIST: { if (Value.Lst) { Value.Lst->DeleteObjects(); DeleteObj(Value.Lst); } break; } case GV_HASHTABLE: { if (Value.Hash) { // for (LVariant *v = (LVariant*) Value.Hash->First(); v; v = (LVariant*) Value.Hash->Next()) for (auto i : *Value.Hash) { DeleteObj(i.value); } DeleteObj(Value.Hash); } break; } case GV_LSURFACE: { if (Value.Surface.Own && Value.Surface.Ptr) { Value.Surface.Ptr->DecRef(); Value.Surface.Ptr = NULL; } break; } /* case GV_GFILE: { if (Value.File.Ptr && Value.File.Own) { Value.File.Ptr->DecRef(); Value.File.Ptr = NULL; } break; } */ case GV_STREAM: { if (Value.Stream.Ptr) { if (Value.Stream.Own) delete Value.Stream.Ptr; Value.Stream.Ptr = NULL; } break; } } Type = GV_NULL; ZeroObj(Value); } int64 LVariant::Length() { switch (Type) { case GV_INT32: return sizeof(Value.Int); case GV_INT64: return sizeof(Value.Int64); case GV_BOOL: return sizeof(Value.Bool); case GV_DOUBLE: return sizeof(Value.Dbl); case GV_STRING: return Value.String ? strlen(Value.String) : 0; case GV_BINARY: return Value.Binary.Length; case GV_LIST: { int64 Sz = 0; if (Value.Lst) { for (auto v : *Value.Lst) Sz += v->Length(); } return Sz; } case GV_DOM: { LVariant v; if (Value.Dom) Value.Dom->GetValue("length", v); return v.CastInt32(); } case GV_DOMREF: break; case GV_VOID_PTR: return sizeof(Value.Ptr); case GV_DATETIME: return sizeof(*Value.Date); case GV_HASHTABLE: { int64 Sz = 0; if (Value.Hash) { // for (LVariant *v=Value.Hash->First(); v; v=Value.Hash->Next()) for (auto i : *Value.Hash) Sz += i.value->Length(); } return Sz; } case GV_OPERATOR: return sizeof(Value.Op); case GV_CUSTOM: break; case GV_WSTRING: return Value.WString ? StrlenW(Value.WString) * sizeof(char16) : 0; case GV_LSURFACE: { int64 Sz = 0; if (Value.Surface.Ptr) { LRect r = Value.Surface.Ptr->Bounds(); int Bytes = Value.Surface.Ptr->GetBits() >> 3; Sz = r.X() * r.Y() * Bytes; } return Sz; } case GV_GVIEW: return sizeof(LView); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_STREAM: return Value.Stream.Ptr->GetSize(); default: break; } return 0; } bool LVariant::IsInt() { return Type == GV_INT32 || Type == GV_INT64; } bool LVariant::IsBool() { return Type == GV_BOOL; } bool LVariant::IsDouble() { return Type == GV_DOUBLE; } bool LVariant::IsString() { return Type == GV_STRING; } bool LVariant::IsBinary() { return Type == GV_BINARY; } bool LVariant::IsNull() { return Type == GV_NULL; } #define IsList() (Type == GV_LIST && Value.Lst) LVariant &LVariant::Cast(LVariantType NewType) { if (NewType != Type) { switch (NewType) { default: { // No conversion possible break; } case GV_INT32: { *this = (int)CastInt32(); break; } case GV_INT64: { *this = (int64_t) CastInt64(); break; } case GV_BOOL: { if (Type == GV_DOUBLE) { *this = Value.Dbl != 0.0; } else { *this = CastInt32() != 0; } break; } case GV_DOUBLE: { *this = CastDouble(); break; } case GV_STRING: { *this = CastString(); break; } case GV_DATETIME: { switch (Type) { case GV_STRING: { // String -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set(Value.String); Empty(); Value.Date = Dt; Type = NewType; } break; } case GV_INT64: { // Int64 (system date) -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set((uint64_t)Value.Int64); Empty(); Value.Date = Dt; Type = NewType; } break; } default: { // No conversion available break; } } break; } } } return *this; } -void *LVariant::CastVoidPtr() +void *LVariant::CastVoidPtr() const { switch (Type) { default: break; case GV_STRING: return Value.String; case GV_BINARY: return Value.Binary.Data; case GV_LIST: return Value.Lst; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_VOID_PTR: return Value.Ptr; case GV_DATETIME: return Value.Date; case GV_HASHTABLE: return Value.Hash; case GV_CUSTOM: return Value.Custom.Data; case GV_WSTRING: return Value.WString; case GV_LSURFACE: return Value.Surface.Ptr; case GV_GVIEW: return Value.View; case GV_LMOUSE: return Value.Mouse; case GV_LKEY: return Value.Key; } return 0; } -LDom *LVariant::CastDom() +LDom *LVariant::CastDom() const { switch (Type) { default: break; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_STREAM: return dynamic_cast(Value.Stream.Ptr); case GV_LSURFACE: return Value.Surface.Ptr; case GV_CUSTOM: return Value.Custom.Dom; } return NULL; } -bool LVariant::CastBool() +bool LVariant::CastBool() const { switch (Type) { default: LAssert(0); break; case GV_NULL: return false; case GV_INT32: return Value.Int != 0; case GV_INT64: return Value.Int64 != 0; case GV_BOOL: return Value.Bool; case GV_DOUBLE: return Value.Dbl != 0.0; case GV_BINARY: return Value.Binary.Data != NULL; case GV_LIST: return Value.Lst != NULL; case GV_DOM: return Value.Dom != NULL; case GV_DOMREF: return Value.DomRef.Dom != NULL; case GV_VOID_PTR: return Value.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_DATETIME: return Value.Date != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_OPERATOR: return Value.Op != OpNull; case GV_CUSTOM: return Value.Custom.Dom != 0 && Value.Custom.Data != 0; /* case GV_GFILE: return Value.File.Ptr != NULL; */ case GV_STREAM: return Value.Stream.Ptr != NULL; // As far as I understand this is the behavour in Python, which I'm using for // a reference to what the "correct" thing to do here is. Basically it's treating // the string like a pointer instead of a value. If the pointer is valid the // conversion to bool return true, and false if it's not a valid pointer. This // means things like if (!StrinLVariant) evaluate correctly in the scripting engine // but it means that if you want to evaluate the value of the varient you should // use CastInt32 instead. case GV_STRING: return ValidStr(Value.String); case GV_WSTRING: return ValidStrW(Value.WString); } return false; } -double LVariant::CastDouble() +double LVariant::CastDouble() const { switch (Type) { default: break; case GV_BOOL: return Value.Bool ? 1.0 : 0.0; case GV_DOUBLE: return Value.Dbl; case GV_INT32: return (double)Value.Int; case GV_INT64: return (double)Value.Int64; case GV_STRING: return Value.String ? atof(Value.String) : 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastDouble(); } } break; } } return 0; } -int32 LVariant::CastInt32() +int32 LVariant::CastInt32() const { switch (Type) { default: break; case GV_BOOL: return (int32)Value.Bool; case GV_DOUBLE: return (int32)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return (int32)Value.Int64; case GV_STRING: if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return static_cast(Atoi(Value.String, 16)); return atoi(Value.String); case GV_DOM: return Value.Dom != 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt32(); } } break; } case GV_LIST: return Value.Lst != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_LSURFACE: return Value.Surface.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_STREAM: return Value.Stream.Ptr != NULL; } return 0; } -int64 LVariant::CastInt64() +int64 LVariant::CastInt64() const { switch (Type) { default: break; case GV_BOOL: return (int64)Value.Bool; case GV_DOUBLE: return (int64)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return Value.Int64; case GV_STRING: { if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return Atoi(Value.String, 16); return Atoi(Value.String); } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt64(); } } break; } } return 0; } char *LVariant::CastString() { char i[40]; switch (Type) { case GV_LIST: { LStringPipe p(256); List::I it = Value.Lst->begin(); bool First = true; p.Print("{"); for (LVariant *v = *it; v; v = *++it) { if (v->Type == GV_STRING || v->Type == GV_WSTRING) p.Print("%s\"%s\"", First ? "" : ", ", v->CastString()); else p.Print("%s%s", First ? "" : ", ", v->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_HASHTABLE: { LStringPipe p(256); p.Print("{"); bool First = true; // const char *k; // for (LVariant *v = Value.Hash->First(&k); v; v = Value.Hash->Next(&k)) for (auto i : *Value.Hash) { p.Print("%s%s = %s", First ? "" : ", ", i.key, i.value->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastString(); } } break; } case GV_INT32: { sprintf_s(i, sizeof(i), "%i", Value.Int); *this = i; return Str(); } case GV_DOUBLE: { sprintf_s(i, sizeof(i), "%f", Value.Dbl); *this = i; return Str(); } case GV_BOOL: { sprintf_s(i, sizeof(i), "%i", Value.Bool); *this = i; return Str(); } case GV_INT64: { sprintf_s(i, sizeof(i), LPrintfInt64, Value.Int64); *this = i; return Str(); } case GV_STRING: case GV_WSTRING: { return Str(); } case GV_DATETIME: { if (Value.Date) { char s[64]; Value.Date->Get(s, sizeof(s)); *this = s; return Str(); } break; } case GV_DOM: { sprintf_s(i, sizeof(i), "dom:%p", Value.Dom); *this = i; break; } default: { break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////// LDom *LDom::ResolveObject(const char *Var, LString &Name, LString &Array) { LDom *Object = this; try { // Tokenize the string LString::Array t; for (auto *s = Var; s && *s; ) { const char *e = s; while (*e && *e != '.') { if (*e == '[') { e++; while (*e && *e != ']') { if (*e == '\"' || *e == '\'') { char d = *e++; while (*e && *e != d) e++; if (*e == d) e++; } else e++; } if (*e == ']') e++; } else e++; } LString part = LString(s, e - s).Strip(); if (part.Length() > 0) t.New() = part; // Store non-empty part s = *e ? e + 1 : e; } // Process elements for (int i=0; iGetVariant(Obj, v, Index)) { if (v.Type == GV_LIST) { int N = atoi(Index); LVariant *Element = v.Value.Lst->ItemAt(N); if (Element && Element->Type == GV_DOM) { Object = Element->Value.Dom; } else { return NULL; } } else if (v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } else { return NULL; } } else { if (Object->GetVariant(Obj, v) && v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } } } } catch (...) { LgiTrace("LDom::ResolveObject crashed: '%s'\n", Var); return NULL; } return Object; } struct LDomPropMap { LHashTbl, LDomProperty> ToProp; LHashTbl, const char *> ToString; LDomPropMap() { #undef _ #define _(symbol, txt) Define(txt, symbol); #include "lgi/common/DomFields.h" #undef _ } void Define(const char *s, LDomProperty p) { if (!s) return; #if defined(_DEBUG) // Check for duplicates. auto existing_prop = ToProp.Find(s); LAssert(existing_prop == ObjNone); auto existing_str = ToString.Find(p); LAssert(existing_str == NULL); #endif ToProp.Add(s, p); ToString.Add(p, s); } } DomPropMap; LDomProperty LStringToDomProp(const char *Str) { return DomPropMap.ToProp.Find(Str); } const char *LDomPropToString(LDomProperty Prop) { return DomPropMap.ToString.Find(Prop); } bool LDom::GetValue(const char *Var, LVariant &Value) { if (!Var) return false; if (!_OnAccess(true)) { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); return false; } bool Status = false; LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->GetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr); } _OnAccess(false); return Status; } bool LDom::SetValue(const char *Var, LVariant &Value) { bool Status = false; if (Var) { // LMutex *Sem = dynamic_cast(this); if (_OnAccess(true)) { LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->SetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr); } _OnAccess(false); } else { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); } } return Status; } bool LVariant::Add(LVariant *v, int Where) { if (!v) { LAssert(!"No value to insert."); return false; } if (Type == GV_NULL) SetList(); if (Type != GV_LIST) { LAssert(!"Not a list variant"); return false; } return Value.Lst->Insert(v, Where); } LString LVariant::ToString() { LString s; switch (Type) { case GV_NULL: s = "NULL"; break; case GV_INT32: s.Printf("(int)%i", Value.Int); break; case GV_INT64: s.Printf("(int64)" LPrintfInt64, Value.Int64); break; case GV_BOOL: s.Printf("(bool)%s", Value.Bool ? "true" : "false"); break; case GV_DOUBLE: s.Printf("(double)%f", Value.Dbl); break; case GV_STRING: s.Printf("(string)\"%s\"", Value.String); break; case GV_BINARY: s.Printf("(binary[%i])%p", Value.Binary.Length, Value.Binary.Data); break; case GV_LIST: s.Printf("(list[%i])%p", Value.Lst?Value.Lst->Length():0, Value.Lst); break; case GV_DOM: s.Printf("(dom)%p", Value.Dom); break; case GV_DOMREF: s.Printf("(dom)%p.%s", Value.DomRef.Dom, Value.DomRef.Name); break; case GV_VOID_PTR: s.Printf("(void*)%p", Value.Ptr); break; case GV_DATETIME: { char dt[64]; Value.Date->Get(dt, sizeof(dt)); s.Printf("(datetime)%s", dt); break; } case GV_HASHTABLE: s.Printf("(hashtbl)%p", Value.Hash); break; case GV_OPERATOR: s.Printf("(operator)%s", OperatorToString(Value.Op)); break; case GV_CUSTOM: s.Printf("(custom.%s)%p", Value.Custom.Dom->GetName(), Value.Custom.Data); break; case GV_WSTRING: s.Printf("(wstring)\"%S\"", Value.WString); break; case GV_LSURFACE: s.Printf("(gsurface)%p", Value.Surface.Ptr); break; case GV_GVIEW: s.Printf("(gview)%p", Value.View); break; case GV_LMOUSE: s.Printf("(gmouse)%p", Value.Mouse); break; case GV_LKEY: s.Printf("(gkey)%p", Value.Key); break; case GV_STREAM: s.Printf("(stream)%p", Value.Stream.Ptr); break; default: s = "(unknown)NULL"; break; } return s; } ///////////////////////////////////////////////////////////////////////////////////////////////////// LCustomType::LCustomType(const char *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::LCustomType(const char16 *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::~LCustomType() { Flds.DeleteObjects(); Methods.DeleteObjects(); } size_t LCustomType::Sizeof() { return (size_t)PadSize(); } ssize_t LCustomType::PadSize() { if (Pack > 1) { // Bump size to the pack boundary... int Remain = Size % Pack; if (Remain) return Size + Pack - Remain; } return Size; } int LCustomType::IndexOf(const char *Field) { return FldMap.Find(Field); } int LCustomType::AddressOf(const char *Field) { if (!Field) return -1; for (unsigned i=0; iName, Field)) return (int)i; } return -1; } bool LCustomType::DefineField(const char *Name, LCustomType *Type, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL || Type == NULL) { LAssert(!"Invalid parameter."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = GV_CUSTOM; Def->Bytes = Type->Sizeof(); Def->ArrayLen = ArrayLen; Size += Def->Bytes * ArrayLen; return true; } bool LCustomType::DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL) { LAssert(!"No field name."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = Type; Def->Bytes = Bytes; Def->ArrayLen = ArrayLen; Size += Bytes * ArrayLen; return true; } LCustomType::Method *LCustomType::GetMethod(const char *Name) { return MethodMap.Find(Name); } LCustomType::Method *LCustomType::DefineMethod(const char *Name, LArray &Params, size_t Address) { Method *m = MethodMap.Find(Name); if (m) { LAssert(!"Method already defined."); return NULL; } Methods.Add(m = new Method); m->Name = Name; m->Params = Params; m->Address = Address; MethodMap.Add(Name, m); return m; } bool LCustomType::CustomField::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String Value = Name; break; case ObjLength: // Type: Int32 Value = Bytes; break; default: return false; } return true; } ssize_t LCustomType::CustomField::Sizeof() { switch (Type) { case GV_INT32: return sizeof(int32); case GV_INT64: return sizeof(int64); case GV_BOOL: return sizeof(bool); case GV_DOUBLE: return sizeof(double); case GV_STRING: return sizeof(char); case GV_DATETIME: return sizeof(LDateTime); case GV_HASHTABLE: return sizeof(LVariant::LHash); case GV_OPERATOR: return sizeof(LOperator); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_CUSTOM: return Nested->Sizeof(); default: LAssert(!"Unknown type."); break; } return 0; } bool LCustomType::Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } uint8_t *Ptr = This + Def->Offset; Out.Empty(); switch (Def->Type) { case GV_STRING: { int Len; for (Len = 0; Ptr[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStr((char*)Ptr, Len)); break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; int Len; for (Len = 0; p[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStrW(p, Len)); break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Out.Value.Int = Ptr[ArrayIndex]; Out.Type = GV_INT32; break; } case 2: { Out.Value.Int = ((uint16*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 4: { Out.Value.Int = ((uint32_t*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 8: { Out.Value.Int64 = ((uint64*)Ptr)[ArrayIndex]; Out.Type = GV_INT64; break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { Out = *((LVariant*)Ptr); break; } default: { LAssert(!"Impl this type."); return false; } } return true; } bool LCustomType::Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; uint8_t *Ptr = This + Def->Offset; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } switch (Def->Type) { case GV_STRING: { char *s = In.Str(); if (!s) { *Ptr = 0; break; } if (Def->Bytes == 1) { // Straight up string copy... if (s) strcpy_s((char*)Ptr, Def->ArrayLen, s); else *Ptr = 0; } else if (Def->Bytes == sizeof(char16)) { // utf8 -> wide conversion... const void *In = Ptr; ssize_t Len = strlen(s); ssize_t Ch = LBufConvertCp(Ptr, LGI_WideCharset, Def->ArrayLen-1, In, "utf-8", Len); if (Ch >= 0) { // Null terminate Ptr[Ch] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; char16 *w = In.WStr(); if (!w) { *p = 0; break; } if (Def->Bytes == sizeof(char16)) { // Straight string copy... Strcpy(p, Def->ArrayLen, w); } else { // Conversion to utf-8 const void *In = Ptr; ssize_t Len = StrlenW(w) * sizeof(char16); ssize_t Ch = LBufConvertCp(Ptr, "utf-8", Def->ArrayLen-sizeof(char16), In, LGI_WideCharset, Len); if (Ch >= 0) { // Null terminate p[Ch/sizeof(char16)] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Ptr[ArrayIndex] = In.CastInt32(); break; } case 2: { ((uint16*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 4: { ((uint32_t*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 8: { ((uint64*)Ptr)[ArrayIndex] = In.CastInt64(); break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { *((LVariant*)Ptr) = In; break; } default: LAssert(!"Impl this type."); break; } return true; } bool LCustomType::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String { Value = Name; return true; } case ObjType: // Type: String { Value = "LCustomType"; return true; } case ObjLength: // Type: Int32 { Value = (int)Sizeof(); return true; } case ObjField: // Type: CustomField[] { if (Array) { int Index = atoi(Array); if (Index >= 0 && Index < Flds.Length()) { Value = (LDom*)&Flds[Index]; return true; } } else { Value = (int)Flds.Length(); break; } break; } default: break; } LAssert(0); return false; } bool LCustomType::SetVariant(const char *Name, LVariant &Value, const char *Array) { LAssert(0); return false; } bool LCustomType::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (!MethodName || !ReturnValue) return false; if (!_stricmp(MethodName, "New")) { ReturnValue->Empty(); ReturnValue->Type = GV_CUSTOM; ReturnValue->Value.Custom.Dom = this; ReturnValue->Value.Custom.Data = new uint8_t[Sizeof()]; return true; } if (!_stricmp(MethodName, "Delete")) // Type: (Object) { for (unsigned i=0; iType == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } } return true; } LAssert(0); return false; } diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp --- a/src/common/Lgi/ViewCommon.cpp +++ b/src/common/Lgi/ViewCommon.cpp @@ -1,2539 +1,2711 @@ /// \file /// \author Matthew Allen #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/LgiRes.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Popup.h" #include "lgi/common/CssTools.h" #include "ViewPriv.h" #if 0 #define DEBUG_CAPTURE(...) printf(__VA_ARGS__) #else #define DEBUG_CAPTURE(...) #endif #if !WINNATIVE int LPulseThread::PulseThreadCount = 0; #endif ////////////////////////////////////////////////////////////////////////////////////// // Helper LPoint lgi_view_offset(LViewI *v, bool Debug = false) { LPoint Offset; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) break; LRect pos = p->GetPos(); LRect cli = p->GetClient(false); if (Debug) { const char *cls = p->GetClass(); LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n", cls, pos.x1, pos.y1, Offset.x + pos.x1, Offset.y + pos.y1, cli.GetStr()); } Offset.x += pos.x1 + cli.x1; Offset.y += pos.y1 + cli.y1; } return Offset; } LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug) { static LMouse Temp; Temp = Info; if (Wnd) { if (Debug #if 0 || Capturing #endif ) LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n", Info.Target?Info.Target->GetClass():"", Wnd?Wnd->GetClass():"", Info.x, Info.y); if (Temp.Target != Wnd) { if (Temp.Target) { auto *TargetWnd = Temp.Target->GetWindow(); auto *WndWnd = Wnd->GetWindow(); if (TargetWnd == WndWnd) { LPoint TargetOff = lgi_view_offset(Temp.Target, Debug); if (Debug) LgiTrace(" WndOffset:\n"); LPoint WndOffset = lgi_view_offset(Wnd, Debug); Temp.x += TargetOff.x - WndOffset.x; Temp.y += TargetOff.y - WndOffset.y; #if 0 LRect c = Wnd->GetClient(false); Temp.x -= c.x1; Temp.y -= c.y1; if (Debug) LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1); #endif Temp.Target = Wnd; } } else { LPoint Offset; Temp.Target = Wnd; if (Wnd->WindowVirtualOffset(&Offset)) { LRect c = Wnd->GetClient(false); Temp.x -= Offset.x + c.x1; Temp.y -= Offset.y + c.y1; } } } } LAssert(Temp.Target != NULL); return Temp; } ////////////////////////////////////////////////////////////////////////////////////// // LView class methods LViewI *LView::_Capturing = 0; LViewI *LView::_Over = 0; #if LGI_VIEW_HASH struct ViewTbl : public LMutex { typedef LHashTbl, int> T; private: T Map; public: ViewTbl() : Map(2000), LMutex("ViewTbl") { } T *Lock() { if (!LMutex::Lock(_FL)) return NULL; return ⤅ } } ViewTblInst; bool LView::LockHandler(LViewI *v, LView::LockOp Op) { ViewTbl::T *m = ViewTblInst.Lock(); if (!m) return false; int Ref = m->Find(v); bool Status = false; switch (Op) { case OpCreate: { if (Ref == 0) Status = m->Add(v, 1); else LAssert(!"Already exists?"); break; } case OpDelete: { if (Ref == 1) Status = m->Delete(v); else LAssert(!"Either locked or missing."); break; } case OpExists: { Status = Ref > 0; break; } } ViewTblInst.Unlock(); return Status; } #endif LView::LView(OsView view) { #ifdef _DEBUG _Debug = false; #endif d = new LViewPrivate(this); #ifdef LGI_SDL _View = this; #elif LGI_VIEW_HANDLE && !defined(HAIKU) _View = view; #endif - _Window = 0; - _Lock = 0; - _InLock = 0; - _BorderSize = 0; - _IsToolBar = false; Pos.ZOff(-1, -1); WndFlags = GWF_VISIBLE; #ifndef LGI_VIEW_HASH #error "LGI_VIEW_HASH needs to be defined" #elif LGI_VIEW_HASH LockHandler(this, OpCreate); // printf("Adding %p to hash\n", (LViewI*)this); #endif } LView::~LView() { if (d->SinkHnd >= 0) { LEventSinkMap::Dispatch.RemoveSink(this); d->SinkHnd = -1; } #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif for (unsigned i=0; iOwner == this) - { - // printf("%s:%i - ~%s setting %s->Owner to NULL\n", _FL, GetClass(), pu->GetClass()); pu->Owner = NULL; - } } _Delete(); - DeleteObj(d); + + // printf("%p::~LView delete %p th=%u\n", this, d, GetCurrentThreadId()); + DeleteObj(d); + // printf("%p::~LView\n", this); } int LView::AddDispatch() { if (d->SinkHnd < 0) d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this); return d->SinkHnd; } LString LView::CssStyles(const char *Set) { if (Set) { d->Styles = Set; d->CssDirty = true; } return d->Styles; } LString::Array *LView::CssClasses() { return &d->Classes; } LArray LView::IterateViews() { LArray a; for (auto c: Children) a.Add(c); return a; } bool LView::AddView(LViewI *v, int Where) { LAssert(!Children.HasItem(v)); bool Add = Children.Insert(v, Where); if (Add) { LView *gv = v->GetGView(); if (gv && gv->_Window != _Window) { LAssert(!_InLock); gv->_Window = _Window; } v->SetParent(this); v->OnAttach(); OnChildrenChanged(v, true); } return Add; } bool LView::DelView(LViewI *v) { bool Has = Children.HasItem(v); bool b = Children.Delete(v); if (Has) OnChildrenChanged(v, false); Has = Children.HasItem(v); LAssert(!Has); return b; } bool LView::HasView(LViewI *v) { return Children.HasItem(v); } OsWindow LView::WindowHandle() { - auto *w = GetWindow(); - return (w) ? w->WindowHandle() : (OsWindow)NULL; + auto w = GetWindow(); + auto h = w ? w->WindowHandle() : NULL; + return h; } LWindow *LView::GetWindow() { if (!_Window) { // Walk up parent list and find someone who has a window auto *w = d->GetParent(); for (; w; w = w->d ? w->d->GetParent() : NULL) { if (w->_Window) { LAssert(!_InLock); _Window = w->_Window; break; } } } return dynamic_cast(_Window); } bool LView::Lock(const char *file, int line, int TimeOut) { - if (!_Window) - GetWindow(); + #ifdef HAIKU - _InLock++; - // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); - if (_Window && _Window->_Lock) - { - if (TimeOut < 0) + bool Debug = !Stricmp("LList", GetClass()); + if (!d || !d->Hnd) + { + if (Debug) + printf("%s:%i - no handle %p %p\n", _FL, d, d ? d->Hnd : NULL); + return false; + } + + if (d->Hnd->Parent() == NULL) + { + if (Debug) + printf("%s:%p - Lock() no parent.\n", GetClass(), this); + return true; + } + + if (TimeOut >= 0) + { + auto r = d->Hnd->LockLooperWithTimeout(TimeOut * 1000); + if (r == B_OK) + { + _InLock++; + if (Debug) + printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent()); + return true; + } + + printf("%s:%i - Lock(%i) failed with %x.\n", _FL, TimeOut, r); + return false; + } + + auto r = d->Hnd->LockLooper(); + if (r) { - return _Window->_Lock->Lock(file, line); + _InLock++; + + if (Debug) + { + auto w = WindowHandle(); + printf("%s:%p - Lock() cnt=%i myThread=%i wndThread=%i.\n", + GetClass(), + this, + _InLock, + GetCurrentThreadId(), + w ? w->Thread() : -1); + } + return true; } - else + + if (Debug) + printf("%s:%i - Lock(%s:%i) failed.\n", _FL, file, line); + return false; + #else + if (!_Window) + GetWindow(); + + _InLock++; + // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); + if (_Window && _Window->_Lock) { - return _Window->_Lock->LockWithTimeout(TimeOut, file, line); + if (TimeOut < 0) + { + return _Window->_Lock->Lock(file, line); + } + else + { + return _Window->_Lock->LockWithTimeout(TimeOut, file, line); + } } - } - return true; + return true; + #endif } void LView::Unlock() { #ifdef HAIKU if (!d || !d->Hnd) { printf("%s:%i - Unlock() error, no hnd.\n", _FL); return; } if (!d->Hnd->Parent()) { // printf("%s:%p - Unlock() no parent.\n", GetClass(), this); return; } if (_InLock > 0) { // printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock); d->Hnd->UnlockLooper(); _InLock--; // printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock); } else { printf("%s:%i - Unlock() without lock.\n", _FL); } #else if (_Window && _Window->_Lock) { _Window->_Lock->Unlock(); } _InLock--; // LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock); #endif } void LView::OnMouseClick(LMouse &m) { } void LView::OnMouseEnter(LMouse &m) { } void LView::OnMouseExit(LMouse &m) { } void LView::OnMouseMove(LMouse &m) { } bool LView::OnMouseWheel(double Lines) { return false; } bool LView::OnKey(LKey &k) { return false; } void LView::OnAttach() { List::I it = Children.begin(); for (LViewI *v = *it; v; v = *++it) { if (!v->GetParent()) v->SetParent(this); } #if 0 // defined __GTK_H__ if (_View && !DropTarget()) { // If one of our parents is drop capable we need to set a dest here LViewI *p; for (p = GetParent(); p; p = p->GetParent()) { if (p->DropTarget()) { break; } } if (p) { Gtk::gtk_drag_dest_set( _View, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); // printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass()); } else { Gtk::gtk_drag_dest_unset(_View); // printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass()); } } #endif } void LView::OnCreate() { } void LView::OnDestroy() { } void LView::OnFocus(bool f) { // printf("%s::OnFocus(%i)\n", GetClass(), f); } void LView::OnPulse() { } void LView::OnPosChange() { } bool LView::OnRequestClose(bool OsShuttingDown) { return true; } int LView::OnHitTest(int x, int y) { return -1; } void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { } void LView::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } int LView::OnNotify(LViewI *Ctrl, LNotification Data) { if (!Ctrl) return 0; if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate) { // Default activation is to focus the current control. Focus(true); } else if (d && d->Parent) { // default behaviour is just to pass the // notification up to the parent // FIXME: eventually we need to call the 'LNotification' parent fn... return d->Parent->OnNotify(Ctrl, Data); } return 0; } int LView::OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void LView::OnNcPaint(LSurface *pDC, LRect &r) { int Border = Sunken() || Raised() ? _BorderSize : 0; if (Border == 2) { LEdge e; if (Sunken()) e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge; else e = DefaultRaisedEdge; #if WINNATIVE if (d->IsThemed) DrawThemeBorder(pDC, r); else #endif LWideBorder(pDC, r, e); } else if (Border == 1) { LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); } } #if LGI_COCOA || defined(__GTK_H__) -/* -uint64 nPaint = 0; -uint64 PaintTime = 0; -*/ - -void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) -{ /* - uint64 StartTs = Update ? LCurrentTime() : 0; - d->InPaint = true; + uint64 nPaint = 0; + uint64 PaintTime = 0; */ - // Create temp DC if needed... - LAutoPtr Local; - if (!pDC) - { - if (!Local.Reset(new LScreenDC(this))) - return; - pDC = Local; - } - - #if 0 - // This is useful for coverage checking - pDC->Colour(LColour(255, 0, 255)); - pDC->Rectangle(); - #endif - - // Non-Client drawing - LRect r; - if (Offset) - { - r = Pos; - r.Offset(Offset); - } - else + void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { - r = GetClient().ZeroTranslate(); - } + /* + uint64 StartTs = Update ? LCurrentTime() : 0; + d->InPaint = true; + */ + + // Create temp DC if needed... + LAutoPtr Local; + if (!pDC) + { + if (!Local.Reset(new LScreenDC(this))) + return; + pDC = Local; + } + + #if 0 + // This is useful for coverage checking + pDC->Colour(LColour(255, 0, 255)); + pDC->Rectangle(); + #endif - pDC->SetClient(&r); - LRect zr1 = r.ZeroTranslate(), zr2 = zr1; - OnNcPaint(pDC, zr1); - pDC->SetClient(NULL); - if (zr2 != zr1) - { - r.x1 -= zr2.x1 - zr1.x1; - r.y1 -= zr2.y1 - zr1.y1; - r.x2 -= zr2.x2 - zr1.x2; - r.y2 -= zr2.y2 - zr1.y2; - } - LPoint o(r.x1, r.y1); // Origin of client + // Non-Client drawing + LRect r; + if (Offset) + { + r = Pos; + r.Offset(Offset); + } + else + { + r = GetClient().ZeroTranslate(); + } - // Paint this view's contents... - pDC->SetClient(&r); - - #if 0 - if (_Debug) - { - #if defined(__GTK_H__) - Gtk::cairo_matrix_t matrix; - cairo_get_matrix(pDC->Handle(), &matrix); + pDC->SetClient(&r); + LRect zr1 = r.ZeroTranslate(), zr2 = zr1; + OnNcPaint(pDC, zr1); + pDC->SetClient(NULL); + if (zr2 != zr1) + { + r.x1 -= zr2.x1 - zr1.x1; + r.y1 -= zr2.y1 - zr1.y1; + r.x2 -= zr2.x2 - zr1.x2; + r.y2 -= zr2.y2 - zr1.y2; + } + LPoint o(r.x1, r.y1); // Origin of client - double ex[4]; - cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); - ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; - LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", - GetClass(), r.GetStr(), - ex[0], ex[1], ex[2], ex[3], - matrix.x0, matrix.y0); - #elif LGI_COCOA - auto Ctx = pDC->Handle(); - CGAffineTransform t = CGContextGetCTM(Ctx); - LRect cr = CGContextGetClipBoundingBox(Ctx); - printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", - GetClass(), - GetPos().GetStr(), - t.a, t.b, t.c, t.d, t.tx, t.ty, - cr.GetStr(), - r.GetStr()); - #endif - } - #endif + // Paint this view's contents... + pDC->SetClient(&r); + + #if 0 + if (_Debug) + { + #if defined(__GTK_H__) + Gtk::cairo_matrix_t matrix; + cairo_get_matrix(pDC->Handle(), &matrix); - OnPaint(pDC); - - pDC->SetClient(NULL); - - // Paint all the children... - for (auto i : Children) - { - LView *w = i->GetGView(); - if (w && w->Visible()) - { - if (!w->Pos.Valid()) - continue; - - #if 0 - if (w->_Debug) - LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); + double ex[4]; + cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); + ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; + LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", + GetClass(), r.GetStr(), + ex[0], ex[1], ex[2], ex[3], + matrix.x0, matrix.y0); + #elif LGI_COCOA + auto Ctx = pDC->Handle(); + CGAffineTransform t = CGContextGetCTM(Ctx); + LRect cr = CGContextGetClipBoundingBox(Ctx); + printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", + GetClass(), + GetPos().GetStr(), + t.a, t.b, t.c, t.d, t.tx, t.ty, + cr.GetStr(), + r.GetStr()); #endif - w->_Paint(pDC, &o); } - } -} -#else -void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) -{ - // Create temp DC if needed... - LAutoPtr Local; - if (!pDC) - { - Local.Reset(new LScreenDC(this)); - pDC = Local; - } - if (!pDC) - { - printf("%s:%i - No context to draw in.\n", _FL); - return; - } + #endif + + OnPaint(pDC); + + pDC->SetClient(NULL); - #if 0 - // This is useful for coverage checking - pDC->Colour(LColour(255, 0, 255)); - pDC->Rectangle(); - #endif - - bool HasClient = false; - LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; - LPoint o; - if (Offset) - o = *Offset; + // Paint all the children... + for (auto i : Children) + { + LView *w = i->GetGView(); + if (w && w->Visible()) + { + if (!w->Pos.Valid()) + continue; - #if WINNATIVE - if (!_View) - #endif - { - // Non-Client drawing - Client = r; - OnNcPaint(pDC, Client); - HasClient = GetParent() && (Client != r); - if (HasClient) - { - Client.Offset(o.x, o.y); - pDC->SetClient(&Client); + #if 0 + if (w->_Debug) + LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); + #endif + w->_Paint(pDC, &o); + } } } - r.Offset(o.x, o.y); +#else - // Paint this view's contents - if (Update) + void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { - LRect OldClip = pDC->ClipRgn(); - pDC->ClipRgn(Update); - OnPaint(pDC); - pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); - } - else - { - OnPaint(pDC); + // Create temp DC if needed... + LAutoPtr Local; + if (!pDC) + { + Local.Reset(new LScreenDC(this)); + pDC = Local; + } + if (!pDC) + { + printf("%s:%i - No context to draw in.\n", _FL); + return; + } + + #if 0 + // This is useful for coverage checking + pDC->Colour(LColour(255, 0, 255)); + pDC->Rectangle(); + #endif + + bool HasClient = false; + LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; + LPoint o; + if (Offset) + o = *Offset; + + #if WINNATIVE + if (!_View) + #endif + { + // Non-Client drawing + Client = r; + OnNcPaint(pDC, Client); + HasClient = GetParent() && (Client != r); + if (HasClient) + { + Client.Offset(o.x, o.y); + pDC->SetClient(&Client); + } + } + + r.Offset(o.x, o.y); + + // Paint this view's contents + if (Update) + { + LRect OldClip = pDC->ClipRgn(); + pDC->ClipRgn(Update); + OnPaint(pDC); + pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); + } + else + { + OnPaint(pDC); + } + + #if PAINT_VIRTUAL_CHILDREN + // Paint any virtual children + for (auto i : Children) + { + LView *w = i->GetGView(); + if (w && w->Visible()) + { + #if LGI_VIEW_HANDLE + if (!w->Handle()) + #endif + { + LRect p = w->GetPos(); + p.Offset(o.x, o.y); + if (HasClient) + p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); + + LPoint co(p.x1, p.y1); + // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); + pDC->SetClient(&p); + w->_Paint(pDC, &co); + pDC->SetClient(NULL); + } + } + } + #endif + + if (HasClient) + pDC->SetClient(0); } - #if PAINT_VIRTUAL_CHILDREN - // Paint any virtual children - for (auto i : Children) - { - LView *w = i->GetGView(); - if (w && w->Visible()) - { - #if LGI_VIEW_HANDLE - if (!w->Handle()) - #endif - { - LRect p = w->GetPos(); - p.Offset(o.x, o.y); - if (HasClient) - p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); - - LPoint co(p.x1, p.y1); - // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); - pDC->SetClient(&p); - w->_Paint(pDC, &co); - pDC->SetClient(NULL); - } - } - } - #endif - - if (HasClient) - pDC->SetClient(0); -} #endif LViewI *LView::GetParent() { ThreadCheck(); return d ? d->Parent : NULL; } void LView::SetParent(LViewI *p) { ThreadCheck(); d->Parent = p ? p->GetGView() : NULL; d->ParentI = p; } void LView::SendNotify(LNotification note) { LViewI *n = d->Notify ? d->Notify : d->Parent; if (n) { if ( #if LGI_VIEW_HANDLE && !defined(HAIKU) !_View || #endif InThread()) { n->OnNotify(this, note); } else { // We are not in the same thread as the target window. So we post a message // across to the view. if (GetId() <= 0) { // We are going to generate a control Id to help the receiver of the // M_CHANGE message find out view later on. The reason for doing this // instead of sending a pointer to the object, is that the object // _could_ be deleted between the message being sent and being received. // Which would result in an invalid memory access on that object. LViewI *p = GetWindow(); if (!p) { // No window? Find the top most parent we can... p = this; while (p->GetParent()) p = p->GetParent(); } if (p) { // Give the control a valid ID int i; for (i=10; i<1000; i++) { if (!p->FindControl(i)) { printf("Giving the ctrl '%s' the id '%i' for SendNotify\n", GetClass(), i); SetId(i); break; } } } else { // Ok this is really bad... go random (better than nothing) SetId(5000 + LRand(2000)); } } LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise // the receiver will never be able to find our object. // printf("Post M_CHANGE %i %i\n", GetId(), Data); n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note)); } } } LViewI *LView::GetNotify() { ThreadCheck(); return d->Notify; } void LView::SetNotify(LViewI *p) { ThreadCheck(); d->Notify = p; } #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 int IsAdjacent(LRect &a, LRect &b) { if ( (a.x1 == b.x2 + 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_LEFT; } if ( (a.x2 == b.x1 - 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_RIGHT; } if ( (a.y1 == b.y2 + 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_UP; } if ( (a.y2 == b.y1 - 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_DOWN; } return 0; } LRect JoinAdjacent(LRect &a, LRect &b, int Adj) { LRect t; switch (Adj) { case ADJ_LEFT: case ADJ_RIGHT: { t.y1 = MAX(a.y1, b.y1); t.y2 = MIN(a.y2, b.y2); t.x1 = MIN(a.x1, b.x1); t.x2 = MAX(a.x2, b.x2); break; } case ADJ_UP: case ADJ_DOWN: { t.y1 = MIN(a.y1, b.y1); t.y2 = MAX(a.y2, b.y2); t.x1 = MAX(a.x1, b.x1); t.x2 = MIN(a.x2, b.x2); break; } } return t; } LRect *LView::FindLargest(LRegion &r) { ThreadCheck(); int Pixels = 0; LRect *Best = 0; static LRect Final; // do initial run through the list to find largest single region for (LRect *i = r.First(); i; i = r.Next()) { int Pix = i->X() * i->Y(); if (Pix > Pixels) { Pixels = Pix; Best = i; } } if (Best) { Final = *Best; Pixels = Final.X() * Final.Y(); int LastPixels = Pixels; LRect LastRgn = Final; int ThisPixels = Pixels; LRect ThisRgn = Final; LRegion TempList; for (LRect *i = r.First(); i; i = r.Next()) { TempList.Union(i); } TempList.Subtract(Best); do { LastPixels = ThisPixels; LastRgn = ThisRgn; // search for adjoining rectangles that maybe we can add for (LRect *i = TempList.First(); i; i = TempList.Next()) { int Adj = IsAdjacent(ThisRgn, *i); if (Adj) { LRect t = JoinAdjacent(ThisRgn, *i, Adj); int Pix = t.X() * t.Y(); if (Pix > ThisPixels) { ThisPixels = Pix; ThisRgn = t; TempList.Subtract(i); } } } } while (LastPixels < ThisPixels); Final = ThisRgn; } else return 0; return &Final; } LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy) { ThreadCheck(); int X = 1000000; int Y = 1000000; LRect *Best = 0; for (LRect *i = r.First(); i; i = r.Next()) { if ((i->X() >= Sx && i->Y() >= Sy) && (i->X() < X || i->Y() < Y)) { X = i->X(); Y = i->Y(); Best = i; } } return Best; } LRect *LView::FindLargestEdge(LRegion &r, int Edge) { LRect *Best = 0; ThreadCheck(); for (LRect *i = r.First(); i; i = r.Next()) { if (!Best) { Best = i; } if ( ((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1)) || ((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1)) ) { Best = i; } if ( ( ((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2)) ) && ( i->X() > Best->X() ) ) { Best = i; } if ( ( ((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1)) ) && ( i->Y() > Best->Y() ) ) { Best = i; } } return Best; } LViewI *LView::FindReal(LPoint *Offset) { ThreadCheck(); if (Offset) { Offset->x = 0; Offset->y = 0; } #if !LGI_VIEW_HANDLE LViewI *w = GetWindow(); #endif LViewI *p = d->Parent; while (p && #if !LGI_VIEW_HANDLE p != w #else !p->Handle() #endif ) { if (Offset) { Offset->x += Pos.x1; Offset->y += Pos.y1; } p = p->GetParent(); } if (p && #if !LGI_VIEW_HANDLE p == w #else p->Handle() #endif ) { return p; } return NULL; } bool LView::HandleCapture(LView *Wnd, bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd)); if (c) { if (_Capturing == Wnd) { DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0); } else { DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0); _Capturing = Wnd; #if defined(__GTK_H__) if (d->CaptureThread) d->CaptureThread->Cancel(); d->CaptureThread = new LCaptureThread(this); #elif WINNATIVE LPoint Offset; LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset); HWND h = v ? v->Handle() : NULL; if (h) SetCapture(h); else LAssert(0); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_TRUE); #else LAppInst->CaptureMouse(true); #endif #endif } } else if (_Capturing) { DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0); _Capturing = NULL; #if defined(__GTK_H__) if (d->CaptureThread) { d->CaptureThread->Cancel(); d->CaptureThread = NULL; // It'll delete itself... } #elif WINNATIVE ReleaseCapture(); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_FALSE); #else LAppInst->CaptureMouse(false); #endif #endif } return true; } bool LView::IsCapturing() { - DEBUG_CAPTURE("%s::IsCapturing()=%i\n", GetClass(), (int)(_Capturing == this)); + // DEBUG_CAPTURE("%s::IsCapturing()=%p==%p==%i\n", GetClass(), _Capturing, this, (int)(_Capturing == this)); return _Capturing == this; } bool LView::Capture(bool c) { ThreadCheck(); - DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass()); + DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass(), c); return HandleCapture(this, c); } bool LView::Enabled() { ThreadCheck(); #if WINNATIVE if (_View) return IsWindowEnabled(_View) != 0; #endif return !TestFlag(GViewFlags, GWF_DISABLED); } void LView::Enabled(bool i) { ThreadCheck(); if (!i) SetFlag(GViewFlags, GWF_DISABLED); else ClearFlag(GViewFlags, GWF_DISABLED); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE EnableWindow(_View, i); #elif defined LGI_CARBON if (i) { OSStatus e = EnableControl(_View); if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e); } else { OSStatus e = DisableControl(_View); if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e); } #endif } #endif Invalidate(); } bool LView::Visible() { // This is a read only operation... which is kinda thread-safe... // ThreadCheck(); #if WINNATIVE if (_View) /* This takes into account all the parent windows as well... Which is kinda not what I want. I want this to reflect just this window. return IsWindowVisible(_View); */ return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0; #endif return TestFlag(GViewFlags, GWF_VISIBLE); } void LView::Visible(bool v) { ThreadCheck(); if (v) SetFlag(GViewFlags, GWF_VISIBLE); else ClearFlag(GViewFlags, GWF_VISIBLE); #if defined(HAIKU) LLocker lck(d->Hnd, _FL); if (!IsAttached() || lck.Lock()) { + const int attempts = 3; + // printf("%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); if (v) - d->Hnd->Show(); + { + bool parentHidden = false; + for (auto p = d->Hnd->Parent(); p; p = p->Parent()) + { + if (p->IsHidden()) + { + parentHidden = true; + break; + } + } + if (!parentHidden) // Don't try and show if one of the parent's is hidden. + { + for (int i=0; iHnd->IsHidden(); i++) + { + // printf("\t%p Show\n", this); + d->Hnd->Show(); + } + if (d->Hnd->IsHidden()) + { + printf("%s:%i - Failed to show %s.\n", _FL, GetClass()); + for (auto p = d->Hnd->Parent(); p; p = p->Parent()) + printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); + } + } + } else - d->Hnd->Hide(); + { + for (int i=0; iHnd->IsHidden(); i++) + { + // printf("\t%p Hide\n", this); + d->Hnd->Hide(); + } + if (!d->Hnd->IsHidden()) + { + printf("%s:%i - Failed to hide %s.\n", _FL, GetClass()); + for (auto p = d->Hnd->Parent(); p; p = p->Parent()) + printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); + } + } + // printf("\t%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); } else LgiTrace("%s:%i - Can't lock.\n", _FL); #elif LGI_VIEW_HANDLE if (_View) { #if WINNATIVE ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE); #elif LGI_COCOA LAutoPool Pool; [_View.p setHidden:!v]; #elif LGI_CARBON Boolean is = HIViewIsVisible(_View); if (v != is) { OSErr e = HIViewSetVisible(_View, v); if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n", _FL, _View, v, e, GetClass()); } #endif } else #endif { Invalidate(); } } bool LView::Focus() { ThreadCheck(); bool Has = false; #if defined(__GTK_H__) LWindow *w = GetWindow(); if (w) { bool Active = w->IsActive(); if (Active) Has = w->GetFocus() == static_cast(this); } + #elif defined(HAIKU) + LLocker lck(d->Hnd, _FL); + if (lck.Lock()) + { + Has = d->Hnd->IsFocus(); + lck.Unlock(); + } #elif defined(WINNATIVE) if (_View) { HWND hFocus = GetFocus(); Has = hFocus == _View; } #elif LGI_COCOA Has = TestFlag(WndFlags, GWF_FOCUS); #elif LGI_CARBON LWindow *w = GetWindow(); if (w) { ControlRef Cur; OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur); if (e) LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e); else Has = (Cur == _View); } #endif #if !LGI_CARBON if (Has) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); #endif return Has; } void LView::Focus(bool i) { ThreadCheck(); if (i) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); auto *Wnd = GetWindow(); if (Wnd) + { Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus); + } #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) #endif { - #if defined(LGI_SDL) || defined(__GTK_H__) + #if defined(HAIKU) + + _Focus(i); + + #elif defined(LGI_SDL) || defined(__GTK_H__) // Nop: Focus is all handled by Lgi's LWindow class. #elif WINNATIVE if (i) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name()); } SetFocus(_View); } } else { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow()); } SetFocus(GetDesktopWindow()); } #elif defined LGI_CARBON LViewI *Wnd = GetWindow(); if (Wnd && i) { OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1); if (e) { // e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart); // if (e) { HIViewRef p = HIViewGetSuperview(_View); // errCouldntSetFocus printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p); } } // else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass()); } else printf("%s:%i - no window?\n", _FL); #endif } } LDragDropSource *LView::DropSource(LDragDropSource *Set) { if (Set) d->DropSource = Set; return d->DropSource; } LDragDropTarget *LView::DropTarget(LDragDropTarget *Set) { if (Set) d->DropTarget = Set; return d->DropTarget; } #if defined LGI_CARBON extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #if defined __GTK_H__ // Recursively add drag dest to all view and all children bool GtkAddDragDest(LViewI *v, bool IsTarget) { if (!v) return false; LWindow *w = v->GetWindow(); if (!w) return false; auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget); if (IsTarget) { Gtk::gtk_drag_dest_set( wid, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); } else { Gtk::gtk_drag_dest_unset(wid); } for (LViewI *c: v->IterateViews()) GtkAddDragDest(c, IsTarget); return true; } #endif bool LView::DropTarget(bool t) { ThreadCheck(); bool Status = false; if (t) SetFlag(GViewFlags, GWF_DROP_TARGET); else ClearFlag(GViewFlags, GWF_DROP_TARGET); #if WINNATIVE if (_View) { if (t) { if (!d->DropTarget) DragAcceptFiles(_View, t); else Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK; } else { if (_View && d->DropTarget) Status = RevokeDragDrop(_View) == S_OK; } } #elif defined MAC && !defined(LGI_SDL) LWindow *Wnd = dynamic_cast(GetWindow()); if (Wnd) { Wnd->SetDragHandlers(t); if (!d->DropTarget) d->DropTarget = t ? Wnd : 0; } #if LGI_COCOA LWindow *w = GetWindow(); if (w) { OsWindow h = w->WindowHandle(); if (h) { NSMutableArray *a = [[NSMutableArray alloc] init]; if (a) { [a addObject:(NSString*)kUTTypeItem]; for (id item in NSFilePromiseReceiver.readableDraggedTypes) [a addObject:item]; [h.p.contentView registerForDraggedTypes:a]; [a release]; } } } #elif LGI_CARBON if (t) { static EventTypeSpec DragEvents[] = { { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragReceive }, }; if (!d->DndHandler) { OSStatus e = ::InstallControlEventHandler( _View, NewEventHandlerUPP(LgiViewDndHandler), GetEventTypeCount(DragEvents), DragEvents, (void*)this, &d->DndHandler); if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e); } SetControlDragTrackingEnabled(_View, true); } else { SetControlDragTrackingEnabled(_View, false); } #endif #elif defined __GTK_H__ Status = GtkAddDragDest(this, t); if (Status && !d->DropTarget) d->DropTarget = t ? GetWindow() : 0; #endif return Status; } bool LView::Sunken() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE); #else return TestFlag(GViewFlags, GWF_SUNKEN); #endif } void LView::Sunken(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE); else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE); if (_View) SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle); #else if (i) SetFlag(GViewFlags, GWF_SUNKEN); else ClearFlag(GViewFlags, GWF_SUNKEN); #endif if (i) { if (!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } bool LView::Flat() { // ThreadCheck(); #if WINNATIVE return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) && !TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return !TestFlag(GViewFlags, GWF_SUNKEN) && !TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Flat(bool i) { ThreadCheck(); #if WINNATIVE ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE)); #else ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN)); #endif } bool LView::Raised() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Raised(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE); else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else if (i) SetFlag(GViewFlags, GWF_RAISED); else ClearFlag(GViewFlags, GWF_RAISED); #endif if (i) { if (!!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } int LView::GetId() { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. return d->CtrlId; } void LView::SetId(int i) { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. d->CtrlId = i; #if WINNATIVE if (_View) SetWindowLong(_View, GWL_ID, d->CtrlId); #elif defined __GTK_H__ #elif defined MAC #endif } bool LView::GetTabStop() { ThreadCheck(); #if WINNATIVE return TestFlag(d->WndStyle, WS_TABSTOP); #else return d->TabStop; #endif } void LView::SetTabStop(bool b) { ThreadCheck(); #if WINNATIVE if (b) SetFlag(d->WndStyle, WS_TABSTOP); else ClearFlag(d->WndStyle, WS_TABSTOP); #else d->TabStop = b; #endif } int64 LView::GetCtrlValue(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Value() : 0; } void LView::SetCtrlValue(int Id, int64 i) { ThreadCheck(); LViewI *w = FindControl(Id); if (w) w->Value(i); } const char *LView::GetCtrlName(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Name() : 0; } void LView::SetCtrlName(int Id, const char *s) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Name(s); } else { PostEvent( M_SET_CTRL_NAME, (LMessage::Param)Id, (LMessage::Param)new LString(s)); } } bool LView::GetCtrlEnabled(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Enabled() : 0; } void LView::SetCtrlEnabled(int Id, bool Enabled) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Enabled(Enabled); } else { PostEvent( M_SET_CTRL_ENABLE, (LMessage::Param)Id, (LMessage::Param)Enabled); } } bool LView::GetCtrlVisible(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); if (!w) LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Visible() : 0; } void LView::SetCtrlVisible(int Id, bool v) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Visible(v); } else { PostEvent( M_SET_CTRL_VISIBLE, (LMessage::Param)Id, (LMessage::Param)v); } } bool LView::AttachChildren() { for (auto c: Children) { bool a = c->IsAttached(); if (!a) { if (!c->Attach(this)) { LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass()); return false; } } } return true; } LFont *LView::GetFont() { if (!d->Font && d->Css && LResources::GetLoadStyles()) { LFontCache *fc = LAppInst->GetFontCache(); if (fc) { LFont *f = fc->GetFont(d->Css); if (f) { if (d->FontOwnType == GV_FontOwned) DeleteObj(d->Font); d->Font = f; d->FontOwnType = GV_FontCached; } } } return d->Font ? d->Font : LSysFont; } void LView::SetFont(LFont *Font, bool OwnIt) { bool Change = d->Font != Font; if (Change) { if (d->FontOwnType == GV_FontOwned) { LAssert(d->Font != LSysFont); DeleteObj(d->Font); } d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr; d->Font = Font; #if WINNATIVE if (_View) SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0); #endif for (LViewI *p = GetParent(); p; p = p->GetParent()) { LTableLayout *Tl = dynamic_cast(p); if (Tl) { Tl->InvalidateLayout(); break; } } Invalidate(); } } bool LView::IsOver(LMouse &m) { return (m.x >= 0) && (m.y >= 0) && (m.x < Pos.X()) && (m.y < Pos.Y()); } bool LView::WindowVirtualOffset(LPoint *Offset) { bool Status = false; if (Offset) { Offset->x = 0; Offset->y = 0; for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent()) { #if !LGI_VIEW_HANDLE auto IsWnd = dynamic_cast(Wnd); if (!IsWnd) #else if (!Wnd->Handle()) #endif { LRect r = Wnd->GetPos(); LViewI *Par = Wnd->GetParent(); if (Par) { LRect c = Par->GetClient(false); Offset->x += r.x1 + c.x1; Offset->y += r.y1 + c.y1; } else { Offset->x += r.x1; Offset->y += r.y1; } Status = true; } else break; } } return Status; } LString _ViewDesc(LViewI *v) { LString s; s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId()); return s; } LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth) { char Tabs[64]; if (DebugDepth) { memset(Tabs, 9, DebugDepth); Tabs[DebugDepth] = 0; LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length()); } // We iterate over the child in reverse order because if they overlap the // end of the list is on "top". So they should get the click or whatever // before the the lower windows. auto it = Children.rbegin(); int n = (int)Children.Length() - 1; for (LViewI *c = *it; c; c = *--it) { LRect CPos = c->GetPos(); if (CPos.Overlap(x, y) && c->Visible()) { LRect CClient; CClient = c->GetClient(false); int Ox = CPos.x1 + CClient.x1; int Oy = CPos.y1 + CClient.y1; if (DebugDepth) { LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), CClient.GetStr(), x, y, x - Ox, y - Oy); } LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0); if (Child) return Child; } else if (DebugDepth) { LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y); } } if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y()) { return this; } return NULL; } LColour LView::StyleColour(int CssPropType, LColour Default, int Depth) { LColour c = Default; if ((CssPropType >> 8) == LCss::TypeColor) { LViewI *v = this; for (int i=0; v && iGetParent()) { auto Style = v->GetCss(); if (Style) { auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType); if (Colour) { if (Colour->Type == LCss::ColorRgb) { c.Set(Colour->Rgb32, 32); break; } else if (Colour->Type == LCss::ColorTransparent) { c.Empty(); break; } } } if (dynamic_cast(v) || dynamic_cast(v)) break; } } return c; } bool LView::InThread() { #if WINNATIVE HWND Hnd = _View; for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent()) { Hnd = p->Handle(); } auto CurThreadId = GetCurrentThreadId(); auto GuiThreadId = LAppInst->GetGuiThreadId(); DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId; return CurThreadId == ViewThread; #elif defined(HAIKU) return true; #else OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0; #if 0 if (Gui != Me) LgiTrace("%s:%i - Out of thread:" #ifdef LGI_COCOA "%llx, %llx" #else "%x, %x" #endif "\n", _FL, Gui, Me); #endif return Gui == Me; #endif } -bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b) +bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t timeoutMs) { #ifdef LGI_SDL + return LPostEvent(this, Cmd, a, b); + #elif defined(HAIKU) - if (!d || !d->Hnd || !d->Hnd->LockLooper()) + + if (!d || !d->Hnd) + { + // printf("%s:%i - Bad pointers %p %p\n", _FL, d, d ? d->Hnd : NULL); + return false; + } + + BWindow *bWnd = NULL; + LWindow *wnd = dynamic_cast(this); + if (wnd) + { + bWnd = wnd->WindowHandle(); + } + else + { + // Look for an attached view to lock... + for (LViewI *v = this; v; v = v->GetParent()) + { + auto vhnd = v->Handle(); + if (vhnd && ::IsAttached(vhnd)) + { + bWnd = vhnd->Window(); + break; + } + } + } + + BMessage m(Cmd); + + auto r = m.AddInt64(LMessage::PropA, a); + if (r != B_OK) + printf("%s:%i - AddUInt64 failed.\n", _FL); + r = m.AddInt64(LMessage::PropB, b); + if (r != B_OK) + printf("%s:%i - AddUInt64 failed.\n", _FL); + r = m.AddPointer(LMessage::PropView, this); + if (r != B_OK) + printf("%s:%i - AddPointer failed.\n", _FL); + + if (bWnd) + { + r = bWnd->PostMessage(&m); + if (r != B_OK) + printf("%s:%i - PostMessage failed.\n", _FL); + + return r == B_OK; + } + + // Not attached yet... + d->MsgQue.Add(new BMessage(m)); + // printf("%s:%i - PostEvent.MsgQue=%i\n", _FL, (int)d->MsgQue.Length()); + + return true; + + #elif WINNATIVE + + if (!_View) return false; - BMessage *m = new BMessage(Cmd); - if (!m) - return false; - m->AddUInt64(LMessage::PropNames[0], a); - m->AddUInt64(LMessage::PropNames[1], b); - - auto w = d && d->Hnd ? d->Hnd->Window() : NULL; - status_t r = w->PostMessage(m); - d->Hnd->UnlockLooper(); - - return r == B_OK; - #elif WINNATIVE - if (!_View) - return false; BOOL Res = ::PostMessage(_View, Cmd, a, b); if (!Res) { auto Err = GetLastError(); int asd=0; } + return Res != 0; + #elif !LGI_VIEW_HANDLE + return LAppInst->PostEvent(this, Cmd, a, b); + #else + if (_View) return LPostEvent(_View, Cmd, a, b); + LAssert(0); return false; + #endif } bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient) { if (r) { for (int i=0; iLength(); i++) { bool Last = i == r->Length()-1; Invalidate((*r)[i], Last ? Repaint : false, NonClient); } return true; } return false; } LButton *FindDefault(LViewI *w) { LButton *But = 0; for (auto c: w->IterateViews()) { But = dynamic_cast(c); if (But && But->Default()) { break; } But = FindDefault(c); if (But) { break; } } return But; } bool LView::Name(const char *n) { LBase::Name(n); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE auto Temp = LBase::NameW(); SetWindowTextW(_View, Temp ? Temp : L""); #endif } #endif Invalidate(); return true; } const char *LView::Name() { #if WINNATIVE if (_View) { LView::NameW(); } #endif return LBase::Name(); } bool LView::NameW(const char16 *n) { LBase::NameW(n); #if WINNATIVE if (_View && n) { auto Txt = LBase::NameW(); SetWindowTextW(_View, Txt); } #endif Invalidate(); return true; } const char16 *LView::NameW() { #if WINNATIVE if (_View) { int Length = GetWindowTextLengthW(_View); if (Length > 0) { char16 *Buf = new char16[Length+1]; if (Buf) { Buf[0] = 0; int Chars = GetWindowTextW(_View, Buf, Length+1); Buf[Chars] = 0; LBase::NameW(Buf); } DeleteArray(Buf); } else { LBase::NameW(0); } } #endif return LBase::NameW(); } LViewI *LView::FindControl(int Id) { LAssert(Id != -1); if (GetId() == Id) { return this; } for (auto c : Children) { LViewI *Ctrl = c->FindControl(Id); if (Ctrl) { return Ctrl; } } return 0; } LPoint LView::GetMinimumSize() { return d->MinimumSize; } void LView::SetMinimumSize(LPoint Size) { d->MinimumSize = Size; bool Change = false; LRect p = Pos; if (X() < d->MinimumSize.x) { p.x2 = p.x1 + d->MinimumSize.x - 1; Change = true; } if (Y() < d->MinimumSize.y) { p.y2 = p.y1 + d->MinimumSize.y - 1; Change = true; } if (Change) { SetPos(p); } } bool LView::SetColour(LColour &c, bool Fore) { LCss *css = GetCss(true); if (!css) return false; if (Fore) { if (c.IsValid()) css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropColor); } else { if (c.IsValid()) css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropBackgroundColor); } return true; } /* bool LView::SetCssStyle(const char *CssStyle) { if (!d->Css && !d->Css.Reset(new LCss)) return false; const char *Defs = CssStyle; bool b = d->Css->Parse(Defs, LCss::ParseRelaxed); if (b && d->FontOwnType == GV_FontCached) { d->Font = NULL; d->FontOwnType = GV_FontPtr; } return b; } */ void LView::SetCss(LCss *css) { d->Css.Reset(css); } LCss *LView::GetCss(bool Create) { if (Create && !d->Css) d->Css.Reset(new LCss); if (d->CssDirty && d->Css) { const char *Defs = d->Styles; if (d->Css->Parse(Defs, LCss::ParseRelaxed)) d->CssDirty = false; } return d->Css; } LPoint &LView::GetWindowBorderSize() { static LPoint s; ZeroObj(s); #if WINNATIVE if (_View) { RECT Wnd, Client; GetWindowRect(Handle(), &Wnd); GetClientRect(Handle(), &Client); s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left); s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top); } #elif defined __GTK_H__ #elif defined MAC s.x = 0; s.y = 22; #endif return s; } #ifdef _DEBUG #if defined(LGI_CARBON) void DumpHiview(HIViewRef v, int Depth = 0) { char Sp[256]; memset(Sp, ' ', Depth << 2); Sp[Depth<<2] = 0; printf("%sHIView=%p", Sp, v); if (v) { Boolean vis = HIViewIsVisible(v); Boolean en = HIViewIsEnabled(v, NULL); HIRect pos; HIViewGetFrame(v, &pos); char cls[128]; ZeroObj(cls); GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls); printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s", vis, en, pos.origin.x, pos.origin.y, pos.size.width, pos.size.height, cls); } printf("\n"); for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c)) { DumpHiview(c, Depth + 1); } } #elif defined(__GTK_H__) void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL) { using namespace Gtk; if (!w) return; char Sp[65] = {0}; if (Depth) memset(Sp, ' ', *((int*)Depth)*2); auto *Obj = G_OBJECT(w); LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI"); GtkAllocation a; gtk_widget_get_allocation(w, &a); LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height); if (GTK_IS_CONTAINER(w)) { auto *c = GTK_CONTAINER(w); if (c) { int Next = Depth ? *((int*)Depth)+1 : 1; gtk_container_foreach(c, DumpGtk, &Next); } } } -#elif defined(HAIKU) +#elif defined(HAIKU) || defined(WINDOWS) template - void _Dump(int Depth, T *v) + void _Dump(int Depth, T v) { LString Sp, Name; Sp.Length(Depth<<1); memset(Sp.Get(), ' ', Depth<<1); - - LRect Frame = v->Frame(); - LViewPrivate *Priv = dynamic_cast(v); - if (Priv) - Name = Priv->View->GetClass(); + Sp.Get()[Depth<<1] = 0; - printf("%s### %p::%s frame=%s vis=%i\n", - Sp.Get(), v, Name.Get(), Frame.GetStr(), !v->IsHidden()); + #if defined(HAIKU) + LRect Frame = v->Frame(); + Name = v->Name(); + bool IsHidden = v->IsHidden(); + #else + RECT rc; GetWindowRect(v, &rc); + LRect Frame = rc; + wchar_t txt[256]; + GetWindowTextW(v, txt, CountOf(txt)); + Name = txt; + bool IsHidden = !(GetWindowLong(v, GWL_STYLE) & WS_VISIBLE); + #endif + + LgiTrace("%s%p::%s frame=%s vis=%i\n", + Sp.Get(), v, Name.Get(), Frame.GetStr(), !IsHidden); + + #if defined(HAIKU) for (int32 i=0; iCountChildren(); i++) - { _Dump(Depth + 1, v->ChildAt(i)); - } + #else + for (auto h = GetWindow(v, GW_CHILD); h; h = GetWindow(h, GW_HWNDNEXT)) + _Dump(Depth + 1, h); + #endif } #endif void LView::_Dump(int Depth) { char Sp[65] = {0}; memset(Sp, ' ', Depth*2); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View); LgiTrace(s); List::I i = Children.Start(); for (LViewI *c = *i; c; c = *++i) { LView *v = c->GetGView(); if (v) v->_Dump(Depth+1); } #elif defined(LGI_CARBON) DumpHiview(_View); #elif defined(__GTK_H__) // DumpGtk(_View); - #elif defined(HAIKU) + #else + #if defined(HAIKU) LLocker lck(WindowHandle(), _FL); if (lck.Lock()) + #endif ::_Dump(0, WindowHandle()); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// static LArray *AllFactories = NULL; #if defined(WIN32) static HANDLE FactoryEvent; #else static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT; static void GFactoryInitFactories() { AllFactories = new LArray; } #endif LViewFactory::LViewFactory() { #if defined(WIN32) char16 Name[64]; swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId()); HANDLE h = CreateEventW(NULL, false, false, Name); DWORD err = GetLastError(); if (err != ERROR_ALREADY_EXISTS) { FactoryEvent = h; AllFactories = new LArray; } else { LAssert(AllFactories != NULL); } #else pthread_once(&FactoryOnce, GFactoryInitFactories); #endif if (AllFactories) AllFactories->Add(this); } LViewFactory::~LViewFactory() { if (AllFactories) { AllFactories->Delete(this); if (AllFactories->Length() == 0) { DeleteObj(AllFactories); #if defined(WIN32) CloseHandle(FactoryEvent); #endif } } } LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text) { if (ValidStr(Class) && AllFactories) { for (int i=0; iLength(); i++) { LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text); if (v) { return v; } } } return 0; } #ifdef _DEBUG #if defined(__GTK_H__) using namespace Gtk; #include "LgiWidget.h" #endif void LView::Debug() { _Debug = true; #if defined LGI_COCOA d->ClassName = GetClass(); #endif } #endif diff --git a/src/common/Lgi/WindowCommon.cpp b/src/common/Lgi/WindowCommon.cpp --- a/src/common/Lgi/WindowCommon.cpp +++ b/src/common/Lgi/WindowCommon.cpp @@ -1,206 +1,208 @@ // // 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; + 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); } + printf("center %s %s\n", p.GetStr(), Screen.GetStr()); + 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,1479 +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(OPENSSL_SHLIB_VERSION) + #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(), SSL_LIBRARY)) { if (LFileExists(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/Skins/Gel/Gel.cpp b/src/common/Skins/Gel/Gel.cpp --- a/src/common/Skins/Gel/Gel.cpp +++ b/src/common/Skins/Gel/Gel.cpp @@ -1,981 +1,980 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Path.h" #include "lgi/common/Button.h" #include "lgi/common/Combo.h" #include "lgi/common/CheckBox.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/TableLayout.h" #if defined WINNATIVE #define BTN_TEXT_OFFSET_Y -1 #else #define BTN_TEXT_OFFSET_Y 0 #endif #define GREY24 Rgb24(44, 44, 44) #define GREY32(a) Rgba32(44, 44, 44, a) #define CHECK_BORDER 2 #define CHECK_RADIUS 3 #define Btn_Value 0x1 #define Btn_Enabled 0x2 #define Btn_Max ((Btn_Value|Btn_Enabled)+1) /* #if defined(WIN32) && !defined(LGI_SDL) // Wine can't handle 32 bit DIB sections... :( #define OsDefaultCs System24BitColourSpace #else */ // 32bit is marginally faster to render to. #define OsDefaultCs System32BitColourSpace // #endif #define CUSTOM_COLOURS 0 class GelSkin : public LSkinEngine { LApp *App; LColour c80; LColour c160; LColour c172; LColour c222; LColour c232; LColour c253; LColour c255; LMemDC *CheckBox[Btn_Max]; LMemDC *RadioBtn[Btn_Max]; /* COLOUR LgiLighten(COLOUR c32, int Amount) { System32BitPixel *p = (System32BitPixel*)&c32; p->r += ((255 - p->r) * Amount) >> 8; p->g += ((255 - p->g) * Amount) >> 8; p->b += ((255 - p->b) * Amount) >> 8; return Rgba32(p->r, p->g, p->b, p->a); } COLOUR LgiDarken(COLOUR c32, int Amount) { System32BitPixel *p = (System32BitPixel*)&c32; p->r = (p->r * Amount) >> 8; p->g = (p->g * Amount) >> 8; p->b = (p->b * Amount) >> 8; return Rgba32(p->r, p->g, p->b, p->a); } */ LColour Tint(LColour back, double amt) { bool Darken = back.GetGray() >= 128; // auto ff = (float)(1.0f - amt); LColour Mixer = Darken ? LColour::Black : LColour::White; // printf("Darken=%i, Mixer=%s, back=%s %f\n", Darken, Mixer.GetStr(), back.GetStr(), ff); return back.Mix(Mixer, (float)(1.0f - amt)); } void FillPath(LPath *Path, LSurface *pDC, LColour Back, bool Down, bool Enabled = true) { if (pDC) { LRect r(0, 0, pDC->X()-1, pDC->Y()-1); auto Top = Tint(Back, 253.0 / 240.0); auto Mid = Tint(Back, 232.0 / 240.0); auto Mid2 = Tint(Back, 222.0 / 240.0); auto Bot = Tint(Back, 255 / 240.0); if (!Enabled) { double Amt = 230.0 / 255.0; Top = Tint(Top, Amt); Mid = Tint(Mid, Amt); Mid2 = Tint(Mid2, Amt); Bot = Tint(Bot, Amt); } // Draw background LPath e; e.Rectangle(r.x1, r.y1, r.x2+1, r.y2+1); LPointF c1(r.x1, r.y1); LPointF d1(r.x1, r.y2+1); if (Down) { LBlendStop s1[] = { {0.0, Rgba32(192, 192, 192, 255)}, {0.1, Top.c32()}, {0.6, Mid.c32()}, {0.601, Mid2.c32()}, {1.0, Bot.c32()}, }; LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); e.Fill(pDC, b1); } else { LBlendStop s1[] = { {0.0, Top.c32()}, {0.5, Mid.c32()}, {0.501, Mid2.c32()}, {1.0, Bot.c32()}, }; LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); e.Fill(pDC, b1); } pDC->Colour(Tint(Back, 255.0/240.0)); pDC->Line(0, pDC->Y()-1, pDC->X()-1, pDC->Y()-1); pDC->Colour(Tint(Back, 198.0/240.0)); pDC->Line(0, pDC->Y()-2, pDC->X()-2, pDC->Y()-2); pDC->Line(pDC->X()-1, 0, pDC->X()-1, pDC->Y()-2); } } void DrawBtn(LSurface *pDC, LRect &r, LColour Back, bool Down, bool Enabled, bool Default = false) { if (!pDC) return; #if LGI_SDL - pDC->Colour(Base); - pDC->Rectangle(&r); - pDC->Colour(LColour(96, 96, 96)); - if (Down) - { - pDC->Line(r.x1, r.y1, r.x2, r.y1); - pDC->Line(r.x1, r.y1, r.x1, r.y2); - } - else - { - pDC->Line(r.x1, r.y2, r.x2, r.y2); - pDC->Line(r.x2, r.y1, r.x2, r.y2); - } - - #else - - LRect Client = r; - { - // Edge - LPath e; - LRectF r(Client); - // r.y2++; - e.RoundRect(r, 6); - - COLOUR EdgeColour = Default ? Rgba32(40, 40, 40, 255) : Rgba32(114, 114, 114, 255); - LSolidBrush b(EdgeColour); - e.Fill(pDC, b); - } - - { - // Border - LPath e; - LRectF r(Client); - // r.y2++; - int Resize = Default ? 2 : 1; - r.Size(Resize, Resize); + pDC->Colour(Base); + pDC->Rectangle(&r); + pDC->Colour(LColour(96, 96, 96)); if (Down) { - r.x1 = r.x1 + 1; - r.y1 = r.y1 + 1; - } - e.RoundRect(r, 6 - Resize); - - // Fill - LColour Top = Tint(Back, 253.0 / 240.0); - LColour Mid = Tint(Back, 232.0 / 240.0); - LColour Mid2 = Tint(Back, 222.0 / 240.0); - LColour Bot = Tint(Back, 255.0 / 240.0); - - if (!Enabled) - { - auto Amt = 230.0 / 240.0; - Top = Tint(Top, Amt); - Mid = Tint(Mid, Amt); - Mid2 = Tint(Mid2, Amt); - Bot = Tint(Bot, Amt); - } - - LPointF c1(r.x1, r.y1); - LPointF d1(r.x1, r.y2); - if (Down) - { - LBlendStop s1[] = - { - {0.0, Rgba32(192, 192, 192, 255)}, - {0.1, Top.c32()}, - {0.6, Tint(Mid, 230.0/240.0).c32()}, - {0.601, Tint(Mid2, 230.0/240.0).c32()}, - {1.0, Tint(Bot, 230.0/240.0).c32()}, - }; - LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); - e.Fill(pDC, b1); + pDC->Line(r.x1, r.y1, r.x2, r.y1); + pDC->Line(r.x1, r.y1, r.x1, r.y2); } else { - LBlendStop s1[] = - { - {0.0, Top.c32()}, - {0.5, Mid.c32()}, - {0.501, Mid2.c32()}, - {1.0, Bot.c32()}, - }; - LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); - e.Fill(pDC, b1); + pDC->Line(r.x1, r.y2, r.x2, r.y2); + pDC->Line(r.x2, r.y1, r.x2, r.y2); + } + + #else + + LRect Client = r; + { + // Edge + LPath e; + LRectF r(Client); + e.RoundRect(r, 6); + + COLOUR EdgeColour = Default ? Rgba32(40, 40, 40, 255) : Rgba32(114, 114, 114, 255); + LSolidBrush b(EdgeColour); + e.Fill(pDC, b); } - double Round = (r.X()-13)/r.X(); - if (Round < 0.6) - Round = 0.6; + { + // Border + LPath e; + LRectF r(Client); + // r.y2++; + int Resize = Default ? 2 : 1; + r.Size(Resize, Resize); + if (Down) + { + r.x1 = r.x1 + 1; + r.y1 = r.y1 + 1; + } + e.RoundRect(r, 6 - Resize); + + // Fill + LColour Top = Tint(Back, 253.0 / 240.0); + LColour Mid = Tint(Back, 232.0 / 240.0); + LColour Mid2 = Tint(Back, 222.0 / 240.0); + LColour Bot = Tint(Back, 255.0 / 240.0); - int Sa = Down ? 128 : 50; - LBlendStop s3[] = - { - {Round, GREY32(0)}, - {1.0, GREY32(Sa)}, - }; + if (!Enabled) + { + auto Amt = 230.0 / 240.0; + Top = Tint(Top, Amt); + Mid = Tint(Mid, Amt); + Mid2 = Tint(Mid2, Amt); + Bot = Tint(Bot, Amt); + } - // Rounded corners - LPointF c3(r.x1 + (r.X()/2), r.y1 + (r.Y()/2)); - LPointF d3(r.x1, r.y1); - LRadialBlendBrush b3(c3, d3, CountOf(s3), s3); - e.Fill(pDC, b3); - } + LPointF c1(r.x1, r.y1); + LPointF d1(r.x1, r.y2); + if (Down) + { + LBlendStop s1[] = + { + {0.0, Rgba32(192, 192, 192, 255)}, + {0.1, Top.c32()}, + {0.6, Tint(Mid, 230.0/240.0).c32()}, + {0.601, Tint(Mid2, 230.0/240.0).c32()}, + {1.0, Tint(Bot, 230.0/240.0).c32()}, + }; + LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); + e.Fill(pDC, b1); + } + else + { + LBlendStop s1[] = + { + {0.0, Top.c32()}, + {0.5, Mid.c32()}, + {0.501, Mid2.c32()}, + {1.0, Bot.c32()}, + }; + LLinearBlendBrush b1(c1, d1, CountOf(s1), s1); + e.Fill(pDC, b1); + } + + double Round = (r.X()-13)/r.X(); + if (Round < 0.6) + Round = 0.6; + + int Sa = Down ? 128 : 50; + LBlendStop s3[] = + { + {Round, GREY32(0)}, + {1.0, GREY32(Sa)}, + }; + + // Rounded corners + LPointF c3(r.x1 + (r.X()/2), r.y1 + (r.Y()/2)); + LPointF d3(r.x1, r.y1); + LRadialBlendBrush b3(c3, d3, CountOf(s3), s3); + e.Fill(pDC, b3); + } #endif } LMemDC *DrawCtrl(LViewI *Ctrl, LRect *Sz, int Flags, bool Round) { LMemDC *Mem = new LMemDC; if (Mem && Mem->Create(Sz ? Sz->X() : 14, Sz ? Sz->Y() : 14, OsDefaultCs)) { // blank out background LColour Back = Ctrl->GetGView()->StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); Mem->Colour(Back); Mem->Rectangle(); LRectF Box(0, 0, Mem->X(), Mem->Y()); double Radius = Box.X()/2; LPointF Center(Box.X()/2, Box.Y()/2); // int Grey = R24(LC_MED); bool Enabled = (Flags & Btn_Enabled) != 0; if (Enabled) { // draw sunken border LRectF r = Box; LPath p; if (Round) p.Circle(Center, Radius); else p.RoundRect(r, CHECK_RADIUS + CHECK_BORDER); // gradient from 169,169,169 at the top through to 225,225,225 LPointF a(0, 0), b(0, 15); LBlendStop s[] = { {0, c172.c32()}, {1, c253.c32()} }; LLinearBlendBrush c(a, b, 2, s); p.Fill(Mem, c); } if (Enabled) { // draw button center LRectF r = Box; r.Size(CHECK_BORDER+1, CHECK_BORDER+1); LPath p; if (Round) p.Circle(Center, r.X()/2); else p.RoundRect(r, CHECK_RADIUS-1); if (Enabled) { LPointF a(0, r.y1), b(0, r.y2); LBlendStop s[] = { {1.0/15.0, c255.c32()}, {1.0/14.0, c253.c32()}, {1, c232.c32()} }; LLinearBlendBrush c(a, b, CountOf(s), s); p.Fill(Mem, c); } else { LSolidBrush c(LSysColour(L_MED)); p.Fill(Mem, c); } } else { // draw button highlight, a white outline shifted down 1 pixel LRectF r = Box; r.Size(CHECK_BORDER, CHECK_BORDER); r.Offset(0, 1); LPointF Cntr = Center; Cntr.y = Cntr.y + 1; LPath p; if (Round) p.Circle(Cntr, r.X()/2); else p.RoundRect(r, CHECK_RADIUS); r.Size(1, 1); if (Round) p.Circle(Cntr, r.X()/2); else p.RoundRect(r, CHECK_RADIUS-1); LSolidBrush c(LSysColour(L_LIGHT)); p.Fill(Mem, c); } { // draw button outline LRectF r = Box; r.Size(CHECK_BORDER, CHECK_BORDER); LPath p; if (Round) p.Circle(Center, r.X()/2); else p.RoundRect(r, CHECK_RADIUS); r.Size(1, 1); if (Round) p.Circle(Center, r.X()/2); else p.RoundRect(r, CHECK_RADIUS-1); LSolidBrush c(Enabled ? c80 : c160); p.Fill(Mem, c); } if (Flags & Btn_Value) { // draw the check mark LRectF r = Box; int Px = (int) (Box.X()/6); r.Size(CHECK_BORDER+Px, CHECK_BORDER+Px); double Cx = r.x1 + (r.X() / 2); double Cy = r.y1 + (r.Y() / 2); double A = r.X() / 6; double B = (r.X() / 2) - A; LPath p; if (Round) { p.Circle(Center, r.X()/3); } else { p.MoveTo(r.x1, r.y1); p.LineTo(r.x1 + A, r.y1); p.LineTo(Cx, r.y1 + B); p.LineTo(r.x2 - A, r.y1); p.LineTo(r.x2, r.y1); p.LineTo(r.x2, r.y1 + A); p.LineTo(r.x2 - B, Cy); p.LineTo(r.x2, r.y2 - A); p.LineTo(r.x2, r.y2); p.LineTo(r.x2 - A, r.y2); p.LineTo(Cx, r.y2 - B); p.LineTo(r.x1 + A, r.y2); p.LineTo(r.x1, r.y2); p.LineTo(r.x1, r.y2 - A); p.LineTo(r.x1 + B, Cy); p.LineTo(r.x1, r.y1 + A); p.LineTo(r.x1, r.y1); } LSolidBrush c(Enabled ? c80 : c160); p.Fill(Mem, c); } } return Mem; } void DrawText(LSkinState *State, int x, int y, LRect &rcFill, bool Enabled, LView *Ctrl, LCssTools &Tools, bool Debug = false) { LCss::ColorDef CssFore, CssBack; LColour Fore = Tools.GetFore(), Back = Tools.GetBack(), Light, Low; if (!Enabled) { Light = LColour(L_LIGHT); Low = LColour(L_LOW); } LRegion Rgn; Rgn = rcFill; LArray *Text = State->AllText(); LSurface *pDC = State->pScreen; if (Text && Text->Length() > 0 && rcFill.X() > 3) { LRect Bounds; for (unsigned i=0; iLength(); i++) { LLayoutString *t = dynamic_cast((*Text)[i]); if (!t) break; LRect c; c.ZOff(t->X() - 1, t->Y() - 1); c.Offset(x + (t->Fx >> LDisplayString::FShift), y + t->y); Rgn.Subtract(&c); if (i) Bounds.Union(&c); else Bounds = c; LFont *f = t->GetFont(); if (Enabled) { f->Colour(Fore, Back); f->Transparent(!Back.IsValid()); t->Draw(pDC, c.x1, c.y1, &c); } else { f->Transparent(!Back.IsValid()); f->Colour(Light, Back); t->Draw(pDC, c.x1 + 1, c.y1 + 1, &c); f->Transparent(true); f->Colour(Low, Back); t->Draw(pDC, c.x1, c.y1, &c); } } if ((Ctrl->Focus() && Enabled) || Debug) { pDC->Colour(Debug ? LColour::Blue : LColour(L_MIDGREY)); pDC->Box(&Bounds); } } if (Back.IsValid()) { pDC->Colour(Back); for (LRect *rc = Rgn.First(); rc; rc = Rgn.Next()) pDC->Rectangle(rc); } } public: GelSkin(LApp *a) { // printf("Skin @ %i bits\n", GdcD->GetBits()); App = a; ZeroObj(CheckBox); ZeroObj(RadioBtn); LColour Med = LColour(L_MED); double Nominal = 240.0; c80 = Tint(Med, 80.0/Nominal); c160 = Tint(Med, 160.0/Nominal); c172 = Tint(Med, 172.0/Nominal); c222 = Tint(Med, 222.0/Nominal); c232 = Tint(Med, 232.0/Nominal); c253 = Tint(Med, 252.0/Nominal); c255 = Tint(Med, 255.0/Nominal); } ~GelSkin() { int i; for (i=0; iX(), Ctrl->Y(), OsDefaultCs)) { State->pScreen->Colour(Rgb24(255, 0, 255), 24); State->pScreen->Rectangle(); return; } // Font if (Ctrl->GetFont() == LSysFont) Ctrl->SetFont(LSysBold); // Background LCssTools Tools(Ctrl->GetCss(), Ctrl->GetFont()); LColour DefaultBack(L_HIGH); LColour &Fore = Tools.GetFore(), &Back = Tools.GetBack(&DefaultBack); LColour NoPaint(LSysColour(L_MED)); if (Ctrl->GetCss()) { LCss::ColorDef np = Ctrl->GetCss()->NoPaintColor(); if (np.Type == LCss::ColorRgb) NoPaint.Set(np.Rgb32, 32); else NoPaint.Empty(); } - #if defined(WINDOWS) + #if defined(WINDOWS) || defined(HAIKU) if (!NoPaint.IsValid()) { // We have to paint something otherwise we'll get garbage auto p = Ctrl->GetParent(); NoPaint = LColour(L_MED); if (p) // Use the parent's background? NoPaint = p->GetGView()->StyleColour(LCss::PropBackgroundColor, NoPaint); } #endif if (NoPaint.IsValid()) Mem.Colour(NoPaint); else Mem.Colour(0, 32); Mem.Rectangle(); DrawBtn(&Mem, Ctrl->GetClient(), Back, Ctrl->Value() != 0, Ctrl->Enabled(), Ctrl->Default()); LSurface *Out = &Mem; LArray *Txt = State->AllText(); int ContentX = 0; int SpacingPx = 4; if (State->Image) ContentX += State->Image->X(); int MaxTxt = 0; if (Txt) { for (unsigned i=0; iLength(); i++) { MaxTxt = MAX(MaxTxt, (*Txt)[i]->X()); } ContentX += MaxTxt; } if (State->Image && Txt && Txt->Length() > 0) ContentX += SpacingPx; int CurX = (Ctrl->X() - ContentX) >> 1; int Off = Ctrl->Value() ? 1 : 0; if (State->Image) { int CurY = (Ctrl->Y() - State->Image->Y()) >> 1; int Op = Out->Op(GDC_ALPHA); Out->Blt(CurX+Off, CurY+Off, State->Image); Out->Op(Op); CurX += State->Image->X() + SpacingPx; } if (Txt && Txt->Length() > 0) { LDisplayString *First = (*Txt)[0]; int sx = MaxTxt, sy = (int) Txt->Length() * First->Y(); int ty = (Ctrl->Y()-sy) >> 1; LFont *f = First->GetFont(); f->Transparent(true); for (unsigned i=0; iLength(); i++) { LDisplayString *Text = (*Txt)[i]; if (Ctrl->Enabled()) { f->Colour(Fore, Back); Text->Draw(Out, CurX+Off, ty+Off+BTN_TEXT_OFFSET_Y); } else { f->Colour(LColour(L_LIGHT), Back); Text->Draw(Out, CurX+Off+1, ty+Off+1+BTN_TEXT_OFFSET_Y); f->Colour(LColour(L_LOW), Back); Text->Draw(Out, CurX+Off, ty+Off+BTN_TEXT_OFFSET_Y); } ty += Text->Y(); } if (Ctrl->Focus()) { LRect b(CurX-2, ty, CurX + sx + 1, ty + sy - 2); b.Offset(Off, Off); Out->Colour(Rgb24(180, 180, 180), 24); Out->Box(&b); } } int Op = State->pScreen->Op(GDC_ALPHA); State->pScreen->Blt(0, 0, &Mem); State->pScreen->Op(Op); } void OnPaint_ListColumn(ProcColumnPaint Callback, void *UserData, LSkinState *State) { // Setup memory context LRect r = State->Rect; LMemDC Mem(r.X(), r.Y(), OsDefaultCs); if (!Mem[0]) return; LCssTools Tools(State->View); auto Ws = LColour(L_WORKSPACE); auto Back = Tint(Tools.GetBack(&Ws, 0), 220.0/240.0); r.Offset(-r.x1, -r.y1); LPath e; e.Rectangle(r.x1, r.y1, r.x2, r.y2); static bool LastEnabled = true; FillPath(&e, &Mem, Back, State ? State->Value != 0 : false, State ? LastEnabled = State->Enabled : LastEnabled); if (State && State->Value) { Mem.Colour(Rgb24(0xc0, 0xc0, 0xc0), 24); Mem.Line(r.x1, r.y1, r.x1, r.y2-1); Mem.Colour(Rgb24(0xe0, 0xe0, 0xe0), 24); Mem.Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2); } r.Inset(2, 2); if (Callback) { Mem.Op(GDC_ALPHA); Callback(UserData, &Mem, r, false); } State->pScreen->Blt(State->Rect.x1, State->Rect.y1, &Mem); } void OnPaint_LCombo(LCombo *Ctrl, LSkinState *State) { LMemDC Mem; if (Mem.Create(Ctrl->X(), Ctrl->Y(), OsDefaultCs)) { // Font if (Ctrl->GetFont() == LSysFont) Ctrl->SetFont(LSysBold); // Back LColour TextDefault(L_TEXT), BackDefault(L_HIGH); LCssTools Tools(Ctrl->GetCss(), Ctrl->GetFont()); LColour &Fore = Tools.GetFore(&TextDefault), &Back = Tools.GetBack(); if (Back.IsValid()) { Mem.Colour(Back); Mem.Rectangle(); } DrawBtn(&Mem, Ctrl->GetClient(), BackDefault, false, State->Enabled); int n = 22; LColour DkGrey(L_DKGREY); if (Ctrl->X() > 32) { LDisplayString *Text = State->FirstText(); if (Text) { int sx = Text->X(), sy = Text->Y(); int tx = LCombo::Pad.x1; int ty = (Ctrl->Y()-sy+1) >> 1; int Off = 0; LRect c = Ctrl->GetClient(); c.x1 += 8; c.x2 -= n + 3; int Cx = Ctrl->X(); int PadX = LCombo::Pad.x1 + LCombo::Pad.x2; if (Text->X() > PadX) { // Make the text fit Text->TruncateWithDots(Cx - PadX); } LFont *f = Text->GetFont(); f->Transparent(true); if (Ctrl->Enabled()) { f->Colour(Fore, Back); Text->Draw(&Mem, tx+Off, ty+Off+BTN_TEXT_OFFSET_Y, &c); } else { f->Colour(LColour(L_LIGHT), LColour(L_MED)); Text->Draw(&Mem, tx+Off+1, ty+Off+1+BTN_TEXT_OFFSET_Y, &c); f->Colour(LColour(L_LOW), LColour(L_MED)); Text->Draw(&Mem, tx+Off, ty+Off+BTN_TEXT_OFFSET_Y, &c); } if (Ctrl->Focus() && c.X() > 4) { LRect b(tx-2, ty, tx + sx + 1, ty + sy - 2); b.Offset(Off, Off); c.Inset(-2, 0); b.Bound(&c); Mem.Colour(Rgb24(180, 180, 180), 24); Mem.Box(&b); } } // Draw separator Mem.Colour(Rgba32(180, 180, 180, 255), 32); Mem.Line(Mem.X()-n, 1, Mem.X()-n, Mem.Y()-2); } Mem.Colour(State->Enabled ? Fore : DkGrey); int Bx = Mem.X() < 26 ? Mem.X()/2 : Mem.X()-13, By = (Mem.Y() + 4) >> 1; for (int i=0; i<5; i++) { Mem.Line(Bx-i, By-i, Bx+i, By-i); } State->pScreen->Blt(0, 0, &Mem); } else { State->pScreen->Colour(Rgb24(255, 0, 255), 24); State->pScreen->Rectangle(); } } #define DEBUG_CHECKBOX 0 void OnPaint_LCheckBox(LCheckBox *Ctrl, LSkinState *State) { int Flags = (Ctrl->Value() ? Btn_Value : 0) | (Ctrl->Enabled() ? Btn_Enabled : 0); // Create the bitmaps in cache if not already there LCssTools Tools(Ctrl); LColour &Back = Tools.GetBack(); LMemDC *Temp = 0; LMemDC *&Mem = Back.IsValid() ? Temp : CheckBox[Flags]; if (Mem && (Mem->X() != State->Rect.X() || Mem->Y() != State->Rect.Y())) DeleteObj(Mem); if (!Mem) Mem = DrawCtrl(Ctrl, &State->Rect, Flags, false); LRect TxtBounds = State->TextBounds(); // Output to screen auto pDC = State->pScreen; if (Mem) { LRect &Box = State->Rect; pDC->Blt(Box.x1, Box.y1, Mem); LRect Box1(Box.x1, 0, Box.x2, Box.y1 - 1); LRect Box2(Box.x1, Box.y2 + 1, Box.x2, Ctrl->Y()-1); pDC->Colour(Back.IsValid() ? Back : LColour(L_MED)); if (Box.y1 > 0) pDC->Rectangle(&Box1); if (Box.y2 < Ctrl->Y() - 1) pDC->Rectangle(&Box2); #if DEBUG_CHECKBOX pDC->Colour(LColour::Red); if (Box.y1 > 0) pDC->Box(&Box1); pDC->Colour(LColour::Green); if (Box.y2 < Ctrl->Y() - 1) pDC->Box(&Box2); #endif // Draw text LRect t(Mem->X(), 0, Ctrl->X()-1, Ctrl->Y()-1); if (t.Valid()) { DrawText(State, Mem->X() + LTableLayout::CellSpacing, t.Y() > TxtBounds.Y() ? (t.Y()-TxtBounds.Y())>>1 : 0, t, (Flags & Btn_Enabled) != 0, Ctrl, Tools, DEBUG_CHECKBOX); } } else { pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); } DeleteObj(Temp); } void OnPaint_LRadioButton(LRadioButton *Ctrl, LSkinState *State) { int Flags = (Ctrl->Value() ? Btn_Value : 0) | (Ctrl->Enabled() ? Btn_Enabled : 0); // Create the bitmaps in cache if not already there LMemDC *&Mem = RadioBtn[Flags]; if (!Mem || State->ForceUpdate) { DeleteObj(Mem); Mem = DrawCtrl(Ctrl, &State->Rect, Flags, true); } // Output to screen if (Mem) { // Draw icon LRect ico; LCssTools Tools(Ctrl); LColour &Back = Tools.GetBack(); ico.ZOff(Mem->X()-1, Mem->Y()-1); if (ico.Y() < Ctrl->Y()) ico.Offset(0, (Ctrl->Y() - ico.Y()) >> 1); State->pScreen->Blt(ico.x1, ico.y1, Mem); if (Back.IsValid()) { State->pScreen->Colour(Back); if (ico.y1 > 0) State->pScreen->Rectangle(0, 0, ico.x2, ico.y1-1); if (ico.y2 < Ctrl->Y()) State->pScreen->Rectangle(0, ico.y2+1, ico.x2, Ctrl->Y()-1); } // Draw text LRect t(Mem->X(), 0, Ctrl->X()-1, Ctrl->Y()-1); if (t.Valid()) { int y = 0; if (State->TextObjects()) { LDisplayString *ds = State->FirstText(); if (ds && t.Y() > ds->Y()) { y = (t.Y() - ds->Y()) >> 1; } } DrawText(State, Mem->X() + 4, y, t, (Flags & Btn_Enabled) != 0, Ctrl, Tools); } } else { State->pScreen->Colour(Rgb24(255, 0, 255), 24); State->pScreen->Rectangle(); } } LFont *GetDefaultFont(char *Class) { if (Class && stricmp(Class, Res_Button) == 0) { return LSysBold; } return LSysFont; } }; LSkinEngine * CreateSkinEngine(class LApp *App) { return new GelSkin(App); } diff --git a/src/common/Text/DocView.cpp b/src/common/Text/DocView.cpp --- a/src/common/Text/DocView.cpp +++ b/src/common/Text/DocView.cpp @@ -1,224 +1,224 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DocView.h" #define SubtractPtr(a, b) ((a)-(b)) const char *LDocView::WhiteSpace = " \t\r\n"; const char *LDocView::Delimiters = "!@#$%^&*()'\":;,.<>/?[]{}-=+\\|`~"; const char *LDocView::UrlDelim = "!~/:%+-?@&$#._=,;*()|"; LDocumentEnv::LDocumentEnv(LDocView *v) { if (v) { Viewers.Add(v); } } LDocumentEnv::~LDocumentEnv() { for (uint32_t i=0; iEnvironment = 0; } } int LDocumentEnv::NextUid() { if (!Lock(_FL)) return -1; int Uid = 0; for (auto v : Viewers) Uid = MAX(Uid, v->GetDocumentUid() + 1); Unlock(); return Uid; } void LDocumentEnv::OnDone(LAutoPtr j) { LoadJob *ld = dynamic_cast(j.Get()); if (ld) { if (Lock(_FL)) { LDocView *View = NULL; for (unsigned i=0; iGetDocumentUid(); if (Uid == ld->UserUid) { View = Viewers[i]; break; } } if (View) { View->OnContent(ld); j.Release(); } Unlock(); } } else LAssert(!"RTTI failed."); } ////////////////////////////////////////////////////////////////////////////////// char16 *ConvertToCrLf(char16 *Text) { if (Text) { // add '\r's int Lfs = 0; int Len = 0; char16 *s=Text; for (; *s; s++) { if (*s == '\n') Lfs++; Len++; } char16 *Temp = new char16[Len+Lfs+1]; if (Temp) { char16 *d=Temp; s = Text; for (; *s; s++) { if (*s == '\n') { *d++ = 0x0d; *d++ = 0x0a; } else if (*s == '\r') { // ignore } else { *d++ = *s; } } *d++ = 0; DeleteObj(Text); return Temp; } } return Text; } ////////////////////////////////////////////////////////////////////////////////// #include "lgi/common/Net.h" LDocumentEnv::LoadType LDefaultDocumentEnv::GetContent(LoadJob *&j) { if (!j || !ValidStr(j->Uri)) return LoadError; char p[MAX_PATH_LEN]; char *FullPath = NULL; char *FileName = NULL; LUri u(j->Uri); if (u.sProtocol && !_stricmp(u.sProtocol, "file")) FileName = u.sPath; else FileName = j->Uri; if (FileName) { LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p)); LMakePath(p, sizeof(p), p, FileName); if (LFileExists(p)) { FullPath = p; } else { auto f = LFindFile(FileName); if (f) strcpy_s(FullPath = p, sizeof(p), f); } } if (LFileExists(FullPath)) { LString Mt = LGetFileMimeType(FullPath); if (Mt.Find("image/") == 0) { j->pDC.Reset(GdcD->Load(p)); return LoadImmediate; } j->Filename = FullPath; return LoadImmediate; } return LoadError; } bool LDefaultDocumentEnv::OnNavigate(LDocView *Parent, const char *Uri) { if (Uri) { if ( _strnicmp(Uri, "mailto:", 7) == 0 || ( strchr(Uri, '@') != 0 && strchr(Uri, '/') == 0 ) ) { // email - LArray Apps; + LArray Apps; if (LGetAppsForMimeType("application/email", Apps)) { - LAppInfo *First = Apps[0]; + LAppInfo *First = &Apps[0]; LStringPipe a; char *Arg = First->Params ? strstr(First->Params, "%1") : 0; if (Arg) { // change '%1' into the email address in question a.Write(First->Params, (int) (Arg-First->Params)); a.Print("%s%s", Uri, Arg + 2); } else { // no argument place holder... just pass as a normal arg a.Print(" %s", Uri); } LAutoString Exe(TrimStr(First->Path, "\"\'")); LAutoString Args(a.NewStr()); LString ErrorMsg; if (LExecute(Exe, Args, ".", &ErrorMsg)) return true; LgiMsg(Parent, "Failed to open '%s':\n%s", LAppInst->LBase::Name(), MB_OK, Exe.Get(), ErrorMsg.Get()); } else { LgiMsg(Parent, "Couldn't get app to handle email.", LAppInst->LBase::Name(), MB_OK); } } else { // webpage LString ErrorMsg; if (LExecute(Uri, NULL, NULL, &ErrorMsg)) return true; LgiMsg(Parent, "Failed to open '%s':\n%s", LAppInst->LBase::Name(), MB_OK, Uri, ErrorMsg.Get()); } } return false; } diff --git a/src/common/Text/Html.cpp b/src/common/Text/Html.cpp --- a/src/common/Text/Html.cpp +++ b/src/common/Text/Html.cpp @@ -1,9474 +1,9482 @@ #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Html.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Variant.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Unicode.h" #include "lgi/common/Emoji.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/GdcTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Path.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Net.h" #include "lgi/common/Base64.h" #include "lgi/common/Menu.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Homoglyphs.h" #include "lgi/common/Charset.h" #include "HtmlPriv.h" #define DEBUG_TABLE_LAYOUT 1 #define DEBUG_DRAW_TD 0 #define DEBUG_RESTYLE 0 #define DEBUG_TAG_BY_POS 0 #define DEBUG_SELECTION 0 #define DEBUG_TEXT_AREA 0 #define ENABLE_IMAGE_RESIZING 1 #define DOCUMENT_LOAD_IMAGES 1 #define MAX_RECURSION_DEPTH 300 #define ALLOW_TABLE_GROWTH 1 #define LGI_HTML_MAXPAINT_TIME 350 // ms #define FLOAT_TOLERANCE 0.001 #define CRASH_TRACE 0 #ifdef MAC #define HTML_USE_DOUBLE_BUFFER 0 #else #define HTML_USE_DOUBLE_BUFFER 1 #endif #define GT_TRANSPARENT 0x00000000 #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif #undef CellSpacing #define DefaultCellSpacing 0 #define DefaultCellPadding 1 #ifdef MAC #define MinimumPointSize 9 #define MinimumBodyFontSize 12 #else #define MinimumPointSize 8 #define MinimumBodyFontSize 11 #endif // #define DefaultFont "font-family: Times; font-size: 16pt;" #define DefaultBodyMargin "5px" #define DefaultImgSize 16 #define DefaultMissingCellColour GT_TRANSPARENT // Rgb32(0xf0,0xf0,0xf0) #define ShowNbsp 0 #define FontPxHeight(fnt) (fnt->GetHeight() - (int)(fnt->Leading() + 0.5)) #if 0 // def _DEBUG #define DefaultTableBorder Rgb32(0xf8, 0xf8, 0xf8) #else #define DefaultTableBorder GT_TRANSPARENT #endif #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DEBUG_LOG(...) if (Table->Debug) LgiTrace(__VA_ARGS__) #else #define DEBUG_LOG(...) #endif #define IsTableCell(id) ( ((id) == TAG_TD) || ((id) == TAG_TH) ) #define IsTableTag() (TagId == TAG_TABLE || TagId == TAG_TR || TagId == TAG_TD || TagId == TAG_TH) #define GetCssLen(a, b) a().Type == LCss::LenInherit ? b() : a() static char WordDelim[] = ".,<>/?[]{}()*&^%$#@!+|\'\""; static char16 WhiteW[] = {' ', '\t', '\r', '\n', 0}; #if 0 static char DefaultCss[] = { "a { color: blue; text-decoration: underline; }" "body { margin: 8px; }" "strong { font-weight: bolder; }" "pre { font-family: monospace }" "h1 { font-size: 2em; margin: .67em 0px; }" "h2 { font-size: 1.5em; margin: .75em 0px; }" "h3 { font-size: 1.17em; margin: .83em 0px; }" "h4, p," "blockquote, ul," "fieldset, form," "ol, dl, dir," "menu { margin: 1.12em 0px; }" "h5 { font-size: .83em; margin: 1.5em 0px; }" "h6 { font-size: .75em; margin: 1.67em 0px; }" "strike, del { text-decoration: line-through; }" "hr { border: 1px inset; }" "center { text-align: center; }" "h1, h2, h3, h4," "h5, h6, b," "strong { font-weight: bolder; }" }; #endif template void RemoveChars(T *str, T *remove_list) { T *i = str, *o = str, *c; while (*i) { for (c = remove_list; *c; c++) { if (*c == *i) break; } if (*c == 0) *o++ = *i; i++; } *o++ = NULL; } ////////////////////////////////////////////////////////////////////// using namespace Html1; namespace Html1 { class LHtmlPrivate { public: LHashTbl, LTag*> Loading; LHtmlStaticInst Inst; bool CursorVis; LRect CursorPos; LPoint Content; bool WordSelectMode; bool LinkDoubleClick; LAutoString OnLoadAnchor; bool DecodeEmoji; LAutoString EmojiImg; int NextCtrlId; uint64 SetScrollTime; int DeferredLoads; bool IsParsing; bool IsLoaded; bool StyleDirty; // Paint time limits... bool MaxPaintTimeout = false; int MaxPaintTime = LGI_HTML_MAXPAINT_TIME; // Find settings LAutoWString FindText; bool MatchCase; LHtmlPrivate() { IsLoaded = false; StyleDirty = false; IsParsing = false; LinkDoubleClick = true; WordSelectMode = false; NextCtrlId = 2000; SetScrollTime = 0; CursorVis = false; CursorPos.ZOff(-1, -1); DeferredLoads = 0; char EmojiPng[MAX_PATH_LEN]; #ifdef MAC LMakePath(EmojiPng, sizeof(EmojiPng), LGetExeFile(), "Contents/Resources/Emoji.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/emoji.png"); #endif if (LFileExists(EmojiPng)) { DecodeEmoji = true; EmojiImg.Reset(NewStr(EmojiPng)); } else DecodeEmoji = false; } ~LHtmlPrivate() { } }; class InputButton : public LButton { LTag *Tag; public: InputButton(LTag *tag, int Id, const char *Label) : LButton(Id, 0, 0, -1, -1, Label) { Tag = tag; } void OnClick(const LMouse &m) { Tag->OnClick(m); } }; class LFontCache { LHtml *Owner; List Fonts; public: LFontCache(LHtml *owner) { Owner = owner; } ~LFontCache() { Fonts.DeleteObjects(); } LFont *FontAt(int i) { return Fonts.ItemAt(i); } LFont *FindMatch(LFont *m) { for (auto f: Fonts) { if (*f == *m) { return f; } } return 0; } LFont *GetFont(LCss *Style) { if (!Style) return NULL; LFont *Default = Owner->GetFont(); LCss::StringsDef Face = Style->FontFamily(); if (Face.Length() < 1 || !ValidStr(Face[0])) { Face.Empty(); const char *DefFace = Default->Face(); LAssert(ValidStr(DefFace)); Face.Add(NewStr(DefFace)); } LAssert(ValidStr(Face[0])); LCss::Len Size = Style->FontSize(); LCss::FontWeightType Weight = Style->FontWeight(); bool IsBold = Weight == LCss::FontWeightBold || Weight == LCss::FontWeightBolder || Weight > LCss::FontWeight400; bool IsItalic = Style->FontStyle() == LCss::FontStyleItalic; bool IsUnderline = Style->TextDecoration() == LCss::TextDecorUnderline; if (Size.Type == LCss::LenInherit || Size.Type == LCss::LenNormal) { Size.Type = LCss::LenPt; Size.Value = (float)Default->PointSize(); } auto Scale = Owner->GetDpiScale(); if (Size.Type == LCss::LenPx) { Size.Value *= (float) Scale.y; int RequestPx = (int) Size.Value; // Look for cached fonts of the right size... for (auto f: Fonts) { if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { int Px = FontPxHeight(f); int Diff = Px - RequestPx; if (Diff >= 0 && Diff <= 2) return f; } } } else if (Size.Type == LCss::LenPt) { double Pt = Size.Value; for (auto f: Fonts) { if (!f->Face() || Face.Length() == 0) { LAssert(0); break; } auto FntSz = f->Size(); if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && FntSz.Type == LCss::LenPt && std::abs(FntSz.Value - Pt) < FLOAT_TOLERANCE && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { // Return cached font return f; } } } else if (Size.Type == LCss::LenPercent) { // Most of the percentages will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize() / 100.0f; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::LenEm) { // Most of the relative sizes will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize(); if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeXXSmall || Size.Type == LCss::SizeXSmall || Size.Type == LCss::SizeSmall || Size.Type == LCss::SizeMedium || Size.Type == LCss::SizeLarge || Size.Type == LCss::SizeXLarge || Size.Type == LCss::SizeXXLarge) { int Idx = Size.Type-LCss::SizeXXSmall; LAssert(Idx >= 0 && Idx < CountOf(LCss::FontSizeTable)); Size.Type = LCss::LenPt; Size.Value = Default->PointSize() * LCss::FontSizeTable[Idx]; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeSmaller) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() - 1); } else if (Size.Type == LCss::SizeLarger) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() + 1); } else LAssert(!"Not impl."); LFont *f; if ((f = new LFont)) { auto ff = ValidStr(Face[0]) ? Face[0] : Default->Face(); f->Face(ff); f->Size(Size.IsValid() ? Size : Default->Size()); f->Bold(IsBold); f->Italic(IsItalic); f->Underline(IsUnderline); // printf("Add cache font %s,%i %i,%i,%i\n", f->Face(), f->PointSize(), f->Bold(), f->Italic(), f->Underline()); if (std::abs(Size.Value) < FLOAT_TOLERANCE) ; else if (!f->Create((char*)0, 0)) { // Broken font... f->Face(Default->Face()); LFont *DefMatch = FindMatch(f); // printf("Falling back to default face for '%s:%i', DefMatch=%p\n", ff, f->PointSize(), DefMatch); if (DefMatch) { DeleteObj(f); return DefMatch; } else { if (!f->Create((char*)0, 0)) { DeleteObj(f); return Fonts[0]; } } } // Not already cached Fonts.Insert(f); if (!f->Face()) { LAssert(0); } return f; } return 0; } }; class LFlowRegion { LCss::LengthType Align = LCss::LenInherit; List Line; // These pointers aren't owned by the flow region // When the line is finish, all the tag regions // will need to be vertically aligned struct LFlowStack { int LeftAbs; int RightAbs; int TopAbs; }; LArray Stack; public: LHtml *Html; int x1, x2; // Left and right margins int y1; // Current y position int y2; // Maximum used y position int cx; // Current insertion point int my; // How much of the area above y2 was just margin LPoint MAX; // Max dimensions int Inline; int InBody; LFlowRegion(LHtml *html, bool inbody) { Html = html; x1 = x2 = y1 = y2 = cx = my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LHtml *html, LRect r, bool inbody) { Html = html; MAX.x = cx = x1 = r.x1; MAX.y = y1 = y2 = r.y1; x2 = r.x2; my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LFlowRegion &r) { Html = r.Html; x1 = r.x1; x2 = r.x2; y1 = r.y1; MAX.x = cx = r.cx; MAX.y = y2 = r.y2; my = r.my; Inline = r.Inline; InBody = r.InBody; } LString ToString() { LString s; s.Printf("Flow: x=%i(%i)%i y=%i,%i my=%i inline=%i", x1, cx, x2, y1, y2, my, Inline); return s; } int X() { return x2 - cx; } void X(int newx) { x2 = x1 + newx - 1; } int Width() { return x2 - x1 + 1; } LFlowRegion &operator +=(LRect r) { x1 += r.x1; cx += r.x1; x2 -= r.x2; y1 += r.y1; y2 += r.y1; return *this; } LFlowRegion &operator -=(LRect r) { x1 -= r.x1; cx -= r.x1; x2 += r.x2; y1 += r.y2; y2 += r.y2; return *this; } void AlignText(); void FinishLine(bool Margin = false); void EndBlock(); void Insert(LFlowRect *Tr, LCss::LengthType Align); LRect *LineBounds(); void Indent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Left.IsValid() ? ResolveX(Left, Tag, IsMargin) : 0; Fs.RightAbs = Right.IsValid() ? ResolveX(Right, Tag, IsMargin) : 0; Fs.TopAbs = Top.IsValid() ? ResolveY(Top, Tag, IsMargin) : 0; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Indent(LRect &Px, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Px.x1; Fs.RightAbs = Px.x2; Fs.TopAbs = Px.y1; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Outdent(LRect &Px, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int &BottomAbs = Px.y2; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } void Outdent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int BottomAbs = Bottom.IsValid() ? ResolveY(Bottom, Tag, IsMargin) : 0; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } int ResolveX(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { default: case LCss::LenInherit: return IsMargin ? 0 : X(); case LCss::LenPx: // return MIN((int)l.Value, X()); return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().x / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().x / 2.54); case LCss::LenEm: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int)(l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { int my_x = X(); int px = (int) (l.Value * my_x / 100.0); return px; } case LCss::LenAuto: { if (IsMargin) return 0; else return X(); break; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } } return 0; } bool LimitX(int &x, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; if (Min.IsValid()) { int Px = Min.ToPx(x2 - x1 + 1, f, false); if (Px > x) { x = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(x2 - x1 + 1, f, false); if (Px < x) { x = Px; Limited = true; } } return Limited; } int ResolveY(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { case LCss::LenInherit: case LCss::LenAuto: case LCss::LenNormal: case LCss::LenPx: return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().y / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().y / 2.54); case LCss::LenEm: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { // Walk up tree of tags to find an absolute size... LCss::Len Ab; for (LTag *p = ToTag(t->Parent); p; p = ToTag(p->Parent)) { auto h = p->Height(); if (h.IsValid() && !h.IsDynamic()) { Ab = h; break; } } if (!Ab.IsValid()) { LAssert(Html != NULL); Ab.Type = LCss::LenPx; Ab.Value = (float)Html->Y(); } LCss::Len m = Ab * l; return (int)m.ToPx(0, f);; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } case LCss::AlignLeft: case LCss::AlignRight: case LCss::AlignCenter: case LCss::AlignJustify: case LCss::VerticalBaseline: case LCss::VerticalSub: case LCss::VerticalSuper: case LCss::VerticalTop: case LCss::VerticalTextTop: case LCss::VerticalMiddle: case LCss::VerticalBottom: case LCss::VerticalTextBottom: { // Meaningless in this context break; } default: { LAssert(!"Not supported."); break; } } return 0; } bool LimitY(int &y, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; int TotalY = Html ? Html->Y() : 0; if (Min.IsValid()) { int Px = Min.ToPx(TotalY, f, false); if (Px > y) { y = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(TotalY, f, false); if (Px < y) { y = Px; Limited = true; } } return Limited; } LRect ResolveMargin(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->MarginLeft(), Tag, true); r.y1 = ResolveY(Src->MarginTop(), Tag, true); r.x2 = ResolveX(Src->MarginRight(), Tag, true); r.y2 = ResolveY(Src->MarginBottom(), Tag, true); return r; } LRect ResolveBorder(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->BorderLeft(), Tag, true); r.y1 = ResolveY(Src->BorderTop(), Tag, true); r.x2 = ResolveX(Src->BorderRight(), Tag, true); r.y2 = ResolveY(Src->BorderBottom(), Tag, true); return r; } LRect ResolvePadding(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->PaddingLeft(), Tag, true); r.y1 = ResolveY(Src->PaddingTop(), Tag, true); r.x2 = ResolveX(Src->PaddingRight(), Tag, true); r.y2 = ResolveY(Src->PaddingBottom(), Tag, true); return r; } }; }; ////////////////////////////////////////////////////////////////////// static bool ParseDistance(char *s, float &d, char *units = 0) { if (!s) return false; while (*s && IsWhiteSpace(*s)) s++; if (!IsDigit(*s) && !strchr("-.", *s)) return false; d = (float)atof(s); while (*s && (IsDigit(*s) || strchr("-.", *s))) s++; while (*s && IsWhiteSpace(*s)) s++; char _units[128]; char *o = units = units ? units : _units; while (*s && (IsAlpha(*s) || *s == '%')) { *o++ = *s++; } *o++ = 0; return true; } LHtmlLength::LHtmlLength() { d = 0; PrevAbs = 0; u = LCss::LenInherit; } LHtmlLength::LHtmlLength(char *s) { Set(s); } bool LHtmlLength::IsValid() { return u != LCss::LenInherit; } bool LHtmlLength::IsDynamic() { return u == LCss::LenPercent || d == 0.0; } LHtmlLength::operator float () { return d; } LHtmlLength &LHtmlLength::operator =(float val) { d = val; u = LCss::LenPx; return *this; } LCss::LengthType LHtmlLength::GetUnits() { return u; } void LHtmlLength::Set(char *s) { if (ValidStr(s)) { char Units[256] = ""; if (ParseDistance(s, d, Units)) { if (Units[0]) { if (strchr(Units, '%')) { u = LCss::LenPercent; } else if (stristr(Units, "pt")) { u = LCss::LenPt; } else if (stristr(Units, "em")) { u = LCss::LenEm; } else if (stristr(Units, "ex")) { u = LCss::LenEx; } else { u = LCss::LenPx; } } else { u = LCss::LenPx; } } } } float LHtmlLength::Get(LFlowRegion *Flow, LFont *Font, bool Lock) { switch (u) { default: break; case LCss::LenEm: { return PrevAbs = d * (Font ? Font->GetHeight() : 14); break; } case LCss::LenEx: { return PrevAbs = (Font ? Font->GetHeight() * d : 14) / 2; break; } case LCss::LenPercent: { if (Lock || PrevAbs == 0.0) { return PrevAbs = (Flow->X() * d / 100); } else { return PrevAbs; } break; } } float FlowX = Flow ? Flow->X() : d; return PrevAbs = MIN(FlowX, d); } LHtmlLine::LHtmlLine() { LineStyle = -1; LineReset = 0x80000000; } LHtmlLine::~LHtmlLine() { } LHtmlLine &LHtmlLine::operator =(int i) { d = (float)i; return *this; } void LHtmlLine::Set(char *s) { LToken t(s, " \t"); LineReset = 0x80000000; LineStyle = -1; char *Style = 0; for (unsigned i=0; iColourMap.Find(c) ) { LHtmlParser::ParseColour(c, Colour); } else if (_strnicmp(c, "rgb(", 4) == 0) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), c); while (!strchr(c, ')') && (c = t[++i])) { strcat(Buf, c); } LHtmlParser::ParseColour(Buf, Colour); } else if (IsDigit(*c)) { LHtmlLength::Set(c); } else if (_stricmp(c, "none") == 0) { Style = 0; } else if ( _stricmp(c, "dotted") == 0 || _stricmp(c, "dashed") == 0 || _stricmp(c, "solid") == 0 || _stricmp(c, "float") == 0 || _stricmp(c, "groove") == 0 || _stricmp(c, "ridge") == 0 || _stricmp(c, "inset") == 0 || _stricmp(c, "outse") == 0) { Style = c; } else { // ??? } } if (Style && _stricmp(Style, "dotted") == 0) { switch ((int)d) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } } ////////////////////////////////////////////////////////////////////// LRect LTag::GetRect(bool Client) { LRect r(Pos.x, Pos.y, Pos.x + Size.x - 1, Pos.y + Size.y - 1); if (!Client) { for (LTag *p = ToTag(Parent); p; p=ToTag(p->Parent)) { r.Offset(p->Pos.x, p->Pos.y); } } return r; } LCss::LengthType LTag::GetAlign(bool x) { for (LTag *t = this; t; t = ToTag(t->Parent)) { LCss::Len l; if (x) { if (IsTableCell(TagId) && Cell && Cell->XAlign) l.Type = Cell->XAlign; else l = t->TextAlign(); } else { l = t->VerticalAlign(); } if (l.Type != LenInherit) { return l.Type; } if (t->TagId == TAG_TABLE) break; } return LenInherit; } ////////////////////////////////////////////////////////////////////// void LFlowRegion::EndBlock() { if (cx > x1) FinishLine(); } void LFlowRegion::AlignText() { if (Align != LCss::AlignLeft) { int Used = 0; for (auto l : Line) Used += l->X(); int Total = x2 - x1 + 1; if (Used < Total) { int Offset = 0; if (Align == LCss::AlignCenter) Offset = (Total - Used) / 2; else if (Align == LCss::AlignRight) Offset = Total - Used; if (Offset) for (auto l : Line) { if (l->Tag->Display() != LCss::DispInlineBlock) l->Offset(Offset, 0); } } } } void LFlowRegion::FinishLine(bool Margin) { // AlignText(); if (y2 > y1) { my = Margin ? y2 - y1 : 0; y1 = y2; } else { int fy = Html->DefFont()->GetHeight(); my = Margin ? fy : 0; y1 += fy; } cx = x1; y2 = y1; Line.Empty(); } LRect *LFlowRegion::LineBounds() { auto It = Line.begin(); LFlowRect *Prev = *It; LFlowRect *r=Prev; if (r) { LRect b; b = *r; int Ox = r->Tag->AbsX(); int Oy = r->Tag->AbsY(); b.Offset(Ox, Oy); // int Ox = 0, Oy = 0; while ((r = *(++It) )) { LRect c = *r; Ox = r->Tag->AbsX(); Oy = r->Tag->AbsY(); c.Offset(Ox, Oy); /* Ox += r->Tag->Pos.x - Prev->Tag->Pos.x; Oy += r->Tag->Pos.y - Prev->Tag->Pos.y; c.Offset(Ox, Oy); */ b.Union(&c); Prev = r; } static LRect Rgn; Rgn = b; return &Rgn; } return 0; } void LFlowRegion::Insert(LFlowRect *Tr, LCss::LengthType align) { if (Tr) { Align = align; Line.Insert(Tr); } } ////////////////////////////////////////////////////////////////////// LTag::LTag(LHtml *h, LHtmlElement *p) : LHtmlElement(p), Attr(8) { Ctrl = 0; CtrlType = CtrlNone; TipId = 0; Display(DispInline); Html = h; ImageResized = false; Cursor = -1; Selection = -1; Font = 0; LineHeightCache = -1; HtmlId = NULL; // TableBorder = 0; Cell = NULL; TagId = CONTENT; Info = 0; Pos.x = Pos.y = 0; #ifdef _DEBUG Debug = false; #endif } LTag::~LTag() { if (Html->Cursor == this) { Html->Cursor = 0; } if (Html->Selection == this) { Html->Selection = 0; } DeleteObj(Ctrl); Attr.DeleteArrays(); DeleteObj(Cell); } void LTag::OnChange(PropType Prop) { } bool LTag::OnClick(const LMouse &m) { if (!Html->Environment) return false; const char *OnClick = NULL; if (Get("onclick", OnClick)) { Html->Environment->OnExecuteScript(Html, (char*)OnClick); } else { OnNotify(LNotification(m)); } return true; } void LTag::Set(const char *attr, const char *val) { char *existing = Attr.Find(attr); if (existing) DeleteArray(existing); if (val) Attr.Add(attr, NewStr(val)); } bool LTag::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: // Type: LCssStyle { Value = &StyleDom; return true; } case ObjTextContent: // Type: String { Value = Text(); return true; } default: { char *a = Attr.Find(Name); if (a) { Value = a; return true; } break; } } return false; } bool LTag::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: { const char *Defs = Value.Str(); if (!Defs) return false; return Parse(Defs, ParseRelaxed); } case ObjTextContent: { const char *s = Value.Str(); if (s) { LAutoWString w(CleanText(s, strlen(s), "utf-8", true, true)); Txt = w; return true; } break; } case ObjInnerHtml: // Type: String { // Clear out existing tags.. Children.DeleteObjects(); char *Doc = Value.CastString(); if (Doc) { // Create new tags... bool BackOut = false; while (Doc && *Doc) { LTag *t = new LTag(Html, this); if (t) { Doc = Html->ParseHtml(t, Doc, 1, false, &BackOut); if (!Doc) break; } else break; } } else return false; break; } default: { Set(Name, Value.CastString()); SetStyle(); break; } } Html->ViewWidth = -1; return true; } ssize_t LTag::GetTextStart() { if (PreText() && TextPos.Length() > 1) { LFlowRect *t = TextPos[1]; if (t) return t->Text - Text(); } else if (TextPos.Length() > 0) { LFlowRect *t = TextPos[0]; if (t && Text()) { LAssert(t->Text >= Text() && t->Text <= Text()+2); return t->Text - Text(); } } return 0; } static bool TextToStream(LStream &Out, char16 *Text) { if (!Text) return true; uint8_t Buf[256]; uint8_t *s = Buf; ssize_t Len = sizeof(Buf); while (*Text) { #define WriteExistingContent() \ if (s > Buf) \ Out.Write(Buf, (int)(s - Buf)); \ s = Buf; \ Len = sizeof(Buf); \ Buf[0] = 0; if (*Text == '<' || *Text == '>') { WriteExistingContent(); Out.Print("&%ct;", *Text == '<' ? 'l' : 'g'); } else if (*Text == 0xa0) { WriteExistingContent(); Out.Write((char*)" ", 6); } else { LgiUtf32To8(*Text, s, Len); if (Len < 16) { WriteExistingContent(); } } Text++; } if (s > Buf) Out.Write(Buf, s - Buf); return true; } bool LTag::CreateSource(LStringPipe &p, int Depth, bool LastWasBlock) { char *Tabs = new char[Depth+1]; memset(Tabs, '\t', Depth); Tabs[Depth] = 0; if (ValidStr(Tag)) { if (IsBlock()) { p.Print("%s%s<%s", TagId != TAG_HTML ? "\n" : "", Tabs, Tag.Get()); } else { p.Print("<%s", Tag.Get()); } if (Attr.Length()) { // const char *a; // for (char *v = Attr.First(&a); v; v = Attr.Next(&a)) for (auto v : Attr) { if (_stricmp(v.key, "style")) p.Print(" %s=\"%s\"", v.key, v.value); } } if (Props.Length()) { LCss *Css = this; LCss Tmp; #define DelProp(p) \ if (Css == this) { Tmp = *Css; Css = &Tmp; } \ Css->DeleteProp(p); // Clean out any default CSS properties where we can... LHtmlElemInfo *i = LHtmlStatic::Inst->GetTagInfo(Tag); if (i) { if (Props.Find(PropDisplay) && ( (!i->Block() && Display() == DispInline) || (i->Block() && Display() == DispBlock) )) { DelProp(PropDisplay); } switch (TagId) { default: break; case TAG_A: { LCss::ColorDef Blue(LCss::ColorRgb, Rgb32(0, 0, 255)); if (Props.Find(PropColor) && Color() == Blue) DelProp(PropColor); if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration) break; } case TAG_BODY: { LCss::Len FivePx(LCss::LenPx, 5.0f); if (Props.Find(PropPaddingLeft) && PaddingLeft() == FivePx) DelProp(PropPaddingLeft) if (Props.Find(PropPaddingTop) && PaddingTop() == FivePx) DelProp(PropPaddingTop) if (Props.Find(PropPaddingRight) && PaddingRight() == FivePx) DelProp(PropPaddingRight) break; } case TAG_B: { if (Props.Find(PropFontWeight) && FontWeight() == LCss::FontWeightBold) DelProp(PropFontWeight); break; } case TAG_U: { if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration); break; } case TAG_I: { if (Props.Find(PropFontStyle) && FontStyle() == LCss::FontStyleItalic) DelProp(PropFontStyle); break; } } } // Convert CSS props to a string and emit them... auto s = Css->ToString(); if (ValidStr(s)) { // Clean off any trailing whitespace... char *e = s ? s + strlen(s) : NULL; while (e && strchr(WhiteSpace, e[-1])) *--e = 0; // Print them to the tags attributes... p.Print(" style=\"%s\"", s.Get()); } } } if (Children.Length() || TagId == TAG_STYLE) // { if (Tag) { p.Write((char*)">", 1); TextToStream(p, Text()); } bool Last = IsBlock(); for (unsigned i=0; iCreateSource(p, Parent ? Depth+1 : 0, Last); Last = c->IsBlock(); } if (Tag) { if (IsBlock()) { if (Children.Length()) p.Print("\n%s", Tabs); } p.Print("", Tag.Get()); } } else if (Tag) { if (Text()) { p.Write((char*)">", 1); TextToStream(p, Text()); p.Print("", Tag.Get()); } else { p.Print("/>\n"); } } else { TextToStream(p, Text()); } DeleteArray(Tabs); return true; } void LTag::SetTag(const char *NewTag) { Tag.Reset(NewStr(NewTag)); if (NewTag) { Info = Html->GetTagInfo(Tag); if (Info) { TagId = Info->Id; Display(Info->Flags & LHtmlElemInfo::TI_BLOCK ? LCss::DispBlock : LCss::DispInline); } } else { Info = NULL; TagId = CONTENT; } SetStyle(); } LColour LTag::_Colour(bool f) { for (LTag *t = this; t; t = ToTag(t->Parent)) { ColorDef c = f ? t->Color() : t->BackgroundColor(); if (c.Type != ColorInherit) { return LColour(c.Rgb32, 32); } #if 1 if (!f && t->TagId == TAG_TABLE) break; #else /* This implements some basic level of colour inheritance for background colours. See test case 'cisra-cqs.html'. */ if (!f && t->TagId == TAG_TABLE) break; #endif } return LColour(); } void LTag::CopyClipboard(LMemQueue &p, bool &InSelection) { ssize_t Min = -1; ssize_t Max = -1; if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection); Max = MAX(Cursor, Selection); } else if (InSelection) { Max = MAX(Cursor, Selection); } else { Min = MAX(Cursor, Selection); } ssize_t Off = -1; ssize_t Chars = 0; auto Start = GetTextStart(); if (Min >= 0 && Max >= 0) { Off = Min + Start; Chars = Max - Min; } else if (Min >= 0) { Off = Min + Start; Chars = StrlenW(Text()) - Min; InSelection = true; } else if (Max >= 0) { Off = Start; Chars = Max; InSelection = false; } else if (InSelection) { Off = Start; Chars = StrlenW(Text()); } if (Off >= 0 && Chars > 0) { p.Write((uchar*) (Text() + Off), Chars * sizeof(char16)); } if (InSelection) { switch (TagId) { default: break; case TAG_BR: { char16 NL[] = {'\n', 0}; p.Write((uchar*) NL, sizeof(char16)); break; } case TAG_P: { char16 NL[] = {'\n', '\n', 0}; p.Write((uchar*) NL, sizeof(char16) * 2); break; } } } for (unsigned i=0; iCopyClipboard(p, InSelection); } } static char* _DumpColour(LCss::ColorDef c) { static char Buf[4][32]; #ifdef _MSC_VER static LONG Cur = 0; LONG Idx = InterlockedIncrement(&Cur); #else static int Cur = 0; int Idx = __sync_fetch_and_add(&Cur, 1); #endif char *b = Buf[Idx % 4]; if (c.Type == LCss::ColorInherit) strcpy_s(b, 32, "Inherit"); else sprintf_s(b, 32, "%2.2x,%2.2x,%2.2x(%2.2x)", R32(c.Rgb32),G32(c.Rgb32),B32(c.Rgb32),A32(c.Rgb32)); return b; } void LTag::_Dump(LStringPipe &Buf, int Depth) { LString Tabs; Tabs.Set(NULL, Depth); memset(Tabs.Get(), '\t', Depth); const char *Empty = ""; char *ElementName = TagId == CONTENT ? (char*)"Content" : (TagId == ROOT ? (char*)"Root" : Tag); Buf.Print( "%s%s(%p)%s%s%s (%i) Pos=%i,%i Size=%i,%i Color=%s/%s", Tabs.Get(), ElementName, this, HtmlId ? "#" : Empty, HtmlId ? HtmlId : Empty, #ifdef _DEBUG Debug ? " debug" : Empty, #else Empty, #endif WasClosed, Pos.x, Pos.y, Size.x, Size.y, _DumpColour(Color()), _DumpColour(BackgroundColor())); for (unsigned i=0; iText, Tr->Len)); if (Utf8) { size_t Len = strlen(Utf8); if (Len > 40) { Utf8[40] = 0; } } else if (Tr->Text) { Utf8.Reset(NewStr("")); } Buf.Print("Tr(%i,%i %ix%i '%s') ", Tr->x1, Tr->y1, Tr->X(), Tr->Y(), Utf8.Get()); } Buf.Print("\r\n"); for (unsigned i=0; i_Dump(Buf, Depth+1); } if (Children.Length()) { Buf.Print("%s/%s\r\n", Tabs.Get(), ElementName); } } LAutoWString LTag::DumpW() { LStringPipe Buf; // Buf.Print("Html pos=%s\n", Html?Html->GetPos().GetStr():0); _Dump(Buf, 0); LAutoString a(Buf.NewStr()); LAutoWString w(Utf8ToWide(a)); return w; } LAutoString LTag::DescribeElement() { LStringPipe s(256); s.Print("%s", Tag ? Tag.Get() : "CONTENT"); if (HtmlId) s.Print("#%s", HtmlId); for (unsigned i=0; iDefFont(); } return f; } LFont *LTag::GetFont() { if (!Font) { if (PropAddress(PropFontFamily) != 0 || FontSize().Type != LenInherit || FontStyle() != FontStyleInherit || FontVariant() != FontVariantInherit || FontWeight() != FontWeightInherit || TextDecoration() != TextDecorInherit) { LCss c; LCss::PropMap Map; Map.Add(PropFontFamily, new LCss::PropArray); Map.Add(PropFontSize, new LCss::PropArray); Map.Add(PropFontStyle, new LCss::PropArray); Map.Add(PropFontVariant, new LCss::PropArray); Map.Add(PropFontWeight, new LCss::PropArray); Map.Add(PropTextDecoration, new LCss::PropArray); for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_IFRAME) break; if (!c.InheritCollect(*t, Map)) break; } c.InheritResolve(Map); Map.DeleteObjects(); if ((Font = Html->FontCache->GetFont(&c))) return Font; } else { LTag *t = this; while (!t->Font && t->Parent) { t = ToTag(t->Parent); } if (t->Font) return t->Font; } Font = Html->DefFont(); } return Font; } LTag *LTag::PrevTag() { if (Parent) { ssize_t i = Parent->Children.IndexOf(this); if (i >= 0) { return ToTag(Parent->Children[i - 1]); } } return 0; } void LTag::Invalidate() { LRect p = GetRect(); for (LTag *t=ToTag(Parent); t; t=ToTag(t->Parent)) { p.Offset(t->Pos.x, t->Pos.y); } Html->Invalidate(&p); } LTag *LTag::IsAnchor(LString *Uri) { LTag *a = 0; for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_A) { a = t; break; } } if (a && Uri) { const char *u = 0; if (a->Get("href", u)) { LAutoWString w(CleanText(u, strlen(u), "utf-8")); if (w) { *Uri = w; } } } return a; } bool LTag::OnMouseClick(LMouse &m) { bool Processed = false; if (m.IsContextMenu()) { LString Uri; const char *ImgSrc = NULL; LTag *a = IsAnchor(&Uri); bool IsImg = TagId == TAG_IMG; if (IsImg) Get("src", ImgSrc); bool IsAnchor = a && ValidStr(Uri); if (IsAnchor || IsImg) { LSubMenu RClick; #define IDM_COPY_LINK 100 #define IDM_COPY_IMG 101 if (Html->GetMouse(m, true)) { int Id = 0; if (IsAnchor) RClick.AppendItem(LLoadString(L_COPY_LINK_LOCATION, "&Copy Link Location"), IDM_COPY_LINK, Uri != NULL); if (IsImg) RClick.AppendItem("Copy Image Location", IDM_COPY_IMG, ImgSrc != NULL); if (Html->GetEnv()) Html->GetEnv()->AppendItems(&RClick, Uri); switch (Id = RClick.Float(Html, m.x, m.y)) { case IDM_COPY_LINK: { LClipBoard Clip(Html); Clip.Text(Uri); break; } case IDM_COPY_IMG: { LClipBoard Clip(Html); Clip.Text(ImgSrc); break; } default: { if (Html->GetEnv()) Html->GetEnv()->OnMenu(Html, Id, a); break; } } } Processed = true; } } else if (m.Down() && m.Left()) { #ifdef _DEBUG if (m.Ctrl()) { auto Style = ToString(); LStringPipe p(256); p.Print("Tag: %s\n", Tag ? Tag.Get() : "CONTENT"); if (Class.Length()) { p.Print("Class(es): "); for (unsigned i=0; iParent; t=ToTag(t->Parent)) { LStringPipe Tmp; Tmp.Print(" %s", t->Tag ? t->Tag.Get() : "CONTENT"); if (t->HtmlId) { Tmp.Print("#%s", t->HtmlId); } for (unsigned i=0; iClass.Length(); i++) { Tmp.Print(".%s", t->Class[i].Get()); } LAutoString Txt(Tmp.NewStr()); p.Print("%s", Txt.Get()); LDisplayString Ds(LSysFont, Txt); int Px = 170 - Ds.X(); int Chars = Px / Sp.X(); for (int c=0; cPos.x, t->Pos.y, t->Size.x, t->Size.y); } LAutoString a(p.NewStr()); LgiMsg( Html, "%s", Html->GetClass(), MB_OK, a.Get()); } else #endif { LString Uri; if (Html && Html->Environment) { if (IsAnchor(&Uri)) { if (Uri) { if (!Html->d->LinkDoubleClick || m.Double()) { Html->Environment->OnNavigate(Html, Uri); Processed = true; } } const char *OnClk = NULL; if (!Processed && Get("onclick", OnClk)) { Html->Environment->OnExecuteScript(Html, (char*)OnClk); } } else { Processed = OnClick(m); } } } } return Processed; } LTag *LTag::GetBlockParent(ssize_t *Idx) { if (IsBlock()) { if (Idx) *Idx = 0; return this; } for (LTag *t = this; t; t = ToTag(t->Parent)) { if (ToTag(t->Parent)->IsBlock()) { if (Idx) { *Idx = t->Parent->Children.IndexOf(t); } return ToTag(t->Parent); } } return 0; } LTag *LTag::GetAnchor(char *Name) { if (!Name) return 0; const char *n; if (IsAnchor(0) && Get("name", n) && n && !_stricmp(Name, n)) { return this; } for (unsigned i=0; iGetAnchor(Name); if (Result) return Result; } return 0; } LTag *LTag::GetTagByName(const char *Name) { if (Name) { if (Tag && _stricmp(Tag, Name) == 0) { return this; } for (unsigned i=0; iGetTagByName(Name); if (Result) return Result; } } return 0; } static int IsNearRect(LRect *r, int x, int y) { if (r->Overlap(x, y)) { return 0; } else if (x >= r->x1 && x <= r->x2) { if (y < r->y1) return r->y1 - y; else return y - r->y2; } else if (y >= r->y1 && y <= r->y2) { if (x < r->x1) return r->x1 - x; else return x - r->x2; } int64 dx = 0; int64 dy = 0; if (x < r->x1) { if (y < r->y1) { // top left dx = r->x1 - x; dy = r->y1 - y; } else { // bottom left dx = r->x1 - x; dy = y - r->y2; } } else { if (y < r->y1) { // top right dx = x - r->x2; dy = r->y1 - y; } else { // bottom right dx = x - r->x2; dy = y - r->y2; } } return (int) sqrt( (double) ( (dx * dx) + (dy * dy) ) ); } ssize_t LTag::NearestChar(LFlowRect *Tr, int x, int y) { LFont *f = GetFont(); if (f) { LDisplayString ds(f, Tr->Text, Tr->Len); ssize_t c = ds.CharAt(x - Tr->x1); if (Tr->Text == PreText()) { return 0; } else { char16 *t = Tr->Text + c; size_t Len = StrlenW(Text()); if (t >= Text() && t <= Text() + Len) { return (t - Text()) - GetTextStart(); } else { LgiTrace("%s:%i - Error getting char at position.\n", _FL); } } } return -1; } void LTag::GetTagByPos(LTagHit &TagHit, int x, int y, int Depth, bool InBody, bool DebugLog) { /* InBody: Originally I had this test in the code but it seems that some test cases have actual content after the body. And testing for "InBody" breaks functionality for those cases (see "spam4.html" and the unsubscribe link at the end of the doc). */ if (TagId == TAG_IMG) { LRect img(0, 0, Size.x - 1, Size.y - 1); if (/*InBody &&*/ img.Overlap(x, y)) { TagHit.Direct = this; TagHit.Block = 0; } } else if (/*InBody &&*/ TextPos.Length()) { for (unsigned i=0; i= Tr->y1 && y <= Tr->y2; int Near = IsNearRect(Tr, x, y); if (Near >= 0 && Near < 100) { if ( !TagHit.NearestText || ( SameRow && !TagHit.NearSameRow ) || ( SameRow == TagHit.NearSameRow && Near < TagHit.Near ) ) { TagHit.NearestText = this; TagHit.NearSameRow = SameRow; TagHit.Block = Tr; TagHit.Near = Near; TagHit.Index = NearestChar(Tr, x, y); if (DebugLog) { LgiTrace("%i:GetTagByPos HitText %s #%s, idx=%i, near=%i, txt='%S'\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near, Tr->Text); } if (!TagHit.Near) { TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; } } } } } else if ( TagId != TAG_TR && Tag && x >= 0 && y >= 0 && x < Size.x && y < Size.y // && InBody ) { // Direct hit TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; if (DebugLog) { LgiTrace("%i:GetTagByPos DirectHit %s #%s, idx=%i, near=%i\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near); } } if (TagId == TAG_BODY) InBody = true; for (unsigned i=0; iPos.x >= 0 && t->Pos.y >= 0) { t->GetTagByPos(TagHit, x - t->Pos.x, y - t->Pos.y, Depth + 1, InBody, DebugLog); } } } int LTag::OnNotify(LNotification n) { if (!Ctrl || !Html->InThread()) return 0; switch (CtrlType) { case CtrlSubmit: { LTag *Form = this; while (Form && Form->TagId != TAG_FORM) Form = ToTag(Form->Parent); if (Form) Html->OnSubmitForm(Form); break; } default: { CtrlValue = Ctrl->Name(); break; } } return 0; } void LTag::CollectFormValues(LHashTbl,char*> &f) { if (CtrlType != CtrlNone) { const char *Name; if (Get("name", Name)) { char *Existing = f.Find(Name); if (Existing) DeleteArray(Existing); char *Val = CtrlValue.Str(); if (Val) { LStringPipe p(256); for (char *v = Val; *v; v++) { if (*v == ' ') p.Write("+", 1); else if (IsAlpha(*v) || IsDigit(*v) || *v == '_' || *v == '.') p.Write(v, 1); else p.Print("%%%02.2X", *v); } f.Add(Name, p.NewStr()); } else { f.Add(Name, NewStr("")); } } } for (unsigned i=0; iCollectFormValues(f); } } LTag *LTag::FindCtrlId(int Id) { if (Ctrl && Ctrl->GetId() == Id) return this; for (unsigned i=0; iFindCtrlId(Id); if (f) return f; } return NULL; } void LTag::Find(int TagType, LArray &Out) { if (TagId == TagType) { Out.Add(this); } for (unsigned i=0; iFind(TagType, Out); } } void LTag::SetImage(const char *Uri, LSurface *Img) { if (Img) { if (TagId != TAG_IMG) { ImageDef *Def = (ImageDef*)LCss::Props.Find(PropBackgroundImage); if (Def) { Def->Type = ImageOwn; DeleteObj(Def->Img); Def->Img = Img; } } else { if (Img->GetColourSpace() == CsIndex8) { if (Image.Reset(new LMemDC(Img->X(), Img->Y(), System32BitColourSpace))) { Image->Colour(0, 32); Image->Rectangle(); Image->Blt(0, 0, Img); } else LgiTrace("%s:%i - SetImage can't promote 8bit image to 32bit.\n", _FL); } else Image.Reset(Img); LRect r = XSubRect(); if (r.Valid()) { LAutoPtr t(new LMemDC(r.X(), r.Y(), Image->GetColourSpace())); if (t) { t->Blt(0, 0, Image, &r); Image = t; } } } for (unsigned i=0; iCell) { t->Cell->MinContent = 0; t->Cell->MaxContent = 0; } } } else { Html->d->Loading.Add(Uri, this); } } void LTag::LoadImage(const char *Uri) { #if DOCUMENT_LOAD_IMAGES if (!Html->Environment) return; LUri u(Uri); bool LdImg = Html->GetLoadImages(); bool IsRemote = u.sProtocol && ( !_stricmp(u.sProtocol, "http") || !_stricmp(u.sProtocol, "https") || !_stricmp(u.sProtocol, "ftp") ); if (IsRemote && !LdImg) { Html->NeedsCapability("RemoteContent"); return; } else if (u.IsProtocol("data")) { if (!u.sPath) return; const char *s = u.sPath; if (*s++ != '/') return; LAutoString Type(LTokStr(s)); if (*s++ != ',') return; auto p = LString(Type).SplitDelimit(",;:"); if (p.Length() != 2 || !p.Last().Equals("base64")) return; LString Name = LString("name.") + p[0]; auto Filter = LFilterFactory::New(Name, FILTER_CAP_READ, NULL); if (!Filter) return; auto slen = strlen(s); auto blen = BufferLen_64ToBin(slen); LMemStream bin; bin.SetSize(blen); ConvertBase64ToBinary((uint8_t*)bin.GetBasePtr(), blen, s, slen); bin.SetPos(0); if (!Image.Reset(new LMemDC)) return; auto result = Filter->ReadImage(Image, &bin); if (result != LFilter::IoSuccess) Image.Reset(); return; } LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); j->Uri.Reset(NewStr(Uri)); j->Env = Html->Environment; j->UserData = this; j->UserUid = Html->GetDocumentUid(); // LgiTrace("%s:%i - new job %p, %p\n", _FL, j, j->UserData); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { SetImage(Uri, j->pDC.Release()); } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } #endif } void LTag::LoadImages() { const char *Uri = 0; if (Html->Environment && TagId == TAG_IMG && !Image) { if (Get("src", Uri)) LoadImage(Uri); } for (unsigned i=0; iLoadImages(); } } void LTag::ImageLoaded(char *uri, LSurface *Img, int &Used) { const char *Uri = 0; if (!Image && Get("src", Uri)) { if (strcmp(Uri, uri) == 0) { if (Used == 0) { SetImage(Uri, Img); } else { SetImage(Uri, new LMemDC(Img)); } Used++; } } for (unsigned i=0; iImageLoaded(uri, Img, Used); } } struct LTagElementCallback : public LCss::ElementCallback { const char *Val; const char *GetElement(LTag *obj) { return obj->Tag; } const char *GetAttr(LTag *obj, const char *Attr) { if (obj->Get(Attr, Val)) return Val; return NULL; } bool GetClasses(LString::Array &Classes, LTag *obj) { Classes = obj->Class; return Classes.Length() > 0; } LTag *GetParent(LTag *obj) { return ToTag(obj->Parent); } LArray GetChildren(LTag *obj) { LArray c; for (unsigned i=0; iChildren.Length(); i++) c.Add(ToTag(obj->Children[i])); return c; } }; void LTag::RestyleAll() { Restyle(); for (unsigned i=0; iRestyleAll(); } } // After CSS has changed this function scans through the CSS and applies any rules // that match the current tag. void LTag::Restyle() { // Use the matching built into the LCss Store. LCss::SelArray Styles; LTagElementCallback Context; if (Html->CssStore.Match(Styles, &Context, this)) { for (unsigned i=0; iStyle); } } // Do the element specific styles const char *s; if (Get("style", s)) SetCssStyle(s); #if DEBUG_RESTYLE && defined(_DEBUG) if (Debug) { auto Style = ToString(); LgiTrace(">>>> %s <<<<:\n%s\n\n", Tag.Get(), Style.Get()); } #endif } void LTag::SetStyle() { const static float FntMul[] = { 0.6f, // size=1 0.89f, // size=2 1.0f, // size=3 1.2f, // size=4 1.5f, // size=5 2.0f, // size=6 3.0f // size=7 }; const char *s = 0; #ifdef _DEBUG if (Get("debug", s)) { if ((Debug = atoi(s))) { LgiTrace("Debug Tag: %p '%s'\n", this, Tag ? Tag.Get() : "CONTENT"); } } #endif if (Get("Color", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { Color(Def); } } if (Get("Background", s) || Get("bgcolor", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { BackgroundColor(Def); } else { LCss::ImageDef Img; Img.Type = ImageUri; Img.Uri = s; BackgroundImage(Img); BackgroundRepeat(RepeatBoth); } } switch (TagId) { default: { if (!Stricmp(Tag.Get(), "o:p")) Display(LCss::DispNone); break; } case TAG_LINK: { const char *Type, *Href; if (Html->Environment && Get("type", Type) && Get("href", Href) && !Stricmp(Type, "text/css") && !Html->CssHref.Find(Href)) { LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); LTag *t = this; j->Uri.Reset(NewStr(Href)); j->Env = Html->Environment; j->UserData = t; j->UserUid = Html->GetDocumentUid(); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { LStreamI *s = j->GetStream(); if (s) { int Len = (int)s->GetSize(); if (Len > 0) { LAutoString a(new char[Len+1]); ssize_t r = s->Read(a, Len); a[r] = 0; Html->CssHref.Add(Href, true); Html->OnAddStyle("text/css", a); } } } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } } break; } case TAG_BLOCKQUOTE: { MarginTop(Len("8px")); MarginBottom(Len("8px")); MarginLeft(Len("16px")); if (Get("Type", s)) { if (_stricmp(s, "cite") == 0) { BorderLeft(BorderDef(this, "1px solid blue")); PaddingLeft(Len("0.5em")); /* ColorDef Def; Def.Type = ColorRgb; Def.Rgb32 = Rgb32(0x80, 0x80, 0x80); Color(Def); */ } } break; } case TAG_P: { MarginBottom(Len("1em")); break; } case TAG_A: { const char *Href; if (Get("href", Href)) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = Rgb32(0, 0, 255); Color(c); TextDecoration(TextDecorUnderline); } break; } case TAG_TABLE: { Len l; if (!Cell) Cell = new TblCell; if (Get("border", s)) { BorderDef b; if (b.Parse(this, s)) { BorderLeft(b); BorderRight(b); BorderTop(b); BorderBottom(b); } } if (Get("cellspacing", s) && l.Parse(s, PropBorderSpacing, ParseRelaxed)) { BorderSpacing(l); } else { // BorderSpacing(LCss::Len(LCss::LenPx, 2.0f)); } if (Get("cellpadding", s) && l.Parse(s, Prop_CellPadding, ParseRelaxed)) { _CellPadding(l); } if (Get("align", s)) { Len l; if (l.Parse(s)) Cell->XAlign = l.Type; } break; } case TAG_TD: case TAG_TH: { if (!Cell) Cell = new TblCell; LTag *Table = GetTable(); if (Table) { Len l = Table->_CellPadding(); if (!l.IsValid()) { l.Type = LCss::LenPx; l.Value = DefaultCellPadding; } PaddingLeft(l); PaddingRight(l); PaddingTop(l); PaddingBottom(l); } if (TagId == TAG_TH) FontWeight(LCss::FontWeightBold); break; } case TAG_BODY: { MarginLeft(Len(Get("leftmargin", s) ? s : DefaultBodyMargin)); MarginTop(Len(Get("topmargin", s) ? s : DefaultBodyMargin)); MarginRight(Len(Get("rightmargin", s) ? s : DefaultBodyMargin)); if (Get("text", s)) { ColorDef c; if (c.Parse(s)) { Color(c); } } break; } case TAG_OL: case TAG_UL: { MarginLeft(Len("16px")); break; } case TAG_STRONG: case TAG_B: { FontWeight(FontWeightBold); break; } case TAG_I: { FontStyle(FontStyleItalic); break; } case TAG_U: { TextDecoration(TextDecorUnderline); break; } case TAG_SUP: { VerticalAlign(VerticalSuper); FontSize(SizeSmaller); break; } case TAG_SUB: { VerticalAlign(VerticalSub); FontSize(SizeSmaller); break; } case TAG_TITLE: { Display(LCss::DispNone); break; } } if (Get("width", s)) { Len l; if (l.Parse(s, PropWidth, ParseRelaxed)) { Width(l); } } if (Get("height", s)) { Len l; if (l.Parse(s, PropHeight, ParseRelaxed)) Height(l); } if (Get("align", s)) { if (_stricmp(s, "left") == 0) TextAlign(Len(AlignLeft)); else if (_stricmp(s, "right") == 0) TextAlign(Len(AlignRight)); else if (_stricmp(s, "center") == 0) TextAlign(Len(AlignCenter)); } if (Get("valign", s)) { if (_stricmp(s, "top") == 0) VerticalAlign(Len(VerticalTop)); else if (_stricmp(s, "middle") == 0) VerticalAlign(Len(VerticalMiddle)); else if (_stricmp(s, "bottom") == 0) VerticalAlign(Len(VerticalBottom)); } Get("id", HtmlId); if (Get("class", s)) { Class = LString(s).SplitDelimit(" \t"); } Restyle(); switch (TagId) { default: break; case TAG_BIG: { LCss::Len l; l.Type = SizeLarger; FontSize(l); break; } /* case TAG_META: { LAutoString Cs; const char *s; if (Get("http-equiv", s) && _stricmp(s, "Content-Type") == 0) { const char *ContentType; if (Get("content", ContentType)) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { char16 *cs = NULL; Html->ParsePropValue(CharSet + 8, cs); Cs.Reset(WideToUtf8(cs)); DeleteArray(cs); } } } if (Get("name", s) && _stricmp(s, "charset") == 0 && Get("content", s)) { Cs.Reset(NewStr(s)); } else if (Get("charset", s)) { Cs.Reset(NewStr(s)); } if (Cs) { if (Cs && _stricmp(Cs, "utf-16") != 0 && _stricmp(Cs, "utf-32") != 0 && LGetCsInfo(Cs)) { // Html->SetCharset(Cs); } } break; } */ case TAG_BODY: { LCss::ColorDef Bk = BackgroundColor(); if (Bk.Type != ColorInherit) { // Copy the background up to the LHtml wrapper Html->GetCss(true)->BackgroundColor(Bk); } /* LFont *f = GetFont(); if (FontSize().Type == LenInherit) { FontSize(Len(LenPt, (float)f->PointSize())); } */ break; } case TAG_HEAD: { Display(DispNone); break; } case TAG_PRE: { LFontType Type; if (Type.GetSystemFont("Fixed")) { LAssert(ValidStr(Type.GetFace())); FontFamily(StringsDef(Type.GetFace())); } break; } case TAG_TR: break; case TAG_TD: case TAG_TH: { LAssert(Cell != NULL); const char *s; if (Get("colspan", s)) Cell->Span.x = atoi(s); else Cell->Span.x = 1; if (Get("rowspan", s)) Cell->Span.y = atoi(s); else Cell->Span.y = 1; Cell->Span.x = MAX(Cell->Span.x, 1); Cell->Span.y = MAX(Cell->Span.y, 1); if (Display() == DispInline || Display() == DispInlineBlock) { Display(DispBlock); // Inline-block TD??? Nope. } break; } case TAG_IMG: { const char *Uri; if (Html->Environment && Get("src", Uri)) { // printf("Uri: %s\n", Uri); LoadImage(Uri); } break; } case TAG_H1: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[5])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H2: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[4])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H3: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[3])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H4: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[2])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H5: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[1])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H6: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[0])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_FONT: { const char *s = 0; if (Get("Face", s)) { char16 *cw = CleanText(s, strlen(s), "utf-8", true); char *c8 = WideToUtf8(cw); DeleteArray(cw); LToken Faces(c8, ","); DeleteArray(c8); char *face = TrimStr(Faces[0]); if (ValidStr(face)) { FontFamily(face); DeleteArray(face); } else { LgiTrace("%s:%i - No face for font tag.\n", __FILE__, __LINE__); } } if (Get("Size", s)) { bool Digit = false, NonW = false; for (auto *c = s; *c; c++) { if (IsDigit(*c) || *c == '-') Digit = true; else if (!IsWhiteSpace(*c)) NonW = true; } if (Digit && !NonW) { auto Sz = atoi(s); switch (Sz) { case 1: FontSize(Len(LCss::LenEm, 0.63f)); break; case 2: FontSize(Len(LCss::LenEm, 0.82f)); break; case 3: FontSize(Len(LCss::LenEm, 1.0f)); break; case 4: FontSize(Len(LCss::LenEm, 1.13f)); break; case 5: FontSize(Len(LCss::LenEm, 1.5f)); break; case 6: FontSize(Len(LCss::LenEm, 2.0f)); break; case 7: FontSize(Len(LCss::LenEm, 3.0f)); break; } } else { FontSize(Len(s)); } } break; } case TAG_SELECT: { if (!Html->InThread()) break; LAssert(!Ctrl); Ctrl = new LCombo(Html->d->NextCtrlId++, 0, 0, 100, LSysFont->GetHeight() + 8, NULL); CtrlType = CtrlSelect; break; } case TAG_INPUT: { if (!Html->InThread()) break; LAssert(!Ctrl); const char *Type, *Value = NULL; Get("value", Value); LAutoWString CleanValue(Value ? CleanText(Value, strlen(Value), "utf-8", true, true) : NULL); if (CleanValue) { CtrlValue = CleanValue; } if (Get("type", Type)) { if (!_stricmp(Type, "password")) CtrlType = CtrlPassword; else if (!_stricmp(Type, "email")) CtrlType = CtrlEmail; else if (!_stricmp(Type, "text")) CtrlType = CtrlText; else if (!_stricmp(Type, "button")) CtrlType = CtrlButton; else if (!_stricmp(Type, "submit")) CtrlType = CtrlSubmit; else if (!_stricmp(Type, "hidden")) CtrlType = CtrlHidden; DeleteObj(Ctrl); if (CtrlType == CtrlEmail || CtrlType == CtrlText || CtrlType == CtrlPassword) { LEdit *Ed; LAutoString UtfCleanValue(WideToUtf8(CleanValue)); Ctrl = Ed = new LEdit(Html->d->NextCtrlId++, 0, 0, 60, LSysFont->GetHeight() + 8, UtfCleanValue); if (Ctrl) { Ed->Sunken(false); Ed->Password(CtrlType == CtrlPassword); } } else if (CtrlType == CtrlButton || CtrlType == CtrlSubmit) { LAutoString UtfCleanValue(WideToUtf8(CleanValue)); if (UtfCleanValue) { Ctrl = new InputButton(this, Html->d->NextCtrlId++, UtfCleanValue); } } } break; } } if (IsBlock()) { LCss::ImageDef bk = BackgroundImage(); if (bk.Type == LCss::ImageUri && ValidStr(bk.Uri) && !bk.Uri.Equals("transparent")) { LoadImage(bk.Uri); } } if (Ctrl) { LFont *f = GetFont(); if (f) Ctrl->SetFont(f, false); } } void LTag::OnStyleChange(const char *name) { if (!Stricmp(name, "display") && Html) { Html->Layout(true); Html->Invalidate(); } } void LTag::SetCssStyle(const char *Style) { if (Style) { // Strip out comments char *Comment = NULL; while ((Comment = strstr((char*)Style, "/*"))) { char *End = strstr(Comment+2, "*/"); if (!End) break; for (char *c = Comment; cDocCharSet && Html->Charset) { DocAndCsTheSame = _stricmp(Html->DocCharSet, Html->Charset) == 0; } if (SourceCs) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, SourceCs, Len); } else if (Html->DocCharSet && Html->Charset && !DocAndCsTheSame && !Html->OverideDocCharset) { char *DocText = (char*)LNewConvertCp(Html->DocCharSet, s, Html->Charset, Len); t = (char16*) LNewConvertCp(LGI_WideCharset, DocText, Html->DocCharSet, -1); DeleteArray(DocText); } else if (Html->DocCharSet) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->DocCharSet, Len); } else { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->Charset.Get() ? Html->Charset.Get() : DefaultCs, Len); } if (t && ConversionAllowed) { char16 *o = t; for (char16 *i=t; *i; ) { switch (*i) { case '&': { i++; if (*i == '#') { // Unicode Number char n[32] = "", *p = n; i++; if (*i == 'x' || *i == 'X') { // Hex number i++; while ( *i && ( IsDigit(*i) || (*i >= 'A' && *i <= 'F') || (*i >= 'a' && *i <= 'f') ) && (p - n) < 31) { *p++ = (char)*i++; } } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = (char)*i++; } } *p++ = 0; char16 Ch = atoi(n); if (Ch) { *o++ = Ch; } if (*i && *i != ';') i--; } else { // Named Char char16 *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } LAutoWString Var(NewStrW(i, e-i)); char16 Char = LHtmlStatic::Inst->VarMap.Find(Var); if (Char) { *o++ = Char; i = e; } else { i--; *o++ = *i; } } break; } case '\r': { break; } case ' ': case '\t': case '\n': { if (KeepWhiteSpace) { *o++ = *i; } else { *o++ = ' '; // Skip furthur whitespace while (i[1] && IsWhiteSpace(i[1])) { i++; } } break; } default: { // Normal char *o++ = *i; break; } } if (*i) i++; else break; } *o++ = 0; } if (t && !*t) { DeleteArray(t); } return t; } char *LTag::ParseText(char *Doc) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = LColour(L_WORKSPACE).c32(); BackgroundColor(c); TagId = TAG_BODY; Tag.Reset(NewStr("body")); Info = Html->GetTagInfo(Tag); char *OriginalCp = NewStr(Html->Charset); LStringPipe Utf16; char *s = Doc; while (s) { if (*s == '\r') { s++; } else if (*s == '<') { // Process tag char *e = s; e++; while (*e && *e != '>') { if (*e == '\"' || *e == '\'') { char *q = strchr(e + 1, *e); if (q) e = q + 1; else e++; } else e++; } if (*e == '>') e++; // Output tag Html->SetCharset("iso-8859-1"); char16 *t = CleanText(s, e - s, NULL, false); if (t) { Utf16.Push(t); DeleteArray(t); } s = e; } else if (!*s || *s == '\n') { // Output previous line char16 *Line = Utf16.NewStrW(); if (Line) { LTag *t = new LTag(Html, this); if (t) { t->Color(LColour(L_TEXT)); t->Text(Line); } } if (*s == '\n') { s++; LTag *t = new LTag(Html, this); if (t) { t->TagId = TAG_BR; t->Tag.Reset(NewStr("br")); t->Info = Html->GetTagInfo(t->Tag); } } else break; } else { // Seek end of text char *e = s; while (*e && *e != '\r' && *e != '\n' && *e != '<') e++; // Output text Html->SetCharset(OriginalCp); LAutoWString t(CleanText(s, e - s, NULL, false)); if (t) { Utf16.Push(t); } s = e; } } Html->SetCharset(OriginalCp); DeleteArray(OriginalCp); return 0; } bool LTag::ConvertToText(TextConvertState &State) { const static char *Rule = "------------------------------------------------------"; int DepthInc = 0; switch (TagId) { default: break; case TAG_P: if (State.GetPrev()) State.NewLine(); break; case TAG_UL: case TAG_OL: DepthInc = 2; break; } if (ValidStrW(Txt)) { for (int i=0; iConvertToUnicode(Txt); else u.Reset(WideToUtf8(Txt)); if (u) { size_t u_len = strlen(u); State.Write(u, u_len); } } State.Depth += DepthInc; for (unsigned i=0; iConvertToText(State); } State.Depth -= DepthInc; if (IsBlock()) { if (State.CharsOnLine) State.NewLine(); } else { switch (TagId) { case TAG_A: { // Emit the link to the anchor if it's different from the text of the span... const char *Href; if (Get("href", Href) && ValidStrW(Txt)) { if (_strnicmp(Href, "mailto:", 7) == 0) Href += 7; size_t HrefLen = strlen(Href); LAutoWString h(CleanText(Href, HrefLen, "utf-8")); if (h && StrcmpW(h, Txt) != 0) { // Href different from the text of the link State.Write(" (", 2); State.Write(Href, HrefLen); State.Write(")", 1); } } break; } case TAG_HR: { State.Write(Rule, strlen(Rule)); State.NewLine(); break; } case TAG_BR: { State.NewLine(); break; } default: break; } } return true; } char *LTag::NextTag(char *s) { while (s && *s) { char *n = strchr(s, '<'); if (n) { if (!n[1]) return NULL; if (IsAlpha(n[1]) || strchr("!/", n[1]) || n[1] == '?') { return n; } s = n + 1; } else break; } return 0; } void LHtml::CloseTag(LTag *t) { if (!t) return; OpenTags.Delete(t); } bool LTag::OnUnhandledColor(LCss::ColorDef *def, const char *&s) { const char *e = s; while (*e && (IsText(*e) || *e == '_')) e++; char tmp[256]; ssize_t len = e - s; memcpy(tmp, s, len); tmp[len] = 0; int m = LHtmlStatic::Inst->ColourMap.Find(tmp); s = e; if (m >= 0) { def->Type = LCss::ColorRgb; def->Rgb32 = Rgb24To32(m); return true; } return false; } void LTag::ZeroTableElements() { if (TagId == TAG_TABLE || TagId == TAG_TR || IsTableCell(TagId)) { Size.x = 0; Size.y = 0; if (Cell) { Cell->MinContent = 0; Cell->MaxContent = 0; } for (unsigned i=0; iZeroTableElements(); } } } void LTag::ResetCaches() { /* If during the parse process a callback causes a layout to happen then it's possible to have partial information in the LHtmlTableLayout structure, like missing TD cells. Because they haven't been parsed yet. This is called at the end of the parsing to reset all the cached info in LHtmlTableLayout. That way when the first real layout happens all the data is there. */ if (Cell) DeleteObj(Cell->Cells); for (size_t i=0; iResetCaches(); } LPoint LTag::GetTableSize() { LPoint s(0, 0); if (Cell && Cell->Cells) { Cell->Cells->GetSize(s.x, s.y); } return s; } LTag *LTag::GetTableCell(int x, int y) { LTag *t = this; while ( t && !t->Cell && !t->Cell->Cells && t->Parent) { t = ToTag(t->Parent); } if (t && t->Cell && t->Cell->Cells) { return t->Cell->Cells->Get(x, y); } return 0; } // This function gets the largest and smallest piece of content // in this cell and all it's children. bool LTag::GetWidthMetrics(LTag *Table, uint16 &Min, uint16 &Max) { bool Status = true; int MarginPx = 0; int LineWidth = 0; if (Display() == LCss::DispNone) return true; // Break the text into words and measure... if (Text()) { int MinContent = 0; int MaxContent = 0; LFont *f = GetFont(); if (f) { for (char16 *s = Text(); s && *s; ) { // Skip whitespace... while (*s && StrchrW(WhiteW, *s)) s++; // Find end of non-whitespace char16 *e = s; while (*e && !StrchrW(WhiteW, *e)) e++; // Find size of the word ssize_t Len = e - s; if (Len > 0) { LDisplayString ds(f, s, Len); MinContent = MAX(MinContent, ds.X()); } // Move to the next word. s = (*e) ? e + 1 : 0; } LDisplayString ds(f, Text()); LineWidth = MaxContent = ds.X(); } #if 0//def _DEBUG if (Debug) { LgiTrace("GetWidthMetrics Font=%p Sz=%i,%i\n", f, MinContent, MaxContent); } #endif Min = MAX(Min, MinContent); Max = MAX(Max, MaxContent); } // Specific tag handling? switch (TagId) { default: { if (IsBlock()) { MarginPx = (int)(BorderLeft().ToPx() + BorderRight().ToPx() + PaddingLeft().ToPx() + PaddingRight().ToPx()); } break; } case TAG_IMG: { Len w = Width(); if (w.IsValid()) { int x = (int) w.Value; Min = MAX(Min, x); Max = MAX(Max, x); } else if (Image) { Min = Max = Image->X(); } else { Size.x = Size.y = DefaultImgSize; Min = MAX(Min, Size.x); Max = MAX(Max, Size.x); } break; } case TAG_TD: case TAG_TH: { Len w = Width(); if (w.IsValid()) { if (w.IsDynamic()) { Min = MAX(Min, (int)w.Value); Max = MAX(Max, (int)w.Value); } else { Max = w.ToPx(0, GetFont()); } } else { LCss::BorderDef BLeft = BorderLeft(); LCss::BorderDef BRight = BorderRight(); LCss::Len PLeft = PaddingLeft(); LCss::Len PRight = PaddingRight(); MarginPx = (int)(PLeft.ToPx() + PRight.ToPx() + BLeft.ToPx()); if (Table->BorderCollapse() == LCss::CollapseCollapse) MarginPx += BRight.ToPx(); } break; } case TAG_TABLE: { Len w = Width(); if (w.IsValid() && !w.IsDynamic()) { // Fixed width table... int CellSpacing = BorderSpacing().ToPx(Min, GetFont()); int Px = ((int)w.Value) + (CellSpacing << 1); Min = MAX(Min, Px); Max = MAX(Max, Px); return true; } else { LPoint s; LHtmlTableLayout c(this); c.GetSize(s.x, s.y); // Auto layout table LArray ColMin, ColMax; for (int y=0; yGetWidthMetrics(Table, a, b)) { ColMin[x] = MAX(ColMin[x], a); ColMax[x] = MAX(ColMax[x], b); } x += t->Cell->Span.x; } else break; } } int MinSum = 0, MaxSum = 0; for (int i=0; iGetWidthMetrics(Table, Min, TagMax); LineWidth += TagMax; if (c->TagId == TAG_BR || c->TagId == TAG_LI) { Max = MAX(Max, LineWidth); LineWidth = 0; } } Max = MAX(Max, LineWidth); Min += MarginPx; Max += MarginPx; return Status; } static void DistributeSize(LArray &a, int Start, int Span, int Size, int Border) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i T Sum(LArray &a) { T s = 0; for (unsigned i=0; iCells) { #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Debug) { //int asd=0; } #endif Cell->Cells = new LHtmlTableLayout(this); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Cell->Cells && Debug) Cell->Cells->Dump(); #endif } if (Cell->Cells) Cell->Cells->LayoutTable(f, Depth); } void LHtmlTableLayout::AllocatePx(int StartCol, int Cols, int MinPx, bool HasToFillAllAvailable) { // Get the existing total size and size of the column set int CurrentTotalX = GetTotalX(); int CurrentSpanX = GetTotalX(StartCol, Cols); int MaxAdditionalPx = AvailableX - CurrentTotalX; if (MaxAdditionalPx <= 0) return; // Calculate the maximum space we have for this column set int AvailPx = (CurrentSpanX + MaxAdditionalPx) - BorderX1 - BorderX2; // Allocate any remaining space... int RemainingPx = MaxAdditionalPx; LArray Growable, NonGrowable, SizeInherit; int GrowablePx = 0; for (int x=StartCol; x 0) { GrowablePx += DiffPx; Growable.Add(x); } else if (MinCol[x] > 0) { NonGrowable.Add(x); } else if (MinCol[x] == 0 && CurrentSpanX < AvailPx) { // Growable.Add(x); } if (SizeCol[x].Type == LCss::LenInherit) SizeInherit.Add(x); } if (GrowablePx < RemainingPx && HasToFillAllAvailable) { if (Growable.Length() == 0) { // Add any suitable non-growable columns as well for (unsigned i=0; i MinCol[Largest]) Largest = i; } Growable.Add(Largest); } } if (Growable.Length()) { // Some growable columns... int Added = 0; // Reasonably increase the size of the columns... for (unsigned i=0; i 0) { AddPx = DiffPx; } else if (DiffPx > 0) { double Ratio = (double)DiffPx / GrowablePx; AddPx = (int) (Ratio * RemainingPx); } else { AddPx = RemainingPx / (int)Growable.Length(); } LAssert(AddPx >= 0); MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); Added += AddPx; } if (Added < RemainingPx && HasToFillAllAvailable) { // Still more to add, so if (SizeInherit.Length()) { Growable = SizeInherit; } else { int Largest = -1; for (unsigned i=0; i MinCol[Largest]) Largest = x; } Growable.Length(1); Growable[0] = Largest; } int AddPx = (RemainingPx - Added) / (int)Growable.Length(); for (unsigned i=0; i= 0); } else { MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); Added += AddPx; } } } } } struct ColInfo { int Large; int Growable; int Idx; int Px; }; int ColInfoCmp(ColInfo *a, ColInfo *b) { int LDiff = b->Large - a->Large; int LGrow = b->Growable - a->Growable; int LSize = b->Px - a->Px; return LDiff + LGrow + LSize; } void LHtmlTableLayout::DeallocatePx(int StartCol, int Cols, int MaxPx) { int TotalPx = GetTotalX(StartCol, Cols); if (TotalPx <= MaxPx || MaxPx == 0) return; int TrimPx = TotalPx - MaxPx; LArray Inf; int HalfMax = MaxPx >> 1; unsigned Interesting = 0; int InterestingPx = 0; for (int x=StartCol; x HalfMax; ci.Growable = MinCol[x] < MaxCol[x]; if (ci.Large || ci.Growable) { Interesting++; InterestingPx += ci.Px; } } Inf.Sort(ColInfoCmp); if (InterestingPx > 0) { for (unsigned i=0; i= 0); } else break; } } } int LHtmlTableLayout::GetTotalX(int StartCol, int Cols) { if (Cols < 0) Cols = s.x; int TotalX = BorderX1 + BorderX2 + CellSpacing; for (int x=StartCol; xZeroTableElements(); MinCol.Length(0); MaxCol.Length(0); MaxRow.Length(0); SizeCol.Length(0); LCss::Len BdrSpacing = Table->BorderSpacing(); CellSpacing = BdrSpacing.IsValid() ? (int)BdrSpacing.Value : 0; // Resolve total table width. TableWidth = Table->Width(); if (TableWidth.IsValid()) AvailableX = f->ResolveX(TableWidth, Table, false); else AvailableX = f->X(); LCss::Len MaxWidth = Table->MaxWidth(); if (MaxWidth.IsValid()) { int Px = f->ResolveX(MaxWidth, Table, false); if (Px < AvailableX) AvailableX = Px; } TableBorder = f->ResolveBorder(Table, Table); if (Table->BorderCollapse() != LCss::CollapseCollapse) TablePadding = f->ResolvePadding(Table, Table); else TablePadding.ZOff(0, 0); BorderX1 = TableBorder.x1 + TablePadding.x1; BorderX2 = TableBorder.x2 + TablePadding.x2; #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("AvailableX=%i, BorderX1=%i, BorderX2=%i\n", AvailableX, BorderX1, BorderX2); #endif #ifdef _DEBUG if (Table->Debug) { printf("Table Debug\n"); } #endif // Size detection pass int y; for (y=0; yGetFont(); t->Cell->BorderPx = f->ResolveBorder(t, t); t->Cell->PaddingPx = f->ResolvePadding(t, t); if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { LCss::DisplayType Disp = t->Display(); if (Disp == LCss::DispNone) continue; LCss::Len Content = t->Width(); if (Content.IsValid() && t->Cell->Span.x == 1) { if (SizeCol[x].IsValid()) { int OldPx = f->ResolveX(SizeCol[x], t, false); int NewPx = f->ResolveX(Content, t, false); if (NewPx > OldPx) { SizeCol[x] = Content; } } else { SizeCol[x] = Content; } } if (!t->GetWidthMetrics(Table, t->Cell->MinContent, t->Cell->MaxContent)) { t->Cell->MinContent = 16; t->Cell->MaxContent = 16; } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->Span.x == 1) { int BoxPx = t->Cell->BorderPx.x1 + t->Cell->BorderPx.x2 + t->Cell->PaddingPx.x1 + t->Cell->PaddingPx.x2; MinCol[x] = MAX(MinCol[x], t->Cell->MinContent + BoxPx); LAssert(MinCol[x] >= 0); MaxCol[x] = MAX(MaxCol[x], t->Cell->MaxContent + BoxPx); } } x += t->Cell->Span.x; } else break; } } // How much space used so far? int TotalX = GetTotalX(); if (TotalX > AvailableX) { // FIXME: // Off -> 'cisra-cqs.html' renders correctly. // On -> 'cisra_outage.html', 'steam1.html' renders correctly. #if 1 DeallocatePx(0, (int)MinCol.Length(), AvailableX); TotalX = GetTotalX(); #endif } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DumpCols(msg) \ if (Table->Debug) \ { \ LgiTrace("%s Ln%i - TotalX=%i AvailableX=%i\n", msg, __LINE__, TotalX, AvailableX); \ for (unsigned i=0; iDebug) { printf("TableDebug\n"); } #endif // Process spanned cells for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { if (t->Cell->Span.x > 1 || t->Cell->Span.y > 1) { int i; int ColMin = -CellSpacing; int ColMax = -CellSpacing; for (i=0; iCell->Span.x; i++) { ColMin += MinCol[x + i] + CellSpacing; ColMax += MaxCol[x + i] + CellSpacing; } LCss::Len Width = t->Width(); if (Width.IsValid()) { int Px = f->ResolveX(Width, t, false); t->Cell->MinContent = MAX(t->Cell->MinContent, Px); t->Cell->MaxContent = MAX(t->Cell->MaxContent, Px); } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->MinContent > ColMin) AllocatePx(t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MinContent, false); if (t->Cell->MaxContent > ColMax) DistributeSize(MaxCol, t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MaxContent, CellSpacing); } x += t->Cell->Span.x; } else break; } } TotalX = GetTotalX(); DumpCols("AfterSpannedCells"); // Sometimes the web page specifies too many percentages: // Scale them all. float PercentSum = 0.0f; for (int i=0; i 100.0) { float Ratio = PercentSum / 100.0f; for (int i=0; iResolveX(w, Table, false); if (w.Type == LCss::LenPercent) { MaxCol[x] = Px; } else if (Px > MinCol[x]) { int RemainingPx = AvailableX - TotalX; int AddPx = Px - MinCol[x]; AddPx = MIN(RemainingPx, AddPx); TotalX += AddPx; MinCol[x] += AddPx; LAssert(MinCol[x] >= 0); } } } } TotalX = GetTotalX(); DumpCols("AfterCssNonPercentageSizes"); if (TotalX > AvailableX) { #if !ALLOW_TABLE_GROWTH // Deallocate space if overused // Take some from the largest column int Largest = 0; for (int i=0; i MinCol[Largest]) { Largest = i; } } int Take = TotalX - AvailableX; if (Take < MinCol[Largest]) { MinCol[Largest] = MinCol[Largest] - Take; LAssert(MinCol[Largest] >= 0); TotalX -= Take; } DumpCols("AfterSpaceDealloc"); #endif } else if (TotalX < AvailableX) { AllocatePx(0, s.x, AvailableX, TableWidth.IsValid()); DumpCols("AfterRemainingAlloc"); } // Layout cell horizontally and then flow the contents to get // the height of all the cells LArray RowPad; MaxRow.Length(s.y); for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { t->Pos.x = XPos; t->Size.x = -CellSpacing; XPos -= CellSpacing; RowPad[y].y1 = MAX(RowPad[y].y1, t->Cell->BorderPx.y1 + t->Cell->PaddingPx.y1); RowPad[y].y2 = MAX(RowPad[y].y2, t->Cell->BorderPx.y2 + t->Cell->PaddingPx.y2); LRect Box(0, 0, -CellSpacing, 0); for (int i=0; iCell->Span.x; i++) { int ColSize = MinCol[x + i] + CellSpacing; LAssert(ColSize >= 0); if (ColSize < 0) break; t->Size.x += ColSize; XPos += ColSize; Box.x2 += ColSize; } LCss::Len Ht = t->Height(); LFlowRegion r(Table->Html, Box, true); t->OnFlow(&r, Depth+1); if (r.MAX.y > r.y2) { t->Size.y = MAX(r.MAX.y, t->Size.y); } if (Ht.IsValid() && Ht.Type != LCss::LenPercent) { int h = f->ResolveY(Ht, t, false); t->Size.y = MAX(h, t->Size.y); DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } } #if defined(_DEBUG) DEBUG_LOG("%s:%i - AfterCellFlow\n", _FL); for (unsigned i=0; iCell->Pos.x == x && t->Cell->Pos.y == y) { LCss::Len Ht = t->Height(); if (!(Ht.IsValid() && Ht.Type != LCss::LenPercent)) { DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } else break; } } // Cell positioning int Cx = BorderX1 + CellSpacing; int Cy = TableBorder.y1 + TablePadding.y1 + CellSpacing; for (y=0; yParent); if (Row && Row->TagId == TAG_TR) { t = new LTag(Table->Html, Row); if (t) { t->TagId = TAG_TD; t->Tag.Reset(NewStr("td")); t->Info = Table->Html->GetTagInfo(t->Tag); if ((t->Cell = new LTag::TblCell)) { t->Cell->Pos.x = x; t->Cell->Pos.y = y; t->Cell->Span.x = 1; t->Cell->Span.y = 1; } t->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, DefaultMissingCellColour)); Set(Table); } else break; } else break; } if (t) { if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { int RowPadOffset = RowPad[y].y1 - t->Cell->BorderPx.y1 - t->Cell->PaddingPx.y1; t->Pos.x = Cx; t->Pos.y = Cy + RowPadOffset; t->Size.x = -CellSpacing; for (int i=0; iCell->Span.x; i++) { int w = MinCol[x + i] + CellSpacing; t->Size.x += w; Cx += w; } t->Size.y = -CellSpacing; for (int n=0; nCell->Span.y; n++) { t->Size.y += MaxRow[y+n] + CellSpacing; } Table->Size.x = MAX(Cx + BorderX2, Table->Size.x); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("cell(%i,%i) = pos(%i,%i)+size(%i,%i)\n", t->Cell->Pos.x, t->Cell->Pos.y, t->Pos.x, t->Pos.y, t->Size.x, t->Size.y); } #endif } else { Cx += t->Size.x + CellSpacing; } x += t->Cell->Span.x; } else break; Prev = t; } Cx = BorderX1 + CellSpacing; Cy += MaxRow[y] + CellSpacing; } switch (Table->Cell->XAlign ? Table->Cell->XAlign : ToTag(Table->Parent)->GetAlign(true)) { case LCss::AlignCenter: { int fx = f->X(); int Ox = (fx-Table->Size.x) >> 1; Table->Pos.x = f->x1 + MAX(Ox, 0); DEBUG_LOG("%s:%i - AlignCenter fx=%i ox=%i pos.x=%i size.x=%i\n", _FL, fx, Ox, Table->Pos.x, Table->Size.x); break; } case LCss::AlignRight: { Table->Pos.x = f->x2 - Table->Size.x; DEBUG_LOG("%s:%i - AlignRight f->x2=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } default: { Table->Pos.x = f->x1; DEBUG_LOG("%s:%i - AlignLeft f->x1=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } } Table->Pos.y = f->y1; Table->Size.y = Cy + TablePadding.y2 + TableBorder.y2; } LRect LTag::ChildBounds() { LRect b(0, 0, -1, -1); for (unsigned i=0; iGetRect(); b.Union(&c); } else { b = t->GetRect(); } } return b; } LPoint LTag::AbsolutePos() { LPoint p; for (LTag *t=this; t; t=ToTag(t->Parent)) { p += t->Pos; } return p; } void LTag::SetSize(LPoint &s) { Size = s; } LHtmlArea::~LHtmlArea() { DeleteObjects(); } LRect LHtmlArea::Bounds() { LRect n(0, 0, -1, -1); for (unsigned i=0; iLength(); i++) { LRect *r = (*c)[i]; if (!Top || (r && (r->y1 < Top->y1))) { Top = r; } } return Top; } void LHtmlArea::FlowText(LTag *Tag, LFlowRegion *Flow, LFont *Font, int LineHeight, char16 *Text, LCss::LengthType Align) { if (!Flow || !Text || !Font) return; SetFixedLength(false); char16 *Start = Text; size_t FullLen = StrlenW(Text); #if 1 if (!Tag->Html->GetReadOnly() && !*Text) { // Insert a text rect for this tag, even though it's empty. // This allows the user to place the cursor on a blank line. LFlowRect *Tr = new LFlowRect; Tr->Tag = Tag; Tr->Text = Text; Tr->x1 = Flow->cx; Tr->x2 = Tr->x1 + 1; Tr->y1 = Flow->y1; Tr->y2 = Tr->y1 + Font->GetHeight(); LAssert(Tr->y2 >= Tr->y1); Flow->y2 = MAX(Flow->y2, Tr->y2+1); Flow->cx = Tr->x2 + 1; Add(Tr); Flow->Insert(Tr, Align); return; } #endif while (*Text) { LFlowRect *Tr = new LFlowRect; if (!Tr) break; Tr->Tag = Tag; Restart: Tr->x1 = Flow->cx; Tr->y1 = Flow->y1; #if 1 // I removed this at one stage but forget why. // Remove white space at start of line if not in edit mode.. if (Tag->Html->GetReadOnly() && Flow->x1 == Flow->cx && *Text == ' ') { Text++; if (!*Text) { DeleteObj(Tr); break; } } #endif Tr->Text = Text; LDisplayString ds(Font, Text, MIN(1024, FullLen - (Text-Start))); ssize_t Chars = ds.CharAt(Flow->X()); bool Wrap = false; if (Text[Chars]) { // Word wrap // Seek back to the nearest break opportunity ssize_t n = Chars; while (n > 0 && !StrchrW(WhiteW, Text[n])) n--; if (n == 0) { if (Flow->x1 == Flow->cx) { // Already started from the margin and it's too long to // fit across the entire page, just let it hang off the right edge. // Seek to the end of the word for (Tr->Len = Chars; Text[Tr->Len] && !StrchrW(WhiteW, Text[Tr->Len]); Tr->Len++) ; // Wrap... if (*Text == ' ') Text++; } else { // Not at the start of the margin Flow->FinishLine(); goto Restart; } } else { Tr->Len = n; LAssert(Tr->Len > 0); Wrap = true; } } else { // Fits.. Tr->Len = Chars; LAssert(Tr->Len > 0); } LDisplayString ds2(Font, Tr->Text, Tr->Len); Tr->x2 = ds2.X(); Tr->y2 = LineHeight > 0 ? LineHeight - 1 : 0; if (Wrap) { Flow->cx = Flow->x1; Flow->y1 += Tr->y2 + 1; Tr->x2 = Flow->x2 - Tag->RelX(); } else { Tr->x2 += Tr->x1 - 1; Flow->cx = Tr->x2 + 1; } Tr->y2 += Tr->y1; Flow->y2 = MAX(Flow->y2, Tr->y2 + 1); Add(Tr); Flow->Insert(Tr, Align); Text += Tr->Len; if (Wrap) { while (*Text == ' ') Text++; } Tag->Size.x = MAX(Tag->Size.x, Tr->x2 + 1); Tag->Size.y = MAX(Tag->Size.y, Tr->y2 + 1); Flow->MAX.x = MAX(Flow->MAX.x, Tr->x2); Flow->MAX.y = MAX(Flow->MAX.y, Tr->y2); if (Tr->Len == 0) break; } SetFixedLength(true); } char16 htoi(char16 c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; LAssert(0); return 0; } bool LTag::Serialize(LXmlTag *t, bool Write) { LRect pos; if (Write) { // Obj -> Tag if (Tag) t->SetAttr("tag", Tag); pos.ZOff(Size.x, Size.y); pos.Offset(Pos.x, Pos.y); t->SetAttr("pos", pos.GetStr()); t->SetAttr("tagid", TagId); if (Txt) { LStringPipe p(256); for (char16 *c = Txt; *c; c++) { if (*c > ' ' && *c < 127 && !strchr("%<>\'\"", *c)) p.Print("%c", (char)*c); else p.Print("%%%.4x", *c); } LAutoString Tmp(p.NewStr()); t->SetContent(Tmp); } if (Props.Length()) { auto CssStyles = ToString(); LAssert(!strchr(CssStyles, '\"')); t->SetAttr("style", CssStyles); } if (Html->Cursor == this) { LAssert(Cursor >= 0); t->SetAttr("cursor", (int64)Cursor); } else LAssert(Cursor < 0); if (Html->Selection == this) { LAssert(Selection >= 0); t->SetAttr("selection", (int64)Selection); } else LAssert(Selection < 0); for (unsigned i=0; iInsertTag(child); if (!tag->Serialize(child, Write)) { return false; } } } else { // Tag -> Obj Tag.Reset(NewStr(t->GetAttr("tag"))); TagId = (HtmlTag) t->GetAsInt("tagid"); pos.SetStr(t->GetAttr("pos")); if (pos.Valid()) { Pos.x = pos.x1; Pos.y = pos.y1; Size.x = pos.x2; Size.y = pos.y2; } if (ValidStr(t->GetContent())) { LStringPipe p(256); char *c = t->GetContent(); SkipWhiteSpace(c); for (; *c && *c > ' '; c++) { char16 ch; if (*c == '%') { ch = 0; for (int i=0; i<4 && *c; i++) { ch <<= 4; ch |= htoi(*++c); } } else ch = *c; p.Write(&ch, sizeof(ch)); } Txt.Reset(p.NewStrW()); } const char *s = t->GetAttr("style"); if (s) Parse(s, ParseRelaxed); s = t->GetAttr("cursor"); if (s) { LAssert(Html->Cursor == NULL); Html->Cursor = this; Cursor = atoi(s); LAssert(Cursor >= 0); } s = t->GetAttr("selection"); if (s) { LAssert(Html->Selection == NULL); Html->Selection = this; Selection = atoi(s); LAssert(Selection >= 0); } #ifdef _DEBUG s = t->GetAttr("debug"); if (s && atoi(s) != 0) Debug = true; #endif for (int i=0; iChildren.Length(); i++) { LXmlTag *child = t->Children[i]; if (child->IsTag("e")) { LTag *tag = new LTag(Html, NULL); if (!tag) { LAssert(0); return false; } if (!tag->Serialize(child, Write)) { return false; } Attach(tag); } } } return true; } /* /// This method centers the text in the area given to the tag. Used for inline block elements. void LTag::CenterText() { if (!Parent) return; // Find the size of the text elements. int ContentPx = 0; for (unsigned i=0; iX(); } LFont *f = GetFont(); int ParentPx = ToTag(Parent)->Size.x; int AvailPx = Size.x; // Remove the border and padding from the content area AvailPx -= BorderLeft().ToPx(ParentPx, f); AvailPx -= BorderRight().ToPx(ParentPx, f); AvailPx -= PaddingLeft().ToPx(ParentPx, f); AvailPx -= PaddingRight().ToPx(ParentPx, f); if (AvailPx > ContentPx) { // Now offset all the regions to the right int OffPx = (AvailPx - ContentPx) >> 1; for (unsigned i=0; iOffset(OffPx, 0); } } } */ void LTag::OnFlow(LFlowRegion *Flow, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH) return; DisplayType Disp = Display(); if (Disp == DispNone) return; LFont *f = GetFont(); LFlowRegion Local(*Flow); bool Restart = true; int BlockFlowWidth = 0; const char *ImgAltText = NULL; Size.x = 0; Size.y = 0; LCssTools Tools(this, f); LRect rc(Flow->X(), Html->Y()); PadPx = Tools.GetPadding(rc); if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } switch (TagId) { default: break; case TAG_BODY: { Flow->InBody++; break; } case TAG_IFRAME: { LFlowRegion Temp = *Flow; Flow->EndBlock(); Flow->Indent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); // Flow children for (unsigned i=0; iOnFlow(&Temp, Depth + 1); if (TagId == TAG_TR) { Temp.x2 -= MIN(t->Size.x, Temp.X()); } } Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); BoundParents(); return; break; } case TAG_TR: { Size.x = Flow->X(); break; } case TAG_IMG: { Size.x = Size.y = 0; LCss::Len w = Width(); LCss::Len h = Height(); // LCss::Len MinX = MinWidth(); // LCss::Len MaxX = MaxWidth(); LCss::Len MinY = MinHeight(); LCss::Len MaxY = MaxHeight(); LAutoPtr a; int ImgX, ImgY; if (Image) { ImgX = Image->X(); ImgY = Image->Y(); } else if (Get("alt", ImgAltText) && ValidStr(ImgAltText)) { LDisplayString a(f, ImgAltText); ImgX = a.X() + 4; ImgY = a.Y() + 4; } else { ImgX = DefaultImgSize; ImgY = DefaultImgSize; } double AspectRatio = ImgY != 0 ? (double)ImgX / ImgY : 1.0; bool XLimit = false, YLimit = false; double Scale = 1.0; if (w.IsValid() && w.Type != LenAuto) { Size.x = Flow->ResolveX(w, this, false); XLimit = true; } else { int Fx = Flow->x2 - Flow->x1 + 1; if (ImgX > Fx) { Size.x = Fx; // * 0.8; if (Image) Scale = (double) Fx / ImgX; } else { Size.x = ImgX; } } XLimit |= Flow->LimitX(Size.x, MinWidth(), MaxWidth(), f); if (h.IsValid() && h.Type != LenAuto) { Size.y = Flow->ResolveY(h, this, false); YLimit = true; } else { Size.y = (int) (ImgY * Scale); } YLimit |= Flow->LimitY(Size.y, MinHeight(), MaxHeight(), f); if ( (XLimit ^ YLimit) && Image ) { if (XLimit) { Size.y = (int) ceil((double)Size.x / AspectRatio); } else { Size.x = (int) ceil((double)Size.y * AspectRatio); } } if (MinY.IsValid()) { int Px = Flow->ResolveY(MinY, this, false); if (Size.y < Px) Size.y = Px; } if (MaxY.IsValid()) { int Px = Flow->ResolveY(MaxY, this, false); if (Size.y > Px) Size.y = Px; } if (Disp == DispInline || Disp == DispInlineBlock) { Restart = false; if (Flow->cx > Flow->x1 && Size.x > Flow->X()) { Flow->FinishLine(); } Pos.y = Flow->y1; Flow->y2 = MAX(Flow->y1, Pos.y + Size.y - 1); LCss::LengthType a = GetAlign(true); switch (a) { case AlignCenter: { int Fx = Flow->x2 - Flow->x1; Pos.x = Flow->x1 + ((Fx - Size.x) / 2); break; } case AlignRight: { Pos.x = Flow->x2 - Size.x; break; } default: { Pos.x = Flow->cx; break; } } } break; } case TAG_HR: { Flow->FinishLine(); Pos.x = Flow->x1; Pos.y = Flow->y1 + 7; Size.x = Flow->X(); Size.y = 2; Flow->cx ++; Flow->y2 += 16; Flow->FinishLine(); return; break; } case TAG_TABLE: { Flow->EndBlock(); LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); LayoutTable(Flow, Depth + 1); Flow->y1 += Size.y; Flow->y2 = Flow->y1; Flow->cx = Flow->x1; Flow->my = 0; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); Flow->Outdent(this, left, top, right, bottom, true); BoundParents(); return; } } if (Disp == DispBlock || Disp == DispInlineBlock) { // This is a block level element, so end the previous non-block elements if (Disp == DispBlock) Flow->EndBlock(); #ifdef _DEBUG if (Debug) LgiTrace("Before %s\n", Flow->ToString().Get()); #endif BlockFlowWidth = Flow->X(); // Indent the margin... LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); // Set the width if any LCss::Len Wid = Width(); if (!IsTableCell(TagId) && Wid.IsValid()) Size.x = Flow->ResolveX(Wid, this, false); else if (TagId != TAG_IMG) { if (Disp == DispInlineBlock) // Flow->Inline) Size.x = 0; // block inside inline-block default to fit the content else Size.x = Flow->X(); } else if (Disp == DispInlineBlock) Size.x = 0; if (MaxWidth().IsValid()) { int Px = Flow->ResolveX(MaxWidth(), this, false); if (Size.x > Px) Size.x = Px; } if (MinWidth().IsValid()) { int Px = Flow->ResolveX(MinWidth(), this, false); if (Size.x < Px) Size.x = Px; } Pos.x = Disp == DispInlineBlock ? Flow->cx : Flow->x1; Pos.y = Flow->y1; Flow->y1 -= Pos.y; Flow->y2 -= Pos.y; if (Disp == DispBlock) { Flow->x1 -= Pos.x; Flow->x2 = Flow->x1 + Size.x; Flow->cx -= Pos.x; Flow->Indent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Flow->Indent(PadPx, false); } else { Flow->x2 = Flow->X(); Flow->x1 = Flow->ResolveX(BorderLeft(), this, true) + Flow->ResolveX(PaddingLeft(), this, true); Flow->cx = Flow->x1; Flow->y1 += Flow->ResolveY(BorderTop(), this, true) + Flow->ResolveY(PaddingTop(), this, true); Flow->y2 = Flow->y1; if (!IsTableTag()) Flow->Inline++; } } else { Flow->Indent(PadPx, false); } if (f) { // Clear the previous text layout... TextPos.DeleteObjects(); switch (TagId) { default: break; case TAG_LI: { // Insert the list marker if (!PreText()) { LCss::ListStyleTypes s = Parent->ListStyleType(); if (s == ListInherit) { if (Parent->TagId == TAG_OL) s = ListDecimal; else if (Parent->TagId == TAG_UL) s = ListDisc; } switch (s) { default: break; case ListDecimal: { ssize_t Index = Parent->Children.IndexOf(this); char Txt[32]; sprintf_s(Txt, sizeof(Txt), "%i. ", (int)(Index + 1)); PreText(Utf8ToWide(Txt)); break; } case ListDisc: { PreText(NewStrW(LHtmlListItem)); break; } } } if (PreText()) TextPos.FlowText(this, Flow, f, f->GetHeight(), PreText(), AlignLeft); break; } case TAG_IMG: { if (Disp == DispBlock) { Flow->cx += Size.x; Flow->y2 += Size.y; } break; } } if (Text() && Flow->InBody) { // Setup the line height cache if (LineHeightCache < 0) { LCss::Len LineHt; LFont *LineFnt = GetFont(); for (LTag *t = this; t && !LineHt.IsValid(); t = ToTag(t->Parent)) { LineHt = t->LineHeight(); if (t->TagId == TAG_TABLE) break; } if (LineFnt) { int FontPx = LineFnt->GetHeight(); if (!LineHt.IsValid() || LineHt.Type == LCss::LenAuto || LineHt.Type == LCss::LenNormal) { LineHeightCache = FontPx; // LgiTrace("LineHeight FontPx=%i Px=%i Auto\n", FontPx, LineHeightCache); } else if (LineHt.Type == LCss::LenPx) { auto Scale = Html->GetDpiScale().y; LineHt.Value *= (float)Scale; LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i (Scale=%f)\n", FontPx, LineHeightCache, Scale); } else { LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i ToPx\n", FontPx, LineHeightCache); } } } // Flow in the rest of the text... char16 *Txt = Text(); LCss::LengthType Align = GetAlign(true); TextPos.FlowText(this, Flow, f, LineHeightCache, Txt, Align); #ifdef _DEBUG if (Debug) LgiTrace("%s:%i - %p.size=%p\n", _FL, this, &Size.x); #endif } } // Flow children PostFlowAlign.Length(0); for (unsigned i=0; iPosition()) { case PosStatic: case PosAbsolute: case PosFixed: { LFlowRegion old = *Flow; t->OnFlow(Flow, Depth + 1); // Try and reset the flow to how it was before... Flow->x1 = old.x1; Flow->x2 = old.x2; Flow->cx = old.cx; Flow->y1 = old.y1; Flow->y2 = old.y2; Flow->MAX.x = MAX(Flow->MAX.x, old.MAX.x); Flow->MAX.y = MAX(Flow->MAX.y, old.MAX.y); break; } default: { t->OnFlow(Flow, Depth + 1); break; } } if (TagId == TAG_TR) { Flow->x2 -= MIN(t->Size.x, Flow->X()); } } LCss::LengthType XAlign = GetAlign(true); int FlowSz = Flow->Width(); // Align the children... for (auto &group: PostFlowAlign) { int MinX = FlowSz, MaxX = 0; for (auto &a: group) { MinX = MIN(MinX, a.t->Pos.x); MaxX = MAX(MaxX, a.t->Pos.x + a.t->Size.x - 1); } int TotalX = MaxX - MinX + 1; int FirstX = group.Length() ? group[0].t->Pos.x : 0; for (auto &a: group) { if (a.XAlign == LCss::AlignCenter) { int OffX = (Size.x - TotalX) >> 1; if (OffX > 0) { a.t->Pos.x += OffX; } } else if (a.XAlign == LCss::AlignRight) { int OffX = FlowSz - FirstX - TotalX; if (OffX > 0) { a.t->Pos.x += OffX; } } } } if (Disp == DispBlock || Disp == DispInlineBlock) { LCss::Len Ht = Height(); LCss::Len MaxHt = MaxHeight(); // I dunno, there should be a better way... :-( if (MarginLeft().Type == LenAuto && MarginRight().Type == LenAuto) { XAlign = LCss::AlignCenter; } bool AcceptHt = !IsTableCell(TagId) && Ht.Type != LenPercent; if (AcceptHt) { if (Ht.IsValid()) { int HtPx = Flow->ResolveY(Ht, this, false); if (HtPx > Flow->y2) Flow->y2 = HtPx; } if (MaxHt.IsValid()) { int MaxHtPx = Flow->ResolveY(MaxHt, this, false); if (MaxHtPx < Flow->y2) { Flow->y2 = MaxHtPx; Flow->MAX.y = MIN(Flow->y2, Flow->MAX.y); } } } if (Disp == DispBlock) { Flow->EndBlock(); int OldFlowSize = Flow->x2 - Flow->x1 + 1; Flow->Outdent(this, PaddingLeft(), PaddingTop(), PaddingRight(), PaddingBottom(), false); Flow->Outdent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Size.y = Flow->y2 > 0 ? Flow->y2 : 0; Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); int NewFlowSize = Flow->x2 - Flow->x1 + 1; int Diff = NewFlowSize - OldFlowSize; if (Diff) Flow->MAX.x += Diff; Flow->y1 = Flow->y2; Flow->x2 = Flow->x1 + BlockFlowWidth; } else { LCss::Len Wid = Width(); int WidPx = Wid.IsValid() ? Flow->ResolveX(Wid, this, true) : 0; Size.x = MAX(WidPx, Size.x); Size.x += Flow->ResolveX(PaddingRight(), this, true); Size.x += Flow->ResolveX(BorderRight(), this, true); int MarginR = Flow->ResolveX(MarginRight(), this, true); int MarginB = Flow->ResolveX(MarginBottom(), this, true); Flow->x1 = Local.x1 - Pos.x; Flow->cx = Local.cx + Size.x + MarginR - Pos.x; Flow->x2 = Local.x2 - Pos.x; if (Height().IsValid()) { Size.y = Flow->ResolveY(Height(), this, false); Flow->y2 = MAX(Flow->y1 + Size.y + MarginB - 1, Flow->y2); } else { Flow->y2 += Flow->ResolveX(PaddingBottom(), this, true); Flow->y2 += Flow->ResolveX(BorderBottom(), this, true); Size.y = Flow->y2; } Flow->y1 = Local.y1 - Pos.y; Flow->y2 = MAX(Local.y2, Flow->y1+Size.y-1); if (!IsTableTag()) Flow->Inline--; } // Can't do alignment here because pos is used to // restart the parents flow region... } else { Flow->Outdent(PadPx, false); switch (TagId) { default: break; case TAG_SELECT: case TAG_INPUT: { if (Html->InThread() && Ctrl) { LRect r = Ctrl->GetPos(); if (Width().IsValid()) Size.x = Flow->ResolveX(Width(), this, false); else Size.x = r.X(); if (Height().IsValid()) Size.y = Flow->ResolveY(Height(), this, false); else Size.y = r.Y(); if (Html->IsAttached() && !Ctrl->IsAttached()) Ctrl->Attach(Html); } Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_IMG: { Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_BR: { int OldFlowY2 = Flow->y2; Flow->FinishLine(); Size.y = Flow->y2 - OldFlowY2; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_CENTER: { int Px = Flow->X(); for (auto e: Children) { LTag *t = ToTag(e); if (t && t->IsBlock() && t->Size.x < Px) { t->Pos.x = (Px - t->Size.x) >> 1; } } break; } } } BoundParents(); if (Restart) { Flow->x1 += Pos.x; Flow->x2 += Pos.x; Flow->cx += Pos.x; Flow->y1 += Pos.y; Flow->y2 += Pos.y; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); } if (Disp == DispBlock || Disp == DispInlineBlock) { if (XAlign == LCss::AlignCenter || XAlign == LCss::AlignRight) { int Match = 0; auto parent = ToTag(Parent); for (auto &grp: parent->PostFlowAlign) { bool Overlaps = false; for (auto &a: grp) { if (a.Overlap(this)) { Overlaps = true; break; } } if (!Overlaps) Match++; } auto &grp = parent->PostFlowAlign[Match]; if (grp.Length() == 0) { grp.x1 = Flow->x1; grp.x2 = Flow->x2; } auto &pf = grp.New(); pf.Disp = Disp; pf.XAlign = XAlign; pf.t = this; } } if (TagId == TAG_BODY && Flow->InBody > 0) { Flow->InBody--; } } bool LTag::PeekTag(char *s, char *tag) { bool Status = false; if (s && tag) { if (*s == '<') { char *t = 0; Html->ParseName(++s, &t); if (t) { Status = _stricmp(t, tag) == 0; } DeleteArray(t); } } return Status; } LTag *LTag::GetTable() { LTag *t = 0; for (t=ToTag(Parent); t && t->TagId != TAG_TABLE; t = ToTag(t->Parent)) ; return t; } void LTag::BoundParents() { if (!Parent) return; LTag *np; for (LTag *n=this; n; n = np) { np = ToTag(n->Parent); if (!np || np->TagId == TAG_IFRAME) break; np->Size.x = MAX(np->Size.x, n->Pos.x + n->Size.x); np->Size.y = MAX(np->Size.y, n->Pos.y + n->Size.y); } } struct DrawBorder { LSurface *pDC; uint32_t LineStyle; uint32_t LineReset; uint32_t OldStyle; DrawBorder(LSurface *pdc, LCss::BorderDef &d) { LineStyle = 0xffffffff; LineReset = 0x80000000; if (d.Style == LCss::BorderDotted) { switch ((int)d.Value) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } pDC = pdc; OldStyle = pDC->LineStyle(); } ~DrawBorder() { pDC->LineStyle(OldStyle); } }; void LTag::GetInlineRegion(LRegion &rgn, int ox, int oy) { if (TagId == TAG_IMG) { LRect rc(0, 0, Size.x-1, Size.y-1); rc.Offset(ox + Pos.x, oy + Pos.y); rgn.Union(&rc); } else { for (unsigned i=0; iGetInlineRegion(rgn, ox + Pos.x, oy + Pos.y); } } class CornersImg : public LMemDC { public: int Px, Px2; CornersImg( float RadPx, LRect *BorderPx, LCss::BorderDef **defs, LColour &Back, bool DrawBackground) { Px = 0; Px2 = 0; //Radius.Type != LCss::LenInherit && if (RadPx > 0.0f) { Px = (int)ceil(RadPx); Px2 = Px << 1; if (Create(Px2, Px2, System32BitColourSpace)) { #if 1 Colour(0, 32); #else Colour(LColour(255, 0, 255)); #endif Rectangle(); LPointF ctr(Px, Px); LPointF LeftPt(0.0, Px); LPointF TopPt(Px, 0.0); LPointF RightPt(X(), Px); LPointF BottomPt(Px, Y()); int x_px[4] = {BorderPx->x1, BorderPx->x2, BorderPx->x2, BorderPx->x1}; int y_px[4] = {BorderPx->y1, BorderPx->y1, BorderPx->y2, BorderPx->y2}; LPointF *pts[4] = {&LeftPt, &TopPt, &RightPt, &BottomPt}; // Draw border parts.. for (int i=0; i<4; i++) { int k = (i + 1) % 4; // Setup the stops LBlendStop stops[2] = { {0.0, 0}, {1.0, 0} }; uint32_t iColour = defs[i]->Color.IsValid() ? defs[i]->Color.Rgb32 : Back.c32(); uint32_t kColour = defs[k]->Color.IsValid() ? defs[k]->Color.Rgb32 : Back.c32(); if (defs[i]->IsValid() && defs[k]->IsValid()) { stops[0].c32 = iColour; stops[1].c32 = kColour; } else if (defs[i]->IsValid()) { stops[0].c32 = stops[1].c32 = iColour; } else { stops[0].c32 = stops[1].c32 = kColour; } // Create a brush LLinearBlendBrush br ( *pts[i], *pts[k], 2, stops ); // Setup the clip LRect clip( (int)MIN(pts[i]->x, pts[k]->x), (int)MIN(pts[i]->y, pts[k]->y), (int)MAX(pts[i]->x, pts[k]->x)-1, (int)MAX(pts[i]->y, pts[k]->y)-1); ClipRgn(&clip); // Draw the arc... LPath p; p.Circle(ctr, Px); if (defs[i]->IsValid() || defs[k]->IsValid()) p.Fill(this, br); // Fill the background p.Empty(); p.Ellipse(ctr, Px-x_px[i], Px-y_px[i]); if (DrawBackground) { LSolidBrush br(Back); p.Fill(this, br); } else { LEraseBrush br; p.Fill(this, br); } ClipRgn(NULL); } #ifdef MAC ConvertPreMulAlpha(true); #endif #if 0 static int count = 0; LString file; file.Printf("c:\\temp\\img-%i.bmp", ++count); GdcD->Save(file, Corners); #endif } } } }; void LTag::PaintBorderAndBackground(LSurface *pDC, LColour &Back, LRect *BorderPx) { LArray r; LRect BorderPxRc; bool DrawBackground = !Back.IsTransparent(); #ifdef _DEBUG if (Debug) { //int asd=0; } #endif if (!BorderPx) BorderPx = &BorderPxRc; BorderPx->ZOff(0, 0); // Get all the border info and work out the pixel sizes. LFont *f = GetFont(); #define DoEdge(coord, axis, name) \ BorderDef name = Border##name(); \ BorderPx->coord = name.Style != LCss::BorderNone ? name.ToPx(Size.axis, f) : 0; #define BorderValid(name) \ ((name).IsValid() && (name).Style != LCss::BorderNone) DoEdge(x1, x, Left); DoEdge(y1, y, Top); DoEdge(x2, x, Right); DoEdge(y2, y, Bottom); LCss::BorderDef *defs[4] = {&Left, &Top, &Right, &Bottom}; if (BorderValid(Left) || BorderValid(Right) || BorderValid(Top) || BorderValid(Bottom) || DrawBackground) { // Work out the rectangles switch (Display()) { case DispInlineBlock: case DispBlock: { r[0].ZOff(Size.x-1, Size.y-1); break; } case DispInline: { LRegion rgn; GetInlineRegion(rgn); if (BorderPx) { for (int i=0; ix1 -= BorderPx->x1 + PadPx.x1; r->y1 -= BorderPx->y1 + PadPx.y1; r->x2 += BorderPx->x2 + PadPx.x2; r->y2 += BorderPx->y2 + PadPx.y2; } } r.Length(rgn.Length()); auto p = r.AddressOf(); for (auto i = rgn.First(); i; i = rgn.Next()) *p++ = *i; break; } default: return; } // If we are drawing rounded corners, draw them into a memory context LAutoPtr Corners; int Px = 0, Px2 = 0; LCss::Len Radius = BorderRadius(); float RadPx = Radius.Type == LCss::LenPx ? Radius.Value : Radius.ToPx(Size.x, GetFont()); bool HasRadius = Radius.Type != LCss::LenInherit && RadPx > 0.0f; // Loop over the rectangles and draw everything int Op = pDC->Op(GDC_ALPHA); for (unsigned i=0; i rc.Y()) { Px = rc.Y() / 2; Px2 = Px << 1; } if (!Corners || Corners->Px2 != Px2) { Corners.Reset(new CornersImg((float)Px, BorderPx, defs, Back, DrawBackground)); } // top left LRect r(0, 0, Px-1, Px-1); pDC->Blt(rc.x1, rc.y1, Corners, &r); // top right r.Set(Px, 0, Corners->X()-1, Px-1); pDC->Blt(rc.x2-Px+1, rc.y1, Corners, &r); // bottom left r.Set(0, Px, Px-1, Corners->Y()-1); pDC->Blt(rc.x1, rc.y2-Px+1, Corners, &r); // bottom right r.Set(Px, Px, Corners->X()-1, Corners->Y()-1); pDC->Blt(rc.x2-Px+1, rc.y2-Px+1, Corners, &r); #if 1 pDC->Colour(Back); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #else pDC->Colour(LColour(255, 0, 0, 0x80)); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Colour(LColour(0, 255, 0, 0x80)); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Colour(LColour(0, 0, 255, 0x80)); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #endif } else if (DrawBackground) { pDC->Colour(Back); pDC->Rectangle(&rc); } LCss::BorderDef *b; if ((b = &Left) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1 + i, rc.y1+Px, rc.x1+i, rc.y2-Px); } } if ((b = &Top) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y1+i, rc.x2-Px, rc.y1+i); } } if ((b = &Right) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x2-i, rc.y1+Px, rc.x2-i, rc.y2-Px); } } if ((b = &Bottom) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y2-i, rc.x2-Px, rc.y2-i); } } } pDC->Op(Op); } } static void FillRectWithImage(LSurface *pDC, LRect *r, LSurface *Image, LCss::RepeatType Repeat) { int Px = 0, Py = 0; int Old = pDC->Op(GDC_ALPHA); if (!Image) return; switch (Repeat) { default: case LCss::RepeatBoth: { for (int y=0; yY(); y += Image->Y()) { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py + y, Image); } } break; } case LCss::RepeatX: { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py, Image); } break; } case LCss::RepeatY: { for (int y=0; yY(); y += Image->Y()) { pDC->Blt(Px, Py + y, Image); } break; } case LCss::RepeatNone: { pDC->Blt(Px, Py, Image); break; } } pDC->Op(Old); } void LTag::OnPaint(LSurface *pDC, bool &InSelection, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH || Display() == DispNone) return; if ( #ifdef _DEBUG !Html->_Debug && #endif LCurrentTime() - Html->PaintStart > Html->d->MaxPaintTime) { Html->d->MaxPaintTimeout = true; return; } int Px, Py; pDC->GetOrigin(Px, Py); #if 0 if (Debug) { Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; Html->WindowVirtualOffset(&Offset); LRect cli; pDC->GetClient(&cli); printf("\tTag paint mx=%g,%g off=%i,%i p=%i,%i Pos=%i,%i cli=%s\n", mx.x0, mx.y0, Offset.x, Offset.y, Px, Py, Pos.x, Pos.y, cli.GetStr()); } #endif switch (TagId) { case TAG_INPUT: case TAG_SELECT: { if (Ctrl) { int64 Sx = 0, Sy = 0; int64 LineY = GetFont()->GetHeight(); Html->GetScrollPos(Sx, Sy); Sx *= LineY; Sy *= LineY; LRect r(0, 0, Size.x-1, Size.y-1), Px; LColour back = _Colour(false); PaintBorderAndBackground(pDC, back, &Px); if (!dynamic_cast(Ctrl)) { r.x1 += Px.x1; r.y1 += Px.y1; r.x2 -= Px.x2; r.y2 -= Px.y2; } r.Offset(AbsX() - (int)Sx, AbsY() - (int)Sy); Ctrl->SetPos(r); } if (TagId == TAG_SELECT) return; break; } case TAG_BODY: { auto b = _Colour(false); if (!b.IsTransparent()) { pDC->Colour(b); pDC->Rectangle(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } if (Image) { LRect r; r.ZOff(Size.x-1, Size.y-1); FillRectWithImage(pDC, &r, Image, BackgroundRepeat()); } break; } case TAG_HEAD: { // Nothing under here to draw. return; } case TAG_HR: { pDC->Colour(L_MED); pDC->Rectangle(0, 0, Size.x - 1, Size.y - 1); break; } case TAG_TR: case TAG_TBODY: case TAG_META: { // Draws nothing... break; } case TAG_IMG: { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); if (Image) { #if ENABLE_IMAGE_RESIZING if ( !ImageResized && ( Size.x != Image->X() || Size.y != Image->Y() ) ) { ImageResized = true; LColourSpace Cs = Image->GetColourSpace(); if (Cs == CsIndex8 && Image->AlphaDC()) Cs = System32BitColourSpace; LAutoPtr r(new LMemDC(Size.x, Size.y, Cs)); if (r) { if (Cs == CsIndex8) r->Palette(new LPalette(Image->Palette())); ResampleDC(r, Image); Image = r; } } #endif int Old = pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, Image); pDC->Op(Old); } else if (Size.x > 1 && Size.y > 1) { LRect b(0, 0, Size.x-1, Size.y-1); LColour Fill(LColour(L_MED).Mix(LColour(L_LIGHT), 0.2f)); LColour Border(L_MED); // Border pDC->Colour(Border); pDC->Box(&b); b.Inset(1, 1); pDC->Box(&b); b.Inset(1, 1); pDC->Colour(Fill); pDC->Rectangle(&b); const char *Alt; LColour Red(LColour(255, 0, 0).Mix(Fill, 0.3f)); if (Get("alt", Alt) && ValidStr(Alt)) { LDisplayString Ds(Html->GetFont(), Alt); Html->GetFont()->Colour(Red, Fill); Ds.Draw(pDC, 2, 2, &b); } else if (Size.x >= 16 && Size.y >= 16) { // Red 'x' int Cx = b.x1 + (b.X()/2); int Cy = b.y1 + (b.Y()/2); LRect c(Cx-4, Cy-4, Cx+4, Cy+4); pDC->Colour(Red); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x1, c.y2, c.x2, c.y1); pDC->Line(c.x1, c.y1 + 1, c.x2 - 1, c.y2); pDC->Line(c.x1 + 1, c.y1, c.x2, c.y2 - 1); pDC->Line(c.x1 + 1, c.y2, c.x2, c.y1 + 1); pDC->Line(c.x1, c.y2 - 1, c.x2 - 1, c.y1); } } pDC->ClipRgn(0); break; } default: { LColour fore = _Colour(true); LColour back = _Colour(false); if (Display() == DispBlock && Html->Environment) { LCss::ImageDef Img = BackgroundImage(); if (Img.Img) { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); FillRectWithImage(pDC, &Clip, Img.Img, BackgroundRepeat()); pDC->ClipRgn(NULL); back.Empty(); } } PaintBorderAndBackground(pDC, back, NULL); LFont *f = GetFont(); #if DEBUG_TEXT_AREA bool IsEditor = Html ? !Html->GetReadOnly() : false; #else bool IsEditor = false; #endif if (f && TextPos.Length()) { // This is the non-display part of the font bounding box int LeadingPx = (int)(f->Leading() + 0.5); // This is the displayable part of the font int FontPx = f->GetHeight() - LeadingPx; // This is the pixel height we're aiming to fill int EffectiveLineHt = LineHeightCache >= 0 ? MAX(FontPx, LineHeightCache) : FontPx; // This gets added to the y coord of each piece of text int LineHtOff = ((EffectiveLineHt - FontPx + 1) >> 1) - LeadingPx; #define FontColour(InSelection) \ f->Transparent(!InSelection && !IsEditor); \ if (InSelection) \ f->Colour(L_FOCUS_SEL_FORE, L_FOCUS_SEL_BACK); \ else \ { \ LColour bk(back.IsTransparent() ? LColour(L_WORKSPACE) : back); \ LColour fr(fore.IsTransparent() ? LColour(DefaultTextColour) : fore); \ if (IsEditor) \ bk = bk.Mix(LColour::Black, 0.05f); \ f->Colour(fr, bk); \ } if (Html->HasSelection() && (Selection >= 0 || Cursor >= 0) && Selection != Cursor) { ssize_t Min = -1; ssize_t Max = -1; ssize_t Base = GetTextStart(); if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection) + Base; Max = MAX(Cursor, Selection) + Base; } else if (InSelection) { Max = MAX(Cursor, Selection) + Base; } else { Min = MAX(Cursor, Selection) + Base; } LRect CursorPos; CursorPos.ZOff(-1, -1); for (unsigned i=0; iText - Text(); ssize_t Done = 0; int x = Tr->x1; if (Tr->Len == 0) { // Is this a selection edge point? if (!InSelection && Min == 0) { InSelection = !InSelection; } else if (InSelection && Max == 0) { InSelection = !InSelection; } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } break; } while (Done < Tr->Len) { ssize_t c = Tr->Len - Done; FontColour(InSelection); // Is this a selection edge point? if ( !InSelection && Min - Start >= Done && Min - Start < Done + Tr->Len) { InSelection = !InSelection; c = Min - Start - Done; } else if ( InSelection && Max - Start >= Done && Max - Start <= Tr->Len) { InSelection = !InSelection; c = Max - Start - Done; } // Draw the text run LDisplayString ds(f, Tr->Text + Done, c); if (IsEditor) { LRect r(x, Tr->y1, x + ds.X() - 1, Tr->y2); ds.Draw(pDC, x, Tr->y1 + LineHtOff, &r); } else { ds.Draw(pDC, x, Tr->y1 + LineHtOff); } x += ds.X(); Done += c; // Is this is end of the tag? if (Tr->Len == Done) { // Is it also a selection edge? if ( !InSelection && Min - Start == Done) { InSelection = !InSelection; } else if ( InSelection && Max - Start == Done) { InSelection = !InSelection; } } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } } if (Html->d->CursorVis && CursorPos.Valid()) { pDC->Colour(L_TEXT); pDC->Rectangle(&CursorPos); } } else if (Cursor >= 0) { FontColour(InSelection); ssize_t Base = GetTextStart(); for (unsigned i=0; iText - Text()) - Base; LAssert(Tr->y2 >= Tr->y1); LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); if ( ( Tr->Text == PreText() && !ValidStrW(Text()) ) || ( Cursor >= Pos && Cursor <= Pos + Tr->Len ) ) { ssize_t Off = Tr->Text == PreText() ? StrlenW(PreText()) : Cursor - Pos; pDC->Colour(L_TEXT); LRect c; if (Off) { LDisplayString ds(f, Tr->Text, Off); int x = ds.X(); if (x >= Tr->X()) x = Tr->X()-1; c.Set(Tr->x1 + x, Tr->y1, Tr->x1 + x + 1, Tr->y1 + f->GetHeight()); } else { c.Set(Tr->x1, Tr->y1, Tr->x1 + 1, Tr->y1 + f->GetHeight()); } Html->d->CursorPos = c; if (Html->d->CursorVis) pDC->Rectangle(&c); Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } else { FontColour(InSelection); for (auto &Tr: TextPos) { LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); } } } break; } } #if DEBUG_TABLE_LAYOUT && 0 if (IsTableCell(TagId)) { LTag *Tbl = this; while (Tbl->TagId != TAG_TABLE && Tbl->Parent) Tbl = Tbl->Parent; if (Tbl && Tbl->TagId == TAG_TABLE && Tbl->Debug) { pDC->Colour(LColour(255, 0, 0)); pDC->Box(0, 0, Size.x-1, Size.y-1); } } #endif for (unsigned i=0; iSetOrigin(Px - t->Pos.x, Py - t->Pos.y); t->OnPaint(pDC, InSelection, Depth + 1); pDC->SetOrigin(Px, Py); } #if DEBUG_DRAW_TD if (TagId == TAG_TD) { LTag *Tbl = this; while (Tbl && Tbl->TagId != TAG_TABLE) Tbl = ToTag(Tbl->Parent); if (Tbl && Tbl->Debug) { int Ls = pDC->LineStyle(LSurface::LineDot); pDC->Colour(LColour::Blue); pDC->Box(0, 0, Size.x-1, Size.y-1); pDC->LineStyle(Ls); } } #endif } ////////////////////////////////////////////////////////////////////// LHtml::LHtml(int id, int x, int y, int cx, int cy, LDocumentEnv *e) : LDocView(e), ResObject(Res_Custom), LHtmlParser(NULL) { View = this; d = new LHtmlPrivate; SetReadOnly(true); ViewWidth = -1; SetId(id); LRect r(x, y, x+cx, y+cy); SetPos(r); Cursor = 0; Selection = 0; DocumentUid = 0; _New(); } LHtml::~LHtml() { _Delete(); DeleteObj(d); if (JobSem.Lock(_FL)) { JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } } void LHtml::_New() { d->StyleDirty = false; d->IsLoaded = false; d->Content.x = d->Content.y = 0; d->DeferredLoads = 0; Tag = 0; DocCharSet.Reset(); IsHtml = true; #ifdef DefaultFont LFont *Def = new LFont; if (Def) { if (Def->CreateFromCss(DefaultFont)) SetFont(Def, true); else DeleteObj(Def); } #endif FontCache = new LFontCache(this); SetScrollBars(false, false); } void LHtml::_Delete() { LAssert(!d->IsParsing); CssStore.Empty(); CssHref.Empty(); OpenTags.Length(0); Source.Reset(); DeleteObj(Tag); DeleteObj(FontCache); } LFont *LHtml::DefFont() { return GetFont(); } void LHtml::OnAddStyle(const char *MimeType, const char *Styles) { if (Styles) { const char *c = Styles; bool Status = CssStore.Parse(c); if (Status) { d->StyleDirty = true; } #if 0 // def _DEBUG bool LogCss = false; if (!Status) { char p[MAX_PATH_LEN]; sprintf_s(p, sizeof(p), "c:\\temp\\css_parse_failure_%i.txt", LRand()); LFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); if (CssStore.Error) f.Print("Error: %s\n\n", CssStore.Error.Get()); f.Write(Styles, strlen(Styles)); f.Close(); } } if (LogCss) { LStringPipe p; CssStore.Dump(p); LAutoString a(p.NewStr()); LFile f; if (f.Open("C:\\temp\\css.txt", O_WRITE)) { f.Write(a, strlen(a)); f.Close(); } } #endif } } void LHtml::ParseDocument(const char *Doc) { if (!Tag) { Tag = new LTag(this, 0); } if (GetCss()) GetCss()->DeleteProp(LCss::PropBackgroundColor); if (Tag) { Tag->TagId = ROOT; OpenTags.Length(0); if (IsHtml) { Parse(Tag, Doc); // Add body tag if not specified... LTag *Html = Tag->GetTagByName("html"); LTag *Body = Tag->GetTagByName("body"); if (!Html && !Body) { if ((Html = new LTag(this, 0))) Html->SetTag("html"); if ((Body = new LTag(this, Html))) Body->SetTag("body"); Html->Attach(Body); if (Tag->Text()) { LTag *Content = new LTag(this, Body); if (Content) { Content->TagId = CONTENT; Content->Text(NewStrW(Tag->Text())); } } while (Tag->Children.Length()) { LTag *t = ToTag(Tag->Children.First()); Body->Attach(t, Body->Children.Length()); } DeleteObj(Tag); Tag = Html; } else if (!Body) { if ((Body = new LTag(this, Html))) Body->SetTag("body"); for (unsigned i=0; iChildren.Length(); i++) { LTag *t = ToTag(Html->Children[i]); if (t->TagId != TAG_HEAD) { Body->Attach(t); i--; } } Html->Attach(Body); } if (Html && Body) { char16 *t = Tag->Text(); if (t) { if (ValidStrW(t)) { LTag *Content = new LTag(this, 0); if (Content) { Content->Text(NewStrW(Tag->Text())); Body->Attach(Content, 0); } } Tag->Text(0); } #if 0 // Enabling this breaks the test file 'gw2.html'. for (LTag *t = Html->Tags.First(); t; ) { if (t->Tag && t->Tag[0] == '!') { Tag->Attach(t, 0); t = Html->Tags.Current(); } else if (t->TagId != TAG_HEAD && t != Body) { if (t->TagId == TAG_HTML) { LTag *c; while ((c = t->Tags.First())) { Html->Attach(c, 0); } t->Detach(); DeleteObj(t); } else { t->Detach(); Body->Attach(t); } t = Html->Tags.Current(); } else { t = Html->Tags.Next(); } } #endif if (Environment) { const char *OnLoad; if (Body->Get("onload", OnLoad)) { Environment->OnExecuteScript(this, (char*)OnLoad); } } } } else { Tag->ParseText(Source); } } ViewWidth = -1; if (Tag) Tag->ResetCaches(); Invalidate(); } bool LHtml::NameW(const char16 *s) { LAutoPtr utf(WideToUtf8(s)); return Name(utf); } const char16 *LHtml::NameW() { LBase::Name(Source); return LBase::NameW(); } bool LHtml::Name(const char *s) { int Uid = -1; if (Environment) Uid = Environment->NextUid(); if (Uid < 0) Uid = GetDocumentUid() + 1; SetDocumentUid(Uid); _Delete(); _New(); IsHtml = false; // Detect HTML const char *c = s; while ((c = strchr(c, '<'))) { char *t = 0; c = ParseName((char*) ++c, &t); if (t && GetTagInfo(t)) { DeleteArray(t); IsHtml = true; break; } DeleteArray(t); } // Parse d->IsParsing = true; ParseDocument(s); d->IsParsing = false; if (Tag && d->StyleDirty) { d->StyleDirty = false; Tag->RestyleAll(); } if (d->DeferredLoads == 0) { OnLoad(); } Invalidate(); return true; } const char *LHtml::Name() { if (!Source && Tag) { LStringPipe s(1024); Tag->CreateSource(s); Source.Reset(s.NewStr()); } return Source; } LMessage::Result LHtml::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COPY: { Copy(); break; } case M_JOBS_LOADED: { bool Update = false; int InitDeferredLoads = d->DeferredLoads; if (JobSem.Lock(_FL)) { for (unsigned i=0; iUserData); if (j->UserUid == MyUid && j->UserData != NULL) { Html1::LTag *r = static_cast(j->UserData); if (d->DeferredLoads > 0) d->DeferredLoads--; // Check the tag is still in our tree... if (Tag->HasChild(r)) { // Process the returned data... if (r->TagId == TAG_IMG) { if (j->pDC) { r->SetImage(j->Uri, j->pDC.Release()); ViewWidth = 0; Update = true; } else if (j->Stream) { LAutoPtr pDC(GdcD->Load(dynamic_cast(j->Stream.Get()))); if (pDC) { r->SetImage(j->Uri, pDC.Release()); ViewWidth = 0; Update = true; } else LgiTrace("%s:%i - Image decode failed for '%s'\n", _FL, j->Uri.Get()); } else if (j->Status == LDocumentEnv::LoadJob::JobOk) LgiTrace("%s:%i - Unexpected job type for '%s'\n", _FL, j->Uri.Get()); } else if (r->TagId == TAG_LINK) { if (!CssHref.Find(j->Uri)) { LStreamI *s = j->GetStream(); if (s) { s->ChangeThread(); int Size = (int)s->GetSize(); LAutoString Style(new char[Size+1]); ssize_t rd = s->Read(Style, Size); if (rd > 0) { Style[rd] = 0; CssHref.Add(j->Uri, true); OnAddStyle("text/css", Style); ViewWidth = 0; Update = true; } } } } else if (r->TagId == TAG_IFRAME) { // Remote IFRAME loading not support for security reasons. } else LgiTrace("%s:%i - Unexpected tag '%s' for URI '%s'\n", _FL, r->Tag.Get(), j->Uri.Get()); } else { /* Html1::LTag *p = ToTag(r->Parent); while (p && p->Parent) p = ToTag(p->Parent); */ LgiTrace("%s:%i - No child tag for job.\n", _FL); } } // else it's from another (historical) HTML control, ignore } JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } if (InitDeferredLoads > 0 && d->DeferredLoads <= 0) { LAssert(d->DeferredLoads == 0); d->DeferredLoads = 0; OnLoad(); } if (Update) { OnPosChange(); Invalidate(); } break; } } return LDocView::OnEvent(Msg); } int LHtml::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_VSCROLL: { int LineY = GetFont()->GetHeight(); if (Tag) Tag->ClearToolTips(); if (n.Type == LNotifyScrollBarCreate && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = d->Content.y / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } Invalidate(); break; } default: { LTag *Ctrl = Tag ? Tag->FindCtrlId(c->GetId()) : NULL; if (Ctrl) return Ctrl->OnNotify(n); break; } } return LLayout::OnNotify(c, n); } void LHtml::OnPosChange() { LLayout::OnPosChange(); if (ViewWidth != X()) { Invalidate(); } } LPoint LHtml::Layout(bool ForceLayout) { LRect Client = GetClient(); if (Tag && (ViewWidth != Client.X() || ForceLayout)) { LFlowRegion f(this, Client, false); // Flow text, width is different Tag->OnFlow(&f, 0); ViewWidth = Client.X(); d->Content.x = f.MAX.x + 1; d->Content.y = f.MAX.y + 1; // Set up scroll box bool Sy = f.y2 > Y(); int LineY = GetFont()->GetHeight(); uint64 Now = LCurrentTime(); if (Now - d->SetScrollTime > 100) { d->SetScrollTime = Now; SetScrollBars(false, Sy); if (Sy && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = f.y2 / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } } else { // LgiTrace("%s - Dropping SetScroll, loop detected: %i ms\n", GetClass(), (int)(Now - d->SetScrollTime)); } } return d->Content; } LPointF LHtml::GetDpiScale() { LPointF Scale(1.0, 1.0); auto Wnd = GetWindow(); if (Wnd) Scale = Wnd->GetDpiScale(); return Scale; } void LHtml::OnPaint(LSurface *ScreenDC) { // LProfile Prof("LHtml::OnPaint"); #if HTML_USE_DOUBLE_BUFFER LRect Client = GetClient(); if (ScreenDC->IsScreen()) { if (!MemDC || (MemDC->X() < Client.X() || MemDC->Y() < Client.Y())) { if (MemDC.Reset(new LMemDC)) { int Sx = Client.X() + 10; int Sy = Client.Y() + 10; if (!MemDC->Create(Sx, Sy, System32BitColourSpace)) { MemDC.Reset(); } } } if (MemDC) { MemDC->ClipRgn(NULL); #if 0//def _DEBUG MemDC->Colour(LColour(255, 0, 255)); MemDC->Rectangle(); #endif } } #endif LSurface *pDC = MemDC ? MemDC : ScreenDC; #if 0 Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; WindowVirtualOffset(&Offset); printf("\tHtml paint mx=%g,%g off=%i,%i\n", mx.x0, mx.y0, Offset.x, Offset.y); #endif LColour cBack; if (GetCss()) { LCss::ColorDef Bk = GetCss()->BackgroundColor(); if (Bk.Type == LCss::ColorRgb) cBack = Bk; } if (!cBack.IsValid()) cBack = LColour(Enabled() ? L_WORKSPACE : L_MED); pDC->Colour(cBack); pDC->Rectangle(); if (Tag) { Layout(); if (VScroll) { int LineY = GetFont()->GetHeight(); int Vs = (int)VScroll->Value(); pDC->SetOrigin(0, Vs * LineY); } bool InSelection = false; PaintStart = LCurrentTime(); d->MaxPaintTimeout = false; Tag->OnPaint(pDC, InSelection, 0); if (d->MaxPaintTimeout) { LgiTrace("%s:%i - Html max paint time reached: %i ms.\n", _FL, LCurrentTime() - PaintStart); } } #if HTML_USE_DOUBLE_BUFFER if (MemDC) { pDC->SetOrigin(0, 0); ScreenDC->Blt(0, 0, MemDC); } #endif if (d->OnLoadAnchor && VScroll) { LAutoString a = d->OnLoadAnchor; GotoAnchor(a); LAssert(d->OnLoadAnchor == 0); } } bool LHtml::HasSelection() { if (Cursor && Selection) { return Cursor->Cursor >= 0 && Selection->Selection >= 0 && !(Cursor == Selection && Cursor->Cursor == Selection->Selection); } return false; } void LHtml::UnSelectAll() { bool i = false; if (Cursor) { Cursor->Cursor = -1; Cursor = NULL; i = true; } if (Selection) { Selection->Selection = -1; Selection = NULL; i = true; } if (i) { Invalidate(); } } void LHtml::SelectAll() { } LTag *LHtml::GetLastChild(LTag *t) { if (t && t->Children.Length()) { for (LTag *i = ToTag(t->Children.Last()); i; ) { LTag *c = i->Children.Length() ? ToTag(i->Children.Last()) : NULL; if (c) i = c; else return i; } } return 0; } LTag *LHtml::PrevTag(LTag *t) { // This returns the previous tag in the tree as if all the tags were // listed via recursion using "in order". // Walk up the parent chain looking for a prev for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a parent? if (p->Parent) { // Prev? LTag *pp = ToTag(p->Parent); ssize_t Idx = pp->Children.IndexOf(p); LTag *Prev = Idx > 0 ? ToTag(pp->Children[Idx - 1]) : NULL; if (Prev) { LTag *Last = GetLastChild(Prev); return Last ? Last : Prev; } else { return ToTag(p->Parent); } } } return 0; } LTag *LHtml::NextTag(LTag *t) { // This returns the next tag in the tree as if all the tags were // listed via recursion using "in order". // Does this have a child tag? if (t->Children.Length() > 0) { return ToTag(t->Children.First()); } else { // Walk up the parent chain for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a next? if (p->Parent) { LTag *pp = ToTag(p->Parent); size_t Idx = pp->Children.IndexOf(p); LTag *Next = pp->Children.Length() > Idx + 1 ? ToTag(pp->Children[Idx + 1]) : NULL; if (Next) { return Next; } } } } return 0; } int LHtml::GetTagDepth(LTag *Tag) { // Returns the depth of the tag in the tree. int n = 0; for (LTag *t = Tag; t; t = ToTag(t->Parent)) { n++; } return n; } bool LHtml::IsCursorFirst() { if (!Cursor || !Selection) return false; return CompareTagPos(Cursor, Cursor->Cursor, Selection, Selection->Selection); } bool LHtml::CompareTagPos(LTag *a, ssize_t AIdx, LTag *b, ssize_t BIdx) { // Returns true if the 'a' is before 'b' point. if (!a || !b) return false; if (a == b) { return AIdx < BIdx; } else { LArray ATree, BTree; for (LTag *t = a; t; t = ToTag(t->Parent)) ATree.AddAt(0, t); for (LTag *t = b; t; t = ToTag(t->Parent)) BTree.AddAt(0, t); ssize_t Depth = MIN(ATree.Length(), BTree.Length()); for (int i=0; i 0); LTag *p = ATree[i-1]; LAssert(BTree[i-1] == p); ssize_t ai = p->Children.IndexOf(at); ssize_t bi = p->Children.IndexOf(bt); return ai < bi; } } } return false; } void LHtml::SetLoadImages(bool i) { if (i ^ GetLoadImages()) { LDocView::SetLoadImages(i); SendNotify(LNotifyShowImagesChanged); if (GetLoadImages() && Tag) { Tag->LoadImages(); } } } char *LHtml::GetSelection() { char *s = 0; if (Cursor && Selection) { LMemQueue p; bool InSelection = false; Tag->CopyClipboard(p, InSelection); int Len = (int)p.GetSize(); if (Len > 0) { char16 *t = (char16*)p.New(sizeof(char16)); if (t) { size_t Len = StrlenW(t); for (int i=0; iOnFind(Dlg); } +*/ void BuildTagList(LArray &t, LTag *Tag) { t.Add(Tag); for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(Tag->Children[i]); BuildTagList(t, c); } } static void FormEncode(LStringPipe &p, const char *c) { const char *s = c; while (*c) { while (*c && *c != ' ') c++; if (c > s) { p.Write(s, c - s); c = s; } if (*c == ' ') { p.Write("+", 1); s = c; } else break; } } bool LHtml::OnSubmitForm(LTag *Form) { if (!Form || !Environment) { LAssert(!"Bad param"); return false; } const char *Method = NULL; const char *Action = NULL; if (!Form->Get("method", Method) || !Form->Get("action", Action)) { LAssert(!"Missing form action/method"); return false; } LHashTbl,char*> f; Form->CollectFormValues(f); bool Status = false; if (!_stricmp(Method, "post")) { LStringPipe p(256); bool First = true; // const char *Field; // for (char *Val = f.First(&Field); Val; Val = f.Next(&Field)) for (auto v : f) { if (First) First = false; else p.Write("&", 1); FormEncode(p, v.key); p.Write("=", 1); FormEncode(p, v.value); } LAutoPtr Data(p.NewStr()); Status = Environment->OnPostForm(this, Action, Data); } else if (!_stricmp(Method, "get")) { Status = Environment->OnNavigate(this, Action); } else { LAssert(!"Bad form method."); } f.DeleteArrays(); return Status; } bool LHtml::OnFind(LFindReplaceCommon *Params) { bool Status = false; if (Params) { if (!Params->Find) return Status; d->FindText.Reset(Utf8ToWide(Params->Find)); d->MatchCase = Params->MatchCase; } if (!Cursor) Cursor = Tag; if (Cursor && d->FindText) { LArray Tags; BuildTagList(Tags, Tag); ssize_t Start = Tags.IndexOf(Cursor); for (unsigned i=1; iText()) { char16 *Hit; if (d->MatchCase) Hit = StrstrW(s->Text(), d->FindText); else Hit = StristrW(s->Text(), d->FindText); if (Hit) { // found something... UnSelectAll(); Selection = Cursor = s; Cursor->Cursor = Hit - s->Text(); Selection->Selection = Cursor->Cursor + StrlenW(d->FindText); OnCursorChanged(); if (VScroll) { // Scroll the tag into view... int y = s->AbsY(); int LineY = GetFont()->GetHeight(); int Val = y / LineY; SetVScroll(Val); } Invalidate(); Status = true; break; } } } } return Status; } -bool LHtml::DoFind() -{ - LFindDlg Dlg(this, 0, FindCallback, this); - return Dlg.DoModal() != 0; +void LHtml::DoFind(std::function Callback) +{ + LFindDlg *Dlg = new LFindDlg(this, + [&](auto dlg, auto action) + { + OnFind(dlg); + delete dlg; + }); + + Dlg->DoModal(NULL); } bool LHtml::OnKey(LKey &k) { bool Status = false; if (k.Down()) { int Dy = 0; int LineY = GetFont()->GetHeight(); int Page = GetClient().Y() / LineY; switch (k.vkey) { case LK_F3: { OnFind(NULL); break; } #ifdef WIN32 case LK_INSERT: goto DoCopy; #endif case LK_UP: { Dy = -1; Status = true; break; } case LK_DOWN: { Dy = 1; Status = true; break; } case LK_PAGEUP: { Dy = -Page; Status = true; break; } case LK_PAGEDOWN: { Dy = Page; Status = true; break; } case LK_HOME: { Dy = (int) (VScroll ? -VScroll->Value() : 0); Status = true; break; } case LK_END: { if (VScroll) { LRange r = VScroll->GetRange(); Dy = (int)(r.End() - Page); } Status = true; break; } default: { switch (k.c16) { case 'f': case 'F': { if (k.CtrlCmd()) { - DoFind(); + DoFind(NULL); Status = true; } break; } case 'c': case 'C': { #ifdef WIN32 DoCopy: #endif if (k.CtrlCmd()) { Copy(); Status = true; } break; } } break; } } if (Dy && VScroll) SetVScroll(VScroll->Value() + Dy); } return Status; } int LHtml::ScrollY() { return GetFont()->GetHeight() * (VScroll ? (int)VScroll->Value() : 0); } void LHtml::OnMouseClick(LMouse &m) { Capture(m.Down()); SetPulse(m.Down() ? 200 : -1); if (m.Down()) { Focus(true); int Offset = ScrollY(); bool TagProcessedClick = false; LTagHit Hit; if (Tag) { Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false, DEBUG_TAG_BY_POS); #if DEBUG_TAG_BY_POS Hit.Dump("MouseClick"); #endif } if (m.Left() && !m.IsContextMenu()) { if (m.Double()) { d->WordSelectMode = true; if (Cursor) { // Extend the selection out to the current word's boundaries. Selection = Cursor; Selection->Selection = Cursor->Cursor; if (Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); char16 *Text = Cursor->Text() + Base; while (Text[Cursor->Cursor]) { char16 c = Text[Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } if (Selection->Text()) { ssize_t Base = Selection->GetTextStart(); char16 *Sel = Selection->Text() + Base; while (Selection->Selection > 0) { char16 c = Sel[Selection->Selection - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Selection->Selection--; } } Invalidate(); SendNotify(LNotifySelectionChanged); } } else if (Hit.NearestText) { d->WordSelectMode = false; UnSelectAll(); Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("StartSelect Near='%20S' Idx=%i\n", Hit.NearestText->Text(), Hit.Index); #endif OnCursorChanged(); SendNotify(LNotifySelectionChanged); } else { #if DEBUG_SELECTION LgiTrace("StartSelect no text hit %p, %p\n", Cursor, Selection); #endif } } if (Hit.NearestText && Hit.Near == 0) { TagProcessedClick = Hit.NearestText->OnMouseClick(m); } else if (Hit.Direct) { TagProcessedClick = Hit.Direct->OnMouseClick(m); } #ifdef _DEBUG else if (m.Left() && m.Ctrl()) { LgiMsg(this, "No tag under the cursor.", GetClass()); } #endif if (!TagProcessedClick && m.IsContextMenu()) { LSubMenu RClick; enum ContextMenuCmds { IDM_DUMP = 100, IDM_COPY_SRC, IDM_VIEW_SRC, IDM_EXTERNAL, IDM_COPY, IDM_VIEW_IMAGES, }; #define IDM_CHARSET_BASE 10000 RClick.AppendItem (LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); LMenuItem *Vs = RClick.AppendItem (LLoadString(L_VIEW_SOURCE, "View Source"), IDM_VIEW_SRC, Source != 0); RClick.AppendItem (LLoadString(L_COPY_SOURCE, "Copy Source"), IDM_COPY_SRC, Source != 0); LMenuItem *Load = RClick.AppendItem (LLoadString(L_VIEW_IMAGES, "View External Images"), IDM_VIEW_IMAGES, true); if (Load) Load->Checked(GetLoadImages()); RClick.AppendItem (LLoadString(L_VIEW_IN_DEFAULT_BROWSER, "View in Default Browser"), IDM_EXTERNAL, Source != 0); LSubMenu *Cs = RClick.AppendSub (LLoadString(L_CHANGE_CHARSET, "Change Charset")); if (Cs) { int n=0; for (LCharset *c = LGetCsList(); c->Charset; c++, n++) { Cs->AppendItem(c->Charset, IDM_CHARSET_BASE + n, c->IsAvailable()); } } if (!GetReadOnly() || // Is editor #ifdef _DEBUG 1 #else 0 #endif ) { RClick.AppendSeparator(); RClick.AppendItem("Dump Layout", IDM_DUMP, Tag != 0); } if (Vs) { Vs->Checked(!IsHtml); } if (OnContextMenuCreate(Hit, RClick) && GetMouse(m, true)) { int Id = RClick.Float(this, m.x, m.y); switch (Id) { case IDM_COPY: { Copy(); break; } case IDM_VIEW_SRC: { if (Vs) { DeleteObj(Tag); IsHtml = !IsHtml; ParseDocument(Source); } break; } case IDM_COPY_SRC: { if (Source) { LClipBoard c(this); const char *ViewCs = GetCharset(); if (ViewCs) { LAutoWString w((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); if (w) c.TextW(w); } else c.Text(Source); } break; } case IDM_VIEW_IMAGES: { SetLoadImages(!GetLoadImages()); break; } case IDM_DUMP: { if (Tag) { LAutoWString s = Tag->DumpW(); if (s) { LClipBoard c(this); c.TextW(s); } } break; } case IDM_EXTERNAL: { if (!Source) { LgiTrace("%s:%i - No HTML source code.\n", _FL); break; } char Path[MAX_PATH_LEN]; if (!LGetSystemPath(LSP_TEMP, Path, sizeof(Path))) { LgiTrace("%s:%i - Failed to get the system path.\n", _FL); break; } char f[32]; sprintf_s(f, sizeof(f), "_%i.html", LRand(1000000)); LMakePath(Path, sizeof(Path), Path, f); LFile F; if (!F.Open(Path, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path); break; } LStringPipe Ex; bool Error = false; F.SetSize(0); LAutoWString SrcMem; const char *ViewCs = GetCharset(); if (ViewCs) SrcMem.Reset((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); else SrcMem.Reset(Utf8ToWide(Source)); for (char16 *s=SrcMem; s && *s;) { char16 *cid = StristrW(s, L"cid:"); while (cid && !strchr("\'\"", cid[-1])) { cid = StristrW(cid+1, L"cid:"); } if (cid) { char16 Delim = cid[-1]; char16 *e = StrchrW(cid, Delim); if (e) { *e = 0; if (StrchrW(cid, '\n')) { *e = Delim; Error = true; break; } else { char File[MAX_PATH_LEN] = ""; if (Environment) { LDocumentEnv::LoadJob *j = Environment->NewJob(); if (j) { j->Uri.Reset(WideToUtf8(cid)); j->Env = Environment; j->Pref = LDocumentEnv::LoadJob::FmtFilename; j->UserUid = GetDocumentUid(); LDocumentEnv::LoadType Result = Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { if (j->Filename) strcpy_s(File, sizeof(File), j->Filename); } else if (Result == LDocumentEnv::LoadDeferred) { d->DeferredLoads++; } DeleteObj(j); } } *e = Delim; Ex.Push(s, cid - s); if (File[0]) { char *d; while ((d = strchr(File, '\\'))) { *d = '/'; } Ex.Push(L"file:///"); LAutoWString w(Utf8ToWide(File)); Ex.Push(w); } s = e; } } else { Error = true; break; } } else { Ex.Push(s); break; } } if (!Error) { int64 WideChars = Ex.GetSize() / sizeof(char16); LAutoWString w(Ex.NewStrW()); LAutoString u(WideToUtf8(w, WideChars)); if (u) F.Write(u, strlen(u)); F.Close(); LString Err; if (!LExecute(Path, NULL, NULL, &Err)) { LgiMsg( this, "Failed to open '%s'\n%s", LAppInst ? LAppInst->LBase::Name() : GetClass(), MB_OK, Path, Err.Get()); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { LCharset *c = LGetCsList() + (Id - IDM_CHARSET_BASE); if (c->Charset) { Charset = c->Charset; OverideDocCharset = true; char *Src = Source.Release(); _Delete(); _New(); Source.Reset(Src); ParseDocument(Source); Invalidate(); SendNotify(LNotifyCharsetChanged); } } else { OnContextMenuCommand(Hit, Id); } break; } } } } } else // Up Click { if (Selection && Cursor && Selection == Cursor && Selection->Selection == Cursor->Cursor) { Selection->Selection = -1; Selection = 0; SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("NoSelect on release\n"); #endif } } } void LHtml::OnLoad() { d->IsLoaded = true; SendNotify(LNotifyDocLoaded); } LTag *LHtml::GetTagByPos(int x, int y, ssize_t *Index, LPoint *LocalCoords, bool DebugLog) { LTag *Status = NULL; if (Tag) { if (DebugLog) LgiTrace("GetTagByPos starting...\n"); LTagHit Hit; Tag->GetTagByPos(Hit, x, y, 0, DebugLog); if (DebugLog) LgiTrace("GetTagByPos Hit=%s, %i, %i...\n\n", Hit.Direct ? Hit.Direct->Tag.Get() : 0, Hit.Index, Hit.Near); Status = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (Hit.NearestText && Hit.Near < 30) { if (Index) *Index = Hit.Index; if (LocalCoords) *LocalCoords = Hit.LocalCoords; } } return Status; } void LHtml::SetVScroll(int64 v) { if (!VScroll) return; if (Tag) Tag->ClearToolTips(); VScroll->Value(v); Invalidate(); } bool LHtml::OnMouseWheel(double Lines) { if (VScroll) SetVScroll(VScroll->Value() + (int64)Lines); return true; } LCursor LHtml::GetCursor(int x, int y) { int Offset = ScrollY(); ssize_t Index = -1; LPoint LocalCoords; LTag *Tag = GetTagByPos(x, y + Offset, &Index, &LocalCoords); if (Tag) { LString Uri; if (LocalCoords.x >= 0 && LocalCoords.y >= 0 && Tag->IsAnchor(&Uri)) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(x, y) && ValidStr(Uri)) { return LCUR_PointingHand; } } } return LCUR_Normal; } void LTag::ClearToolTips() { if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } for (auto c: Children) ToTag(c)->ClearToolTips(); } void LHtml::OnMouseMove(LMouse &m) { if (!Tag) return; int Offset = ScrollY(); LTagHit Hit; Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false); if (!Hit.Direct && !Hit.NearestText) return; LString Uri; LTag *HitTag = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (HitTag && HitTag->TipId == 0 && Hit.LocalCoords.x >= 0 && Hit.LocalCoords.y >= 0 && HitTag->IsAnchor(&Uri) && Uri) { if (!Tip.GetParent()) { Tip.Attach(this); } LRect r = HitTag->GetRect(false); r.Offset(0, -Offset); if (!HitTag->TipId) HitTag->TipId = Tip.NewTip(Uri, r); // LgiTrace("NewTip: %s @ %s, ID=%i\n", Uri.Get(), r.GetStr(), HitTag->TipId); } if (IsCapturing() && Cursor && Hit.NearestText) { if (!Selection) { Selection = Cursor; Selection->Selection = Cursor->Cursor; Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("CreateSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif } else if ((Cursor != Hit.NearestText) || (Cursor->Cursor != Hit.Index)) { // Move the cursor to track the mouse if (Cursor) { Cursor->Cursor = -1; } Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("ExtendSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif if (d->WordSelectMode && Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); if (IsCursorFirst()) { // Extend the cursor up the document to include the whole word while (Cursor->Cursor > 0) { char16 c = Cursor->Text()[Base + Cursor->Cursor - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor--; } } else { // Extend the cursor down the document to include the whole word while (Cursor->Text()[Base + Cursor->Cursor]) { char16 c = Cursor->Text()[Base + Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } } OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); } } } void LHtml::OnPulse() { if (VScroll && IsCapturing()) { int Fy = DefFont() ? DefFont()->GetHeight() : 16; LMouse m; if (GetMouse(m, false)) { LRect c = GetClient(); int Lines = 0; if (m.y < c.y1) { // Scroll up Lines = (c.y1 - m.y + Fy - 1) / -Fy; } else if (m.y > c.y2) { // Scroll down Lines = (m.y - c.y2 + Fy - 1) / Fy; } if (Lines && VScroll) SetVScroll(VScroll->Value() + Lines); } } } LRect *LHtml::GetCursorPos() { return &d->CursorPos; } void LHtml::SetCursorVis(bool b) { if (d->CursorVis ^ b) { d->CursorVis = b; Invalidate(); } } bool LHtml::GetCursorVis() { return d->CursorVis; } LDom *ElementById(LTag *t, char *id) { if (t && id) { const char *i; if (t->Get("id", i) && _stricmp(i, id) == 0) return t; for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(t->Children[i]); LDom *n = ElementById(c, id); if (n) return n; } } return 0; } LDom *LHtml::getElementById(char *Id) { return ElementById(Tag, Id); } bool LHtml::GetLinkDoubleClick() { return d->LinkDoubleClick; } void LHtml::SetLinkDoubleClick(bool b) { d->LinkDoubleClick = b; } bool LHtml::GetFormattedContent(const char *MimeType, LString &Out, LArray *Media) { if (!MimeType) { LAssert(!"No MIME type for getting formatted content"); return false; } if (!_stricmp(MimeType, "text/html")) { // We can handle this type... LArray Imgs; if (Media) { // Find all the image tags... Tag->Find(TAG_IMG, Imgs); // Give them CID's if they don't already have them for (unsigned i=0; iGet("src", Src) && !Img->Get("cid", Cid)) { char id[256]; sprintf_s(id, sizeof(id), "%x.%x", (unsigned)LCurrentTime(), (unsigned)LRand()); Img->Set("cid", id); Img->Get("cid", Cid); } if (Src && Cid) { LFile *f = new LFile; if (f) { if (f->Open(Src, O_READ)) { // Add the exported image stream to the media array LDocView::ContentMedia &m = Media->New(); m.Id = Cid; m.Stream.Reset(f); } } } } } // Export the HTML, including the CID's from the first step Out = Name(); } else if (!_stricmp(MimeType, "text/plain")) { // Convert DOM tree down to text instead... LStringPipe p(512); if (Tag) { LTag::TextConvertState State(&p); Tag->ConvertToText(State); } Out = p.NewGStr(); } return false; } void LHtml::OnContent(LDocumentEnv::LoadJob *Res) { if (JobSem.Lock(_FL)) { JobSem.Jobs.Add(Res); JobSem.Unlock(); PostEvent(M_JOBS_LOADED); } } LHtmlElement *LHtml::CreateElement(LHtmlElement *Parent) { return new LTag(this, Parent); } bool LHtml::GetVariant(const char *Name, LVariant &Value, const char *Array) { if (!_stricmp(Name, "supportLists")) // Type: Bool Value = false; else if (!_stricmp(Name, "vml")) // Type: Bool // Vector Markup Language Value = false; else if (!_stricmp(Name, "mso")) // Type: Bool // mso = Microsoft Office Value = false; else return false; return true; } bool LHtml::EvaluateCondition(const char *Cond) { if (!Cond) return true; // This is a really bad attempt at writing an expression evaluator. // I could of course use the scripting language but that would pull // in a fairly large dependency on the HTML control. However user // apps that already have that could reimplement this virtual function // if they feel like it. LArray Str; for (const char *c = Cond; *c; ) { if (IsAlpha(*c)) { Str.Add(LTokStr(c)); } else if (IsWhiteSpace(*c)) { c++; } else { const char *e = c; while (*e && !IsWhiteSpace(*e) && !IsAlpha(*e)) e++; Str.Add(NewStr(c, e - c)); LAssert(e > c); if (e > c) c = e; else break; } } bool Result = true; bool Not = false; for (unsigned i=0; iGetAnchor(Name); if (a) { if (VScroll) { int LineY = GetFont()->GetHeight(); int Ay = a->AbsY(); int Scr = Ay / LineY; SetVScroll(Scr); VScroll->SendNotify(); } else d->OnLoadAnchor.Reset(NewStr(Name)); } } return false; } bool LHtml::GetEmoji() { return d->DecodeEmoji; } void LHtml::SetEmoji(bool i) { d->DecodeEmoji = i; } void LHtml::SetMaxPaintTime(int Ms) { d->MaxPaintTime = Ms; } bool LHtml::GetMaxPaintTimeout() { return d->MaxPaintTimeout; } //////////////////////////////////////////////////////////////////////// class LHtml_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LHtml") == 0) { return new LHtml(-1, 0, 0, 100, 100, new LDefaultDocumentEnv); } return 0; } } LHtml_Factory; ////////////////////////////////////////////////////////////////////// struct BuildContext { LHtmlTableLayout *Layout; LTag *Table; LTag *TBody; LTag *CurTr; LTag *CurTd; int cx, cy; BuildContext() { Layout = NULL; cx = cy = 0; Table = NULL; TBody = NULL; CurTr = NULL; CurTd = NULL; } bool Build(LTag *t, int Depth) { bool RetReattach = false; switch (t->TagId) { case TAG_TABLE: { if (!Table) Table = t; else return false; break; } case TAG_TBODY: { if (TBody) return false; TBody = t; break; } case TAG_TR: { CurTr = t; break; } case TAG_TD: { CurTd = t; if (t->Parent != CurTr) { if ( !CurTr && (Table || TBody) ) { LTag *p = TBody ? TBody : Table; CurTr = new LTag(p->Html, p); if (CurTr) { CurTr->Tag.Reset(NewStr("tr")); CurTr->TagId = TAG_TR; ssize_t Idx = t->Parent->Children.IndexOf(t); t->Parent->Attach(CurTr, Idx); } } if (CurTr) { CurTr->Attach(t); RetReattach = true; } else { LAssert(0); return false; } } t->Cell->Pos.x = cx; t->Cell->Pos.y = cy; Layout->Set(t); break; } default: { if (CurTd == t->Parent) return false; break; } } for (unsigned n=0; nChildren.Length(); n++) { LTag *c = ToTag(t->Children[n]); bool Reattached = Build(c, Depth+1); if (Reattached) n--; } if (t->TagId == TAG_TR) { CurTr = NULL; cy++; cx = 0; Layout->s.y = cy; } if (t->TagId == TAG_TD) { CurTd = NULL; cx += t->Cell->Span.x; Layout->s.x = MAX(cx, Layout->s.x); } return RetReattach; } }; LHtmlTableLayout::LHtmlTableLayout(LTag *table) { Table = table; if (!Table) return; #if 0 BuildContext Ctx; Ctx.Layout = this; Ctx.Build(table, 0); #else int y = 0; LTag *FakeRow = 0; LTag *FakeCell = 0; LTag *r; for (size_t i=0; iChildren.Length(); i++) { r = ToTag(Table->Children[i]); if (r->Display() == LCss::DispNone) continue; if (r->TagId == TAG_TR) { FakeRow = 0; FakeCell = 0; } else if (r->TagId == TAG_TBODY) { ssize_t Index = Table->Children.IndexOf(r); for (size_t n=0; nChildren.Length(); n++) { LTag *t = ToTag(r->Children[n]); Table->Children.AddAt(++Index, t); t->Parent = Table; /* LgiTrace("Moving '%s'(%p) from TBODY(%p) into '%s'(%p)\n", t->Tag, t, r, t->Parent->Tag, t->Parent); */ } r->Children.Length(0); } else { if (!FakeRow) { if ((FakeRow = new LTag(Table->Html, 0))) { FakeRow->Tag.Reset(NewStr("tr")); FakeRow->TagId = TAG_TR; ssize_t Idx = Table->Children.IndexOf(r); Table->Attach(FakeRow, Idx); } } if (FakeRow) { if (!IsTableCell(r->TagId) && !FakeCell) { if ((FakeCell = new LTag(Table->Html, FakeRow))) { FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } } } ssize_t Idx = Table->Children.IndexOf(r); r->Detach(); if (IsTableCell(r->TagId)) { FakeRow->Attach(r); } else { LAssert(FakeCell != NULL); FakeCell->Attach(r); } i = Idx - 1; } } } FakeCell = NULL; for (size_t n=0; nChildren.Length(); n++) { LTag *r = ToTag(Table->Children[n]); if (r->TagId == TAG_TR) { int x = 0; for (size_t i=0; iChildren.Length(); i++) { LTag *cell = ToTag(r->Children[i]); if (!IsTableCell(cell->TagId)) { if (!FakeCell) { // Make a fake TD cell FakeCell = new LTag(Table->Html, NULL); FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } // Join the fake TD into the TR r->Children[i] = FakeCell; FakeCell->Parent = r; } else { // Not the first non-TD tag, so delete it from the TR. Only the // fake TD will remain in the TR. r->Children.DeleteAt(i--, true); } // Insert the tag into it as a child FakeCell->Children.Add(cell); cell->Parent = FakeCell; cell = FakeCell; } else { FakeCell = NULL; } if (IsTableCell(cell->TagId)) { if (cell->Display() == LCss::DispNone) continue; while (Get(x, y)) { x++; } cell->Cell->Pos.x = x; cell->Cell->Pos.y = y; Set(cell); x += cell->Cell->Span.x; } } y++; FakeCell = NULL; } } #endif } void LHtmlTableLayout::Dump() { int Sx, Sy; GetSize(Sx, Sy); LgiTrace("Table %i x %i cells.\n", Sx, Sy); for (int x=0; xCell->Pos.x, t->Cell->Pos.y, t->Cell->Span.x, t->Cell->Span.y); LgiTrace("%-10s", s); } LgiTrace("\n"); } LgiTrace("\n"); } void LHtmlTableLayout::GetAll(List &All) { LHashTbl, bool> Added; for (size_t y=0; y= (int) c.Length()) return NULL; CellArray &a = c[y]; if (x >= (int) a.Length()) return NULL; return a[x]; } bool LHtmlTableLayout::Set(LTag *t) { if (!t) return false; for (int y=0; yCell->Span.y; y++) { for (int x=0; xCell->Span.x; x++) { // LAssert(!c[y][x]); c[t->Cell->Pos.y + y][t->Cell->Pos.x + x] = t; } } return true; } void LTagHit::Dump(const char *Desc) { LArray d, n; LTag *t = Direct; unsigned i; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { d.AddAt(0, t); } t = NearestText; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { n.AddAt(0, t); } LgiTrace("Hit: %s Direct: ", Desc); for (i=0; i%s", d[i]->Tag ? d[i]->Tag.Get() : "CONTENT"); LgiTrace(" Nearest: "); for (i=0; i%s", n[i]->Tag ? n[i]->Tag.Get() : "CONTENT"); LgiTrace(" Local: %ix%i Index: %i Block: %s '%.10S'\n", LocalCoords.x, LocalCoords.y, Index, Block ? Block->GetStr() : NULL, Block ? Block->Text + Index : NULL); } diff --git a/src/common/Text/TextConvert.cpp b/src/common/Text/TextConvert.cpp --- a/src/common/Text/TextConvert.cpp +++ b/src/common/Text/TextConvert.cpp @@ -1,387 +1,387 @@ -#include "lgi/common/Lgi.h" -#include "lgi/common/TextConvert.h" -#include "lgi/common/Mime.h" -#include "lgi/common/Base64.h" +#include "lgi/common/Lgi.h" +#include "lgi/common/TextConvert.h" +#include "lgi/common/Mime.h" +#include "lgi/common/Base64.h" #include "lgi/common/Charset.h" - -// return true if there are any characters with the 0x80 bit set -bool Is8Bit(char *Text) -{ - if (!Text) - return false; - - while (*Text) - { - if (*Text & 0x80) - return true; - Text++; - } - - return false; -} - -char ConvHexToBin(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c + 10 - 'a'; - if (c >= 'A' && c <= 'F') - return c + 10 - 'A'; - return 0; -} - -char *DecodeBase64Str(char *Str, ssize_t Len) -{ - if (Str) - { - ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; - ssize_t BinLen = BufferLen_64ToBin(B64Len); - char *s = new char[BinLen+1]; - if (s) - { - ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); - s[Converted] = 0; - DeleteArray(Str); - Str = s; - } - } - return Str; -} - -char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) -{ - if (Str) - { - if (Len < 0) Len = strlen(Str); - uchar *s = new uchar[Len+1]; - if (s) - { - char *Out = (char*) s; - char *Text = Str; - - for (int i=0; i s) - p.Write(s, e - s); - break; - } - e++; - } - - if (Decode) - { - // is there a word remaining - bool Encoded = false; - char *Start = e + 2; - char *First = strchr(Start, '?'); - char *Second = First ? strchr(First + 1, '?') : NULL; - char *End = Second ? strstr(Second + 1, "?=") : NULL; - if (End) - { - LString Cp(Start, First - Start); - int Type = CONTENT_NONE; - bool StripUnderscores = false; - if (ToUpper(First[1]) == 'B') - { - // Base64 encoding - Type = CONTENT_BASE64; - } - else if (ToUpper(First[1]) == 'Q') - { - // Quoted printable - Type = CONTENT_QUOTED_PRINTABLE; - StripUnderscores = true; - } - - if (Type != CONTENT_NONE) - { - Second++; - char *Block = NewStr(Second, End-Second); - if (Block) - { - switch (Type) - { - case CONTENT_BASE64: - Block = DecodeBase64Str(Block); - break; - case CONTENT_QUOTED_PRINTABLE: - Block = DecodeQuotedPrintableStr(Block); - break; - } - - size_t Len = strlen(Block); - if (StripUnderscores) - { - for (char *i=Block; *i; i++) - { - if (*i == '_') - *i = ' '; - } - } - - if (Cp && !_stricmp(Cp, "utf-8")) - { - p.Write((uchar*)Block, Len); - } - else - { - auto Inst = LCharsetSystem::Inst(); - LString Detect = Inst && Inst->DetectCharset ? Inst->DetectCharset(LString(Block, Len)) : NULL; - - LAutoString Utf8((char*)LNewConvertCp("utf-8", Block, Detect ? Detect : Cp, Len)); - if (Utf8) - { - if (LIsUtf8(Utf8)) - p.Write((uchar*)Utf8.Get(), strlen(Utf8)); - } - else - { - p.Write((uchar*)Block, Len); - } - } - - DeleteArray(Block); - } - - s = End + 2; - if (*s == '\n') - { - s++; - while (*s && strchr(WhiteSpace, *s)) s++; - } - - Encoded = true; - } - } - - if (!Encoded) - { - // Encoding error, just emit the raw string and exit. - size_t Len = strlen(s); - p.Write((uchar*) s, Len); - break; - } - } - else if (Descape) - { - // Un-escape the string... - e++; - if (*e) - p.Write(e, 1); - else - break; - s = e + 1; - } - else - { - // Last segment of string... - LAssert(*e == 0); - if (e > s) - p.Write(s, e - s); - break; - } - } - - DeleteArray(Str); - return p.NewStr(); -} - -#define MIME_MAX_LINE 76 - -char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength) -{ - if (!CodePage) - { - CodePage = "utf-8"; - } - - LStringPipe p(256); - - if (!Str) - return NULL; - - if (Is8Bit(Str)) - { - // pick an encoding - bool Base64 = false; - const char *DestCp = "utf-8"; - size_t Len = strlen(Str);; - if (_stricmp(CodePage, "utf-8") == 0) - { - DestCp = LUnicodeToCharset(Str, Len, CharsetPrefs); - } - - int Chars = 0; - for (unsigned i=0; i 0 && - ((double)Chars/Len) > 0.4 - ) - ) - { - Base64 = true; - } - - char *Buf = (char*)LNewConvertCp(DestCp, Str, CodePage, Len); - if (Buf) - { - // encode the word - char Prefix[64]; - int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); - p.Write(Prefix, Ch); - LineLength += Ch; - - if (Base64) - { - // Base64 - size_t InLen = strlen(Buf); - // int EstBytes = BufferLen_BinTo64(InLen); - - char Temp[512]; - ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); - p.Push(Temp, Bytes); - } - else - { - // Quoted printable - for (char *w = Buf; *w; w++) - { - if (*w == ' ') - { - if (LineLength > MIME_MAX_LINE - 3) - { - p.Print("?=\r\n\t%s", Prefix); - LineLength = 1 + strlen(Prefix); - } - - p.Write((char*)"_", 1); - LineLength++; - } - else if (*w & 0x80 || - *w == '_' || - *w == '?' || - *w == '=') - { - if (LineLength > MIME_MAX_LINE - 5) - { - p.Print("?=\r\n\t%s", Prefix); - LineLength = 1 + strlen(Prefix); - } - - char Temp[16]; - Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); - p.Write(Temp, Ch); - LineLength += Ch; - } - else - { - if (LineLength > MIME_MAX_LINE - 3) - { - p.Print("?=\r\n\t%s", Prefix); - LineLength = 1 + strlen(Prefix); - } - - p.Write(w, 1); - LineLength++; - } - } - } - - p.Push("?="); - DeleteArray(Buf); - } - - DeleteArray(Str); - Str = p.NewStr(); - } - else - { - bool RecodeNewLines = false; - for (char *s = Str; *s; s++) - { - if (*s == '\n' && (s == Str || s[-1] != '\r')) - { - RecodeNewLines = true; - break; - } - } - - if (RecodeNewLines) - { - for (char *s = Str; *s; s++) - { - if (*s == '\r') - ; - else if (*s == '\n') - p.Write("\r\n", 2); - else - p.Write(s, 1); - } - - DeleteArray(Str); - Str = p.NewStr(); - } - } - - return Str; -} - + +// return true if there are any characters with the 0x80 bit set +bool Is8Bit(char *Text) +{ + if (!Text) + return false; + + while (*Text) + { + if (*Text & 0x80) + return true; + Text++; + } + + return false; +} + +char ConvHexToBin(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c + 10 - 'a'; + if (c >= 'A' && c <= 'F') + return c + 10 - 'A'; + return 0; +} + +char *DecodeBase64Str(char *Str, ssize_t Len) +{ + if (Str) + { + ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; + ssize_t BinLen = BufferLen_64ToBin(B64Len); + char *s = new char[BinLen+1]; + if (s) + { + ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); + s[Converted] = 0; + DeleteArray(Str); + Str = s; + } + } + return Str; +} + +char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) +{ + if (Str) + { + if (Len < 0) Len = strlen(Str); + uchar *s = new uchar[Len+1]; + if (s) + { + char *Out = (char*) s; + char *Text = Str; + + for (int i=0; i s) + p.Write(s, e - s); + break; + } + e++; + } + + if (Decode) + { + // is there a word remaining + bool Encoded = false; + char *Start = e + 2; + char *First = strchr(Start, '?'); + char *Second = First ? strchr(First + 1, '?') : NULL; + char *End = Second ? strstr(Second + 1, "?=") : NULL; + if (End) + { + LString Cp(Start, First - Start); + int Type = CONTENT_NONE; + bool StripUnderscores = false; + if (ToUpper(First[1]) == 'B') + { + // Base64 encoding + Type = CONTENT_BASE64; + } + else if (ToUpper(First[1]) == 'Q') + { + // Quoted printable + Type = CONTENT_QUOTED_PRINTABLE; + StripUnderscores = true; + } + + if (Type != CONTENT_NONE) + { + Second++; + char *Block = NewStr(Second, End-Second); + if (Block) + { + switch (Type) + { + case CONTENT_BASE64: + Block = DecodeBase64Str(Block); + break; + case CONTENT_QUOTED_PRINTABLE: + Block = DecodeQuotedPrintableStr(Block); + break; + } + + size_t Len = strlen(Block); + if (StripUnderscores) + { + for (char *i=Block; *i; i++) + { + if (*i == '_') + *i = ' '; + } + } + + if (Cp && !_stricmp(Cp, "utf-8")) + { + p.Write((uchar*)Block, Len); + } + else + { + auto Inst = LCharsetSystem::Inst(); + LString Detect = Inst && Inst->DetectCharset ? Inst->DetectCharset(LString(Block, Len)) : NULL; + + LAutoString Utf8((char*)LNewConvertCp("utf-8", Block, Detect ? Detect : Cp, Len)); + if (Utf8) + { + if (LIsUtf8(Utf8)) + p.Write((uchar*)Utf8.Get(), strlen(Utf8)); + } + else + { + p.Write((uchar*)Block, Len); + } + } + + DeleteArray(Block); + } + + s = End + 2; + if (*s == '\n') + { + s++; + while (*s && strchr(WhiteSpace, *s)) s++; + } + + Encoded = true; + } + } + + if (!Encoded) + { + // Encoding error, just emit the raw string and exit. + size_t Len = strlen(s); + p.Write((uchar*) s, Len); + break; + } + } + else if (Descape) + { + // Un-escape the string... + e++; + if (*e) + p.Write(e, 1); + else + break; + s = e + 1; + } + else + { + // Last segment of string... + LAssert(*e == 0); + if (e > s) + p.Write(s, e - s); + break; + } + } + + DeleteArray(Str); + return p.NewStr(); +} + +#define MIME_MAX_LINE 76 + +char *EncodeRfc2047(char *Str, const char *CodePage, List *CharsetPrefs, ssize_t LineLength) +{ + if (!CodePage) + { + CodePage = "utf-8"; + } + + LStringPipe p(256); + + if (!Str) + return NULL; + + if (Is8Bit(Str)) + { + // pick an encoding + bool Base64 = false; + const char *DestCp = "utf-8"; + size_t Len = strlen(Str);; + if (_stricmp(CodePage, "utf-8") == 0) + { + DestCp = LUnicodeToCharset(Str, Len, CharsetPrefs); + } + + int Chars = 0; + for (unsigned i=0; i 0 && + ((double)Chars/Len) > 0.4 + ) + ) + { + Base64 = true; + } + + char *Buf = (char*)LNewConvertCp(DestCp, Str, CodePage, Len); + if (Buf) + { + // encode the word + char Prefix[64]; + int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCp, Base64 ? 'B' : 'Q'); + p.Write(Prefix, Ch); + LineLength += Ch; + + if (Base64) + { + // Base64 + size_t InLen = strlen(Buf); + // int EstBytes = BufferLen_BinTo64(InLen); + + char Temp[512]; + ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); + p.Push(Temp, Bytes); + } + else + { + // Quoted printable + for (char *w = Buf; *w; w++) + { + if (*w == ' ') + { + if (LineLength > MIME_MAX_LINE - 3) + { + p.Print("?=\r\n\t%s", Prefix); + LineLength = 1 + strlen(Prefix); + } + + p.Write((char*)"_", 1); + LineLength++; + } + else if (*w & 0x80 || + *w == '_' || + *w == '?' || + *w == '=') + { + if (LineLength > MIME_MAX_LINE - 5) + { + p.Print("?=\r\n\t%s", Prefix); + LineLength = 1 + strlen(Prefix); + } + + char Temp[16]; + Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); + p.Write(Temp, Ch); + LineLength += Ch; + } + else + { + if (LineLength > MIME_MAX_LINE - 3) + { + p.Print("?=\r\n\t%s", Prefix); + LineLength = 1 + strlen(Prefix); + } + + p.Write(w, 1); + LineLength++; + } + } + } + + p.Push("?="); + DeleteArray(Buf); + } + + DeleteArray(Str); + Str = p.NewStr(); + } + else + { + bool RecodeNewLines = false; + for (char *s = Str; *s; s++) + { + if (*s == '\n' && (s == Str || s[-1] != '\r')) + { + RecodeNewLines = true; + break; + } + } + + if (RecodeNewLines) + { + for (char *s = Str; *s; s++) + { + if (*s == '\r') + ; + else if (*s == '\n') + p.Write("\r\n", 2); + else + p.Write(s, 1); + } + + DeleteArray(Str); + Str = p.NewStr(); + } + } + + return Str; +} + diff --git a/src/common/Text/TextView3.cpp b/src/common/Text/TextView3.cpp --- a/src/common/Text/TextView3.cpp +++ b/src/common/Text/TextView3.cpp @@ -1,5411 +1,5442 @@ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #ifdef WIN32 #include #endif #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #undef max #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms #define PULSE_TIMEOUT 500 // ms #define CURSOR_BLINK 1000 // ms #define ALLOC_BLOCK 64 #define IDC_VS 1000 #ifdef WINDOWS #define DOUBLE_BUFFER_PAINT 1 #endif enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class LDocFindReplaceParams3 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; bool SearchUpwards; LDocFindReplaceParams3() : LMutex("LDocFindReplaceParams3") { MatchCase = false; MatchWord = false; SelectionOnly = false; SearchUpwards = false; } }; class LTextView3Private : public LCss, public LMutex { public: LTextView3 *View; LRect rPadding; int PourX; bool LayoutDirty; ssize_t DirtyStart, DirtyLen; LColour UrlColour; bool CenterCursor; ssize_t WordSelectMode; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache; // Find/Replace Params bool OwnFindReplaceParams; LDocFindReplaceParams3 *FindReplaceParams; // Map buffer ssize_t MapLen; char16 *MapBuf; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView3Private(LTextView3 *view) : LMutex("LTextView3Private") { View = view; WordSelectMode = -1; PourX = -1; VScrollCache = -1; DirtyStart = DirtyLen = 0; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); CenterCursor = false; LayoutDirty = true; rPadding.ZOff(0, 0); MapBuf = 0; MapLen = 0; OwnFindReplaceParams = true; FindReplaceParams = new LDocFindReplaceParams3; } ~LTextView3Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView3Undo : public LUndoEvent { LTextView3 *View; LArray Changes; LTextView3Undo(LTextView3 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView3::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView3::LTextView3( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView3Private(this)); TabSize = TAB_SIZE; IndentSize = TAB_SIZE; // setup window SetId(Id); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else #endif d->Padding(LCss::Len(LCss::LenPx, 2)); #ifdef _DEBUG // debug times _PourTime = 0; _StyleTime = 0; _PaintTime = 0; #endif // Data Alloc = ALLOC_BLOCK; Text = new char16[Alloc]; if (Text) *Text = 0; Cursor = 0; Size = 0; // Display if (FontType) Font = FontType->Create(); else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView3::~LTextView3() { #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView3::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView3::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView3::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView3::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView3::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView3::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); CanScrollX = i != TEXTED_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView3::GetFont() { return Font; } LFont *LTextView3::GetBold() { return Bold; } void LTextView3::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView3::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } void LTextView3::LogLines() { int Idx = 0; LgiTrace("DocSize: %i\n", (int)Size); for (auto i : Line) { LgiTrace(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr()); Idx++; } #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif } bool LTextView3::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto i : Line) { LTextLine *l = i; if (l->Start != Pos) { LogLines(); LAssert(!"Incorrect start."); return false; } char16 *e = c; if (WrapType == TEXTED_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { LogLines(); LAssert(!"Incorrect length."); return false; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { LogLines(); LAssert(!"Lines not joined vertically"); } if (*e) { if (*e == '\n') e++; else if (WrapType == TEXTED_WRAP_REFLOW) e++; } Pos = e - Text; c = e; Idx++; Prev = l; } if (WrapType == TEXTED_WRAP_NONE && Pos != Size) { LogLines(); LAssert(!"Last line != end of doc"); return false; } return true; } int LTextView3::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView3::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif #if !defined(HAIKU) LAssert(InThread()); #endif LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); // LgiTrace("Pour %i:%i Cur=%p Idx=%i\n", (int)Start, (int)Length, (int)Cur, (int)Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (auto i = Idx >= 0 ? Line.begin(Idx) : Line.rbegin(); *i; i--, Idx--) { Cur = *i; if (Cur->r.Valid()) { Cy = Cur->r.y1; if (Idx < 0) Idx = Line.IndexOf(Cur); break; } } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; // LgiTrace("Reset start to %i:%i because Cur!=NULL\n", (int)Start, (int)Length); } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; //int LastX = 0; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); if (WrapType == TEXTED_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (auto i = Line.begin(Idx); *i; i++, Idx++) { LTextLine *l = *i; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine; l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } Line.Insert(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewGStr(); #endif PartialPour = false; PartialPourLines = 0; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); Cur = NULL; } int Cx = 0; ssize_t i; for (i=Start; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine; if (l) { l->Start = i; l->Len = e - i; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) { PartialPour = false; PartialPourLines = 0; } SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG // ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || Last->Start + Last->Len < Size) { LTextLine *l = new LTextLine; if (l) { l->Start = Size; l->Len = 0; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { #if 0 LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); #endif SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView3::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView3::LStyle *LTextView3::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView3::LStyle *LTextView3::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView3::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; // Url->Email = Inf.Email; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView3::Insert(size_t At, const char16 *Data, ssize_t Len) { LProfile Prof("LTextView3::Insert"); Prof.HideResultsIfBelow(1000); LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } Prof.Add("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16)); Prof.Add("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate Prof.Add("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Insert(Cur = new LTextLine); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { if (WrapType == TEXTED_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); Prof.Add("NoWrap add lines"); // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; // Create a new line... Cur = new LTextLine(); if (!Cur) return false; Cur->Start = Pos + 1; Line.Insert(Cur, ++Idx); } } Prof.Add("CalcLen"); // Make sure the last Line's length is set.. Cur->CalcLen(Text); Prof.Add("UpdatePos"); // Now update all the positions of the following lines... for (auto i = Line.begin(++Idx); *i; i++) (*i)->Start += Len; } else { // Clear all lines to the end of the doc... for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode if (WrapType == TEXTED_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG // Prof.Add("Validate"); // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { Prof.Add("PourText"); PourText(At, Len); Prof.Add("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView3::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; if (WrapType == TEXTED_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (auto i = Line.begin(Idx + 1); *i; i++) (*i)->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (auto i = Line.begin(Index); *i; i++) delete *i; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change if (WrapType == TEXTED_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView3::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } List::I LTextView3::GetTextLineIt(ssize_t Offset, ssize_t *Index) { int i = 0; for (auto It = Line.begin(); It != Line.end(); It++) { auto l = *It; if (Offset >= l->Start && Offset <= l->Start+l->Len) { if (Index) *Index = i; return It; } i++; } return Line.end(); } int64 LTextView3::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView3::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView3::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView3::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView3::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView3::NameW() { return Text; } const char16 *LTextView3::TextAtLine(size_t Index) { if (Index >= Line.Length()) return NULL; auto ln = Line[Index]; return Text + ln->Start; } bool LTextView3::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView3::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView3::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView3::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView3::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView3::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView3::GetLines() { return Line.Length(); } void LTextView3::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView3::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView3::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView3::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView3::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; auto SLine = GetTextLine(Start); auto ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView3::SetBorder(int b) { } bool LTextView3::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView3::Copy() { bool Status = true; +printf("txt copy\n"); + if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView3::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } -bool LTextView3::ClearDirty(bool Ask, const char *FileName) +void LTextView3::ClearDirty(std::function OnStatus, bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { - LFileSelect Select; - Select.Parent(this); - if (!FileName && - Select.Save()) + auto DoSave = [&](bool ok) + { + Save(FileName); + if (OnStatus) + OnStatus(ok); + }; + + if (!FileName) { - FileName = Select.Name(); + LFileSelect *Select = new LFileSelect; + Select->Parent(this); + Select->Save([&FileName, &DoSave](auto Select, auto ok) + { + if (ok) + FileName = Select->Name(); + DoSave(ok); + delete Select; + }); } - - Save(FileName); + else DoSave(true); } else if (Answer == IDCANCEL) { - return false; + if (OnStatus) + OnStatus(false); + return; } } - return true; + if (OnStatus) + OnStatus(true); } bool LTextView3::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView3::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView3::GetLastError() { return d->LastError; } void LTextView3::UpdateScrollBars(bool Reset) { if (!VScroll) return; LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = std::max(PartialPourLines, Line.Length()); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } -bool LTextView3::DoCase(bool Upper) +void LTextView3::DoCase(std::function Callback, bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } - return true; + if (Callback) + Callback(Text != NULL); } ssize_t LTextView3::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } -void LTextView3::SetLine(int i, bool select) +void LTextView3::SetLine(int64_t i, bool select) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, select); d->CenterCursor = false; } } -bool LTextView3::DoGoto() +void LTextView3::DoGoto(std::function Callback) { - LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); - if (Dlg.DoModal() == IDOK && - Dlg.GetStr()) + LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); + Dlg->DoModal([this, Dlg, Callback](auto d, auto code) { - SetLine(atoi(Dlg.GetStr())); - } - - return true; + auto ok = code == IDOK && Dlg->GetStr(); + if (ok) + SetLine(Dlg->GetStr().Int()); + if (Callback) + Callback(ok); + delete Dlg; + }); } LDocFindReplaceParams *LTextView3::CreateFindReplaceParams() { return new LDocFindReplaceParams3; } void LTextView3::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (LDocFindReplaceParams3*) Params; } } -bool LTextView3::DoFindNext() +void LTextView3::DoFindNext(std::function OnStatus) { bool Status = false; - if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } - - return Status; + if (OnStatus) + OnStatus(Status); } -bool -Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User) -{ - LTextView3 *v = (LTextView3*) User; - - if (v->d->FindReplaceParams && - v->d->FindReplaceParams->Lock(_FL)) - { - v->d->FindReplaceParams->MatchWord = Dlg->MatchWord; - v->d->FindReplaceParams->MatchCase = Dlg->MatchCase; - v->d->FindReplaceParams->SelectionOnly = Dlg->SelectionOnly; - v->d->FindReplaceParams->SearchUpwards = Dlg->SearchUpwards; - v->d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg->Find)); - - v->d->FindReplaceParams->Unlock(); - } - - return v->DoFindNext(); -} - -bool LTextView3::DoFind() +void LTextView3::DoFind(std::function Callback) { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } - LFindDlg Dlg(this, u, Text3_FindCallback, this); - Dlg.DoModal(); - Focus(true); - - return false; + LFindDlg Dlg(this, [View=this, Params=d->FindReplaceParams, Callback](LFindReplaceCommon *Dlg, int Action) + { + if (Params && + Params->Lock(_FL)) + { + Params->MatchWord = Dlg->MatchWord; + Params->MatchCase = Dlg->MatchCase; + Params->SelectionOnly = Dlg->SelectionOnly; + Params->SearchUpwards = Dlg->SearchUpwards; + Params->LastFind.Reset(Utf8ToWide(Dlg->Find)); + + Params->Unlock(); + } + + View->DoFindNext([View, Callback](bool ok) + { + View->Focus(true); + if (Callback) + Callback(ok); + }); + }, u); + + Dlg.DoModal(NULL); } -bool LTextView3::DoReplace() +void LTextView3::DoReplace(std::function Callback) { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } char *LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind); char *LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace); - LReplaceDlg Dlg(this, LastFind8, LastReplace8); + LReplaceDlg Dlg(this, + [&](LFindReplaceCommon *Dlg, int Action) + { + LReplaceDlg *Replace = dynamic_cast(Dlg); + LAssert(Replace != NULL); + + DeleteArray(LastFind8); + DeleteArray(LastReplace8); + + if (Action == IDCANCEL) + return; + + if (d->FindReplaceParams->Lock(_FL)) + { + d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find)); + d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace)); + d->FindReplaceParams->MatchWord = Replace->MatchWord; + d->FindReplaceParams->MatchCase = Replace->MatchCase; + d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly; + + switch (Action) + { + case IDC_FR_FIND: + { + OnFind( d->FindReplaceParams->LastFind, + d->FindReplaceParams->MatchWord, + d->FindReplaceParams->MatchCase, + d->FindReplaceParams->SelectionOnly, + d->FindReplaceParams->SearchUpwards); + break; + } + case IDOK: + case IDC_FR_REPLACE: + { + OnReplace( d->FindReplaceParams->LastFind, + d->FindReplaceParams->LastReplace, + Action == IDOK, + d->FindReplaceParams->MatchWord, + d->FindReplaceParams->MatchCase, + d->FindReplaceParams->SelectionOnly, + d->FindReplaceParams->SearchUpwards); + break; + } + } + + d->FindReplaceParams->Unlock(); + } + }, + LastFind8, + LastReplace8); Dlg.MatchWord = d->FindReplaceParams->MatchWord; Dlg.MatchCase = d->FindReplaceParams->MatchCase; Dlg.SelectionOnly = HasSelection(); - int Action = Dlg.DoModal(); - - DeleteArray(LastFind8); - DeleteArray(LastReplace8); - - if (Action != IDCANCEL) - { - d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg.Find)); - d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Dlg.Replace)); - d->FindReplaceParams->MatchWord = Dlg.MatchWord; - d->FindReplaceParams->MatchCase = Dlg.MatchCase; - d->FindReplaceParams->SelectionOnly = Dlg.SelectionOnly; - } - - switch (Action) - { - case IDC_FR_FIND: - { - OnFind( d->FindReplaceParams->LastFind, - d->FindReplaceParams->MatchWord, - d->FindReplaceParams->MatchCase, - d->FindReplaceParams->SelectionOnly, - d->FindReplaceParams->SearchUpwards); - break; - } - case IDOK: - case IDC_FR_REPLACE: - { - OnReplace( d->FindReplaceParams->LastFind, - d->FindReplaceParams->LastReplace, - Action == IDOK, - d->FindReplaceParams->MatchWord, - d->FindReplaceParams->MatchCase, - d->FindReplaceParams->SelectionOnly, - d->FindReplaceParams->SearchUpwards); - break; - } - } - - return false; + Dlg.DoModal(NULL); } void LTextView3::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView3::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } /* What was this even supposed to do? LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) */ return i; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView3::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView3::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView3::SeekLine(ssize_t Offset, GTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView3::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView3::OnSetHidden(int Hidden) { } void LTextView3::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { - /* + #if 0 auto Client = GetClient(); LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); - */ + #endif SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView3::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView3::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView3::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS if (Ctrls.Length() == 0) SetPulse(PULSE_TIMEOUT); Ctrls.Add(this); #else SetPulse(PULSE_TIMEOUT); #endif } void LTextView3::OnEscape(LKey &K) { } bool LTextView3::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView3::OnFocus(bool f) { Invalidate(); } ssize_t LTextView3::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; int Y = (VScroll) ? (int)VScroll->Value() : 0; auto It = Line.begin(Y); if (It != Line.end()) y += (*It)->r.y1; while (It != Line.end()) { auto l = *It; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) { return l->Start; } else if (x > l->r.x2) { return l->Start + l->Len; } } if (Down) It++; else It--; Y++; } // outside text area if (Down) { It = Line.rbegin(); if (It != Line.end()) { if (y > (*It)->r.y2) { // end of document return Size; } } } return 0; } void LTextView3::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView3::Redo() { UndoQue.Redo(); } void LTextView3::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); - LInput i(this, s, "Indent Size:", "Text"); - if (i.DoModal()) + + LInput *i = new LInput(this, s, "Indent Size:", "Text"); + i->DoModal([this, i](auto dlg, auto code) { - IndentSize = atoi(i.GetStr()); - } + if (code) + IndentSize = atoi(i->GetStr()); + delete i; + }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); - LInput i(this, s, "Tab Size:", "Text"); - if (i.DoModal()) + + LInput *i = new LInput(this, s, "Tab Size:", "Text"); + i->DoModal([this, i](auto dlg, auto code) { - SetTabSize(atoi(i.GetStr())); - } + SetTabSize(atoi(i->GetStr())); + delete i; + }); break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView3::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool LTextView3::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView3::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView3::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (m.IsContextMenu()) { DoContextMenu(m); return; } else if (m.Left()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } } if (!Processed) { Capture(m.Down()); } } int LTextView3::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView3::OnMouseMove(LMouse &m) { m.x += ScrollX; ssize_t Hit = HitText(m.x, m.y, true); if (IsCapturing()) { if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } } LCursor LTextView3::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView3::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { - DoFindNext(); + DoFindNext(NULL); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView3_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView3_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView3_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) { - DoFind(); + DoFind(NULL); } return true; break; } case 'g': case 'G': { if (k.Down()) { - DoGoto(); + DoGoto(NULL); } return true; break; } case 'h': case 'H': { if (k.Down()) { - DoReplace(); + DoReplace(NULL); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { - DoCase(k.Shift()); + DoCase(NULL, k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView3::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView3::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView3::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView3::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView3::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView3::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView3::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if DOUBLE_BUFFER_PAINT LDoubleBuffer MemBuf(pDC); #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } if (Text && Font) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); auto It = Line.begin(k); LTextLine *l = NULL; int Dy = 0; if (It != Line.end()) Dy = -(*It)->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (It != Line.end() && (l = *It) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ((l = *It) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; Tr.Offset(0, y - Tr.y1); //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif y += LineY; It++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView3::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView3::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) - DoFindNext(); + DoFindNext(NULL); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView3::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView3::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView3::OnPulse() { #ifdef WINDOWS InternalPulse(); #else for (auto c: Ctrls) c->InternalPulse(); #endif } void LTextView3::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView3::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } /////////////////////////////////////////////////////////////////////////////// class LTextView3_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView3") == 0) { return new LTextView3(-1, 0, 0, 2000, 2000); } return 0; } } TextView3_Factory; diff --git a/src/common/Text/TextView4.cpp b/src/common/Text/TextView4.cpp --- a/src/common/Text/TextView4.cpp +++ b/src/common/Text/TextView4.cpp @@ -1,5483 +1,5512 @@ #ifdef WIN32 #include #include #endif #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView4.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms #define PULSE_TIMEOUT 250 // ms #define CURSOR_BLINK 1000 // ms #define ALLOC_BLOCK 64 #define IDC_VS 1000 enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class GDocFindReplaceParams4 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; bool SearchUpwards; GDocFindReplaceParams4() : LMutex("GDocFindReplaceParams4") { MatchCase = false; MatchWord = false; SelectionOnly = false; SearchUpwards = false; } }; class LTextView4Private : public LCss, public LMutex { public: LTextView4 *View; LRect rPadding; int PourX; bool LayoutDirty; ssize_t DirtyStart, DirtyLen; LColour UrlColour; bool CenterCursor; ssize_t WordSelectMode; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache; // Find/Replace Params bool OwnFindReplaceParams; GDocFindReplaceParams4 *FindReplaceParams; // Map buffer ssize_t MapLen; char16 *MapBuf; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView4Private(LTextView4 *view) : LMutex("LTextView4Private") { View = view; WordSelectMode = -1; PourX = -1; VScrollCache = -1; DirtyStart = DirtyLen = 0; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); CenterCursor = false; LayoutDirty = true; rPadding.ZOff(0, 0); MapBuf = 0; MapLen = 0; OwnFindReplaceParams = true; FindReplaceParams = new GDocFindReplaceParams4; } ~LTextView4Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView4Undo : public LUndoEvent { LTextView4 *View; LArray Changes; LTextView4Undo(LTextView4 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView4::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView4::LTextView4( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView4Private(this)); PourEnabled = true; PartialPour = false; AdjustStylePos = true; BlinkTs = 0; LineY = 1; MaxX = 0; TextCache = 0; UndoOn = true; UndoCur = NULL; Font = 0; FixedWidthFont = false; FixedFont = 0; ShowWhiteSpace = false; ObscurePassword = false; TabSize = TAB_SIZE; IndentSize = TAB_SIZE; HardTabs = true; CanScrollX = false; Blink = true; // setup window SetId(Id); // default options Dirty = false; #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else CrLf = false; #endif Underline = NULL; Bold = NULL; d->Padding(LCss::Len(LCss::LenPx, 2)); #ifdef _DEBUG // debug times _PourTime = 0; _StyleTime = 0; _PaintTime = 0; #endif // Data Alloc = ALLOC_BLOCK; Text = new char16[Alloc]; if (Text) *Text = 0; Cursor = 0; Size = 0; // Display SelStart = SelEnd = -1; DocOffset = 0; ScrollX = 0; if (FontType) { Font = FontType->Create(); } else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView4::~LTextView4() { #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView4::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView4::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView4::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView4::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView4::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView4::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); CanScrollX = i != TEXTED_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView4::GetFont() { return Font; } LFont *LTextView4::GetBold() { return Bold; } void LTextView4::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView4::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } void LTextView4::LogLines() { int Idx = 0; LStringPipe p; p.Print("DocSize: %i\n", (int)Size); for (auto i : Line) { p.Print(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr()); Idx++; } LgiTrace(p.NewGStr()); #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif } bool LTextView4::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto l: Line) { if (l->Start != Pos) { LogLines(); LAssert(!"Incorrect start."); return false; } char16 *e = c; if (WrapType == TEXTED_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { LogLines(); LAssert(!"Incorrect length."); return false; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { LogLines(); LAssert(!"Lines not joined vertically"); } if (*e) { if (*e == '\n') e++; else if (WrapType == TEXTED_WRAP_REFLOW) e++; } Pos = e - Text; c = e; Idx++; Prev = l; } if (WrapType == TEXTED_WRAP_NONE && Pos != Size) { LogLines(); LAssert(!"Last line != end of doc"); return false; } return true; } int LTextView4::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView4::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif LAssert(InThread()); LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (int64_t mid, s = 0, e = Idx >= 0 ? Idx : Line.Length() - 1; s < e; ) { if (e - s <= 1) { if (Line[e]->r.Valid()) mid = e; else mid = s; Cur = Line[mid]; Cy = Cur->r.y1; Idx = mid; break; } else mid = s + ((e - s) >> 1); auto sItem = Line[mid]; if (sItem->r.Valid()) s = mid + 1; // Search Mid->e else e = mid - 1; // Search s->Mid } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); if (WrapType == TEXTED_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (; Idx < (ssize_t)Line.Length(); Idx++) { LTextLine *l = Line[Idx]; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine; l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } LAssert(l->Len > 0); Line.Add(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewGStr(); #endif PartialPour = false; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (size_t i=Idx; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine; if (l) { l->Start = i; l->Len = e - i; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; LAssert(l->Len > 0); Line.Add(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; // LgiTrace("Pour timeout...\n"); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) PartialPour = false; SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || Last->Start + Last->Len < Size) { auto LastEnd = Last ? Last->End() : 0; LTextLine *l = new LTextLine; if (l) { l->Start = LastEnd; l->Len = Size - LastEnd; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Add(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { // LgiTrace("%s:%i - SetScrollBars(%i) %i %i\n", _FL, ScrollYNeeded, Client.Y(), (Line.Length() * LineY)); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView4::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView4::LStyle *LTextView4::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView4::LStyle *LTextView4::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView4::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView4::Insert(size_t At, const char16 *Data, ssize_t Len) { static int count = -1; count++; LProfile Prof("LTextView4::Insert"); Prof.HideResultsIfBelow(1000); LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } Prof.Add("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16)); Prof.Add("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate Prof.Add("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj; if (!UndoCur) Obj.Reset(new LTextView4Undo(this)); auto u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); else LAssert(!"No undo obj?"); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Add(Cur = new LTextLine); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { if (WrapType == TEXTED_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); Prof.Add("NoWrap add lines"); // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; // Create a new line... Cur = new LTextLine(); if (!Cur) return false; Cur->Start = Pos + 1; Line.AddAt(++Idx, Cur); } } Prof.Add("CalcLen"); // Make sure the last Line's length is set.. Cur->CalcLen(Text); Prof.Add("UpdatePos"); // Now update all the positions of the following lines... for (size_t i = ++Idx; i < Line.Length(); i++) Line[i]->Start += Len; } else { // Clear all lines to the end of the doc... LgiTrace("ClearLines %i\n", (int)Idx+1); for (size_t i = ++Idx; i < Line.Length(); i++) delete Line[i]; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode if (WrapType == TEXTED_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG // Prof.Add("Validate"); // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { Prof.Add("PourText"); PourText(At, Len); Prof.Add("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView4::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; if (WrapType == TEXTED_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (size_t i = Idx + 1; i < Line.Length(); i++) Line[i]->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (size_t i = Index; i < Line.Length(); i++) delete Line[i]; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change if (WrapType == TEXTED_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView4::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } LArray::I LTextView4::GetTextLineIt(ssize_t Offset, ssize_t *Index) { if (Line.Length() == 0) { if (Index) *Index = 0; return Line.end(); } if (Offset <= 0) { if (Index) *Index = 0; return Line.begin(); } else if (Line.Length()) { auto l = Line.Last(); if (Offset > l->End()) { if (Index) *Index = Line.Length() - 1; return Line.begin(Line.Length()-1); } } size_t mid = 0, s = 0, e = Line.Length() - 1; while (s < e) { if (e - s <= 1) { if (Line[s]->Overlap(Offset)) mid = s; else if (Line[e]->Overlap(Offset)) mid = e; else { LAssert(!"Needs to match Line s or e..."); break; } } else mid = s + ((e - s) >> 1); auto l = Line[mid]; auto end = l->End(); if (Offset < l->Start) e = mid - 1; else if (Offset > end) s = mid + 1; else { LAssert(Line[mid]->Overlap(Offset)); if (Index) *Index = mid; return Line.begin(mid); } } LAssert(Line[s]->Overlap(Offset)); if (Index) *Index = s; return Line.begin(s); } int64 LTextView4::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView4::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView4::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView4::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView4::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView4::NameW() { return Text; } bool LTextView4::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView4::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView4::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView4::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView4::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView4::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView4::GetLines() { return Line.Length(); } void LTextView4::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView4::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView4::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView4::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView4::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView4::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; LTextLine *SLine = GetTextLine(Start); LTextLine *ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView4::SetBorder(int b) { } bool LTextView4::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView4::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView4::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } -bool LTextView4::ClearDirty(bool Ask, const char *FileName) +void LTextView4::ClearDirty(std::function OnStatus, bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { - LFileSelect Select; - Select.Parent(this); - if (!FileName && - Select.Save()) + auto DoSave = [&](bool ok) + { + Save(FileName); + if (OnStatus) + OnStatus(ok); + }; + + if (!FileName) { - FileName = Select.Name(); + LFileSelect *Select = new LFileSelect; + Select->Parent(this); + Select->Save([&FileName, &DoSave](auto Select, auto ok) + { + if (ok) + FileName = Select->Name(); + DoSave(ok); + delete Select; + }); } - - Save(FileName); + else DoSave(true); } else if (Answer == IDCANCEL) { - return false; + if (OnStatus) + OnStatus(false); + return; } } - return true; + if (OnStatus) + OnStatus(true); } bool LTextView4::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView4::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView4::GetLastError() { return d->LastError; } void LTextView4::UpdateScrollBars(bool Reset) { if (VScroll) { LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = GetLines(); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } } -bool LTextView4::DoCase(bool Upper) +void LTextView4::DoCase(std::function Callback, bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } - return true; + if (Callback) + Callback(Text != NULL); } ssize_t LTextView4::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } -void LTextView4::SetLine(int i) +void LTextView4::SetLine(int64_t i) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, false); d->CenterCursor = false; } } -bool LTextView4::DoGoto() +void LTextView4::DoGoto(std::function Callback) { - LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); - if (Dlg.DoModal() == IDOK && - Dlg.GetStr()) + LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); + Dlg->DoModal([this, Dlg, Callback](auto d, auto code) { - SetLine(atoi(Dlg.GetStr())); - } - - return true; + auto ok = code == IDOK && Dlg->GetStr(); + if (ok) + SetLine(Dlg->GetStr().Int()); + if (Callback) + Callback(ok); + delete Dlg; + }); } LDocFindReplaceParams *LTextView4::CreateFindReplaceParams() { return new GDocFindReplaceParams4; } void LTextView4::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (GDocFindReplaceParams4*) Params; } } -bool LTextView4::DoFindNext() +void LTextView4::DoFindNext(std::function Callback) { bool Status = false; - if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } - - return Status; + if (Callback) + Callback(Status); } -bool -Text4_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User) -{ - LTextView4 *v = (LTextView4*) User; - - if (v->d->FindReplaceParams && - v->d->FindReplaceParams->Lock(_FL)) - { - v->d->FindReplaceParams->MatchWord = Dlg->MatchWord; - v->d->FindReplaceParams->MatchCase = Dlg->MatchCase; - v->d->FindReplaceParams->SelectionOnly = Dlg->SelectionOnly; - v->d->FindReplaceParams->SearchUpwards = Dlg->SearchUpwards; - v->d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg->Find)); - - v->d->FindReplaceParams->Unlock(); - } - - return v->DoFindNext(); -} - -bool LTextView4::DoFind() +void LTextView4::DoFind(std::function Callback) { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } - LFindDlg Dlg(this, u, Text4_FindCallback, this); - Dlg.DoModal(); - Focus(true); - - return false; + LFindDlg Dlg(this, [View=this, Params=d->FindReplaceParams, Callback](LFindReplaceCommon *Dlg, int Action) + { + if (Params && + Params->Lock(_FL)) + { + Params->MatchWord = Dlg->MatchWord; + Params->MatchCase = Dlg->MatchCase; + Params->SelectionOnly = Dlg->SelectionOnly; + Params->SearchUpwards = Dlg->SearchUpwards; + Params->LastFind.Reset(Utf8ToWide(Dlg->Find)); + + Params->Unlock(); + } + + View->DoFindNext([View, Callback](bool ok) + { + View->Focus(true); + if (Callback) + Callback(ok); + }); + }, u); + + Dlg.DoModal(NULL); } -bool LTextView4::DoReplace() +void LTextView4::DoReplace(std::function Callback) { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } char *LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind); char *LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace); - LReplaceDlg Dlg(this, LastFind8, LastReplace8); + LReplaceDlg Dlg(this, + [&](LFindReplaceCommon *Dlg, int Action) + { + LReplaceDlg *Replace = dynamic_cast(Dlg); + LAssert(Replace != NULL); + + DeleteArray(LastFind8); + DeleteArray(LastReplace8); + + if (Action == IDCANCEL) + return; + + if (d->FindReplaceParams->Lock(_FL)) + { + d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find)); + d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace)); + d->FindReplaceParams->MatchWord = Replace->MatchWord; + d->FindReplaceParams->MatchCase = Replace->MatchCase; + d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly; + + switch (Action) + { + case IDC_FR_FIND: + { + OnFind( d->FindReplaceParams->LastFind, + d->FindReplaceParams->MatchWord, + d->FindReplaceParams->MatchCase, + d->FindReplaceParams->SelectionOnly, + d->FindReplaceParams->SearchUpwards); + break; + } + case IDOK: + case IDC_FR_REPLACE: + { + OnReplace( d->FindReplaceParams->LastFind, + d->FindReplaceParams->LastReplace, + Action == IDOK, + d->FindReplaceParams->MatchWord, + d->FindReplaceParams->MatchCase, + d->FindReplaceParams->SelectionOnly, + d->FindReplaceParams->SearchUpwards); + break; + } + } + + d->FindReplaceParams->Unlock(); + } + }, + LastFind8, + LastReplace8); Dlg.MatchWord = d->FindReplaceParams->MatchWord; Dlg.MatchCase = d->FindReplaceParams->MatchCase; Dlg.SelectionOnly = HasSelection(); - int Action = Dlg.DoModal(); - - DeleteArray(LastFind8); - DeleteArray(LastReplace8); - - if (Action != IDCANCEL) - { - d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Dlg.Find)); - d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Dlg.Replace)); - d->FindReplaceParams->MatchWord = Dlg.MatchWord; - d->FindReplaceParams->MatchCase = Dlg.MatchCase; - d->FindReplaceParams->SelectionOnly = Dlg.SelectionOnly; - } - - switch (Action) - { - case IDC_FR_FIND: - { - OnFind( d->FindReplaceParams->LastFind, - d->FindReplaceParams->MatchWord, - d->FindReplaceParams->MatchCase, - d->FindReplaceParams->SelectionOnly, - d->FindReplaceParams->SearchUpwards); - break; - } - case IDOK: - case IDC_FR_REPLACE: - { - OnReplace( d->FindReplaceParams->LastFind, - d->FindReplaceParams->LastReplace, - Action == IDOK, - d->FindReplaceParams->MatchWord, - d->FindReplaceParams->MatchCase, - d->FindReplaceParams->SelectionOnly, - d->FindReplaceParams->SearchUpwards); - break; - } - } - - return false; + Dlg.DoModal(NULL); } void LTextView4::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView4::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End - FindLen; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) return r.Start; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView4::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView4::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView4::SeekLine(ssize_t Offset, GTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView4::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView4::OnSetHidden(int Hidden) { } void LTextView4::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { // printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView4::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView4::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView4::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS if (Ctrls.Length() == 0) #endif SetPulse(PULSE_TIMEOUT); #ifndef WINDOWS Ctrls.Add(this); #endif } void LTextView4::OnEscape(LKey &K) { } bool LTextView4::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView4::OnFocus(bool f) { Invalidate(); } ssize_t LTextView4::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; auto Y = VScroll ? VScroll->Value() : 0; if (Y < (ssize_t)Line.Length()) y += Line[Y]->r.y1; while (Y>=0 && Y<(ssize_t)Line.Length()) { auto l = Line[Y]; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) return l->Start; else if (x > l->r.x2) return l->Start + l->Len; } if (Down) Y++; else Y--; } // outside text area if (Down) { if (Line.Length()) { if (y > Line.Last()->r.y2) { // end of document return Size; } } } return 0; } void LTextView4::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView4::Redo() { UndoQue.Redo(); } void LTextView4::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); - LInput i(this, s, "Indent Size:", "Text"); - if (i.DoModal()) + + LInput *i = new LInput(this, s, "Indent Size:", "Text"); + i->DoModal([this, i](auto dlg, auto code) { - IndentSize = atoi(i.GetStr()); - } + if (code) + IndentSize = atoi(i->GetStr()); + delete i; + }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); - LInput i(this, s, "Tab Size:", "Text"); - if (i.DoModal()) + + LInput *i = new LInput(this, s, "Tab Size:", "Text"); + i->DoModal([this, i](auto dlg, auto code) { - SetTabSize(atoi(i.GetStr())); - } + SetTabSize(atoi(i->GetStr())); + delete i; + }); break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView4::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool LTextView4::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView4::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView4::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (!m.IsContextMenu()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } else { DoContextMenu(m); return; } } if (!Processed) { Capture(m.Down()); } } int LTextView4::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView4::OnMouseMove(LMouse &m) { m.x += ScrollX; ssize_t Hit = HitText(m.x, m.y, true); if (IsCapturing()) { if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } } LCursor LTextView4::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView4::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { - DoFindNext(); + DoFindNext(NULL); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView4_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView4_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView4_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) { - DoFind(); + DoFind(NULL); } return true; break; } case 'g': case 'G': { if (k.Down()) { - DoGoto(); + DoGoto(NULL); } return true; break; } case 'h': case 'H': { if (k.Down()) { - DoReplace(); + DoReplace(NULL); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { - DoCase(k.Shift()); + DoCase(NULL, k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView4::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView4::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView4::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView4::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView4::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView4::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView4::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } #ifdef DOUBLE_BUFFER_PAINT LMemDC *pMem = new LMemDC; pOut = pMem; #endif if (Text && Font #ifdef DOUBLE_BUFFER_PAINT && pMem && pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits()) #endif ) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); LTextLine *l = NULL; int Dy = 0; if (k < Line.Length()) Dy = -Line[k]->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (k < Line.Length() && (l = Line[k]) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ( k < Line.Length() && (l = Line[k]) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; #ifdef DOUBLE_BUFFER_PAINT Tr.Offset(-Tr.x1, -Tr.y1); #else Tr.Offset(0, y - Tr.y1); #endif //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; #ifdef DOUBLE_BUFFER_PAINT c.Offset(-d->rPadding.x1, -y); #endif pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif #ifdef DOUBLE_BUFFER_PAINT // dump to screen pDC->Blt(d->rPadding.x1, y, pOut); #endif y += LineY; k++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } #ifdef DOUBLE_BUFFER_PAINT DeleteObj(pMem); #endif } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView4::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView4::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) - DoFindNext(); + DoFindNext(NULL); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView4::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView4::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView4::OnPulse() { #ifdef WINDOWS InternalPulse(); #else for (auto c: Ctrls) c->InternalPulse(); #endif } void LTextView4::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView4::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } /////////////////////////////////////////////////////////////////////////////// class LTextView4_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView4") == 0) { return new LTextView4(-1, 0, 0, 2000, 2000); } return 0; } } TextView4_Factory; diff --git a/src/common/Widgets/Box.cpp b/src/common/Widgets/Box.cpp --- a/src/common/Widgets/Box.cpp +++ b/src/common/Widgets/Box.cpp @@ -1,921 +1,926 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Popup.h" #include "lgi/common/Notifications.h" #undef max #define DEFAULT_MINIMUM_SIZE_PX 5 #define ACTIVE_SPACER_SIZE_PX 9 #if 0 //def _DEBUG #define LOG(...) if (_Debug) LgiTrace(__VA_ARGS__) #else #define LOG(...) #endif static int DefaultSpacerPx() { static int px = -1; if (px < 0) { auto dpi = LScreenDpi(); px = std::max(dpi.x / 17, 5); } return px; }; #define DEFAULT_SPACER_PX DefaultSpacerPx() enum LBoxMessages { M_CHILDREN_CHANGED = M_USER + 0x2000 }; struct LBoxViewInfo { LViewI *View = NULL; LCss::Len Size; // Original size before layout }; struct LBoxPriv { public: bool Vertical = false; bool Dirty = false; LArray Spacers; LBox::Spacer *Dragging = NULL; LPoint DragOffset; LArray Info; LBoxPriv() { } LBoxViewInfo *GetInfo(LViewI *v) { for (auto &i: Info) if (i.View == v) return &i; auto &i = Info.New(); i.View = v; return &i; } int GetBox(LRect &r) { return Vertical ? r.Y() : r.X(); } LBox::Spacer *HitTest(int x, int y) { for (int i=0; iUnregisterHook(this); DeleteObj(d); } bool LBox::IsVertical() { return d->Vertical; } void LBox::SetVertical(bool v) { if (d->Vertical != v) { d->Vertical = v; OnPosChange(); } } LBox::Spacer *LBox::GetSpacer(int idx) { if (Children.Length()) { while (d->Spacers.Length() < Children.Length() - 1) { Spacer &s = d->Spacers.New(); s.SizePx = DEFAULT_SPACER_PX; // s.Colour.c24(DEFAULT_SPACER_COLOUR24); } } return idx >= 0 && idx < d->Spacers.Length() ? &d->Spacers[idx] : NULL; } LViewI *LBox::GetViewAt(int i) { return Children[i]; } bool LBox::SetViewAt(uint32_t i, LViewI *v) { if (!v || i > Children.Length()) { return false; } if (v->GetParent()) v->Detach(); v->Visible(true); bool Status; if (i < Children.Length()) { // Remove existing view.. LViewI *existing = Children[i]; if (existing == v) return true; if (existing) existing->Detach(); Status = AddView(v, i); } else { Status = AddView(v); } if (Status) { AttachChildren(); } return Status; } void LBox::OnCreate() { AttachChildren(); OnPosChange(); LWindow *Wnd = GetWindow(); if (Wnd) Wnd->RegisterHook(this, LMouseEvents); } bool LBox::OnViewMouse(LView *v, LMouse &m) { // This hook allows the LBox to catch clicks nearby the splits even if the splits are too small // to grab normally. Consider the case of a split that is 1px wide. The active region needs to // be a little larger than that, however a normal click would go through to the child windows // on either side of the split rather than to the LBox. if (!m.IsMove() && m.Down()) { // Convert click to the local coordinates of this view LMouse Local = m; while (v && v != (LView*)this && v->GetParent()) { if (dynamic_cast(v)) return true; LRect p = v->GetPos(); Local.x += p.x1; Local.y += p.y1; LViewI *vi = v->GetParent(); v = vi ? vi->GetGView() : NULL; } if (v == (LView*)this) { // Is the click over our spacers? Spacer *s = d->HitTest(Local.x, Local.y); if (s) { // Pass the click to ourselves and prevent the normal view from getting it. OnMouseClick(Local); return false; } } } return true; } bool LBox::Pour(LRegion &r) { LRect *p = FindLargest(r); if (!p) return false; SetPos(*p); return true; } void LBox::OnPaint(LSurface *pDC) { if (d->Dirty) { d->Dirty = false; OnPosChange(); } LRect cli = GetClient(); LCssTools tools(GetCss(), GetFont()); cli = tools.PaintBorderAndPadding(pDC, cli); LColour cBack = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); size_t ChildViews = Children.Length(); if (ChildViews == 0) { pDC->Colour(cBack); pDC->Rectangle(&cli); } else { #if 0 // coverage check... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(&cli); #endif LRegion Painted(cli); for (int i=0; iSpacers.Length(); i++) { Spacer &s = d->Spacers[i]; if (s.Colour.IsValid()) pDC->Colour(s.Colour); else pDC->Colour(cBack); pDC->Rectangle(&s.Pos); Painted.Subtract(&s.Pos); } for (auto c : Children) Painted.Subtract(&c->GetPos()); for (auto r = Painted.First(); r; r = Painted.Next()) { pDC->Colour(cBack); pDC->Rectangle(r); } } } struct BoxRange { int MinPx, MaxPx; LCss::Len Size, Min, Max; LViewI *View; BoxRange &Init() { MinPx = MaxPx = DEFAULT_MINIMUM_SIZE_PX; View = NULL; return *this; } LString toString(const char *label, LCss::Len &l) { if (!l.IsValid()) return ""; LStringPipe p; l.ToString(p); return LString(", ") + label + "=" + p.NewGStr(); } LString toString() { LStringPipe p; Size.ToString(p); LString s; s.Printf("%s/%p, %i->%i%s%s%s", View?View->GetClass():NULL, View, MinPx, MaxPx, toString("sz", Size).Get(), toString("min", Min).Get(), toString("max", Max).Get()); return s; } }; void LBox::OnPosChange() { LCssTools tools(GetCss(), GetFont()); LRect client = GetClient(); if (!client.Valid()) return; LRect content = tools.ApplyBorder(client); content = tools.ApplyPadding(content); GetSpacer(0); auto views = IterateViews(); int Cur = content.x1, Idx = 0; int AvailablePx = d->GetBox(content); if (AvailablePx <= 0) { LOG("%s:%i - No available px.\n", _FL); return; } LArray Sizes; int SpacerPx = 0; int FixedPx = 0; LArray FixedChildren; int MinPx = 0; int PercentPx = 0; LArray PercentChildren; float PercentCount = 0.0f; LArray AutoChildren; LOG("%s:%i - %i views, %i avail px\n", _FL, (int)views.Length(), AvailablePx); // Do first pass over children and find their sizes for (LViewI *c: views) { LCss *css = c->GetCss(); BoxRange &box = Sizes[Idx].Init(); auto vi = d->GetInfo(c); box.View = c; // Get any available CSS size if (css) { if (IsVertical()) { box.Size = css->Height(); box.Min = css->MinHeight(); box.Max = css->MaxHeight(); } else { box.Size = css->Width(); box.Min = css->MinWidth(); box.Max = css->MaxWidth(); } } // Work out some min and max values if (box.Size.IsValid()) { if (!vi->Size.IsValid()) vi->Size = box.Size; if (box.Size.Type == LCss::LenPercent) { box.MaxPx = box.Size.ToPx(AvailablePx, GetFont()); PercentPx += box.MaxPx; PercentCount += box.Size.Value; PercentChildren.Add(Idx); } else if (box.Size.IsDynamic()) { AutoChildren.Add(Idx); } else { // Fixed children get first crack at the space box.MaxPx = box.Size.ToPx(AvailablePx, GetFont()); FixedPx += box.MaxPx; FixedChildren.Add(Idx); } } else { AutoChildren.Add(Idx); if (vi) { if (IsVertical()) vi->Size = LCss::Len(LCss::LenPx, c->Y()); else vi->Size = LCss::Len(LCss::LenPx, c->X()); } } MinPx += box.MinPx; LOG(" %s\n", box.toString().Get()); // Allocate area for spacers in the Fixed portion if (Idx < Children.Length() - 1) { Spacer &s = d->Spacers[Idx]; SpacerPx += s.SizePx; } Idx++; } LOG(" FixedChildren=" LPrintfSizeT " AutoChildren=" LPrintfSizeT "\n", FixedChildren.Length(), AutoChildren.Length()); // Convert all the percentage sizes to px int RemainingPx = AvailablePx - SpacerPx - FixedPx; LOG(" RemainingPx=%i (%i - %i - %i)\n", RemainingPx, AvailablePx, SpacerPx, FixedPx); if (RemainingPx < 0) { // De-allocate space from the fixed size views... int FitPx = AvailablePx - SpacerPx - MinPx; double Ratio = (double)FitPx / FixedPx; LOG(" Dealloc FitPx=%i Ratio=%.2f\n", FitPx, Ratio); for (size_t i=0; iGetCss(); BoxRange &box = Sizes[i]; if (css) { if (!box.Size.IsDynamic()) { int OldPx = box.MaxPx; int NewPx = (int)(OldPx * Ratio); LAssert(NewPx == 0 || NewPx < OldPx); box.MaxPx = NewPx; box.Size = LCss::Len(LCss::LenPx, NewPx); LOG(" %s: px=%i->%i\n", c->GetClass(), OldPx, NewPx); FixedPx -= OldPx - NewPx; } } } RemainingPx = AvailablePx - SpacerPx - FixedPx; } LOG(" RemainingPx=%i (%i - %i - %i)\n", RemainingPx, AvailablePx, SpacerPx, FixedPx); for (int i=0; i %i\n", box.toString().Get(), PercentPx, RemainingPx); if (PercentPx > RemainingPx) { if (AutoChildren.Length() > 0 || PercentChildren.Length() > 1) { // Well... ah... we better leave _some_ space for them. auto AutoPx = 16 * AutoChildren.Length(); float Ratio = ((float)RemainingPx - AutoPx) / PercentPx; int Px = (int) (box.MaxPx * Ratio); box.Size.Type = LCss::LenPx; box.Size.Value = (float) Px; RemainingPx -= Px; } else { // We can just take all the space... box.Size.Type = LCss::LenPx; box.Size.Value = (float) RemainingPx; RemainingPx = 0; } } else { box.Size.Type = LCss::LenPx; box.Size.Value = (float) box.MaxPx; RemainingPx -= box.MaxPx; } } } // Convert auto children to px auto AutoPx = AutoChildren.Length() > 0 ? RemainingPx / AutoChildren.Length() : 0; LOG(" AutoPx=%i, %i / " LPrintfSizeT "\n", AutoPx, RemainingPx, AutoChildren.Length()); while (AutoChildren.Length()) { auto i = AutoChildren[0]; BoxRange &box = Sizes[i]; AutoChildren.DeleteAt(0, true); LOG(" Auto: %s\n", box.toString().Get()); box.Size.Type = LCss::LenPx; if (AutoChildren.Length() > 0) { box.Size.Type = LCss::LenPx; box.Size.Value = (float) AutoPx; RemainingPx -= (int)AutoPx; } else { box.Size.Type = LCss::LenPx; box.Size.Value = (float) RemainingPx; RemainingPx = 0; } LOG(" AutoAlloc: %s\n", box.toString().Get()); } auto Fnt = GetFont(); for (int i=0; iVertical) { viewPos.y1 = Cur; viewPos.y2 = Cur + Px - 1; } else { viewPos.x1 = Cur; viewPos.x2 = Cur + Px - 1; } box.View->SetPos(viewPos); LOG(" View[%i] = %s.\n", i, viewPos.GetStr()); #ifdef WIN32 // This forces the update, otherwise the child's display lags till the // mouse is released *rolls eyes* box.View->Invalidate((LRect*)NULL, true); #endif Cur += Px; // Allocate area for spacer if (i < Sizes.Length() - 1) { Spacer &s = d->Spacers[i]; s.Pos = content; if (d->Vertical) { s.Pos.y1 = Cur; s.Pos.y2 = Cur + s.SizePx - 1; } else { s.Pos.x1 = Cur; s.Pos.x2 = Cur + s.SizePx - 1; } Cur += s.SizePx; } } } void LBox::OnMouseClick(LMouse &m) { #if 0 { LString::Array a; for (LViewI *p = this; p; p = p->GetParent()) a.New() = p->GetClass(); m.Trace(LString("LBox::OnMouseClick-") + LString(".").Join(a)); } #endif + // m.Trace("Lbox click."); if (m.Down()) { d->Dragging = d->HitTest(m.x, m.y); + Capture(d->Dragging != NULL); if (d->Dragging) { d->DragOffset.x = m.x - d->Dragging->Pos.x1; d->DragOffset.y = m.y - d->Dragging->Pos.y1; - Capture(d->Dragging != NULL); } } else if (IsCapturing()) { Capture(false); d->Dragging = NULL; } } bool IsValidLen(LCss *c, LCss::PropType p) { if (!c || c->GetType(p) != LCss::TypeLen) return false; LCss::Len *l = (LCss::Len*)c->PropAddress(p); if (!l) return false; return l->IsValid(); } void LBox::OnMouseMove(LMouse &m) { + // m.Trace("Lbox move"); if (!d->Dragging || !IsCapturing()) + { return; + } #if 0 { LString::Array a; for (LViewI *p = this; p; p = p->GetParent()) a.New().Printf("%s/%p", p->GetClass(), p); m.Trace(LString("LBox::OnMouseMove-") + LString(".").Join(a)); } #endif if (!m.Down()) { // Something else got the up click? + printf("No button down... so uncapturing..\n"); Capture(false); return; } int DragIndex = (int) (d->Dragging - &d->Spacers[0]); if (DragIndex < 0 || DragIndex >= d->Spacers.Length()) { LAssert(0); return; } LViewI *Prev = Children[DragIndex]; if (!Prev) { LAssert(0); return; } LViewI *Next = DragIndex < Children.Length() ? Children[DragIndex+1] : NULL; LCssTools tools(GetCss(), GetFont()); LRect Content = tools.ApplyMargin(GetClient()); int ContentPx = d->GetBox(Content); LRect SplitPos = d->Dragging->Pos; LCss *PrevStyle = Prev->GetCss(); LCss::PropType Style = d->Vertical ? LCss::PropHeight : LCss::PropWidth; bool EditPrev = !Next || IsValidLen(PrevStyle, Style); LViewI *Edit = EditPrev ? Prev : Next; LAssert(Edit != NULL); LRect ViewPos = Edit->GetPos(); auto *EditCss = Edit->GetCss(true); if (d->Vertical) { // Work out the minimum height of the view LCss::Len MinHeight = EditCss->MinHeight(); int MinPx = MinHeight.IsValid() ? MinHeight.ToPx(ViewPos.Y(), Edit->GetFont()) : DEFAULT_MINIMUM_SIZE_PX; int Offset = m.y - d->DragOffset.y - SplitPos.y1; if (Offset) { // Slide up and down the Y axis // Limit to the min size LRect r = ViewPos; if (EditPrev) { r.y2 += Offset; if (r.Y() < MinPx) { int Diff = MinPx - r.Y(); Offset += Diff; r.y2 += Diff; } } else { r.y1 += Offset; if (r.Y() < MinPx) { int Diff = MinPx - r.Y(); Offset -= Diff; r.y1 -= Diff; } } if (Offset) { SplitPos.Offset(0, Offset); // Save the new height of the view LCss::Len Ht = EditCss->Height(); if (Ht.Type == LCss::LenPercent && ContentPx > 0) { Ht.Value = (float)r.Y() * 100 / ContentPx; } else { Ht.Type = LCss::LenPx; Ht.Value = (float)r.Y(); } EditCss->Height(Ht); } } } else { // Work out the minimum width of the view LCss::Len MinWidth = EditCss->MinWidth(); int MinPx = MinWidth.IsValid() ? MinWidth.ToPx(ViewPos.X(), Edit->GetFont()) : DEFAULT_MINIMUM_SIZE_PX; int Offset = m.x - d->DragOffset.x - SplitPos.x1; if (Offset) { // Slide along the X axis // Limit to the min size LRect r = ViewPos; if (EditPrev) { r.x2 += Offset; int rx = r.X(); if (r.X() < MinPx) { int Diff = MinPx - rx; Offset += Diff; r.x2 += Diff; } } else { r.x1 += Offset; int rx = r.X(); if (r.X() < MinPx) { int Diff = MinPx - rx; Offset -= Diff; r.x1 -= Diff; } } if (Offset) { SplitPos.Offset(Offset, 0); // Save the new height of the view LCss::Len Wid = EditCss->Width(); if (Wid.Type == LCss::LenPercent && ContentPx > 0) { Wid.Value = (float)r.X() * 100 / ContentPx; } else { Wid.Type = LCss::LenPx; Wid.Value = (float)r.X(); } EditCss->Width(Wid); } } } OnPosChange(); Invalidate((LRect*)NULL, true); } int LBox::OnNotify(LViewI *Ctrl, LNotification n) { if (n.Type == LNotifyTableLayoutRefresh) { d->Dirty = true; #if LGI_VIEW_HANDLE if (Handle()) #endif PostEvent(M_CHILDREN_CHANGED); } return LView::OnNotify(Ctrl, n); } void LBox::OnChildrenChanged(LViewI *Wnd, bool Attaching) { #if 0 LgiTrace("LBox(%s)::OnChildrenChanged(%s, %i)\n", Name(), Wnd ? Wnd->GetClass() : NULL, Attaching); for (int i=0; iGetClass(), Children[i]->Handle(), Children[i]->Visible()); #endif d->Dirty = true; #if LGI_VIEW_HANDLE if (Handle()) #endif PostEvent(M_CHILDREN_CHANGED); } int64 LBox::Value() { LViewI *v = Children.Length() ? Children[0] : NULL; if (!v) return 0; LCss *css = v->GetCss(); if (!css) return 0; LCss::Len l = d->Vertical ? css->Height() : css->Width(); if (l.Type != LCss::LenPx) return 0; return (int64)l.Value; } void LBox::Value(int64 i) { LViewI *v = Children.Length() ? Children[0] : NULL; if (!v) return; LCss *css = v->GetCss(true); if (!css) return; if (d->Vertical) css->Height(LCss::Len(LCss::LenPx, (float)i)); else css->Width(LCss::Len(LCss::LenPx, (float)i)); OnPosChange(); } LCursor LBox::GetCursor(int x, int y) { Spacer *Over = d->HitTest(x, y); if (Over) return (d->Vertical) ? LCUR_SizeVer : LCUR_SizeHor; else return LCUR_Normal; } bool LBox::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = -1; Inf.Width.Max = -1; Inf.Height.Min = -1; Inf.Height.Max = -1; return true; } bool LBox::Serialize(LDom *Dom, const char *OptName, bool Write) { if (Write) { } else { } LAssert(0); return false; } bool LBox::SetSize(int ViewIndex, LCss::Len Size) { LViewI *v = Children[ViewIndex]; if (!v) return false; LCss *c = v->GetCss(true); if (!c) return false; c->Width(Size); return true; } LMessage::Result LBox::OnEvent(LMessage *Msg) { if (Msg->Msg() == M_CHILDREN_CHANGED) { if (d->Dirty) { d->Dirty = false; OnPosChange(); } } return LView::OnEvent(Msg); } diff --git a/src/common/Widgets/Button.cpp b/src/common/Widgets/Button.cpp --- a/src/common/Widgets/Button.cpp +++ b/src/common/Widgets/Button.cpp @@ -1,560 +1,565 @@ #if !defined(_WIN32) || (XP_BUTTON != 0) #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Button.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" #include "lgi/common/StringLayout.h" #include "lgi/common/CssTools.h" #define DOWN_MOUSE 0x1 #define DOWN_KEY 0x2 #if 0 #define DEBUG_LOG(...) printf(__VA_ARGS__) #else #define DEBUG_LOG(...) #endif // Size of extra pixels, beyond the size of the text itself. LPoint LButton::Overhead = LPoint( // Extra width needed #if defined(MAC) && !defined(LGI_SDL) 24, #else 16, #endif // Extra height needed 6 ); class LButtonPrivate : public LStringLayout { public: int Pressed; bool KeyDown; bool Over; bool WantsDefault; bool Toggle; LRect TxtSz; LSurface *Image; bool OwnImage; LButtonPrivate() : LStringLayout(LAppInst->GetFontCache()) { AmpersandToUnderline = true; Pressed = 0; KeyDown = false; Toggle = false; Over = 0; WantsDefault = false; Image = NULL; OwnImage = false; SetWrap(false); } ~LButtonPrivate() { if (OwnImage) DeleteObj(Image); } void Layout(LCss *css, const char *s) { Empty(); LCss c(*css); c.FontWeight(LCss::FontWeightBold); Add(s, &c); int32 MinX, MaxX; DoPreLayout(MinX, MaxX); DoLayout(MaxX); TxtSz = GetBounds(); } }; LButton::LButton(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_Button) { d = new LButtonPrivate; Name(name); LRect r(x, y, x + (cx <= 0 ? d->TxtSz.X() + Overhead.x : cx) - 1, y + (cy <= 0 ? d->TxtSz.Y() + Overhead.y : cy) - 1); - LAssert(r.Valid()); + LAssert(r.Valid()); SetPos(r); SetId(id); SetTabStop(true); } LButton::~LButton() { if (GetWindow() && GetWindow()->_Default == this) { GetWindow()->_Default = 0; } DeleteObj(d); } int LButton::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl == (LViewI*)this && n.Type == LNotifyActivate) { LMouse m; if (n.IsMouseEvent()) m = n.GetMouseEvent(); else GetMouse(m); OnClick(m); } return 0; } bool LButton::Default() { if (GetWindow()) return GetWindow()->_Default == this; return false; } void LButton::Default(bool b) { if (GetWindow()) { GetWindow()->_Default = b ? this : 0; if (IsAttached()) { Invalidate(); } } else { d->WantsDefault = b; } } bool LButton::GetIsToggle() { return d->Toggle; } void LButton::SetIsToggle(bool toggle) { d->Toggle = toggle; } LSurface *LButton::GetImage() { return d->Image; } bool LButton::SetImage(const char *FileName) { if (d->OwnImage) DeleteObj(d->Image); d->Image = GdcD->Load(FileName); Invalidate(); return d->OwnImage = d->Image != NULL; } bool LButton::SetImage(LSurface *Img, bool OwnIt) { if (d->OwnImage) DeleteObj(d->Image); d->Image = Img; d->OwnImage = d->Image != NULL && OwnIt; Invalidate(); return d->OwnImage; } void LButton::OnStyleChange() { d->Layout(GetCss(true), LBase::Name()); } bool LButton::Name(const char *n) { bool Status = LView::Name(n); OnStyleChange(); return Status; } bool LButton::NameW(const char16 *n) { bool Status = LView::NameW(n); OnStyleChange(); return Status; } void LButton::SetFont(LFont *Fnt, bool OwnIt) { LAssert(Fnt && Fnt->Handle()); LView::SetFont(Fnt, OwnIt); OnStyleChange(); Invalidate(); } LMessage::Result LButton::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } void LButton::OnMouseClick(LMouse &m) { if (!Enabled()) { DEBUG_LOG("Not enabled\n"); return; } if (d->Toggle) { DEBUG_LOG("OnMouseClick: Toggle=true, m.Down=%i\n", m.Down()); if (m.Down()) { Value(!Value()); OnClick(m); } } else { bool Click = IsCapturing(); DEBUG_LOG("OnMouseClick: Toggle=false, Click=%i, m.Down()=%i\n", Click, m.Down()); Capture(m.Down()); if (Click ^ m.Down()) { DEBUG_LOG("d->Over=%i\n", d->Over); if (d->Over) { if (m.Down()) { d->Pressed++; Focus(true); } else { d->Pressed--; } Invalidate(); DEBUG_LOG("m.Down()=%i d->Pressed=%i\n", m.Down(), d->Pressed); if (!m.Down() && d->Pressed == 0) { // This may delete ourself, so do it last. OnClick(m); } } } } } void LButton::OnMouseEnter(LMouse &m) { DEBUG_LOG("OnMouseEnter\n"); d->Over = true; if (IsCapturing()) { Value(d->Pressed + 1); } else if (Enabled()) { if (!LAppInst->SkinEngine) Invalidate(); } } void LButton::OnMouseExit(LMouse &m) { DEBUG_LOG("OnMouseExit\n"); d->Over = false; if (IsCapturing()) { Value(d->Pressed - 1); } else if (Enabled()) { if (!LAppInst->SkinEngine) Invalidate(); } } bool LButton::OnKey(LKey &k) { if ( #ifdef WINNATIVE k.IsChar || #endif !Enabled()) { return false; } switch (k.vkey) { case LK_ESCAPE: { if (GetId() != IDCANCEL) { break; } // else fall thru } case LK_SPACE: case LK_RETURN: #ifndef WINDOWS case LK_KEYPADENTER: #endif { if (d->KeyDown ^ k.Down()) { d->KeyDown = k.Down(); if (k.Down()) { d->Pressed++; } else { d->Pressed--; } Invalidate(); if (!k.Down() && d->Pressed == 0) { LMouse m; GetMouse(m); OnClick(m); } } return true; break; } } return false; } void LButton::OnClick(const LMouse &m) { SendNotify(LNotification(m)); } void LButton::OnFocus(bool f) { Invalidate(); } void LButton::OnPaint(LSurface *pDC) { #if defined LGI_CARBON LColour NoPaintColour = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); if (!NoPaintColour.IsTransparent()) { pDC->Colour(NoPaintColour); pDC->Rectangle(); } LRect rc = GetClient(); rc.x1 += 2; rc.y2 -= 1; rc.x2 -= 1; HIRect Bounds = rc; HIThemeButtonDrawInfo Info; HIRect LabelRect; Info.version = 0; Info.state = d->Pressed ? kThemeStatePressed : (Enabled() ? kThemeStateActive : kThemeStateInactive); Info.kind = kThemePushButton; Info.value = /*Default() ? kThemeButtonOn :*/ kThemeButtonOff; Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone; OSStatus e = HIThemeDrawButton( &Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal, &LabelRect); if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e); else { LPoint pt; LRect r = GetClient(); pt.x = r.x1 + ((r.X()-d->TxtSz.X())/2) + (d->Pressed != 0); pt.y = r.y1 + ((r.Y()-d->TxtSz.Y())/2) + (d->Pressed != 0); d->Paint(pDC, pt, LColour(), r, Enabled(), Info.state == kThemeStatePressed); } + + // #elif defined(HAIKU) + + // FIXME: + // Use BControlLook to do the drawing... #else if (LApp::SkinEngine && TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_BUTTON)) { LSkinState State; State.pScreen = pDC; State.MouseOver = d->Over; State.Image = d->Image; if (X() < GdcD->X() && Y() < GdcD->Y()) LApp::SkinEngine->OnPaint_LButton(this, &State); LPoint pt; LRect r = GetClient(); pt.x = r.x1 + ((r.X()-d->TxtSz.X())/2) + (d->Pressed != 0); pt.y = r.y1 + ((r.Y()-d->TxtSz.Y())/2) + (d->Pressed != 0); d->Paint(pDC, pt, LColour(), r, Enabled(), false); if (Focus()) { LRect r = GetClient(); r.Inset(5, 3); pDC->Colour(LColour(180, 180, 180)); pDC->LineStyle(LSurface::LineAlternate); pDC->Box(&r); } } else { LColour Back(d->Over ? L_HIGH : L_MED); LRect r(0, 0, X()-1, Y()-1); if (Default()) { pDC->Colour(L_BLACK); pDC->Box(&r); r.Inset(1, 1); } LWideBorder(pDC, r, d->Pressed ? DefaultSunkenEdge : DefaultRaisedEdge); LPoint pt; pt.x = r.x1 + ((r.X()-d->TxtSz.X())/2) + (d->Pressed != 0); pt.y = r.y1 + ((r.Y()-d->TxtSz.Y())/2) + (d->Pressed != 0); d->Paint(pDC, pt, Back, r, Enabled(), false); } #endif } int64 LButton::Value() { return d->Pressed != 0; } void LButton::Value(int64 i) { d->Pressed = (int)i; Invalidate(); } void LButton::OnCreate() { } void LButton::OnAttach() { LResources::StyleElement(this); OnStyleChange(); LView::OnAttach(); if (d->WantsDefault) { d->WantsDefault = false; if (GetWindow()) GetWindow()->_Default = this; } } void LButton::SetPreferredSize(int x, int y) { LRect r = GetPos(); int Ix = d->Image ? d->Image->X() : 0; int Iy = d->Image ? d->Image->Y() : 0; int Cx = d->TxtSz.X() + Ix + (d->TxtSz.X() && d->Image ? LTableLayout::CellSpacing : 0); int Cy = MAX(d->TxtSz.Y(), Iy); r.SetSize((x > 0 ? x : Cx + Overhead.x), (y > 0 ? y : Cy + Overhead.y)); SetPos(r); } bool LButton::OnLayout(LViewLayoutInfo &Inf) { LPoint Dpi(96, 96); auto Css = GetCss(); auto Font = GetFont(); LCssTools Tools(Css, Font); auto c = GetClient(); auto TxtMin = d->GetMin(); auto TxtMax = d->GetMax(); auto Wnd = GetWindow(); if (Wnd) Dpi = Wnd->GetDpi(); double Scale = (double)Dpi.x / 96.0; LRect DefaultPad(Scale*Overhead.x/2, Scale*Overhead.y/2, Scale*Overhead.x/2, Scale*Overhead.y/2); LRect Pad = Tools.GetPadding(c, &DefaultPad), Border = Tools.GetBorder(c); if (!Inf.Width.Min) { int BaseX = Pad.x1 + Pad.x2 + Border.x1 + Border.x2; int ImgX = d->Image ? d->Image->X() + 4/*img->text spacer*/ : 0; LCss::Len MinX, MaxX; if (Css) { MinX = Css->MinWidth(); MaxX = Css->MaxWidth(); } Inf.Width.Min = MinX.IsValid() ? MinX.ToPx(c.X(), Font) : BaseX + ImgX + TxtMin.x; Inf.Width.Max = MaxX.IsValid() ? MaxX.ToPx(c.X(), Font) : BaseX + ImgX + TxtMax.x; /* LgiTrace("%i.Layout.Btn.x = %i, %i valid=%i,%i c=%s, base=%i, img=%i\n", GetId(), Inf.Width.Min, Inf.Width.Max, MinX.IsValid(), MaxX.IsValid(), c.GetStr(), BaseX, ImgX); */ } else { int BaseY = Pad.y1 + Pad.y2 + Border.y1 + Border.y2; int ImgY = d->Image ? d->Image->Y() : 0; LCss::Len MinY, MaxY; if (Css) { MinY = Css->MinHeight(); MaxY = Css->MaxHeight(); } Inf.Height.Min = MinY.IsValid() ? MinY.ToPx(c.Y(), Font) : BaseY + MAX(ImgY, TxtMin.y); Inf.Height.Max = MaxY.IsValid() ? MaxY.ToPx(c.Y(), Font) : BaseY + MAX(ImgY, TxtMax.y); // LgiTrace("%i.Layout.Btn.y = %i, %i\n", GetId(), Inf.Height.Min, Inf.Height.Max); } return true; } #endif diff --git a/src/common/Widgets/CheckBox.cpp b/src/common/Widgets/CheckBox.cpp --- a/src/common/Widgets/CheckBox.cpp +++ b/src/common/Widgets/CheckBox.cpp @@ -1,462 +1,463 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/CheckBox.h" #include "lgi/common/DisplayString.h" #include "lgi/common/StringLayout.h" #include "lgi/common/LgiRes.h" static int PadX1Px = 20; static int PadX2Px = 6; #ifdef MAC static int PadYPx = 8; static int TextYOffset = 6; #else static int PadYPx = 0; static int TextYOffset = 0; #endif static int MinYSize = 16; class LCheckBoxPrivate : public LMutex, public LStringLayout { LCheckBox *Ctrl; public: int64 Val; bool Over; bool Three; LRect ValuePos; LCheckBoxPrivate(LCheckBox *ctrl) : LMutex("LCheckBoxPrivate"), LStringLayout(LAppInst->GetFontCache()) { Ctrl = ctrl; Val = 0; Over = false; Three = false; Wrap = true; AmpersandToUnderline = true; ValuePos.ZOff(-1, -1); } bool PreLayout(int32 &Min, int32 &Max) { if (Lock(_FL)) { DoPreLayout(Min, Max); Unlock(); } else return false; return true; } bool Layout(int Px) { if (Lock(_FL)) { DoLayout(Px, MinYSize); Unlock(); } else return false; return true; } }; /////////////////////////////////////////////////////////////////////////////////////////// // Check box LCheckBox::LCheckBox(int id, int x, int y, int cx, int cy, const char *name, int InitState) : ResObject(Res_CheckBox) { d = new LCheckBoxPrivate(this); Name(name); LPoint Max = d->GetMax(); if (cx < 0) cx = Max.x + PadX1Px + PadX2Px; if (cy < 0) cy = MAX(Max.y, MinYSize) + PadYPx; d->Val = InitState; LRect r(x, y, x+cx, y+cy); SetPos(r); SetId(id); SetTabStop(true); } LCheckBox::~LCheckBox() { DeleteObj(d); } #ifdef WINNATIVE int LCheckBox::SysOnNotify(int Msg, int Code) { return 0; } #endif void LCheckBox::OnAttach() { LResources::StyleElement(this); OnStyleChange(); LView::OnAttach(); } void LCheckBox::OnStyleChange() { if (d->Lock(_FL)) { d->Empty(); d->Add(LView::Name(), GetCss()); d->DoLayout(X()); d->Unlock(); Invalidate(); } } int LCheckBox::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl == (LViewI*)this && n.Type == LNotifyActivate) { Value(!Value()); } return 0; } LMessage::Result LCheckBox::OnEvent(LMessage *m) { return LView::OnEvent(m); } bool LCheckBox::ThreeState() { return d->Three; } void LCheckBox::ThreeState(bool t) { d->Three = t; } int64 LCheckBox::Value() { return d->Val; } void LCheckBox::Value(int64 i) { if (d->Val != i) { d->Val = i; Invalidate(&d->ValuePos); SendNotify(LNotifyValueChanged); } } bool LCheckBox::Name(const char *n) { bool Status = false; if (d->Lock(_FL)) { Status = LView::Name(n); d->Empty(); d->Add(n, GetCss()); d->SetBaseFont(GetFont()); auto x = X(); d->DoLayout(x ? x : GdcD->X()); d->Unlock(); } return Status; } bool LCheckBox::NameW(const char16 *n) { bool Status = false; if (d->Lock(_FL)) { Status = LView::NameW(n); d->Empty(); d->Add(LBase::Name(), GetCss()); d->SetBaseFont(GetFont()); auto x = X(); d->DoLayout(x ? x : GdcD->X()); d->Unlock(); } return Status; } void LCheckBox::SetFont(LFont *Fnt, bool OwnIt) { LAssert(Fnt && Fnt->Handle()); if (d->Lock(_FL)) { LView::SetFont(Fnt, OwnIt); d->Unlock(); } d->Layout(X()); Invalidate(); } void LCheckBox::OnMouseClick(LMouse &m) { if (Enabled()) { int Click = IsCapturing(); Capture(d->Over = m.Down()); if (m.Down()) { Focus(true); } LRect r(0, 0, X()-1, Y()-1); if (!m.Down() && r.Overlap(m.x, m.y) && Click) { if (d->Three) { switch (d->Val) { case 0: Value(2); break; case 2: Value(1); break; default: Value(0); break; } } else { Value(!d->Val); } } else { Invalidate(&d->ValuePos); } } } void LCheckBox::OnMouseEnter(LMouse &m) { if (IsCapturing()) { d->Over = true; Invalidate(&d->ValuePos); } } void LCheckBox::OnMouseExit(LMouse &m) { if (IsCapturing()) { d->Over = false; Invalidate(&d->ValuePos); } } bool LCheckBox::OnKey(LKey &k) { switch (k.vkey) { case LK_SPACE: { if (!k.Down()) Value(!Value()); return true; } } return false; } void LCheckBox::OnFocus(bool f) { Invalidate(); } void LCheckBox::OnPosChange() { d->Layout(X()); } bool LCheckBox::OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Min) { auto n = Name(); if (n) { d->PreLayout(Inf.Width.Min, Inf.Width.Max); // FIXME: no wrapping support so.... Inf.Width.Min = Inf.Width.Max; Inf.Width.Min += PadX1Px + PadX2Px; Inf.Width.Max += PadX1Px + PadX2Px; } else { Inf.Width.Min = Inf.Width.Max = BoxSize(); } } else { d->Layout(Inf.Width.Max); Inf.Height.Min = d->GetMin().y + PadYPx; Inf.Height.Max = d->GetMax().y + PadYPx; } return true; } int LCheckBox::BoxSize() { auto Fnt = GetFont(); int Px = (int) Fnt->Ascent() + 2; - if (Px > Y()) Px = Y(); + if (Px > Y()) + Px = Y(); return Px; } void LCheckBox::OnPaint(LSurface *pDC) { #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif if (LApp::SkinEngine && TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_CHECKBOX)) { // auto Fnt = GetFont(); int Px = BoxSize(); LSkinState State; State.pScreen = pDC; State.MouseOver = d->Over; State.aText = d->GetStrs(); d->ValuePos.Set(0, 0, Px-1, Px-1); d->ValuePos.Offset(0, (Y()-d->ValuePos.Y())>>1); State.Rect = d->ValuePos; LApp::SkinEngine->OnPaint_LCheckBox(this, &State); } else { bool en = Enabled(); LRect r = GetClient(); #if defined MAC && !LGI_COCOA d->ValuePos.Set(0, 0, PadX1Px, MinYSize); #else d->ValuePos.Set(0, 0, 12, 12); #endif if (d->ValuePos.y2 < r.y2) { pDC->Colour(L_MED); pDC->Rectangle(0, d->ValuePos.y2+1, d->ValuePos.x2, r.y2); } LRect t = r; t.x1 = d->ValuePos.x2 + 1; // LColour cFore = StyleColour(LCss::PropColor, LC_TEXT); LColour cBack = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); if (d->Lock(_FL)) { int Ty = MAX(0, r.Y() - d->GetBounds().Y()) >> 1; LPoint pt(t.x1, t.y1 + MIN(Ty, TextYOffset)); d->Paint(pDC, pt, cBack, t, en, false); d->Unlock(); } #if defined LGI_CARBON if (!cBack.IsTransparent()) { pDC->Colour(cBack); pDC->Rectangle(d->ValuePos.x1, d->ValuePos.y1, d->ValuePos.x2, Y()-1); } LRect c = GetClient(); #if 0 pDC->Colour(LColour(255, 0, 0)); pDC->Box(&c); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x2, c.y1, c.x1, c.y2); #endif for (LViewI *v = this; v && !v->Handle(); v = v->GetParent()) { LRect p = v->GetPos(); c.Offset(p.x1, p.y1); } HIRect Bounds = c; HIThemeButtonDrawInfo Info; HIRect LabelRect; Info.version = 0; Info.state = d->Val ? kThemeStatePressed : (Enabled() ? kThemeStateActive : kThemeStateInactive); Info.kind = kThemeCheckBox; Info.value = d->Val ? kThemeButtonOn : kThemeButtonOff; Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone; OSStatus e = HIThemeDrawButton( &Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal, &LabelRect); if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e); #else LWideBorder(pDC, d->ValuePos, DefaultSunkenEdge); pDC->Colour(d->Over || !en ? L_MED : L_WORKSPACE); pDC->Rectangle(&d->ValuePos); pDC->Colour(en ? L_TEXT : L_LOW); if (d->Three && d->Val == 2) { for (int y=d->ValuePos.y1; y<=d->ValuePos.y2; y++) { for (int x=d->ValuePos.x1; x<=d->ValuePos.x2; x++) { if ( (x&1) ^ (y&1) ) { pDC->Set(x, y); } } } } else if (d->Val) { pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-1); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+2, d->ValuePos.x2-2, d->ValuePos.y2-1); pDC->Line(d->ValuePos.x1+2, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-2); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+1); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-2, d->ValuePos.x2-2, d->ValuePos.y1+1); pDC->Line(d->ValuePos.x1+2, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+2); } #endif } } diff --git a/src/common/Widgets/ControlTree.cpp b/src/common/Widgets/ControlTree.cpp --- a/src/common/Widgets/ControlTree.cpp +++ b/src/common/Widgets/ControlTree.cpp @@ -1,638 +1,642 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/ControlTree.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/FileSelect.h" #define IDC_BROWSE -10 class LControlTreePriv { public: LResources *Factory; LControlTreePriv() { Factory = 0; } }; LControlTree::Item::Item(int ctrlId, char *Txt, const char *opt, LVariantType type, LArray *pEnum) { if (ValidStr(opt)) Opt.Reset(NewStr(opt)); CtrlId = ctrlId; Enum.Reset(pEnum); SetText(Txt); Type = type; Ctrl = 0; Browse = 0; } LControlTree::Item::~Item() { } void LControlTree::Item::SetEnum(LAutoPtr e) { Enum = e; Type = GV_INT32; } void LControlTree::Item::OnVisible(bool v) { if (Ctrl) { if (v) PositionControls(); else { Ctrl->Visible(false); if (Browse) Browse->Visible(false); } } } LControlTree::Item *LControlTree::Item::Find(const char *opt) { if (Opt && !_stricmp(Opt, opt)) { return this; } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { LControlTree::Item *f = ci->Find(opt); if (f) return f; } } return 0; } bool LControlTree::Item::Serialize(LDom *Store, bool Write) { if (Opt) { if (Write) { Save(); Store->SetValue(Opt, Value); } else { Store->GetValue(Opt, Value); } } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { ci->Serialize(Store, Write); } } return true; } void LControlTree::Item::SetValue(LVariant &v) { Value = v; if (LTreeItem::Select()) { DeleteObj(Ctrl); DeleteObj(Browse); Select(true); } else Update(); } LRect &LControlTree::Item::GetRect() { static LRect r; r.ZOff(-1, -1); LRect *p = _GetRect(TreeItemText); if (p) { bool HasBrowse = (Flags & TYPE_FILE) != 0; int x = p->x2 + 5; r.x1 = x; r.x2 = GetTree()->GetPos().X() - (HasBrowse ? 50 : 10); int Cy = Ctrl ? Ctrl->Y() : 16; r.y1 = (p->y1 + (p->Y()/2)) - (Cy / 2); r.y2 = r.y1 + Cy - 1; } return r; } void LControlTree::Item::Save() { if (Ctrl) { switch (Type) { case GV_STRING: { auto v = Ctrl->Name(); if (Stricmp(v, Value.Str())) { Value = v; Tree->SendNotify(LNotifyValueChanged); } break; } case GV_BOOL: { bool b = Ctrl->Value() != 0; if (b != (Value.CastInt32() != 0)) { Value = b; Tree->SendNotify(LNotifyValueChanged); } break; } default: { int Idx = (int)Ctrl->Value(); if (Enum && Enum->Length()) { if (Idx >= 0 && Idx < (int)Enum->Length()) { LControlTree::EnumValue &e = (*Enum)[Idx]; if (e.Value.Type == GV_STRING) { if (Stricmp(Value.Str(), e.Value.Str())) { Value = e.Value; Tree->SendNotify(LNotifyValueChanged); } } else if (Idx != Value.CastInt32()) { Value = Idx; Tree->SendNotify(LNotifyValueChanged); } } else LAssert(0); } else if (Idx != Value.CastInt32()) { Value = Idx; Tree->SendNotify(LNotifyValueChanged); } break; } } } } void LControlTree::Item::Select(bool b) { LTreeItem::Select(b); if ((Ctrl != 0) ^ b) { if (b) { LAssert(Ctrl == 0); int FontY = LSysFont->GetHeight(); int CtrlY = FontY + (FontY >> 1); switch (Type) { default: break; case GV_STRING: if ((Ctrl = new LEdit(CtrlId, 0, 0, 200, CtrlY, 0))) Ctrl->Name(Value.Str()); if (Flags & TYPE_FILE) Browse = new LButton(IDC_BROWSE, 0, 0, -1, CtrlY, "..."); break; case GV_BOOL: if ((Ctrl = new LCheckBox(CtrlId, 0, 0, 14, 16, 0))) Ctrl->Value(Value.CastInt32()); break; case GV_INT32: if (Enum) { LCombo *Cbo; if ((Ctrl = (Cbo = new LCombo(CtrlId, 0, 0, 120, CtrlY, 0)))) { int Idx = -1; for (unsigned i=0; iLength(); i++) { EnumValue &e = (*Enum)[i]; Cbo->Insert(e.Name); if (e.Value == Value) Idx = i; } if (Idx >= 0) Ctrl->Value(Idx); } } else { if ((Ctrl = new LEdit(CtrlId, 0, 0, 60, CtrlY, 0))) Ctrl->Value(Value.CastInt32()); } break; } if (Ctrl) { LColour Ws = LColour(L_WORKSPACE); Ctrl->SetColour(Ws, false); Ctrl->Visible(false); Ctrl->Attach(GetTree()); if (Browse) { Browse->SetColour(Ws, false); Browse->Visible(false); Browse->Attach(GetTree()); } PositionControls(); } } else if (Ctrl) { Save(); DeleteObj(Ctrl); DeleteObj(Browse); } Update(); } } void LControlTree::Item::PositionControls() { if (Ctrl) { LRect r = GetRect(); Ctrl->SetPos(r); Ctrl->Visible(true); if (Browse) { LRect b = Browse->GetPos(); b.Offset(r.x2 + 5 - b.x1, r.y1 - b.y1); Browse->SetPos(b); Browse->Visible(true); } } } void LControlTree::Item::OnPaint(ItemPaintCtx &Ctx) { LTreeItem::OnPaint(Ctx); if (!Ctrl) { LCssTools Tools(GetTree()); auto Ws = LColour(L_WORKSPACE); LSysBold->Colour(Tools.GetFore(), Tools.GetBack(&Ws, 0)); LSysBold->Transparent(true); LRect p = GetRect(); switch (Type) { default: break; case GV_INT32: { char s[32], *Disp = 0; if (Enum) { for (unsigned i=0; iLength(); i++) { EnumValue &e = (*Enum)[i]; #if 0 LString s1 = e.Value.ToString(); LString s2 = Value.ToString(); LgiTrace("EnumMatch %s: %s - %s\n", e.Name, s1.Get(), s2.Get()); #endif if (e.Value == Value) { Disp = e.Name; break; } } if (Disp) { LDisplayString ds(LSysBold, Disp); ds.Draw(Ctx.pDC, p.x1 + 8, p.y1 + 1); } else { LDisplayString ds(LSysFont, LLoadString(L_CONTROLTREE_NO_VALUE, "(no value)")); LSysFont->Colour(LColour(L_LOW), LColour(L_WORKSPACE)); ds.Draw(Ctx.pDC, p.x1 + 8, p.y1 + 1); } } else { sprintf_s(Disp = s, sizeof(s), "%i", Value.CastInt32()); LDisplayString ds(LSysBold, Disp); ds.Draw(Ctx.pDC, p.x1 + 6, p.y1 + 2); } break; } case GV_STRING: { LDisplayString ds(LSysBold, Value.Str()); ds.Draw(Ctx.pDC, p.x1 + 6, p.y1 + 2); break; } case GV_BOOL: { LDisplayString ds(LSysBold, (char*) (Value.CastInt32() ? "true" : "false")); ds.Draw(Ctx.pDC, p.x1 + 1, p.y1 + 1); break; } } } else { PositionControls(); } } /////////////////////////////////////////////////////////////////////// LControlTree::LControlTree() : LTree(-1, 0, 0, 100, 100) { _ObjName = Res_ControlTree; d = new LControlTreePriv; } LControlTree::~LControlTree() { DeleteObj(d); } LControlTree::Item *LControlTree::Find(const char *opt) { for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { LControlTree::Item *f = ci->Find(opt); if (f) return f; } } return 0; } bool LControlTree::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { switch (LStringToDomProp(MethodName)) { case ControlSerialize: { if (Args.Length() != 2) { LAssert(!"Wrong argument count."); return false; } auto *Store = Args[0]->CastDom(); if (!Store) { LAssert(!"Missing store object."); return false; } auto Write = Args[1]->CastInt32() != 0; *ReturnValue = Serialize(Store, Write); return true; } default: break; } return false; } bool LControlTree::Serialize(LDom *Store, bool Write) { bool Error = false; if (!Store) { LAssert(!"Invalid param."); return false; } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) Error = Error | ci->Serialize(Store, Write); else LAssert(!"Not a control tree item."); } return !Error; } LControlTree::Item *LControlTree::Resolve(bool Create, const char *Path, int CtrlId, LVariantType Type, LArray *Enum) { auto t = LString(Path).SplitDelimit("."); if (t.Length() > 0) { LTreeNode *Cur = this; for (unsigned i=0; iGetChild(); c; c = c->GetNext()) { const char *s = c->GetText(); if (s && _stricmp(t[i], s) == 0) { Match = c; break; } } if (Match) { Cur = Match; } else { if (Create && i == t.Length() - 1) { LControlTree::Item *Ci = new LControlTree::Item(CtrlId, t[i], Path, Type, Enum); if (Ci) { Cur->Insert(Ci); LTreeItem *p = Ci->GetParent(); if (p) p->Expanded(true); return Ci; } } return 0; } } return dynamic_cast(Cur); } return 0; } LTreeItem *LControlTree::Insert(const char *DomPath, int CtrlId, LVariantType Type, LVariant *Value, LArray *Enum) { LControlTree::Item *c = Resolve(true, DomPath, CtrlId, Type, Enum); if (c) { if (Value) c->SetValue(*Value); } return 0; } void LControlTree::ReadTree(LXmlTag *t, LTreeNode *n) { for (auto c: t->Children) { int CtrlId = -1; int StrRef = c->GetAsInt("ref"); LStringRes *Str = d->Factory->StrFromRef(StrRef); LAssert(Str != NULL); if (!Str) continue; CtrlId = Str->Id; char *Type = c->GetAttr("ControlType"); LVariantType iType = GV_NULL; int Flags = 0; if (Type) { if (!_stricmp(Type, "string")) iType = GV_STRING; else if (!_stricmp(Type, "file")) { iType = GV_STRING; Flags |= LControlTree::Item::TYPE_FILE; } else if (!_stricmp(Type, "bool")) iType = GV_BOOL; else if (!_stricmp(Type, "int") || !_stricmp(Type, "enum")) iType = GV_INT32; } const char *Opt = c->GetAttr("ControlTag"); LControlTree::Item *ct = new LControlTree::Item(CtrlId, Str?Str->Str:(char*)"#error", ValidStr(Opt) ? Opt : NULL, iType, 0); if (ct) { ct->Flags = Flags; n->Insert(ct); ReadTree(c, ct); ct->Expanded(true); } } } bool LControlTree::SetVariant(const char *Name, LVariant &Value, const char *Array) { if (!Name) return false; if (!_stricmp(Name, "Tree")) { if (Value.Type != GV_DOM) return false; Empty(); LXmlTag *x = dynamic_cast(Value.Value.Dom); if (!x) LAssert(!"Not the right object."); else if (d->Factory) ReadTree(x, this); else LAssert(!"No factory."); d->Factory = 0; } else if (!_stricmp(Name, "LgiFactory")) { d->Factory = dynamic_cast((ResFactory*)Value.CastVoidPtr()); } else return false; return true; } int LControlTree::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_BROWSE: { Item *i = dynamic_cast(Selection()); if (i) { - LFileSelect s; - s.Parent(this); - if (s.Open()) + auto s = new LFileSelect; + s->Parent(this); + s->Open([i](auto dlg, auto status) { - LVariant v; - i->SetValue(v = s.Name()); - } + if (status) + { + LVariant v; + i->SetValue(v = dlg->Name()); + } + delete dlg; + }); } return 0; } } return LTree::OnNotify(c, n); } /////////////////////////////////////////////////////////////////////////////// class LControlTree_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LControlTree") == 0) { return new LControlTree; } return 0; } } ControlTree_Factory; diff --git a/src/common/Widgets/Editor/RichTextEdit.cpp b/src/common/Widgets/Editor/RichTextEdit.cpp --- a/src/common/Widgets/Editor/RichTextEdit.cpp +++ b/src/common/Widgets/Editor/RichTextEdit.cpp @@ -1,3064 +1,3098 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #ifdef WIN32 #include #endif #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/FontCache.h" #include "lgi/common/Unicode.h" #include "lgi/common/DropFiles.h" #include "lgi/common/HtmlCommon.h" #include "lgi/common/HtmlParser.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/Homoglyphs.h" // If this is not found add $lgi/private/common to your include paths #include "ViewPriv.h" #define DefaultCharset "utf-8" #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define ALLOC_BLOCK 64 #define IDC_VS 1000 #define PAINT_BORDER Back #define PAINT_AFTER_LINE Back #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif // static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #include "RichTextEditPriv.h" ////////////////////////////////////////////////////////////////////// LRichTextEdit::LRichTextEdit( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(new LRichTextPriv(this, &d)); // setup window SetId(Id); SetTabStop(true); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else CrLf = false; #endif d->Padding(LCss::Len(LCss::LenPx, 4)); #if 0 d->BackgroundColor(LCss::ColorDef(LColour::Green)); #else d->BackgroundColor(LColour(L_WORKSPACE)); #endif SetFont(LSysFont); #if 0 // def _DEBUG Name("\n" "\n" " This is some bold text to test with.
\n" " A second line of text for testing.\n" "\n" "\n"); #endif } LRichTextEdit::~LRichTextEdit() { // 'd' is owned by the LView CSS autoptr. } bool LRichTextEdit::SetSpellCheck(LSpellCheck *sp) { if ((d->SpellCheck = sp)) { if (IsAttached()) d->SpellCheck->EnumLanguages(AddDispatch()); // else call that OnCreate } return d->SpellCheck != NULL; } bool LRichTextEdit::IsDirty() { return d->Dirty; } void LRichTextEdit::IsDirty(bool dirty) { if (d->Dirty ^ dirty) { d->Dirty = dirty; } } void LRichTextEdit::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LDocView::SetFixedWidthFont(i); } } OnFontChange(); Invalidate(); } } void LRichTextEdit::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } LRect LRichTextEdit::GetArea(RectType Type) { return Type >= ContentArea && Type <= MaxArea ? d->Areas[Type] : LRect(0, 0, -1, -1); } bool LRichTextEdit::ShowStyleTools() { return d->ShowTools; } void LRichTextEdit::ShowStyleTools(bool b) { if (d->ShowTools ^ b) { d->ShowTools = b; Invalidate(); } } void LRichTextEdit::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LRichTextEdit::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); OnPosChange(); Invalidate(); } LFont *LRichTextEdit::GetFont() { return d->Font; } void LRichTextEdit::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { d->Font.Reset(f); } else if (d->Font.Reset(new LFont)) { *d->Font = *f; d->Font->Create(NULL, 0, 0); } OnFontChange(); } void LRichTextEdit::OnFontChange() { } void LRichTextEdit::PourText(ssize_t Start, ssize_t Length /* == 0 means it's a delete */) { } void LRichTextEdit::PourStyle(ssize_t Start, ssize_t EditSize) { } bool LRichTextEdit::Insert(int At, char16 *Data, int Len) { return false; } bool LRichTextEdit::Delete(int At, int Len) { return false; } bool LRichTextEdit::DeleteSelection(char16 **Cut) { AutoTrans t(new LRichTextPriv::Transaction); if (!d->DeleteSelection(t, Cut)) return false; return d->AddTrans(t); } int64 LRichTextEdit::Value() { const char *n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LRichTextEdit::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } bool LRichTextEdit::GetFormattedContent(const char *MimeType, LString &Out, LArray *Media) { if (!MimeType || _stricmp(MimeType, "text/html")) return false; if (!d->ToHtml(Media)) return false; Out = d->UtfNameCache; return true; } const char *LRichTextEdit::Name() { d->ToHtml(); return d->UtfNameCache; } const char *LRichTextEdit::GetCharset() { return d->Charset; } void LRichTextEdit::SetCharset(const char *s) { d->Charset = s; } bool LRichTextEdit::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { Value = d->HtmlLinkAsCid; break; } case SpellCheckLanguage: { Value = d->SpellLang.Get(); break; } case SpellCheckDictionary: { Value = d->SpellDict.Get(); break; } default: return false; } return true; } bool LRichTextEdit::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { d->HtmlLinkAsCid = Value.CastInt32() != 0; break; } case SpellCheckLanguage: { d->SpellLang = Value.Str(); break; } case SpellCheckDictionary: { d->SpellDict = Value.Str(); break; } default: return false; } return true; } static LHtmlElement *FindElement(LHtmlElement *e, HtmlTag TagId) { if (e->TagId == TagId) return e; for (unsigned i = 0; i < e->Children.Length(); i++) { LHtmlElement *c = FindElement(e->Children[i], TagId); if (c) return c; } return NULL; } void LRichTextEdit::OnAddStyle(const char *MimeType, const char *Styles) { if (d->CreationCtx) { d->CreationCtx->StyleStore.Parse(Styles); } } bool LRichTextEdit::Name(const char *s) { d->Empty(); d->OriginalText = s; LHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new LRichTextPriv::CreateContext(d))) return false; if (!d->LHtmlParser::Parse(&Root, s)) return d->Error(_FL, "Failed to parse HTML."); LHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; bool Status = d->FromHtml(Body, *d->CreationCtx); // d->DumpBlocks(); if (!d->Blocks.Length()) { d->EmptyDoc(); } else { // Clear out any zero length blocks. for (unsigned i=0; iBlocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; if (b->Length() == 0) { d->Blocks.DeleteAt(i--, true); DeleteObj(b); } } } if (Status) SetCursor(0, false); Invalidate(); return Status; } const char16 *LRichTextEdit::NameW() { d->WideNameCache.Reset(Utf8ToWide(Name())); return d->WideNameCache; } bool LRichTextEdit::NameW(const char16 *s) { LAutoString a(WideToUtf8(s)); return Name(a); } char *LRichTextEdit::GetSelection() { if (!HasSelection()) return NULL; LArray Text; if (!d->GetSelection(&Text, NULL)) return NULL; return WideToUtf8(&Text[0]); } bool LRichTextEdit::HasSelection() { return d->Selection.Get() != NULL; } void LRichTextEdit::SelectAll() { AutoCursor Start(new BlkCursor(d->Blocks.First(), 0, 0)); d->SetCursor(Start); LRichTextPriv::Block *Last = d->Blocks.Length() ? d->Blocks.Last() : NULL; if (Last) { AutoCursor End(new BlkCursor(Last, Last->Length(), Last->GetLines()-1)); d->SetCursor(End, true); } else d->Selection.Reset(); Invalidate(); } void LRichTextEdit::UnSelectAll() { bool Update = HasSelection(); if (Update) { d->Selection.Reset(); Invalidate(); } } void LRichTextEdit::SetStylePrefix(LString s) { d->SetPrefix(s); } bool LRichTextEdit::IsBusy(bool Stop) { return d->IsBusy(Stop); } size_t LRichTextEdit::GetLines() { uint32_t Count = 0; for (size_t i=0; iBlocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; Count += b->GetLines(); } return Count; } int LRichTextEdit::GetLine() { if (!d->Cursor) return -1; ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LAssert(0); return -1; } int Count = 0; // Count lines in blocks before the cursor... for (int i=0; iBlocks[i]; Count += b->GetLines(); } // Add the lines in the cursor's block... if (d->Cursor->LineHint) { Count += d->Cursor->LineHint; } else { LArray BlockLine; if (d->Cursor->Blk->OffsetToLine(d->Cursor->Offset, NULL, &BlockLine)) Count += BlockLine.First(); else { // Hmmm... LAssert(!"Can't find block line."); return -1; } } return Count; } void LRichTextEdit::SetLine(int i) { int Count = 0; // Count lines in blocks before the cursor... for (int i=0; i<(int)d->Blocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; int Lines = b->GetLines(); if (i >= Count && i < Count + Lines) { auto BlockLine = i - Count; auto Offset = b->LineToOffset(BlockLine); if (Offset >= 0) { AutoCursor c(new BlkCursor(b, Offset, BlockLine)); d->SetCursor(c); break; } } Count += Lines; } } void LRichTextEdit::GetTextExtent(int &x, int &y) { x = d->DocumentExtent.x; y = d->DocumentExtent.y; } bool LRichTextEdit::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t Offset = -1; int BlockLines = -1; LRichTextPriv::Block *b = d->GetBlockByIndex(Index, &Offset, NULL, &BlockLines); if (!b) return false; int Cols; LArray Lines; if (b->OffsetToLine(Offset, &Cols, &Lines)) return false; Pt.x = Cols; Pt.y = BlockLines + Lines.First(); return true; } ssize_t LRichTextEdit::GetCaret(bool Cur) { if (!d->Cursor) return -1; ssize_t CharPos = 0; for (ssize_t i=0; i<(ssize_t)d->Blocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; if (d->Cursor->Blk == b) return CharPos + d->Cursor->Offset; CharPos += b->Length(); } LAssert(!"Cursor block not found."); return -1; } bool LRichTextEdit::IndexAt(int x, int y, ssize_t &Off, int &LineHint) { LPoint Doc = d->ScreenToDoc(x, y); Off = d->HitTest(Doc.x, Doc.y, LineHint); return Off >= 0; } ssize_t LRichTextEdit::IndexAt(int x, int y) { ssize_t Idx; int Line; if (!IndexAt(x, y, Idx, Line)) return -1; return Idx; } void LRichTextEdit::SetCursor(int i, bool Select, bool ForceFullUpdate) { ssize_t Offset = -1; LRichTextPriv::Block *Blk = d->GetBlockByIndex(i, &Offset); if (Blk) { AutoCursor c(new BlkCursor(Blk, Offset, -1)); if (c) d->SetCursor(c, Select); } } bool LRichTextEdit::Cut() { if (!HasSelection()) return false; char16 *Txt = NULL; if (!DeleteSelection(&Txt)) return false; bool Status = true; if (Txt) { LClipBoard Cb(this); Status = Cb.TextW(Txt); DeleteArray(Txt); } SendNotify(LNotifyDocChanged); return Status; } bool LRichTextEdit::Copy() { if (!HasSelection()) return false; LArray PlainText; LAutoString Html; if (!d->GetSelection(&PlainText, &Html)) return false; LString PlainUtf8 = PlainText.AddressOf(); if (HasHomoglyphs(PlainUtf8, PlainUtf8.Length())) { if (LgiMsg( this, "Text contains homoglyph characters that maybe a phishing attack.\n" "Do you really want to copy it?", "Warning", MB_YESNO) == IDNO) return false; } // Put on the clipboard LClipBoard Cb(this); bool Status = Cb.TextW(PlainText.AddressOf()); Cb.Html(Html, false); return Status; } bool LRichTextEdit::Paste() { LString Html; LAutoWString Text; LAutoPtr Img; LClipBoard Cb(this); return Cb.Bitmap([&](auto bmp, auto str) { Img = bmp; if (!Img) { Html = Cb.Html(); if (!Html) Text.Reset(NewStrW(Cb.TextW())); } if (!Html && !Text && !Img) return false; if (!d->Cursor || !d->Cursor->Blk) { LAssert(0); return false; } AutoTrans Trans(new LRichTextPriv::Transaction); if (HasSelection()) { if (!d->DeleteSelection(Trans, NULL)) return false; } if (Html) { LHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new LRichTextPriv::CreateContext(d))) return false; if (!d->LHtmlParser::Parse(&Root, Html)) return d->Error(_FL, "Failed to parse HTML."); LHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; if (d->Cursor) { auto *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); LRichTextPriv::Block *After = NULL; ssize_t AddIndex = BlkIdx;; // Split 'b' to make room for pasted objects if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } // else Insert before cursor block auto *PastePoint = new LRichTextPriv::TextBlock(d); if (PastePoint) { d->Blocks.AddAt(AddIndex++, PastePoint); if (After) d->Blocks.AddAt(AddIndex++, After); d->CreationCtx->Tb = PastePoint; d->FromHtml(Body, *d->CreationCtx); } } } else if (Text) { LAutoPtr Utf32((uint32_t*)LNewConvertCp("utf-32", Text, LGI_WideCharset)); ptrdiff_t Len = Strlen(Utf32.Get()); if (!d->Cursor->Blk->AddText(Trans, d->Cursor->Offset, Utf32.Get(), (int)Len)) { LAssert(0); return false; } d->Cursor->Offset += Len; d->Cursor->LineHint = -1; } else if (Img) { LRichTextPriv::Block *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); LRichTextPriv::Block *After = NULL; ssize_t AddIndex; LAssert(BlkIdx >= 0); // Split 'b' to make room for the image if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } LRichTextPriv::ImageBlock *ImgBlk = new LRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); Img->MakeOpaque(); ImgBlk->SetImage(Img); AutoCursor c(new BlkCursor(ImgBlk, 1, -1)); d->SetCursor(c); } } Invalidate(); SendNotify(LNotifyDocChanged); return d->AddTrans(Trans); }); } bool LRichTextEdit::ClearDirty(bool Ask, const char *FileName) { if (1 /*dirty*/) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { - LFileSelect Select; - Select.Parent(this); - if (!FileName && - Select.Save()) + if (FileName) + Save(FileName); + else { - FileName = Select.Name(); + LFileSelect *Select = new LFileSelect; + Select->Parent(this); + Select->Save([&](auto dlg, auto status) + { + if (status) + FileName = dlg->Name(); + delete dlg; + }); } - - Save(FileName); } else if (Answer == IDCANCEL) { return false; } } return true; } bool LRichTextEdit::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { size_t Bytes = (size_t)f.GetSize(); SetCursor(0, false); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } } DeleteArray(c8); } else { } Invalidate(); } return Status; } bool LRichTextEdit::Save(const char *FileName, const char *CharSet) { LFile f; if (!FileName || !f.Open(FileName, O_WRITE)) return false; f.SetSize(0); const char *Nm = Name(); if (!Nm) return false; size_t Len = strlen(Nm); return f.Write(Nm, (int)Len) == Len; } void LRichTextEdit::UpdateScrollBars(bool Reset) { if (VScroll) { //LRect Before = GetClient(); } } -bool LRichTextEdit::DoCase(bool Upper) +void LRichTextEdit::DoCase(std::function Callback, bool Upper) { if (!HasSelection()) - return false; + { + if (Callback) + Callback(false); + return; + } bool Cf = d->CursorFirst(); LRichTextPriv::BlockCursor *Start = Cf ? d->Cursor : d->Selection; LRichTextPriv::BlockCursor *End = Cf ? d->Selection : d->Cursor; if (Start->Blk == End->Blk) { // In the same block... ssize_t Len = End->Offset - Start->Offset; Start->Blk->DoCase(NoTransaction, Start->Offset, Len, Upper); } else { // Multi-block delete... // 1) Delete all the content to the end of the first block ssize_t StartLen = Start->Blk->Length(); if (Start->Offset < StartLen) Start->Blk->DoCase(NoTransaction, Start->Offset, StartLen - Start->Offset, Upper); // 2) Delete any blocks between 'Start' and 'End' ssize_t i = d->Blocks.IndexOf(Start->Blk); if (i >= 0) { for (++i; d->Blocks[i] != End->Blk && i < (int)d->Blocks.Length(); ) { LRichTextPriv::Block *b = d->Blocks[i]; b->DoCase(NoTransaction, 0, -1, Upper); } } else { LAssert(0); - return false; + if (Callback) + Callback(false); + return; } // 3) Delete any text up to the Cursor in the 'End' block End->Blk->DoCase(NoTransaction, 0, End->Offset, Upper); } // Update the screen d->Dirty = true; Invalidate(); - return true; + if (Callback) + Callback(true); } -bool LRichTextEdit::DoGoto() +void LRichTextEdit::DoGoto(std::function Callback) { - LInput Dlg(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); - if (Dlg.DoModal() == IDOK) + auto input = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); + input->DoModal([this, input, Callback](auto dlg, auto ctrlId) { - LString s = Dlg.GetStr(); - int64 i = s.Int(); - if (i >= 0) - SetLine((int)i); - } + if (ctrlId == IDOK) + { + LString s = input->GetStr(); + int64 i = s.Int(); + if (i >= 0) + { + SetLine((int)i); + if (Callback) + Callback(true); + return; + } + } - return true; + if (Callback) + Callback(false); + + delete dlg; + }); } LDocFindReplaceParams *LRichTextEdit::CreateFindReplaceParams() { return new LDocFindReplaceParams3; } void LRichTextEdit::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { } } -bool LRichTextEdit::DoFindNext() +void LRichTextEdit::DoFindNext(std::function Callback) { - return false; -} - -bool -RichText_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User) -{ - return ((LRichTextEdit*)User)->OnFind(Dlg); + if (Callback) + Callback(false); } ////////////////////////////////////////////////////////////////////////////////// FIND -bool LRichTextEdit::DoFind() +void LRichTextEdit::DoFind(std::function Callback) { LArray Sel; if (HasSelection()) d->GetSelection(&Sel, NULL); LAutoString u(Sel.Length() ? WideToUtf8(&Sel.First()) : NULL); - LFindDlg Dlg(this, u, RichText_FindCallback, this); - Dlg.DoModal(); - Focus(true); - return false; + + auto Dlg = new LFindDlg(this, + [&](auto dlg, auto ctrlId) + { + return OnFind(dlg); + }, + u); + Dlg->DoModal([this, Dlg, Callback](auto dlg, auto ctrlId) + { + if (Callback) + Callback(ctrlId != IDCANCEL); + + Focus(true); + + delete dlg; + }); } bool LRichTextEdit::OnFind(LFindReplaceCommon *Params) { if (!Params || !d->Cursor) { LAssert(0); return false; } LAutoPtr w((uint32_t*)LNewConvertCp("utf-32", Params->Find, "utf-8", Params->Find.Length())); ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LAssert(0); return false; } for (unsigned n = 0; n < d->Blocks.Length(); n++) { ssize_t i = Idx + n; LRichTextPriv::Block *b = d->Blocks[i % d->Blocks.Length()]; ssize_t At = n ? 0 : d->Cursor->Offset; ssize_t Result = b->FindAt(At, w, Params); if (Result >= At) { ptrdiff_t Len = Strlen(w.Get()); AutoCursor Sel(new BlkCursor(b, Result, -1)); d->SetCursor(Sel, false); AutoCursor Cur(new BlkCursor(b, Result + Len, -1)); return d->SetCursor(Cur, true); } } return false; } ////////////////////////////////////////////////////////////////////////////////// REPLACE -bool LRichTextEdit::DoReplace() +void LRichTextEdit::DoReplace(std::function Callback) { - return false; + if (Callback) + Callback(false); } bool LRichTextEdit::OnReplace(LFindReplaceCommon *Params) { return false; } ////////////////////////////////////////////////////////////////////////////////// void LRichTextEdit::SelectWord(size_t From) { int BlockIdx; ssize_t Start, End; LRichTextPriv::Block *b = d->GetBlockByIndex(From, &Start, &BlockIdx); if (!b) return; LArray Txt; if (!b->CopyAt(0, b->Length(), &Txt)) return; End = Start; while (Start > 0 && !IsWordBreakChar(Txt[Start-1])) Start--; while ( End < b->Length() && ( End == Txt.Length() || !IsWordBreakChar(Txt[End]) ) ) End++; AutoCursor c(new BlkCursor(b, Start, -1)); d->SetCursor(c); c.Reset(new BlkCursor(b, End, -1)); d->SetCursor(c, true); } bool LRichTextEdit::OnMultiLineTab(bool In) { return false; } void LRichTextEdit::OnSetHidden(int Hidden) { } void LRichTextEdit::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); // LRect c = GetClient(); Processing = false; } LLayout::OnPosChange(); } int LRichTextEdit::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.SupportsFileDrops(); #ifdef WINDOWS Formats.Supports("UniformResourceLocatorW"); #endif return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LRichTextEdit::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Effect = DROPEFFECT_NONE; for (unsigned i=0; iAreas[ContentArea].Overlap(Pt.x, Pt.y)) { int AddIndex = -1; LPoint TestPt( Pt.x - d->Areas[ContentArea].x1, Pt.y - d->Areas[ContentArea].y1); if (VScroll) TestPt.y += (int)(VScroll->Value() * d->ScrollLinePx); LDropFiles Df(dd); for (unsigned n=0; nHitTest(TestPt.x, TestPt.y, LineHint); if (Idx >= 0) { ssize_t BlkOffset; int BlkIdx; LRichTextPriv::Block *b = d->GetBlockByIndex(Idx, &BlkOffset, &BlkIdx); if (b) { LRichTextPriv::Block *After = NULL; // Split 'b' to make room for the image if (BlkOffset > 0) { After = b->Split(NoTransaction, BlkOffset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } LRichTextPriv::ImageBlock *ImgBlk = new LRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); ImgBlk->Load(f); Effect = DROPEFFECT_COPY; } } } } } } break; } } if (Effect != DROPEFFECT_NONE) { Invalidate(); SendNotify(LNotifyDocChanged); } return Effect; } void LRichTextEdit::OnCreate() { SetWindow(this); DropTarget(true); if (Focus()) SetPulse(RTE_PULSE_RATE); if (d->SpellCheck) d->SpellCheck->EnumLanguages(AddDispatch()); } void LRichTextEdit::OnEscape(LKey &K) { } bool LRichTextEdit::OnMouseWheel(double l) { if (VScroll) { VScroll->Value(VScroll->Value() + (int64)l); Invalidate(); } return true; } void LRichTextEdit::OnFocus(bool f) { Invalidate(); SetPulse(f ? RTE_PULSE_RATE : -1); } ssize_t LRichTextEdit::HitTest(int x, int y) { int Line = -1; return d->HitTest(x, y, Line); } void LRichTextEdit::Undo() { if (d->UndoPos > 0) d->SetUndoPos(d->UndoPos - 1); } void LRichTextEdit::Redo() { if (d->UndoPos < (int)d->UndoQue.Length()) d->SetUndoPos(d->UndoPos + 1); } #ifdef _DEBUG class NodeView : public LWindow { public: LTree *Tree; NodeView(LViewI *w) { LRect r(0, 0, 500, 600); SetPos(r); MoveSameScreen(w); Attach(0); if ((Tree = new LTree(100, 0, 0, 100, 100))) { Tree->SetPourLargest(true); Tree->Attach(this); } } }; #endif void LRichTextEdit::DoContextMenu(LMouse &m) { LMenuItem *i; LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LRichTextPriv::Block *Over = NULL; LRect &Content = d->Areas[ContentArea]; LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Offset = -1, BlkOffset = -1; if (Content.Overlap(m.x, m.y)) { int LineHint; Offset = d->HitTest(Doc.x, Doc.y, LineHint, &Over, &BlkOffset); } if (Over) Over->DoContext(RClick, Doc, BlkOffset, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_RTE_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_RTE_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_RTE_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_RTE_UNDO, false /* UndoQue.CanUndo() */); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_RTE_REDO, false /* UndoQue.CanRedo() */); RClick.AppendSeparator(); #if 0 i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); #endif i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); LSubMenu *Src = RClick.AppendSub("Source"); if (Src) { Src->AppendItem("Copy Original", IDM_COPY_ORIGINAL, d->OriginalText.Get() != NULL); Src->AppendItem("Copy Current", IDM_COPY_CURRENT); #ifdef _DEBUG Src->AppendItem("Dump Nodes", IDM_DUMP_NODES); // Edit->DumpNodes(Tree); #endif } if (Over) { #ifdef _DEBUG // RClick.AppendItem(Over->GetClass(), -1, false); #endif Over->DoContext(RClick, Doc, BlkOffset, false); } if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m.x, m.y)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_RTE_CUT: { Cut(); break; } case IDM_RTE_COPY: { Copy(); break; } case IDM_RTE_PASTE: { Paste(); break; } case IDM_RTE_UNDO: { Undo(); break; } case IDM_RTE_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); - LInput i(this, s, "Indent Size:", "Text"); - if (i.DoModal()) + auto i = new LInput(this, s, "Indent Size:", "Text"); + i->DoModal([this, i](auto dlg, auto ctrlId) { - IndentSize = (uint8_t)i.GetStr().Int(); - } + if (ctrlId == IDOK) + { + IndentSize = (uint8_t)i->GetStr().Int(); + Invalidate(); + } + delete dlg; + }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); - LInput i(this, s, "Tab Size:", "Text"); - if (i.DoModal()) + auto i = new LInput(this, s, "Tab Size:", "Text"); + i->DoModal([this, i](auto dlg, auto ctrlId) { - SetTabSize((uint8_t)i.GetStr().Int()); - } + if (ctrlId == IDOK) + { + SetTabSize((uint8_t)i->GetStr().Int()); + Invalidate(); + } + delete dlg; + }); break; } case IDM_COPY_ORIGINAL: { LClipBoard c(this); c.Text(d->OriginalText); break; } case IDM_COPY_CURRENT: { LClipBoard c(this); c.Text(Name()); break; } case IDM_DUMP_NODES: { #ifdef _DEBUG NodeView *nv = new NodeView(GetWindow()); DumpNodes(nv->Tree); nv->Visible(true); #endif break; } default: { if (Over) { LMessage Cmd(M_COMMAND, Id); if (Over->OnEvent(&Cmd)) break; } if (Environment) Environment->OnMenu(this, Id, 0); break; } } } void LRichTextEdit::OnMouseClick(LMouse &m) { bool Processed = false; RectType Clicked = d->PosToButton(m); if (m.Down()) { Focus(true); if (m.IsContextMenu()) { DoContextMenu(m); return; } else { Focus(true); if (d->Areas[ToolsArea].Overlap(m.x, m.y) // || d->Areas[CapabilityArea].Overlap(m.x, m.y) ) { if (Clicked != MaxArea) { if (d->BtnState[Clicked].IsPress) { d->BtnState[d->ClickedBtn = Clicked].Pressed = true; Invalidate(d->Areas + Clicked); Capture(true); } else { Processed |= d->ClickBtn(m, Clicked); } } } else { d->WordSelectMode = !Processed && m.Double(); AutoCursor c(new BlkCursor(NULL, 0, 0)); LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->ClickedBtn = ContentArea; d->SetCursor(c, m.Shift()); if (d->WordSelectMode) SelectWord(Idx); } } } } else if (IsCapturing()) { Capture(false); if (d->ClickedBtn != MaxArea) { d->BtnState[d->ClickedBtn].Pressed = false; Invalidate(d->Areas + d->ClickedBtn); Processed |= d->ClickBtn(m, Clicked); } d->ClickedBtn = MaxArea; } if (!Processed) { Capture(m.Down()); } } int LRichTextEdit::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LRichTextEdit::OnMouseMove(LMouse &m) { LRichTextEdit::RectType OverBtn = d->PosToButton(m); if (d->OverBtn != OverBtn) { if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = false; Invalidate(&d->Areas[d->OverBtn]); } d->OverBtn = OverBtn; if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = true; Invalidate(&d->Areas[d->OverBtn]); } } if (IsCapturing()) { if (d->ClickedBtn == ContentArea) { AutoCursor c; LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx) && c) { if (d->WordSelectMode && d->Selection) { // Extend the selection to include the whole word if (!d->CursorFirst()) { // Extend towards the end of the doc... LArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset < (int)Txt.Length() && !IsWordBreakChar(Txt[c->Offset]) ) c->Offset++; } } else { // Extend towards the start of the doc... LArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset > 0 && !IsWordBreakChar(Txt[c->Offset-1]) ) c->Offset--; } } } d->SetCursor(c, m.Left()); } } } #ifdef WIN32 LRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(m.x, m.y)) { /* LStyle *s = HitStyle(Hit); TCHAR *c = (s) ? s->GetCursor() : 0; if (!c) c = IDC_IBEAM; ::SetCursor(LoadCursor(0, MAKEINTRESOURCE(c))); */ } #endif } bool LRichTextEdit::OnKey(LKey &k) { if (k.Down() && d->Cursor) d->Cursor->Blink = true; if (k.c16 == 17) return false; #ifdef WINDOWS // Wtf is this? // Weeeelll, windows likes to send a LK_TAB after a Ctrl+I doesn't it? // And this just takes care of that TAB before it can overwrite your // selection. if (ToLower(k.c16) == 'i' && k.Ctrl()) { d->EatVkeys.Add(LK_TAB); } else if (d->EatVkeys.Length()) { auto Idx = d->EatVkeys.IndexOf(k.vkey); if (Idx >= 0) { // Yum yum d->EatVkeys.DeleteAt(Idx); return true; } } #endif // k.Trace("LRichTextEdit::OnKey"); if (k.IsContextMenu()) { LMouse m; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down() && d->Cursor && d->Cursor->Blk) { // letter/number etc LRichTextPriv::Block *b = d->Cursor->Blk; AutoTrans Trans(new LRichTextPriv::Transaction); d->DeleteSelection(Trans, NULL); LNamedStyle *AddStyle = NULL; if (d->StyleDirty.Length() > 0) { LAutoPtr Mod(new LCss); if (Mod) { // Get base styles at the cursor.. LNamedStyle *Base = b->GetStyle(d->Cursor->Offset); if (Base) *Mod = *Base; // Apply dirty toolbar styles... if (d->StyleDirty.HasItem(FontFamilyBtn)) Mod->FontFamily(LCss::StringsDef(d->Values[FontFamilyBtn].Str())); if (d->StyleDirty.HasItem(FontSizeBtn)) Mod->FontSize(LCss::Len(LCss::LenPt, (float) d->Values[FontSizeBtn].CastDouble())); if (d->StyleDirty.HasItem(BoldBtn)) Mod->FontWeight(d->Values[BoldBtn].CastInt32() ? LCss::FontWeightBold : LCss::FontWeightNormal); if (d->StyleDirty.HasItem(ItalicBtn)) Mod->FontStyle(d->Values[ItalicBtn].CastInt32() ? LCss::FontStyleItalic : LCss::FontStyleNormal); if (d->StyleDirty.HasItem(UnderlineBtn)) Mod->TextDecoration(d->Values[UnderlineBtn].CastInt32() ? LCss::TextDecorUnderline : LCss::TextDecorNone); if (d->StyleDirty.HasItem(ForegroundColourBtn)) Mod->Color(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[ForegroundColourBtn].CastInt64())); if (d->StyleDirty.HasItem(BackgroundColourBtn)) Mod->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[BackgroundColourBtn].CastInt64())); AddStyle = d->AddStyleToCache(Mod); } d->StyleDirty.Length(0); } else if (b->Length() == 0) { // We have no existing style to modify, so create one from scratch. LAutoPtr Mod(new LCss); if (Mod) { // Apply dirty toolbar styles... Mod->FontFamily(LCss::StringsDef(d->Values[FontFamilyBtn].Str())); Mod->FontSize(LCss::Len(LCss::LenPt, (float)d->Values[FontSizeBtn].CastDouble())); Mod->FontWeight(d->Values[BoldBtn].CastInt32() ? LCss::FontWeightBold : LCss::FontWeightNormal); Mod->FontStyle(d->Values[ItalicBtn].CastInt32() ? LCss::FontStyleItalic : LCss::FontStyleNormal); Mod->TextDecoration(d->Values[UnderlineBtn].CastInt32() ? LCss::TextDecorUnderline : LCss::TextDecorNone); Mod->Color(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[ForegroundColourBtn].CastInt64())); auto Bk = d->Values[BackgroundColourBtn].CastInt64(); if (Bk > 0) Mod->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, (uint32_t)Bk)); AddStyle = d->AddStyleToCache(Mod); } } uint32_t Ch = k.c16; if (b->AddText(Trans, d->Cursor->Offset, &Ch, 1, AddStyle)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(LNotifyDocChanged); d->AddTrans(Trans); } } return true; } break; } case LK_RETURN: { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); SendNotify(LNotifyDocChanged); } return true; } case LK_BACKSPACE: { if (GetReadOnly()) break; bool Changed = false; AutoTrans Trans(new LRichTextPriv::Transaction); if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { LRichTextPriv::Block *b; if (HasSelection()) { Changed = d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset > 0) { Changed = b->DeleteAt(Trans, d->Cursor->Offset-1, 1) > 0; if (Changed) { // Has block size reached 0? if (b->Length() == 0) { // Then delete it... LRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new LRichTextPriv::BlockCursor(n, 0, 0)); } else { // No other block to go to, so leave this empty block at the end // of the documnent but set the cursor correctly. d->Cursor->Set(0); } } else { d->Cursor->Set(d->Cursor->Offset - 1); } } } else { // At the start of a block: LRichTextPriv::Block *Prev = d->Prev(d->Cursor->Blk); if (Prev) { // Try and merge the two blocks... ssize_t Len = Prev->Length(); d->Merge(Trans, Prev, d->Cursor->Blk); AutoCursor c(new BlkCursor(Prev, Len, -1)); d->SetCursor(c); } else // at the start of the doc... { // Don't send the doc changed... return true; } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } return true; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: return !GetReadOnly(); case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) Redo(); else Undo(); } } else if (k.Ctrl()) { if (k.Down()) { // Implement delete by word LAssert(!"Impl backspace by word"); } } return true; } break; } case LK_F3: { if (k.Down()) - DoFindNext(); + DoFindNext(NULL); return true; } case LK_LEFT: { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { LRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Cursor : *d->Selection)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_StartOfLine; else #endif d->Seek(d->Cursor, #ifdef MAC k.Alt() ? #else k.Ctrl() ? #endif LRichTextPriv::SkLeftWord : LRichTextPriv::SkLeftChar, k.Shift()); } } return true; } case LK_RIGHT: { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { LRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Selection : *d->Cursor)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_EndOfLine; #endif d->Seek(d->Cursor, #ifdef MAC k.Alt() ? #else k.Ctrl() ? #endif LRichTextPriv::SkRightWord : LRichTextPriv::SkRightChar, k.Shift()); } } return true; } case LK_UP: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageUp; #endif d->Seek(d->Cursor, LRichTextPriv::SkUpLine, k.Shift()); } return true; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageDown; #endif d->Seek(d->Cursor, LRichTextPriv::SkDownLine, k.Shift()); } return true; } case LK_END: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_EndOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? LRichTextPriv::SkDocEnd : LRichTextPriv::SkLineEnd, k.Shift()); } return true; } case LK_HOME: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_StartOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? LRichTextPriv::SkDocStart : LRichTextPriv::SkLineStart, k.Shift()); } return true; } case LK_PAGEUP: { #ifdef MAC GTextView4_PageUp: #endif if (k.Down()) { d->Seek(d->Cursor, LRichTextPriv::SkUpPage, k.Shift()); } return true; break; } case LK_PAGEDOWN: { #ifdef MAC GTextView4_PageDown: #endif if (k.Down()) { d->Seek(d->Cursor, LRichTextPriv::SkDownPage, k.Shift()); } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (GetReadOnly()) break; if (!k.Down()) return true; bool Changed = false; LRichTextPriv::Block *b; AutoTrans Trans(new LRichTextPriv::Transaction); if (HasSelection()) { if (k.Shift()) Changed |= Cut(); else Changed |= d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset >= b->Length()) { // Cursor is at the end of this block, pull the styles // from the next block into this one. LRichTextPriv::Block *next = d->Next(b); if (!next) { // No next block, therefor nothing to delete break; } // Try and merge the blocks if (d->Merge(Trans, b, next)) Changed = true; else { // If the cursor is on the last empty line of a text block, // we should delete that '\n' first LRichTextPriv::TextBlock *tb = dynamic_cast(b); if (tb && tb->IsEmptyLine(d->Cursor)) Changed = tb->StripLast(Trans); // move the cursor to the next block d->Cursor.Reset(new LRichTextPriv::BlockCursor(b = next, 0, 0)); } } if (!Changed && b->DeleteAt(Trans, d->Cursor->Offset, 1)) { if (b->Length() == 0) { LRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new LRichTextPriv::BlockCursor(n, 0, 0)); } } Changed = true; } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } return true; } default: { if (k.c16 == 17) break; if (k.c16 == ' ' && k.Ctrl() && k.Alt() && d->Cursor && d->Cursor->Blk) { if (k.Down()) { // letter/number etc LRichTextPriv::Block *b = d->Cursor->Blk; uint32_t Nbsp[] = {0xa0}; if (b->AddText(NoTransaction, d->Cursor->Offset, Nbsp, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(LNotifyDocChanged); } } break; } if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { /* if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } */ break; } case 0xbb: // Ctrl+'+' { /* if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } */ break; } case 'a': case 'A': { if (k.Down()) { // select all SelectAll(); } return true; break; } case 'b': case 'B': { if (k.Down()) { // Bold selection LMouse m; GetMouse(m); d->ClickBtn(m, BoldBtn); } return true; break; } case 'l': case 'L': { if (k.Down()) { // Underline selection LMouse m; GetMouse(m); d->ClickBtn(m, UnderlineBtn); } return true; break; } case 'i': case 'I': { if (k.Down()) { // Italic selection LMouse m; GetMouse(m); d->ClickBtn(m, ItalicBtn); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) - DoFind(); + DoFind(NULL); return true; } case 'g': case 'G': { if (k.Down()) - { - DoGoto(); - } + DoGoto(NULL); return true; break; } case 'h': case 'H': { if (k.Down()) - { - DoReplace(); - } + DoReplace(NULL); return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) - { - DoCase(k.Shift()); - } + DoCase(NULL, k.Shift()); return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LRichTextEdit::OnEnter(LKey &k) { AutoTrans Trans(new LRichTextPriv::Transaction); // Enter key handling bool Changed = false; if (HasSelection()) Changed |= d->DeleteSelection(Trans, NULL); if (d->Cursor && d->Cursor->Blk) { LRichTextPriv::Block *b = d->Cursor->Blk; const uint32_t Nl[] = {'\n'}; if (b->AddText(Trans, d->Cursor->Offset, Nl, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Changed = true; } else { // Some blocks don't take text. However a new block can be created or // the text added to the start of the next block if (d->Cursor->Offset == 0) { LRichTextPriv::Block *Prev = d->Prev(b); if (Prev) Changed = Prev->AddText(Trans, Prev->Length(), Nl, 1); else // No previous... must by first block... create new block: { LRichTextPriv::TextBlock *tb = new LRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.AddAt(0, tb); } } } else if (d->Cursor->Offset == b->Length()) { LRichTextPriv::Block *Next = d->Next(b); if (Next) { if ((Changed = Next->AddText(Trans, 0, Nl, 1))) d->Cursor->Set(Next, 0, -1); } else // No next block. Create one: { LRichTextPriv::TextBlock *tb = new LRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.Add(tb); } } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } } void LRichTextEdit::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LRichTextEdit::OnPaint(LSurface *pDC) { LRect r = GetClient(); if (!r.Valid()) return; #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif int FontY = GetFont()->GetHeight(); LCssTools ct(d, d->Font); r = ct.PaintBorder(pDC, r); bool HasSpace = r.Y() > (FontY * 3); if (d->ShowTools && HasSpace) { d->Areas[ToolsArea] = r; d->Areas[ToolsArea].y2 = d->Areas[ToolsArea].y1 + (FontY + 8) - 1; r.y1 = d->Areas[ToolsArea].y2 + 1; } else { d->Areas[ToolsArea].ZOff(-1, -1); } d->Areas[ContentArea] = r; if (d->Layout(VScroll)) d->Paint(pDC, VScroll); // else the scroll bars changed, wait for re-paint } LMessage::Result LRichTextEdit::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } case M_BLOCK_MSG: { LRichTextPriv::Block *b = (LRichTextPriv::Block*)Msg->A(); LAutoPtr msg((LMessage*)Msg->B()); if (d->Blocks.HasItem(b) && msg) { b->OnEvent(msg); } else printf("%s:%i - No block to receive M_BLOCK_MSG.\n", _FL); break; } case M_ENUMERATE_LANGUAGES: { LAutoPtr< LArray > Languages((LArray*)Msg->A()); if (!Languages) { LgiTrace("%s:%i - M_ENUMERATE_LANGUAGES no param\n", _FL); break; } // LgiTrace("%s:%i - Got M_ENUMERATE_LANGUAGES %s\n", _FL, d->SpellLang.Get()); bool Match = false; for (auto &s: *Languages) { if (s.LangCode.Equals(d->SpellLang) || s.EnglishName.Equals(d->SpellLang)) { // LgiTrace("%s:%i - EnumDict called %s\n", _FL, s.LangCode.Get()); d->SpellCheck->EnumDictionaries(AddDispatch(), s.LangCode); Match = true; break; } } if (!Match) LgiTrace("%s:%i - EnumDict not called %s\n", _FL, d->SpellLang.Get()); break; } case M_ENUMERATE_DICTIONARIES: { LAutoPtr< LArray > Dictionaries((LArray*)Msg->A()); if (!Dictionaries) break; bool Match = false; for (auto &s: *Dictionaries) { // LgiTrace("%s:%i - M_ENUMERATE_DICTIONARIES: %s, %s\n", _FL, s.Dict.Get(), d->SpellDict.Get()); if (s.Dict.Equals(d->SpellDict)) { d->SpellCheck->SetDictionary(AddDispatch(), s.Lang, s.Dict); Match = true; break; } } if (!Match) d->SpellCheck->SetDictionary(AddDispatch(), d->SpellLang, NULL); break; } case M_SET_DICTIONARY: { d->SpellDictionaryLoaded = Msg->A() != 0; // LgiTrace("%s:%i - M_SET_DICTIONARY=%i\n", _FL, d->SpellDictionaryLoaded); if (d->SpellDictionaryLoaded) { AutoTrans Trans(new LRichTextPriv::Transaction); // Get any loaded text blocks to check their spelling bool Status = false; for (unsigned i=0; iBlocks.Length(); i++) { Status |= d->Blocks[i]->OnDictionary(Trans); } if (Status) d->AddTrans(Trans); } break; } case M_CHECK_TEXT: { LAutoPtr Ct((LSpellCheck::CheckText*)Msg->A()); if (!Ct || Ct->User.Length() > 1) { LAssert(0); break; } LRichTextPriv::Block *b = (LRichTextPriv::Block*)Ct->User[SpellBlockPtr].CastVoidPtr(); if (!d->Blocks.HasItem(b)) break; b->SetSpellingErrors(Ct->Errors, *Ct); Invalidate(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return 0 /*Size*/; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } case M_COMPONENT_INSTALLED: { LAutoPtr Comp((LString*)Msg->A()); if (Comp) d->OnComponentInstall(*Comp); break; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LRichTextEdit::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { Invalidate(d->Areas + ContentArea); } return 0; } void LRichTextEdit::OnPulse() { if (!ReadOnly && d->Cursor) { uint64 n = LCurrentTime(); if (d->BlinkTs - n >= RTE_CURSOR_BLINK_RATE) { d->BlinkTs = n; d->Cursor->Blink = !d->Cursor->Blink; d->InvalidateDoc(&d->Cursor->Pos); } // Do autoscroll while the user has clicked and dragged off the control: if (VScroll && IsCapturing() && d->ClickedBtn == LRichTextEdit::ContentArea) { LMouse m; GetMouse(m); // Is the mouse outside the content window LRect &r = d->Areas[ContentArea]; if (!r.Overlap(m.x, m.y)) { AutoCursor c(new BlkCursor(NULL, 0, 0)); LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->SetCursor(c, true); if (d->WordSelectMode) SelectWord(Idx); } // Update the screen. d->InvalidateDoc(NULL); } } } } void LRichTextEdit::OnUrl(char *Url) { if (Environment) { Environment->OnNavigate(this, Url); } } bool LRichTextEdit::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; // Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } #if _DEBUG void LRichTextEdit::SelectNode(LString Param) { LRichTextPriv::Block *b = (LRichTextPriv::Block*) Param.Int(16); bool Valid = false; for (auto i : d->Blocks) { if (i == b) Valid = true; i->DrawDebug = false; } if (Valid) { b->DrawDebug = true; Invalidate(); } } void LRichTextEdit::DumpNodes(LTree *Root) { d->DumpNodes(Root); } #endif /////////////////////////////////////////////////////////////////////////////// SelectColour::SelectColour(LRichTextPriv *priv, LPoint p, LRichTextEdit::RectType t) : LPopup(priv->View) { d = priv; Type = t; int Px = 16; int PxSp = Px + 2; int x = 6; int y = 6; // Do grey ramp for (int i=0; i<8; i++) { Entry &en = e.New(); int Grey = i * 255 / 7; en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (i * PxSp), y); en.c.Rgb(Grey, Grey, Grey); } // Do colours y += PxSp + 4; int SatRange = 255 - 64; int SatStart = 255 - 32; int HueStep = 360 / 8; for (int sat=0; sat<8; sat++) { for (int hue=0; hue<8; hue++) { LColour c; c.SetHLS(hue * HueStep, SatStart - ((sat * SatRange) / 7), 255); c.ToRGB(); Entry &en = e.New(); en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (hue * PxSp), y); en.c = c; } y += PxSp; } SetParent(d->View); LRect r(0, 0, 12 + (8 * PxSp) - 1, y + 6 - 1); r.Offset(p.x, p.y); SetPos(r); Visible(true); } void SelectColour::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); for (unsigned i=0; iColour(e[i].c); pDC->Rectangle(&e[i].r); } } void SelectColour::OnMouseClick(LMouse &m) { if (m.Down()) { for (unsigned i=0; iValues[Type] = (int64)e[i].c.c32(); d->View->Invalidate(d->Areas + Type); d->OnStyleChange(Type); Visible(false); break; } } } } void SelectColour::Visible(bool i) { LPopup::Visible(i); if (!i) { d->View->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// #define EMOJI_PAD 2 #include "lgi/common/Emoji.h" int EmojiMenu::Cur = 0; EmojiMenu::EmojiMenu(LRichTextPriv *priv, LPoint p) : LPopup(priv->View) { d = priv; d->GetEmojiImage(); int MaxIdx = 0; LHashTbl, int> Map; for (int b=0; b= 0) { Map.Add(Emoji.Index, u); MaxIdx = MAX(MaxIdx, Emoji.Index); } } } int Sz = EMOJI_CELL_SIZE - 1; int PaneCount = 5; int PaneSz = (int)(Map.Length() / PaneCount); int ImgIdx = 0; int PaneSelectSz = LSysFont->GetHeight() * 2; int Rows = (PaneSz + EMOJI_GROUP_X - 1) / EMOJI_GROUP_X; LRect r(0, 0, (EMOJI_CELL_SIZE + EMOJI_PAD) * EMOJI_GROUP_X + EMOJI_PAD, (EMOJI_CELL_SIZE + EMOJI_PAD) * Rows + EMOJI_PAD + PaneSelectSz); r.Offset(p.x, p.y); SetPos(r); for (int pi = 0; pi < PaneCount; pi++) { Pane &p = Panes[pi]; int Wid = X() - (EMOJI_PAD*2); p.Btn.x1 = EMOJI_PAD + (pi * Wid / PaneCount); p.Btn.y1 = EMOJI_PAD; p.Btn.x2 = EMOJI_PAD + ((pi + 1) * Wid / PaneCount) - 1; p.Btn.y2 = EMOJI_PAD + PaneSelectSz; int Dx = EMOJI_PAD; int Dy = p.Btn.y2 + 1; while ((int)p.e.Length() < PaneSz && ImgIdx <= MaxIdx) { uint32_t u = Map.Find(ImgIdx); if (u) { Emoji &Ch = p.e.New(); Ch.u = u; int Sx = ImgIdx % EMOJI_GROUP_X; int Sy = ImgIdx / EMOJI_GROUP_X; Ch.Src.ZOff(Sz, Sz); Ch.Src.Offset(Sx * EMOJI_CELL_SIZE, Sy * EMOJI_CELL_SIZE); Ch.Dst.ZOff(Sz, Sz); Ch.Dst.Offset(Dx, Dy); Dx += EMOJI_PAD + EMOJI_CELL_SIZE; if (Dx + EMOJI_PAD + EMOJI_CELL_SIZE >= r.X()) { Dx = EMOJI_PAD; Dy += EMOJI_PAD + EMOJI_CELL_SIZE; } } ImgIdx++; } } SetParent(d->View); Visible(true); } void EmojiMenu::OnPaint(LSurface *pDC) { LAutoPtr DblBuf; if (!pDC->SupportsAlphaCompositing()) DblBuf.Reset(new LDoubleBuffer(pDC)); pDC->Colour(L_MED); pDC->Rectangle(); LSurface *EmojiImg = d->GetEmojiImage(); if (EmojiImg) { pDC->Op(GDC_ALPHA); for (unsigned i=0; iColour(L_LIGHT); pDC->Rectangle(&p.Btn); } LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); Ds.Draw(pDC, p.Btn.x1 + ((p.Btn.X()-Ds.X())>>1), p.Btn.y1 + ((p.Btn.Y()-Ds.Y())>>1)); } Pane &p = Panes[Cur]; for (unsigned i=0; iBlt(g.Dst.x1, g.Dst.y1, EmojiImg, &g.Src); } } else { LRect c = GetClient(); LDisplayString Ds(LSysFont, "Loading..."); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(true); Ds.Draw(pDC, (c.X()-Ds.X())>>1, (c.Y()-Ds.Y())>>1); } } bool EmojiMenu::InsertEmoji(uint32_t Ch) { if (!d->Cursor || !d->Cursor->Blk) return false; AutoTrans Trans(new LRichTextPriv::Transaction); if (!d->Cursor->Blk->AddText(NoTransaction, d->Cursor->Offset, &Ch, 1, NULL)) return false; AutoCursor c(new BlkCursor(*d->Cursor)); c->Offset++; d->SetCursor(c); d->AddTrans(Trans); d->Dirty = true; d->InvalidateDoc(NULL); d->View->SendNotify(LNotifyDocChanged); return true; } void EmojiMenu::OnMouseClick(LMouse &m) { if (m.Down()) { for (unsigned i=0; iView->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// class LRichTextEdit_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LRichTextEdit") == 0) { return new LRichTextEdit(-1, 0, 0, 2000, 2000); } return 0; } } RichTextEdit_Factory; diff --git a/src/common/Widgets/Editor/RichTextEditPriv.cpp b/src/common/Widgets/Editor/RichTextEditPriv.cpp --- a/src/common/Widgets/Editor/RichTextEditPriv.cpp +++ b/src/common/Widgets/Editor/RichTextEditPriv.cpp @@ -1,2495 +1,2499 @@ #include "lgi/common/Lgi.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #include "RichTextEditPriv.h" /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Utf16to32(LArray &Out, const uint16_t *In, ssize_t WordLen) { if (WordLen == 0) { Out.Length(0); return true; } // Count the length of utf32 chars... const uint16 *Ptr = In; ssize_t Bytes = sizeof(*In) * WordLen; int Chars = 0; while ( Bytes >= sizeof(*In) && LgiUtf16To32(Ptr, Bytes) > 0) Chars++; // Set the output buffer size.. if (!Out.Length(Chars)) return false; // Convert the string... Ptr = (uint16*)In; Bytes = sizeof(*In) * WordLen; uint32_t *o = &Out[0]; #ifdef _DEBUG uint32_t *e = o + Out.Length(); #endif while ( Bytes >= sizeof(*In)) { *o++ = LgiUtf16To32(Ptr, Bytes); } LAssert(o == e); return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const char *LRichEditElemContext::GetElement(LRichEditElem *obj) { return obj->Tag; } const char *LRichEditElemContext::GetAttr(LRichEditElem *obj, const char *Attr) { const char *a = NULL; obj->Get(Attr, a); return a; } bool LRichEditElemContext::GetClasses(LString::Array &Classes, LRichEditElem *obj) { const char *c; if (!obj->Get("class", c)) return false; LString cls = c; Classes = cls.Split(" "); return Classes.Length() > 0; } LRichEditElem *LRichEditElemContext::GetParent(LRichEditElem *obj) { return dynamic_cast(obj->Parent); } LArray LRichEditElemContext::GetChildren(LRichEditElem *obj) { LArray a; for (unsigned i=0; iChildren.Length(); i++) a.Add(dynamic_cast(obj->Children[i])); return a; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LCssCache::LCssCache() { Idx = 1; } LCssCache::~LCssCache() { Styles.DeleteObjects(); } uint32_t LCssCache::GetStyles() { uint32_t c = 0; for (unsigned i=0; iRefCount != 0; } return c; } void LCssCache::ZeroRefCounts() { for (unsigned i=0; iRefCount = 0; } } bool LCssCache::OutputStyles(LStream &s, int TabDepth) { char Tabs[64]; memset(Tabs, '\t', TabDepth); Tabs[TabDepth] = 0; for (unsigned i=0; iRefCount > 0) { s.Print("%s.%s {\n", Tabs, ns->Name.Get()); LAutoString a = ns->ToString(); LString all = a.Get(); LString::Array lines = all.Split("\n"); for (unsigned n=0; n &s) { if (!s) return NULL; // Look through existing styles for a match... for (unsigned i=0; iName.Printf("%sStyle%i", p?p:"", Idx++); *(LCss*)ns = *s.Get(); Styles.Add(ns); #if 0 // _DEBUG LAutoString ss = ns->ToString(); if (ss) LgiTrace("%s = %s\n", ns->Name.Get(), ss.Get()); #endif } return ns; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// BlockCursorState::BlockCursorState(bool cursor, LRichTextPriv::BlockCursor *c) { Cursor = cursor; Offset = c->Offset; LineHint = c->LineHint; BlockUid = c->Blk->GetUid(); } bool BlockCursorState::Apply(LRichTextPriv *Ctx, bool Forward) { LAutoPtr &Bc = Cursor ? Ctx->Cursor : Ctx->Selection; if (!Bc) return false; ssize_t o = Bc->Offset; int lh = Bc->LineHint; int uid = Bc->Blk->GetUid(); int Index; LRichTextPriv::Block *b; if (!Ctx->GetBlockByUid(b, BlockUid, &Index)) return false; if (b != Bc->Blk) Bc.Reset(new LRichTextPriv::BlockCursor(b, Offset, LineHint)); else { Bc->Offset = Offset; Bc->LineHint = LineHint; } Offset = o; LineHint = lh; BlockUid = uid; return true; } /// This is the simplest form of undo, just save the entire block state, and restore it if needed CompleteTextBlockState::CompleteTextBlockState(LRichTextPriv *Ctx, LRichTextPriv::TextBlock *Tb) { if (Ctx->Cursor) Cur.Reset(new BlockCursorState(true, Ctx->Cursor)); if (Ctx->Selection) Sel.Reset(new BlockCursorState(false, Ctx->Selection)); if (Tb) { Uid = Tb->GetUid(); Blk.Reset(new LRichTextPriv::TextBlock(Tb)); } } bool CompleteTextBlockState::Apply(LRichTextPriv *Ctx, bool Forward) { int Index; LRichTextPriv::TextBlock *b; if (!Ctx->GetBlockByUid(b, Uid, &Index)) return false; // Swap the local state with the block in the ctx Blk->UpdateSpellingAndLinks(NULL, LRange(0, Blk->Length())); Ctx->Blocks[Index] = Blk.Release(); Blk.Reset(b); // Update cursors if (Cur) Cur->Apply(Ctx, Forward); if (Sel) Sel->Apply(Ctx, Forward); return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// MultiBlockState::MultiBlockState(LRichTextPriv *ctx, ssize_t Start) { Ctx = ctx; Index = Start; Length = -1; } bool MultiBlockState::Apply(LRichTextPriv *Ctx, bool Forward) { if (Index < 0 || Length < 0) { LAssert(!"Missing parameters"); return false; } // Undo: Swap 'Length' blocks Ctx->Blocks with Blks ssize_t OldLen = Blks.Length(); bool Status = Blks.SwapRange(LRange(0, OldLen), Ctx->Blocks, LRange(Index, Length)); if (Status) Length = OldLen; return Status; } bool MultiBlockState::Copy(ssize_t Idx) { if (!Ctx->Blocks.AddressOf(Idx)) return false; LRichTextPriv::Block *b = Ctx->Blocks[Idx]->Clone(); if (!b) return false; Blks.Add(b); return true; } bool MultiBlockState::Cut(ssize_t Idx) { if (!Ctx->Blocks.AddressOf(Idx)) return false; LRichTextPriv::Block *b = Ctx->Blocks[Idx]; if (!b) return false; Blks.Add(b); return Ctx->Blocks.DeleteAt(Idx, true); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LRichTextPriv::LRichTextPriv(LRichTextEdit *view, LRichTextPriv **Ptr) : LHtmlParser(view), LFontCache(LSysFont) { if (Ptr) *Ptr = this; BlinkTs = 0; View = view; Log = &LogBuffer; NextUid = 1; UndoPos = 0; UndoPosLock = false; WordSelectMode = false; Dirty = false; ScrollOffsetPx = 0; ShowTools = true; ScrollChange = false; DocumentExtent.x = 0; DocumentExtent.y = 0; SpellCheck = NULL; SpellDictionaryLoaded = false; HtmlLinkAsCid = false; ScrollLinePx = LSysFont->GetHeight(); if (Font.Reset(new LFont)) *Font = *LSysFont; for (unsigned i=0; i DeletedText; LArray *DelTxt = Cut ? &DeletedText : NULL; bool Cf = CursorFirst(); LRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; LRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Start->Blk == End->Blk) { // In the same block... just delete the text ssize_t Len = End->Offset - Start->Offset; LRichTextPriv::Block *NextBlk = Next(Start->Blk); if (Len >= Start->Blk->Length() && NextBlk) { // Delete entire block ssize_t i = Blocks.IndexOf(Start->Blk); LAutoPtr MultiState(new MultiBlockState(this, i)); MultiState->Cut(i); MultiState->Length = 0; Start->Set(NextBlk, 0, 0); Trans->Add(MultiState.Release()); } else { Start->Blk->DeleteAt(Trans, Start->Offset, Len, DelTxt); } } else { // Multi-block delete... ssize_t i = Blocks.IndexOf(Start->Blk); ssize_t e = Blocks.IndexOf(End->Blk); LAutoPtr MultiState(new MultiBlockState(this, i)); // 1) Delete all the content to the end of the first block ssize_t StartLen = Start->Blk->Length(); if (Start->Offset < StartLen) { MultiState->Copy(i++); Start->Blk->DeleteAt(NoTransaction, Start->Offset, StartLen - Start->Offset, DelTxt); } else if (Start->Offset == StartLen) { // This can happen because there is an implied '\n' at the end of each block. The // next block always starts on a new line. We just increment the index 'i' to avoid // the next loop deleting the start block. i++; // If the start and end blocks can be merged, the new line will eventually get removed // then. If not, then... well whatevs. } else { LAssert(0); return Error(_FL, "Cursor outside start block."); } // 2) Delete any blocks between 'Start' and 'End' if (i >= 0) { while (Blocks[i] != End->Blk && i < (int)Blocks.Length()) { LRichTextPriv::Block *b = Blocks[i]; b->CopyAt(0, -1, DelTxt); printf("%s:%i - inter, %i\n", _FL, (int)i); MultiState->Cut(i); e--; } } else { LAssert(0); return Error(_FL, "Start block has no index.");; } if (End->Offset > 0) { // 3) Delete any text up to the Cursor in the 'End' block MultiState->Copy(i); printf("%s:%i - end, %i-%i, %i\n", _FL, (int)0, (int)End->Offset, (int)End->Blk->Length()); End->Blk->DeleteAt(NoTransaction, 0, End->Offset, DelTxt); } // Try and merge the start and end blocks bool MergeOk = Merge(NoTransaction, Start->Blk, End->Blk); MultiState->Length = MergeOk ? 1 : 2; Trans->Add(MultiState.Release()); } // Set the cursor and update the screen Cursor->Set(Start->Blk, Start->Offset, Start->LineHint); Selection.Reset(); View->Invalidate(); if (Cut) { DelTxt->Add(0); *Cut = (char16*)LNewConvertCp(LGI_WideCharset, &DelTxt->First(), "utf-32", DelTxt->Length()*sizeof(uint32_t)); } return true; } LRichTextPriv::Block *LRichTextPriv::Next(Block *b) { ssize_t Idx = Blocks.IndexOf(b); if (Idx < 0) return NULL; if (++Idx >= (int)Blocks.Length()) return NULL; return Blocks[Idx]; } LRichTextPriv::Block *LRichTextPriv::Prev(Block *b) { ssize_t Idx = Blocks.IndexOf(b); if (Idx <= 0) return NULL; return Blocks[--Idx]; } bool LRichTextPriv::AddTrans(LAutoPtr &t) { if (t) { if (UndoPosLock) { LgiTrace("%s:%i - AddTrans failed - UndoPosLocked.\n", _FL); return false; } // Delete any transaction history after 'UndoPos' for (size_t i=UndoPos; i UndoPos) { // Forward in queue Transaction *t = UndoQue[UndoPos]; UndoPosLock = true; if (!t->Apply(this, true)) goto ApplyError; UndoPosLock = false; LAssert(UndoPos == Prev); UndoPos++; } else if (Pos < UndoPos) { // Back in queue Transaction *t = UndoQue[UndoPos-1]; UndoPosLock = true; if (!t->Apply(this, false)) goto ApplyError; UndoPosLock = false; LAssert(UndoPos == Prev); UndoPos--; } else break; // We are done } Dirty = true; InvalidateDoc(NULL); return true; ApplyError: UndoPosLock = false; return false; } bool LRichTextPriv::IsBusy(bool Stop) { for (unsigned i=0; iIsBusy(Stop)) return true; } return false; } bool LRichTextPriv::Error(const char *file, int line, const char *fmt, ...) { va_list Arg; va_start(Arg, fmt); LString s; LPrintf(s, fmt, Arg); va_end(Arg); LString Err; Err.Printf("%s:%i - Error: %s\n", file, line, s.Get()); Log->Write(Err, Err.Length()); Err = LogBuffer.NewGStr(); LgiTrace("%.*s", Err.Length(), Err.Get()); LAssert(0); return false; } void LRichTextPriv::UpdateStyleUI() { if (!Cursor || !Cursor->Blk) { Error(_FL, "Not a valid cursor."); return; } TextBlock *b = dynamic_cast(Cursor->Blk); LArray Styles; if (b) b->GetTextAt(Cursor->Offset, Styles); StyleText *st = Styles.Length() ? Styles.First() : NULL; LFont *f = NULL; if (st) f = GetFont(st->GetStyle()); else if (View) f = View->GetFont(); else if (LAppInst) f = LSysFont; if (f) { Values[LRichTextEdit::FontFamilyBtn] = f->Face(); Values[LRichTextEdit::FontSizeBtn] = f->PointSize(); Values[LRichTextEdit::FontSizeBtn].CastString(); Values[LRichTextEdit::BoldBtn] = f->Bold(); Values[LRichTextEdit::ItalicBtn] = f->Italic(); Values[LRichTextEdit::UnderlineBtn] = f->Underline(); } else { Values[LRichTextEdit::FontFamilyBtn] = "(Unknown)"; } Values[LRichTextEdit::ForegroundColourBtn] = (int64) (st && st->Colours.Fore.IsValid() ? st->Colours.Fore.c32() : TextColour.c32()); Values[LRichTextEdit::BackgroundColourBtn] = (int64) (st && st->Colours.Back.IsValid() ? st->Colours.Back.c32() : 0); if (View) View->Invalidate(Areas + LRichTextEdit::ToolsArea); } void LRichTextPriv::ScrollTo(LRect r) { LRect Content = Areas[LRichTextEdit::ContentArea]; Content.Offset(-Content.x1, ScrollOffsetPx-Content.y1); if (ScrollLinePx > 0) { if (r.y1 < Content.y1) { int OffsetPx = MAX(r.y1, 0); View->SetScrollPos(0, OffsetPx / ScrollLinePx); InvalidateDoc(NULL); } if (r.y2 > Content.y2) { int OffsetPx = r.y2 - Content.Y(); View->SetScrollPos(0, (OffsetPx + ScrollLinePx - 1) / ScrollLinePx); InvalidateDoc(NULL); } } } void LRichTextPriv::InvalidateDoc(LRect *r) { // Transform the coordinates from doc to screen space LRect &c = Areas[LRichTextEdit::ContentArea]; if (r) { LRect t = *r; t.Offset(c.x1, c.y1 - ScrollOffsetPx); View->Invalidate(&t); } else View->Invalidate(&c); } void LRichTextPriv::EmptyDoc() { Block *Def = new TextBlock(this); if (Def) { Blocks.Add(Def); Cursor.Reset(new BlockCursor(Def, 0, 0)); UpdateStyleUI(); } } void LRichTextPriv::Empty() { // Delete cursors first to avoid hanging references Cursor.Reset(); Selection.Reset(); // Clear the block list.. Blocks.DeleteObjects(); } bool LRichTextPriv::Seek(BlockCursor *In, SeekType Dir, bool Select) { if (!In || !In->Blk || Blocks.Length() == 0) return Error(_FL, "Not a valid 'In' cursor, Blks=%i", Blocks.Length()); LAutoPtr c; bool Status = false; switch (Dir) { case SkLineEnd: case SkLineStart: case SkUpLine: case SkDownLine: { if (!c.Reset(new BlockCursor(*In))) break; Block *b = c->Blk; Status = b->Seek(Dir, *c); if (Status) break; if (Dir == SkUpLine) { // No more lines in the current block... // Move to the next block. ssize_t CurIdx = Blocks.IndexOf(b); ssize_t NewIdx = CurIdx - 1; if (NewIdx >= 0) { Block *b = Blocks[NewIdx]; if (!b) return Error(_FL, "No block at %i", NewIdx); c.Reset(new BlockCursor(b, b->Length(), b->GetLines() - 1)); LAssert(c->Offset >= 0); Status = true; } } else if (Dir == SkDownLine) { // No more lines in the current block... // Move to the next block. ssize_t CurIdx = Blocks.IndexOf(b); ssize_t NewIdx = CurIdx + 1; if ((unsigned)NewIdx < Blocks.Length()) { Block *b = Blocks[NewIdx]; if (!b) return Error(_FL, "No block at %i", NewIdx); c.Reset(new BlockCursor(b, 0, 0)); LAssert(c->Offset >= 0); Status = true; } } break; } case SkDocStart: { if (!c.Reset(new BlockCursor(Blocks[0], 0, 0))) break; Status = true; break; } case SkDocEnd: { if (Blocks.Length() == 0) break; Block *l = Blocks.Last(); if (!c.Reset(new BlockCursor(l, l->Length(), -1))) break; Status = true; break; } case SkLeftChar: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset > 0) { LArray Ln; c->Blk->OffsetToLine(c->Offset, NULL, &Ln); if (Ln.Length() == 2 && c->LineHint == Ln.Last()) { c->LineHint = Ln.First(); } else { c->Offset--; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.First(); } Status = true; } else // Seek to previous block { SeekPrevBlock: ssize_t Idx = Blocks.IndexOf(c->Blk); if (Idx < 0) { LAssert(0); break; } if (Idx == 0) break; // Beginning of document Block *b = Blocks[--Idx]; if (!b) { LAssert(0); break; } if (!c.Reset(new BlockCursor(b, b->Length(), b->GetLines()-1))) break; Status = true; } break; } case SkLeftWord: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset > 0) { LArray a; c->Blk->CopyAt(0, c->Offset, &a); ssize_t i = c->Offset; while (i > 0 && IsWordBreakChar(a[i-1])) i--; while (i > 0 && !IsWordBreakChar(a[i-1])) i--; c->Offset = i; LArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln[0]; Status = true; } else // Seek into previous block? { goto SeekPrevBlock; } break; } case SkRightChar: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset < c->Blk->Length()) { LArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln) && Ln.Length() == 2 && c->LineHint == Ln.First()) { c->LineHint = Ln.Last(); } else { c->Offset++; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.First(); } Status = true; } else // Seek to next block { SeekNextBlock: ssize_t Idx = Blocks.IndexOf(c->Blk); if (Idx < 0) return Error(_FL, "Block ptr index error."); if (Idx >= (int)Blocks.Length() - 1) break; // End of document Block *b = Blocks[++Idx]; if (!b) return Error(_FL, "No block at %i.", Idx); if (!c.Reset(new BlockCursor(b, 0, 0))) break; Status = true; } break; } case SkRightWord: { if (!c.Reset(new BlockCursor(*In))) break; if (c->Offset < c->Blk->Length()) { LArray a; ssize_t RemainingCh = c->Blk->Length() - c->Offset; c->Blk->CopyAt(c->Offset, RemainingCh, &a); int i = 0; while (i < RemainingCh && !IsWordBreakChar(a[i])) i++; while (i < RemainingCh && IsWordBreakChar(a[i])) i++; c->Offset += i; LArray Ln; if (c->Blk->OffsetToLine(c->Offset, NULL, &Ln)) c->LineHint = Ln.Last(); else c->LineHint = -1; Status = true; } else // Seek into next block? { goto SeekNextBlock; } break; } case SkUpPage: { LRect &Content = Areas[LRichTextEdit::ContentArea]; int LineHint = -1; int TargetY = In->Pos.y1 - Content.Y(); ssize_t Idx = HitTest(In->Pos.x1, MAX(TargetY, 0), LineHint); if (Idx >= 0) { ssize_t Offset = -1; Block *b = GetBlockByIndex(Idx, &Offset); if (b) { if (!c.Reset(new BlockCursor(b, Offset, LineHint))) break; Status = true; } } break; } case SkDownPage: { LRect &Content = Areas[LRichTextEdit::ContentArea]; int LineHint = -1; int TargetY = In->Pos.y1 + Content.Y(); ssize_t Idx = HitTest(In->Pos.x1, MIN(TargetY, DocumentExtent.y-1), LineHint); if (Idx >= 0) { ssize_t Offset = -1; int BlkIdx = -1; ssize_t CursorBlkIdx = Blocks.IndexOf(Cursor->Blk); Block *b = GetBlockByIndex(Idx, &Offset, &BlkIdx); if (!b || BlkIdx < CursorBlkIdx || (BlkIdx == CursorBlkIdx && Offset < Cursor->Offset)) { LAssert(!"GetBlockByIndex failed.\n"); LgiTrace("%s:%i - GetBlockByIndex failed.\n", _FL); } if (b) { if (!c.Reset(new BlockCursor(b, Offset, LineHint))) break; Status = true; } } break; } default: { return Error(_FL, "Unknown seek type."); } } if (Status) SetCursor(c, Select); return Status; } bool LRichTextPriv::CursorFirst() { if (!Cursor || !Selection) return true; ssize_t CIdx = Blocks.IndexOf(Cursor->Blk); ssize_t SIdx = Blocks.IndexOf(Selection->Blk); if (CIdx != SIdx) return CIdx < SIdx; return Cursor->Offset < Selection->Offset; } bool LRichTextPriv::SetCursor(LAutoPtr c, bool Select) { LRect InvalidRc(0, 0, -1, -1); if (!c || !c->Blk) return Error(_FL, "Invalid cursor."); if (Select && !Selection) { // Selection starting... save cursor as selection end point if (Cursor) InvalidRc = Cursor->Line; Selection = Cursor; } else if (!Select && Selection) { // Selection ending... invalidate selection region and delete // selection end point LRect r = SelectionRect(); InvalidateDoc(&r); Selection.Reset(); // LgiTrace("Ending selection delete(sel) Idx=%i\n", i); } else if (Select && Cursor) { // Changing selection... InvalidRc = Cursor->Line; } if (Cursor && !Select) { // Just moving cursor InvalidateDoc(&Cursor->Pos); } if (!Cursor) Cursor.Reset(new BlockCursor(*c)); else Cursor = c; // LgiTrace("%s:%i - SetCursor: %i, line: %i\n", _FL, Cursor->Offset, Cursor->LineHint); if (Cursor && Selection && *Cursor == *Selection) Selection.Reset(); Cursor->Blk->GetPosFromIndex(Cursor); UpdateStyleUI(); #if DEBUG_OUTLINE_CUR_DISPLAY_STR || DEBUG_OUTLINE_CUR_STYLE_TEXT InvalidateDoc(NULL); #else if (Select) InvalidRc.Union(&Cursor->Line); else InvalidateDoc(&Cursor->Pos); if (InvalidRc.Valid()) { // Update the screen InvalidateDoc(&InvalidRc); } #endif // Check the cursor is on the visible part of the document. if (Cursor->Pos.Valid()) ScrollTo(Cursor->Pos); return true; } LRect LRichTextPriv::SelectionRect() { LRect SelRc; if (Cursor) { SelRc = Cursor->Line; if (Selection) SelRc.Union(&Selection->Line); } else if (Selection) { SelRc = Selection->Line; } return SelRc; } ssize_t LRichTextPriv::IndexOfCursor(BlockCursor *c) { if (!c || !c->Blk) { Error(_FL, "Invalid cursor param."); return -1; } ssize_t CharPos = 0; for (unsigned i=0; iBlk == b) return CharPos + c->Offset; CharPos += b->Length(); } LAssert(0); return -1; } LPoint LRichTextPriv::ScreenToDoc(int x, int y) { LRect &Content = Areas[LRichTextEdit::ContentArea]; return LPoint(x - Content.x1, y - Content.y1 + ScrollOffsetPx); } LPoint LRichTextPriv::DocToScreen(int x, int y) { LRect &Content = Areas[LRichTextEdit::ContentArea]; return LPoint(x + Content.x1, y + Content.y1 - ScrollOffsetPx); } bool LRichTextPriv::Merge(Transaction *Trans, Block *a, Block *b) { TextBlock *ta = dynamic_cast(a); TextBlock *tb = dynamic_cast(b); if (!ta || !tb) return false; ta->Txt.Add(tb->Txt); ta->LayoutDirty = true; ta->Len += tb->Len; tb->Txt.Length(0); Blocks.Delete(b, true); Dirty = true; return true; } LSurface *LEmojiImage::GetEmojiImage() { if (!EmojiImg) { LString p = LGetSystemPath(LSP_APP_INSTALL); if (!p) { LgiTrace("%s:%i - No app install path.\n", _FL); return NULL; } char File[MAX_PATH_LEN] = ""; LMakePath(File, sizeof(File), p, "..\\src\\common\\Text\\Emoji\\EmojiMap.png"); LString a; if (!LFileExists(File)) a = LFindFile("EmojiMap.png"); EmojiImg.Reset(GdcD->Load(a ? a : File, false)); } return EmojiImg; } ssize_t LRichTextPriv::HitTest(int x, int y, int &LineHint, Block **Blk, ssize_t *BlkOffset) { ssize_t CharPos = 0; HitTestResult r(x, y); if (Blocks.Length() == 0) { Error(_FL, "No blocks."); return -1; } Block *b = Blocks.First(); LRect rc = b->GetPos(); if (y < rc.y1) { if (Blk) *Blk = b; return 0; } for (unsigned i=0; iGetPos(); bool Over = y >= p.y1 && y <= p.y2; if (b->HitTest(r)) { LineHint = r.LineHint; if (Blk) *Blk = b; if (BlkOffset) *BlkOffset = r.Idx; return CharPos + r.Idx; } else if (Over) { Error(_FL, "Block failed to hit, i=%i, pos=%s, y=%i.", i, p.GetStr(), y); } CharPos += b->Length(); } b = Blocks.Last(); rc = b->GetPos(); if (y > rc.y2) { if (Blk) *Blk = b; return CharPos + b->Length(); } return -1; } bool LRichTextPriv::CursorFromPos(int x, int y, LAutoPtr *Cursor, ssize_t *GlobalIdx) { ssize_t CharPos = 0; HitTestResult r(x, y); for (unsigned i=0; iHitTest(r)) { if (Cursor) Cursor->Reset(new BlockCursor(b, r.Idx, r.LineHint)); if (GlobalIdx) *GlobalIdx = CharPos + r.Idx; return true; } CharPos += b->Length(); } return false; } LRichTextPriv::Block *LRichTextPriv::GetBlockByIndex(ssize_t Index, ssize_t *Offset, int *BlockIdx, int *LineCount) { ssize_t CharPos = 0; int Lines = 0; for (unsigned i=0; iLength(); int Ln = b->GetLines(); if (Index >= CharPos && Index < CharPos + Len) { if (BlockIdx) *BlockIdx = i; if (Offset) *Offset = Index - CharPos; return b; } CharPos += b->Length(); Lines += Ln; } Block *b = Blocks.Last(); if (Offset) *Offset = b->Length(); if (BlockIdx) *BlockIdx = (int)Blocks.Length() - 1; if (LineCount) *LineCount = Lines; return b; } bool LRichTextPriv::Layout(LScrollBar *&ScrollY) { Flow f(this); ScrollLinePx = View->GetFont()->GetHeight(); LAssert(ScrollLinePx > 0); if (ScrollLinePx <= 0) ScrollLinePx = 16; LRect Client = Areas[LRichTextEdit::ContentArea]; Client.Offset(-Client.x1, -Client.y1); DocumentExtent.x = Client.X(); LCssTools Ct(this, Font); LRect Content = Ct.ApplyPadding(Client); f.Left = Content.x1; f.Right = Content.x2; f.Top = f.CurY = Content.y1; for (unsigned i=0; iOnLayout(f); if ((f.CurY > Client.Y()) && ScrollY==NULL && !ScrollChange) { // We need to add a scroll bar View->SetScrollBars(false, true); View->Invalidate(); ScrollChange = true; return false; } } DocumentExtent.y = f.CurY + (Client.y2 - Content.y2); if (ScrollY) { int Lines = (f.CurY + ScrollLinePx - 1) / ScrollLinePx; int PageLines = (Client.Y() + ScrollLinePx - 1) / ScrollLinePx; ScrollY->SetPage(PageLines); ScrollY->SetRange(Lines); } if (Cursor) { LAssert(Cursor->Blk != NULL); if (Cursor->Blk) Cursor->Blk->GetPosFromIndex(Cursor); } return true; } void LRichTextPriv::OnStyleChange(LRichTextEdit::RectType t) { LCss s; switch (t) { case LRichTextEdit::FontFamilyBtn: { LCss::StringsDef Fam(Values[t].Str()); s.FontFamily(Fam); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case LRichTextEdit::FontSizeBtn: { double Pt = Values[t].CastDouble(); s.FontSize(LCss::Len(LCss::LenPt, (float)Pt)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case LRichTextEdit::BoldBtn: { s.FontWeight(LCss::FontWeightBold); if (!ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case LRichTextEdit::ItalicBtn: { s.FontStyle(LCss::FontStyleItalic); if (!ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case LRichTextEdit::UnderlineBtn: { s.TextDecoration(LCss::TextDecorUnderline); if (ChangeSelectionStyle(&s, Values[t].CastBool())) StyleDirty.Add(t); break; } case LRichTextEdit::ForegroundColourBtn: { s.Color(LCss::ColorDef(LCss::ColorRgb, (uint32_t) Values[t].Value.Int64)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } case LRichTextEdit::BackgroundColourBtn: { s.BackgroundColor(LCss::ColorDef(LCss::ColorRgb, (uint32_t) Values[t].Value.Int64)); if (!ChangeSelectionStyle(&s, true)) StyleDirty.Add(t); break; } default: break; } } bool LRichTextPriv::ChangeSelectionStyle(LCss *Style, bool Add) { if (!Selection) return false; LAutoPtr Trans(new Transaction); bool Cf = CursorFirst(); LRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; LRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Start->Blk == End->Blk) { // Change style in the same block... ssize_t Len = End->Offset - Start->Offset; if (!Start->Blk->ChangeStyle(Trans, Start->Offset, Len, Style, Add)) return false; } else { // Multi-block style change... // 1) Change style on the content to the end of the first block Start->Blk->ChangeStyle(Trans, Start->Offset, -1, Style, Add); // 2) Change style on blocks between 'Start' and 'End' ssize_t i = Blocks.IndexOf(Start->Blk); if (i >= 0) { for (++i; Blocks[i] != End->Blk && i < (int)Blocks.Length(); i++) { LRichTextPriv::Block *&b = Blocks[i]; if (!b->ChangeStyle(Trans, 0, -1, Style, Add)) return false; } } else { return Error(_FL, "Start block has no index."); } // 3) Change style up to the Cursor in the 'End' block if (!End->Blk->ChangeStyle(Trans, 0, End->Offset, Style, Add)) return false; } Cursor->Pos.ZOff(-1, -1); InvalidateDoc(NULL); AddTrans(Trans); return true; } void LRichTextPriv::PaintBtn(LSurface *pDC, LRichTextEdit::RectType t) { LRect r = Areas[t]; LVariant &v = Values[t]; bool Down = (v.Type == GV_BOOL && v.Value.Bool) || (BtnState[t].IsPress && BtnState[t].Pressed && BtnState[t].MouseOver); LSysFont->Colour(L_TEXT, BtnState[t].MouseOver ? L_LIGHT : L_MED); LSysFont->Transparent(false); LColour Low(96, 96, 96); pDC->Colour(Down ? LColour::White : Low); pDC->Line(r.x1, r.y2, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x2, r.y2); pDC->Colour(Down ? Low : LColour::White); pDC->Line(r.x1, r.y1, r.x2, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2); r.Inset(1, 1); switch (v.Type) { case GV_STRING: { LDisplayString Ds(LSysFont, v.Str()); Ds.Draw(pDC, r.x1 + ((r.X()-Ds.X())>>1) + Down, r.y1 + ((r.Y()-Ds.Y())>>1) + Down, &r); break; } case GV_INT64: { if (v.Value.Int64) { pDC->Colour((uint32_t)v.Value.Int64, 32); pDC->Rectangle(&r); } else { // Transparent int g[2] = { 128, 192 }; pDC->ClipRgn(&r); for (int y=0; y>1)%2) ^ ((x>>1)%2); pDC->Colour(LColour(g[i],g[i],g[i])); pDC->Rectangle(r.x1+x, r.y1+y, r.x1+x+1, r.y1+y+1); } } pDC->ClipRgn(NULL); } break; } case GV_BOOL: { const char *Label = NULL; switch (t) { case LRichTextEdit::BoldBtn: Label = "B"; break; case LRichTextEdit::ItalicBtn: Label = "I"; break; case LRichTextEdit::UnderlineBtn: Label = "U"; break; default: break; } if (!Label) break; LDisplayString Ds(LSysFont, Label); Ds.Draw(pDC, r.x1 + ((r.X()-Ds.X())>>1) + Down, r.y1 + ((r.Y()-Ds.Y())>>1) + Down, &r); break; } default: break; } } bool LRichTextPriv::MakeLink(TextBlock *tb, ssize_t Offset, ssize_t Len, LString Link) { if (!tb) return false; LArray st; if (!tb->GetTextAt(Offset, st)) return false; LAutoPtr ns(new LNamedStyle); if (ns) { if (st.Last()->GetStyle()) *ns = *st.Last()->GetStyle(); ns->TextDecoration(LCss::TextDecorUnderline); ns->Color(LCss::ColorDef(LCss::ColorRgb, LColour::Blue.c32())); LAutoPtr Trans(new Transaction); tb->ChangeStyle(Trans, Offset, Len, ns, true); if (tb->GetTextAt(Offset + 1, st)) { st.First()->Element = TAG_A; st.First()->Param = Link; } AddTrans(Trans); } return true; } bool LRichTextPriv::ClickBtn(LMouse &m, LRichTextEdit::RectType t) { switch (t) { case LRichTextEdit::FontFamilyBtn: { LString::Array Fonts; if (!LFontSystem::Inst()->EnumerateFonts(Fonts)) return Error(_FL, "EnumerateFonts failed."); bool UseSub = (LSysFont->GetHeight() * Fonts.Length()) > (GdcD->Y() * 0.8); LSubMenu s; LSubMenu *Cur = NULL; int Idx = 1; char Last = 0; for (unsigned i=0; iAppendItem(f, Idx++); else break; } else { s.AppendItem(f, Idx++); } } LPoint p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); int Result = s.Float(View, p.x, p.y, true); if (Result) { Values[t] = Fonts[Result-1]; View->Invalidate(Areas+t); OnStyleChange(t); } break; } case LRichTextEdit::FontSizeBtn: { static const char *Sizes[] = { "6", "7", "8", "9", "10", "11", "12", "14", "16", "18", "20", "24", "28", "32", "40", "50", "60", "80", "100", "120", 0 }; LSubMenu s; for (int Idx = 0; Sizes[Idx]; Idx++) s.AppendItem(Sizes[Idx], Idx+1); LPoint p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); int Result = s.Float(View, p.x, p.y, true); if (Result) { Values[t] = Sizes[Result-1]; View->Invalidate(Areas+t); OnStyleChange(t); } break; } case LRichTextEdit::BoldBtn: case LRichTextEdit::ItalicBtn: case LRichTextEdit::UnderlineBtn: { Values[t] = !Values[t].CastBool(); View->Invalidate(Areas+t); OnStyleChange(t); break; } case LRichTextEdit::ForegroundColourBtn: case LRichTextEdit::BackgroundColourBtn: { LPoint p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); new SelectColour(this, p, t); break; } case LRichTextEdit::MakeLinkBtn: { if (!Cursor || !Cursor->Blk) break; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) break; LArray st; if (!tb->GetTextAt(Cursor->Offset, st)) break; StyleText *a = st.Length() > 1 && st[1]->Element == TAG_A ? st[1] : st.First()->Element == TAG_A ? st[0] : NULL; if (a) { // Edit the existing link... - LInput i(View, a->Param, "Edit link:", "Link"); - if (i.DoModal()) + auto i = new LInput(View, a->Param, "Edit link:", "Link"); + i->DoModal([this, i, a](auto dlg, auto ctrlId) { - a->Param = i.GetStr(); - } + if (ctrlId == IDOK) + a->Param = i->GetStr(); + delete dlg; + }); } else if (Selection) { // Turn current selection into link - LInput i(View, NULL, "Edit link:", "Link"); - if (i.DoModal()) + auto i = new LInput(View, NULL, "Edit link:", "Link"); + i->DoModal([this, i, tb](auto dlg, auto ctrlId) { - BlockCursor *Start = CursorFirst() ? Cursor : Selection; - BlockCursor *End = CursorFirst() ? Selection : Cursor; - if (!Start || !End) return false; - if (Start->Blk != End->Blk) + if (ctrlId == IDOK) { - LgiMsg(View, "Selection too large.", "Error"); - return false; + BlockCursor *Start = CursorFirst() ? Cursor : Selection; + BlockCursor *End = CursorFirst() ? Selection : Cursor; + if (!Start || !End) + ; + else if (Start->Blk != End->Blk) + LgiMsg(View, "Selection too large.", "Error"); + else + MakeLink(tb, + Start->Offset, + End->Offset - Start->Offset, + i->GetStr()); } - - MakeLink(tb, - Start->Offset, - End->Offset - Start->Offset, - i.GetStr()); - } + delete dlg; + }); } break; } case LRichTextEdit::RemoveLinkBtn: { if (!Cursor || !Cursor->Blk) break; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) break; LArray st; if (!tb->GetTextAt(Cursor->Offset, st)) break; StyleText *a = st.Length() > 1 && st[1]->Element == TAG_A ? st[1] : st.First()->Element == TAG_A ? st[0] : NULL; if (a) { // Remove the existing link... a->Element = CONTENT; a->Param.Empty(); LAutoPtr s(new LCss); LNamedStyle *Ns = a->GetStyle(); if (Ns) *s = *Ns; if (s->TextDecoration() == LCss::TextDecorUnderline) s->DeleteProp(LCss::PropTextDecoration); if ((LColour)s->Color() == LColour::Blue) s->DeleteProp(LCss::PropColor); Ns = AddStyleToCache(s); a->SetStyle(Ns); tb->LayoutDirty = true; InvalidateDoc(NULL); } break; } case LRichTextEdit::RemoveStyleBtn: { LCss s; ChangeSelectionStyle(&s, false); break; } /* case LRichTextEdit::CapabilityBtn: { View->OnCloseInstaller(); break; } */ case LRichTextEdit::EmojiBtn: { LPoint p(Areas[t].x1, Areas[t].y2 + 1); View->PointToScreen(p); new EmojiMenu(this, p); break; } case LRichTextEdit::HorzRuleBtn: { InsertHorzRule(); break; } default: return false; } return true; } bool LRichTextPriv::InsertHorzRule() { if (!Cursor || !Cursor->Blk) return false; TextBlock *tb = dynamic_cast(Cursor->Blk); if (!tb) return false; LAutoPtr Trans(new Transaction); DeleteSelection(Trans, NULL); ssize_t InsertIdx = Blocks.IndexOf(tb) + 1; LRichTextPriv::Block *After = NULL; if (Cursor->Offset == 0) { InsertIdx--; } else if (Cursor->Offset < tb->Length()) { After = tb->Split(Trans, Cursor->Offset); if (!After) return false; tb->StripLast(Trans); } HorzRuleBlock *Hr = new HorzRuleBlock(this); if (!Hr) return false; Blocks.AddAt(InsertIdx++, Hr); if (After) Blocks.AddAt(InsertIdx++, After); AddTrans(Trans); InvalidateDoc(NULL); LAutoPtr c(new BlockCursor(Hr, 0, 0)); return SetCursor(c); } void LRichTextPriv::Paint(LSurface *pDC, LScrollBar *&ScrollY) { if (Areas[LRichTextEdit::ToolsArea].Valid()) { // Draw tools area... LRect &t = Areas[LRichTextEdit::ToolsArea]; #ifdef WIN32 LDoubleBuffer Buf(pDC, &t); #endif LColour ToolBar = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_LOW)); pDC->Colour(ToolBar); pDC->Rectangle(&t); LRect r = t; r.Inset(3, 3); #define AllocPx(sz, border) \ LRect(r.x1, r.y1, r.x1 + (int)(sz) - 1, r.y2); r.x1 += (int)(sz) + border Areas[LRichTextEdit::FontFamilyBtn] = AllocPx(130, 6); Areas[LRichTextEdit::FontSizeBtn] = AllocPx(40, 6); Areas[LRichTextEdit::BoldBtn] = AllocPx(r.Y(), 0); Areas[LRichTextEdit::ItalicBtn] = AllocPx(r.Y(), 0); Areas[LRichTextEdit::UnderlineBtn] = AllocPx(r.Y(), 6); Areas[LRichTextEdit::ForegroundColourBtn] = AllocPx(r.Y()*1.5, 0); Areas[LRichTextEdit::BackgroundColourBtn] = AllocPx(r.Y()*1.5, 6); { LDisplayString Ds(LSysFont, TEXT_LINK); Areas[LRichTextEdit::MakeLinkBtn] = AllocPx(Ds.X() + 12, 0); } { LDisplayString Ds(LSysFont, TEXT_REMOVE_LINK); Areas[LRichTextEdit::RemoveLinkBtn] = AllocPx(Ds.X() + 12, 6); } { LDisplayString Ds(LSysFont, TEXT_REMOVE_STYLE); Areas[LRichTextEdit::RemoveStyleBtn] = AllocPx(Ds.X() + 12, 6); } for (unsigned int i=LRichTextEdit::EmojiBtn; iGetColourSpace())) { LAssert(!"MemDC creation failed."); return; } LSurface *pScreen = pDC; pDC = &Mem; r.Offset(-r.x1, -r.y1); #else pDC->GetOrigin(Origin.x, Origin.y); pDC->ClipRgn(&r); #endif ScrollOffsetPx = ScrollY ? (int)(ScrollY->Value() * ScrollLinePx) : 0; pDC->SetOrigin(Origin.x-r.x1, Origin.y-r.y1+ScrollOffsetPx); int DrawPx = ScrollOffsetPx + Areas[LRichTextEdit::ContentArea].Y(); int ExtraPx = DrawPx > DocumentExtent.y ? DrawPx - DocumentExtent.y : 0; r.Set(0, 0, DocumentExtent.x-1, DocumentExtent.y-1); // Fill the padding... LCssTools ct(this, Font); r = ct.PaintPadding(pDC, r); // Fill the background... #if DEBUG_COVERAGE_CHECK pDC->Colour(LColour(255, 0, 255)); #else LCss::ColorDef cBack = BackgroundColor(); // pDC->Colour(cBack.IsValid() ? (LColour)cBack : LColour(LC_WORKSPACE, 24)); #endif pDC->Rectangle(&r); if (ExtraPx) pDC->Rectangle(0, DocumentExtent.y, DocumentExtent.x-1, DocumentExtent.y+ExtraPx); PaintContext Ctx; Ctx.pDC = pDC; Ctx.Cursor = Cursor; Ctx.Select = Selection; Ctx.Colours[Unselected].Fore.Set(L_TEXT); Ctx.Colours[Unselected].Back.Set(L_WORKSPACE); if (View->Focus()) { Ctx.Colours[Selected].Fore.Set(L_FOCUS_SEL_FORE); Ctx.Colours[Selected].Back.Set(L_FOCUS_SEL_BACK); } else { Ctx.Colours[Selected].Fore.Set(L_NON_FOCUS_SEL_FORE); Ctx.Colours[Selected].Back.Set(L_NON_FOCUS_SEL_BACK); } for (unsigned i=0; iOnPaint(Ctx); #if DEBUG_OUTLINE_BLOCKS pDC->Colour(LColour(192, 192, 192)); pDC->LineStyle(LSurface::LineDot); pDC->Box(&b->GetPos()); pDC->LineStyle(LSurface::LineSolid); #endif } } #ifdef _DEBUG pDC->Colour(LColour::Green); for (unsigned i=0; iBox(&DebugRects[i]); } #endif #if 0 // Outline the line the cursor is on if (Cursor) { pDC->Colour(LColour::Blue); pDC->LineStyle(LSurface::LineDot); pDC->Box(&Cursor->Line); } #endif #if defined(WINDOWS) && !DEBUG_NO_DOUBLE_BUF Mem.SetOrigin(0, 0); pScreen->Blt(Areas[LRichTextEdit::ContentArea].x1, Areas[LRichTextEdit::ContentArea].y1, &Mem); #else pDC->ClipRgn(NULL); #endif } LHtmlElement *LRichTextPriv::CreateElement(LHtmlElement *Parent) { return new LRichEditElem(Parent); } bool LRichTextPriv::ToHtml(LArray *Media, BlockCursor *From, BlockCursor *To) { UtfNameCache.Reset(); if (!Blocks.Length()) return false; LStringPipe p(256); p.Print("\n" "\n" "\t\n"); ZeroRefCounts(); ssize_t Start = From ? Blocks.IndexOf(From->Blk) : 0; ssize_t End = To ? Blocks.IndexOf(To->Blk) : Blocks.Length() - 1; ssize_t StartIdx = From ? From->Offset : 0; ssize_t EndIdx = To ? To->Offset : Blocks.Last()->Length(); for (ssize_t i=Start; i<=End; i++) { Blocks[i]->IncAllStyleRefs(); } if (GetStyles()) { p.Print("\t\n"); } p.Print("\n" "\n"); for (ssize_t i=Start; i<=End; i++) { Block *b = Blocks[i]; LRange r; if (i == Start) r.Start = StartIdx; if (i == End) r.Len = EndIdx - r.Start; b->ToHtml(p, Media, r.Valid() ? &r : NULL); } p.Print("\n\n"); LAutoString a(p.NewStr()); UtfNameCache = a; return UtfNameCache.Get() != NULL; } void LRichTextPriv::DumpBlocks() { LgiTrace("LRichTextPriv Blocks=%i\n", Blocks.Length()); for (unsigned i=0; iGetClass(), b->GetStyle(), b->GetStyle() ? b->GetStyle()->Name.Get() : NULL); b->Dump(); LgiTrace("}\n"); } } struct HtmlElementCb : public LCss::ElementCallback { const char *GetElement(LHtmlElement *obj) { return obj->Tag; } const char *GetAttr(LHtmlElement *obj, const char *Attr) { const char *Val = NULL; return obj->Get(Attr, Val) ? Val : NULL; } bool GetClasses(LString::Array &Classes, LHtmlElement *obj) { const char *Cls = NULL; if (!obj->Get("class", Cls)) return false; Classes = LString(Cls).SplitDelimit(); return true; } LHtmlElement *GetParent(LHtmlElement *obj) { return obj->Parent; } LArray GetChildren(LHtmlElement *obj) { return obj->Children; } }; bool LRichTextPriv::FromHtml(LHtmlElement *e, CreateContext &ctx, LCss *ParentStyle, int Depth) { char Sp[48]; int SpLen = MIN(Depth << 1, sizeof(Sp) - 1); memset(Sp, ' ', SpLen); Sp[SpLen] = 0; for (unsigned i = 0; i < e->Children.Length(); i++) { LHtmlElement *c = e->Children[i]; LAutoPtr Style; if (ParentStyle) Style.Reset(new LCss(*ParentStyle)); LCss::SelArray Matches; HtmlElementCb Cb; if (ctx.StyleStore.Match(Matches, &Cb, c) && Matches.Length() > 0 && (Style.Get() || Style.Reset(new LCss))) { for (auto s : Matches) { const char *p = s->Style; Style->Parse(p, LCss::ParseRelaxed); } } // Check to see if the element is block level and end the previous // paragraph if so. c->Info = c->Tag ? LHtmlStatic::Inst->GetTagInfo(c->Tag) : NULL; bool IsBlock = c->Info != NULL && c->Info->Block(); switch (c->TagId) { case TAG_STYLE: { char16 *Style = e->GetText(); if (ValidStrW(Style)) LAssert(!"Impl me."); continue; break; } case TAG_B: { if (!Style) Style.Reset(new LCss); if (Style) Style->FontWeight(LCss::FontWeightBold); break; } case TAG_I: { if (!Style) Style.Reset(new LCss); if (Style) Style->FontStyle(LCss::FontStyleItalic); break; } case TAG_BLOCKQUOTE: { if (!Style) Style.Reset(new LCss); if (Style) { Style->MarginTop(LCss::Len("0.5em")); Style->MarginBottom(LCss::Len("0.5em")); Style->MarginLeft(LCss::Len("1em")); if (ctx.Tb) ctx.Tb->StripLast(NoTransaction); } break; } case TAG_A: { if (!Style) Style.Reset(new LCss); if (Style) { Style->TextDecoration(LCss::TextDecorUnderline); Style->Color(LCss::ColorDef(LCss::ColorRgb, LColour::Blue.c32())); } break; } case TAG_HR: { if (ctx.Tb) ctx.Tb->StripLast(NoTransaction); // Fall through } case TAG_IMG: { ctx.Tb = NULL; IsBlock = true; break; } default: { break; } } const char *Css, *Class; if (c->Get("style", Css)) { if (!Style) Style.Reset(new LCss); if (Style) Style->Parse(Css, ParseRelaxed); } if (c->Get("class", Class)) { LCss::SelArray Selectors; LRichEditElemContext StyleCtx; if (ctx.StyleStore.Match(Selectors, &StyleCtx, dynamic_cast(c))) { for (unsigned n=0; nStyle; if (s) { if (!Style) Style.Reset(new LCss); if (Style) Style->Parse(s, LCss::ParseRelaxed); } } } } } LNamedStyle *CachedStyle = AddStyleToCache(Style); if ( (IsBlock && ctx.LastChar != '\n') || c->TagId == TAG_BR ) { if (!ctx.Tb && c->TagId == TAG_BR) { // Don't do this for IMG and HR layout. Blocks.Add(ctx.Tb = new TextBlock(this)); if (CachedStyle && ctx.Tb) ctx.Tb->SetStyle(CachedStyle); } if (ctx.Tb) { const uint32_t Nl[] = {'\n', 0}; ctx.Tb->AddText(NoTransaction, -1, Nl, 1, NULL); ctx.LastChar = '\n'; ctx.StartOfLine = true; } } bool EndStyleChange = false; if (c->TagId == TAG_IMG) { Blocks.Add(ctx.Ib = new ImageBlock(this)); if (ctx.Ib) { const char *s; if (c->Get("src", s)) ctx.Ib->Source = s; if (c->Get("width", s)) { LCss::Len Sz(s); int Px = Sz.ToPx(); if (Px) ctx.Ib->Size.x = Px; } if (c->Get("height", s)) { LCss::Len Sz(s); int Px = Sz.ToPx(); if (Px) ctx.Ib->Size.y = Px; } if (CachedStyle) ctx.Ib->SetStyle(CachedStyle); ctx.Ib->Load(); } } else if (c->TagId == TAG_HR) { Blocks.Add(ctx.Hrb = new HorzRuleBlock(this)); } else if (c->TagId == TAG_A) { ctx.StartOfLine |= ctx.AddText(CachedStyle, c->GetText()); if (ctx.Tb && ctx.Tb->Txt.Length()) { StyleText *st = ctx.Tb->Txt.Last(); st->Element = TAG_A; const char *Link; if (c->Get("href", Link)) st->Param = Link; } } else { if (IsBlock && ctx.Tb != NULL) { if (CachedStyle != ctx.Tb->GetStyle()) { if (ctx.Tb->Length() == 0) { ctx.Tb->SetStyle(CachedStyle); } else { // Start a new block because the styles are different... EndStyleChange = true; auto Idx = Blocks.IndexOf(ctx.Tb); ctx.Tb = new TextBlock(this); if (Idx >= 0) Blocks.AddAt(Idx+1, ctx.Tb); else Blocks.Add(ctx.Tb); if (CachedStyle) ctx.Tb->SetStyle(CachedStyle); } } } char16 *Txt = c->GetText(); if ( Txt && ( !ctx.StartOfLine || ValidStrW(Txt) ) ) { if (!ctx.Tb) { Blocks.Add(ctx.Tb = new TextBlock(this)); ctx.Tb->SetStyle(CachedStyle); } #ifdef __GTK_H__ for (auto *i = Txt; *i; i++) if (*i == 0xa0) *i = ' '; #endif ctx.AddText(CachedStyle, Txt); ctx.StartOfLine = false; } } if (!FromHtml(c, ctx, Style, Depth + 1)) return false; if (EndStyleChange) ctx.Tb = NULL; if (IsBlock) ctx.StartOfLine = true; } return true; } bool LRichTextPriv::GetSelection(LArray *Text, LAutoString *Html) { if (!Text && !Html) return false; LArray Utf32; bool Cf = CursorFirst(); LRichTextPriv::BlockCursor *Start = Cf ? Cursor : Selection; LRichTextPriv::BlockCursor *End = Cf ? Selection : Cursor; if (Html) { if (ToHtml(NULL, Start, End)) *Html = UtfNameCache; } if (Text) { if (Start->Blk == End->Blk) { // In the same block... just copy ssize_t Len = End->Offset - Start->Offset; Start->Blk->CopyAt(Start->Offset, Len, &Utf32); } else { // Multi-block copy... // 1) Copy the content to the end of the first block Start->Blk->CopyAt(Start->Offset, -1, &Utf32); // 2) Copy any blocks between 'Start' and 'End' ssize_t i = Blocks.IndexOf(Start->Blk); ssize_t EndIdx = Blocks.IndexOf(End->Blk); if (i >= 0 && EndIdx >= i) { for (++i; Blocks[i] != End->Blk && i < (int)Blocks.Length(); i++) { LRichTextPriv::Block *&b = Blocks[i]; b->CopyAt(0, -1, &Utf32); } } else return Error(_FL, "Blocks missing index: %i, %i.", i, EndIdx); // 3) Delete any text up to the Cursor in the 'End' block End->Blk->CopyAt(0, End->Offset, &Utf32); } char16 *w = (char16*)LNewConvertCp(LGI_WideCharset, &Utf32[0], "utf-32", Utf32.Length() * sizeof(uint32_t)); if (!w) return Error(_FL, "Failed to convert %i utf32 to wide.", Utf32.Length()); Text->Add(w, Strlen(w)); Text->Add(0); } return true; } LRichTextEdit::RectType LRichTextPriv::PosToButton(LMouse &m) { if (Areas[LRichTextEdit::ToolsArea].Overlap(m.x, m.y) // || Areas[LRichTextEdit::CapabilityArea].Overlap(m.x, m.y) ) { for (unsigned i=LRichTextEdit::FontFamilyBtn; iOnComponentInstall(Name); } } #ifdef _DEBUG void LRichTextPriv::DumpNodes(LTree *Root) { if (Cursor) { LTreeItem *ti = new LTreeItem; ti->SetText("Cursor"); PrintNode(ti, "Offset=%i", Cursor->Offset); PrintNode(ti, "Pos=%s", Cursor->Pos.GetStr()); PrintNode(ti, "LineHint=%i", Cursor->LineHint); PrintNode(ti, "Blk=%i", Cursor->Blk ? Blocks.IndexOf(Cursor->Blk) : -2); Root->Insert(ti); } if (Selection) { LTreeItem *ti = new LTreeItem; ti->SetText("Selection"); PrintNode(ti, "Offset=%i", Selection->Offset); PrintNode(ti, "Pos=%s", Selection->Pos.GetStr()); PrintNode(ti, "LineHint=%i", Selection->LineHint); PrintNode(ti, "Blk=%i", Selection->Blk ? Blocks.IndexOf(Selection->Blk) : -2); Root->Insert(ti); } for (unsigned i=0; iDumpNodes(ti); LString s; s.Printf("[%i] %s", i, ti->GetText()); ti->SetText(s); Root->Insert(ti); } } LTreeItem *PrintNode(LTreeItem *Parent, const char *Fmt, ...) { LTreeItem *i = new LTreeItem; LString s; va_list Arg; va_start(Arg, Fmt); s.Printf(Arg, Fmt); va_end(Arg); s = s.Replace("\n", "\\n"); i->SetText(s); Parent->Insert(i); return i; } #endif diff --git a/src/common/Widgets/Editor/RichTextEditPriv.h b/src/common/Widgets/Editor/RichTextEditPriv.h --- a/src/common/Widgets/Editor/RichTextEditPriv.h +++ b/src/common/Widgets/Editor/RichTextEditPriv.h @@ -1,1397 +1,1397 @@ /* Rich text design notes: - The document is an array of Blocks (Blocks have no hierarchy) - Blocks have a length in characters. New lines are considered as one '\n' char. - The main type of block is the TextBlock - TextBlock contains: - array of StyleText: This is the source text. Each run of text has a style associated with it. This forms the input to the layout algorithm and is what the user is editing. - array of TextLine: Contains all the info needed to render one line of text. Essentially the output of the layout engine. Contains an array of DisplayStr objects. i.e. Characters in the exact same style as each other. It will regularly be deleted and re-flowed from the StyleText objects. - For a plaint text document the entire thing is contained by the one TextBlock. - There is an Image block, where the image is treated as one character object. - Also a horizontal rule block. */ #ifndef _RICH_TEXT_EDIT_PRIV_H_ #define _RICH_TEXT_EDIT_PRIV_H_ #include "lgi/common/HtmlCommon.h" #include "lgi/common/HtmlParser.h" #include "lgi/common/FontCache.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ColourSpace.h" #include "lgi/common/Popup.h" #include "lgi/common/Emoji.h" #include "lgi/common/SpellCheck.h" #define DEBUG_LOG_CURSOR_COUNT 0 #define DEBUG_OUTLINE_CUR_DISPLAY_STR 0 #define DEBUG_OUTLINE_CUR_STYLE_TEXT 0 #define DEBUG_OUTLINE_BLOCKS 0 #define DEBUG_NO_DOUBLE_BUF 0 #define DEBUG_COVERAGE_CHECK 0 #define DEBUG_NUMBERED_LAYOUTS 0 #if 0 // _DEBUG #define LOG_FN LgiTrace #else #define LOG_FN d->Log->Print #endif #define TEXT_LINK "Link" #define TEXT_REMOVE_LINK "X" #define TEXT_REMOVE_STYLE "Remove Style" #define TEXT_CAP_BTN "Ok" #define TEXT_EMOJI ":)" #define TEXT_HORZRULE "HR" #define RTE_CURSOR_BLINK_RATE 1000 #define RTE_PULSE_RATE 200 #define RICH_TEXT_RESIZED_JPEG_QUALITY 83 // out of 100, high = better quality #define NoTransaction NULL #define IsWordBreakChar(ch) \ ( \ ( \ (ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n' \ ) \ || \ ( \ EmojiToIconIndex(&(ch), 1).Index >= 0 \ ) \ ) enum RteCommands { // IDM_OPEN = 10, IDM_NEW = 2000, IDM_RTE_COPY, IDM_RTE_CUT, IDM_RTE_PASTE, IDM_RTE_UNDO, IDM_RTE_REDO, IDM_COPY_URL, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ORIGINAL, IDM_COPY_CURRENT, IDM_DUMP_NODES, IDM_CLOCKWISE, IDM_ANTI_CLOCKWISE, IDM_X_FLIP, IDM_Y_FLIP, IDM_SCALE_IMAGE, IDM_OPEN_URL, CODEPAGE_BASE = 100, CONVERT_CODEPAGE_BASE = 200, SPELLING_BASE = 300 }; ////////////////////////////////////////////////////////////////////// #define PtrCheckBreak(ptr) if (!ptr) { LAssert(!"Invalid ptr"); break; } #undef FixedToInt #define FixedToInt(fixed) ((fixed)>>LDisplayString::FShift) #undef IntToFixed #define IntToFixed(val) ((val)<, LString> Attr; public: LRichEditElem(LHtmlElement *parent) : LHtmlElement(parent) { } bool Get(const char *attr, const char *&val) { if (!attr) return false; LString s = Attr.Find(attr); if (!s) return false; val = s; return true; } void Set(const char *attr, const char *val) { if (!attr) return; Attr.Add(attr, LString(val)); } void SetStyle() { } }; struct LRichEditElemContext : public LCss::ElementCallback { /// Returns the element name const char *GetElement(LRichEditElem *obj); /// Returns the document unque element ID const char *GetAttr(LRichEditElem *obj, const char *Attr); /// Returns the class bool GetClasses(LString::Array &Classes, LRichEditElem *obj); /// Returns the parent object LRichEditElem *GetParent(LRichEditElem *obj); /// Returns an array of child objects LArray GetChildren(LRichEditElem *obj); }; class LDocFindReplaceParams3 : public LDocFindReplaceParams { public: // Find/Replace History char16 *LastFind; char16 *LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; LDocFindReplaceParams3() { LastFind = 0; LastReplace = 0; MatchCase = false; MatchWord = false; SelectionOnly = false; } ~LDocFindReplaceParams3() { DeleteArray(LastFind); DeleteArray(LastReplace); } }; struct LNamedStyle : public LCss { int RefCount; LString Name; LNamedStyle() { RefCount = 0; } }; class LCssCache { int Idx; LArray Styles; LString Prefix; public: LCssCache(); ~LCssCache(); void SetPrefix(LString s) { Prefix = s; } uint32_t GetStyles(); void ZeroRefCounts(); bool OutputStyles(LStream &s, int TabDepth); LNamedStyle *AddStyleToCache(LAutoPtr &s); }; class LRichTextPriv; class SelectColour : public LPopup { LRichTextPriv *d; LRichTextEdit::RectType Type; struct Entry { LRect r; LColour c; }; LArray e; public: SelectColour(LRichTextPriv *priv, LPoint p, LRichTextEdit::RectType t); const char *GetClass() { return "SelectColour"; } void OnPaint(LSurface *pDC); void OnMouseClick(LMouse &m); void Visible(bool i); }; class EmojiMenu : public LPopup { LRichTextPriv *d; struct Emoji { LRect Src, Dst; uint32_t u; }; struct Pane { LRect Btn; LArray e; }; LArray Panes; static int Cur; public: EmojiMenu(LRichTextPriv *priv, LPoint p); void OnPaint(LSurface *pDC); void OnMouseClick(LMouse &m); void Visible(bool i); bool InsertEmoji(uint32_t Ch); }; struct CtrlCap { LString Name, Param; void Set(const char *name, const char *param) { Name = name; Param = param; } }; struct ButtonState { uint8_t IsMenu : 1; uint8_t IsPress : 1; uint8_t Pressed : 1; uint8_t MouseOver : 1; }; extern bool Utf16to32(LArray &Out, const uint16_t *In, ssize_t Len); class LEmojiImage { LAutoPtr EmojiImg; public: LSurface *GetEmojiImage(); }; class LRichTextPriv : public LCss, public LHtmlParser, public LHtmlStaticInst, public LCssCache, public LFontCache, public LEmojiImage { LStringPipe LogBuffer; public: enum SelectModeType { Unselected = 0, Selected = 1, }; enum SeekType { SkUnknown, SkLineStart, SkLineEnd, SkDocStart, SkDocEnd, // Horizontal navigation SkLeftChar, SkLeftWord, SkRightChar, SkRightWord, // Vertical navigation SkUpPage, SkUpLine, SkCurrentLine, SkDownLine, SkDownPage, }; struct DisplayStr; struct BlockCursor; class Block; LRichTextEdit *View; LString OriginalText; LAutoWString WideNameCache; LAutoString UtfNameCache; LAutoPtr Font; bool WordSelectMode; bool Dirty; LPoint DocumentExtent; // Px LString Charset; LHtmlStaticInst Inst; int NextUid; LStream *Log; bool HtmlLinkAsCid; uint64 BlinkTs; // Spell check support LSpellCheck *SpellCheck; bool SpellDictionaryLoaded; LString SpellLang, SpellDict; // This is set when the user changes a style without a selection, // indicating that we should start a new run when new text is entered LArray StyleDirty; // Toolbar bool ShowTools; LRichTextEdit::RectType ClickedBtn, OverBtn; ButtonState BtnState[LRichTextEdit::MaxArea]; LRect Areas[LRichTextEdit::MaxArea]; LVariant Values[LRichTextEdit::MaxArea]; // Scrolling int ScrollLinePx; int ScrollOffsetPx; bool ScrollChange; // Eat keys (OS bug work arounds) LArray EatVkeys; // Debug stuff LArray DebugRects; // Constructor LRichTextPriv(LRichTextEdit *view, LRichTextPriv **Ptr); ~LRichTextPriv(); bool Error(const char *file, int line, const char *fmt, ...); bool IsBusy(bool Stop = false); struct Flow { LRichTextPriv *d; LSurface *pDC; // Used for printing. int Left, Right;// Left and right margin positions as measured in px // from the left of the page (controls client area). int Top; int CurY; // Current y position down the page in document co-ords bool Visible; // true if the current block overlaps the visible page // If false, the implementation can take short cuts and // guess various dimensions. Flow(LRichTextPriv *priv) { d = priv; pDC = NULL; Left = 0; Top = 0; Right = 1000; CurY = 0; Visible = true; } int X() { return Right - Left + 1; } LString Describe() { LString s; s.Printf("Left=%i Right=%i CurY=%i", Left, Right, CurY); return s; } }; struct ColourPair { LColour Fore, Back; void Empty() { Fore.Empty(); Back.Empty(); } }; /// This is a run of text, all of the same style class StyleText : public LArray { LNamedStyle *Style; // owned by the CSS cache public: ColourPair Colours; HtmlTag Element; LString Param; bool Emoji; StyleText(const StyleText *St); StyleText(const uint32_t *t = NULL, ssize_t Chars = -1, LNamedStyle *style = NULL); uint32_t *At(ssize_t i); LNamedStyle *GetStyle(); void SetStyle(LNamedStyle *s); }; struct PaintContext { int Index; LSurface *pDC; SelectModeType Type; ColourPair Colours[2]; BlockCursor *Cursor, *Select; // Cursor stuff int CurEndPoint; LArray EndPoints; PaintContext() { Index = 0; pDC = NULL; Type = Unselected; Cursor = NULL; Select = NULL; CurEndPoint = 0; } LColour &Fore() { return Colours[Type].Fore; } LColour &Back() { return Colours[Type].Back; } void DrawBox(LRect &r, LRect &Edge, LColour &c) { if (Edge.x1 > 0 || Edge.x2 > 0 || Edge.y1 > 0 || Edge.y2 > 0) { pDC->Colour(c); if (Edge.x1) { pDC->Rectangle(r.x1, r.y1, r.x1 + Edge.x1 - 1, r.y2); r.x1 += Edge.x1; } if (Edge.y1) { pDC->Rectangle(r.x1, r.y1, r.x2, r.y1 + Edge.y1 - 1); r.y1 += Edge.y1; } if (Edge.y2) { pDC->Rectangle(r.x1, r.y2 - Edge.y2 + 1, r.x2, r.y2); r.y2 -= Edge.y2; } if (Edge.x2) { pDC->Rectangle(r.x2 - Edge.x2 + 1, r.y1, r.x2, r.y2); r.x2 -= Edge.x2; } } } // This handles calculating the selection stuff for simple "one char" blocks // like images and HR. Call this at the start of the OnPaint. // \return TRUE if the content should be drawn selected. bool SelectBeforePaint(class LRichTextPriv::Block *b) { CurEndPoint = 0; if (b->Cursors > 0 && Select) { // Selection end point checks... if (Cursor && Cursor->Blk == b) EndPoints.Add(Cursor->Offset); if (Select && Select->Blk == b) EndPoints.Add(Select->Offset); // Sort the end points if (EndPoints.Length() > 1 && EndPoints[0] > EndPoints[1]) { ssize_t ep = EndPoints[0]; EndPoints[0] = EndPoints[1]; EndPoints[1] = ep; } } // Before selection end point if (CurEndPoint < (ssize_t)EndPoints.Length() && EndPoints[CurEndPoint] == 0) { Type = Type == Selected ? Unselected : Selected; CurEndPoint++; } return Type == Selected; } // Call this after the OnPaint // \return TRUE if the content after the block is selected. bool SelectAfterPaint(class LRichTextPriv::Block *b) { // After image selection end point if (CurEndPoint < (ssize_t)EndPoints.Length() && EndPoints[CurEndPoint] == 1) { Type = Type == Selected ? Unselected : Selected; CurEndPoint++; } return Type == Selected; } }; struct HitTestResult { LPoint In; Block *Blk; DisplayStr *Ds; ssize_t Idx; int LineHint; bool Near; HitTestResult(int x, int y) { In.x = x; In.y = y; Blk = NULL; Ds = NULL; Idx = -1; LineHint = -1; Near = false; } }; ////////////////////////////////////////////////////////////////////////////////////////////// // Undo structures... struct DocChange { virtual ~DocChange() {} virtual bool Apply(LRichTextPriv *Ctx, bool Forward) = 0; }; class Transaction { public: LArray Changes; ~Transaction() { Changes.DeleteObjects(); } void Add(DocChange *Dc) { Changes.Add(Dc); } bool Apply(LRichTextPriv *Ctx, bool Forward) { for (unsigned i=0; iApply(Ctx, Forward)) return false; } return true; } }; LArray UndoQue; ssize_t UndoPos; bool UndoPosLock; bool AddTrans(LAutoPtr &t); bool SetUndoPos(ssize_t Pos); template bool GetBlockByUid(T *&Ptr, int Uid, int *Idx = NULL) { for (unsigned i=0; iGetUid() == Uid) { if (Idx) *Idx = i; return (Ptr = dynamic_cast(b)) != NULL; } } if (Idx) *Idx = -1; return false; } ////////////////////////////////////////////////////////////////////////////////////////////// // A Block is like a DIV in HTML, it's as wide as the page and // always starts and ends on a whole line. class Block : public LEventSinkI, public LEventTargetI { protected: int BlockUid; LRichTextPriv *d; public: /// This is the number of cursors current referencing this Block. int8 Cursors; /// Draw debug selection bool DrawDebug; Block(LRichTextPriv *priv) { d = priv; DrawDebug = false; BlockUid = d->NextUid++; Cursors = 0; } Block(const Block *blk) { d = blk->d; DrawDebug = false; BlockUid = blk->GetUid(); Cursors = 0; } virtual ~Block() { // We must have removed cursors by the time we are deleted // otherwise there will be a hanging pointer in the cursor // object. LAssert(Cursors == 0); } // Events - bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) + bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) { bool r = d->View->PostEvent(M_BLOCK_MSG, (LMessage::Param)(Block*)this, (LMessage::Param)new LMessage(Cmd, a, b)); #if defined(_DEBUG) if (!r) LgiTrace("%s:%i - Warning: PostEvent failed..\n", _FL); #endif return r; } // If this returns non-zero further command processing is aborted. LMessage::Result OnEvent(LMessage *Msg) { return false; } /************************************************ * Get state methods, do not modify the block * ***********************************************/ virtual const char *GetClass() { return "Block"; } virtual LRect GetPos() = 0; virtual ssize_t Length() = 0; virtual bool HitTest(HitTestResult &htr) = 0; virtual bool GetPosFromIndex(BlockCursor *Cursor) = 0; virtual bool OnLayout(Flow &f) = 0; virtual void OnPaint(PaintContext &Ctx) = 0; virtual bool ToHtml(LStream &s, LArray *Media, LRange *Rgn) = 0; virtual bool OffsetToLine(ssize_t Offset, int *ColX, LArray *LineY) = 0; virtual ssize_t LineToOffset(ssize_t Line) = 0; virtual int GetLines() = 0; virtual ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, LFindReplaceCommon *Params) = 0; virtual void SetSpellingErrors(LArray &Errors, LRange r) {} virtual void IncAllStyleRefs() {} virtual void Dump() {} virtual LNamedStyle *GetStyle(ssize_t At = -1) = 0; virtual int GetUid() const { return BlockUid; } virtual bool DoContext(LSubMenu &s, LPoint Doc, ssize_t Offset /* internal to this block, not the whole doc. */, bool TopOfMenu) { return false; } #ifdef _DEBUG virtual void DumpNodes(LTreeItem *Ti) = 0; #endif virtual bool IsValid() { return false; } virtual bool IsBusy(bool Stop = false) { return false; } virtual Block *Clone() = 0; virtual void OnComponentInstall(LString Name) {} // Copy some or all of the text out virtual ssize_t CopyAt(ssize_t Offset, ssize_t Chars, LArray *Text) { return false; } /// This method moves a cursor index. /// \returns the new cursor index or -1 on error. virtual bool Seek ( /// [In] true if the next line is needed, false for the previous line SeekType To, /// [In/Out] The starting cursor. BlockCursor &Cursor ) = 0; /************************************************ * Change state methods, require a transaction * ***********************************************/ // Add some text at a given position virtual bool AddText ( /// Current transaction Transaction *Trans, /// The index to add at (-1 = the end) ssize_t AtOffset, /// The text itself const uint32_t *Str, /// [Optional] The number of characters ssize_t Chars = -1, /// [Optional] Style to give the text, NULL means "use the existing style" LNamedStyle *Style = NULL ) { return false; } /// Delete some chars /// \returns the number of chars actually removed virtual ssize_t DeleteAt ( Transaction *Trans, ssize_t Offset, ssize_t Chars, LArray *DeletedText = NULL ) { return false; } /// Changes the style of a range of characters virtual bool ChangeStyle ( Transaction *Trans, ssize_t Offset, ssize_t Chars, LCss *Style, bool Add ) { return false; } virtual bool DoCase ( /// Current transaction Transaction *Trans, /// Start index of text to change ssize_t StartIdx, /// Number of chars to change ssize_t Chars, /// True if upper case is desired bool Upper ) { return false; } // Split a block virtual Block *Split ( /// Current transaction Transaction *Trans, /// The index to add at (-1 = the end) ssize_t AtOffset ) { return NULL; } // Event called on dictionary load virtual bool OnDictionary(Transaction *Trans) { return false; } }; struct BlockCursor { // The block the cursor is in. Block *Blk; // This is the character offset of the cursor relative to // the start of 'Blk'. ssize_t Offset; // In wrapped text, a given offset can either be at the end // of one line or the start of the next line. This tells the // text block which line the cursor is actually on. int LineHint; // This is the position on the screen in doc coords. LRect Pos; // This is the position line that the cursor is on. This is // used to calculate the bounds for screen updates. LRect Line; // Cursor is currently blinking on bool Blink; BlockCursor(const BlockCursor &c); BlockCursor(Block *b, ssize_t off, int line); ~BlockCursor(); BlockCursor &operator =(const BlockCursor &c); void Set(ssize_t off); void Set(Block *b, ssize_t off, int line); bool operator ==(const BlockCursor &c) { return Blk == c.Blk && Offset == c.Offset; } #ifdef _DEBUG void DumpNodes(LTreeItem *Ti); #endif }; LAutoPtr Cursor, Selection; /// This is part or all of a Text run struct DisplayStr : public LDisplayString { StyleText *Src; ssize_t Chars; // The number of UTF-32 characters. This can be different to // LDisplayString::Length() in the case that LDisplayString // is using UTF-16 (i.e. Windows). int OffsetY; // Offset of this string from the TextLine's box in the Y axis DisplayStr(StyleText *src, LFont *f, const uint32_t *s, ssize_t l = -1, LSurface *pdc = NULL) : LDisplayString(f, #ifndef WINDOWS (char16*) #endif s, l, pdc) { Src = src; OffsetY = 0; #if defined(_MSC_VER) Chars = l < 0 ? Strlen(s) : l; #else Chars = WideWords; #endif // LAssert(l == 0 || FX() > 0); } template T *Utf16Seek(T *s, ssize_t i) { T *e = s + i; while (s < e) { uint16 n = *s & 0xfc00; if (n == 0xd800) { s++; if (s >= e) break; n = *s & 0xfc00; if (n != 0xdc00) { LAssert(!"Unexpected surrogate"); continue; } // else skip over the 2nd surrogate } s++; } return s; } ssize_t WideLen() { #if defined(LGI_DSP_STR_CACHE) return WideWords; #else return StrWords; #endif } // Make a sub-string of this display string virtual LAutoPtr Clone(ssize_t Start, ssize_t Len = -1) { LAutoPtr c; auto WideW = WideLen(); if (WideW > 0 && Len != 0) { const char16 *Str = *this; if (Len < 0) Len = WideW - Start; if (Start >= 0 && Start < (int)WideW && Start + Len <= (int)WideW) { #if defined(_MSC_VER) LAssert(Str != NULL); const char16 *s = Utf16Seek(Str, Start); const char16 *e = Utf16Seek(s, Len); LArray Tmp; if (Utf16to32(Tmp, (const uint16_t*)s, e - s)) c.Reset(new DisplayStr(Src, GetFont(), &Tmp[0], Tmp.Length(), pDC)); #else c.Reset(new DisplayStr(Src, GetFont(), (uint32_t*)Str + Start, Len, pDC)); #endif } } return c; } virtual void Paint(LSurface *pDC, int &FixX, int FixY, LColour &Back) { FDraw(pDC, FixX, FixY); FixX += FX(); } virtual double GetAscent() { return Font->Ascent(); } virtual ssize_t PosToIndex(int x, bool Nearest) { return CharAt(x); } }; struct EmojiDisplayStr : public DisplayStr { LArray SrcRect; LSurface *Img = NULL; LCss::Len Size; int CharPx = 0; #if defined(_MSC_VER) LArray Utf32; #endif EmojiDisplayStr(StyleText *src, LSurface *img, LCss::Len &fntSize, const uint32_t *s, ssize_t l = -1); LAutoPtr Clone(ssize_t Start, ssize_t Len = -1); void Paint(LSurface *pDC, int &FixX, int FixY, LColour &Back); double GetAscent(); ssize_t PosToIndex(int XPos, bool Nearest); }; /// This structure is a layout of a full line of text. Made up of one or more /// display string objects. struct TextLine { /// This is a position relative to the parent Block LRect PosOff; /// The array of display strings LArray Strs; /// Is '1' for lines that have a new line character at the end. uint8_t NewLine; TextLine(int XOffsetPx, int WidthPx, int YOffsetPx); ssize_t Length(); /// This runs after the layout line has been filled with display strings. /// It measures the line and works out the right offsets for each strings /// so that their baselines all match up correctly. void LayoutOffsets(int DefaultFontHt); }; class TextBlock : public Block { LNamedStyle *Style; LArray SpellingErrors; int PaintErrIdx, ClickErrIdx; LSpellCheck::SpellingError *SpErr; LString ClickedUri; bool PreEdit(Transaction *Trans); void DrawDisplayString(LSurface *pDC, DisplayStr *Ds, int &FixX, int FixY, LColour &Bk, ssize_t &Pos); public: // Runs of characters in the same style: pre-layout. LArray Txt; // Runs of characters (display strings) of potentially different styles on the same line: post-layout. LArray Layout; // True if the 'Layout' data is out of date. bool LayoutDirty; // Size of the edges LRect Margin, Border, Padding; // Default font for the block LFont *Fnt; // Chars in the whole block (sum of all Text lengths) ssize_t Len; // Position in document co-ordinates LRect Pos; TextBlock(LRichTextPriv *priv); TextBlock(const TextBlock *Copy); ~TextBlock(); bool IsValid(); // No state change methods const char *GetClass() { return "TextBlock"; } int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, LArray *LineY); ssize_t LineToOffset(ssize_t Line); LRect GetPos() { return Pos; } void Dump(); LNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(LNamedStyle *s); ssize_t Length(); bool ToHtml(LStream &s, LArray *Media, LRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, LArray &t); ssize_t CopyAt(ssize_t Offset, ssize_t Chars, LArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, LFindReplaceCommon *Params); void IncAllStyleRefs(); void SetSpellingErrors(LArray &Errors, LRange r); bool DoContext(LSubMenu &s, LPoint Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(LTreeItem *Ti); #endif Block *Clone(); bool IsEmptyLine(BlockCursor *Cursor); void UpdateSpellingAndLinks(Transaction *Trans, LRange r); // Events LMessage::Result OnEvent(LMessage *Msg); // Transactional changes bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, LNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, LCss *Style, bool Add); ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, LArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); Block *Split(Transaction *Trans, ssize_t AtOffset); bool StripLast(Transaction *Trans, const char *Set = " \t\r\n"); // Strip trailing new line if present.. bool OnDictionary(Transaction *Trans); }; class HorzRuleBlock : public Block { LRect Pos; bool IsDeleted; public: HorzRuleBlock(LRichTextPriv *priv); HorzRuleBlock(const HorzRuleBlock *Copy); ~HorzRuleBlock(); bool IsValid(); // No state change methods const char *GetClass() { return "HorzRuleBlock"; } int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, LArray *LineY); ssize_t LineToOffset(ssize_t Line); LRect GetPos() { return Pos; } void Dump(); LNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(LNamedStyle *s); ssize_t Length(); bool ToHtml(LStream &s, LArray *Media, LRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, LArray &t); ssize_t CopyAt(ssize_t Offset, ssize_t Chars, LArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, LFindReplaceCommon *Params); void IncAllStyleRefs(); bool DoContext(LSubMenu &s, LPoint Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(LTreeItem *Ti); #endif Block *Clone(); // Events LMessage::Result OnEvent(LMessage *Msg); // Transactional changes bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, LNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, LCss *Style, bool Add); ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, LArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); Block *Split(Transaction *Trans, ssize_t AtOffset); }; class ImageBlock : public Block { public: struct ScaleInf { LPoint Sz; LString MimeType; LAutoPtr Compressed; int Percent; ScaleInf() { Sz.x = Sz.y = 0; Percent = 0; } }; int ThreadHnd; protected: LNamedStyle *Style; int Scale; LRect SourceValid; LString FileName; LString ContentId; LString StreamMimeType; LString FileMimeType; LArray Scales; int ResizeIdx; int ThreadBusy; bool IsDeleted; void UpdateThreadBusy(const char *File, int Line, int Off); int GetThreadHandle(); void UpdateDisplay(int y); void UpdateDisplayImg(); public: LAutoPtr SourceImg, DisplayImg, SelectImg; LRect Margin, Border, Padding; LString Source; LPoint Size; bool LayoutDirty; LRect Pos; // position in document co-ordinates LRect ImgPos; ImageBlock(LRichTextPriv *priv); ImageBlock(const ImageBlock *Copy); ~ImageBlock(); bool IsValid(); bool IsBusy(bool Stop = false); bool Load(const char *Src = NULL); bool SetImage(LAutoPtr Img); // No state change methods int GetLines(); bool OffsetToLine(ssize_t Offset, int *ColX, LArray *LineY); ssize_t LineToOffset(ssize_t Line); LRect GetPos() { return Pos; } void Dump(); LNamedStyle *GetStyle(ssize_t At = -1); void SetStyle(LNamedStyle *s); ssize_t Length(); bool ToHtml(LStream &s, LArray *Media, LRange *Rng); bool GetPosFromIndex(BlockCursor *Cursor); bool HitTest(HitTestResult &htr); void OnPaint(PaintContext &Ctx); bool OnLayout(Flow &flow); ssize_t GetTextAt(ssize_t Offset, LArray &t); ssize_t CopyAt(ssize_t Offset, ssize_t Chars, LArray *Text); bool Seek(SeekType To, BlockCursor &Cursor); ssize_t FindAt(ssize_t StartIdx, const uint32_t *Str, LFindReplaceCommon *Params); void IncAllStyleRefs(); bool DoContext(LSubMenu &s, LPoint Doc, ssize_t Offset, bool Spelling); #ifdef _DEBUG void DumpNodes(LTreeItem *Ti); #endif Block *Clone(); void OnComponentInstall(LString Name); // Events LMessage::Result OnEvent(LMessage *Msg); // Transactional changes bool AddText(Transaction *Trans, ssize_t AtOffset, const uint32_t *Str, ssize_t Chars = -1, LNamedStyle *Style = NULL); bool ChangeStyle(Transaction *Trans, ssize_t Offset, ssize_t Chars, LCss *Style, bool Add); ssize_t DeleteAt(Transaction *Trans, ssize_t BlkOffset, ssize_t Chars, LArray *DeletedText = NULL); bool DoCase(Transaction *Trans, ssize_t StartIdx, ssize_t Chars, bool Upper); }; LArray Blocks; Block *Next(Block *b); Block *Prev(Block *b); void InvalidateDoc(LRect *r); void ScrollTo(LRect r); void UpdateStyleUI(); void EmptyDoc(); void Empty(); bool Seek(BlockCursor *In, SeekType Dir, bool Select); bool CursorFirst(); bool SetCursor(LAutoPtr c, bool Select = false); LRect SelectionRect(); bool GetSelection(LArray *Text, LAutoString *Html); ssize_t IndexOfCursor(BlockCursor *c); ssize_t HitTest(int x, int y, int &LineHint, Block **Blk = NULL, ssize_t *BlkOffset = NULL); bool CursorFromPos(int x, int y, LAutoPtr *Cursor, ssize_t *GlobalIdx); Block *GetBlockByIndex(ssize_t Index, ssize_t *Offset = NULL, int *BlockIdx = NULL, int *LineCount = NULL); bool Layout(LScrollBar *&ScrollY); void OnStyleChange(LRichTextEdit::RectType t); bool ChangeSelectionStyle(LCss *Style, bool Add); void PaintBtn(LSurface *pDC, LRichTextEdit::RectType t); bool MakeLink(TextBlock *tb, ssize_t Offset, ssize_t Len, LString Link); bool ClickBtn(LMouse &m, LRichTextEdit::RectType t); bool InsertHorzRule(); void Paint(LSurface *pDC, LScrollBar *&ScrollY); LHtmlElement *CreateElement(LHtmlElement *Parent); LPoint ScreenToDoc(int x, int y); LPoint DocToScreen(int x, int y); bool Merge(Transaction *Trans, Block *a, Block *b); bool DeleteSelection(Transaction *t, char16 **Cut); LRichTextEdit::RectType PosToButton(LMouse &m); void OnComponentInstall(LString Name); struct CreateContext { TextBlock *Tb; ImageBlock *Ib; HorzRuleBlock *Hrb; LArray Buf; uint32_t LastChar; LFontCache *FontCache; LCss::Store StyleStore; bool StartOfLine; CreateContext(LFontCache *fc) { Tb = NULL; Ib = NULL; Hrb = NULL; LastChar = '\n'; FontCache = fc; StartOfLine = true; } bool AddText(LNamedStyle *Style, char16 *Str) { if (!Str || !Tb) return false; int Used = 0; char16 *s = Str; char16 *e = s + StrlenW(s); while (s < e) { if (*s == '\r') { s++; continue; } if (IsWhiteSpace(*s)) { Buf[Used++] = ' '; while (s < e && IsWhiteSpace(*s)) s++; } else { #ifdef WINDOWS ssize_t Len = s[0] && s[1] ? 4 : (s[0] ? 2 : 0); Buf[Used++] = LgiUtf16To32((const uint16 *&)s, Len); #else Buf[Used++] = *s++; #endif while (s < e && !IsWhiteSpace(*s)) { #ifdef WINDOWS Len = s[0] && s[1] ? 4 : (s[0] ? 2 : 0); Buf[Used++] = LgiUtf16To32((const uint16 *&)s, Len); #else Buf[Used++] = *s++; #endif } } } bool Status = false; if (Used > 0) { Status = Tb->AddText(NoTransaction, -1, &Buf[0], Used, Style); LastChar = Buf[Used-1]; } return Status; } }; LAutoPtr CreationCtx; bool ToHtml(LArray *Media = NULL, BlockCursor *From = NULL, BlockCursor *To = NULL); void DumpBlocks(); bool FromHtml(LHtmlElement *e, CreateContext &ctx, LCss *ParentStyle = NULL, int Depth = 0); #ifdef _DEBUG void DumpNodes(LTree *Root); #endif }; struct BlockCursorState { bool Cursor; ssize_t Offset; int LineHint; int BlockUid; BlockCursorState(bool cursor, LRichTextPriv::BlockCursor *c); bool Apply(LRichTextPriv *Ctx, bool Forward); }; struct CompleteTextBlockState : public LRichTextPriv::DocChange { int Uid; LAutoPtr Cur, Sel; LAutoPtr Blk; CompleteTextBlockState(LRichTextPriv *Ctx, LRichTextPriv::TextBlock *Tb); bool Apply(LRichTextPriv *Ctx, bool Forward); }; struct MultiBlockState : public LRichTextPriv::DocChange { LRichTextPriv *Ctx; ssize_t Index; // Number of blocks before the edit ssize_t Length; // Of the other version currently in the Ctx stack LArray Blks; MultiBlockState(LRichTextPriv *ctx, ssize_t Start); bool Apply(LRichTextPriv *Ctx, bool Forward); bool Copy(ssize_t Idx); bool Cut(ssize_t Idx); }; #ifdef _DEBUG LTreeItem *PrintNode(LTreeItem *Parent, const char *Fmt, ...); #endif typedef LRichTextPriv::BlockCursor BlkCursor; typedef LAutoPtr AutoCursor; typedef LAutoPtr AutoTrans; #endif diff --git a/src/common/Widgets/Graph.cpp b/src/common/Widgets/Graph.cpp --- a/src/common/Widgets/Graph.cpp +++ b/src/common/Widgets/Graph.cpp @@ -1,1013 +1,1017 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Graph.h" #include "lgi/common/DocView.h" #include "lgi/common/DisplayString.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include #define SELECTION_SIZE 2 struct GraphAv { - uint64 Sum; - uint64 Count; + uint64 Sum; + uint64 Count; }; struct DataSeriesPriv { LColour colour; LArray values; }; struct LGraphPriv { constexpr static int AxisMarkPx = 8; LGraph *View = NULL; int XAxis = 0, YAxis = 0; LVariantType XType, YType; LVariant MaxX, MinX; LVariant MaxY, MinY; - + LArray Data; LGraph::Style Style; LPoint MouseLoc; bool ShowCursor = false; LString LabelX, LabelY; double Zoom = 1.0, Px = 0.0, Py = 0.0; // Averages bool Average; - LArray Ave; - int BucketSize; - - // Selection - LAutoPtr Select; - LArray Selection; + LArray Ave; + int BucketSize; + + // Selection + LAutoPtr Select; + LArray Selection; LGraphPriv(LGraph *view) : View(view) { - #if 1 - Style = LGraph::PointGraph; - #else - Style = LGraph::LineGraph; - #endif + #if 1 + Style = LGraph::PointGraph; + #else + Style = LGraph::LineGraph; + #endif Empty(); } ~LGraphPriv() { Empty(); } void Empty() { Zoom = 1.0; Px = 0.0; Py = 0.0; LabelX.Empty(); LabelY.Empty(); Average = false; BucketSize = 500; Data.DeleteObjects(); - XType = GV_NULL; - YType = GV_NULL; + XType = GV_NULL; + YType = GV_NULL; MinX.Empty(); MinY.Empty(); MaxX.Empty(); MaxY.Empty(); } LVariantType GuessType(char *s) { bool Dot = false; bool Num = false; bool Alpha = false; bool Delims = false; while (s && *s) { if (IsAlpha(*s)) Alpha = true; else if (IsDigit(*s)) Num = true; else if (strchr("/\\-_:", *s)) Delims = true; else if (*s == '.') Dot = true; s++; } if (Num) { if (Delims) return GV_DATETIME; if (Dot) return GV_DOUBLE; return GV_INT64; } else { return GV_STRING; } } bool Convert(LVariant &v, LVariantType type, char *in) { if (!in) return false; switch (type) { case GV_DOUBLE: v = atof(in); break; case GV_DATETIME: { LDateTime dt; dt.SetFormat(0); dt.Set(in); v = &dt; break; } case GV_INT64: v = (int64_t)atoi64(in); break; case GV_STRING: v = in; break; default: LAssert(!"Not impl."); break; } return true; } int Compare(LVariant &a, LVariant &b) { // a - b if (a.Type != b.Type) { LAssert(!"Only defined for comparing values of the same type."); return 0; } switch (a.Type) { case GV_DOUBLE: { double d = a.Value.Dbl - b.Value.Dbl; if (d < 0) return -1; else if (d > 0) return 1; break; } case GV_DATETIME: { return a.Value.Date->Compare(b.Value.Date); break; } case GV_INT64: { int64 i = a.Value.Int64 - b.Value.Int64; if (i < 0) return -1; else if (i > 0) return 1; break; } case GV_STRING: { return stricmp(a.Str(), b.Str()); break; } default: { LAssert(!"Not impl."); break; } } return 0; } LVariant ViewToData(int coord, int pixels, LVariant &min, LVariant &max) { LVariant r; if (pixels <= 0) return r; switch (min.Type) { case GV_DATETIME: { uint64 Min, Max; min.Value.Date->Get(Min); max.Value.Date->Get(Max); uint64 ts = Min + ( ((uint64)coord * (Max - Min)) / pixels); LDateTime dt; dt.Set(ts); r = &dt; break; } case GV_INT64: { int64 Min, Max; Min = min.CastInt64(); Max = max.CastInt64(); r = Min + (((int64)coord * (Max - Min)) / pixels); break; } case GV_DOUBLE: { double Min, Max; Min = min.CastDouble(); Max = max.CastDouble(); r = Min + (((double)coord * (Max - Min)) / pixels); break; } default: LAssert(0); break; } return r; } int DataToView(LVariant &v, int pixels, LVariant &min, LVariant &max) { if (v.Type != min.Type || v.Type != max.Type) { LAssert(!"Incompatible types."); return 0; } switch (v.Type) { case GV_DATETIME: { uint64 Min, Max, Val; min.Value.Date->Get(Min); max.Value.Date->Get(Max); v.Value.Date->Get(Val); int64 Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } case GV_INT64: { int64 Min, Max, Val; Min = min.CastInt64(); Max = max.CastInt64(); Val = v.CastInt64(); int64 Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } case GV_DOUBLE: { double Min, Max, Val; Min = min.CastDouble(); Max = max.CastDouble(); Val = v.CastDouble(); double Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } default: LAssert(0); break; } return 0; } LString DataToString(LVariant &v) { LString s; switch (v.Type) { case GV_DATETIME: { if (v.Value.Date->Hours() || v.Value.Date->Minutes()) s = v.Value.Date->Get(); else s = v.Value.Date->GetDate(); break; } case GV_INT64: { s.Printf(LPrintfInt64, v.CastInt64()); break; } case GV_INT32: { s.Printf("%" PRIi32, v.CastInt32()); break; } case GV_DOUBLE: { s.Printf("%g", v.CastDouble()); break; } default: { LAssert(!"Impl me."); break; } } return s; } void DrawAxis(LSurface *pDC, LRect &r, int xaxis, LVariant &min, LVariant &max, LString &label) { LVariant v = min; bool First = true; bool Loop = true; if (min.Type == GV_NULL || max.Type == GV_NULL) return; int x = xaxis ? r.x1 : r.x2; int y = xaxis ? r.y1 : r.y2; int pixels = xaxis ? r.X() : r.Y(); int64 int_range = 0; double dbl_inc = 0.0; int64 int64_inc = 0; int date_inc = 1; auto Fnt = View->GetFont(); Fnt->Colour(L_TEXT, L_WORKSPACE); LArray Values; while (Loop) { Values.Add(v); switch (v.Type) { default: { Loop = false; return; break; } case GV_DATETIME: { if (First) { - uint64 s, e; - min.Value.Date->Get(s); - max.Value.Date->Get(e); - int64 period = e - s; - double days = (double)period / LDateTime::DayLength; - if (days > 7) - date_inc = (int) (days / 5); - else - date_inc = 1; + uint64 s, e; + min.Value.Date->Get(s); + max.Value.Date->Get(e); + int64 period = e - s; + double days = (double)period / LDateTime::DayLength; + if (days > 7) + date_inc = (int) (days / 5); + else + date_inc = 1; v.Value.Date->SetTime("0:0:0"); } v.Value.Date->AddDays(date_inc); Loop = *v.Value.Date < *max.Value.Date; break; } case GV_INT64: { if (First) { int64 int64_range = max.CastInt64() - min.CastInt64(); int64 rng = int64_range; int p = 0; while (rng > 10) { p++; rng /= 10; } while (rng < 1) { p--; rng *= 10; } int64_inc = (int64) pow(10.0, p); int64 d = (int64)((v.CastInt64() + int64_inc) / int64_inc); v = d * int64_inc; } else { v = v.CastInt64() + int64_inc; } Loop = v.CastInt64() < max.CastInt64(); break; } case GV_DOUBLE: { if (First) { double dbl_range = max.CastDouble() - min.CastDouble(); double rng = dbl_range; if (std::abs(rng - 0.0) > 0.0001) { int p = 0; while (rng > 10) { p++; rng /= 10; } while (rng < 1) { p--; rng *= 10; } dbl_inc = pow(10.0, p); int d = (int)((v.CastDouble() + dbl_inc) / dbl_inc); v = (double)d * dbl_inc; } else v = 0.0; } else { v = v.CastDouble() + dbl_inc; } Loop = v.CastDouble() < max.CastDouble(); break; } } First = false; } Values.Add(max); for (int i=0; iLine(dx, dy, dx, dy + 5); else pDC->Line(dx, dy, dx - 5, dy); } if (label) { LDisplayString ds(Fnt, label); ds.Draw(pDC, r.Center().x, r.y2-ds.Y()); } } LColour GenerateColour() { LColour c; c.SetHLS((uint16_t) (Data.Length() * 360 / 8), 255, 128); c.ToRGB(); return c; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LGraph::DataSeries::DataSeries(LGraphPriv *graphPriv, const char *name) { d = new DataSeriesPriv; priv = graphPriv; Name(name); } LGraph::DataSeries::~DataSeries() { } LColour LGraph::DataSeries::GetColour() { return d->colour; } void LGraph::DataSeries::SetColour(LColour c) { d->colour = c; } bool LGraph::DataSeries::AddPair(char *x, char *y, void *UserData) { - if (!x || !y) - return false; + if (!x || !y) + return false; - if (priv->XType == GV_NULL) - priv->XType = priv->GuessType(x); - if (priv->YType == GV_NULL) - priv->YType = priv->GuessType(y); + if (priv->XType == GV_NULL) + priv->XType = priv->GuessType(x); + if (priv->YType == GV_NULL) + priv->YType = priv->GuessType(y); - Pair &p = d->values.New(); - p.UserData = UserData; - - if (priv->Convert(p.x, priv->XType, x)) - { - if (priv->MaxX.IsNull() || priv->Compare(p.x, priv->MaxX) > 0) - priv->MaxX = p.x; - if (priv->MinX.IsNull() || priv->Compare(p.x, priv->MinX) < 0) - priv->MinX = p.x; - } - else - { + Pair &p = d->values.New(); + p.UserData = UserData; + + if (priv->Convert(p.x, priv->XType, x)) + { + if (priv->MaxX.IsNull() || priv->Compare(p.x, priv->MaxX) > 0) + priv->MaxX = p.x; + if (priv->MinX.IsNull() || priv->Compare(p.x, priv->MinX) < 0) + priv->MinX = p.x; + } + else + { d->values.PopLast(); return false; - } + } - if (priv->Convert(p.y, priv->YType, y)) - { - if (priv->MaxY.IsNull() || priv->Compare(p.y, priv->MaxY) > 0) - priv->MaxY = p.y; - if (priv->MinY.IsNull() || priv->Compare(p.y, priv->MinY) < 0) - priv->MinY = p.y; - } - else - { - d->values.PopLast(); - return false; - } - - return true; + if (priv->Convert(p.y, priv->YType, y)) + { + if (priv->MaxY.IsNull() || priv->Compare(p.y, priv->MaxY) > 0) + priv->MaxY = p.y; + if (priv->MinY.IsNull() || priv->Compare(p.y, priv->MinY) < 0) + priv->MinY = p.y; + } + else + { + d->values.PopLast(); + return false; + } + + return true; } bool LGraph::DataSeries::SetDataSource(LDbRecordset *Rs, int XAxis, int YAxis) { if (!Rs) return false; priv->XType = GV_NULL; priv->YType = GV_NULL; - if (XAxis >= 0) - priv->XAxis = XAxis; - if (YAxis >= 0) - priv->YAxis = YAxis; + if (XAxis >= 0) + priv->XAxis = XAxis; + if (YAxis >= 0) + priv->YAxis = YAxis; if (Rs->Fields() >= 2) { - int Idx = 0; + int Idx = 0; for (bool b = Rs->MoveFirst(); b; b = Rs->MoveNext(), Idx++) { - if (priv->XAxis < 0 || priv->YAxis < 0) - { - for (int i=0; iFields(); i++) - { - char *s = (*Rs)[i]; - LVariantType t = priv->GuessType(s); - if (t != GV_NULL && t != GV_STRING) - { - if (priv->XAxis < 0) - { - priv->XAxis = i; - priv->XType = t; - } - else if (priv->YAxis < 0) - { - priv->YAxis = i; - priv->YType = t; - } - else break; - } - } - } + if (priv->XAxis < 0 || priv->YAxis < 0) + { + for (int i=0; iFields(); i++) + { + char *s = (*Rs)[i]; + LVariantType t = priv->GuessType(s); + if (t != GV_NULL && t != GV_STRING) + { + if (priv->XAxis < 0) + { + priv->XAxis = i; + priv->XType = t; + } + else if (priv->YAxis < 0) + { + priv->YAxis = i; + priv->YType = t; + } + else break; + } + } + } if (priv->XAxis >= 0 && priv->YAxis >= 0) - AddPair((*Rs)[priv->XAxis], (*Rs)[priv->YAxis]); + AddPair((*Rs)[priv->XAxis], (*Rs)[priv->YAxis]); } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LGraph::LGraph(int Id, int XAxis, int YAxis) { d = new LGraphPriv(this); - d->XAxis = XAxis; - d->YAxis = YAxis; + d->XAxis = XAxis; + d->YAxis = YAxis; SetPourLargest(true); } LGraph::~LGraph() { DeleteObj(d); } void LGraph::Empty() { d->Empty(); Invalidate(); } LGraph::DataSeries *LGraph::GetData(const char *Name, bool Create) { for (auto s: d->Data) if (!Stricmp(s->Name(), Name)) return s; if (!Create) return NULL; auto s = new DataSeries(d, Name); if (!s) return NULL; s->SetColour(d->GenerateColour()); d->Data.Add(s); return s; } LGraph::DataSeries *LGraph::GetDataAt(size_t index) { return d->Data.IdxCheck(index) ? d->Data[index] : NULL; } size_t LGraph::GetDataLength() { return d->Data.Length(); } void LGraph::SetStyle(Style s) { - d->Style = s; - Invalidate(); + d->Style = s; + Invalidate(); } LGraph::Style LGraph::GetStyle() { - return d->Style; + return d->Style; } enum Msg { - IDM_LINE = 100, - IDM_POINT, - IDM_AVERAGE, - IDM_AVERAGE_SAVE, + IDM_LINE = 100, + IDM_POINT, + IDM_AVERAGE, + IDM_AVERAGE_SAVE, IDM_SHOW_CURSOR, }; void LGraph::OnMouseClick(LMouse &m) { - if (m.IsContextMenu()) - { - LSubMenu s; - m.ToScreen(); + if (m.IsContextMenu()) + { + LSubMenu s; + m.ToScreen(); auto CursorItem = s.AppendItem("Show Cursor", IDM_SHOW_CURSOR); if (CursorItem) CursorItem->Checked(d->ShowCursor); - auto style = s.AppendSub("Style"); - style->AppendItem("Line", IDM_LINE); - style->AppendItem("Point", IDM_POINT); - - auto a = s.AppendSub("Average"); - auto i = a->AppendItem("Show", IDM_AVERAGE); - i->Checked(d->Average); - a->AppendItem("Save", IDM_AVERAGE_SAVE); + auto style = s.AppendSub("Style"); + style->AppendItem("Line", IDM_LINE); + style->AppendItem("Point", IDM_POINT); + + auto a = s.AppendSub("Average"); + auto i = a->AppendItem("Show", IDM_AVERAGE); + i->Checked(d->Average); + a->AppendItem("Save", IDM_AVERAGE_SAVE); - switch (s.Float(this, m.x, m.y)) - { + switch (s.Float(this, m.x, m.y)) + { case IDM_SHOW_CURSOR: ShowCursor(!d->ShowCursor); break; - case IDM_LINE: - SetStyle(LineGraph); - break; - case IDM_POINT: - SetStyle(PointGraph); - break; - case IDM_AVERAGE: - d->Average = !d->Average; - Invalidate(); - break; - case IDM_AVERAGE_SAVE: - { - if (!d->Ave.Length()) - { - LgiMsg(this, "No average calculated.", "LGraph"); - break; - } + case IDM_LINE: + SetStyle(LineGraph); + break; + case IDM_POINT: + SetStyle(PointGraph); + break; + case IDM_AVERAGE: + d->Average = !d->Average; + Invalidate(); + break; + case IDM_AVERAGE_SAVE: + { + if (!d->Ave.Length()) + { + LgiMsg(this, "No average calculated.", "LGraph"); + break; + } - LFileSelect s; - s.Parent(this); - s.Name("average.csv"); - char Desktop[MAX_PATH_LEN]; - LGetSystemPath(LSP_DESKTOP, Desktop, sizeof(Desktop)); - s.InitialDir(Desktop); - if (!s.Save()) - break; - - LFile o; - if (!o.Open(s.Name(), O_WRITE)) - { - LgiMsg(this, "Failed to open file for writing.", "LGraph"); - break; - } + auto s = new LFileSelect(this); + s->Name("average.csv"); + char Desktop[MAX_PATH_LEN]; + LGetSystemPath(LSP_DESKTOP, Desktop, sizeof(Desktop)); + s->InitialDir(Desktop); + s->Save([&](auto dlg, auto status) + { + if (status) + { + LFile o; + if (!o.Open(dlg->Name(), O_WRITE)) + { + LgiMsg(this, "Failed to open file for writing.", "LGraph"); + return; + } - o.SetSize(0); - switch (d->MinX.Type) - { - case GV_INT64: - { - auto XRange = d->MaxX.CastInt64() - d->MinX.CastInt64() + 1; - for (int b=0; bBucketSize; b++) - { - GraphAv &g = d->Ave[b]; - int64 x = d->MinX.CastInt64() + (((b * d->BucketSize) + (d->BucketSize >> 1)) / XRange); - int64 y = (g.Count) ? g.Sum / g.Count : 0; - o.Print(LPrintfInt64 "," LPrintfInt64 "\n", x, y); - } - break; - } - case GV_DOUBLE: - { - double XRange = d->MaxX.CastDouble() - d->MinX.CastDouble(); - for (int b=0; bBucketSize; b++) - { - GraphAv &g = d->Ave[b]; - double x = d->MinX.CastDouble() + ( ((double)b+0.5) * XRange / d->BucketSize); - int64 y = (g.Count) ? g.Sum / g.Count : 0; - o.Print("%f," LPrintfInt64 "\n", x, y); - } - break; - } - default: - LAssert(0); - break; - } - break; - } - } - } - else if (m.Left() && m.Down()) - { - d->Select.Reset(new LPoint); - d->Select->x = m.x; - d->Select->y = m.y; - Invalidate(); - } + o.SetSize(0); + switch (d->MinX.Type) + { + case GV_INT64: + { + auto XRange = d->MaxX.CastInt64() - d->MinX.CastInt64() + 1; + for (int b=0; bBucketSize; b++) + { + GraphAv &g = d->Ave[b]; + int64 x = d->MinX.CastInt64() + (((b * d->BucketSize) + (d->BucketSize >> 1)) / XRange); + int64 y = (g.Count) ? g.Sum / g.Count : 0; + o.Print(LPrintfInt64 "," LPrintfInt64 "\n", x, y); + } + break; + } + case GV_DOUBLE: + { + double XRange = d->MaxX.CastDouble() - d->MinX.CastDouble(); + for (int b=0; bBucketSize; b++) + { + GraphAv &g = d->Ave[b]; + double x = d->MinX.CastDouble() + ( ((double)b+0.5) * XRange / d->BucketSize); + int64 y = (g.Count) ? g.Sum / g.Count : 0; + o.Print("%f," LPrintfInt64 "\n", x, y); + } + break; + } + default: + LAssert(0); + break; + } + } + + delete dlg; + }); + break; + } + } + } + else if (m.Left() && m.Down()) + { + d->Select.Reset(new LPoint); + d->Select->x = m.x; + d->Select->y = m.y; + Invalidate(); + } } LArray *LGraph::GetSelection() { - return &d->Selection; + return &d->Selection; } bool LGraph::ShowCursor() { return d->ShowCursor; } void LGraph::ShowCursor(bool show) { if (show ^ d->ShowCursor) { d->ShowCursor = show; Invalidate(); } } const char *LGraph::GetLabel(bool XAxis) { if (XAxis) return d->LabelX; else return d->LabelY; } void LGraph::SetLabel(bool XAxis, const char *Label) { if (XAxis) d->LabelX = Label; else d->LabelY = Label; Invalidate(); } LGraph::Range LGraph::GetRange(bool XAxis) { Range r; if (XAxis) { r.Min = d->MinX; r.Max = d->MaxX; } else { r.Min = d->MinY; r.Max = d->MaxY; } return r; } void LGraph::SetRange(bool XAxis, Range r) { if (XAxis) { d->MinX = r.Min; d->MaxX = r.Max; } else { d->MinY = r.Min; d->MaxY = r.Max; } Invalidate(); } void LGraph::OnMouseMove(LMouse &m) { d->MouseLoc = m; if (d->ShowCursor) Invalidate(); } bool LGraph::OnMouseWheel(double Lines) { LMouse m; GetMouse(m); if (m.Ctrl()) d->Zoom -= Lines / 30; else if (m.Shift()) d->Px -= Lines / (50 * d->Zoom); else d->Py -= Lines / (50 * d->Zoom); Invalidate(); return true; } void LGraph::OnPaint(LSurface *pDC) { LAutoPtr DoubleBuf; if (d->ShowCursor) DoubleBuf.Reset(new LDoubleBuffer(pDC)); pDC->Colour(L_WORKSPACE); pDC->Rectangle(); LColour cBorder(222, 222, 222); LRect c = GetClient(); LRect data = c; data.Inset(20, 20); data.x2 -= 40; data.SetSize((int)(d->Zoom * data.X()), (int)(d->Zoom * data.Y())); data.Offset((int)(d->Px * data.X()), (int)(d->Py * data.Y())); LRect y = data; y.x2 = y.x1 + 60; data.x1 = y.x2 + 1; LRect x = data; x.y1 = x.y2 - 60; y.y2 = data.y2 = x.y1 - 1; pDC->Colour(cBorder); pDC->Box(&data); // Draw axis d->DrawAxis(pDC, x, true, d->MinX, d->MaxX, d->LabelX); d->DrawAxis(pDC, y, false, d->MinY, d->MaxY, d->LabelY); if (d->ShowCursor) { // Draw in cursor... if (data.Overlap(d->MouseLoc)) { // X axis cursor info auto xCur = d->ViewToData(d->MouseLoc.x - data.x1, data.X(), d->MinX, d->MaxX); pDC->VLine(d->MouseLoc.x, data.y1, data.y2 + d->AxisMarkPx); LDisplayString dsX(GetFont(), d->DataToString(xCur)); dsX.Draw(pDC, d->MouseLoc.x - (dsX.X() >> 1), data.y2 + d->AxisMarkPx + dsX.Y()); // Y axis auto yCur = d->ViewToData(data.y2 - d->MouseLoc.y, data.Y(), d->MinY, d->MaxY); pDC->HLine(data.x1 - d->AxisMarkPx, data.x2, d->MouseLoc.y); LDisplayString dsY(GetFont(), d->DataToString(yCur)); dsY.Draw(pDC, data.x1 - d->AxisMarkPx - dsY.X(), d->MouseLoc.y - (dsY.Y() >> 1)); } } // Draw data int cx, cy, px, py; pDC->Colour(LColour(0, 0, 222)); - if (d->Average && !d->Ave.Length()) - { + if (d->Average && !d->Ave.Length()) + { for (auto data: d->Data) { auto &values = data->d->values; for (int i=0; iDataToView(p.x, d->BucketSize, d->MinX, d->MaxX); d->Ave[Bucket].Sum += p.y.CastInt64(); d->Ave[Bucket].Count++; } } - } + } switch (d->Style) { - case LineGraph: - { + case LineGraph: + { for (auto data: d->Data) { auto &values = data->d->values; pDC->Colour(data->GetColour()); for (int i=0; iDataToView(p.x, x.X(), d->MinX, d->MaxX); cy = y.y2 - (int)d->DataToView(p.y, y.Y(), d->MinY, d->MaxY); if (i) pDC->Line(cx, cy, px, py); px = cx; py = cy; } } - break; - } - case PointGraph: - { + break; + } + case PointGraph: + { for (auto data: d->Data) { auto &values = data->d->values; pDC->Colour(data->GetColour()); for (int i=0; iDataToView(p.x, x.X(), d->MinX, d->MaxX); int ymap = (int)d->DataToView(p.y, y.Y(), d->MinY, d->MaxY); // LgiTrace("%s -> %i (%s, %s)\n", p.x.Value.Date->Get().Get(), xmap, d->MinX.Value.Date->Get().Get(), d->MaxX.Value.Date->Get().Get()); cx = x.x1 + xmap; cy = y.y2 - ymap; pDC->Set(cx, cy); - + if (d->Select && abs(d->Select->x - cx) < SELECTION_SIZE && abs(d->Select->y - cy) < SELECTION_SIZE) { d->Selection.Add(&p); } } - + if (d->Average) { int px = -1, py = -1; pDC->Colour(LColour(255, 0, 0)); for (int b=0; bBucketSize; b++) { if (d->Ave[b].Count) { int cx = x.x1 + (((b * x.X()) + (x.X() >> 1)) / d->BucketSize); LVariant v = d->Ave[b].Sum / d->Ave[b].Count; int cy = y.y2 - (int)d->DataToView(v, y.Y(), d->MinY, d->MaxY); - + if (py >= 0) { pDC->Line(cx, cy, px, py); } - + px = cx; py = cy; } } } } if (d->Select) { d->Select.Reset(); SendNotify(); } - break; - } + break; + } } } diff --git a/src/common/Widgets/ItemContainer.cpp b/src/common/Widgets/ItemContainer.cpp --- a/src/common/Widgets/ItemContainer.cpp +++ b/src/common/Widgets/ItemContainer.cpp @@ -1,1312 +1,1314 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ItemContainer.h" #include "lgi/common/DisplayString.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Edit.h" #include "lgi/common/CssTools.h" // Colours #if defined(__GTK_H__) #define DOUBLE_BUFFER_COLUMN_DRAWING 1 #else #define DOUBLE_BUFFER_COLUMN_DRAWING 0 #endif #if defined(WIN32) #if !defined(WS_EX_LAYERED) #define WS_EX_LAYERED 0x80000 #endif #if !defined(LWA_ALPHA) #define LWA_ALPHA 2 #endif typedef BOOL (__stdcall *_SetLayeredWindowAttributes)(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); #endif class LItemColumnPrivate { public: LRect Pos; bool Down; bool Drag; LItemContainer *Parent; char *cName; LDisplayString *Txt; int cWidth; int cType; LSurface *cIcon; int cImage; int cMark; bool OwnIcon; bool CanResize; LItemColumnPrivate(LItemContainer *parent) { Parent = parent; Txt = 0; cName = 0; cWidth = 0; cIcon = 0; cType = GIC_ASK_TEXT; cImage = -1; cMark = GLI_MARK_NONE; Down = false; OwnIcon = false; CanResize = true; Drag = false; } ~LItemColumnPrivate() { DeleteArray(cName); if (OwnIcon) { DeleteObj(cIcon); } DeleteObj(Txt); } }; static LColour cActiveCol(0x86, 0xba, 0xe9); static void FillStops(LArray &Stops, LRect &r, bool Active) { if (Active) { Stops[0].Set(0.0f, LColour(0xd0, 0xe2, 0xf5)); Stops[1].Set(2.0f / (r.Y() - 2), LColour(0x98, 0xc1, 0xe9)); Stops[2].Set(0.5f, LColour(0x86, 0xba, 0xe9)); Stops[3].Set(0.51f, LColour(0x68, 0xaf, 0xea)); Stops[4].Set(1.0f, LColour(0xbb, 0xfc, 0xff)); } else { LColour cMed(L_MED), cWs(L_WORKSPACE); if (cWs.GetGray() < 96) { cMed = cMed.Mix(LColour::White, 0.25f); cWs = cWs.Mix(LColour::White, 0.25f); } Stops[0].Set(0.0f, cWs); Stops[1].Set(0.5f, cMed.Mix(cWs)); Stops[2].Set(0.51f, cMed); Stops[3].Set(1.0f, cWs); } } ////////////////////////////////////////////////////////////////////////////////////// LItemContainer::LItemContainer() { Flags = 0; DragMode = 0; DragCol = NULL; ColClick = -1; ColumnHeaders = true; ColumnHeader.ZOff(-1, -1); Columns.SetFixedLength(true); ItemEdit = NULL; } LItemContainer::~LItemContainer() { DeleteObj(ItemEdit); DeleteObj(DragCol); Columns.DeleteObjects(); } void LItemContainer::PaintColumnHeadings(LSurface *pDC) { // Draw column headings if (!ColumnHeaders || !ColumnHeader.Valid()) return; LSurface *ColDC = pDC; LRect cr; #if DOUBLE_BUFFER_COLUMN_DRAWING LMemDC Bmp; if (!pDC->SupportsAlphaCompositing() && Bmp.Create(ColumnHeader.X(), ColumnHeader.Y(), System32BitColourSpace)) { ColDC = &Bmp; Bmp.Op(GDC_ALPHA); cr = ColumnHeader.ZeroTranslate(); } else #endif { cr = ColumnHeader; pDC->ClipRgn(&cr); } // Draw columns int cx = cr.x1; if (IconCol) { cr.x1 = cx; cr.x2 = cr.x1 + IconCol->Width() - 1; IconCol->SetPos(cr); IconCol->OnPaint(ColDC, cr); cx += IconCol->Width(); } // Draw other columns for (int i=0; iWidth() - 1; c->SetPos(cr); c->OnPaint(ColDC, cr); cx += c->Width(); } else LAssert(0); } // Draw ending piece cr.x1 = cx; cr.x2 = ColumnHeader.x2 + 2; if (cr.Valid()) { // Draw end section where there are no columns #ifdef MAC LArray Stops; LRect j(cr.x1, cr.y1, cr.x2-1, cr.y2-1); FillStops(Stops, j, false); LFillGradient(ColDC, j, true, Stops); ColDC->Colour(L_LOW); ColDC->Line(cr.x1, cr.y2, cr.x2, cr.y2); #else if (LApp::SkinEngine) { LSkinState State; State.pScreen = ColDC; State.Rect = cr; State.Enabled = Enabled(); State.View = this; LApp::SkinEngine->OnPaint_ListColumn(0, 0, &State); } else { LWideBorder(ColDC, cr, DefaultRaisedEdge); ColDC->Colour(LColour(L_MED)); ColDC->Rectangle(&cr); } #endif } #if DOUBLE_BUFFER_COLUMN_DRAWING if (!pDC->SupportsAlphaCompositing()) pDC->Blt(ColumnHeader.x1, ColumnHeader.y1, &Bmp); else #endif pDC->ClipRgn(0); } LItemColumn *LItemContainer::AddColumn(const char *Name, int Width, int Where) { LItemColumn *c = 0; if (Lock(_FL)) { c = new LItemColumn(this, Name, Width); if (c) { Columns.SetFixedLength(false); Columns.AddAt(Where, c); Columns.SetFixedLength(true); UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return c; } bool LItemContainer::AddColumn(LItemColumn *Col, int Where) { bool Status = false; if (Col && Lock(_FL)) { Columns.SetFixedLength(false); Status = Columns.AddAt(Where, Col); Columns.SetFixedLength(true); if (Status) { UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return Status; } void LItemContainer::DragColumn(int Index) { DeleteObj(DragCol); if (Index >= 0) { DragCol = new LDragColumn(this, Index); if (DragCol) { Capture(true); DragMode = DRAG_COLUMN; } } } int LItemContainer::ColumnAtX(int x, LItemColumn **Col, int *Offset) { LItemColumn *Column = 0; if (!Col) Col = &Column; int Cx = GetImageList() ? 16 : 0; int c; for (c=0; c= Cx && x < Cx + (*Col)->Width()) { if (Offset) *Offset = Cx; return c; } Cx += (*Col)->Width(); } return -1; } void LItemContainer::EmptyColumns() { Columns.DeleteObjects(); Invalidate(&ColumnHeader); SendNotify(LNotifyItemColumnsChanged); } int LItemContainer::HitColumn(int x, int y, LItemColumn *&Resize, LItemColumn *&Over) { int Index = -1; Resize = 0; Over = 0; if (ColumnHeaders && ColumnHeader.Overlap(x, y)) { // Clicked on a column heading int cx = ColumnHeader.x1 + ((IconCol) ? IconCol->Width() : 0); for (int n = 0; n < Columns.Length(); n++) { LItemColumn *c = Columns[n]; cx += c->Width(); if (abs(x-cx) < 5) { if (c->Resizable()) { Resize = c; Index = n; break; } } else if (c->d->Pos.Overlap(x, y)) { Over = c; Index = n; break; } } } return Index; } void LItemContainer::OnColumnClick(int Col, LMouse &m) { ColClick = Col; ColMouse = m; LNotification n(LNotifyItemColumnClicked); n.Int[0] = Col; SendNotify(n); } bool LItemContainer::GetColumnClickInfo(int &Col, LMouse &m) { if (ColClick >= 0) { Col = ColClick; m = ColMouse; return true; } return false; } void LItemContainer::GetColumnSizes(ColSizes &cs) { // Read in the current sizes cs.FixedPx = 0; cs.ResizePx = 0; for (int i=0; iResizable()) { ColInfo &Inf = cs.Info.New(); Inf.Col = c; Inf.Idx = i; Inf.ContentPx = c->GetContentSize(); Inf.WidthPx = c->Width(); cs.ResizePx += Inf.ContentPx; } else { cs.FixedPx += c->Width(); } } } LMessage::Result LItemContainer::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESIZE_TO_CONTENT: { ResizeColumnsToContent((int)Msg->A()); break; } default: break; } return LLayout::OnEvent(Msg); } void LItemContainer::ResizeColumnsToContent(int Border) { if (!InThread()) { PostEvent(M_RESIZE_TO_CONTENT, Border); return; } if (Lock(_FL)) { // Read in the current sizes ColSizes Sizes; GetColumnSizes(Sizes); // Allocate space int AvailablePx = GetClient().X() - 5; if (VScroll) AvailablePx -= VScroll->X(); int ExpandPx = AvailablePx - Sizes.FixedPx; Sizes.Info.Sort([](auto a, auto b) { int AGrowPx = a->GrowPx(); int BGrowPx = b->GrowPx(); return AGrowPx - BGrowPx; }); for (int i=0; iResizable()) { if (ExpandPx > Sizes.ResizePx) { // Everything fits... Inf.Col->Width(Inf.ContentPx + Border); } else { int Cx = GetClient().X(); double Ratio = Cx ? (double)Inf.ContentPx / Cx : 1.0; if (Ratio < 0.25) { Inf.Col->Width(Inf.ContentPx + Border); } else { // Need to scale to fit... int Px = Inf.ContentPx * ExpandPx / Sizes.ResizePx; Inf.Col->Width(Px + Border); } } ClearDs(Inf.Idx); } } Unlock(); } Invalidate(); } ////////////////////////////////////////////////////////////////////////////// LDragColumn::LDragColumn(LItemContainer *list, int col) { List = list; Index = col; Offset = 0; #ifdef LINUX Back = 0; #endif Col = List->ColumnAt(Index); if (Col) { Col->d->Down = false; Col->d->Drag = true; LRect r = Col->d->Pos; r.y1 = 0; r.y2 = List->Y()-1; List->Invalidate(&r, true); #if WINNATIVE LArray Ver; bool Layered = ( LGetOs(&Ver) == LGI_OS_WIN32 || LGetOs(&Ver) == LGI_OS_WIN64 ) && Ver[0] >= 5; SetStyle(WS_POPUP); SetExStyle(GetExStyle() | WS_EX_TOOLWINDOW); if (Layered) { SetExStyle(GetExStyle() | WS_EX_LAYERED | WS_EX_TRANSPARENT); } #endif Attach(0); #if WINNATIVE if (Layered) { SetWindowLong(Handle(), GWL_EXSTYLE, GetWindowLong(Handle(), GWL_EXSTYLE) | WS_EX_LAYERED); LLibrary User32("User32"); _SetLayeredWindowAttributes SetLayeredWindowAttributes = (_SetLayeredWindowAttributes)User32.GetAddress("SetLayeredWindowAttributes"); if (SetLayeredWindowAttributes) { if (!SetLayeredWindowAttributes(Handle(), 0, DRAG_COL_ALPHA, LWA_ALPHA)) { DWORD Err = GetLastError(); } } } #elif defined(__GTK_H__) Gtk::GtkWindow *w = WindowHandle(); if (w) { gtk_window_set_decorated(w, FALSE); gtk_widget_set_opacity(GtkCast(w, gtk_widget, GtkWidget), DRAG_COL_ALPHA / 255.0); } #endif LMouse m; List->GetMouse(m); Offset = m.x - r.x1; List->PointToScreen(ListScrPos); r.Offset(ListScrPos.x, ListScrPos.y); SetPos(r); Visible(true); } } LDragColumn::~LDragColumn() { Visible(false); if (Col) { Col->d->Drag = false; } List->Invalidate(); } #if LINUX_TRANS_COL void LDragColumn::OnPosChange() { Invalidate(); } #endif void LDragColumn::OnPaint(LSurface *pScreen) { #if LINUX_TRANS_COL LSurface *Buf = new LMemDC(X(), Y(), GdcD->GetBits()); LSurface *pDC = new LMemDC(X(), Y(), GdcD->GetBits()); #else LSurface *pDC = pScreen; #endif pDC->SetOrigin(Col->d->Pos.x1, 0); if (Col) Col->d->Drag = false; List->OnPaint(pDC); if (Col) Col->d->Drag = true; pDC->SetOrigin(0, 0); #if LINUX_TRANS_COL if (Buf && pDC) { LRect p = GetPos(); // Fill the buffer with the background Buf->Blt(ListScrPos.x - p.x1, 0, Back); // Draw painted column over the back with alpha Buf->Op(GDC_ALPHA); LApplicator *App = Buf->Applicator(); if (App) { App->SetVar(GAPP_ALPHA_A, DRAG_COL_ALPHA); } Buf->Blt(0, 0, pDC); // Put result on the screen pScreen->Blt(0, 0, Buf); } DeleteObj(Buf); DeleteObj(pDC); #endif } /////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////// // List column LItemColumn::LItemColumn(LItemContainer *parent, const char *name, int width) : ResObject(Res_Column) { d = new LItemColumnPrivate(parent); d->cWidth = width; if (name) Name(name); } LItemColumn::~LItemColumn() { if (d->Drag) { d->Parent->DragColumn(-1); } DeleteObj(d); } LItemContainer *LItemColumn::GetList() { return d->Parent; } void LItemColumn::Image(int i) { d->cImage = i; } int LItemColumn::Image() { return d->cImage; } bool LItemColumn::Resizable() { return d->CanResize; } void LItemColumn::Resizable(bool i) { d->CanResize = i; } bool LItemColumn::InDrag() { return d->Drag; } LRect LItemColumn::GetPos() { return d->Pos; } void LItemColumn::SetPos(LRect &r) { d->Pos = r; } void LItemColumn::Name(const char *n) { DeleteArray(d->cName); DeleteObj(d->Txt); d->cName = NewStr(n); LFont *f = d->Parent && d->Parent->GetFont() && d->Parent->GetFont()->Handle() ? d->Parent->GetFont() : LSysFont; d->Txt = new LDisplayString(f, (char*)n); if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } char *LItemColumn::Name() { return d->cName; } int LItemColumn::GetIndex() { if (d->Parent) { return (int)d->Parent->Columns.IndexOf(this); } return -1; } int LItemColumn::GetContentSize() { return d->Parent->GetContentSize(GetIndex()); } void LItemColumn::Width(int i) { if (d->cWidth != i) { d->cWidth = i; // If we are attached to a list... if (d->Parent) { /* FIXME int MyIndex = GetIndex(); // Clear all the cached strings for this column for (List::I it=d->Parent->Items.Start(); it.In(); it++) { DeleteObj((*it)->d->Display[MyIndex]); } if (d->Parent->IsAttached()) { // Update the screen from this column across LRect Up = d->Parent->GetClient(); Up.x1 = d->Pos.x1; d->Parent->Invalidate(&Up); } */ } // Notify listener auto p = d->Parent; if (p) p->SendNotify(LNotifyItemColumnsResized); } } int LItemColumn::Width() { return d->cWidth; } void LItemColumn::Mark(int i) { d->cMark = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Mark() { return d->cMark; } void LItemColumn::Type(int i) { d->cType = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Type() { return d->cType; } void LItemColumn::Icon(LSurface *i, bool Own) { if (d->OwnIcon) { DeleteObj(d->cIcon); } d->cIcon = i; d->OwnIcon = Own; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } LSurface *LItemColumn::Icon() { return d->cIcon; } bool LItemColumn::Value() { return d->Down; } void LItemColumn::Value(bool i) { d->Down = i; } void LItemColumn::OnPaint_Content(LSurface *pDC, LRect &r, bool FillBackground) { - if (!d->Drag) + if (d->Drag) + return; + + LCssTools Tools(d->Parent); + auto Fore = Tools.GetFore(); + auto cMed = LColour(L_MED); + int Off = d->Down ? 1 : 0; + int Mx = r.x1 + 8, My = r.y1 + ((r.Y() - 8) / 2); + if (d->cIcon) { - LCssTools Tools(d->Parent); - auto Fore = Tools.GetFore(); - auto cMed = LColour(L_MED); - int Off = d->Down ? 1 : 0; - int Mx = r.x1 + 8, My = r.y1 + ((r.Y() - 8) / 2); - if (d->cIcon) + if (FillBackground) { - if (FillBackground) - { - pDC->Colour(cMed); - pDC->Rectangle(&r); - } + pDC->Colour(cMed); + pDC->Rectangle(&r); + } - int x = (r.X()-d->cIcon->X()) / 2; - - pDC->Blt( r.x1 + x + Off, - r.y1 + ((r.Y()-d->cIcon->Y())/2) + Off, - d->cIcon); + int x = (r.X()-d->cIcon->X()) / 2; + + pDC->Blt( r.x1 + x + Off, + r.y1 + ((r.Y()-d->cIcon->Y())/2) + Off, + d->cIcon); - if (d->cMark) - { - Mx += x + d->cIcon->X() + 4; - } + if (d->cMark) + { + Mx += x + d->cIcon->X() + 4; } - else if (d->cImage >= 0 && d->Parent) + } + else if (d->cImage >= 0 && d->Parent) + { + LColour Background = cMed; + if (FillBackground) { - LColour Background = cMed; - if (FillBackground) - { - pDC->Colour(Background); - pDC->Rectangle(&r); - } - - if (d->Parent->GetImageList()) + pDC->Colour(Background); + pDC->Rectangle(&r); + } + + if (d->Parent->GetImageList()) + { + LRect *b = d->Parent->GetImageList()->GetBounds(); + int x = r.x1; + int y = r.y1; + if (b) { - LRect *b = d->Parent->GetImageList()->GetBounds(); - int x = r.x1; - int y = r.y1; - if (b) - { - b += d->cImage; - x = r.x1 + ((r.X()-b->X()) / 2) - b->x1; - y = r.y1 + ((r.Y()-b->Y()) / 2) - b->y1; - } - - d->Parent->GetImageList()->Draw(pDC, - x + Off, - y + Off, - d->cImage, - Background); - } - - if (d->cMark) - { - Mx += d->Parent->GetImageList()->TileX() + 4; - } - } - else if (ValidStr(d->cName) && d->Txt) - { - LFont *f = d->Txt->GetFont(); - if (!f) - { - LAssert(0); - return; - } - - LColour cText = Fore; - #ifdef MAC - // Contrast check - if (d->cMark && (cText - cActiveCol) < 64) - cText = cText.Invert(); - #endif - - f->Transparent(!FillBackground); - f->Colour(cText, cMed); - int ty = d->Txt->Y(); - int ry = r.Y(); - int y = r.y1 + ((ry - ty) >> 1); - d->Txt->Draw(pDC, r.x1 + Off + 3, y + Off, &r); - - if (d->cMark) - { - Mx += d->Txt->X(); - } - } - else - { - if (FillBackground) - { - pDC->Colour(cMed); - pDC->Rectangle(&r); - } + b += d->cImage; + x = r.x1 + ((r.X()-b->X()) / 2) - b->x1; + y = r.y1 + ((r.Y()-b->Y()) / 2) - b->y1; + } + + d->Parent->GetImageList()->Draw(pDC, + x + Off, + y + Off, + d->cImage, + Background); } - #define ARROW_SIZE 9 - pDC->Colour(Fore); - Mx += Off; - My += Off - 1; + if (d->cMark) + { + Mx += d->Parent->GetImageList()->TileX() + 4; + } + } + else if (ValidStr(d->cName) && d->Txt) + { + LFont *f = d->Txt->GetFont(); + if (!f) + { + LAssert(0); + return; + } - switch (d->cMark) + LColour cText = Fore; + #ifdef MAC + // Contrast check + if (d->cMark && (cText - cActiveCol) < 64) + cText = cText.Invert(); + #endif + + f->Transparent(!FillBackground); + f->Colour(cText, cMed); + int ty = d->Txt->Y(); + int ry = r.Y(); + int y = r.y1 + ((ry - ty) >> 1); + + // d->Txt->_debug = true; + d->Txt->Draw(pDC, r.x1 + Off + 3, y + Off, &r); + + if (d->cMark) { - case GLI_MARK_UP_ARROW: - { - pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); - pDC->Line(Mx, My + 2, Mx + 2, My); - pDC->Line(Mx + 2, My, Mx + 4, My + 2); - break; - } - case GLI_MARK_DOWN_ARROW: - { - pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); - pDC->Line( Mx, - My + ARROW_SIZE - 3, - Mx + 2, - My + ARROW_SIZE - 1); - pDC->Line( Mx + 2, - My + ARROW_SIZE - 1, - Mx + 4, - My + ARROW_SIZE - 3); - break; - } + Mx += d->Txt->X(); + } + } + else + { + if (FillBackground) + { + pDC->Colour(cMed); + pDC->Rectangle(&r); + } + } + + #define ARROW_SIZE 9 + pDC->Colour(Fore); + Mx += Off; + My += Off - 1; + + switch (d->cMark) + { + case GLI_MARK_UP_ARROW: + { + pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); + pDC->Line(Mx, My + 2, Mx + 2, My); + pDC->Line(Mx + 2, My, Mx + 4, My + 2); + break; + } + case GLI_MARK_DOWN_ARROW: + { + pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); + pDC->Line( Mx, + My + ARROW_SIZE - 3, + Mx + 2, + My + ARROW_SIZE - 1); + pDC->Line( Mx + 2, + My + ARROW_SIZE - 1, + Mx + 4, + My + ARROW_SIZE - 3); + break; } } } void ColumnPaint(void *UserData, LSurface *pDC, LRect &r, bool FillBackground) { ((LItemColumn*)UserData)->OnPaint_Content(pDC, r, FillBackground); } void LItemColumn::OnPaint(LSurface *pDC, LRect &Rgn) { LRect r = Rgn; if (d->Drag) { pDC->Colour(DragColumnColour); pDC->Rectangle(&r); } else { #ifdef MAC LArray Stops; LRect j(r.x1, r.y1, r.x2-1, r.y2-1); FillStops(Stops, j, d->cMark != 0); LFillGradient(pDC, j, true, Stops); if (d->cMark) pDC->Colour(Rgb24(0x66, 0x93, 0xc0), 24); else pDC->Colour(Rgb24(178, 178, 178), 24); pDC->Line(r.x1, r.y2, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x2, r.y2); LRect n = r; n.Inset(2, 2); OnPaint_Content(pDC, n, false); #else if (LApp::SkinEngine) { LSkinState State; - State.pScreen = pDC; - State.ptrText = &d->Txt; - State.Rect = Rgn; - State.Value = Value(); - State.Enabled = GetList()->Enabled(); - State.View = d->Parent; + State.pScreen = pDC; + State.ptrText = &d->Txt; + State.Rect = Rgn; + State.Value = Value(); + State.Enabled = GetList()->Enabled(); + State.View = d->Parent; LApp::SkinEngine->OnPaint_ListColumn(ColumnPaint, this, &State); } else { if (d->Down) { LThinBorder(pDC, r, DefaultSunkenEdge); LFlatBorder(pDC, r, 1); } else { LWideBorder(pDC, r, DefaultRaisedEdge); } OnPaint_Content(pDC, r, true); } #endif } } /////////////////////////////////////////////////////////////////////////////////////////// LItem::LItem() { SelectionStart = SelectionEnd = -1; } LItem::~LItem() { } LView *LItem::EditLabel(int Col) { LItemContainer *c = GetContainer(); if (!c) return NULL; c->Capture(false); if (!c->ItemEdit) { c->ItemEdit = new LItemEdit(c, this, Col, SelectionStart, SelectionEnd); SelectionStart = SelectionEnd = -1; } return c->ItemEdit; } void LItem::OnEditLabelEnd() { LItemContainer *c = GetContainer(); if (c) c->ItemEdit = NULL; } void LItem::SetEditLabelSelection(int SelStart, int SelEnd) { SelectionStart = SelStart; SelectionEnd = SelEnd; } //////////////////////////////////////////////////////////////////////////////////////////// #define M_END_POPUP (M_USER+0x1500) #define M_LOSING_FOCUS (M_USER+0x1501) class LItemEditBox : public LEdit { LItemEdit *ItemEdit; public: LItemEditBox(LItemEdit *i, int x, int y, const char *s) : LEdit(100, 1, 1, x-3, y-3, s) { ItemEdit = i; Sunken(false); MultiLine(false); #ifndef LINUX SetPos(GetPos()); #endif } const char *GetClass() { return "LItemEditBox"; } void OnCreate() { LEdit::OnCreate(); Focus(true); } void OnFocus(bool f) { if (!f && GetParent()) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEditBox posting M_LOSING_FOCUS\n", _FL); #endif GetParent()->PostEvent(M_LOSING_FOCUS); } LEdit::OnFocus(f); } bool OnKey(LKey &k) { /* This should be handled by LEdit::OnKey now. Which will send a LNotifyEscapeKey or LNotifyReturnKey up to the ItemEdit OnNotify handler. switch (k.vkey) { case LK_RETURN: case LK_ESCAPE: { if (k.Down()) ItemEdit->OnNotify(this, k.c16); return true; } } */ return LEdit::OnKey(k); } bool SetScrollBars(bool x, bool y) { return false; } }; ////////////////////////////////////////////////////////////////////////////////////////// class LItemEditPrivate { public: LItem *Item; LEdit *Edit; int Index; bool Esc; LItemEditPrivate() { Esc = false; Item = 0; Index = 0; } }; LItemEdit::LItemEdit(LView *parent, LItem *item, int index, int SelStart, int SelEnd) : LPopup(parent) { d = new LItemEditPrivate; d->Item = item; d->Index = index; _BorderSize = 0; Sunken(false); Raised(false); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit(%p/%s, %i, %i, %i)\n", _FL, parent, parent?parent->GetClass():0, index, SelStart, SelEnd); #endif LPoint p; SetParent(parent); GetParent()->PointToScreen(p); LRect r = d->Item->GetPos(d->Index); int MinY = 6 + LSysFont->GetHeight(); if (r.Y() < MinY) r.y2 = r.y1 + MinY - 1; r.Offset(p.x, p.y); SetPos(r); if (Attach(parent)) { d->Edit = new LItemEditBox(this, r.X(), r.Y(), d->Item->GetText(d->Index)); if (d->Edit) { d->Edit->Attach(this); d->Edit->Focus(true); if (SelStart >= 0) { d->Edit->Select(SelStart, SelEnd-SelStart+1); } } Visible(true); } } LItemEdit::~LItemEdit() { if (d->Item) { if (d->Edit && !d->Esc) { auto Str = d->Edit->Name(); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - ~LItemEdit, updating item(%i) with '%s'\n", _FL, d->Index, Str); #endif LItemContainer *c = d->Item->GetContainer(); if (d->Item->SetText(Str, d->Index)) { d->Item->Update(); } else { // Item is deleting itself... // Make sure there is no dangling ptr on the container.. if (c) c->ItemEdit = NULL; // And we don't touch the no longer existant item.. d->Item = NULL; } } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Edit=%p Esc=%i\n", _FL, d->Edit, d->Esc); #endif if (d->Item) d->Item->OnEditLabelEnd(); } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Error: No item?\n", _FL); #endif DeleteObj(d); } LItem *LItemEdit::GetItem() { return d->Item; } void LItemEdit::OnPaint(LSurface *pDC) { pDC->Colour(L_BLACK); pDC->Rectangle(); } int LItemEdit::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case 100: { if (n.Type == LNotifyEscapeKey) { d->Esc = true; #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got escape\n", _FL); #endif } if (n.Type == LNotifyEscapeKey || n.Type == LNotifyReturnKey) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit hiding on esc/enter\n", _FL); #endif d->Edit->KeyProcessed(); Visible(false); } break; } } return 0; } void LItemEdit::Visible(bool i) { LPopup::Visible(i); if (!i) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit posting M_END_POPUP\n", _FL); #endif PostEvent(M_END_POPUP); } } bool LItemEdit::OnKey(LKey &k) { if (d->Edit) return d->Edit->OnKey(k); return false; } void LItemEdit::OnFocus(bool f) { if (f && d->Edit) d->Edit->Focus(true); } LMessage::Result LItemEdit::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOSING_FOCUS: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit get M_LOSING_FOCUS\n", _FL); #endif // One of us has to retain focus... don't care which control. if (Focus() || d->Edit->Focus()) break; // else fall thru to end the popup #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit falling thru to M_END_POPUP\n", _FL); #endif } case M_END_POPUP: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got M_END_POPUP, quiting\n", _FL); #endif if (d->Item && d->Item->GetContainer()) { d->Item->GetContainer()->Focus(true); } Quit(); return 0; } } return LPopup::OnEvent(Msg); } 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,2731 +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 = false; bool Visible = true; int ListItem_Image = -1; List Cols; LArray Str; LArray Display; int16 LayoutColumn = -1; LListItemPrivate() { } ~LListItemPrivate() { Cols.DeleteObjects(); EmptyStrings(); EmptyDisplay(); } void EmptyStrings() { Str.DeleteArrays(); } void EmptyDisplay() { Display.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////////////////////////////// LListItemColumn::LListItemColumn(LListItem *item, int col) { _Column = col; _Item = item; _Value = 0; _Item->d->Cols.Insert(this); } LList *LListItemColumn::GetList() { return _Item ? _Item->Parent : 0; } LItemContainer *LListItemColumn::GetContainer() { return GetList(); } LListT *LListItemColumn::GetAllItems() { return GetList() ? &GetList()->Items : 0; } void LListItemColumn::Value(int64 i) { if (i != _Value) { _Value = i; _Item->OnColumnNotify(_Column, _Value); } } LListItemColumn *LListItemColumn::GetItemCol(LListItem *i, int Col) { if (i) { for (auto c: i->d->Cols) { if (c->_Column == Col) { return c; } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////// // List item LListItem::LListItem() { 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 || !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++; } } } -/* -int ListStringCompare(LListItem *a, LListItem *b, NativeInt data) -{ - char *ATxt = (a)->GetText(data); - char *BTxt = (b)->GetText(data); - if (ATxt && BTxt) - return stricmp(ATxt, BTxt); - return 0; -} - -void LList::Sort(LListCompareFunc Compare, NativeInt Data) -{ - if (Lock(_FL)) - { - LListItem *Kb = Items[Keyboard]; - Items.Sort(Compare ? Compare : ListStringCompare, Data); - Keyboard = Kb ? Items.IndexOf(Kb) : -1; - Invalidate(&ItemsPos); - - Unlock(); - } -} -*/ - 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; } } } pDC->Colour(Back); for (LRect *w=Rgn.First(); w; w=Rgn.Next()) { pDC->Rectangle(w); } Unlock(); #if LList_ONPAINT_PROFILE int64 End = LCurrentTime(); printf("LList::OnPaint() pour=%i headers=%i items=%i\n", (int) (t1-Start), (int) (t2-t1), (int) (End-t2)); #endif } void LList::OnFocus(bool b) { LListItem *s = GetSelected(); if (Items.Length()) { if (!s) { s = Items[0]; if (s) s->Select(true); } for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { auto i = *It; if (i->Pos.Valid() && i->d->Selected) { Invalidate(&i->Pos); } } } LLayout::OnFocus(b); if (!b && IsCapturing()) { Capture(false); } } void LList::UpdateAllItems() { if (Lock(_FL)) { bool needsRepour = false; ForAllItems(i) { auto css = i->GetCss(); bool vis = !css || css->Display() != LCss::DispNone; if (i->d->Visible != vis) needsRepour = true; i->d->EmptyDisplay(); } Unlock(); if (needsRepour) PourAll(); Invalidate(); } } int LList::GetContentSize(int Index) { int Max = 0; for (auto It = Items.begin(); It.In(); It++) { LListItem *i = *It; LDisplayString *s = i->d->Display[Index]; LDisplayString *Mem = 0; // If no cached string, create it for the list item if (!s || s->IsTruncated()) { LFont *f = i->GetFont(); if (!f) f = GetFont(); if (!f) f = LSysFont; const char *Text = i->d->Str[Index] ? i->d->Str[Index] : i->GetText(Index); if (s && s->IsTruncated()) { s = Mem = new LDisplayString(f, Text?Text:(char*)""); } else { s = i->d->Display[Index] = new LDisplayString(f, Text?Text:(char*)""); } } // Measure it if (s) { Max = MAX(Max, s->X()); } DeleteObj(Mem); } // Measure the heading too LItemColumn *Col = Columns[Index]; LFont *f = GetFont(); LAssert(f != 0); if (f) { LDisplayString h(f, Col->Name()); int Hx = h.X() + (Col->Mark() ? 10 : 0); Max = MAX(Max, Hx); } return Max; } diff --git a/src/common/Widgets/Popup.cpp b/src/common/Widgets/Popup.cpp --- a/src/common/Widgets/Popup.cpp +++ b/src/common/Widgets/Popup.cpp @@ -1,1195 +1,1217 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Popup.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Thread.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/Menu.h" #if LGI_COCOA #include #endif ///////////////////////////////////////////////////////////////////////////////////// #ifndef WH_MOUSE_LL #define WH_MOUSE_LL 14 #endif #if !defined(MAKELONG) #define MAKELONG(low, high) ( ((low) & 0xffff) | ((high) << 16) ) #endif #define MOUSE_POLL_MS 100 #if defined(__GTK_H__) using namespace Gtk; class LMouseHookPrivate *HookPrivate = 0; #include "LgiWidget.h" bool IsWindow(OsView Wnd) { // Do any available validation of the Wnd we can here #ifdef XWIN // return XWidget::Find(Wnd) != 0; #else return true; #endif } OsView WindowFromPoint(int x, int y, int DebugDepth = 0) { return NULL; } bool GetWindowRect(OsView Wnd, LRect &rc) { return false; } bool ScreenToClient(OsView Wnd, LPoint &p) { return false; } #elif !defined(WINNATIVE) bool IsWindow(OsView v) { return true; } #endif uint32_t LgiGetViewPid(OsView View) { #if WINNATIVE DWORD hWndProcess = 0; GetWindowThreadProcessId(View, &hWndProcess); return hWndProcess; #endif // FIXME: Linux return LProcessId(); } class LMouseHookPrivate : public ::LMutex, public ::LThread { public: bool Loop; OsView hMouseOver; List Popups; #ifdef MAC - OsView ViewHandle; - LThreadEvent Event; + OsView ViewHandle; + LThreadEvent Event; #endif LMouseHookPrivate() : LMutex("MouseHookLock"), LThread("MouseHook") { Loop = false; hMouseOver = NULL; #ifdef MAC ViewHandle = NULL; #endif #if defined(LINUX) // LgiTrace("Mouse hook thread not running! (FIXME)\n"); #else Loop = true; Run(); #endif } ~LMouseHookPrivate() { if (Loop) { Loop = false; #ifdef MAC Event.Signal(); #endif while (!IsExited()) { LSleep(10); } } } void PostEvent(OsView h, int c, LMessage::Param a, LMessage::Param b) { LPostEvent(h, c, a, b); } int Main() { LMouse Old; LView v; while (Loop) { #if defined(MAC) && !defined(LGI_SDL) // Wait for the down click... LThreadEvent::WaitStatus s = Event.Wait(); if (!Loop || s != LThreadEvent::WaitSignaled) { break; } // Now loop for events... LMouse Cur, Prev; Prev.Down(true); do { #if LGI_COCOA NSPoint p = [NSEvent mouseLocation]; Cur.x = (int)p.x; Cur.y = (int)p.y; #elif LGI_CARBON HIPoint p; HIGetMousePosition(kHICoordSpaceScreenPixel, NULL, &p); Cur.x = (int)p.x; Cur.y = (int)p.y; Cur.SetModifer(GetCurrentKeyModifiers()); Cur.SetButton(GetCurrentEventButtonState()); Cur.Down(Cur.Left() || Cur.Right() || Cur.Middle()); // Cur.Trace("MouseHook"); if (!Cur.Down() && Prev.Down()) { // Up click... if (ViewHandle) LPostEvent(ViewHandle, M_MOUSE_TRACK_UP, 0, 0); else printf("%s:%i - No mouse hook view for up click.\n", _FL); } #endif Prev = Cur; LSleep(30); } while (Loop && Cur.Down()); #else LMouse m; v.GetMouse(m, true); if (LockWithTimeout(100, _FL)) { if (m.Down() ^ Old.Down()) { // m.Trace("Hook"); LSubMenu::SysMouseClick(m); } if (m.Down() && !Old.Down()) { // Down click.... uint64 Now = LCurrentTime(); LPopup *Over = 0; for (auto w: Popups) { if (w->GetPos().Overlap(m.x, m.y)) { Over = w; break; } } for (auto w: Popups) { #if 0 LgiTrace("PopupLoop: Over=%p w=%p, w->Vis=%i, Time=%i\n", Over, w, w->Visible(), (int) (Now - w->Start)); #endif if (w != Over && w->Visible() && w->Start < Now - 100) { bool Close = true; #if WINNATIVE // This is a bit of a hack to prevent LPopup's with open context menus from // closing when the user clicks on the context menu. // // FIXME: Linux // Scan the window under the mouse up the parent tree POINT p = { m.x, m.y }; bool HasPopupInParents = false; for (HWND hnd = WindowFromPoint(p); hnd; hnd = ::GetParent(hnd)) { // Is this a popup? ULONG Style = GetWindowLong(hnd, GWL_STYLE); if (TestFlag(Style, WS_POPUP)) { // No it's a normal window, so close the popup HasPopupInParents = true; break; } } Close = !HasPopupInParents; /* // Are we over a popup? RECT rc; GetWindowRect(hnd, &rc); LRect gr = rc; if (gr.Overlap(m.x, m.y)) { // Yes, so don't close it LgiTrace("Popup: Got a click on a LPopup\n"); Close = false; break; } */ #elif defined LINUX /* for (LViewI *v = Over; v; ) { SubMenuImpl *Sub = dynamic_cast(v); if (Sub) { if (v == w) { Close = false; break; } LMenuItem *it = Sub->GetSub()->GetParent(); LSubMenu *mn = it ? it->GetParent() : 0; MenuClickImpl *impl = mn ? mn->Handle() : 0; v = impl ? impl->View() : 0; } else v = 0; } */ #endif if (Close) w->PostEvent(M_SET_VISIBLE, (LMessage::Param)false); } } } Unlock(); } if (m.x != Old.x || m.y != Old.y) { // Mouse moved... OsView hOver = 0; #if WINNATIVE POINT WinPt = { m.x, m.y }; hOver = WindowFromPoint(WinPt); RECT WinRect; GetClientRect(hOver, &WinRect); ScreenToClient(hOver, &WinPt); LRect rc = WinRect; LPoint p(WinPt.x, WinPt.y); #elif defined __GTK_H__ hOver = WindowFromPoint(m.x, m.y); LRect rc; LPoint p(m.x, m.y); if (hOver) { if (!GetWindowRect(hOver, rc)) { LgiTrace("No Rect for over\n"); } if (!ScreenToClient(hOver, p)) { LgiTrace("No conversion for point.\n"); } } else { // LgiTrace("No hOver\n"); } #else // Not implemented. LPoint p; LRect rc; #endif // is the mouse inside the client area? bool Inside = ! (p.x < 0 || p.y < 0 || p.x >= rc.X() || p.y >= rc.Y()); OsView hWnd = (Inside) ? hOver : 0; uint32_t hProcess = LProcessId(); uint32_t hWndProcess = 0; if (hWnd != hMouseOver) { // Window has changed if (hMouseOver && IsWindow(hMouseOver)) { // Window is LOSING mouse // Using 'post' because send can cause a deadlock. hWndProcess = LgiGetViewPid(hMouseOver); if (hWndProcess == hProcess) { PostEvent(hMouseOver, M_MOUSEEXIT, 0, (LMessage::Param)MAKELONG((short) p.x, (short) p.y)); } } // Set current window hMouseOver = hWnd; if (hMouseOver && IsWindow(hMouseOver)) { // Window is GETTING mouse // Using 'post' because send can cause a deadlock. hWndProcess = LgiGetViewPid(hMouseOver); if (hWndProcess == hProcess) { PostEvent(hMouseOver, M_MOUSEENTER, (LMessage::Param)Inside, (LMessage::Param)MAKELONG((short) p.x, (short) p.y)); } } } else { hWndProcess = LgiGetViewPid(hMouseOver); } #if WINNATIVE if (hWndProcess == hProcess) { // This code makes sure that non-LGI windows generate mouse move events // for the mouse hook system... // First find the parent LGI window HWND hWnd = hMouseOver; LViewI *v = 0; while (hWnd && !(v = LWindowFromHandle(hMouseOver))) { HWND w = ::GetParent(hWnd); if (w) hWnd = w; else break; } // Get the window... auto *w = v ? v->GetWindow() : 0; // Post the event to the window PostEvent(w ? w->Handle() : hWnd, M_HANDLEMOUSEMOVE, MAKELONG((short) p.x, (short) p.y), (LPARAM)hMouseOver); } #endif } Old = m; LSleep(MOUSE_POLL_MS); #endif } return 0; } }; LMouseHook::LMouseHook() { d = new LMouseHookPrivate; } LMouseHook::~LMouseHook() { d->Lock(_FL); DeleteObj(d); } void LMouseHook::TrackClick(LView *v) { #ifdef MAC if (v) { #if LGI_VIEW_HANDLE d->ViewHandle = v->Handle(); if (d->ViewHandle) #endif { d->Event.Signal(); } #if LGI_VIEW_HANDLE else printf("%s:%i - No view handle.\n", _FL); #endif } else printf("%s:%i - No view ptr.\n", _FL); #endif } bool LMouseHook::OnViewKey(LView *v, LKey &k) { bool Status = false; if (d->Lock(_FL)) { auto It = d->Popups.rbegin(); if (It != d->Popups.end()) { LView *l = *It; if (l->Visible() && l->OnKey(k)) { Status = true; } } d->Unlock(); } return Status; } void LMouseHook::RegisterPopup(LPopup *p) { if (d->Lock(_FL)) { if (!d->Popups.HasItem(p)) { d->Popups.Insert(p); } d->Unlock(); } } void LMouseHook::UnregisterPopup(LPopup *p) { if (d->Lock(_FL)) { d->Popups.Delete(p); d->Unlock(); } } #if defined(WIN32) LRESULT CALLBACK LMouseHook::MouseProc(int Code, WPARAM a, LPARAM b) { return 0; } #elif defined(LGI_CARBON) WindowRef CreateBorderlessWindow() { Rect r = {0,0,100,100}; WindowRef wr; OSStatus e = CreateNewWindow ( kDocumentWindowClass, (WindowAttributes) ( kWindowStandardHandlerAttribute | kWindowCompositingAttribute | kWindowNoShadowAttribute | kWindowNoTitleBarAttribute ), &r, &wr ); if (e) { LgiTrace("%s:%i - Error: Creating popup window: %i\n", _FL, e); return NULL; } return wr; } #endif ///////////////////////////////////////////////////////////////////////////////////// class LPopupPrivate { public: bool TakeFocus; bool GotOnCreate; LPopupPrivate() { TakeFocus = true; GotOnCreate = false; } }; ::LArray LPopup::CurrentPopups; LPopup::LPopup(LView *owner) #if LGI_CARBON - : LWindow(CreateBorderlessWindow()) + : LWindow(CreateBorderlessWindow()) #elif defined(__GTK_H__) - : LWindow(gtk_window_new(GTK_WINDOW_POPUP)) + : LWindow(gtk_window_new(GTK_WINDOW_POPUP)) #endif { d = new LPopupPrivate; Start = 0; Cancelled = false; CurrentPopups.Add(this); #if LGI_COCOA - Panel = [[NSPanel alloc] init]; - if (Panel) - { - Panel.p.floatingPanel = TRUE; - Panel.p.worksWhenModal = TRUE; - Panel.p.styleMask = NSWindowStyleMaskBorderless; - - Panel.p.contentView = [[LCocoaView alloc] init:this]; - } + Panel = [[NSPanel alloc] init]; + if (Panel) + { + Panel.p.floatingPanel = TRUE; + Panel.p.worksWhenModal = TRUE; + Panel.p.styleMask = NSWindowStyleMaskBorderless; + + Panel.p.contentView = [[LCocoaView alloc] init:this]; + } + #elif defined(HAIKU) + auto w = WindowHandle(); + if (w) + { + auto r = w->SetLook(B_NO_BORDER_WINDOW_LOOK); + if (r != B_OK) + printf("%s:%i - SetLook failed.\n", _FL); + + r = w->SetFeel(B_FLOATING_SUBSET_WINDOW_FEEL); + if (r != B_OK) + printf("%s:%i - SetFeel failed.\n", _FL); + + printf("popup thread=%i\n", w->Thread()); + } + else printf("%s:%i - No WindowHandle()?\n", _FL); #endif if ((Owner = owner)) { #ifndef WIN32 - Owner->PopupChild() = this; + Owner->PopupChild() = this; #endif #if !defined(MAC) && !defined(__GTK_H__) - _Window = Owner->GetWindow(); + _Window = Owner->GetWindow(); #endif SetNotify(Owner); } + Name("Popup"); LView::Visible(false); } LPopup::~LPopup() { CurrentPopups.Delete(this); SendNotify(LNotifyPopupDelete); if (Owner) { #ifndef WIN32 Owner->PopupChild() = 0; #endif #ifdef MAC LDropDown *dd = dynamic_cast(Owner); if (dd) dd->Popup = NULL; #endif } LMouseHook *Hook = LAppInst->GetMouseHook(); if (Hook) Hook->UnregisterPopup(this); while (Children.Length()) { auto It = Children.begin(); auto c = *It; if (!c->GetParent()) Children.Delete(It); delete c; } #if LGI_COCOA if (Panel) { Visible(false); LCocoaView *cv = objc_dynamic_cast(LCocoaView, Panel.p.contentView); if (cv) { // printf("release LCocoaView %p\n", cv); cv.w = NULL; Panel.p.contentView = NULL; [cv release]; cv = NULL; } // printf("release NSPanel %p\n", Panel.p); [Panel.p release]; Panel.p = NULL; // printf("~LPopup %p\n", this); } #endif DeleteObj(d); } #if LGI_COCOA LRect &LPopup::GetPos() { return Pos; } bool LPopup::SetPos(LRect &r, bool repaint) { Pos = r; OnPosChange(); if (Panel) { LRect flipped = LScreenFlip(r); [Panel.p setFrame:flipped display:Visible()]; } return true; } #endif LMessage::Result LPopup::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #if WINNATIVE case WM_DESTROY: { // LgiTrace("Popup Destroyed.\n"); break; } #endif case M_SET_VISIBLE: { Visible(Msg->A() != 0); break; } } return LView::OnEvent(Msg); } void LPopup::TakeFocus(bool Take) { d->TakeFocus = Take; } bool LPopup::Attach(LViewI *p) { - #if defined(LGI_CARBON) + #if defined(LGI_CARBON) || defined(HAIKU) - return LWindow::Attach(NULL); + auto status = LWindow::Attach(NULL); + printf("Popup thread=%i\n", WindowHandle()->Thread()); + return status; #else - if (p) SetParent(p); - else p = GetParent(); + if (p) SetParent(p); + else p = GetParent(); - #if WINNATIVE + #if WINNATIVE - SetStyle(WS_POPUP); - LView::Attach(p); - AttachChildren(); + SetStyle(WS_POPUP); + LView::Attach(p); + AttachChildren(); - #elif defined __GTK_H__ + #elif defined __GTK_H__ - if (p) - { - auto w = p->GetWindow(); - if (w) - { - auto h = w->WindowHandle(); - if (h) - gtk_window_set_transient_for(WindowHandle(), h); - } - } - gtk_window_set_decorated(WindowHandle(), FALSE); - return LWindow::Attach(p); + if (p) + { + auto w = p->GetWindow(); + if (w) + { + auto h = w->WindowHandle(); + if (h) + gtk_window_set_transient_for(WindowHandle(), h); + } + } + gtk_window_set_decorated(WindowHandle(), FALSE); + return LWindow::Attach(p); - #endif + #endif - GetWindow(); + GetWindow(); - #if !LGI_VIEW_HANDLE - return true; - #else - return Handle() != 0; - #endif + #if !LGI_VIEW_HANDLE + return true; + #else + return Handle() != 0; + #endif #endif } void LPopup::Visible(bool i) { if (i) { // When this popup becomes visible, hide any other visible popups... for (auto p: CurrentPopups) { if (p != this && p->Visible()) p->Visible(false); } } else { #if !LGI_POPUP_LWINDOW // When this popup hides and we have the keyboard focus... move the focus // up to the first parent view. If LGI_POPUP_LWINDOW is true, this won't // work because each LWindow has it's own independant focus. Right? auto Wnd = GetWindow(); if (Wnd) { bool HaveFocus = false; for (auto Foc = Wnd->GetFocus(); Foc; Foc = Foc->GetParent()) { // printf("%p::HidePopup %p (%s)\n", (LViewI*)this, Foc, Foc->GetClass()); if (Foc == (LViewI*)this) { HaveFocus = true; break; } } // printf("%s:%i - HaveFocus=%i\n", _FL, HaveFocus); if (HaveFocus) { auto Par = GetParent(); // printf("%s:%i - Par=%s\n", _FL, Par?Par->GetClass():"NULL"); if (Par) Par->Focus(true); } } #endif } #if defined __GTK_H__ auto Wnd = WindowHandle(); if (i && !IsAttached()) { if (!Attach(0)) { printf("%s:%i - Attach failed.\n", _FL); return; } } LView::Visible(i); if (Wnd) { if (i) { gtk_widget_show_all(GTK_WIDGET(Wnd)); gtk_window_move(Wnd, Pos.x1, Pos.y1); gtk_window_resize(Wnd, MAX(1, Pos.X()), Pos.Y()); // printf("%s:%i - Showing Wnd %s.\n", _FL, Pos.GetStr()); } else { gtk_widget_hide(GTK_WIDGET(Wnd)); // printf("%s:%i - Hiding Wnd.\n", _FL); } } else printf("%s:%i - No Wnd.\n", _FL); #else #ifdef WINNATIVE bool HadFocus = false; #endif #ifdef LGI_SDL auto *TopWnd = LAppInst->AppWnd; if (i && TopWnd) { if (!TopWnd->HasView(this)) TopWnd->AddView(this); } #else if ( #if LGI_VIEW_HANDLE !Handle() && #endif i) { #if WINNATIVE SetStyle(WS_POPUP); #endif Attach(NULL); } #endif if (!_Window && Owner) { _Window = Owner->GetWindow(); } AttachChildren(); #ifdef WINNATIVE // See if we or a child window has the focus... for (HWND hWnd = GetFocus(); hWnd; hWnd = ::GetParent(hWnd)) { if (hWnd == Handle()) { HadFocus = true; break; } } if (d->TakeFocus || !i) LView::Visible(i); else ShowWindow(Handle(), SW_SHOWNA); #elif LGI_CARBON SetAlwaysOnTop(true); LWindow::Visible(i); #elif LGI_COCOA if (Panel) { if (i) { [Panel.p makeKeyAndOrderFront:NULL]; if (!d->GotOnCreate) { d->GotOnCreate = true; OnCreate(); } } else [Panel.p orderOut:Panel.p]; } LView::Visible(i); + #elif LGI_POPUP_LWINDOW + + LWindow::Visible(i); + #else LView::Visible(i); #endif #endif #if 1 if (i) { Start = LCurrentTime(); LMouseHook *Hook = LAppInst->GetMouseHook(); if (Hook) Hook->RegisterPopup(this); if (!_Window) { if (Owner) { _Window = Owner->GetWindow(); } else if (GetParent()) { _Window = GetParent()->GetWindow(); } } } else { LMouseHook *Hook = LAppInst->GetMouseHook(); if (Hook) Hook->UnregisterPopup(this); SendNotify(LNotifyPopupHide); /* #if WINNATIVE // This is required to re-focus the owner. // If the popup or a child window gets focus at some point. The // owner doesn't get focus when we close... weird I know. if (Owner && HadFocus) { LgiTrace("%s Setting owner focus %s\n", GetClass(), Owner->GetClass()); Owner->Focus(true); } #endif */ } #endif #ifdef LGI_SDL if (TopWnd) TopWnd->Invalidate(); #endif } bool LPopup::Visible() { #if defined __GTK_H__ if (Wnd) { LView::Visible( #if GtkVer(2, 18) gtk_widget_get_visible(GTK_WIDGET(WindowHandle())) #else (GTK_OBJECT_FLAGS (Wnd) & GTK_VISIBLE) != 0 #endif ); } #endif #if LGI_POPUP_LWINDOW bool v = LWindow::Visible(); #else bool v = LView::Visible(); #endif return v; } ///////////////////////////////////////////////////////////////////////////////////// LDropDown::LDropDown(int Id, int x, int y, int cx, int cy, LPopup *popup) { SetId(Id); LRect r(x, y, x+cx, y+cy); SetPos(r); if ((Popup = popup)) Popup->SetNotify(this); SetTabStop(true); } LDropDown::~LDropDown() { DeleteObj(Popup); } void LDropDown::OnFocus(bool f) { Invalidate(); } LPopup *LDropDown::GetPopup() { return Popup; } void LDropDown::SetPopup(LPopup *popup) { DeleteObj(Popup); Popup = popup; Invalidate(); } bool LDropDown::IsOpen() { return Popup && Popup->Visible(); } void LDropDown::OnPaint(LSurface *pDC) { LRect r = GetClient(); r.Offset(-r.x1, -r.y1); if (!r.Valid()) return; #if defined(LGI_CARBON) LColour NoPaintColour(L_MED); if (GetCss()) { LCss::ColorDef NoPaint = GetCss()->NoPaintColor(); if (NoPaint.Type == LCss::ColorRgb) NoPaintColour.Set(NoPaint.Rgb32, 32); else if (NoPaint.Type == LCss::ColorTransparent) NoPaintColour.Empty(); } if (!NoPaintColour.IsTransparent()) { pDC->Colour(NoPaintColour); pDC->Rectangle(); } LRect rc = GetClient(); rc.x1 += 2; rc.y2 -= 1; rc.x2 -= 1; HIRect Bounds = rc; HIThemeButtonDrawInfo Info; HIRect LabelRect; Info.version = 0; Info.state = Enabled() ? kThemeStateActive : kThemeStateInactive; Info.kind = kThemePushButton; Info.value = kThemeButtonOff; Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone; OSStatus e = HIThemeDrawButton( &Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal, &LabelRect); if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e); #else if (LApp::SkinEngine && TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_BUTTON)) { LMemDC Mem(r.X(), r.Y(), System24BitColourSpace); LCss::ColorDef f; if (GetCss()) f = GetCss()->BackgroundColor(); if (f.Type == LCss::ColorRgb) Mem.Colour(f.Rgb32, 32); else Mem.Colour(L_MED); Mem.Rectangle(); LApp::SkinEngine->DrawBtn(&Mem, r, LColour(L_HIGH), IsOpen(), Enabled()); pDC->Blt(0, 0, &Mem); r.Inset(2, 2); r.x2 -= 2; } else { LWideBorder(pDC, r, IsOpen() ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); if (Focus()) { #if WINNATIVE DrawFocusRect(pDC->Handle(), &((RECT)r)); #else pDC->Colour(L_LOW); pDC->Box(&r); #endif } } #endif int ArrowWidth = 5; double Aspect = (double)r.X() / r.Y(); int Cx = Aspect < 1.2 ? r.x1 + ((r.X() - ArrowWidth) >> 1) : r.x2 - (ArrowWidth << 1); int Cy = r.y1 + ((r.Y() - 3) >> 1); pDC->Colour(Enabled() && Popup ? L_TEXT : L_LOW); if (IsOpen()) { Cx++; Cy++; } for (int i=0; i<3; i++) { pDC->Line(Cx+i, Cy+i, Cx+ArrowWidth-i, Cy+i); } auto Nm = Name(); if (Nm && X() >= 32) { LDisplayString Ds(LSysFont, Nm); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(true); int Offset = IsOpen() ? 1 : 0; Ds.Draw(pDC, (Cx-Ds.X())/2+Offset+r.x1, (Y()-Ds.Y())/2+Offset); } } void LDropDown::Activate() { if (IsOpen()) { // Hide Popup->Visible(false); } else // Show { // Locate under myself LPoint p(X()-1, Y()); PointToScreen(p); LRect r(p.x-Popup->X()+1, p.y, p.x, p.y+Popup->Y()-1); // Show the popup if (!Popup->IsAttached()) { Popup->Attach(this); } Popup->Cancelled = false; Popup->SetPos(r); Popup->Visible(true); } Invalidate(); } bool LDropDown::OnKey(LKey &k) { if (k.IsChar && (k.c16 == ' ' || k.vkey == LK_RETURN)) { if (k.Down()) { Activate(); } return true; } else if (k.vkey == LK_ESCAPE) { if (k.Down() && IsOpen()) { Activate(); } return true; } return false; } void LDropDown::OnMouseClick(LMouse &m) { if (Popup && m.Down()) { Focus(true); Activate(); } } int LDropDown::OnNotify(LViewI *c, LNotification n) { if (c == (LViewI*)Popup) { switch (n.Type) { case LNotifyPopupDelete: { Popup = 0; break; } case LNotifyPopupVisible: { break; } case LNotifyPopupHide: { Invalidate(); OnPopupClose(); break; } default: break; } } return false; } diff --git a/src/common/Widgets/ScrollBar.cpp b/src/common/Widgets/ScrollBar.cpp --- a/src/common/Widgets/ScrollBar.cpp +++ b/src/common/Widgets/ScrollBar.cpp @@ -1,744 +1,733 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/LgiRes.h" #define DrawBorder(dc, r, edge) LThinBorder(dc, r, edge) #if defined(LGI_CARBON) #define MAC_SKIN 1 #elif defined(LGI_COCOA) #define MAC_LOOK 1 #else #define WINXP_LOOK 1 #endif enum ScrollZone { BTN_NONE, BTN_SUB, BTN_SLIDE, BTN_ADD, BTN_PAGE_SUB, BTN_PAGE_ADD, }; class LScrollBarPrivate { public: LScrollBar *Widget; bool Vertical; int64 Value, Min, Max, Page; LRect Sub, Add, Slide, PageSub, PageAdd; int Clicked; bool Over; int SlideOffset; int Ignore; LScrollBarPrivate(LScrollBar *w) { Ignore = 0; Widget = w; Vertical = true; Value = Min = 0; Max = -1; Page = 1; Clicked = BTN_NONE; Over = false; } bool IsVertical() { return Vertical; } int IsOver() { return Over ? Clicked : BTN_NONE; } void DrawIcon(LSurface *pDC, LRect &r, bool Add, LSystemColour c) { pDC->Colour(c); int IconSize = MAX(r.X(), r.Y()) * 2 / 6; int Cx = r.x1 + (r.X() >> 1); int Cy = r.y1 + (r.Y() >> 1); int Off = (IconSize >> 1) * (Add ? 1 : -1); int x = Cx + (IsVertical() ? 0 : Off); int y = Cy + (IsVertical() ? Off : 0); if (Add) { if (IsOver() == BTN_ADD) { x++; y++; } if (IsVertical()) { // down for (int i=0; iLine(x-i, y, x+i, y); - } } else { // right for (int i=0; iLine(x, y-i, x, y+i); - } } } else { if (IsOver() == BTN_SUB) { x++; y++; } if (IsVertical()) { // up for (int i=0; iLine(x-i, y, x+i, y); - } } else { // left for (int i=0; iLine(x, y-i, x, y+i); - } } } } void OnPaint(LSurface *pDC) { LColour SlideCol(L_MED); SlideCol.Rgb( (255 + SlideCol.r()) >> 1, (255 + SlideCol.g()) >> 1, (255 + SlideCol.b()) >> 1); #if MAC_LOOK || MAC_SKIN pDC->Colour(SlideCol); pDC->Rectangle(); if (IsValid()) { LRect r = Slide; r.Inset(3, 3); pDC->Colour(L_LOW); // pDC->Rectangle(&r); double rad = (IsVertical() ? (double)r.X() : (double)r.Y()) / 2; double cx = (double)r.x1 + rad; pDC->FilledArc(cx, r.y1 + rad, rad, 0, 180); pDC->Rectangle(r.x1, r.y1 + rad, r.x2, r.y2 - rad); pDC->FilledArc(cx, r.y2 - rad, rad, 180, 0); } #elif WINXP_LOOK // left/up button LRect r = Sub; DrawBorder(pDC, r, IsOver() == BTN_SUB ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, false, IsValid() ? L_BLACK : L_LOW); // right/down r = Add; DrawBorder(pDC, r, IsOver() == BTN_ADD ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, true, IsValid() ? L_BLACK : L_LOW); // printf("Paint %ix%i, %s\n", pDC->X(), pDC->Y(), Widget->GetPos().GetStr()); if (IsValid()) { // slide space pDC->Colour(SlideCol); pDC->Rectangle(&PageSub); pDC->Rectangle(&PageAdd); // slide button r = Slide; DrawBorder(pDC, r, DefaultRaisedEdge); // IsOver() == BTN_SLIDE ? SUNKEN : RAISED); pDC->Colour(L_MED); if (r.Valid()) pDC->Rectangle(&r); } else { pDC->Colour(SlideCol); pDC->Rectangle(&Slide); } #else #error "No look and feel defined." #endif } int GetWidth() { return IsVertical() ? Widget->X() : Widget->Y(); } int GetLength() { return (IsVertical() ? Widget->Y() : Widget->X()) #if !MAC_LOOK - (GetWidth() * 2) #endif ; } int64 GetRange() { return Max >= Min ? Max - Min + 1 : 0; } bool IsValid() { return Max >= Min; } void CalcRegions() { LRect r = Widget->GetPos(); Vertical = r.Y() > r.X(); int w = GetWidth(); int len = GetLength(); // Button sizes #if MAC_LOOK Sub.ZOff(-1, -1); Add.ZOff(-1, -1); #else Sub.ZOff(w-1, w-1); Add.ZOff(w-1, w-1); // Button positions if (IsVertical()) Add.Offset(0, Widget->GetPos().Y()-w); else Add.Offset(Widget->GetPos().X()-w, 0); #endif // Slider int64 Start, End; #if LGI_SDL int MinSize = w; // Touch UI needs large slide.... #else int MinSize = 18; #endif // printf("Calc %i, " LPrintfInt64 ", " LPrintfInt64 "\n", IsValid(), Min, Max); if (IsValid()) { int64 Range = GetRange(); int64 Size = Range ? MIN((int)Page, Range) * len / Range : len; if (Size < MinSize) Size = MinSize; Start = Range > Page ? Value * (len - Size) / (Range - (int)Page) : 0; End = Start + Size; /* printf("Range=%i Page=%i Size=%i Start=%i End=%i\n", (int)Range, (int)Page, (int)Size, (int)Start, (int)End); */ if (IsVertical()) { Slide.ZOff(w-1, (int) (End-Start-1)); #if MAC_LOOK Slide.Offset(0, (int) (r.y1+Start)); #else Slide.Offset(0, (int) (Sub.y2+1+Start)); #endif if (Start > 1) { PageSub.x1 = Slide.x1; #if MAC_LOOK PageSub.y1 = 0; #else PageSub.y1 = Sub.y2 + 1; #endif PageSub.x2 = Slide.x2; PageSub.y2 = Slide.y1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.y1 - 2) { PageAdd.x1 = Slide.x1; PageAdd.x2 = Slide.x2; PageAdd.y1 = Slide.y2 + 1; #if MAC_LOOK PageAdd.y2 = r.Y()-1; #else PageAdd.y2 = Add.y1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } else { Slide.ZOff((int)(End-Start-1), w-1); Slide.Offset((int)(Sub.x2+1+Start), 0); if (Start > 1) { PageSub.y1 = Slide.y1; #if MAC_LOOK PageSub.x1 = 0; #else PageSub.x1 = Sub.x2 + 1; #endif PageSub.y2 = Slide.y2; PageSub.x2 = Slide.x1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.x1 - 2) { PageAdd.y1 = Slide.y1; PageAdd.y2 = Slide.y2; PageAdd.x1 = Slide.x2 + 1; #if MAC_LOOK PageAdd.x2 = r.X() - 1; #else PageAdd.x2 = Add.x1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } } else { PageAdd.ZOff(-1, -1); PageSub.ZOff(-1, -1); Slide = Widget->GetClient(); if (IsVertical()) { Slide.Inset(0, Sub.y2 + 1); } else { Slide.Inset(Sub.x2 + 1, 0); } } } int OnHit(int x, int y) { #if MAC_SKIN HIThemeTrackDrawInfo Info; LRect Client = Widget->GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = Min; Info.max = Max - Page; Info.value = Value; Info.reserved = 0; Info.attributes = (Widget->Vertical() ? 0 : kThemeTrackHorizontal) | (Widget->Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Widget->Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = Page; Info.trackInfo.scrollbar.pressState = false; HIPoint pt = {(CGFloat)x, (CGFloat)y}; ControlPartCode part; Boolean b = HIThemeHitTestTrack(&Info, &pt, &part); if (b) { switch (part) { case kAppearancePartUpButton: return BTN_SUB; case kAppearancePartDownButton: return BTN_ADD; case 129: return BTN_SLIDE; case kControlPageUpPart: return BTN_PAGE_SUB; case kControlPageDownPart: return BTN_PAGE_ADD; default: printf("%s:%i - Unknown scroll bar hittest value: %i\n", _FL, part); break; } } #else if (Sub.Overlap(x, y)) return BTN_SUB; if (Slide.Overlap(x, y)) return BTN_SLIDE; if (Add.Overlap(x, y)) return BTN_ADD; if (PageSub.Overlap(x, y)) return BTN_PAGE_SUB; if (PageAdd.Overlap(x, y)) return BTN_PAGE_ADD; #endif return BTN_NONE; } int OnClick(int Btn, int x, int y) { if (IsValid()) { switch (Btn) { case BTN_SUB: { SetValue(Value-1); break; } case BTN_ADD: { SetValue(Value+1); break; } case BTN_PAGE_SUB: { SetValue(Value-Page); break; } case BTN_PAGE_ADD: { SetValue(Value+Page); break; } case BTN_SLIDE: { SlideOffset = IsVertical() ? y - Slide.y1 : x - Slide.x1; break; } } } return false; } void SetValue(int64 i) { if (i < Min) { i = Min; } if (IsValid() && i > Max - Page + 1) { i = MAX(Min, Max - Page + 1); } if (Value != i) { Value = i; CalcRegions(); Widget->Invalidate(); LViewI *n = Widget->GetNotify() ? Widget->GetNotify() : Widget->GetParent(); if (n) n->OnNotify(Widget, LNotifyItemChange); } } }; ///////////////////////////////////////////////////////////////////////////////////// LScrollBar::LScrollBar() : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); } LScrollBar::LScrollBar(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); SetId(id); if (name) Name(name); if (cx > cy) { SetVertical(false); } LResources::StyleElement(this); } LScrollBar::~LScrollBar() { DeleteObj(d); } bool LScrollBar::Valid() { return d->Max > d->Min; } int LScrollBar::SCROLL_BAR_SIZE = 0; int LScrollBar::GetScrollSize() { if (!SCROLL_BAR_SIZE) - { - SCROLL_BAR_SIZE = std::max(15, LScreenDpi().x / 5); - } + SCROLL_BAR_SIZE = std::max(15, LScreenDpi().x / 6); return SCROLL_BAR_SIZE; } bool LScrollBar::Attach(LViewI *p) { bool Status = LControl::Attach(p); #if 0 - printf("%p::Attach scroll bar to %s, Status=%i, _View=%p, Vis=%i\n", + printf("%p::Attach scroll bar to %s, Status=%i, Vis=%i\n", this, p->GetClass(), Status, - _View, Visible()); #endif return Status; } void LScrollBar::OnPaint(LSurface *pDC) { #if MAC_SKIN #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif HIThemeTrackDrawInfo Info; LRect Client = GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = d->Min; Info.max = d->Max - d->Page + 1; Info.value = d->Value; Info.reserved = 0; Info.attributes = (Vertical() ? 0 : kThemeTrackHorizontal) | (Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = d->Page; Info.trackInfo.scrollbar.pressState = false; CGContextRef Cr = pDC->Handle(); OSStatus e = HIThemeDrawTrack(&Info, NULL, Cr, kHIThemeOrientationNormal); if (e) printf("%s:%i - HIThemeDrawTrack failed with %li\n", _FL, e); #else d->OnPaint(pDC); #endif } void LScrollBar::OnPosChange() { d->CalcRegions(); } void LScrollBar::OnMouseClick(LMouse &m) { if (d->Max >= d->Min) { int Hit = d->OnHit(m.x, m.y); Capture(m.Down()); if (m.Down()) { if (Hit != d->Clicked) { d->Clicked = Hit; d->Over = true; Invalidate(); d->OnClick(Hit, m.x, m.y); if (Hit != BTN_SLIDE) { d->Ignore = 2; SetPulse(50); } } } else { if (d->Clicked) { d->Clicked = BTN_NONE; d->Over = false; Invalidate(); } } } } void LScrollBar::OnMouseMove(LMouse &m) { if (IsCapturing()) { if (d->Clicked == BTN_SLIDE) { if (d->GetLength()) { int64 Range = d->GetRange(); int Len = d->GetLength(); int Size = d->IsVertical() ? d->Slide.Y() : d->Slide.X(); int Px = (d->IsVertical() ? m.y : m.x) - d->GetWidth() - d->SlideOffset; int64 Value = Px * (Range - d->Page) / (Len - Size); d->SetValue(Value); } } else { int Hit = d->OnHit(m.x, m.y); bool Over = Hit == d->Clicked; if (Over != d->Over) { d->Over = Over; Invalidate(); } } } } bool LScrollBar::OnKey(LKey &k) { return false; } bool LScrollBar::OnMouseWheel(double Lines) { return false; } bool LScrollBar::Vertical() { return d->Vertical; } void LScrollBar::SetVertical(bool v) { d->Vertical = v; d->CalcRegions(); Invalidate(); } int64 LScrollBar::Value() { return d->Value; } void LScrollBar::Value(int64 v) { d->SetValue(v); } LRange LScrollBar::GetRange() const { return LRange(d->Min, d->Max - d->Min + 1); } void LScrollBar::Limits(int64 &Low, int64 &High) { Low = d->Min; High = d->Max; } bool LScrollBar::SetRange(const LRange &r) { if (d->Min != r.Start || d->Max != r.End() - 1) { d->Min = r.Start; d->Max = r.End() - 1; d->Page = MIN(d->Page, d->GetRange()); d->CalcRegions(); Invalidate(); OnConfigure(); } return true; } void LScrollBar::SetLimits(int64 Low, int64 High) { SetRange(LRange(Low, High-Low+1)); } int64 LScrollBar::Page() { return d->Page; } void LScrollBar::SetPage(int64 i) { if (d->Page != i) { d->Page = MAX(i, 1); d->CalcRegions(); Invalidate(); OnConfigure(); } } LMessage::Result LScrollBar::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } void LScrollBar::OnPulse() { if (d->Ignore > 0) { d->Ignore--; } else { LMouse m; if (GetMouse(m)) { int Hit = d->OnHit(m.x, m.y); if (Hit == d->Clicked) { d->OnClick(d->Clicked, m.x, m.y); } } } } diff --git a/src/common/Widgets/TabView.cpp b/src/common/Widgets/TabView.cpp --- a/src/common/Widgets/TabView.cpp +++ b/src/common/Widgets/TabView.cpp @@ -1,1538 +1,1524 @@ /*hdr ** FILE: LTabView.cpp ** AUTHOR: Matthew Allen ** DATE: 20/10/2000 ** DESCRIPTION: Lgi self-drawn tab control ** ** Copyright (C) 2000 Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/TabView.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/DisplayString.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/Path.h" enum TabViewStyle { TvWinXp, // Skin TvWin7, TvMac, }; #define MAC_STYLE_RADIUS 7 #define MAC_DBL_BUF 1 #if defined(__GTK_H__) - #define TAB_TXT_PAD 2 + #define TAB_TXT_PAD 2 #else - #define TAB_TXT_PAD 3 + #define TAB_TXT_PAD 3 #endif #if defined(MAC) && !LGI_COCOA && !defined(LGI_SDL) -#define MAC_PAINT 1 + #define MAC_PAINT 1 #else -#define MAC_PAINT 0 -#endif - -#ifdef WIN32 - -#ifdef TOOL_VLOW -#undef TOOL_VLOW -#endif - -#ifdef TOOL_LOW -#undef TOOL_LOW + #define MAC_PAINT 0 #endif -#ifdef TOOL_HIGH -#undef TOOL_HIGH -#endif - -#ifdef TOOL_VHIGH -#undef TOOL_VHIGH -#endif - -#define TOOL_VLOW GetSysColor(COLOR_3DDKSHADOW) -#define TOOL_LOW GetSysColor(COLOR_3DSHADOW) -#define TOOL_HIGH GetSysColor(COLOR_3DLIGHT) -#define TOOL_VHIGH GetSysColor(COLOR_3DHILIGHT) - -#endif - -#define TAB_MARGIN_X 10 // Px each side of the text label on the tab -#define CLOSE_BTN_SIZE 8 -#define CLOSE_BTN_GAP 8 -#define cFocusFore LColour(L_FOCUS_SEL_FORE) -#define cFocusBack LColour(L_FOCUS_SEL_BACK) +#define TAB_MARGIN_X 10 // Px each side of the text label on the tab +#define CLOSE_BTN_SIZE 8 +#define CLOSE_BTN_GAP 8 +#define cFocusFore LColour(L_FOCUS_SEL_FORE) +#define cFocusBack LColour(L_FOCUS_SEL_BACK) //////////////////////////////////////////////////////////////////////////////////////////// class LTabViewPrivate { public: // General int Current; LRect TabClient; bool PourChildren; // Painting LRect Inset, Tabs; int TabsHeight; double TabsBaseline; int Depth; TabViewStyle Style; enum ResType { ResWorkspace, ResSelectedUnfocused, ResSelectedFocused, ResMax }; LAutoPtr Corners[ResMax]; LColour cBack, cBorder, cFill, cSelUnfoc, cTopEdge, cBottomEdge; // Scrolling int Scroll; // number of buttons scrolled off the left of the control LRect LeftBtn; // scroll left button LRect RightBtn; // scroll right button LTabViewPrivate() { Depth = 0; TabsHeight = 0; TabsBaseline = 0.0; PourChildren = true; Current = 0; TabClient.ZOff(-1, -1); Scroll = 0; LeftBtn.ZOff(-1, -1); RightBtn.ZOff(-1, -1); Style = TvMac; } uint8_t Clamp(int i) { if (i < 0) return 0; if (i > 255) return 255; return (uint8_t)i; } LColour Tint(double amt) { auto Bk = cBack.GetGray(); double Ratio = Bk < 100 ? 1.0 / amt : amt; LColour c( Clamp((int)(Ratio * cBack.r())), Clamp((int)(Ratio * cBack.g())), Clamp((int)(Ratio * cBack.b()))); return c; } bool DrawCircle(LAutoPtr &Dc, LColour c) { if (Dc) return true; double r = 7.0; int x = (int)(r * 2.0); if (!Dc.Reset(new LMemDC(x, x, System32BitColourSpace))) return false; Dc->Colour(0, 32); Dc->Rectangle(); LPath p; p.Circle(r, r, r-0.7); p.SetFillRule(FILLRULE_ODDEVEN); LSolidBrush s(c); p.Fill(Dc, s); p.Empty(); p.Circle(r, r, r); p.Circle(r, r, r - 1.0); p.SetFillRule(FILLRULE_ODDEVEN); LBlendStop Stops[2] = { {0.0, cTopEdge.c32()}, {1.0, cBottomEdge.c32()} }; LPointF a(4, 4), b(9, 9); LLinearBlendBrush s2(a, b, CountOf(Stops), Stops); p.Fill(Dc, s2); - Dc->ConvertPreMulAlpha(true); + if (Dc->IsPreMultipliedAlpha()) + Dc->ConvertPreMulAlpha(true); return true; } void CreateCorners() { LAutoPtr &White = Corners[ResWorkspace]; LAutoPtr &Unfoc = Corners[ResSelectedUnfocused]; LAutoPtr &Sel = Corners[ResSelectedFocused]; DrawCircle(White, LColour(L_WORKSPACE)); DrawCircle(Unfoc, cSelUnfoc); DrawCircle(Sel, cFocusBack); } }; struct LTabPagePriv { LTabPage *Tab; bool NonDefaultFont; LAutoPtr Ds; LTabPagePriv(LTabPage *t) : Tab(t) { NonDefaultFont = false; } LDisplayString *GetDs() { auto Text = Tab->Name(); if (Text && !Ds) { LFont *f = NULL; auto s = Tab->GetCss(); NonDefaultFont = s ? s->HasFontStyle() : false; if (NonDefaultFont) { if ((f = new LFont)) { *f = *LSysFont; if (f->CreateFromCss(s)) Tab->SetFont(f, true); else DeleteObj(f); } } else { f = Tab->GetFont(); } if (f) Ds.Reset(new LDisplayString(f, Text)); else LAssert(!"no font."); } return Ds; } }; class TabIterator : public LArray { public: TabIterator(List &l) { for (auto c : l) { auto p = dynamic_cast(c); if (p) Add(p); } fixed = true; } }; //////////////////////////////////////////////////////////////////////////////////////////// // Tab Control LTabView::LTabView(int id, int x, int y, int cx, int cy, const char *name, int Init) : ResObject(Res_TabView) { d = new LTabViewPrivate; d->Current = Init; SetId(id); LRect r(x, y, x+cx, y+cy); SetPos(r); Name(name); _BorderSize = 0; SetTabStop(true); SetPourLargest(true); #if WINNATIVE SetDlgCode(DLGC_WANTARROWS); #endif } LTabView::~LTabView() { DeleteObj(d); } bool LTabView::GetPourChildren() { return d->PourChildren; } void LTabView::SetPourChildren(bool b) { d->PourChildren = b; } LTabPage *LTabView::TabAt(int Idx) { TabIterator i(Children); return i[Idx]; } size_t LTabView::GetTabs() { return Children.Length(); } LTabPage *LTabView::GetCurrent() { TabIterator i(Children); return i[d->Current]; } int LTabView::TabY() { return d->TabsHeight + (TAB_TXT_PAD << 1); } void LTabView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (!Attaching) { TabIterator c(Children); if (d->Current >= c.Length()) d->Current = (int)c.Length() - 1; #if LGI_VIEW_HANDLE if (Handle()) #endif Invalidate(); } } #if defined(WINNATIVE) LViewI *LTabView::FindControl(HWND hCtrl) { LViewI *Ctrl = 0; if (hCtrl == Handle()) { return this; } TabIterator it(Children); for (int i=0; iFindControl(hCtrl)) return Ctrl; } return 0; } #endif LViewI *LTabView::FindControl(int Id) { if (GetId() == Id) { return this; } LViewI *Ctrl; TabIterator it(Children); for (int i=0; iFindControl(Id))) return Ctrl; } return 0; } bool LTabView::Attach(LViewI *parent) { bool Status = LView::Attach(parent); if (Status) { TabIterator it(Children); LTabPage *p = d->Current < it.Length() ? it[d->Current] : 0; if (p) { OnPosChange(); p->Attach(this); } for (int i=0; i_Window = _Window; } } return Status; } int64 LTabView::Value() { return d->Current; } void LTabView::OnCreate() { LResources::StyleElement(this); d->Depth = 0; LViewI *p = this; while ((p = p->GetParent())) { if (p == (LViewI*)GetWindow()) break; LTabView *tv = dynamic_cast(p); if (tv) d->Depth++; } OnStyleChange(); TabIterator it(Children); LTabPage *page = d->Current < it.Length() ? it[d->Current] : 0; if (page) { page->Attach(this); page->Visible(true); } } void LTabView::Value(int64 i) { if (Children.Length() > 0 && i != d->Current) { // change tab TabIterator it(Children); LTabPage *Old = it[d->Current]; if (Old) { + // printf("%s:%i - old[%i] hide.\n", _FL, d->Current); Old->Visible(false); } + else printf("%s:%i - no old.\n", _FL); d->Current = (int)MIN(i, (ssize_t)it.Length()-1); OnPosChange(); LTabPage *p = it[d->Current]; - if (p && IsAttached()) + if (p) { - p->Attach(this); + if (!p->IsAttached()) + { + // printf("%s:%i - new[%i] attach %p.\n", _FL, d->Current, p->Handle()); + p->Attach(this); + } + // printf("%s:%i - new[%i] visible %p.\n", _FL, d->Current, p->Handle()); p->Visible(true); } Invalidate(); SendNotify(LNotifyValueChanged); + + // GetWindow()->_Dump(); } } LMessage::Result LTabView::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } int LTabView::OnNotify(LViewI *Ctrl, LNotification n) { if (GetParent()) { return GetParent()->OnNotify(Ctrl, n); } return 0; } bool LTabView::Append(LTabPage *Page, int Where) { if (Page) { Page->TabCtrl = this; Page->_Window = _Window; if (IsAttached() && Children.Length() == 1) { Page->Attach(this); OnPosChange(); } else { Page->Visible(Children.Length() == d->Current); AddView(Page); } Invalidate(); return true; } return false; } LTabPage *LTabView::Append(const char *name, int Where) { LTabPage *Page = new LTabPage(name); if (Page) { Page->TabCtrl = this; Page->_Window = _Window; Page->SetParent(this); if (IsAttached() && Children.Length() == 0) { Page->Attach(this); OnPosChange(); } else { Page->Visible(Children.Length() == d->Current); AddView(Page); } Invalidate(); } return Page; } bool LTabView::Delete(LTabPage *Page) { bool Status = false; TabIterator it(Children); ssize_t Index = it.IndexOf(Page); if (Index >= 0) { if (Index == d->Current) { Status = Page->Detach(); LAssert(Status); } else { Status = DelView(Page); LAssert(Status); } delete Page; Value(Index); } return Status; } LRect <abView::GetTabClient() { if (d->Style == TvMac) { d->TabClient = CalcInset(); d->TabClient.Inset(2, 2); // The inset border d->TabClient.y1 = d->Tabs.y2 + 1; // The tab strip LTabPage *p = Children.Length() ? GetCurrent() : NULL; if (p && p->GetCss()) { // Inset by any padding LCss::Len l; d->TabClient.x1 += (l = p->GetCss()->PaddingLeft()).IsValid() ? l.ToPx(d->TabClient.X(), GetFont()) : 0; d->TabClient.y1 += (l = p->GetCss()->PaddingTop()).IsValid() ? l.ToPx(d->TabClient.Y(), GetFont()) : 0; d->TabClient.x2 -= (l = p->GetCss()->PaddingRight()).IsValid() ? l.ToPx(d->TabClient.X(), GetFont()) : 0; d->TabClient.y2 -= (l = p->GetCss()->PaddingBottom()).IsValid() ? l.ToPx(d->TabClient.Y(), GetFont()) : 0; } } else { d->TabClient = LView::GetClient(); d->TabClient.Offset(-d->TabClient.x1, -d->TabClient.y1); d->TabClient.Inset(2, 2); d->TabClient.y1 += TabY(); } return d->TabClient; } int LTabView::HitTest(LMouse &m) { if (d->LeftBtn.Overlap(m.x, m.y)) { return LeftScrollBtn; } else if (d->RightBtn.Overlap(m.x, m.y)) { return RightScrollBtn; } else { // int Hit = -1; TabIterator it(Children); for (int i=0; iTabPos.Overlap(m.x, m.y)) return i; } } return NoBtn; } void LTabView::OnMouseClick(LMouse &m) { bool DownLeft = m.Down() || m.Left(); int Result = HitTest(m); if (m.Down()) Focus(true); if (Result == LeftScrollBtn) { if (DownLeft) { d->Scroll++; Invalidate(); } } else if (Result == RightScrollBtn) { if (DownLeft) { d->Scroll = MAX(0, d->Scroll-1); Invalidate(); } } else if (Result >= 0) { TabIterator it(Children); LTabPage *p = it[Result]; if (p) { if (p->HasButton() && - p->BtnPos.Overlap(m.x, m.y)) + p->BtnPos.Overlap(m)) { if (DownLeft) { p->OnButtonClick(m); // The tab can delete itself after this event return; } } else { // We set this before firing the event, otherwise the // code seeing the notication gets the old value. if (DownLeft) Value(Result); p->OnTabClick(m); } } } if (DownLeft) Focus(true); } bool LTabView::OnKey(LKey &k) { if (k.Down()) { switch (k.vkey) { case LK_LEFT: { if (k.Alt()) break; if (d->Current > 0) { TabIterator it(Children); LTabPage *p = it[d->Current - 1]; if (p && !p->TabPos.Valid()) { if (d->Scroll) d->Scroll--; } Value(d->Current - 1); } return true; break; } case LK_RIGHT: { if (k.Alt()) break; TabIterator it(Children); if (d->Current < it.Length() - 1) { LTabPage *p = it[d->Current + 1]; if (p && !p->TabPos.Valid()) { d->Scroll++; } Value(d->Current + 1); } return true; break; } } } return false; } void LTabView::OnFocus(bool f) { if (!Children.Length()) return; TabIterator it(Children); LTabPage *p = it[d->Current]; if (p) { LRect r = p->TabPos; r.y2 += 2; Invalidate(&r); } } void LTabView::OnAttach() { } LRect <abView::CalcInset() { LRect Padding(0, 0, 0, 0); d->Inset = GetClient(); auto f = GetFont(); if (GetCss()) { LCss::Len l; if ((l = GetCss()->PaddingLeft()).IsValid()) Padding.x1 = l.ToPx(d->Inset.X(), f); if ((l = GetCss()->PaddingTop()).IsValid()) Padding.y1 = l.ToPx(d->Inset.Y(), f); if ((l = GetCss()->PaddingRight()).IsValid()) Padding.x2 = l.ToPx(d->Inset.X(), f); if ((l = GetCss()->PaddingBottom()).IsValid()) Padding.y2 = l.ToPx(d->Inset.Y(), f); } int TabTextY = 0; d->TabsBaseline = 0; TabIterator Tabs(Children); for (auto t : Tabs) { auto Ds = t->d->GetDs(); if (Ds) { TabTextY = MAX(TabTextY, Ds->Y()); auto Fnt = Ds->GetFont(); d->TabsBaseline = MAX(d->TabsBaseline, Fnt->Ascent()); } } if (!TabTextY) TabTextY = f->GetHeight(); d->TabsHeight = TabTextY; d->Inset.x1 += Padding.x1; d->Inset.x2 -= Padding.x2; d->Inset.y1 += Padding.y1; d->Inset.y2 -= Padding.y2; d->Tabs.ZOff(d->Inset.X() - 20, TabY() - 1); d->Tabs.Offset(d->Inset.x1 + 10, d->Inset.y1); d->Inset.y1 = d->Tabs.y1 + (d->Tabs.Y() / 2); return d->Inset; } void LTabView::OnStyleChange() { if (!d->cBack.IsValid()) { LCssTools Tools(this); d->cBack = Tools.GetBack(); d->cTopEdge = d->Tint(0.845); d->cBottomEdge = d->Tint(0.708); d->cSelUnfoc = LColour(L_NON_FOCUS_SEL_BACK); auto mul = pow(0.909f, 1+d->Depth); // 240->218 d->cBorder = d->Tint(mul); mul = pow(0.959f, 1+d->Depth); // 240->230 d->cFill = d->Tint(mul); auto *Css = GetCss(true); if (Css) { if (!Css->BackgroundColor().IsValid()) Css->BackgroundColor(LCss::ColorDef(d->cFill)); } d->CreateCorners(); } TabIterator Tabs(Children); for (auto t : Tabs) t->OnStyleChange(); Invalidate(); } void LTabView::OnPaint(LSurface *pDC) { if (!d->cBack.IsValid()) OnStyleChange(); LCssTools Tools(this); TabIterator it(Children); if (d->Current >= it.Length()) Value(it.Length() - 1); if (d->Style == TvMac) { CalcInset(); LView *Pv = GetParent() ? GetParent()->GetGView() : NULL; LColour NoPaint = (Pv ? Pv : this)->StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); if (!NoPaint.IsTransparent()) { pDC->Colour(NoPaint); pDC->Rectangle(); } #ifdef LGI_CARBON HIRect Bounds = d->Inset; HIThemeTabPaneDrawInfo Info; Info.version = 1; Info.state = Enabled() ? kThemeStateActive : kThemeStateInactive; Info.direction = kThemeTabNorth; Info.size = kHIThemeTabSizeNormal; Info.kind = kHIThemeTabKindNormal; Info.adornment = kHIThemeTabPaneAdornmentNormal; OSStatus e = HIThemeDrawTabPane(&Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal); #else // Draw the inset area at 'd->Inset' LRect Bounds = d->Inset; pDC->Colour(d->cBorder); pDC->Box(&Bounds); Bounds.Inset(1, 1); pDC->Box(&Bounds); Bounds.Inset(1, 1); pDC->Colour(d->cFill); pDC->Rectangle(&Bounds); #endif int x = d->Tabs.x1, y = d->Tabs.y1; #ifndef LGI_CARBON LSurface *pScreen = pDC; #endif for (unsigned i = 0; i < it.Length(); i++) { auto Tab = it[i]; auto Foc = Focus(); LDisplayString *ds = Tab->d->GetDs(); bool First = i == 0; bool Last = i == it.Length() - 1; bool IsCurrent = d->Current == i; LRect r(0, 0, Tab->GetTabPx() - 1, d->Tabs.Y() - 1); r.Offset(x, y); #ifdef LGI_CARBON HIRect TabRc = r; HIThemeTabDrawInfo TabInfo; HIRect Label; TabInfo.version = 1; TabInfo.style = IsCurrent ? (Foc ? kThemeTabFront : kThemeTabNonFrontPressed) : kThemeTabNonFront; TabInfo.direction = Info.direction; TabInfo.size = Info.size; TabInfo.adornment = Info.adornment; TabInfo.kind = Info.kind; if (it.Length() == 1) TabInfo.position = kHIThemeTabPositionOnly; else if (First) TabInfo.position = kHIThemeTabPositionFirst; else if (Last) TabInfo.position = kHIThemeTabPositionLast; else TabInfo.position = kHIThemeTabPositionMiddle; e = HIThemeDrawTab(&TabRc, &TabInfo, pDC->Handle(), kHIThemeOrientationNormal, &Label); r = Label; #else LColour cTabFill = IsCurrent ? (Foc ? cFocusBack : d->cSelUnfoc) : LColour(L_WORKSPACE); LColour cInterTabBorder = d->Tint(0.9625); LRect b = r; #if MAC_DBL_BUF LMemDC Mem; if (First || Last) { if (Mem.Create(r.X(), r.Y(), System32BitColourSpace)) { pDC = &Mem; b.Offset(-b.x1, -b.y1); } Mem.Colour(LColour::Red); Mem.Rectangle(); } #endif pDC->Colour(d->cTopEdge); pDC->Line(b.x1, b.y1, b.x2, b.y1); // top edge if (i == 0) { pDC->Line(b.x1, b.y1, b.x1, b.y2); // left edge } else { pDC->Colour(cInterTabBorder); pDC->Line(b.x1, b.y1+1, b.x1, b.y2+1); // left edge } pDC->Colour(d->cBottomEdge); pDC->Line(b.x2, b.y2, b.x1, b.y2); // bottom edge if (Last) { pDC->Line(b.x2, b.y2, b.x2, b.y1); // right edge } else { pDC->Colour(cInterTabBorder); pDC->Line(b.x2, b.y2-1, b.x2, b.y1+1); // right edge between tabs } b.Inset(1, 1); pDC->Colour(cTabFill); pDC->Rectangle(&b); LRect Clip00(0, 0, MAC_STYLE_RADIUS-1, MAC_STYLE_RADIUS-1); auto Res = IsCurrent ? (Foc ? LTabViewPrivate::ResSelectedFocused : LTabViewPrivate::ResSelectedUnfocused) : LTabViewPrivate::ResWorkspace; if (First) { LRect Clip01 = Clip00.Move(0, r.Y() - Clip00.Y()); #if MAC_DBL_BUF // Erase the areas we will paint over pDC->Op(GDC_SET); pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : NoPaint); pDC->Rectangle(&Clip00); pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : d->cFill); pDC->Rectangle(&Clip01); #endif // Draw in the rounded corners pDC->Op(GDC_ALPHA); pDC->Blt(Clip00.x1, Clip00.y1, d->Corners[Res], Clip00); pDC->Blt(Clip01.x1, Clip01.y1, d->Corners[Res], Clip00.Move(0, MAC_STYLE_RADIUS)); } if (Last) { LRect Clip10 = Clip00.Move(r.X() - Clip00.X(), 0); LRect Clip11 = Clip00.Move(Clip10.x1, r.Y() - Clip00.Y()); #if MAC_DBL_BUF // Erase the areas we will paint over pDC->Op(GDC_SET); pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : NoPaint); pDC->Rectangle(&Clip10); pDC->Colour(pScreen->SupportsAlphaCompositing() ? LColour(0, 32) : d->cFill); pDC->Rectangle(&Clip11); #endif // Draw in the rounded corners pDC->Op(GDC_ALPHA); pDC->Blt(Clip10.x1, Clip10.y1, d->Corners[Res], Clip00.Move(MAC_STYLE_RADIUS, 0)); pDC->Blt(Clip11.x1, Clip11.y1, d->Corners[Res], Clip00.Move(MAC_STYLE_RADIUS, MAC_STYLE_RADIUS)); } #if MAC_DBL_BUF if (First || Last) { if (pScreen->SupportsAlphaCompositing()) pScreen->Op(GDC_ALPHA); pScreen->Blt(r.x1, r.y1, &Mem); } #endif pDC = pScreen; #endif LFont *tf = ds->GetFont(); int BaselineOff = (int) (d->TabsBaseline - tf->Ascent()); tf->Transparent(true); LCss::ColorDef Fore; if (Tab->GetCss()) Fore = Tab->GetCss()->Color(); tf->Fore(Fore.IsValid() ? (LColour)Fore : IsCurrent && Foc ? cFocusFore : Tools.GetFore()); int DsX = r.x1 + TAB_MARGIN_X; int DsY = r.y1 + TAB_TXT_PAD + BaselineOff; ds->Draw(pDC, DsX, DsY, &r); if (Tab->HasButton()) { Tab->BtnPos.ZOff(CLOSE_BTN_SIZE-1, CLOSE_BTN_SIZE-1); Tab->BtnPos.Offset(DsX + ds->X() + CLOSE_BTN_GAP, r.y1 + ((r.Y()-Tab->BtnPos.Y()) >> 1)); Tab->OnButtonPaint(pDC); } else Tab->BtnPos.ZOff(-1, -1); it[i]->TabPos = r; x += r.X() #ifdef LGI_CARBON + (i ? -1 : 2) // Fudge factor to make it look nice, wtf apple? #endif ; } #if 0 pDC->Colour(LColour::Green); pDC->Line(0, 0, pDC->X()-1, pDC->Y()-1); #endif } else if (d->Style == TvWinXp) { if (LApp::SkinEngine && TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_TABVIEW)) { LSkinState State; State.pScreen = pDC; State.MouseOver = false; + LApp::SkinEngine->OnPaint_LTabView(this, &State); } else { LRect r = GetTabClient(); r.Inset(-2, -2); LWideBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(0, 0, X()-1, d->TabClient.y1-3); LTabPage *Sel = 0; int x = r.x1; if (d->Scroll) { d->RightBtn.ZOff(12, TabY() - 2); x = d->RightBtn.x2 + 4; } else { d->RightBtn.ZOff(-1, -1); } d->LeftBtn.ZOff(-1, -1); for (int n=0; nScroll) { p->TabPos.ZOff(-1, -1); } else { int Wid = p->GetTabPx(); p->TabPos.ZOff(Wid, TabY()-3); p->TabPos.Offset(x, 2); if (p->TabPos.x2 > r.x2 - 16) { d->LeftBtn.x2 = X()-1; d->LeftBtn.x1 = d->LeftBtn.x2 - 12; d->LeftBtn.y1 = 0; d->LeftBtn.y2 = TabY() - 2; p->TabPos.ZOff(-1, -1); break; } if (d->Current != n) { p->PaintTab(pDC, false); } else { Sel = p; } x += Wid+1; } } if (!it.Length()) { pDC->Colour(L_MED); pDC->Rectangle(&r); } if (Sel) { Sel->PaintTab(pDC, true); } if (d->LeftBtn.Valid()) { r = d->LeftBtn; LWideBorder(pDC, r, DefaultRaisedEdge); int x = r.x1 + (r.X() >> 1) + 1; int y = r.y1 + (r.Y() >> 1) - 1; pDC->Colour(L_TEXT); for (int i=0; i<4; i++) { pDC->Line(x-i, y-i, x-i, y+i); } } if (d->RightBtn.Valid()) { r = d->RightBtn; LWideBorder(pDC, r, DefaultRaisedEdge); int x = r.x1 + (r.X() >> 1) - 2; int y = r.y1 + (r.Y() >> 1) - 1; pDC->Colour(L_TEXT); for (int i=0; i<4; i++) { pDC->Line(x+i, y-i, x+i, y+i); } } } } else LAssert(!"Not impl."); } void LTabView::OnPosChange() { GetTabClient(); if (Children.Length()) { TabIterator it(Children); LTabPage *p = it[d->Current]; if (p) { p->SetPos(d->TabClient, true); if (d->PourChildren) { LRect r = d->TabClient; r.Offset(-r.x1, -r.y1); LRegion Rgn(r); for (LViewI *c: p->IterateViews()) c->Pour(Rgn); } else { auto It = p->IterateViews(); if (It.Length() == 1) { LTableLayout *tl = dynamic_cast(It[0]); if (tl) { LRect r = p->GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); tl->SetPos(r); } } } } } } ////////////////////////////////////////////////////////////////////////////////// char *_lgi_gview_cmp(LView *a, LView *b) { static char Str[256]; if (a && b) { #if !LGI_VIEW_HANDLE sprintf_s(Str, sizeof(Str), "LView: %p,%p", dynamic_cast(a), dynamic_cast(b)); #else sprintf_s(Str, sizeof(Str), "LView: %p,%p Hnd: %p,%p", dynamic_cast(a), dynamic_cast(b), (void*)a->Handle(), (void*)b->Handle()); #endif } else { Str[0] = 0; } return Str; } LTabPage::LTabPage(const char *name) : ResObject(Res_Tab) { d = new LTabPagePriv(this); LRect r(0, 0, 1000, 1000); SetPos(r); Name(name); Button = false; TabCtrl = 0; TabPos.ZOff(-1, -1); BtnPos.ZOff(-1, -1); GetCss(true)->Padding("4px"); #if WINNATIVE SetStyle(GetStyle() | WS_CLIPCHILDREN); CreateClassW32(GetClass(), 0, CS_HREDRAW | CS_VREDRAW); #elif defined(HAIKU) Visible(false); #endif LResources::StyleElement(this); } LTabPage::~LTabPage() { delete d; } int LTabPage::GetTabPx() { LDisplayString *Ds = d->GetDs(); int Px = TAB_MARGIN_X << 1; if (Ds) { Px += Ds->X(); if (Button) Px += CLOSE_BTN_GAP + CLOSE_BTN_SIZE; } return Px; } bool LTabPage::HasButton() { return Button; } void LTabPage::HasButton(bool b) { Button = b; if (GetParent()) GetParent()->Invalidate(); } void LTabPage::OnButtonClick(LMouse &m) { if (GetId() > 0) SendNotify(LNotifyTabPageButtonClick); } void LTabPage::OnTabClick(LMouse &m) { LViewI *v = GetId() > 0 ? this : GetParent(); v->SendNotify(LNotifyItemClick); } void LTabPage::OnButtonPaint(LSurface *pDC) { #if MAC_PAINT #else // The default is a close button LColour Low(L_LOW); LColour Mid(L_MED); Mid = Mid.Mix(Low); pDC->Colour(Mid); pDC->Line(BtnPos.x1, BtnPos.y1+1, BtnPos.x2-1, BtnPos.y2); pDC->Line(BtnPos.x1+1, BtnPos.y1, BtnPos.x2, BtnPos.y2-1); pDC->Line(BtnPos.x1, BtnPos.y2-1, BtnPos.x2-1, BtnPos.y1); pDC->Line(BtnPos.x1+1, BtnPos.y2, BtnPos.x2, BtnPos.y1+1); pDC->Colour(Low); pDC->Line(BtnPos.x1+1, BtnPos.y1+1, BtnPos.x2-1, BtnPos.y2-1); pDC->Line(BtnPos.x2-1, BtnPos.y1+1, BtnPos.x1+1, BtnPos.y2-1); #endif } int64 LTabPage::Value() { if (!TabCtrl) return false; ssize_t Idx = TabCtrl->IterateViews().IndexOf(this); return TabCtrl->Value() == Idx; } void LTabPage::Value(int64 v) { if (v) Select(); } const char *LTabPage::Name() { return LBase::Name(); } bool LTabPage::Name(const char *name) { bool Status = LView::Name(name); d->Ds.Reset(); if (GetParent()) GetParent()->Invalidate(); return Status; } void LTabPage::PaintTab(LSurface *pDC, bool Selected) { #if MAC_PAINT #else LRect r = TabPos; if (Selected) { r.Inset(-2, -2); } pDC->Colour(L_LIGHT); bool First = false; if (TabCtrl) { TabIterator it(TabCtrl->Children); First = it[0] == this; } if (First) pDC->Line(r.x1, r.y1+1, r.x1, r.y2); else pDC->Line(r.x1, r.y1+1, r.x1, r.y2-1); pDC->Line(r.x1+1, r.y1, r.x2-1, r.y1); pDC->Colour(L_HIGH); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2); pDC->Line(r.x1+1, r.y1+1, r.x2-1, r.y1+1); pDC->Colour(L_LOW); pDC->Line(r.x2-1, r.y1+1, r.x2-1, r.y2); pDC->Colour(L_SHADOW); pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1); r.x2 -= 2; r.x1 += 2; r.y1 += 2; pDC->Colour(L_MED); pDC->Rectangle(&r); pDC->Set(r.x1, r.y1); pDC->Set(r.x2, r.y1); int Cx = r.x1 + TAB_MARGIN_X; auto t = Name(); if (t) { LFont *f = GetFont(); LDisplayString ds(f, t); f->Colour(L_TEXT, L_MED); f->Transparent(true); int y = r.y1 + ((r.Y()-ds.Y())/2); ds.Draw(pDC, Cx, y); if (TabCtrl->Focus() && Selected) { r.Set(Cx, y, Cx+ds.X(), y+ds.Y()); r.Inset(-2, -1); r.y1++; pDC->Colour(L_LOW); pDC->Box(&r); } Cx += ds.X() + CLOSE_BTN_GAP; } if (Button) { BtnPos.ZOff(CLOSE_BTN_SIZE-1, CLOSE_BTN_SIZE-1); BtnPos.Offset(Cx, r.y1 + ((r.Y()-BtnPos.Y()) / 2)); OnButtonPaint(pDC); } else BtnPos.ZOff(-1, -1); #endif } bool LTabPage::Attach(LViewI *parent) { bool Status = false; if (TabCtrl) { if (!IsAttached()) { Status = LView::Attach(parent); } else { Status = true; } for (auto w: Children) { if (!w->IsAttached()) { w->Attach(this); w->SetNotify(TabCtrl->GetParent()); } } } return Status; } LMessage::Result LTabPage::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } void LTabPage::Append(LViewI *Wnd) { if (Wnd) { Wnd->SetNotify(TabCtrl); if (IsAttached() && TabCtrl) { Wnd->Attach(this); LTabView *v = dynamic_cast(GetParent()); if (v && v->GetPourChildren()) { v->OnPosChange(); } } else if (!HasView(Wnd)) { AddView(Wnd); } else LAssert(0); } } bool LTabPage::Remove(LViewI *Wnd) { if (Wnd) { LAssert(HasView(Wnd)); Wnd->Detach(); return true; } return false; } LColour LTabPage::GetBackground() { if (TabCtrl && TabCtrl->d->Style == TvMac) return LColour(226, 226, 226); // 207? else return LColour(L_MED); } void LTabPage::OnStyleChange() { SetFont(LSysFont, false); GetParent()->Invalidate(); } void LTabPage::SetFont(LFont *Font, bool OwnIt) { d->Ds.Reset(); Invalidate(); return LView::SetFont(Font, OwnIt); } void LTabPage::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); LColour Bk = StyleColour(LCss::PropBackgroundColor, TabCtrl ? TabCtrl->d->cFill : LColour(L_MED), 1); pDC->Colour(Bk); pDC->Rectangle(&r); #if 0 pDC->Colour(LColour::Red); pDC->Line(0, 0, pDC->X()-1, pDC->Y()-1); #endif } void LTabPage::OnFocus(bool b) { } bool LTabPage::OnKey(LKey &k) { return false; } bool LTabPage::LoadFromResource(int Res) { LAutoString n; auto ch = IterateViews(); LViewI *v; while ((v = ch[0])) { v->Detach(); DelView(v); ch.Delete(v, true); } bool Status = LResourceLoad::LoadFromResource(Res, this, 0, &n); if (ValidStr(n)) Name(n); /* This isn't needed if the controls properly inherit colours. if (TabCtrl && TabCtrl->d->Style == TvMac) // Sigh for (auto c : Children) c->GetCss(true)->BackgroundColor(LCss::ColorDef(GetBackground())); */ if (IsAttached()) AttachChildren(); return Status; } void LTabPage::Select() { if (GetParent()) { ssize_t Idx = GetParent()->IterateViews().IndexOf(this); if (Idx >= 0) GetParent()->Value(Idx); } } diff --git a/src/common/Widgets/TableLayout.cpp b/src/common/Widgets/TableLayout.cpp --- a/src/common/Widgets/TableLayout.cpp +++ b/src/common/Widgets/TableLayout.cpp @@ -1,2529 +1,2529 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/CssTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Variant.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "lgi/common/CheckBox.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TabView.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Css.h" #undef _FL #define _FL LGetLeaf(__FILE__), __LINE__ enum CellFlag { // A null value when the call flag is not known yet SizeUnknown, // The cell contains fixed size objects SizeFixed, // The cell contains objects that have variable size SizeGrow, // The cell contains objects that will use all available space SizeFill, }; #define Izza(c) dynamic_cast(v) // #define DEBUG_LAYOUT 105 #define DEBUG_PROFILE 0 #define DEBUG_DRAW_CELLS 0 // #define DEBUG_CTRL_ID 105 #ifdef DEBUG_CTRL_ID static LString Indent(int Depth) { return LString(" ") * (Depth << 2); } #endif struct LPrintfLogger : public LStream { public: ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { if (!Ptr) return 0; #ifdef WINNATIVE LgiTrace("%.*s", (int)Size, (const char*)Ptr); return Size; #else return printf("%.*s", (int)Size, (const char*)Ptr); #endif } } PrintfLogger; int LTableLayout::CellSpacing = 4; const char *FlagToString(CellFlag f) { switch (f) { case SizeUnknown: return "Unknown"; case SizeFixed: return "Fixed"; case SizeGrow: return "Grow"; case SizeFill: return "Fill"; } return "error"; } template T CountRange(LArray &a, ssize_t Start, ssize_t End) { T c = 0; for (ssize_t i=Start; i<=End; i++) { c += a[i]; } return c; } struct UnderInfo { int Priority; int Col; // index of column int Grow; // in px }; static void DistributeUnusedSpace( LArray &Min, LArray &Max, LArray &Flags, int Total, int CellSpacing, LStream *Debug = NULL) { // Now allocate unused space int Borders = (int)Min.Length() - 1; int Sum = CountRange(Min, 0, Borders) + (Borders * CellSpacing); if (Sum >= Total) return; int i, Avail = Total - Sum; // Do growable ones first LArray Unders; int UnknownGrow = 0; for (i=0; iPrint("\t\tAdding[%i] fill, pri=%i\n", i, u.Priority); */ } else if (Max[i] > Min[i]) { UnderInfo &u = Unders.New(); u.Col = i; u.Grow = Max[i] - Min[i]; if (u.Grow > Avail) { u.Grow = 0; u.Priority = 2; UnknownGrow++; } else { u.Priority = u.Grow < Avail >> 1 ? 0 : 1; } /* if (Debug) Debug->Print("\t\tAdding[%i] grow, pri=%i\n", i, u.Priority); */ } } Unders.Sort([](auto a, auto b) { if (a->Priority != b->Priority) return a->Priority - b->Priority; return b->Grow - a->Grow; }); int UnknownSplit = 0; for (i=0; Avail>0 && iPrint("\t\tGrow[%i] %i += %i\n", i, Min[u.Col], u.Grow); */ Min[u.Col] += u.Grow; Avail -= u.Grow; } else { if (!UnknownSplit) UnknownSplit = Avail / UnknownGrow; if (!UnknownSplit) UnknownSplit = Avail; /* if (Debug) Debug->Print("\t\tFill[%i] %i += %i\n", i, Min[u.Col], UnknownSplit); */ Min[u.Col] += UnknownSplit; Avail -= UnknownSplit; } } } static void DistributeSize( LArray &a, LArray &Flags, int Start, int Span, int Size, int Border, LStream *Debug = NULL) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i= Size) return; // Get list of growable cells LArray Grow, Fill, Fixed, Unknown; int ExistingGrowPx = 0; int ExistingFillPx = 0; int UnknownPx = 0; for (int i=Start; i 0 && Unknown.Length() > 0) { // Distribute size amongst the unknown cells int AdditionalSize = Size - Cur; for (int i=0; i Children; LCss::DisplayType Disp; LString ClassName; TableCell(LTableLayout *t, int Cx, int Cy); LTableLayout *GetTable() override { return Table; } bool Add(LView *v) override; bool Remove(LView *v) override; LArray GetChildren() override; bool RemoveAll(); Child *HasView(LView *v); LStream &Log(); bool IsSpanned(); bool GetVariant(const char *Name, LVariant &Value, const char *Array) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array) override; int MaxCellWidth(); /// Calculates the minimum and maximum widths this cell can occupy. void LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag); /// Calculate the height of the cell based on the given width void LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags); /// Called after the layout has been done to move the controls into place void LayoutPost(int Depth); void OnPaint(LSurface *pDC); void OnChange(PropType Prop) override; }; class LTableLayoutPrivate { friend class TableCell; bool InLayout; bool DebugLayout; public: LPoint PrevSize, Dpi; bool LayoutDirty; LArray Rows, Cols; LArray Cells; int BorderSpacing; LRect LayoutBounds; int LayoutMinX, LayoutMaxX; LTableLayout *Ctrl; LStream &Log() { return PrintfLogger; } // Object LTableLayoutPrivate(LTableLayout *ctrl); ~LTableLayoutPrivate(); // Utils bool IsInLayout() { return InLayout; } TableCell *GetCellAt(int cx, int cy); void Empty(LRect *Range = NULL); bool CollectRadioButtons(LArray &Btns); void InitBorderSpacing(); double Scale() { if (!Dpi.x) { auto Wnd = Ctrl->GetWindow(); if (Wnd) Dpi = Wnd->GetDpi(); else Dpi.x = Dpi.y = 96; } LAssert(Dpi.x > 0); return Dpi.x / 96.0; } // Layout temporary values LArray MinCol, MaxCol; LArray MinRow, MaxRow; LArray ColFlags, RowFlags; // Layout staged methods, call in order to complete the layout void LayoutHorizontal(const LRect Client, int Depth, int *MinX = NULL, int *MaxX = NULL, CellFlag *Flag = NULL); void LayoutVertical(const LRect Client, int Depth, int *MinY = NULL, int *MaxY = NULL, CellFlag *Flag = NULL); void LayoutPost(const LRect Client, int Depth); // This does the whole layout, basically calling all the stages for you void Layout(const LRect Client, int Depth); }; /////////////////////////////////////////////////////////////////////////////////////////// TableCell::TableCell(LTableLayout *t, int Cx, int Cy) { TextAlign(AlignLeft); VerticalAlign(VerticalTop); Table = t; Cell.ZOff(0, 0); Cell.Offset(Cx, Cy); Padding.ZOff(0, 0); Pos.ZOff(-1, -1); Disp = LCss::DispBlock; Children.SetFixedLength(true); } void TableCell::OnChange(PropType Prop) { if (Prop == PropDisplay) { bool Vis = Display() != LCss::DispNone; for (auto c: Children) c.View->Visible(Vis); } } LStream &TableCell::Log() { /* if (!TopLevelLog) { // Find the top most table layout LTableLayout *Top = Table; for (LViewI* t = Top; t; t = t->GetParent()) { auto tbl = dynamic_cast(t); if (tbl) Top = tbl; if (t->GetId() == 24) break; } TopLevelLog = &Top->d->Dbg; } LAssert(TopLevelLog != NULL); return *TopLevelLog; */ return PrintfLogger; } TableCell::Child *TableCell::HasView(LView *v) { size_t Len = Children.Length(); if (!Len) return NULL; Child *s = &Children[0]; Child *e = s + Len; while (s < e) { if (s->View == v) return s; s++; } return NULL; } LArray TableCell::GetChildren() { LArray a; for (auto &c: Children) a.Add(c.View); return a; } bool TableCell::Add(LView *v) { if (!v || HasView(v)) return false; if (Table->IsAttached()) v->Attach(Table); else Table->AddView(v); Children.SetFixedLength(false); Children.New().View = v; Children.SetFixedLength(true); return true; } bool TableCell::Remove(LView *v) { Child *c = HasView(v); if (!c) return false; Table->DelView(v); if (Children.Length()) { int Idx = (int) (c - &Children.First()); Children.DeleteAt(Idx, true); } return true; } bool TableCell::RemoveAll() { Child *c = &Children[0]; for (unsigned i=0; i 1 || Cell.Y() > 1; } bool TableCell::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (!Value.SetList()) return false; for (unsigned i=0; iType = GV_GVIEW; v->Value.View = c.View; Value.Value.Lst->Insert(v); } } break; } case ContainerSpan: // Type: LRect { Value = Cell.GetStr(); break; } case ContainerAlign: // Type: String { LStringPipe p(128); if (TextAlign().ToString(p)) Value.OwnStr(p.NewStr()); else return false; break; } case ContainerVAlign: // Type: String { LStringPipe p(128); if (VerticalAlign().ToString(p)) Value.OwnStr(ToString()); else return false; break; } case ObjClass: // Type: String { Value = ClassName; break; } case ObjStyle: // Type: String { Value.OwnStr(ToString()); break; } case ObjDebug: // Type: Boolean { Value = Debug; break; } default: return false; } return true; } bool TableCell::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (Value.Type != GV_LIST) { LAssert(!"Should be a list."); return false; } // LProfile p("ContainerChildren"); for (auto v: *Value.Value.Lst) { if (v->Type != GV_VOID_PTR) continue; ResObject *o = (ResObject*)v->Value.Ptr; if (!o) continue; LView *gv = dynamic_cast(o); if (!gv) continue; // p.Add("c1"); Children.SetFixedLength(false); Children.New().View = gv; Children.SetFixedLength(true); // p.Add("c2"); Table->AddView(gv); // p.Add("c3"); gv->SetParent(Table); // p.Add("c4"); LTextLabel *t = dynamic_cast(gv); if (t) t->SetWrap(true); } break; } case ContainerSpan: { LRect r; if (r.SetStr(Value.Str())) Cell = r; else return false; break; } case ContainerAlign: { TextAlign ( Len ( ConvertAlign(Value.Str(), true) ) ); break; } case ContainerVAlign: { VerticalAlign ( Len ( ConvertAlign(Value.Str(), false) ) ); break; } case ObjClass: { ClassName = Value.Str(); LResources *r = LgiGetResObj(); if (r) { LCss::SelArray *a = r->CssStore.ClassMap.Find(ClassName); if (a) { for (int i=0; iLength(); i++) { LCss::Selector *s = (*a)[i]; // This is not exactly a smart matching algorithm. if (s && s->Parts.Length() == 1) { const char *style = s->Style; Parse(style, ParseRelaxed); } } } } break; } case ObjStyle: { const char *style = Value.Str(); if (style) Parse(style, ParseRelaxed); break; } case ObjDebug: { Debug = Value.CastInt32() != 0; break; } default: return false; } return true; } int TableCell::MaxCellWidth() { // Table size minus padding LCssTools t(Table->GetCss(), Table->GetFont()); LRect cli = Table->GetClient(); cli = t.ApplyPadding(cli); // Work out any borders on spanned cells... int BorderPx = Cell.X() > 1 ? (Cell.X() - 1) * Table->d->BorderSpacing : 0; // Return client - padding - border size. return cli.X() - BorderPx; } /// Calculates the minimum and maximum widths this cell can occupy. void TableCell::LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag) { int MaxBtnX = 0; int TotalBtnX = 0; int Min = 0, Max = 0; Pos = Pos.ZeroTranslate(); // Calculate CSS padding #define CalcCssPadding(Prop, Axis, Edge) \ { \ Len l = Prop(); \ if (l.Type == LCss::LenInherit) l = LCss::Padding(); \ if (l.Type) \ Padding.Edge = l.ToPx(Table->Axis(), Table->GetFont()); \ else \ Padding.Edge = 0; \ } Disp = Display(); if (Disp == DispNone) return; CalcCssPadding(PaddingLeft, X, x1) CalcCssPadding(PaddingRight, X, x2) CalcCssPadding(PaddingTop, Y, y1) CalcCssPadding(PaddingBottom, Y, y2) // Cell CSS size: auto Wid = Width(); auto MinWid = MinWidth(); auto MaxWid = MaxWidth(); auto Fnt = Table->GetFont(); int Tx = Table->X(); if (MinWid.IsValid()) Min = MAX(Min, MinWid.ToPx(Tx, Fnt)); if (Wid.IsValid()) { if (Wid.Type == LCss::LenAuto) Flag = SizeFill; else { // If we set Min here too the console app breaks because the percentage // is over-subscribe (95%) to force one cell to grow to fill available space. Max = Wid.ToPx(Tx, Fnt) - Padding.x1 - Padding.x2; if (!Wid.IsDynamic()) { Flag = SizeFixed; Min = Max; /* This is breaking normal usage, where 'Min' == 0. Need to redesign for edge case. if (Padding.x1 + Padding.x2 > Min) { // Remove padding as it's going to oversize the cell Padding.x1 = Padding.x2 = 0; } */ } else { Flag = SizeGrow; } } } if (!Wid.IsValid() || Flag != SizeFixed) { Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; ZeroObj(c->Inf); c->r = v->GetPos().ZeroTranslate(); // Child view CSS size: LCss *Css = v->GetCss(); LCss::Len ChildWid; // LCss::Len ChildMin, ChildMax; if (Css) { ChildWid = Css->Width(); // ChildMin = Css->MinWidth(); // ChildMax = Css->MaxWidth(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (ChildWid.IsValid()) { int MaxPx = MaxCellWidth(); c->Inf.Width.Min = c->Inf.Width.Max = ChildWid.ToPx(MaxPx, v->GetFont()); Min = MAX(Min, c->Inf.Width.Min); Max = MAX(Max, c->Inf.Width.Min); c->r = v->GetPos(); c->r.x2 = c->r.x1 + c->Inf.Width.Min - 1; } else if (v->OnLayout(c->Inf)) { if (c->Inf.Width.Max < 0) { if (Flag != SizeFixed) Flag = SizeFill; } else if (Max) Max += c->Inf.Width.Max + LTableLayout::CellSpacing; else Max = c->Inf.Width.Max; if (c->Inf.Width.Min) { Min = MAX(Min, c->Inf.Width.Min); if (c->Inf.Width.Max > c->Inf.Width.Min && Flag == SizeUnknown) Flag = SizeGrow; } } else if (Izza(LButton)) { LDisplayString ds(v->GetFont(), v->Name()); auto Scale = Table->d->Scale(); LPoint Pad((int)(Scale * LButton::Overhead.x + 0.5), (int)(Scale * LButton::Overhead.y + 0.5)); c->Inf.Width.Min = c->Inf.Width.Max = ds.X() + Pad.x; c->Inf.Height.Min = c->Inf.Height.Max = ds.Y() + Pad.y; MaxBtnX = MAX(MaxBtnX, c->Inf.Width.Min); TotalBtnX = TotalBtnX ? TotalBtnX + LTableLayout::CellSpacing + c->Inf.Width.Min : c->Inf.Width.Min; if (Flag < SizeFixed) Flag = SizeFixed; } else if (Izza(LEdit) || Izza(LScrollBar)) { Min = MAX(Min, 40); if (Flag != SizeFixed) Flag = SizeFill; } else if (Izza(LCombo)) { int PadX = LCombo::Pad.x1 + LCombo::Pad.x2; LCombo *Cbo = Izza(LCombo); LFont *f = Cbo->GetFont(); int min_x = -1, max_x = 0; char *t; for (int i=0; i < Cbo->Length() && (t = (*Cbo)[i]); i++) { LDisplayString ds(f, t); int x = ds.X(); min_x = min_x < 0 ? x : MIN(min_x, x); max_x = MAX(x + 4, max_x); } Min = MAX(Min, min_x + PadX); Max = MAX(Max, max_x + PadX); if (Flag < SizeGrow) Flag = SizeGrow; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { Min = MAX(Min, Dc->X() + 4); Max = MAX(Max, Dc->X() + 4); } else { Min = MAX(Min, 16); Max = MAX(Max, 16); if (Flag < SizeFill) Flag = SizeFill; } } else if (Izza(LList)) { LList *Lst = Izza(LList); int m = 0; for (int i=0; iGetColumns(); i++) { m += Lst->ColumnAt(i)->Width(); } m = MAX(m, 40); Min = MAX(Min, 40); Flag = SizeFill; } else if (Izza(LTree) || Izza(LTabView)) { Min = MAX(Min, 40); Flag = SizeFill; } else { LTableLayout *Tbl = Izza(LTableLayout); if (Tbl) { Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(Table->GetClient(), Depth+1, &Min, &Max, &Flag); if (Wid.IsDynamic()) { if (Min > Max) Min = Max; } } else { Min = MAX(Min, v->X()); Max = MAX(Max, v->X()); } } } } if (MaxBtnX) { Min = MAX(Min, MaxBtnX); Max = MAX(Max, TotalBtnX); } if (MaxWid.IsValid()) { int Tx = Table->X(); int Px = MaxWid.ToPx(Tx, Table->GetFont()) - Padding.x1 - Padding.x2; if (Min > Px) Min = Px; if (Max > Px) Max = Px; } MinX = MAX(MinX, Min + Padding.x1 + Padding.x2); MaxX = MAX(MaxX, Max + Padding.x1 + Padding.x2); } /// Calculate the height of the cell based on the given width void TableCell::LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags) { Pos.ZOff(Width-1, 0); if (Disp == DispNone) return; Len Ht = Height(); if (Ht.Type != LenInherit) { if (Ht.IsDynamic()) { MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); if (Flags < SizeGrow) Flags = SizeGrow; } else { MinY = MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); Pos.y2 = MinY - 1; if (Flags < SizeFixed) Flags = SizeFixed; return; } } LPoint Cur(Pos.x1, Pos.y1); int NextY = Pos.y1; LCss::Len CssWidth = LCss::Width(); Width -= Padding.x1 + Padding.x2; LAssert(Width >= 0); Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (CssWidth.Type != LenInherit) { // In the case where the CSS width is set, we need to default the // OnLayout info with valid widths otherwise the OnLayout calls will // not fill out the height, but initialize the width instead. // Without this !zero check the Bayesian Filter setting dialog in Scribe // has the wrong Help button size. ie if the PreLayout step gave a valid // layout size, don't override it here. if (!c->Inf.Width.Min) c->Inf.Width.Min = Width; if (!c->Inf.Width.Max) c->Inf.Width.Max = Width; } LTableLayout *Tbl = NULL; LCss *Css = v->GetCss(); LCss::Len Ht; if (Css) Ht = Css->Height(); if (!Izza(LButton) && i) Pos.y2 += Table->d->BorderSpacing; if (c->Inf.Width.Min > Width) c->Inf.Width.Min = Width; if (c->Inf.Width.Max > Width) c->Inf.Width.Max = Width; if (Ht.IsValid()) { int CtrlHeight = Ht.ToPx(Table->Y(), v->GetFont()); c->Inf.Height.Min = c->Inf.Height.Max = CtrlHeight; if (MaxY < CtrlHeight) MaxY = CtrlHeight; if (!Ht.IsDynamic() && MinY < CtrlHeight) MinY = CtrlHeight; c->r = v->GetPos().ZeroTranslate(); c->r.y2 = c->r.y1 + CtrlHeight - 1; Pos.y2 = MAX(Pos.y2, c->r.Y()-1); } else if (v->OnLayout(c->Inf)) { // Supports layout info c->r = v->GetPos(); // Process height if (c->Inf.Height.Max < 0) Flags = SizeFill; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; // Process width if (c->Inf.Width.Max < 0) c->r.x2 = c->r.x1 + Width - 1; else c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; if (Cur.x > Pos.x1 && Cur.x + c->r.X() > Pos.x2) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x - c->r.x1, Cur.y - c->r.y1); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); c->IsLayout = true; } else if (Izza(LScrollBar)) { Pos.y2 += 15; } else if (Izza(LButton)) { c->r.ZOff(c->Inf.Width.Min-1, c->Inf.Height.Min-1); if (Cur.x + c->r.X() > Width) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x, Cur.y); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); } else if (Izza(LEdit) || Izza(LCombo)) { LFont *f = v->GetFont(); int y = (f ? f : LSysFont)->GetHeight() + 8; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; if (Izza(LEdit) && Izza(LEdit)->MultiLine()) { Flags = SizeFill; // MaxY = MAX(MaxY, 1000); } } else if (Izza(LRadioButton)) { int y = v->GetFont()->GetHeight() + 2; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; } else if (Izza(LList) || Izza(LTree) || Izza(LTabView)) { Pos.y2 += v->GetFont()->GetHeight() + 8; // MaxY = MAX(MaxY, 1000); Flags = SizeFill; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { MaxY = MAX(MaxY, Dc->Y() + 4); } else { MaxY = MAX(MaxY, 1000); } } else if ((Tbl = Izza(LTableLayout))) { auto Children = Tbl->IterateViews(); c->r.ZOff(Width-1, Table->Y()-1); LCssTools tools(Tbl->GetCss(), Tbl->GetFont()); auto client = tools.ApplyBorder(c->r); Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(client, Depth+1); Tbl->d->LayoutVertical(client, Depth+1, &MinY, &MaxY, &Flags); Tbl->d->LayoutPost(client, Depth+1); Pos.y2 += MinY; c->Inf.Height.Min = MinY; c->Inf.Height.Max = MaxY; } else { // Doesn't support layout info Pos.y2 += v->Y(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); } #endif } // Fix: This if statement is needed to stop LFileSelect dialogs only growing in size, and // the Ok/Cancel shifting off the bottom of the dialog if you shrink the window. if (Flags != SizeFill) { MinY = MAX(MinY, Pos.Y() + Padding.y1 + Padding.y2); MaxY = MAX(MaxY, Pos.Y() + Padding.y1 + Padding.y2); } } /// Called after the layout has been done to move the controls into place void TableCell::LayoutPost(int Depth) { int Cx = Padding.x1; int Cy = Padding.y1; int MaxY = Padding.y1; int RowStart = 0; LArray New; int WidthPx = Pos.X() - Padding.x1 - Padding.x2; int HeightPx = Pos.Y() - Padding.y1 - Padding.y2; #ifdef DEBUG_CTRL_ID bool HasDebugCtrl = false; #endif Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; if (Disp == DispNone) { v->Visible(false); continue; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s padding=%s cur=%i,%i (%s:%i)\n", v->GetId(), c->r.GetStr(), Padding.GetStr(), Cx, Cy, _FL); } #endif LTableLayout *Tbl = Izza(LTableLayout); if (i > 0 && Cx + c->r.X() > Pos.X()) { // Do wrapping int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } for (int n=RowStart; n<=i; n++) { New[n].Offset(OffsetX, 0); } RowStart = i + 1; Cx = Padding.x1; Cy = MaxY + Table->d->BorderSpacing; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i offset %i %i %i, %i %i %i %s:%i\n", v->GetId(), Pos.x1, c->r.x1, Cx, Pos.y1, c->r.y1, Cy, _FL); } #endif c->r.Offset(Pos.x1 - c->r.x1 + Cx, Pos.y1 - c->r.y1 + Cy); if (c->Inf.Width.Max >= WidthPx) c->Inf.Width.Max = WidthPx; if (c->Inf.Height.Max >= HeightPx) c->Inf.Height.Max = HeightPx; if (c->r.Y() > HeightPx) { c->r.y2 = c->r.y1 + HeightPx - 1; } if (Tbl) { c->r.SetSize(Pos.X(), MIN(Pos.Y(), c->Inf.Height.Min)); } else if ( Izza(LList) || Izza(LTree) || Izza(LTabView) || (Izza(LEdit) && Izza(LEdit)->MultiLine()) ) { c->r.y2 = Pos.y2; if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } else if (c->IsLayout) { if (c->Inf.Height.Max < 0) c->r.y2 = Pos.y2; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; } else { if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } New[i] = c->r; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s (%s:%i)\n", v->GetId(), c->r.GetStr(), _FL); } #endif MaxY = MAX(MaxY, c->r.y2 - Pos.y1); Cx += c->r.X() + Table->d->BorderSpacing; } if (Disp == DispNone) { return; } int n; int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } if (OffsetX) { for (n=RowStart; nd->DebugLayout) { Log().Print("\tCell[%i,%i]=%s (%ix%i)\n", Cell.x1, Cell.y1, Pos.GetStr(), Pos.X(), Pos.Y()); } #endif for (n=0; nGetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i %s[%i]=%s, %ix%i, Offy=%i %s (%s:%i)\n", v->GetId(), v->GetClass(), n, New[n].GetStr(), New[n].X(), New[n].Y(), OffsetY, v->Name(), _FL); } #endif v->SetPos(New[n]); } } void TableCell::OnPaint(LSurface *pDC) { LCssTools t(this, Table->GetFont()); LRect r = Pos; t.PaintBorder(pDC, r); LColour Trans; auto bk = t.GetBack(&Trans); if (bk.IsValid()) { pDC->Colour(bk); pDC->Rectangle(&r); } } //////////////////////////////////////////////////////////////////////////// LTableLayoutPrivate::LTableLayoutPrivate(LTableLayout *ctrl) { PrevSize.Set(-1, -1); LayoutDirty = true; InLayout = false; DebugLayout = false; Ctrl = ctrl; BorderSpacing = LTableLayout::CellSpacing; LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } LTableLayoutPrivate::~LTableLayoutPrivate() { Empty(); } bool LTableLayoutPrivate::CollectRadioButtons(LArray &Btns) { for (LViewI *i: Ctrl->IterateViews()) { LRadioButton *b = dynamic_cast(i); if (b) Btns.Add(b); } return Btns.Length() > 0; } void LTableLayoutPrivate::Empty(LRect *Range) { if (Range) { // Clear a range of cells.. for (int i=0; iOverlap(&c->Cell)) { c->RemoveAll(); Cells.DeleteAt(i--, true); DeleteObj(c); } } } else { // Clear all the cells Ctrl->IterateViews().DeleteObjects(); Cells.DeleteObjects(); Rows.Length(0); Cols.Length(0); } PrevSize.Set(-1, -1); LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } TableCell *LTableLayoutPrivate::GetCellAt(int cx, int cy) { for (int i=0; iCell.Overlap(cx, cy)) return Cells[i]; return NULL; } void LTableLayoutPrivate::LayoutHorizontal(const LRect Client, int Depth, int *MinX, int *MaxX, CellFlag *Flag) { // This only gets called when you nest LTableLayout controls. It's // responsible for doing pre layout stuff for an entire control of cells. int Cx, Cy, i; LString::Array Ps; Ps.SetFixedLength(false); LAutoPtr Prof(/*Debug ? new LProfile("Layout") :*/ NULL); // Zero everything to start with MinCol.Length(0); MaxCol.Length(0); MinRow.Length(0); MaxRow.Length(0); ColFlags.Length(0); RowFlags.Length(0); #if DEBUG_LAYOUT if (DebugLayout) { int asd=0; } #endif // Do pre-layout to determine minimum and maximum column widths for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() == 1) { if (Prof) { LString &s = Ps.New(); s.Printf("pre layout %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } int &MinC = MinCol[Cx]; int &MaxC = MaxCol[Cx]; CellFlag &ColF = ColFlags[Cx]; c->LayoutWidth(Depth, MinC, MaxC, ColF); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { Log().Print("\tLayout Id=%i, Size=%i,%i (%s:%i)\n", Ctrl->GetId(), Client.X(), Client.Y(), _FL); for (i=0; iAdd("Pre layout spanned"); // Pre-layout column width for spanned cells for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() > 1) { int Min = 0, Max = 0; CellFlag Flag = SizeUnknown; if (Prof) { LString &s = Ps.New(); s.Printf("spanned %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } if (c->Width().IsValid()) { LCss::Len l = c->Width(); if (l.Type == LCss::LenAuto) { for (int i=c->Cell.x1; i<=c->Cell.x2; i++) { ColFlags[i] = SizeFill; } } else { int Px = l.ToPx(Client.X(), Ctrl->GetFont());; if (l.IsDynamic()) { c->LayoutWidth(Depth, Min, Max, Flag); } else { Min = Max = Px; } } } else { c->LayoutWidth(Depth, Min, Max, Flag); } // Log().Print("Spanned cell: %i,%i\n", Min, Max); if (Max > Client.X()) Max = Client.X(); if (Flag) { bool HasFlag = false; bool HasUnknown = false; for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (ColFlags[i] == Flag) { HasFlag = true; } else if (ColFlags[i] == SizeUnknown) { HasUnknown = true; } } if (!HasFlag) { for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (HasUnknown) { if (ColFlags[i] == SizeUnknown) ColFlags[i] = Flag; } else { if (ColFlags[i] < Flag) ColFlags[i] = Flag; } } } } // This is the total size of all the px currently allocated int AllPx = CountRange(MinCol, 0, Cols.Length()-1) + (((int)Cols.Length() - 1) * BorderSpacing); // This is the minimum size of this cell's cols int MyPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int Remaining = Client.X() - AllPx; // Log().Print("AllPx=%i MyPx=%i, Remaining=%i\n", AllPx, MyPx, Remaining); // This is the total remaining px we could add... if (Remaining > 0) { // Limit the max size of this cell to the existing + remaining px Max = MIN(Max, MyPx + Remaining); // Distribute the max px across the cell's columns. DistributeSize(MinCol, ColFlags, c->Cell.x1, c->Cell.X(), Min, BorderSpacing); DistributeSize(MaxCol, ColFlags, c->Cell.x1, c->Cell.X(), Max, BorderSpacing); } } Cx += c->Cell.X(); } else Cx++; } } LayoutMinX = -BorderSpacing; LayoutMaxX = -BorderSpacing; for (i=0; iAdd("DistributeUnusedSpace"); DistributeUnusedSpace(MinCol, MaxCol, ColFlags, Client.X(), BorderSpacing, DebugLayout?&Log():NULL); #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iAdd("Collect together our sizes"); int Spacing = BorderSpacing * ((int)MinCol.Length() - 1); auto Css = Ctrl->GetCss(); if (MinX) { int x = CountRange(MinCol, 0, MinCol.Length()-1) + Spacing; *MinX = MAX(*MinX, x); if (Css) { auto MinWid = Css->MinWidth(); if (MinWid.IsValid()) { int px = MinWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MinX = MAX(*MinX, px); } } } if (MaxX) { int x = CountRange(MaxCol, 0, MinCol.Length()-1) + Spacing; *MaxX = MAX(*MaxX, x); if (Css) { auto MaxWid = Css->MaxWidth(); if (MaxWid.IsValid()) { int px = MaxWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MaxX = MIN(*MaxX, px); } } } if (Flag) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() == 1) { int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int &Min = MinRow[Cy]; int &Max = MaxRow[Cy]; CellFlag &Flags = RowFlags[Cy]; c->LayoutHeight(Depth, x, Min, Max, Flags); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() > 1) { int WidthPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int InitMinY = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); int InitMaxY = CountRange(MaxRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); //int AllocY = CountRange(MinRow, 0, Rows.Length()-1) + ((Rows.Length() - 1) * BorderSpacing); int MinY = InitMinY; int MaxY = InitMaxY; // int RemainingY = Client.Y() - AllocY; CellFlag RowFlag = SizeUnknown; c->LayoutHeight(Depth, WidthPx, MinY, MaxY, RowFlag); // This code stops the max being set on spanned cells. LArray AddTo; for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if ( RowFlags[y] != SizeFixed && ( RowFlags[y] != SizeGrow || MaxRow[y] > MinRow[y] ) ) AddTo.Add(y); } if (AddTo.Length() == 0) { for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if (!AddTo.HasItem(y)) { if (RowFlags[y] != SizeFixed) AddTo.Add(y); } } } if (AddTo.Length()) { if (MinY > InitMinY) { // Allocate any extra min px somewhere.. int Amt = (MinY - InitMinY) / (int)AddTo.Length(); for (int i=0; i InitMaxY) { // Allocate any extra max px somewhere.. int Amt = (MaxY - InitMaxY) / (int)AddTo.Length(); for (int i=0; i SizeUnknown) { // Apply the size flag somewhere... for (int y=c->Cell.y2; y>=c->Cell.y1; y++) { if (RowFlags[y] == SizeUnknown) { RowFlags[y] = SizeFill; break; } } } } else { // Last chance... stuff extra px in last cell... MaxRow[c->Cell.y2] = MAX(MaxY, MaxRow[c->Cell.y2]); } } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iGetCss(); if (MinY) { int y = CountRange(MinRow, 0, MinRow.Length()-1) + (((int)MinRow.Length()-1) * BorderSpacing); *MinY = MAX(*MinY, y); if (Css) { auto MinHt = Css->MinHeight(); if (MinHt.IsValid()) { auto px = MinHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MinY = MAX(*MinY, px); } } } if (MaxY) { int y = CountRange(MaxRow, 0, MinRow.Length()-1) + (((int)MaxRow.Length()-1) * BorderSpacing); *MaxY = MAX(*MaxY, y); if (Css) { auto MaxHt = Css->MaxHeight(); if (MaxHt.IsValid()) { auto px = MaxHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MaxY = MIN(*MaxY, px); } } } if (Flag) { for (i=0; iGetFont(); // Move cells into their final positions #if DEBUG_LAYOUT if (DebugLayout && Cols.Length() == 7) Log().Print("\tLayoutPost %ix%i\n", Cols.Length(), Rows.Length()); #endif for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy) { LCss::PositionType PosType = c->Position(); int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int y = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); // Set the height of the cell c->Pos.x2 = c->Pos.x1 + x - 1; c->Pos.y2 = c->Pos.y1 + y - 1; if (PosType == LCss::PosAbsolute) { // Hmm this is a bit of a hack... we'll see LCss::Len Left = c->Left(); LCss::Len Top = c->Top(); int LeftPx = Left.IsValid() ? Left.ToPx(Client.X(), Fnt) : Px; int TopPx = Top.IsValid() ? Top.ToPx(Client.Y(), Fnt) : Py; c->Pos.Offset(Client.x1 + LeftPx, Client.y1 + TopPx); } else { c->Pos.Offset(Client.x1 + Px, Client.y1 + Py); #if 0//def DEBUG_CTRL_ID Log().Print("\t\tdbgCtrl=%i Client=%s pos=%s p=%i,%i (%s:%i)\n", DEBUG_CTRL_ID, Client.GetStr(), c->Pos.GetStr(), Px, Py, _FL); #endif } c->LayoutPost(Depth); MaxY = MAX(MaxY, c->Pos.y2); #if DEBUG_LAYOUT if (DebugLayout) { c->Log().Print("\tCell[%i][%i]: %s %s\n", Cx, Cy, c->Pos.GetStr(), Client.GetStr()); } #endif } Px = c->Pos.x2 + BorderSpacing - Client.x1 + 1; Cx += c->Cell.X(); } else { Px = MinCol[Cx] + BorderSpacing; Cx++; } } Py += MinRow[Cy] + BorderSpacing; } LayoutBounds.ZOff(Px-1, Py-1); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("\tLayoutBounds: %s\n", LayoutBounds.GetStr()); #endif } void LTableLayoutPrivate::InitBorderSpacing() { BorderSpacing = LTableLayout::CellSpacing; if (Ctrl->GetCss()) { LCss::Len bs = Ctrl->GetCss()->BorderSpacing(); if (bs.Type != LCss::LenInherit) BorderSpacing = bs.ToPx(Ctrl->X(), Ctrl->GetFont()); } } void LTableLayoutPrivate::Layout(const LRect Client, int Depth) { if (InLayout) { LAssert(!"In layout, no recursion should happen."); return; } InLayout = true; InitBorderSpacing(); #if DEBUG_LAYOUT int CtrlId = Ctrl->GetId(); // auto CtrlChildren = Ctrl->IterateViews(); DebugLayout = CtrlId == DEBUG_LAYOUT && Ctrl->IterateViews().Length() > 0; if (DebugLayout) { int asd=0; } #endif #if DEBUG_PROFILE int64 Start = LCurrentTime(); #endif LString s; s.Printf("Layout id %i: %i x %i", Ctrl->GetId(), Client.X(), Client.Y()); LAutoPtr Prof(/*Debug ? new LProfile(s) :*/ NULL); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("%s\n", s.Get()); #endif if (Prof) Prof->Add("Horz"); LayoutHorizontal(Client, Depth); if (Prof) Prof->Add("Vert"); LayoutVertical(Client, Depth); if (Prof) Prof->Add("Post"); LayoutPost(Client, Depth); if (Prof) Prof->Add("Notify"); #if DEBUG_PROFILE Log().Print("LTableLayout::Layout(%i) = %i ms\n", Ctrl->GetId(), (int)(LCurrentTime()-Start)); #endif InLayout = false; Ctrl->SendNotify(LNotifyTableLayoutChanged); } LTableLayout::LTableLayout(int id) : ResObject(Res_Table) { d = new LTableLayoutPrivate(this); SetPourLargest(true); Name("LTableLayout"); SetId(id); } LTableLayout::~LTableLayout() { DeleteObj(d); } void LTableLayout::OnFocus(bool b) { if (b) { LViewI *v = GetNextTabStop(this, false); if (v) v->Focus(true); } } void LTableLayout::OnCreate() { LResources::StyleElement(this); AttachChildren(); } int LTableLayout::CellX() { return (int)d->Cols.Length(); } int LTableLayout::CellY() { return (int)d->Rows.Length(); } LLayoutCell *LTableLayout::CellAt(int x, int y) { return d->GetCellAt(x, y); } bool LTableLayout::SizeChanged() { LRect r = GetClient(); return r.X() != d->PrevSize.x || r.Y() != d->PrevSize.y; } void LTableLayout::OnPosChange() { LRect r = GetClient(); - bool Up = SizeChanged() || d->LayoutDirty; - // LgiTrace("%s:%i - Up=%i for Id=%i\n", _FL, Up, GetId()); + bool Up = SizeChanged() || d->LayoutDirty; + // LgiTrace("%s:%i - Up=%i for Id=%i\n", _FL, Up, GetId()); if (Up) { d->PrevSize.x = r.X(); d->PrevSize.y = r.Y(); if (d->PrevSize.x > 0) { if (GetCss()) { LCssTools t(GetCss(), GetFont()); r = t.ApplyBorder(r); r = t.ApplyPadding(r); } d->LayoutDirty = false; d->Layout(r, 0); } } } LRect LTableLayout::GetUsedArea() { if (SizeChanged()) { OnPosChange(); } LRect r(0, 0, -1, -1); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; if (i) r.Union(&c->Pos); else r = c->Pos; } return r; } void LTableLayout::InvalidateLayout() { if (!d->LayoutDirty) { d->LayoutDirty = true; for (auto p = GetParent(); p; p = p->GetParent()) { LTableLayout *t = dynamic_cast(p); if (t) t->InvalidateLayout(); } if (IsAttached()) PostEvent(M_TABLE_LAYOUT); } if (!IsAttached()) Invalidate(); } LMessage::Result LTableLayout::OnEvent(LMessage *m) { switch (m->Msg()) { case M_TABLE_LAYOUT: { OnPosChange(); Invalidate(); return 0; } } return LLayout::OnEvent(m); } void LTableLayout::OnPaint(LSurface *pDC) { if (SizeChanged() || d->LayoutDirty) { if (GetId() == 20) Log().Print("20 : clearing layout dirty LGI_VIEW_HANDLE=%i\n", LGI_VIEW_HANDLE); #if LGI_VIEW_HANDLE if (!Handle()) #endif OnPosChange(); #if LGI_VIEW_HANDLE else if (PostEvent(M_TABLE_LAYOUT)) return; else LAssert(!"Post event failed."); #endif if (GetId() == 20) Log().Print("20 : painting\n"); } d->Dpi = GetWindow()->GetDpi(); LCssTools Tools(this); LRect Client = GetClient(); Client = Tools.PaintBorder(pDC, Client); Tools.PaintContent(pDC, Client); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; c->OnPaint(pDC); } #if 0 // DEBUG_DRAW_CELLS pDC->Colour(LColour(255, 0, 0)); pDC->Box(); #endif #if DEBUG_DRAW_CELLS #if defined(DEBUG_LAYOUT) if (GetId() == DEBUG_LAYOUT) #endif { pDC->LineStyle(LSurface::LineDot); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; LRect r = c->Pos; pDC->Colour(c->Debug ? Rgb24(255, 222, 0) : Rgb24(192, 192, 222), 24); pDC->Box(&r); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); } pDC->LineStyle(LSurface::LineSolid); } #endif } bool LTableLayout::GetVariant(const char *Name, LVariant &Value, const char *Array) { return false; } bool ConvertNumbers(LArray &a, char *s) { for (auto &i: LString(s).SplitDelimit(",")) a.Add(i.Float()); return a.Length() > 0; } bool LTableLayout::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case TableLayoutCols: return ConvertNumbers(d->Cols, Value.Str()); case TableLayoutRows: return ConvertNumbers(d->Rows, Value.Str()); case ObjStyle: { const char *Defs = Value.Str(); if (Defs) GetCss(true)->Parse(Defs, LCss::ParseRelaxed); break; } case TableLayoutCell: { auto Coords = LString(Array).SplitDelimit(","); if (Coords.Length() != 2) return false; auto Cx = Coords[0].Int(); auto Cy = Coords[1].Int(); TableCell *c = new TableCell(this, (int)Cx, (int)Cy); if (!c) return false; d->Cells.Add(c); if (Value.Type == GV_VOID_PTR) { LDom **d = (LDom**)Value.Value.Ptr; if (d) *d = c; } break; } default: { LAssert(!"Unsupported property."); return false; } } return true; } void LTableLayout::OnChildrenChanged(LViewI *Wnd, bool Attaching) { InvalidateLayout(); if (Attaching) return; for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; for (int n=0; nChildren.Length(); n++) { if (c->Children[n].View == Wnd) { c->Children.DeleteAt(n); return; } } } } int LTableLayout::OnNotify(LViewI *c, LNotification n) { if (n.Type == LNotifyTableLayoutRefresh) { bool hasTableParent = false; for (LViewI *p = this; p; p = p->GetParent()) { auto tbl = dynamic_cast(p); if (tbl) { hasTableParent = true; tbl->d->LayoutDirty = true; tbl->Invalidate(); break; } } if (hasTableParent) { // One of the parent controls is a table layout, which when it receives this // notification will lay this control out too... so don't do it twice. // LgiTrace("%s:%i - Ignoring LNotifyTableLayoutRefresh because hasTableParent.\n", _FL); } SendNotify(LNotifyTableLayoutRefresh); return 0; } return LLayout::OnNotify(c, n); } int64 LTableLayout::Value() { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue()) return i; } } return -1; } void LTableLayout::Value(int64 v) { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue(i == v); } } void LTableLayout::Empty(LRect *Range) { d->Empty(Range); } LLayoutCell *LTableLayout::GetCell(int cx, int cy, bool create, int colspan, int rowspan) { TableCell *c = d->GetCellAt(cx, cy); if (!c && create) { c = new TableCell(this, cx, cy); if (c) { d->LayoutDirty = true; if (colspan > 1) c->Cell.x2 += colspan - 1; if (rowspan > 1) c->Cell.y2 += rowspan - 1; d->Cells.Add(c); while (d->Cols.Length() <= c->Cell.x2) d->Cols.Add(1); while (d->Rows.Length() <= c->Cell.y2) d->Rows.Add(1); } } return c; } LStream <ableLayout::Log() { return PrintfLogger; } diff --git a/src/common/Widgets/ToolBar.cpp b/src/common/Widgets/ToolBar.cpp --- a/src/common/Widgets/ToolBar.cpp +++ b/src/common/Widgets/ToolBar.cpp @@ -1,1758 +1,1759 @@ /* ** FILE: GToolbar.cpp ** AUTHOR: Matthew Allen ** DATE: 18/10/2001 ** DESCRIPTION: Toolbar classes ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Notifications.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/ToolBar.h" #include "lgi/common/ToolTip.h" #include "lgi/common/Menu.h" #define ToolBarHilightColour LC_HIGH #ifdef WIN32 -HPALETTE GetSystemPalette(); -bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); -bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); -bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop); + HPALETTE GetSystemPalette(); + bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); + bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); + bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop); -#define AttachButton(b) AddView(b); + #define AttachButton(b) AddView(b); #else -#define AttachButton(b) b->Attach(this); + #define AttachButton(b) b->Attach(this); #endif enum IconCacheType { IconNormal, IconHilight, IconDisabled }; COLOUR Map(LSurface *pDC, COLOUR c); //////////////////////////////////////////////////////////////////////// LImageList *LLoadImageList(const char *File, int x, int y) { if (!File) return NULL; if (x < 0 || y < 0) { // Detect dimensions in the filename. auto leaf = LString(File).Split(DIR_STR).Last(); auto parts = leaf.RSplit(".", 1); auto last = parts[0].Split("-").Last(); auto dim = last.Split("x"); if (dim.Length() == 1) { auto i = dim[0].Strip().Int(); if (i > 0) x = y = (int)i; } else if (dim.Length() == 2) { auto X = dim[0].Strip().Int(), Y = dim[1].Strip().Int(); if (X > 0 && Y > 0) { x = (int)X; y = (int)Y; } } } auto Path = LFileExists(File) ? LString(File) : LFindFile(File); if (!Path) { LgiTrace("%s:%i - Couldn't find '%s'\n", _FL, File); return NULL; } LAutoPtr pDC(GdcD->Load(Path)); if (!pDC) { LgiTrace("%s:%i - Couldn't load '%s'\n", _FL, Path.Get()); return NULL; } return new LImageList(x, y, pDC); } LToolBar *LgiLoadToolbar(LViewI *Parent, const char *File, int x, int y) { LToolBar *Toolbar = new LToolBar; if (!Toolbar) return NULL; LString FileName = LFindFile(File); if (FileName) { bool Success = FileName && Toolbar->SetBitmap(FileName, x, y); if (!Success) { LgiMsg(Parent, "Can't load '%s' for the toolbar.\n" "This is probably because libpng/libjpeg is missing.", "LgiLoadToolbar", MB_OK, File); } } else { LgiMsg(Parent, "Can't find the graphic '%s' for the toolbar.\n" "You can find it in this program's archive.", "LgiLoadToolbar", MB_OK, File); } return Toolbar; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// #define ImgLst_Empty 0x40000000 #define IgmLst_Add 0x80000000 class LImageListPriv { public: LImageList *ImgLst; int Sx, Sy; uint8_t DisabledAlpha; struct CacheDC : public LMemDC { bool Disabled; LColour Back; }; LArray Cache; LArray Bounds; CacheDC *GetCache(LColour Back, bool Disabled) { if (Back.IsTransparent()) return NULL; for (int i=0; iBack == Back && dc->Disabled == Disabled) return dc; } CacheDC *dc = new CacheDC; if (dc) { dc->Disabled = Disabled; dc->Back = Back; bool Status = dc->Create(ImgLst->X(), ImgLst->Y(), GdcD->GetColourSpace()); if (Status) { dc->Colour(dc->Back); dc->Rectangle(); dc->Op(GDC_ALPHA); if (Disabled) { LMemDC tmp(ImgLst->X(), ImgLst->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs); tmp.Colour(0, 32); tmp.Rectangle(); tmp.Op(GDC_ALPHA); tmp.Blt(0, 0, ImgLst); tmp.SetConstantAlpha(DisabledAlpha); dc->Blt(0, 0, &tmp); } else { dc->Blt(0, 0, ImgLst); } Cache.Add(dc); } else { delete dc; LAssert(!"Create memdc failed."); } } return dc; } LImageListPriv(LImageList *imglst, int x, int y) { ImgLst = imglst; Sx = x; Sy = y; DisabledAlpha = 40; } ~LImageListPriv() { Cache.DeleteObjects(); } }; static bool HasPad(LColourSpace cs) { if (cs == CsRgbx32 || cs == CsBgrx32 || cs == CsXrgb32 || cs == CsXbgr32) return true; return false; } LImageList::LImageList(int x, int y, LSurface *pDC) { d = new LImageListPriv(this, x, y); uint32_t Transparent = 0; if (pDC && Create(pDC->X(), pDC->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs)) { Colour(Transparent, 32); Rectangle(); int Old = Op(GDC_ALPHA); Blt(0, 0, pDC); Op(Old); #if 0 printf("Toolbar input image is %s, has_alpha=%i, has_pad=%i\n", LColourSpaceToString(pDC->GetColourSpace()), pDC->HasAlpha(), HasPad(pDC->GetColourSpace())); #endif + #if 0 + static int Idx = 0; + char s[256]; + + sprintf_s(s, sizeof(s), "imglst_%i.bmp", Idx++); + GdcD->Save(s, this); + + // sprintf_s(s, sizeof(s), "src_%i.bmp", Idx++); + // GdcD->Save(s, pDC); + #endif + if (pDC->GetBits() < 32 || HasPad(pDC->GetColourSpace())) { - // auto InCs = pDC->GetColourSpace(); - if (!pDC->HasAlpha()) { // No source alpha, do colour keying to create the alpha channel REG uint32_t *p = (uint32_t*)(*this)[0]; if (p) { uint32_t key = *p; for (int y=0; ya == 0) { p->r = 0; p->g = 0; p->b = 0; } p++; } } } - #if 0 - static int Idx = 0; - char s[256]; - sprintf_s(s, sizeof(s), "imglst_%i.bmp", Idx++); - WriteDC(s, this); - #endif + } } LImageList::~LImageList() { DeleteObj(d); } void LImageList::Draw(LSurface *pDC, int Dx, int Dy, int Image, LColour Background, bool Disabled) { if (!pDC) return; LRect rSrc; rSrc.ZOff(d->Sx-1, d->Sy-1); rSrc.Offset(Image * d->Sx, 0); LImageListPriv::CacheDC *Cache = d->GetCache(Background, Disabled); if (!Cache && Background.IsValid()) { LRect rDst; rDst.ZOff(d->Sx-1, d->Sy-1); rDst.Offset(Dx, Dy); pDC->Colour(Background); pDC->Rectangle(&rDst); pDC->Colour(LColour(255, 0, 0)); pDC->Line(rDst.x1, rDst.y1, rDst.x2, rDst.y2); pDC->Line(rDst.x2, rDst.y1, rDst.x1, rDst.y2); return; } - if (pDC->SupportsAlphaCompositing()) + if (pDC->SupportsAlphaCompositing() + #if HAIKU + && !Disabled // Constant alpha blt not supported (yet??!) + #endif + ) { int Old = pDC->Op(GDC_ALPHA, Disabled ? d->DisabledAlpha : -1); pDC->Blt(Dx, Dy, this, &rSrc); pDC->Op(Old); } else if (Cache) { pDC->Blt(Dx, Dy, Cache, &rSrc); } else LAssert(!"Impl me."); } int LImageList::TileX() { return d->Sx; } int LImageList::TileY() { return d->Sy; } int LImageList::GetItems() { return X() / d->Sx; } void LImageList::Update(int Flags) { } uint8_t LImageList::GetDisabledAlpha() { return d->DisabledAlpha; } void LImageList::SetDisabledAlpha(uint8_t alpha) { d->DisabledAlpha = alpha; } LRect LImageList::GetIconRect(int Idx) { LRect r(0, 0, -1, -1); if (Idx >= 0 && Idx < GetItems()) { r.ZOff(d->Sx-1, d->Sy-1); r.Offset(Idx * d->Sx, 0); } return r; } LRect *LImageList::GetBounds() { if (!d->Bounds.Length() && (*this)[0]) { int Items = GetItems(); if (d->Bounds.Length(Items)) { for (int i=0; iBounds[i].ZOff(d->Sx - 1, d->Sy - 1); d->Bounds[i].Offset(i * d->Sx, 0); LFindBounds(this, &d->Bounds[i]); d->Bounds[i].Offset(-i * d->Sx, 0); } } } return &d->Bounds[0]; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// class LToolBarPrivate { public: int Bx, By; int Sx, Sy; bool Vertical; bool Text; int LastIndex; bool OwnImgList; LImageList *ImgList; LFont *Font; LToolTip *Tip; // Customization menu LDom *CustomDom; const char *CustomProp; // bitmap cache LAutoPtr IconCache; LToolBarPrivate() { Bx = By = 16; Sx = Sy = 10; Vertical = false; Text = false; Font = 0; Tip = 0; CustomProp = 0; CustomDom = 0; } bool ShowTextLabels() { return (Text || !ImgList) && Font; } void FixSeparators(LToolBar *Tb) { // Fix up separators so that no 2 separators are next to each other. I.e. // all the buttons between them are switched off. LToolButton *Last = 0; bool HasVis = false; for (LViewI *v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) { if (Btn->Separator()) { Btn->Visible(HasVis); if (HasVis) { Last = Btn; } HasVis = false; } else { HasVis |= Btn->Visible(); } } } if (Last) { Last->Visible(HasVis); } } void Customizable(LToolBar *Tb) { LVariant v; if (CustomDom) { CustomDom->GetValue(CustomProp, v); } char *o; if ((o = v.Str())) { auto t = LString(o).SplitDelimit(","); if (t.Length() >= 1) { Text = stricmp(t[0], "text") == 0; // Make all controls not visible. for (auto v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) v->Visible(false); } // Set sub-set of ctrls visible according to saved ID list for (int i=1; i 0) Tb->SetCtrlVisible(Id, true); } FixSeparators(Tb); } } } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////// struct LToolButtonPriv { LArray Text; }; LToolButton::LToolButton(int Bx, int By) { d = new LToolButtonPriv; Type = TBT_PUSH; SetId(IDM_NONE); Down = false; Clicked = false; Over = false; ImgIndex = -1; NeedsRightClick = false; LRect r(0, 0, Bx+1, By+1); SetPos(r); SetParent(0); TipId = -1; _BorderSize = 0; LResources::StyleElement(this); } LToolButton::~LToolButton() { d->Text.DeleteObjects(); delete d; } bool LToolButton::Name(const char *n) { bool s = LView::Name(n); d->Text.DeleteObjects(); return s; } void LToolButton::Layout() { auto Parent = GetParent(); LToolBar *ToolBar = dynamic_cast(Parent); if (!ToolBar) return; // Text auto s = Name(); if (!ToolBar->d->ShowTextLabels() || !s) return; // Write each word centered on a different line char Buf[256]; strcpy_s(Buf, sizeof(Buf), s); auto t = LString(Buf).SplitDelimit(" "); if (t.Length() < 3) { if (t.Length() > 0) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); if (t.Length() > 1) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[1])); } else if (t.Length() == 3) { sprintf_s(Buf, sizeof(Buf), "%s %s", t[0].Get(), t[1].Get()); LDisplayString *d1 = new LDisplayString(ToolBar->d->Font, Buf); sprintf_s(Buf, sizeof(Buf), "%s %s", t[1].Get(), t[2].Get()); LDisplayString *d2 = new LDisplayString(ToolBar->d->Font, Buf); if (d1 && d2) { if (d1->X() < d2->X()) { DeleteObj(d2); d->Text.Add(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[2])); } else { DeleteObj(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); d->Text.Add(d2); } } } } void LToolButton::OnPaint(LSurface *pDC) { LToolBar *Par = dynamic_cast(GetParent()); bool e = Enabled(); - if (Par) + if (!Par) { - LRect p = GetClient(); - - #if 0 // def _DEBUG pDC->Colour(LColour(255, 0, 255)); - pDC->Rectangle(); - #endif + pDC->Box(); + return; + } + + LRect p = GetClient(); + + #if 0 // def _DEBUG + pDC->Colour(LColour(255, 0, 255)); + pDC->Rectangle(); + #endif - LCssTools Tools(this); - LColour cBack = Tools.GetBack(); - auto BackImg = Tools.GetBackImage(); - bool Hilight = e && Over; - if (Hilight) - cBack = cBack.Mix(LColour::White); + LCssTools Tools(this); + LColour cBack = Tools.GetBack(); + auto BackImg = Tools.GetBackImage(); + bool Hilight = e && Over; + if (Hilight) + cBack = cBack.Mix(LColour::White); + + // Draw Background + if (GetId() >= 0) + { + // Draw border + LColour Background; + if (Down) // Sunken if the button is pressed + LThinBorder(pDC, p, DefaultSunkenEdge); - // Draw Background - if (GetId() >= 0) + if (BackImg) { - // Draw border - LColour Background; - if (Down) // Sunken if the button is pressed - LThinBorder(pDC, p, DefaultSunkenEdge); + LDoubleBuffer Buf(pDC); + Tools.PaintContent(pDC, p); + if (Hilight) + { + // Draw translucent white over image... + pDC->Op(GDC_ALPHA); + pDC->Colour(LColour(255, 255, 255, 128)); + pDC->Rectangle(&p); + } + } + else + { + Background = cBack; + pDC->Colour(Background); + pDC->Box(&p); + p.Inset(1, 1); + } - if (BackImg) + LRect IconPos; + if (Par->d->ImgList) + IconPos.Set(0, 0, Par->d->ImgList->TileX()-1, Par->d->ImgList->TileY()-1); + else + IconPos.ZOff(Par->d->Bx-1, Par->d->By-1); + LRegion Unpainted(p); + + // Center the icon + if (IconPos.X() < p.X() - 1) + IconPos.Offset((p.X() - IconPos.X()) >> 1, 0); + // Offset it if the button is pressed + if (Down) + IconPos.Offset(2, 2); + + // Draw any icon. + if (ImgIndex >= 0) + { + if (Par->d->ImgList) { - LDoubleBuffer Buf(pDC); - Tools.PaintContent(pDC, p); - if (Hilight) + // Draw cached + if (BackImg) + { + Par->d->ImgList->SetDisabledAlpha(0x60); + } + else if (pDC->SupportsAlphaCompositing()) { - // Draw translucent white over image... - pDC->Op(GDC_ALPHA); - pDC->Colour(LColour(255, 255, 255, 128)); - pDC->Rectangle(&p); + pDC->Colour(Background); + pDC->Rectangle(&IconPos); + } + + Par->d->ImgList->Draw(pDC, IconPos.x1, IconPos.y1, ImgIndex, Background, !e); + + Unpainted.Subtract(&IconPos); + + if (!BackImg) + { + // Fill in the rest of the area + pDC->Colour(Background); + for (LRect *r = Unpainted.First(); r; r = Unpainted.Next()) + pDC->Rectangle(r); } } else { - Background = cBack; + // Draw a red cross indicating no icons. pDC->Colour(Background); - pDC->Box(&p); - p.Inset(1, 1); + pDC->Rectangle(&p); + pDC->Colour(LColour::Red); + pDC->Line(IconPos.x1, IconPos.y1, IconPos.x2, IconPos.y2); + pDC->Line(IconPos.x2, IconPos.y1, IconPos.x1, IconPos.y2); + } + } + else if (!BackImg) + { + Tools.PaintContent(pDC, p); + } + + // Text + if (Par->d->ShowTextLabels()) + { + if (Name() && !d->Text.Length()) + { + Layout(); } - LRect IconPos; - if (Par->d->ImgList) - IconPos.Set(0, 0, Par->d->ImgList->TileX()-1, Par->d->ImgList->TileY()-1); - else - IconPos.ZOff(Par->d->Bx-1, Par->d->By-1); - LRegion Unpainted(p); - - // Center the icon - if (IconPos.X() < p.X() - 1) - IconPos.Offset((p.X() - IconPos.X()) >> 1, 0); - // Offset it if the button is pressed - if (Down) - IconPos.Offset(2, 2); - - // Draw any icon. - if (ImgIndex >= 0) + if (d->Text.Length()) { - if (Par->d->ImgList) - { - // Draw cached - if (BackImg) - { - Par->d->ImgList->SetDisabledAlpha(0x60); - } - else if (pDC->SupportsAlphaCompositing()) - { - pDC->Colour(Background); - pDC->Rectangle(&IconPos); - } - - Par->d->ImgList->Draw(pDC, IconPos.x1, IconPos.y1, ImgIndex, Background, !e); - - Unpainted.Subtract(&IconPos); + // Write each word centered on a different line + int Ty = Down + Par->d->By + 2; + LColour a = Tools.GetFore(); + LColour b = Tools.GetBack(); + if (!e) + a = b.Mix(a); - if (!BackImg) - { - // Fill in the rest of the area - pDC->Colour(Background); - for (LRect *r = Unpainted.First(); r; r = Unpainted.Next()) - pDC->Rectangle(r); - } - } - else + Par->d->Font->Colour(a, b); + for (int i=0; iText.Length(); i++) { - // Draw a red cross indicating no icons. - pDC->Colour(Background); - pDC->Rectangle(&p); - pDC->Colour(LColour::Red); - pDC->Line(IconPos.x1, IconPos.y1, IconPos.x2, IconPos.y2); - pDC->Line(IconPos.x2, IconPos.y1, IconPos.x1, IconPos.y2); - } - } - else if (!BackImg) - { - Tools.PaintContent(pDC, p); - } - - // Text - if (Par->d->ShowTextLabels()) - { - if (Name() && !d->Text.Length()) - { - Layout(); - } - - if (d->Text.Length()) - { - // Write each word centered on a different line - int Ty = Down + Par->d->By + 2; - LColour a = Tools.GetFore(); - LColour b = Tools.GetBack(); - if (!e) - a = b.Mix(a); - - Par->d->Font->Colour(a, b); - for (int i=0; iText.Length(); i++) - { - LDisplayString *Ds = d->Text[i]; - Ds->Draw(pDC, Down + ((X()-Ds->X())/2), Ty); - Ty += Ds->Y(); - } + LDisplayString *Ds = d->Text[i]; + Ds->Draw(pDC, Down + ((X()-Ds->X())/2), Ty); + Ty += Ds->Y(); } } } + } + else + { + // Separator + int Px = X()-1; + int Py = Y()-1; + + if (BackImg) + Tools.PaintContent(pDC, p); else { - // Separator - int Px = X()-1; - int Py = Y()-1; + pDC->Colour(cBack); + pDC->Rectangle(); + } - if (BackImg) - Tools.PaintContent(pDC, p); - else - { - pDC->Colour(cBack); - pDC->Rectangle(); - } - - LColour cLow = cBack.Mix(LColour::Black); - LColour cHigh = cBack.Mix(LColour::White, 0.8f); + LColour cLow = cBack.Mix(LColour::Black); + LColour cHigh = cBack.Mix(LColour::White, 0.8f); - if (X() > Y()) - { - int c = Y()/2-1; - pDC->Colour(cLow); - pDC->Line(2, c, Px-2, c); - pDC->Colour(cHigh); - pDC->Line(2, c+1, Px-2, c+1); - } - else - { - int c = X()/2-1; - pDC->Colour(cLow); - pDC->Line(c, 2, c, Py-2); - pDC->Colour(cHigh); - pDC->Line(c+1, 2, c+1, Py-2); - } + if (X() > Y()) + { + int c = Y()/2-1; + pDC->Colour(cLow); + pDC->Line(2, c, Px-2, c); + pDC->Colour(cHigh); + pDC->Line(2, c+1, Px-2, c+1); + } + else + { + int c = X()/2-1; + pDC->Colour(cLow); + pDC->Line(c, 2, c, Py-2); + pDC->Colour(cHigh); + pDC->Line(c+1, 2, c+1, Py-2); } } - - #if 0 // def _DEBUG - pDC->Colour(LColour(255, 0, 255)); - pDC->Box(); - #endif } void LToolButton::Image(int i) { if (ImgIndex != i) { ImgIndex = i; Invalidate(); } } void LToolButton::Value(int64 b) { switch (Type) { case TBT_PUSH: { // do nothing... can't set value break; } case TBT_TOGGLE: { if (Value() != b) { Down = b != 0; Invalidate(); SendNotify(LNotifyValueChanged); } break; } case TBT_RADIO: { if (GetParent() && b) { // Clear any other radio buttons that are down auto it = GetParent()->IterateViews(); ssize_t CurIdx = it.IndexOf(this); if (CurIdx >= 0) { for (ssize_t i=CurIdx-1; i>=0; i--) { LToolButton *But = dynamic_cast(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } for (size_t i=CurIdx+1; i(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } } } Down = b != 0; if (GetParent()) { GetParent()->Invalidate(); SendNotify(LNotifyValueChanged); } break; } } } void LToolButton::SendCommand() { LToolBar *t = dynamic_cast(GetParent()); - if (t) t->OnButtonClick(this); + if (t) + t->OnButtonClick(this); + else + printf("%s:%i - Error: parent not toolbar.\n", _FL); } void LToolButton::OnMouseClick(LMouse &m) { LToolBar *ToolBar = dynamic_cast(GetParent()); #if 0 printf("tool button click %i,%i down=%i, left=%i right=%i middle=%i, ctrl=%i alt=%i shift=%i Double=%i\n", m.x, m.y, m.Down(), m.Left(), m.Right(), m.Middle(), m.Ctrl(), m.Alt(), m.Shift(), m.Double()); #endif if (m.IsContextMenu()) { if (!NeedsRightClick && ToolBar && ToolBar->IsCustomizable()) { m.ToScreen(); ToolBar->ContextMenu(m); } else { SendNotify(LNotification(m)); } } else if (m.Left()) { // left click action... if (GetId() >= 0 && Enabled()) { switch (Type) { case TBT_PUSH: { bool Old = Down; Clicked = m.Down(); Capture(m.Down()); if (Old && IsOver(m)) { - // char *n = Name(); - if (m.Left()) - { - SendCommand(); - } - + SendCommand(); SendNotify(LNotifyActivate); } Down = m.Down(); if (Old != Down) { Invalidate(); } break; } case TBT_TOGGLE: { if (m.Down()) { if (m.Left()) { Value(!Down); SendCommand(); } SendNotify(LNotifyActivate); } break; } case TBT_RADIO: { if (m.Down()) { if (!Down && m.Left()) { Value(true); SendCommand(); } SendNotify(LNotifyActivate); } break; } } } } } void LToolButton::OnMouseEnter(LMouse &m) { if (!Separator() && Enabled()) { Over = true; Invalidate(); } if (Clicked) { Value(true); Invalidate(); } else { LToolBar *Bar = dynamic_cast(GetParent()); if (Bar) { Bar->OnMouseEnter(m); if (!Bar->TextLabels() && Bar->d->Tip && TipId < 0) { TipId = Bar->d->Tip->NewTip(Name(), GetPos()); } } if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, Name()); } } } void LToolButton::OnMouseMove(LMouse &m) { } void LToolButton::OnMouseExit(LMouse &m) { if (Over) { Over = false; Invalidate(); } if (Clicked) { Value(false); Invalidate(); } else if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, ""); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// LToolBar::LToolBar() { d = new LToolBarPrivate; Name("LGI_Toolbar"); _BorderSize = 1; _IsToolBar = 1; // Setup tool button font LFontType SysFontType; if (SysFontType.GetSystemFont("Small")) { d->Font = SysFontType.Create(); if (d->Font) { d->Font->PointSize(MIN(d->Font->PointSize(), LSysFont->PointSize())); d->Font->Colour(L_TEXT); d->Font->Bold(false); d->Font->Transparent(true); } } d->LastIndex = 0; d->OwnImgList = false; d->ImgList = 0; GetCss(true)->BackgroundColor(LColour(L_MED).Mix(LColour::Black, 0.05f)); LResources::StyleElement(this); } LToolBar::~LToolBar() { DeleteObj(d->Tip); if (d->OwnImgList) DeleteObj(d->ImgList); DeleteObj(d->Font); DeleteObj(d); } void LToolBar::OnCreate() { #ifndef WIN32 AttachChildren(); #endif } int LToolBar::GetBx() { return d->Bx; } int LToolBar::GetBy() { return d->By; } void LToolBar::ContextMenu(LMouse &m) { if (IsCustomizable()) { LSubMenu *Sub = new LSubMenu; if (Sub) { int n = 1; for (auto it = Children.begin(); it != Children.end(); it++, n++) { LViewI *v = *it; LToolButton *Btn = dynamic_cast(v); if (Btn && Btn->Separator()) { Sub->AppendSeparator(); } else { auto Item = Sub->AppendItem(v->Name(), n, true); if (Item) { Item->Checked(v->Visible()); } } } Sub->AppendSeparator(); auto Txt = Sub->AppendItem(LLoadString(L_TOOLBAR_SHOW_TEXT, "Show Text Labels"), 1000, true); Txt->Checked(d->Text); bool Save = false; int Pick = Sub->Float(this, m); switch (Pick) { case 1000: { d->Text = !d->Text; Save = true; SendNotify(LNotifyTableLayoutRefresh); break; } default: { LViewI *Ctrl = Children[Pick - 1]; if (Ctrl) { Ctrl->Visible(!Ctrl->Visible()); Save = true; } break; } } DeleteObj(Sub); if (Save) { LStringPipe p(256); p.Push((char*) (d->Text ? "text" : "no")); for (auto v: Children) { if (v->Visible()) { p.Print(",%i", v->GetId()); } } char *o = p.NewStr(); if (o) { if (d->CustomDom) { LVariant v(o); d->CustomDom->SetValue(d->CustomProp, v); } DeleteArray(o); } d->FixSeparators(this); for (auto v: Children) { LToolButton *b = dynamic_cast(v); if (b && b->TipId >= 0) { d->Tip->DeleteTip(b->TipId); b->TipId = -1; } } GetWindow()->PourAll(); } } } } bool LToolBar::IsCustomizable() { return d->CustomDom != 0 && d->CustomProp; } void LToolBar::Customizable(LDom *Store, const char *Option) { d->CustomDom = Store; d->CustomProp = Option; d->Customizable(this); } bool LToolBar::IsVertical() { return d->Vertical; } void LToolBar::IsVertical(bool v) { d->Vertical = v; } bool LToolBar::TextLabels() { return d->Text; } void LToolBar::TextLabels(bool i) { d->Text = i; } LFont *LToolBar::GetFont() { return d->Font; } bool LToolBar::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min == 0) { // Calc width LRegion r(0, 0, 10000, 10000); Pour(r); Inf.Width.Min = X(); Inf.Width.Max = X(); } else { // Calc height Inf.Height.Min = Y(); Inf.Height.Max = Y(); } return true; } #define GetBorderSpacing() GetCss() && GetCss()->BorderSpacing().IsValid() ? \ GetCss()->BorderSpacing().ToPx(X(), GetFont()) : \ 1 bool LToolBar::Pour(LRegion &r) { int BorderSpacing = GetBorderSpacing(); int EndX = 0; int EndY = 0; int MaxDim = 0; LCssTools Tools(this); LRect Border = Tools.GetBorder(r); LRect Padding = Tools.GetPadding(r); int PosX = BorderSpacing + Border.x1 + Padding.x1; int PosY = BorderSpacing + Border.y1 + Padding.y1; LRect ButPos; for (auto But: Children) { if (But->Visible()) { int Tx = 0, Ty = 0; LToolButton *Btn = dynamic_cast(But); if (d->ShowTextLabels()) { if (Btn) { if (Btn->d->Text.Length() == 0) { Btn->Layout(); } for (int i=0; id->Text.Length(); i++) { LDisplayString *Ds = Btn->d->Text[i]; Tx = MAX(Ds->X() + 4, Tx); Ty += Ds->Y(); } } } ButPos = But->GetPos(); if (Btn) { if (Btn->Separator()) { // This will be stretched out later by the code that makes // everything the same height. ButPos.ZOff(BORDER_SEPARATOR+1, BORDER_SEPARATOR+1); } else { if (Btn->Image() >= 0) { // Set initial size to the icon size ButPos.ZOff(d->Bx + 2, d->By + 2); } else { // Otherwise default to text size if (d->Vertical) ButPos.ZOff(0, 7); else ButPos.ZOff(7, 0); } Tx += 4; if (ButPos.X() < Tx) { // Make button wider for text label ButPos.x2 = Tx - 1; } ButPos.y2 += Ty; } } if (d->Vertical) MaxDim = MAX(MaxDim, ButPos.X()); else MaxDim = MAX(MaxDim, ButPos.Y()); ButPos.Offset(PosX - ButPos.x1, PosY - ButPos.y1); if (But->GetId() == IDM_BREAK) { ButPos.ZOff(0, 0); if (d->Vertical) { PosX = MaxDim; PosY = BORDER_SHADE + BorderSpacing; } else { PosX = BORDER_SHADE + BorderSpacing; PosY = MaxDim; } } else { if (d->Vertical) PosY = ButPos.y2 + BorderSpacing; else PosX = ButPos.x2 + BorderSpacing; } But->SetPos(ButPos); } else { LRect p(-100, -100, -90, -90); But->SetPos(p); } } for (auto w: Children) { LRect p = w->GetPos(); if (d->Vertical) { if (w->X() < MaxDim) { p.x2 = p.x1 + MaxDim - 1; w->SetPos(p); } } else { if (w->Y() < MaxDim) { p.y2 = p.y1 + MaxDim - 1; w->SetPos(p); } } EndX = MAX(EndX, p.x2); EndY = MAX(EndY, p.y2); } d->Sx = EndX + BorderSpacing; d->Sy = EndY + BorderSpacing; d->Sx += Border.x2 + Padding.x2; d->Sy += Border.y2 + Padding.y2; LRect n; n.ZOff(MAX(7, d->Sx), MAX(7, d->Sy)); LRect *Best = FindLargestEdge(r, GV_EDGE_TOP); if (Best) { n.Offset(Best->x1, Best->y1); n.Bound(Best); SetPos(n, true); // _Dump(); return true; } else LgiTrace("%s:%i - No best pos.\n", _FL); return false; } void LToolBar::OnButtonClick(LToolButton *Btn) { LViewI *v = GetNotify() ? GetNotify() : GetParent(); if (v && Btn) { int Id = Btn->GetId(); - v->PostEvent(M_COMMAND, (LMessage::Param) Id + if (v->PostEvent(M_COMMAND, (LMessage::Param) Id #if LGI_VIEW_HANDLE , (LMessage::Param) Handle() #endif - ); + )) + ; //printf("Send M_COMMAND(%i)\n", Id); + else + printf("%s:%i - Failed to send M_COMMAND.\n", _FL); } + else printf("%s:%i - Ptr error: %p %p\n", _FL, v, Btn); } int LToolBar::PostDescription(LView *Ctrl, const char *Text) { if (GetParent()) { return GetParent()->PostEvent(M_DESCRIBE, (LMessage::Param) Ctrl, (LMessage::Param) Text); } return 0; } LMessage::Result LToolBar::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CHANGE: { if (GetParent()) return GetParent()->OnEvent(Msg); LAutoPtr note((LNotification*)Msg->B()); break; } } return LView::OnEvent(Msg); } void LToolBar::OnPaint(LSurface *pDC) { LRect c = GetClient(); LCssTools Tools(this); Tools.PaintBorder(pDC, c); Tools.PaintPadding(pDC, c); Tools.PaintContent(pDC, c); } void LToolBar::OnMouseClick(LMouse &m) { } void LToolBar::OnMouseEnter(LMouse &m) { if (!d->Tip) { d->Tip = new LToolTip; if (d->Tip) { d->Tip->Attach(this); } } } void LToolBar::OnMouseExit(LMouse &m) { } void LToolBar::OnMouseMove(LMouse &m) { } bool LToolBar::SetBitmap(char *File, int bx, int by) { - bool Status = false; - - LSurface *pDC = GdcD->Load(File); - if (pDC) - { - Status = SetDC(pDC, bx, by); - DeleteObj(pDC); - } - - return Status; + LAutoPtr pDC(GdcD->Load(File)); + return pDC ? SetDC(pDC, bx, by) : false; } bool LToolBar::SetDC(LSurface *pNewDC, int bx, int by) { if (d->OwnImgList) { DeleteObj(d->ImgList); } d->Bx = bx; d->By = by; if (pNewDC) { d->ImgList = new LImageList(bx, by, pNewDC); if (d->ImgList) { d->OwnImgList = true; return true; } } return false; } LImageList *LToolBar::GetImageList() { return d->ImgList; } bool LToolBar::SetImageList(LImageList *l, int bx, int by, bool Own) { if (d->OwnImgList) DeleteObj(d->ImgList); d->OwnImgList = Own; d->Bx = bx; d->By = by; d->ImgList = l; return d->ImgList != 0; } LToolButton *LToolBar::AppendButton(const char *Tip, int Id, int Type, int Enabled, int IconId) { // bool HasIcon = IconId != TOOL_ICO_NONE; LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->Name(Tip); But->SetId(Id); But->Type = Type; But->Enabled(Enabled != 0); if (IconId >= 0) { But->ImgIndex = IconId; } else if (IconId == TOOL_ICO_NEXT) { But->ImgIndex = d->LastIndex++; } else if (IconId == TOOL_ICO_NONE) { But->ImgIndex = -1; } AttachButton(But); } return But; } bool LToolBar::AppendSeparator() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_SEPARATOR); AttachButton(But); return true; } return false; } bool LToolBar::AppendBreak() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_BREAK); But->SetParent(this); AttachButton(But); return true; } return false; } bool LToolBar::AppendControl(LView *Ctrl) { bool Status = false; if (Ctrl) { Ctrl->SetParent(this); AttachButton(Ctrl); Status = true; } return Status; } void LToolBar::Empty() { for (auto But: Children) { DeleteObj(But); } } #ifdef MAC bool LToolBar::Attach(LViewI *parent) { return LLayout::Attach(parent); } #endif /////////////////////////////////////////////////////////////////////// COLOUR Map(LSurface *pDC, COLOUR c) { if (pDC && pDC->GetBits() <= 8) { if (pDC->IsScreen()) { c = CBit(24, c); } #ifdef WIN32 else { HPALETTE hPal = GetSystemPalette(); if (hPal) { c = GetNearestPaletteIndex(hPal, c); DeleteObject(hPal); } } #endif } return c; } #ifdef WIN32 HPALETTE GetSystemPalette() { HPALETTE hPal = 0; LOGPALETTE *Log = (LOGPALETTE*) new uchar[sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 255)]; if (Log) { Log->palVersion = 0x300; Log->palNumEntries = 256; HDC hDC = CreateCompatibleDC(0); GetSystemPaletteEntries(hDC, 0, 256, Log->palPalEntry); DeleteDC(hDC); hPal = CreatePalette(Log); } return hPal; } bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (DestDC) { DeleteDC(DestDC); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); } if (DestDC) { DeleteDC(DestDC); } return Status; } #endif 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,2250 +1,2261 @@ #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) +struct LTreeLocker +{ + LTree *t = NULL; + bool status = false; + LTreeLocker(LTree *tree, const char *file, int line) : t(tree) + { + if (t) + status = t->Lock(file, line); + } + ~LTreeLocker() + { + if (status && t) + t->Unlock(); + } +}; + +#define TREELOCK(ptr) LTreeLocker _lock(ptr, _FL); + ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility -class LTreePrivate : public LMutex +class LTreePrivate { 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") + 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) + LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; + auto txt = Item->GetText(Col); + if (txt) + { + Ds[Col] = new LDisplayString(f, Item->GetText(Col)); + if (Ds[Col]) { - Ds[Col]->TruncateWithDots(FixPx); + 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); } 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::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) { 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) { - LAutoPtr Lck; - - if (Tree) - Lck.Reset(new LMutex::Auto(Tree->d, -1, _FL)); + TREELOCK(Tree); Str[i] = s; - 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) { + // Draw the text in the context area: LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } + else + { + // No string, fill the space with background + Ctx.pDC->Colour(Ctx.Back); + Ctx.pDC->Rectangle(&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 + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { - TREELOCK + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { - TREELOCK + TREELOCK(this) if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { - TREELOCK + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { - TREELOCK + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { - TREELOCK + TREELOCK(this) 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) + TREELOCK(this) + if (n.Type == 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 + TREELOCK(this) LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { - TREELOCK + TREELOCK(this) if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { - TREELOCK + TREELOCK(this) bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { - TREELOCK + TREELOCK(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { - TREELOCK + TREELOCK(this) LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; - TREELOCK + TREELOCK(this) if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { - TREELOCK + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { - TREELOCK + TREELOCK(this) InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { - TREELOCK + TREELOCK(this) InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { - TREELOCK + TREELOCK(this) 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 + TREELOCK(this) 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 + TREELOCK(this) return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { - TREELOCK + TREELOCK(this) return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; - TREELOCK + TREELOCK(this) Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; - TREELOCK + TREELOCK(this) Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { - TREELOCK + TREELOCK(this) // 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 + TREELOCK(this) d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { - TREELOCK + TREELOCK(this) d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/src/haiku/App.cpp b/src/haiku/App.cpp --- a/src/haiku/App.cpp +++ b/src/haiku/App.cpp @@ -1,1007 +1,1062 @@ #include #include #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/FontCache.h" #include "AppPriv.h" +#include "MimeType.h" + #define DEBUG_MSG_TYPES 0 #define DEBUG_HND_WARNINGS 0 #define IDLE_ALWAYS 0 LString LgiArgsAppPath; //////////////////////////////////////////////////////////////// struct OsAppArgumentsPriv { ::LArray Ptr; ~OsAppArgumentsPriv() { Ptr.DeleteArrays(); } }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; for (int i=0; iPtr.Add(NewStr(arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } bool OsAppArguments::Get(const char *Name, const char **Val) { if (!Name) return false; for (int i=0; iPtr.DeleteArrays(); if (!CmdLine) return; for (char *s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr(s, delim); if (e) d->Ptr.Add(NewStr(s, e - s)); else break; s = e + 1; } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; d->Ptr.Add(NewStr(s, e-s)); s = e; } } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { d->Ptr.DeleteArrays(); for (int i=0; iPtr.Add(NewStr(a.Arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if HAS_SHARED_MIME #include "GFilterUtils.h" #include "mime-types.h" class LSharedMime : public LLibrary { public: LSharedMime() : #ifdef _DEBUG LLibrary("libsharedmime1d") #else LLibrary("libsharedmime1") #endif { } DynFunc0(int, mimetypes_init); DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type); DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags); DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length); DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type); DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type); DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan); DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang); }; #endif #if 1 ///////////////////////////////////////////////////////////////////////////// // // Attempts to cleanup and call drkonqi to process the crash // void LgiCrashHandler(int Sig) { // Don't get into an infinite loop signal(SIGSEGV, SIG_DFL); #ifndef _MSC_VER // Our pid int MyPid = getpid(); printf("LgiCrashHandler trigger MyPid=%i\n", MyPid); #endif exit(-1); } #endif ///////////////////////////////////////////////////////////////////////////// #ifndef XK_Num_Lock #define XK_Num_Lock 0xff7f #endif #ifndef XK_Shift_Lock #define XK_Shift_Lock 0xffe6 #endif #ifndef XK_Caps_Lock #define XK_Caps_Lock 0xffe5 #endif struct Msg { LViewI *v; int m; LMessage::Param a, b; void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B) { v = V; m = M; a = A; b = B; } }; // Out of thread messages... must lock before access. class LMessageQue : public LMutex { public: typedef ::LArray MsgArray; LMessageQue() : LMutex("LMessageQue") { } MsgArray *Lock(const char *file, int line) { if (!LMutex::Lock(file, line)) return NULL; return &q; } operator bool() { return q.Length() > 0; } private: MsgArray q; } MsgQue; ///////////////////////////////////////////////////////////////////////////// LApp *TheApp = NULL; LSkinEngine *LApp::SkinEngine; LMouseHook *LApp::MouseHook; LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; LgiArgsAppPath = AppArgs.Arg[0]; Name(name); d = new LAppPrivate(this); + if (LIsRelativePath(LgiArgsAppPath)) { char Cwd[MAX_PATH_LEN]; getcwd(Cwd, sizeof(Cwd)); LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath); LgiArgsAppPath = Cwd; } + + char AppPathLnk[MAX_PATH_LEN]; + if (LResolveShortcut(LgiArgsAppPath, AppPathLnk, sizeof(AppPathLnk))) + LgiArgsAppPath = AppPathLnk; int WCharSz = sizeof(wchar_t); #if defined(_MSC_VER) LAssert(WCharSz == 2); ::LFile::Path Dlls(LgiArgsAppPath); Dlls--; SetDllDirectoryA(Dlls); #else LAssert(WCharSz == 4); #endif #ifdef _MSC_VER SetEnvironmentVariable(_T("GTK_CSD"), _T("0")); #else setenv("GTK_CSD", "0", true); #endif // We want our printf's NOW! setvbuf(stdout,(char *)NULL,_IONBF,0); // print mesgs immediately. // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; SetAppArgs(AppArgs); srand(LCurrentTime()); LColour::OnChange(); MouseHook = new LMouseHook; d->GetConfig(); // System font setup LFontType SysFontType; if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) SystemNormal->Transparent(true); SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } } LApp::~LApp() { DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(SkinEngine); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = 0; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #ifndef __clang__ (this != 0) && #endif (d != 0); LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { case LGI_MET_DECOR_X: return 8; case LGI_MET_DECOR_Y: return 8 + 19; case LGI_MET_DECOR_CAPTION: return 19; default: break; } return 0; } LViewI *LApp::GetFocus() { // GtkWidget *w = gtk_window_get_focus(GtkWindow *window); return NULL; } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } OsProcessId LApp::GetProcessId() { #ifdef WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } bool LApp::InThread() { OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = GetGuiThreadId(); // printf("Me=%i Gui=%i\n", Me, Gui); return Gui == Me; } -bool LApp::Run(bool Loop, OnIdleProc IdleCallback, void *IdleParam) +bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!InThread()) { printf("%s:%i - Error: Out of thread.\n", _FL); return false; } - if (Loop) - { - printf("Running main loop...\n"); - d->Run(); - printf("Main loop finished.\n"); - } - else - { - } + printf("Running main loop...\n"); + d->Run(); + printf("Main loop finished.\n"); return true; } +bool LApp::Yield() +{ + return false; +} + void LApp::Exit(int Code) { if (Code) { // hard exit ::exit(Code); } else { // soft exit printf("Quitting main loop...\n"); if (d->Lock()) { d->Quit(); d->Unlock(); } } } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); } void LApp::OnReceiveFiles(::LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); else LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe."); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { ::LString Buf; if (GetOption(Option, Buf)) { if (Dest) { if (DestLen > 0) { strcpy_s(Dest, DestLen, Buf); } else return false; } return true; } return false; } bool LApp::GetOption(const char *Option, ::LString &Buf) { if (IsOk() && Option) { int OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { auto *a = d->Args.Arg[i]; if (!a) continue; if (strchr("-/\\", a[0])) { if (strnicmp(a+1, Option, OptLen) == 0) { const char *Arg = NULL; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { auto Len = End-Arg; if (Len > 0) Buf.Set(Arg, Len); else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { ::LArray Files; for (int i=1; iArgs; i++) { auto a = GetAppArgs()->Arg[i]; if (LFileExists(a)) { Files.Add(NewStr(a)); } } // call app if (Files.Length() > 0) { OnReceiveFiles(Files); } // clear up Files.DeleteArrays(); } -::LString LApp::GetFileMimeType(const char *File) +LString LApp::GetFileMimeType(const char *File) { - ::LString Status; - char Full[MAX_PATH_LEN] = ""; - - if (!LFileExists(File)) - { - // Look in the path - LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); - for (int i=0; i 0) - Status = s.SplitDelimit(":").Last().Strip(); - } - - return Status; - } + LgiTrace("%s:%i - GuessMimeType(%s) failed: %i\n", _FL, File, r); + return LString(); } - #endif - - return Status; + return mt.Type(); } -bool LApp::GetAppsForMimeType(char *Mime, ::LArray<::LAppInfo*> &Apps) +bool LApp::GetAppsForMimeType(const char *Mime, LArray &Apps) { // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; int Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } LGetAppsForMimeType(Mime, Apps); LGetAppsForMimeType(AltMime, Apps); return Apps.Length() > 0; } #if defined(LINUX) LLibrary *LApp::GetWindowManagerLib() { if (this != NULL && !d->WmLib) { char Lib[32]; WindowManager Wm = LGetWindowManager(); switch (Wm) { case WM_Kde: strcpy(Lib, "liblgikde3"); break; case WM_Gnome: strcpy(Lib, "liblgignome2"); break; default: strcpy(Lib, "liblgiother"); break; } #ifdef _DEBUG strcat(Lib, "d"); #endif d->WmLib = new LLibrary(Lib, true); if (d->WmLib) { if (d->WmLib->IsLoaded()) { Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit"); if (WmInit) { WmInitParams Params; // Params.Dsp = XObject::XDisplay(); Params.Args = d->Args.Args; Params.Arg = d->Args.Arg; WmInit(&Params); } // else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__); } // else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib); } // else printf("%s:%i - alloc error\n", __FILE__, __LINE__); } return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0; } void LApp::DeleteMeLater(LViewI *v) { d->DeleteLater.Add(v); } void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v) { // Store the clipboard data we will serve d->ClipData = v; } bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types) { return false; } #endif LSymLookup *LApp::GetSymLookup() { return NULL; } bool LApp::IsElevated() { #ifdef WIN32 LAssert(!"What API works here?"); return false; #else return geteuid() == 0; #endif } int LApp::GetCpuCount() { return 1; } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } #ifdef LINUX LApp::DesktopInfo::DesktopInfo(const char *file) { File = file; Dirty = false; if (File) Serialize(false); } bool LApp::DesktopInfo::Serialize(bool Write) { ::LFile f; if (Write) { ::LFile::Path p(File); p--; if (!p.Exists()) return false; } else if (!LFileExists(File)) return false; if (!f.Open(File, Write?O_WRITE:O_READ)) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get()); return false; } if (Write) { f.SetSize(0); for (unsigned i=0; i= 0) { int e = l.Find("]", ++s); if (e >= 0) { Cur = &Data.New(); Cur->Name = l(s, e - s + 1); } } else if ((s = l.Find("=")) >= 0) { if (!Cur) Cur = &Data.New(); KeyPair &kp = Cur->Values.New(); kp.Key = l(0, s).Strip(); kp.Value = l(++s, -1).Strip(); // printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get()); } } } return true; } LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create) { for (unsigned i=0; iGet(Field, false, Dirty); if (kp) { return kp->Value; } } } return ::LString(); } bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect) { if (!Field) return false; Section *s = GetSection(Sect ? Sect : DefaultSection, true); if (!s) return false; KeyPair *kp = s->Get(Field, true, Dirty); if (!kp) return false; if (kp->Value != Value) { kp->Value = Value; Dirty = true; } return true; } LApp::DesktopInfo *LApp::GetDesktopInfo() { auto sExe = LGetExeFile(); ::LFile::Path Exe(sExe); ::LFile::Path Desktop(LSP_HOME); ::LString Leaf; Leaf.Printf("%s.desktop", Exe.Last().Get()); Desktop += ".local/share/applications"; Desktop += Leaf; const char *Ex = Exe; const char *Fn = Desktop; if (d->DesktopInfo.Reset(new DesktopInfo(Desktop))) { // Do a sanity check... ::LString s = d->DesktopInfo->Get("Name"); if (!s && Name()) d->DesktopInfo->Set("Name", Name()); s = d->DesktopInfo->Get("Exec"); if (!s || s != (const char*)sExe) d->DesktopInfo->Set("Exec", sExe); s = d->DesktopInfo->Get("Type"); if (!s) d->DesktopInfo->Set("Type", "Application"); s = d->DesktopInfo->Get("Categories"); if (!s) d->DesktopInfo->Set("Categories", "Application;"); s = d->DesktopInfo->Get("Terminal"); if (!s) d->DesktopInfo->Set("Terminal", "false"); d->DesktopInfo->Update(); } return d->DesktopInfo; } bool LApp::SetApplicationIcon(const char *FileName) { DesktopInfo *di = GetDesktopInfo(); if (!di) return false; ::LString IcoPath = di->Get("Icon"); if (IcoPath == FileName) return true; di->Set("Icon", FileName); return di->Update(); } #endif //////////////////////////////////////////////////////////////// OsApplication *OsApplication::Inst = 0; class OsApplicationPriv { public: OsApplicationPriv() { } }; OsApplication::OsApplication(int Args, const char **Arg) { Inst = this; d = new OsApplicationPriv; } OsApplication::~OsApplication() { DeleteObj(d); Inst = 0; } //////////////////////////////////////////////////////////////// int LMessage::Msg() { return what; } LMessage::Param LMessage::A() { - LMessage::Param a = 0; - FindInt64(PropNames[0], &a); + int64 a = 0; + if (FindInt64(PropA, &a) != B_OK) + { + int32 c = CountNames(B_ANY_TYPE); + printf("%s:%i - Failed to find PropA (%i names)\n", _FL, c); + for (int32 i=0; iPostEvent(what, A(), B()); } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +LLocker::LLocker(BHandler *h, const char *File, int Line) +{ + hnd = h; + file = File; + line = Line; +} + +LLocker::~LLocker() +{ + Unlock(); +} + +bool LLocker::Lock() +{ + if (locked) + { + printf("%s:%i - Locker already locked.\n", file, line); + LAssert(!"Locker already locked."); + return false; + } + if (!hnd) + { + // printf("%s:%i - Locker hnd is NULL.\n", file, line); + return false; + } + + while (!locked) + { + status_t result = hnd->LockLooperWithTimeout(1000 * 1000); + if (result == B_OK) + { + locked = true; + break; + } + else if (result == B_TIMED_OUT) + { + // Warn about failure to lock... + auto cur = GetCurrentThreadId(); + auto locking = hnd->Looper()->LockingThread(); + + printf("%s:%i - Warning: can't lock. cur=%i locking=%i\n", + _FL, + cur, + locking); + } + else if (result == B_BAD_VALUE) + { + break; + } + else + { + // Warn about error + printf("%s:%i - Error from LockLooperWithTimeout = 0x%x\n", _FL, result); + } + } + + return locked; +} + +status_t LLocker::LockWithTimeout(int64 time) +{ + LAssert(!locked); + LAssert(hnd != NULL); + + status_t result = hnd->LockLooperWithTimeout(time); + if (result == B_OK) + locked = true; + + return result; +} + +void LLocker::Unlock() +{ + if (locked) + { + hnd->UnlockLooper(); + locked = false; + } +} diff --git a/src/haiku/ClipBoard.cpp b/src/haiku/ClipBoard.cpp --- a/src/haiku/ClipBoard.cpp +++ b/src/haiku/ClipBoard.cpp @@ -1,156 +1,183 @@ // Clipboard Implementation #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/ClipBoard.h" +#include + #define DEBUG_CLIPBOARD 0 -#define VAR_COUNT 16 #define LGI_CLIP_BINARY "lgi.binary" -#define LGI_RECEIVE_CLIPBOARD_TIMEOUT 4000 -struct ClipData : public LMutex +class LClipBoardPriv : public BClipboard { - ::LVariant v[VAR_COUNT]; +public: + LString Txt; + LAutoWString WTxt; - ClipData() : LMutex("ClipData") + LClipBoardPriv() : BClipboard(NULL) { } - -} Data; - -class LClipBoardPriv -{ -public: }; /////////////////////////////////////////////////////////////////////////////////////////////// LClipBoard::LClipBoard(LView *o) { d = new LClipBoardPriv; Owner = o; Open = false; } LClipBoard::~LClipBoard() { DeleteObj(d); } bool LClipBoard::Empty() { - return false; + if (!d->Lock()) + { + LgiTrace("%s:%i - Can't lock BClipboard.\n", _FL); + return false; + } + + auto result = d->Clear(); + if (result) + printf("%s:%i - clear=%i %s\n", _FL, result, strerror(result)); + result = d->Commit(); + if (result) + printf("%s:%i - commit=%i %s\n", _FL, result, strerror(result)); + + d->Unlock(); + + return true; } 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(); } - - return Status; + + if (!d->Lock()) + { + LgiTrace("%s:%i - Can't lock BClipboard.\n", _FL); + return false; + } + + auto clip = d->Data(); + if (!clip) + { + d->Unlock(); + LgiTrace("%s:%i - No clipboard data.\n", _FL); + return false; + } + + auto result = clip->AddString("text/plain", BString(Str)); + if (result) + printf("%s:%i - AddString=%i %s\n", _FL, result, strerror(result)); + + result = d->Commit(); + if (result) + printf("%s:%i - Commit=%i %s\n", _FL, result, strerror(result)); + + d->Unlock(); + + return result == B_OK; } char *LClipBoard::Text() { - char *t = 0; + if (!d->Lock()) + { + LgiTrace("%s:%i - Can't lock BClipboard.\n", _FL); + return NULL; + } - return t; + auto clip = d->Data(); + + BString s; + auto result = clip->FindString("text/plain", &s); + if (result) + printf("%s:%i - FindString=%i %s\n", _FL, result, strerror(result)); + + d->Txt = s; + + d->Unlock(); + + return d->Txt; } +// Wide char versions for plain text 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); + auto u = Text(); + d->WTxt.Reset(Utf8ToWide(u)); + return d->WTxt; } bool LClipBoard::Bitmap(LSurface *pDC, bool AutoEmpty) { bool Status = false; return Status; } LAutoPtr LClipBoard::Bitmap() { LAutoPtr img; return img; } 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; - } - return false; } ::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; }; bool LClipBoard::Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len) { return false; } diff --git a/src/haiku/File.cpp b/src/haiku/File.cpp --- a/src/haiku/File.cpp +++ b/src/haiku/File.cpp @@ -1,1721 +1,1452 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 -** DESCRIPTION: The new file subsystem +** DESCRIPTION: The file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ - -#include -#include -#include -#include -#include +#include -#include -#include -#include -#ifndef _MSC_VER -#include -#endif +#include "Path.h" +#include "VolumeRoster.h" +#include "Directory.h" #include "lgi/common/LgiDefs.h" #include "lgi/common/File.h" #include "lgi/common/Containers.h" -#include "lgi/common/Token.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiCommon.h" -#include "lgi/common/LgiString.h" #include "lgi/common/DateTime.h" -#if defined(WIN32) -#include "errno.h" -#endif - -/****************************** Defines ***********************************/ - -// #define FILEDEBUG - -#define FLOPPY_360K 0x0001 -#define FLOPPY_720K 0x0002 -#define FLOPPY_1_2M 0x0004 -#define FLOPPY_1_4M 0x0008 -#define FLOPPY_5_25 (FLOPPY_360K | FLOPPY_1_2M) -#define FLOPPY_3_5 (FLOPPY_720K | FLOPPY_1_4M) - -/****************************** Globals ***********************************/ - -LString LFile::Path::Sep(DIR_STR); - -struct ErrorCodeType -{ - const char *Name; - int Code; - const char *Desc; -} -ErrorCodes[] = -{ - #if defined(WIN32) - - {"EPERM", 1, "Not owner"}, - {"ENOENT", 2, "No such file"}, - {"ESRCH", 3, "No such process"}, - {"EINTR", 4, "Interrupted system"}, - {"EIO", 5, "I/O error"}, - {"ENXIO", 6, "No such device"}, - {"E2BIG", 7, "Argument list too long"}, - {"ENOEXEC", 8, "Exec format error"}, - {"EBADF", 9, "Bad file number"}, - {"ECHILD", 10, "No children"}, - {"EAGAIN", 11, "No more processes"}, - {"ENOMEM", 12, "Not enough core"}, - {"EACCES", 13, "Permission denied"}, - {"EFAULT", 14, "Bad address"}, - {"ENOTBLK", 15, "Block device required"}, - {"EBUSY", 16, "Mount device busy"}, - {"EEXIST", 17, "File exists"}, - {"EXDEV", 18, "Cross-device link"}, - {"ENODEV", 19, "No such device"}, - {"ENOTDIR", 20, "Not a directory"}, - {"EISDIR", 21, "Is a directory"}, - {"EINVAL", 22, "Invalid argument"}, - {"ENFILE", 23, "File table overflow"}, - {"EMFILE", 24, "Too many open file"}, - {"ENOTTY", 25, "Not a typewriter"}, - {"ETXTBSY", 26, "Text file busy"}, - {"EFBIG", 27, "File too large"}, - {"ENOSPC", 28, "No space left on"}, - {"ESPIPE", 29, "Illegal seek"}, - {"EROFS", 30, "Read-only file system"}, - {"EMLINK", 31, "Too many links"}, - {"EPIPE", 32, "Broken pipe"}, - {"EWOULDBLOCK", 35, "Operation would block"}, - {"EINPROGRESS", 36, "Operation now in progress"}, - {"EALREADY", 37, "Operation already in progress"}, - {"ENOTSOCK", 38, "Socket operation on"}, - {"EDESTADDRREQ", 39, "Destination address required"}, - {"EMSGSIZE", 40, "Message too long"}, - {"EPROTOTYPE", 41, "Protocol wrong type"}, - {"ENOPROTOOPT", 42, "Protocol not available"}, - {"EPROTONOSUPPORT", 43, "Protocol not supported"}, - {"ESOCKTNOSUPPORT", 44, "Socket type not supported"}, - {"EOPNOTSUPP", 45, "Operation not supported"}, - {"EPFNOSUPPORT", 46, "Protocol family not supported"}, - {"EAFNOSUPPORT", 47, "Address family not supported"}, - {"EADDRINUSE", 48, "Address already in use"}, - {"EADDRNOTAVAIL", 49, "Can't assign requested address"}, - {"ENETDOWN", 50, "Network is down"}, - {"ENETUNREACH", 51, "Network is unreachable"}, - {"ENETRESET", 52, "Network dropped connection"}, - {"ECONNABORTED", 53, "Software caused connection"}, - {"ECONNRESET", 54, "Connection reset by peer"}, - {"ENOBUFS", 55, "No buffer space available"}, - {"EISCONN", 56, "Socket is already connected"}, - {"ENOTCONN", 57, "Socket is not connected" }, - {"ESHUTDOWN", 58, "Can't send after shutdown"}, - {"ETOOMANYREFS", 59, "Too many references"}, - {"ETIMEDOUT", 60, "Connection timed out"}, - {"ECONNREFUSED", 61, "Connection refused"}, - {"ELOOP", 62, "Too many levels of nesting"}, - {"ENAMETOOLONG", 63, "File name too long" }, - {"EHOSTDOWN", 64, "Host is down"}, - {"EHOSTUNREACH", 65, "No route to host"}, - {"ENOTEMPTY", 66, "Directory not empty"}, - {"EPROCLIM", 67, "Too many processes"}, - {"EUSERS", 68, "Too many users"}, - {"EDQUOT", 69, "Disc quota exceeded"}, - {"ESTALE", 70, "Stale NFS file handle"}, - {"EREMOTE", 71, "Too many levels of remote in the path"}, - {"ENOSTR", 72, "Device is not a stream"}, - {"ETIME", 73, "Timer expired"}, - {"ENOSR", 74, "Out of streams resources"}, - {"ENOMSG", 75, "No message"}, - {"EBADMSG", 76, "Trying to read unreadable message"}, - {"EIDRM", 77, "Identifier removed"}, - {"EDEADLK", 78, "Deadlock condition"}, - {"ENOLCK", 79, "No record locks available"}, - {"ENONET", 80, "Machine is not on network"}, - {"ERREMOTE", 81, "Object is remote"}, - {"ENOLINK", 82, "The link has been severed"}, - {"EADV", 83, "ADVERTISE error"}, - {"ESRMNT", 84, "SRMOUNT error"}, - {"ECOMM", 85, "Communication error"}, - {"EPROTO", 86, "Protocol error"}, - {"EMULTIHOP", 87, "Multihop attempted"}, - {"EDOTDOT", 88, "Cross mount point"}, - {"EREMCHG", 89, "Remote address change"}, - - #elif defined(LINUX) || defined(__GTK_H__) - {"EPERM", EPERM, "Operation not permitted"}, - {"ENOENT", ENOENT, "No such file or directory"}, - {"ESRCH", ESRCH, "No such process"}, - {"EINTR", EINTR, "Interrupted system call"}, - {"EIO", EIO, "I/O error"}, - {"ENXIO", ENXIO, "No such device or address"}, - {"E2BIG", E2BIG, "Argument list too long"}, - {"ENOEXEC", ENOEXEC, "Exec format error"}, - {"EBADF", EBADF, "Bad file number"}, - {"ECHILD", ECHILD, "No child processes"}, - {"EAGAIN", EAGAIN, "Try again"}, - {"ENOMEM", ENOMEM, "Out of memory"}, - {"EACCES", EACCES, "Permission denied"}, - {"EFAULT", EFAULT, "Bad address"}, - {"ENOTBLK", ENOTBLK, "Block device required"}, - {"EBUSY", EBUSY, "Device or resource busy"}, - {"EEXIST", EEXIST, "File exists"}, - {"EXDEV", EXDEV, "Cross-device link"}, - {"ENODEV", ENODEV, "No such device"}, - {"ENOTDIR", ENOTDIR, "Not a directory"}, - {"EISDIR", EISDIR, "Is a directory"}, - {"EINVAL", EINVAL, "Invalid argument"}, - {"ENFILE", ENFILE, "File table overflow"}, - {"EMFILE", EMFILE, "Too many open files"}, - {"ENOTTY", ENOTTY, "Not a typewriter"}, - {"ETXTBSY", ETXTBSY, "Text file busy"}, - {"EFBIG", EFBIG, "File too large"}, - {"ENOSPC", ENOSPC, "No space left on device"}, - {"ESPIPE", ESPIPE, "Illegal seek"}, - {"EROFS", EROFS, "Read-only file system"}, - {"EMLINK", EMLINK, "Too many links"}, - {"EPIPE", EPIPE, "Broken pipe"}, - {"EDOM", EDOM, "Math argument out of domain of func"}, - {"ERANGE", ERANGE, "Math result not representable"}, - {"EDEADLK", EDEADLK, "Resource deadlock would occur"}, - {"ENAMETOOLONG", ENAMETOOLONG, "File name too long"}, - {"ENOLCK", ENOLCK, "No record locks available"}, - {"ENOSYS", ENOSYS, "Function not implemented"}, - {"ENOTEMPTY", ENOTEMPTY, "Directory not empty"}, - {"ELOOP", ELOOP, "Too many symbolic links encountered"}, - {"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"}, - {"ENOMSG", ENOMSG, "No message of desired type"}, - {"EIDRM", EIDRM, "Identifier removed"}, - {"EREMOTE", EREMOTE, "Object is remote"}, - {"ENOLINK", ENOLINK, "Link has been severed"}, - {"ENOSTR", ENOSTR, "Device not a stream"}, - {"ENODATA", ENODATA, "No data available"}, - {"ETIME", ETIME, "Timer expired"}, - {"ENOSR", ENOSR, "Out of streams resources"}, - {"EPROTO", EPROTO, "Protocol error"}, - {"EMULTIHOP", EMULTIHOP, "Multihop attempted"}, - {"EBADMSG", EBADMSG, "Not a data message"}, - {"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"}, - {"EILSEQ", EILSEQ, "Illegal byte sequence"}, - {"EUSERS", EUSERS, "Too many users"}, - {"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"}, - {"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"}, - {"EMSGSIZE", EMSGSIZE, "Message too long"}, - {"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"}, - {"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"}, - {"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"}, - {"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"}, - {"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"}, - {"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"}, - {"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"}, - {"EADDRINUSE", EADDRINUSE, "Address already in use"}, - {"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"}, - {"ENETDOWN", ENETDOWN, "Network is down"}, - {"ENETUNREACH", ENETUNREACH, "Network is unreachable"}, - {"ENETRESET", ENETRESET, "Network dropped connection because of reset"}, - {"ECONNABORTED", ECONNABORTED, "Software caused connection abort"}, - {"ECONNRESET", ECONNRESET, "Connection reset by peer"}, - {"ENOBUFS", ENOBUFS, "No buffer space available"}, - {"EISCONN", EISCONN, "Transport endpoint is already connected"}, - {"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"}, - {"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"}, - {"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"}, - {"ETIMEDOUT", ETIMEDOUT, "Connection timed out"}, - {"ECONNREFUSED", ECONNREFUSED, "Connection refused"}, - {"EHOSTDOWN", EHOSTDOWN, "Host is down"}, - {"EHOSTUNREACH", EHOSTUNREACH, "No route to host"}, - {"EALREADY", EALREADY, "Operation already in progress"}, - {"EINPROGRESS", EINPROGRESS, "Operation now in progress"}, - {"ESTALE", ESTALE, "Stale NFS file handle"}, - - #ifndef __GTK_H__ - {"EDQUOT", EDQUOT, "Quota exceeded"}, - {"ENOMEDIUM", ENOMEDIUM, "No medium found"}, - {"EMEDIUMTYPE", EMEDIUMTYPE, "Wrong medium type"}, - {"EUCLEAN", EUCLEAN, "Structure needs cleaning"}, - {"ENOTNAM", ENOTNAM, "Not a XENIX named type file"}, - {"ENAVAIL", ENAVAIL, "No XENIX semaphores available"}, - {"EISNAM", EISNAM, "Is a named type file"}, - {"EREMOTEIO", EREMOTEIO, "Remote I/O error"}, - {"ERESTART", ERESTART, "Interrupted system call should be restarted"}, - {"ESTRPIPE", ESTRPIPE, "Streams pipe error"}, - {"ECOMM", ECOMM, "Communication error on send"}, - {"EDOTDOT", EDOTDOT, "RFS specific error"}, - {"ENOTUNIQ", ENOTUNIQ, "Name not unique on network"}, - {"EBADFD", EBADFD, "File descriptor in bad state"}, - {"EREMCHG", EREMCHG, "Remote address changed"}, - {"ELIBACC", ELIBACC, "Can not access a needed shared library"}, - {"ELIBBAD", ELIBBAD, "Accessing a corrupted shared library"}, - {"ELIBSCN", ELIBSCN, ".lib section in a.out corrupted"}, - {"ELIBMAX", ELIBMAX, "Attempting to link in too many shared libraries"}, - {"ELIBEXEC", ELIBEXEC, "Cannot exec a shared library directly"}, - {"ECHRNG", ECHRNG, "Channel number out of range"}, - {"EL2NSYNC", EL2NSYNC, "Level 2 not synchronized"}, - {"EL3HLT", EL3HLT, "Level 3 halted"}, - {"EL3RST", EL3RST, "Level 3 reset"}, - {"ELNRNG", ELNRNG, "Link number out of range"}, - {"EUNATCH", EUNATCH, "Protocol driver not attached"}, - {"ENOCSI", ENOCSI, "No CSI structure available"}, - {"EL2HLT", EL2HLT, "Level 2 halted"}, - {"EBADE", EBADE, "Invalid exchange"}, - {"EBADR", EBADR, "Invalid request descriptor"}, - {"EXFULL", EXFULL, "Exchange full"}, - {"ENOANO", ENOANO, "No anode"}, - {"EBADRQC", EBADRQC, "Invalid request code"}, - {"EBADSLT", EBADSLT, "Invalid slot"}, - {"EBFONT", EBFONT, "Bad font file format"}, - {"EADV", EADV, "Advertise error"}, - {"ESRMNT", ESRMNT, "Srmount error"}, - {"ENONET", ENONET, "Machine is not on the network"}, - {"ENOPKG", ENOPKG, "Package not installed"}, - #endif - - #endif - - {"NONE", 0, "No error"}, -}; - -const char *GetErrorName(int e) +/****************************** Helper Functions **************************/ +LString BGetFullPath(BEntry &entry) { - for (ErrorCodeType *c=ErrorCodes; c->Code; c++) - { - if (e == c->Code) - { - return c->Name; - } - } - - static char s[32]; - sprintf(s, "Unknown(%i)", e); - return s; + BPath path; + auto r = entry.GetPath(&path); + if (r != B_OK) + return LString(); + return path.Path(); } -const char *GetErrorDesc(int e) +LString BGetFullPath(BDirectory &dir) { - for (ErrorCodeType *c=ErrorCodes; c->Code; c++) - { - if (e == c->Code) - return c->Desc; - } + BEntry entry; + auto r = dir.GetEntry(&entry); + if (r != B_OK) + return LString(); + return BGetFullPath(entry); +} - return 0; -} +LString BGetFullPath(BVolume &volume) +{ + BDirectory directory; + auto r = volume.GetRootDirectory(&directory); + if (r != B_OK) + return LString(); + return BGetFullPath(directory); +} -/****************************** Helper Functions **************************/ char *LReadTextFile(const char *File) { char *s = 0; LFile f; if (File && f.Open(File, O_READ)) { int Len = f.GetSize(); s = new char[Len+1]; if (s) { int Read = f.Read(s, Len); s[Read] = 0; } } return s; } int64 LFileSize(const char *FileName) { - struct stat s; - if (FileName && - stat(FileName, &s) == 0) - { - return s.st_size; - } - - return 0; + BEntry e(FileName); + if (e.InitCheck() != B_OK) + return 0; + + off_t size = 0; + if (e.GetSize(&size) != B_OK) + return 0; + + return size; } bool LDirExists(const char *FileName, char *CorrectCase) { - bool Status = false; - - if (FileName) + if (!FileName) + return false; + + struct stat s; + auto r = lstat(FileName, &s); + if (r == 0) { - struct stat s; - - // Check for exact match... - int r = lstat(FileName, &s); + return S_ISDIR(s.st_mode) || + S_ISLNK(s.st_mode); + } + else + { + r = stat(FileName, &s); if (r == 0) { - Status = S_ISDIR(s.st_mode) || - S_ISLNK(s.st_mode); - // printf("DirStatus(%s) lstat = %i, %i\n", FileName, Status, s.st_mode); - } - else - { - r = stat(FileName, &s); - if (r == 0) - { - Status = S_ISDIR(s.st_mode) || - S_ISLNK(s.st_mode); - // printf("DirStatus(%s) stat ok = %i, %i\n", FileName, Status, s.st_mode); - } - else - { - // printf("DirStatus(%s) lstat and stat failed, r=%i, errno=%i\n", FileName, r, errno); - } + return S_ISDIR(s.st_mode) || + S_ISLNK(s.st_mode); } } - return Status; + return false; } bool LFileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = !S_ISDIR(s.st_mode); } else if (CorrectCase) { // Look for altenate case by enumerating the directory char d[256]; strcpy(d, FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while ((De = readdir(Dir))) { if (stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = (char*) strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - LFileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { - bool Status = false; + if (!LinkFile || !Path || Len < 1) + return false; - return Status; + BEntry e(LinkFile); + auto r = e.InitCheck(); + if (r != B_OK) + { + printf("%s:%i - LResolveShortcut: %i\n", _FL, r); + return false; + } + + if (!e.IsSymLink()) + { + return false; + } + + r = e.SetTo(LinkFile, true); + if (r != B_OK) + { + printf("%s:%i - LResolveShortcut: %i\n", _FL, r); + return false; + } + + auto p = BGetFullPath(e); + strcpy_s(Path, Len, p); + return true; } void WriteStr(LFile &f, const char *s) { uint32_t Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(ulong) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } ///////////////////////////////////////////////////////////////////////// -#include -#include - struct LVolumePriv { - int64 _Size, _Free; - int _Type, _Flags; - LSystemPath SysPath; - LString _Name, _Path; - List _Sub; - List::I _It; + LVolume *Owner = NULL; + int64 Size = 0, Free = 0; + int Type = VT_NONE, Flags = 0; + LSystemPath SysPath = LSP_ROOT; + LString Name, Path; + LVolume *NextVol = NULL, *ChildVol = NULL; - void Init() + LVolumePriv(LVolume *owner, const char *path) : Owner(owner) { - SysPath = LSP_ROOT; - _Type = VT_NONE; - _Flags = 0; - _Size = 0; - _Free = 0; + Path = path; + Name = LGetLeaf(path); + Type = VT_FOLDER; } - LVolumePriv(const char *path) : _It(_Sub.end()) + LVolumePriv(LVolume *owner, LSystemPath sys, const char *name) : Owner(owner) { - Init(); - - _Path = path; - _Name = LGetLeaf(path); - _Type = VT_FOLDER; - } - - LVolumePriv(LSystemPath sys, const char *name) : _It(_Sub.end()) - { - Init(); SysPath = sys; + Name = name; if (SysPath == LSP_ROOT) - _Path = "/"; + Path = "/"; else - _Path = LGetSystemPath(SysPath); - if (_Path) - { - _Name = name; - _Type = sys == LSP_DESKTOP ? VT_DESKTOP : VT_FOLDER; - } + Path = LGetSystemPath(SysPath); + + if (Path) + Type = sys == LSP_DESKTOP ? VT_DESKTOP : VT_FOLDER; } ~LVolumePriv() { - _Sub.DeleteObjects(); + DeleteObj(NextVol); + DeleteObj(ChildVol); } + void Insert(LVolume *vol, LVolume *newVol) + { + if (!vol || !newVol) + return; + + if (vol->d->ChildVol) + { + for (auto v = vol->d->ChildVol; v; v = v->d->NextVol) + { + if (!v->d->NextVol) + { + LAssert(newVol != v->d->Owner); + v->d->NextVol = newVol; + // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); + break; + } + } + } + else + { + LAssert(newVol != vol->d->Owner); + vol->d->ChildVol = newVol; + // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); + } + } + LVolume *First() { - if (SysPath == LSP_DESKTOP && !_Sub.Length()) + if (SysPath == LSP_DESKTOP && !ChildVol) { // Get various shortcuts to points of interest - LVolume *v = new LVolume(LSP_ROOT, "Root"); - if (v) - _Sub.Insert(v); - + Insert(Owner, new LVolume(LSP_ROOT, "Root")); + struct passwd *pw = getpwuid(getuid()); if (pw) - { - v = new LVolume(LSP_HOME, "Home"); - if (v) - _Sub.Insert(v); - } - - // Get mount list - // this is just a hack at this stage to establish some base - // functionality. I would appreciate someone telling me how - // to do this properly. Till then... - LFile f; - if (f.Open("/etc/fstab", O_READ)) - { - auto Buf= f.Read(); - f.Close(); + Insert(Owner, new LVolume(LSP_HOME, "Home")); - auto Lines = Buf.SplitDelimit("\r\n"); - for (auto ln : Lines) - { - auto M = ln.Strip().SplitDelimit(" \t"); - if (M[0](0) != '#' && M.Length() > 2) - { - auto &Device = M[0]; - auto &Mount = M[1]; - auto &FileSys = M[2]; - - if - ( - (Device.Find("/dev/") == 0 || Mount.Find("/mnt/") == 0) - && - Device.Lower().Find("/by-uuid/") < 0 - && - Mount.Length() > 1 - && - !FileSys.Equals("swap") - ) - { - v = new LVolume(0); - if (v) - { - char *MountName = strrchr(Mount, '/'); - v->d->_Name = (MountName ? MountName + 1 : Mount.Get()); - v->d->_Path = Mount; - v->d->_Type = VT_HARDDISK; - - char *Device = M[0]; - // char *FileSys = M[2]; - if (stristr(Device, "fd")) - { - v->d->_Type = VT_FLOPPY; - } - else if (stristr(Device, "cdrom")) - { - v->d->_Type = VT_CDROM; - } - - _Sub.Insert(v); - } - } - } - } - } - - LSystemPath p[] = {LSP_USER_DOCUMENTS, + LSystemPath p[] = { LSP_USER_DOCUMENTS, LSP_USER_MUSIC, LSP_USER_VIDEO, LSP_USER_DOWNLOADS, - LSP_USER_PICTURES}; + LSP_USER_PICTURES }; for (int i=0; id->_Path = Path; - v->d->_Name = *Parts.rbegin(); - v->d->_Type = VT_FOLDER; - _Sub.Insert(v); + auto v = new LVolume(0); + if (Path && v) + { + auto Parts = Path.Split("/"); + v->d->Path = Path; + v->d->Name = *Parts.rbegin(); + v->d->Type = VT_FOLDER; + Insert(Owner, v); + } } } } - _It = _Sub.begin(); - return *_It; + return ChildVol; } LVolume *Next() { - return *(++_It); + if (SysPath == LSP_DESKTOP && !NextVol) + { + NextVol = new LVolume(LSP_MOUNT_POINT, "Mounts"); + + LHashTbl, bool> Map; + + BVolumeRoster roster; + BVolume volume; + for (auto r = roster.GetBootVolume(&volume); + r == B_OK; + r = roster.GetNextVolume(&volume)) + { + auto Path = BGetFullPath(volume); + if (!Path) + continue; + + if (Stricmp(Path.Get(), "/") == 0 || + Stricmp(Path.Get(), "/dev") == 0 || + Stricmp(Path.Get(), "/boot/system/var/shared_memory") == 0) + continue; + + if (volume.Capacity() == (4 << 10)) + continue; + + if (!volume.IsPersistent()) + continue; + + char Name[B_FILE_NAME_LENGTH]; + if (volume.GetName(Name) != B_OK) + continue; + + auto Done = Map.Find(Name); + if (Done) + continue; + Map.Add(Name, true); + + auto v = new LVolume(0); + if (v) + { + v->d->Name = Name; + v->d->Path = Path; + v->d->Type = VT_HARDDISK; + v->d->Size = volume.Capacity(); + v->d->Free = volume.FreeBytes(); + + // printf("%s, %s\n", Name, v->d->Path.Get()); + + Insert(NextVol, v); + } + } + } + + return NextVol; } }; -LVolume::LVolume(const char *Path) +LVolume::LVolume(const char *Path = NULL) { - d = new LVolumePriv(Path); + d = new LVolumePriv(this, Path); } LVolume::LVolume(LSystemPath SysPath, const char *Name) { - d = new LVolumePriv(SysPath, Name); + d = new LVolumePriv(this, SysPath, Name); } LVolume::~LVolume() { DeleteObj(d); } -const char *LVolume::Name() +const char *LVolume::Name() const { - return d->_Name; + return d->Name; } -const char *LVolume::Path() +const char *LVolume::Path() const { - return d->_Path; + return d->Path; } -int LVolume::Type() +int LVolume::Type() const { - return d->_Type; + return d->Type; } -int LVolume::Flags() +int LVolume::Flags() const { - return d->_Flags; + return d->Flags; } -uint64 LVolume::Size() +uint64 LVolume::Size() const { - return d->_Size; + return d->Size; } -uint64 LVolume::Free() +uint64 LVolume::Free() const { - return d->_Free; + return d->Free; } -LSurface *LVolume::Icon() +LSurface *LVolume::Icon() const { return NULL; } -bool LVolume::IsMounted() +bool LVolume::IsMounted() const { return true; } bool LVolume::SetMounted(bool Mount) { return Mount; } LVolume *LVolume::First() { return d->First(); } LVolume *LVolume::Next() { return d->Next(); } void LVolume::Insert(LAutoPtr v) { - d->_Sub.Insert(v.Release()); + d->Insert(this, v.Release()); } LDirectory *LVolume::GetContents() { LDirectory *Dir = 0; - if (d->_Path) + if (d->Path) { Dir = new LDirectory; - if (Dir && !Dir->First(d->_Path)) + if (Dir && !Dir->First(d->Path)) DeleteObj(Dir); } return Dir; } /////////////////////////////////////////////////////////////////////////////// LFileSystem *LFileSystem::Instance = 0; LFileSystem::LFileSystem() { Instance = this; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); } void LFileSystem::OnDeviceChange(char *Reserved) { } LVolume *LFileSystem::GetRootVolume() { if (!Root) Root = new LVolume(LSP_DESKTOP, "Desktop"); return Root; } bool LFileSystem::Copy(const char *From, const char *To, LError *Status, CopyFileCallback Callback, void *Token) { LArray Buf; if (Status) *Status = 0; if (Buf.Length(2 << 20)) { LFile In, Out; if (!In.Open(From, O_READ)) { if (Status) *Status = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (Status) *Status = Out.GetError(); return false; } int64 Size = In.GetSize(), Done = 0; for (int64 i=0; i 0) { int w = Out.Write(&Buf[0], r); if (w <= 0) break; r -= w; Done += w; if (Callback) Callback(Token, Done, Size); } if (r > 0) break; } return Done == Size; } return false; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { char p[MAX_PATH_LEN]; if (LGetSystemPath(LSP_TRASH, p, sizeof(p))) { for (int i=0; i f; f.Add(FileName); return Delete(f, 0, ToTrash); } return false; } bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentTree, LError *ErrorCode) { int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; if (CreateParentTree) { LFile::Path p(PathName); LString dir = DIR_STR; for (size_t i=1; i<=p.Length(); i++) { LFile::Path Par(dir + dir.Join(p.Slice(0, i))); if (!Par.Exists()) { const char *ParDir = Par; r = mkdir(ParDir, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; break; } LgiTrace("%s:%i - Created '%s'\n", _FL, Par.GetFull().Get()); } } if (p.Exists()) return true; } LgiTrace("%s:%i - mkdir('%s') failed with %i, errno=%i\n", _FL, PathName, r, errno); } return r == 0; } bool LFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { - LDirectory *Dir = new LDirectory; - if (Dir && Dir->First(PathName)) + LDirectory Dir; + if (Dir.First(PathName)) { do { - char Str[256]; - Dir->Path(Str, sizeof(Str)); + char Str[MAX_PATH_LEN]; + Dir.Path(Str, sizeof(Str)); - if (Dir->IsDir()) - { + if (Dir.IsDir()) RemoveFolder(Str, Recurse); - } else - { Delete(Str, false); - } } - while (Dir->Next()); + while (Dir.Next()); } - DeleteObj(Dir); } return rmdir(PathName) == 0; } bool LFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (rename(OldName, NewName)) { printf("%s:%i - rename failed, error: %s(%i)\n", - _FL, GetErrorName(errno), errno); + _FL, LErrorCodeToString(errno), errno); return false; } return true; } - -/* -bool Match(char *Name, char *Mask) -{ - strupr(Name); - strupr(Mask); - - while (*Name && *Mask) - { - if (*Mask == '*') - { - if (*Name == *(Mask+1)) - { - Mask++; - } - else - { - Name++; - } - } - else if (*Mask == '?' || *Mask == *Name) - { - Mask++; - Name++; - } - else - { - return false; - } - } - - while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; - - return (*Name == 0 && *Mask == 0); -} -*/ - -short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -int LeapYear(int year) -{ - if (year & 3) - { - return 0; - } - if ((year % 100 == 0) && !(year % 400 == 0)) - { - return 0; - } - - return 1; -} - -///////////////////////////////////////////////////////////////////////////////// -bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const -{ - time_t k = Time; - struct tm *t = localtime(&k); - if (t) - { - strftime(Str, SLen, "%I:%M:%S", t); - return true; - } - return false; -} - -bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const -{ - time_t k = Time; - struct tm *t = localtime(&k); - if (t) - { - strftime(Str, SLen, "%d/%m/%y", t); - return true; - } - return false; -} - -///////////////////////////////////////////////////////////////////////////////// -//////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// struct LDirectoryPriv { char BasePath[MAX_PATH_LEN]; - DIR *Dir; - struct dirent *De; + char *BaseEnd = NULL; + DIR *Dir = NULL; + struct dirent *De = NULL; struct stat Stat; LString Pattern; LDirectoryPriv() { - Dir = 0; - De = 0; BasePath[0] = 0; } bool Ignore() { return De && ( strcmp(De->d_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } int LDirectory::First(const char *Name, const char *Pattern) { Close(); if (!Name) return 0; - strcpy(d->BasePath, Name); + strcpy_s(d->BasePath, sizeof(d->BasePath), Name); + d->BaseEnd = NULL; + if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = Dir; } } } } else { d->Pattern = Pattern; } d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore() && !Next()) return false; } } return d->Dir != NULL && d->De != NULL; } int LDirectory::Next() { int Status = false; while (d->Dir && d->De) { if ((d->De = readdir(d->Dir))) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } return Status; } int LDirectory::Close() { if (d->Dir) { closedir(d->Dir); - d->Dir = 0; + d->Dir = NULL; } - d->De = 0; + d->De = NULL; + d->BaseEnd = NULL; + d->BasePath[0] = 0; return true; } const char *LDirectory::FullPath() { - static char s[MAX_PATH_LEN]; - #warning this should really be optimized, and thread safe... - Path(s, sizeof(s)); - return s; + auto nm = GetName(); + if (!nm) + return NULL; + + if (!d->BaseEnd) + { + d->BaseEnd = d->BasePath + strlen(d->BasePath); + if (d->BaseEnd > d->BasePath && + d->BaseEnd[-1] != DIR_CHAR) + { + *d->BaseEnd++ = DIR_CHAR; + } + } + + auto used = d->BaseEnd - d->BasePath; + auto remaining = sizeof(d->BasePath) - used; + + strcpy_s(d->BaseEnd, remaining, nm); + return d->BasePath; } LString LDirectory::FileName() const { return GetName(); } bool LDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LMakePath(s, BufLen, d->BasePath, GetName()); } int LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int LDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool LDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool LDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool LDirectory::IsDir() const { int a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } bool LDirectory::IsSymLink() const { int a = GetAttributes(); return S_ISLNK(a); } long LDirectory::GetAttributes() const { return d->Stat.st_mode; } char *LDirectory::GetName() const { return (d->De) ? d->De->d_name : NULL; } uint64 LDirectory::GetCreationTime() const { return (uint64) d->Stat.st_ctime * LDateTime::Second64Bit; } uint64 LDirectory::GetLastAccessTime() const { return (uint64) d->Stat.st_atime * LDateTime::Second64Bit; } uint64 LDirectory::GetLastWriteTime() const { return (uint64) d->Stat.st_mtime * LDateTime::Second64Bit; } uint64 LDirectory::GetSize() const { - return (uint32_t)d->Stat.st_size; + return d->Stat.st_size; } int64 LDirectory::GetSizeOnDisk() { - return (uint32_t)d->Stat.st_size; + return d->Stat.st_size; } -///////////////////////////////////////////////////////////////////////////////// -//////////////////////////// File /////////////////////////////////////////////// +bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const +{ + time_t k = Time; + struct tm *t = localtime(&k); + if (t) + { + strftime(Str, SLen, "%I:%M:%S", t); + return true; + } + return false; +} + +bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const +{ + time_t k = Time; + struct tm *t = localtime(&k); + if (t) + { + strftime(Str, SLen, "%d/%m/%y", t); + return true; + } + return false; +} + ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int ErrorCode; LFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; ErrorCode = 0; } ~LFilePrivate() { DeleteArray(Name); } }; LFile::LFile(const char *Path, int Mode) { d = new LFilePrivate; if (Path) Open(Path, Mode); } LFile::~LFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile LFile::Handle() { return d->hFile; } int LFile::GetError() { return d->ErrorCode; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } #define DEBUG_OPEN_FILES 0 #if DEBUG_OPEN_FILES LMutex Lck; LArray OpenFiles; #endif int LFile::Open(const char *File, int Mode) { int Status = false; if (File) { if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode #ifdef O_LARGEFILE | O_LARGEFILE #endif , S_IRUSR | S_IWUSR); if (ValidHandle(d->hFile)) { d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } Status = true; d->Status = true; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { if (!OpenFiles.HasItem(this)) OpenFiles.Add(this); Lck.Unlock(); } #endif } else { d->ErrorCode = errno; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { for (unsigned i=0; iGetName()); Lck.Unlock(); } #endif - printf("LFile::Open failed\n\topen(%s,%08.8x) = %i\n\terrno=%s (%s)\n", + printf("LFile::Open failed\n\topen(%s,%08.8x) = %i\n\terrno=%s\n", File, Mode, d->hFile, - GetErrorName(d->ErrorCode), - GetErrorDesc(d->ErrorCode)); + LErrorCodeToString(d->ErrorCode)); } } return Status; } int LFile::Close() { if (ValidHandle(d->hFile)) { close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { OpenFiles.Delete(this); Lck.Unlock(); } #endif } return true; } +uint64_t LFile::GetModifiedTime() +{ + struct stat s; + stat(d->Name, &s); + return s.st_mtime; +} + +bool LFile::SetModifiedTime(uint64_t dt) +{ + struct stat s; + stat(d->Name, &s); + + struct timeval times[2] = {}; + times[0].tv_sec = s.st_atime; + times[1].tv_sec = dt; + + int r = utimes(d->Name, times); + + return r == 0; +} + void LFile::ChangeThread() { } #define CHUNK 0xFFF0 ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { int Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); } d->Status = Red == Size; return MAX(Red, 0); } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { int Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); } d->Status = Written == Size; return MAX(Written, 0); } #ifdef LINUX #define LINUX64 1 #endif int64 LFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 LFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 LFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 LFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else int64 Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 LFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool LFile::Eof() { return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; Buf = &Buf[Size-1]; while (Size--) { r = read(d->hFile, Buf--, 1); i += r; } return i; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w = 0; Buf = &Buf[Size-1]; while (Size--) { w = write(d->hFile, Buf--, 1); i += w; } return i; } ssize_t LFile::ReadStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; if (Buf && Size > 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } const char *LFile::GetName() { return d->Name; } #define GFileOp(type) LFile &LFile::operator >> (type &i) { d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) { d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp ////////////////////////////////////////////////////////////////////////////// +LString LFile::Path::Sep(DIR_STR); + bool LFile::Path::FixCase() { LString Prev; bool Changed = false; // printf("FixCase '%s'\n", GetFull().Get()); for (size_t i=0; i %s\n", part.Get(), name); Part = Name; Next = (Prev ? Prev : "") + Sep + Part; Changed = true; } } } Prev = Next; } return Changed; } diff --git a/src/haiku/General.cpp b/src/haiku/General.cpp --- a/src/haiku/General.cpp +++ b/src/haiku/General.cpp @@ -1,432 +1,367 @@ // Linux Implementation of General LGI functions #include #include #include #include #include +#include "MimeType.h" +#include "StringList.h" +#include "Path.h" + #define _POSIX_TIMERS #include #include "lgi/common/Lgi.h" #include "lgi/common/SubProcess.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Token.h" #include #include +#include #define DEBUG_GET_APPS_FOR_MIMETYPE 0 //////////////////////////////////////////////////////////////// -// Local helper functions -bool _lgi_check_file(char *Path) -{ - if (Path) - { - if (LFileExists(Path)) - { - // file is there - return true; - } - else - { - // shortcut? - char *e = Path + strlen(Path); - strcpy(e, ".lnk"); - if (LFileExists(Path)) - { - // resolve shortcut - char Link[256]; - if (LResolveShortcut(Path, Link, sizeof(Link))) - { - // check destination of link - if (LFileExists(Link)) - { - strcpy(Path, Link); - return true; - } - } - } - *e = 0; - } - } - - return false; -} - LString LCurrentUserName() { struct passwd *pw = getpwuid(geteuid()); if (pw) return pw->pw_name; - return ""; + return LString(); } - -void LSleep(uint32_t i) +void LSleep(uint32_t ms) { struct timespec request, remain; ZeroObj(request); ZeroObj(remain); - request.tv_sec = i / 1000; - request.tv_nsec = (i % 1000) * 1000000; + request.tv_sec = ms / 1000; + request.tv_nsec = (ms % 1000) * 1000000; - //printf("%i LSleep(%i)\n", LGetCurrentThread(), i); while (nanosleep(&request, &remain) == -1) - { request = remain; - //printf("\t%i Resleeping=%i\n", LGetCurrentThread(), request.tv_sec*1000 + request.tv_nsec/1000); - } } void _lgi_assert(bool b, const char *test, const char *file, int line) { static bool Asserting = false; if (!b && !Asserting) { Asserting = true; printf("%s:%i - Assert failed:\n%s\n", file, line, test); - int *i = 0; - *i = 0; - exit(0); + + LString msg; + msg.Printf("Assert failed: %s\n%s:%i", test, file, line); + BAlert *alert = new BAlert("Assert", msg, "Abort", "Debug", "Ignore"); + auto result = alert->Go(); + printf("alert result=%i\n", result); + switch (result) + { + case 0: // Abort + { + exit(-1); + break; + } + case 1: // Debug + { + int *i = 0; + *i = 0; + break; + } + // default: Ignore.. + } Asserting = false; } } //////////////////////////////////////////////////////////////////////// // Implementations LMessage CreateMsg(int m, int a, int b) { return LMessage(m, a, b); } bool LGetMimeTypeExtensions(const char *Mime, LArray &Ext) { int Start = Ext.Length(); char *e; #define HardCodeExtention(Mime, Ext1, Ext2) \ else if (!stricmp(Mime, Mime)) \ { if (Ext1) Ext.Add(Ext1); \ if (Ext2) Ext.Add(Ext2); } const char *Null = NULL; if (!Mime); HardCodeExtention("text/calendar", "ics", Null) HardCodeExtention("text/x-vcard", "vcf", Null) HardCodeExtention("text/mbox", "mbx", "mbox"); return Ext.Length() > Start; } LString LGetFileMimeType(const char *File) { return LAppInst->GetFileMimeType(File); } -bool _GetSystemFont(char *FontType, char *Font, int FontBufSize, int &PointSize) +static bool MimeTypeToAppInfo(LAppInfo &app, LString MimeType) { - bool Status = false; + BMimeType appType; + auto r = appType.SetTo(MimeType); + if (r != B_OK) + { + LgiTrace("%s:%i - appType.SetTo(%s) failed: %i.\n", _FL, MimeType.Get(), r); + return false; + } + + entry_ref ref; + r = appType.GetAppHint(&ref); + if (r != B_OK) + { + LgiTrace("%s:%i - GetAppHint failed: %i\n", _FL, r); + return false; + } + + app.MimeType = MimeType; + app.Name = ref.name; - LAssert(!"Impl me."); - - return Status; + BEntry entry(&ref); + BPath path; + if (entry.GetPath(&path) == B_OK) + app.Path = path.Path(); + + return true; } -bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) +bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) { - bool Status = false; - - char Args[MAX_PATH_LEN]; - sprintf(Args, "query default %s", Mime); - LStringPipe Output; - - LLanguage *CurLang = LGetLanguageId(); - char LangName[64]; - sprintf_s(LangName, sizeof(LangName), "Name[%s]", CurLang ? CurLang->Id : "en"); - - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf("LGetAppsForMimeType('%s', ..., %i)\nRunning 'xdg-mime %s'\n", - Mime, Limit, Args); - #endif - - LSubProcess p("xdg-mime", Args); - if (p.Start()) + BMimeType mt(Mime); + + BMessage msg; + auto r = mt.GetSupportingApps(&msg); + if (r != B_OK) { - p.Communicate(&Output); - LAutoString o(Output.NewStr()); - - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf("Output:\n%s\n", o.Get()); - #endif - if (o) - { - char *e = o + strlen(o); - while (e > o && strchr(" \t\r\n", e[-1])) - *(--e) = 0; - - char p[MAX_PATH_LEN]; - if (LMakePath(p, sizeof(p), "/usr/share/applications", o)) - { - if (LFileExists(p)) - { - LAutoString txt(LReadTextFile(p)); - LAutoString Section; - - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf("Reading '%s', got %i bytes\n", p, strlen(txt)); - #endif - - if (txt) - { - LAppInfo *ai = new LAppInfo; - Apps.Add(ai); - - LToken t(txt, "\n"); - for (int i=0; iPath.Set(exe, sp-exe); - else - ai->Path = exe; - - Status = true; - } - else if (!stricmp(Var, "Name") || - !stricmp(Var, LangName)) - { - ai->Name = Value; - } - } - } - } + auto &app = Apps.New(); + if (!MimeTypeToAppInfo(app, s.String())) + Apps.PopLast(); + } - #if DEBUG_GET_APPS_FOR_MIMETYPE - printf(" ai='%s' '%s'\n", ai->Name.Get(), ai->Path.Get()); - #endif - } - else LgiTrace("%s:%i - Can't read from '%s'\n", _FL, p); - } - else LgiTrace("%s:%i - '%s' doesn't exist.", _FL, p); - } - else LgiTrace("%s:%i - Failed to create path.\n", _FL); - } - else LgiTrace("%s:%i - No output from xdg-mime\n", _FL); - } - else LgiTrace("%s:%i - Failed to execute xdg-mime\n", _FL); - - return Status; + return true; } LString LGetAppForMimeType(const char *Mime) { - LString App; - LArray Apps; - if (LGetAppsForMimeType(Mime, Apps, 1)) - App = Apps[0]->Path.Get(); - Apps.DeleteObjects(); - return App; + BMimeType mt(Mime); + + char app[B_MIME_TYPE_LENGTH+1] = {}; + auto r = mt.GetPreferredApp(app); + if (r != B_OK) + { + printf("%s:%i - GetPreferredApp for %s failed: %i.\n", _FL, Mime, r); + return false; + } + + LAppInfo info; + if (!MimeTypeToAppInfo(info, app)) + return false; + + // printf("LGetAppForMimeType(%s)=%s\n", Mime, info.Path.Get()); + return info.Path; } int LRand(int Limit) { return rand() % Limit; } LString LErrorCodeToString(uint32_t ErrorCode) { LString e = strerror(ErrorCode); if (!e) e.Printf("UnknownError(%i)", ErrorCode); return e; } bool LExecute(const char *File, const char *Args, const char *Dir, LString *Error) { if (File) { bool IsUrl = false; char App[MAX_PATH_LEN] = ""; if (strnicmp(File, "http://", 7) == 0 || strnicmp(File, "https://", 8) == 0) { IsUrl = true; LGetAppForMimeType("text/html", App, sizeof(App)); } else { struct stat f; char Path[MAX_PATH_LEN]; // see if the file is executable bool InPath = false; bool Ok = stat(File, &f) == 0; if (Ok) { strcpy_s(Path, sizeof(Path), File); } else { // look in the path InPath = true; LToken p(getenv("PATH"), LGI_PATH_SEPARATOR); for (int i=0; iPrintf("'%s' doesn't exist.\n", File); } if (App[0]) { bool FileAdded = false; LString AppPath; LString EscFile = LString::Escape(File, -1, " &"); LString a = App; int Pos; while ((Pos = a.Find("%")) >= 0) { char *s = a.Get() + Pos; printf("a='%s'\n", a.Get()); switch (s[1]) { case 'f': case 'F': { a = a(0,Pos) + EscFile + a(Pos+2,-1); FileAdded = true; break; } case 'u': case 'U': { if (IsUrl) a = a(0,Pos) + EscFile + a(Pos+2,-1); else a = a(0,Pos) + LString("file:") + EscFile + a(Pos+2,-1); FileAdded = true; break; } default: { // we don't understand this command a = a(0,Pos) + a(Pos+2,-1); break; } } printf("a='%s'\n", a.Get()); } if (!FileAdded) { a += " "; a += EscFile; } a += " > /dev/null 2> /dev/null &"; int e; if (Dir) chdir(Dir); printf("a=\n%s\n", a.Get()); if (e = system(a)) { if (Error) *Error = LErrorCodeToString(errno); return false; } return true; } } return false; } bool LPlaySound(const char *FileName, int ASync) { LAssert(!"Impl me."); return false; } diff --git a/src/haiku/Layout.cpp b/src/haiku/Layout.cpp --- a/src/haiku/Layout.cpp +++ b/src/haiku/Layout.cpp @@ -1,316 +1,303 @@ /* ** FILE: Layout.cpp ** AUTHOR: Matthew Allen ** DATE: 1/12/2021 ** DESCRIPTION: Standard Views ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Notifications.h" -#define M_SET_SCROLL (M_USER + 0x2000) - ////////////////////////////////////////////////////////////////////////////// LLayout::LLayout() { _PourLargest = false; VScroll = 0; HScroll = 0; } LLayout::~LLayout() { DeleteObj(HScroll); DeleteObj(VScroll); } LViewI *LLayout::FindControl(int Id) { if (VScroll && VScroll->GetId() == Id) return VScroll; if (HScroll && HScroll->GetId() == Id) return HScroll; return LView::FindControl(Id); } bool LLayout::GetPourLargest() { return _PourLargest; } void LLayout::SetPourLargest(bool i) { _PourLargest = i; } bool LLayout::Pour(LRegion &r) { if (_PourLargest) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } } return false; } void LLayout::GetScrollPos(int64 &x, int64 &y) { - if (HScroll) - { - x = HScroll->Value(); - } - else - { - x = 0; - } - - if (VScroll) - { - y = VScroll->Value(); - } - else - { - y = 0; - } + x = HScroll ? HScroll->Value() : 0; + y = VScroll ? VScroll->Value() : 0; + + printf("GetScrollPos=%i,%i\n", (int)x, (int)y); } void LLayout::SetScrollPos(int64 x, int64 y) { if (HScroll) - { HScroll->Value(x); - } - if (VScroll) - { VScroll->Value(y); - } } bool LLayout::Attach(LViewI *p) { bool Status = LView::Attach(p); AttachScrollBars(); return Status; } bool LLayout::Detach() { return LView::Detach(); } void LLayout::OnCreate() { AttachScrollBars(); OnPosChange(); } void LLayout::AttachScrollBars() { if (HScroll && !HScroll->IsAttached()) { - // LRect r = HScroll->GetPos(); + HScroll->Visible(true); HScroll->Attach(this); HScroll->SetNotify(this); } if (VScroll && !VScroll->IsAttached()) { - // LRect r = VScroll->GetPos(); + VScroll->Visible(true); VScroll->Attach(this); VScroll->SetNotify(this); } } bool LLayout::SetScrollBars(bool x, bool y) { - #ifdef M_SET_SCROLL if (x ^ (HScroll != NULL) || y ^ (VScroll != NULL)) { - if (_SetScroll.x != x || - _SetScroll.y != y || - !_SetScroll.SentMsg) + // printf("%s:%i - setScroll %i,%i attached=%i\n", _FL, x, y, IsAttached()); + + if (!IsAttached()) + { + _SetScrollBars(x, y); + } + else if (_SetScroll.x != x || + _SetScroll.y != y || + !_SetScroll.SentMsg) { // This is to filter out masses of duplicate messages // before they have a chance to be processed. Esp important on // GTK systems where the message handling isn't very fast. _SetScroll.x = x; _SetScroll.y = y; _SetScroll.SentMsg = true; - return PostEvent(M_SET_SCROLL, x, y); + + auto r = PostEvent(M_SET_SCROLL, x, y); + if (!r) + printf("%s:%i - sending M_SET_SCROLL(%i,%i) to myself=%s, r=%i, attached=%i.\n", _FL, x, y, GetClass(), r, IsAttached()); + return r; } // Duplicate... ignore... return true; } - #else - _SetScrollBars(x, y); - #endif return true; } bool LLayout::_SetScrollBars(bool x, bool y) { static bool Processing = false; + // printf("%s:%i - _setScroll %i,%i %i\n", _FL, x, y, Processing); + if (!Processing && (((HScroll!=0) ^ x ) || ((VScroll!=0) ^ y )) ) { Processing = true; if (x) { if (!HScroll) { HScroll = new LScrollBar(IDC_HSCROLL, 0, 0, 100, 10, "LLayout->HScroll"); if (HScroll) { HScroll->SetVertical(false); HScroll->Visible(false); } } } else { DeleteObj(HScroll); } if (y) { if (!VScroll) { VScroll = new LScrollBar(IDC_VSCROLL, 0, 0, 10, 100, "LLayout->VScroll"); if (VScroll) { VScroll->Visible(false); } } } else if (VScroll) { DeleteObj(VScroll); } AttachScrollBars(); OnPosChange(); Invalidate(); Processing = false; } return true; } int LLayout::OnNotify(LViewI *c, LNotification n) { return LView::OnNotify(c, n.Type); } void LLayout::OnPosChange() { LRect r = LView::GetClient(); - LRect v(r.x2-LScrollBar::SCROLL_BAR_SIZE+1, r.y1, r.x2, r.y2); - LRect h(r.x1, r.y2-LScrollBar::SCROLL_BAR_SIZE+1, r.x2, r.y2); + auto Px = LScrollBar::GetScrollSize(); + LRect v(r.x2-Px+1, r.y1, r.x2, r.y2); + LRect h(r.x1, r.y2-Px+1, r.x2, r.y2); if (VScroll && HScroll) { h.x2 = v.x1 - 1; v.y2 = h.y1 - 1; } - + if (VScroll) { VScroll->Visible(true); VScroll->SetPos(v, true); } if (HScroll) { HScroll->Visible(true); HScroll->SetPos(h, true); } } void LLayout::OnNcPaint(LSurface *pDC, LRect &r) { LView::OnNcPaint(pDC, r); if (VScroll && VScroll->Visible()) { r.x2 -= VScroll->X(); } if (HScroll && HScroll->Visible()) { r.y2 -= HScroll->Y(); } if (VScroll && VScroll->Visible() && HScroll && HScroll->Visible()) { // Draw square at the end of each scroll bar LRect s( VScroll->GetPos().x1, HScroll->GetPos().y1, VScroll->GetPos().x2, HScroll->GetPos().y2); pDC->Colour(L_MED); pDC->Rectangle(&s); } } LRect &LLayout::GetClient(bool ClientSpace) { static LRect r; r = LView::GetClient(ClientSpace); if (VScroll && VScroll->Visible()) { + // printf("\tLayout.GetCli r=%s -> ", r.GetStr()); r.x2 = VScroll->GetPos().x1 - 1; + // printf("%s\n", r.GetStr()); } if (HScroll && HScroll->Visible()) - { r.y2 = HScroll->GetPos().y1 - 1; - } return r; } LMessage::Param LLayout::OnEvent(LMessage *Msg) { - #ifdef M_SET_SCROLL if (Msg->Msg() == M_SET_SCROLL) { _SetScroll.SentMsg = false; + // printf("%s:%i - receiving M_SET_SCROLL myself=%s.\n", _FL, GetClass()); _SetScrollBars(Msg->A(), Msg->B()); if (HScroll) HScroll->SendNotify(LNotifyScrollBarCreate); if (VScroll) VScroll->SendNotify(LNotifyScrollBarCreate); + return 0; } - #endif - // if (VScroll) VScroll->OnEvent(Msg); - // if (HScroll) HScroll->OnEvent(Msg); int Status = LView::OnEvent(Msg); if (Msg->Msg() == M_CHANGE && Status == -1 && GetParent()) { Status = GetParent()->OnEvent(Msg); } return Status; } diff --git a/src/haiku/MemDC.cpp b/src/haiku/MemDC.cpp --- a/src/haiku/MemDC.cpp +++ b/src/haiku/MemDC.cpp @@ -1,322 +1,351 @@ /*hdr ** FILE: MemDC.cpp ** AUTHOR: Matthew Allen ** DATE: 14/10/2000 ** DESCRIPTION: GDC v2.xx header ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include +#include "Screen.h" +#include "Region.h" + #include "lgi/common/Gdc2.h" #include "lgi/common/LgiString.h" #include ///////////////////////////////////////////////////////////////////////////////////////////////////// #define ROUND_UP(bits) (((bits) + 7) / 8) class LMemDCPrivate { public: ::LArray Client; LColourSpace CreateCs = CsNone; BBitmap *Bmp = NULL; + BView *View = NULL; LMemDCPrivate() { } ~LMemDCPrivate() { Empty(); } void Empty() { } }; LMemDC::LMemDC(int x, int y, LColourSpace cs, int flags) { d = new LMemDCPrivate; if (cs != CsNone) Create(x, y, cs, flags); } LMemDC::LMemDC(LSurface *pDC) { d = new LMemDCPrivate; if (pDC && Create(pDC->X(), pDC->Y(), pDC->GetColourSpace())) { Blt(0, 0, pDC); } } LMemDC::~LMemDC() { Empty(); DeleteObj(d); } OsBitmap LMemDC::GetBitmap() { return d->Bmp; } OsPainter LMemDC::Handle() { - return NULL; + if (!d->View && d->Bmp) + { + d->View = new BView(d->Bmp->Bounds(), "BBitmapView", B_FOLLOW_NONE, B_WILL_DRAW); + if (d->View) + d->Bmp->AddChild(d->View); + } + + return d->View; } void LMemDC::SetClient(LRect *c) { if (c) { Handle(); LRect Doc; if (d->Client.Length()) Doc = d->Client.Last(); else Doc = Bounds(); LRect r = *c; r.Bound(&Doc); d->Client.Add(r); Clip = r; OriginX = -r.x1; OriginY = -r.y1; } else { if (d->Client.Length()) d->Client.PopLast(); if (d->Client.Length()) { auto &r = d->Client.Last(); OriginX = -r.x1; OriginY = -r.y1; Clip = r; } else { OriginX = 0; OriginY = 0; Clip.ZOff(pMem->x-1, pMem->y-1); } } } void LMemDC::Empty() { d->Empty(); DeleteObj(pMem); } bool LMemDC::Lock() { return false; } bool LMemDC::Unlock() { return false; } bool LMemDC::Create(int x, int y, LColourSpace Cs, int Flags) { - BRect b(0, 0, x, y); - d->Bmp = new BBitmap(b, B_RGB32, false, true); + BRect b(0, 0, x-1, y-1); + d->Bmp = new BBitmap(b, B_RGB32, true, true); if (!d->Bmp || d->Bmp->InitCheck() != B_OK) { DeleteObj(d->Bmp); LgiTrace("%s:%i - Failed to create memDC(%i,%i)\n", _FL, x, y); return false; } - + pMem = new LBmpMem; if (!pMem) return false; - pMem->x = d->Bmp->Bounds().Width(); - pMem->y = d->Bmp->Bounds().Height(); - ColourSpace = pMem->Cs = System32BitColourSpace; - pMem->Line = d->Bmp->BytesPerRow(); - pMem->Base = (uchar*)d->Bmp->Bits(); + pMem->x = x; + pMem->y = y; + pMem->Cs = ColourSpace = System32BitColourSpace; + pMem->Line = d->Bmp->BytesPerRow(); + pMem->Base = (uchar*)d->Bmp->Bits(); int NewOp = (pApp) ? Op() : GDC_SET; if ((Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; ix, pMem->y, LColourSpaceToString(pMem->Cs), pMem->Line, pMem->Base); return true; } void LMemDC::Blt(int x, int y, LSurface *Src, LRect *a) { if (!Src) return; bool Status = false; LBlitRegions br(this, x, y, Src, a); if (!br.Valid()) return; LScreenDC *Screen; if ((Screen = Src->IsScreen())) { - if (pMem->Base) + BScreen scr; + BBitmap *bitmap = NULL; + BRect src = br.SrcClip; + auto r = scr.GetBitmap(&bitmap, TestFlag(Flags, GDC_CAPTURE_CURSOR), &src); + if (r == B_OK) { - + bitmap->LockBits(); + + int dstPx = GetBits() / 8; + size_t srcPx = 4, row = 0, chunk = 0; + get_pixel_size_for(bitmap->ColorSpace(), &srcPx, &row, &chunk); + + for (int y=0; yBits()) + (bitmap->BytesPerRow() * (br.SrcClip.y1 + y)) + (br.SrcClip.x1 * srcPx); + LAssert(!"Impl pixel converter."); + } + + bitmap->UnlockBits(); } - - if (!Status) + else { Colour(Rgb24(255, 0, 255), 24); - Rectangle(); + Rectangle(a); } + + delete bitmap; } else if ((*Src)[0]) { // Memory -> Memory (Source alpha used) LSurface::Blt(x, y, Src, a); } } void LMemDC::StretchBlt(LRect *d, LSurface *Src, LRect *s) { LAssert(!"Not implemented"); } void LMemDC::SetOrigin(int x, int y) { } bool LMemDC::SupportsAlphaCompositing() { return true; } void LMemDC::GetOrigin(int &x, int &y) { LSurface::GetOrigin(x, y); } LRect LMemDC::ClipRgn(LRect *Rgn) { LRect Old = Clip; if (Rgn) { LRect Dc(0, 0, X()-1, Y()-1); Clip = *Rgn; Clip.Offset(-OriginX, -OriginY); Clip.Bound(&Dc); } else { Clip.ZOff(X()-1, Y()-1); } return Old; } void LMemDC::HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b) { if (x1 > x2) LSwap(x1, x2); if (x1 < Clip.x1) x1 = Clip.x1; if (x2 > Clip.x2) x2 = Clip.x2; if (x1 <= x2 && y >= Clip.y1 && y <= Clip.y2 && pApp) { COLOUR Prev = pApp->c; pApp->SetPtr(x1, y); for (; x1 <= x2; x1++) { if (x1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncX(); } pApp->c = Prev; } } void LMemDC::VertLine(int x, int y1, int y2, COLOUR a, COLOUR b) { if (y1 > y2) LSwap(y1, y2); if (y1 < Clip.y1) y1 = Clip.y1; if (y2 > Clip.y2) y2 = Clip.y2; if (y1 <= y2 && x >= Clip.x1 && x <= Clip.x2 && pApp) { COLOUR Prev = pApp->c; pApp->SetPtr(x, y1); for (; y1 <= y2; y1++) { if (y1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncY(); } pApp->c = Prev; } } diff --git a/src/haiku/Menu.cpp b/src/haiku/Menu.cpp --- a/src/haiku/Menu.cpp +++ b/src/haiku/Menu.cpp @@ -1,1093 +1,1168 @@ /*hdr ** FILE: Menu.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku menus ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" -#define DEBUG_MENUS 0 +#include +#include +#include +#include + +#define DEBUG_MENUS 1 #if DEBUG_MENUS #define LOG(...) printf(__VA_ARGS__) #else - #define LOG(...) + #define LOG(...) ; #endif +struct MenuLock : public LLocker +{ + LMenu *menu; + LViewI *lwnd; + BWindow *bwnd; + bool unattached; + + template + BHandler *Init(T *s) + { + menu = NULL; + lwnd = NULL; + bwnd = NULL; + unattached = false; + + if (s) + menu = s->GetMenu(); + if (menu) + lwnd = menu->WindowHandle(); + if (lwnd) + bwnd = lwnd->WindowHandle(); + // printf("%s:%i - %p,%p,%p\n", _FL, menu, lwnd, bwnd); + return bwnd; + } + + template + MenuLock(T *s, const char *file, int line) : + LLocker(Init(s), file, line) + { + /* + printf("%s:%i - MenuLock %p %p, %p %p %p\n", file, line, + bwnd, hnd, + dynamic_cast(bwnd), static_cast(bwnd), (BHandler*)bwnd); + */ + + if (!bwnd) + { + unattached = true; + } + else if (!Lock()) + { + if (bwnd) + unattached = bwnd->Thread() < 0; + if (!unattached) + printf("%s:%i - Failed to lock (%p,%p,%p,%i).\n", file, line, menu, lwnd, bwnd, bwnd ? bwnd->Thread() : 0); + } + } + + operator bool() const + { + return locked || unattached; + } +}; + /////////////////////////////////////////////////////////////////////////////////////////////// static ::LArray Active; +LSubMenu::LSubMenu(OsSubMenu Hnd) +{ + Active.Add(this); + Info = Hnd; +} + LSubMenu::LSubMenu(const char *name, bool Popup) { - Menu = NULL; - Parent = NULL; - Info = NULL; - Active.Add(this); + Info = new BPopUpMenu(name); + printf("Info=%p\n", Info); + if (name) + Name(name); } LSubMenu::~LSubMenu() { Active.Delete(this); while (Items.Length()) { LMenuItem *i = Items[0]; LAssert(i->Parent == this); DeleteObj(i); } } // This is not called in the GUI thread.. void LSubMenu::SysMouseClick(LMouse &m) { - LMouse *ms = new LMouse; - *ms = m; } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where < 0 ? Items.Length() : Where, Shortcut); - if (i) - { - i->Enabled(Enabled); + if (!i) + return NULL; + + i->Enabled(Enabled); + Items.Insert(i, Where); - Items.Insert(i, Where); + MenuLock lck(this, _FL); + if (lck) + { + Info->AddItem(i->Info); + } + else if (Menu) + { + BMessage *m = new BMessage(M_LSUBMENU_APPENDITEM); + m->AddPointer("sub", this); + m->AddPointer("item", i); + if (!Menu->PostMessage(m)) + DeleteObj(i); + } + else printf("%s:%i - error.\n", _FL); - return i; - } - - return 0; + return i; } LMenuItem *LSubMenu::AppendSeparator(int Where) { - LMenuItem *i = new LMenuItem; - if (i) + LMenuItem *i = new LMenuItem(Menu, NULL, NULL, ItemId_Separator, -1); + if (!i) + return NULL; + + i->Parent = this; + Items.Insert(i, Where); + + MenuLock lck(this, _FL); + if (lck) { - i->Parent = this; - i->Menu = Menu; - i->Id(-2); - - Items.Insert(i, Where); - - return i; + Info->AddItem(i->Info); + } + else + { + DeleteObj(i); + return NULL; } - return 0; + return i; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { LBase::Name(Str); - LMenuItem *i = new LMenuItem(Menu, this, Str, Where < 0 ? Items.Length() : Where, NULL); - if (i) + + LMenuItem *i = new LMenuItem(Menu, this, Str, ItemId_Submenu, Where < 0 ? Items.Length() : Where, NULL); + if (!i) + return NULL; + + Items.Insert(i, Where); + + MenuLock lck(this, _FL); + if (lck) { - i->Id(-1); - Items.Insert(i, Where); - - return i->Child; + Info->AddItem(i->Info); } - - return 0; + else + { + DeleteObj(i); + return NULL; + } + + return i->Child; } void LSubMenu::ClearHandle() { Info = NULL; for (auto i: Items) - { i->ClearHandle(); - } } void LSubMenu::Empty() { LMenuItem *i; while ((i = Items[0])) { RemoveItem(i); DeleteObj(i); } } bool LSubMenu::RemoveItem(int i) { return RemoveItem(Items.ItemAt(i)); } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item)) { return Item->Remove(); } return false; } int LSubMenu::Float(LView *From, int x, int y, int Button) { - return 0; + BPopUpMenu *popup = dynamic_cast(Info); + if (!popup) + { + LAssert(!"Not a popup."); + return 0; + } + + if (!From) + { + LAssert(!"No from view."); + return 0; + } + + auto item = popup->Go(BPoint(x, y)); + // printf("item=%p\n", item); + if (!item) + return 0; + + auto *msg = item->Message(); + if (!msg) + { + printf("%s:%i - No message in item.\n", _FL); + return 0; + } + + return ((LMessage*)msg)->A(); } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); // LOG("Find(%i) '%s' %i sub=%p\n", Id, i->Name(), i->Id(), Sub); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: - bool StartUnderline; // Underline the first display string - bool HasAccel; // The last display string should be right aligned - List Strs; // Draw each alternate display string with underline - // except the last in the case of HasAccel==true. - ::LString Shortcut; - - LMenuItemPrivate() - { - HasAccel = false; - StartUnderline = false; - } - - ~LMenuItemPrivate() - { - Strs.DeleteObjects(); - } - - void UpdateStrings(LFont *Font, char *n) + LMenuItem *Item; + LString Shortcut; + + LMenuItemPrivate(LMenuItem *it) : Item(it) { - // Build up our display strings, - Strs.DeleteObjects(); - StartUnderline = false; - - char *Tab = strrchr(n, '\t'); - if (Tab) - { - *Tab = 0; - } - - char *Amp = 0, *a = n; - while (a = strchr(a, '&')) - { - if (a[1] != '&') - { - Amp = a; - break; - } - - a++; - } - - if (Amp) - { - // Before amp - Strs.Insert(new LDisplayString(Font, n, Amp - n )); - - // Amp'd letter - char *e = LSeekUtf8(++Amp, 1); - Strs.Insert(new LDisplayString(Font, Amp, e - Amp )); - - // After Amp - if (*e) - { - Strs.Insert(new LDisplayString(Font, e)); - } - } - else - { - Strs.Insert(new LDisplayString(Font, n)); - } - - if (HasAccel = (Tab != 0)) - { - Strs.Insert(new LDisplayString(Font, Tab + 1)); - *Tab = '\t'; - } - else if (HasAccel = (Shortcut.Get() != 0)) - { - Strs.Insert(new LDisplayString(Font, Shortcut)); - } } }; -static LAutoString MenuItemParse(const char *s) +static LString MenuItemParse(const char *s, char &trigger) { char buf[256], *out = buf; const char *in = s; + + trigger = 0; + while (in && *in && out < buf + sizeof(buf) - 1) { - if (*in == '_') - { - *out++ = '_'; - *out++ = '_'; - } - else if (*in != '&' || in[1] == '&') - { + if (*in == '\t') + break; + else if (*in == '&' && in[1] == '&') + *out++ = *in++; + else if (*in == '&' && in[1] != '&') + trigger = in[1]; + else *out++ = *in; - } - else - { - *out++ = '_'; - } in++; } *out++ = 0; - char *tab = strrchr(buf, '\t'); - if (tab) - { - *tab++ = 0; - } - - return LAutoString(NewStr(buf)); + return buf; } LMenuItem::LMenuItem() { - d = new LMenuItemPrivate(); + d = new LMenuItemPrivate(this); Info = NULL; - Child = NULL; - Menu = NULL; - Parent = NULL; - - Position = -1; - - _Flags = 0; - _Icon = -1; - _Id = 0; } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int id, int Pos, const char *shortcut) { - d = NULL; - LAutoString Txt = MenuItemParse(txt); + d = new LMenuItemPrivate(this); + char trigger; + auto Txt = MenuItemParse(txt, trigger); LBase::Name(txt); - Info = NULL; - Child = NULL; Menu = m; Parent = p; Position = Pos; + _Id = id; + d->Shortcut = shortcut; - _Flags = 0; - _Icon = -1; - _Id = id; + if (id == LSubMenu::ItemId_Submenu) + { + Child = new LSubMenu(new BMenu(Txt)); + if (Child) + { + Child->Menu = Menu; + Child->Parent = this; + + Info = new BMenuItem(Child->Info); + if (Info && trigger) + Info->SetTrigger(ToLower(trigger)); + } + } + else if (id == LSubMenu::ItemId_Separator) + { + Info = new BSeparatorItem(); + } + else // Normal item... + { + Info = new BMenuItem(Txt, new LMessage(M_COMMAND, id)); + if (Info && trigger) + Info->SetTrigger(ToLower(trigger)); + } ScanForAccel(); } LMenuItem::~LMenuItem() { Remove(); DeleteObj(Child); DeleteObj(d); } void LMenuItem::_Measure(LPoint &Size) { } void LMenuItem::_Paint(LSurface *pDC, int Flags) { } void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width) { } -#if 1 - bool LMenuItem::ScanForAccel() { - if (!Menu) - return false; - - return true; -} - -#else - -bool LMenuItem::ScanForAccel() -{ - ::LString Accel; - + LString Accel; if (d->Shortcut) { Accel = d->Shortcut; } else { auto n = LBase::Name(); if (n) { auto Tab = strchr(n, '\t'); if (Tab) Accel = Tab + 1; } } if (!Accel) return false; auto Keys = Accel.SplitDelimit("-+"); if (Keys.Length() == 0) return false; int Flags = 0; - uchar Key = 0; + int Vkey = 0; + int Chr = 0; bool AccelDirty = false; for (int i=0; iAccelGrp, - GtkKey, - mod, - Gtk::GTK_ACCEL_VISIBLE - ); - gtk_widget_show_all(w); - } - else - { - LOG("%s:%i - No gtk key for '%s'\n", _FL, Accel.Get()); - } - - auto Ident = Id(); - LAssert(Ident > 0); - Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) ); - - return true; - } - else + if (!Vkey && !Chr) { LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Accel.Get()); return false; } -} + + if (!Info) + { + LOG("%s:%i - No item handle.\n", _FL); + return false; + } -#endif + uint32 modifiers = + ((Flags & LGI_EF_CTRL) ? B_CONTROL_KEY : 0) | + ((Flags & LGI_EF_ALT) ? B_MENU_KEY : 0) | + ((Flags & LGI_EF_SHIFT) ? B_SHIFT_KEY : 0); + + // ((Flags & LGI_EF_SYSTEM) ? B_COMMAND_KEY : 0); + + if (Vkey >= LK_F1 && Vkey <= LK_F12) + { + #if 1 + + if (Menu) + { + LAssert(Id() > 0); + Menu->Accel.Insert(new LAccelerator(Flags, Vkey, Chr, Id())); + } + + #else + + // This is not supported yet... + auto bwnd = Menu && Menu->WindowHandle() ? Menu->WindowHandle()->WindowHandle() : NULL; + if (bwnd) + { + BMessage *msg = new BMessage(M_COMMAND); + if (msg) + { + msg->AddInt32("key", Key-LK_F1+B_F1_KEY); + bwnd->AddShortcut(B_FUNCTION_KEY, modifiers, msg); + } + else printf("%s:%i - Alloc err.\n", _FL); + } + else printf("%s:%i - No bwnd to add function key shortcut to.\n", _FL); + + #endif + } + else + { + #if 0 + printf("ScanForAccel: %s, %s, Vkey=%i, Chr=%i(%c), Flags: %x ctrl=%i alt=%i shift=%i sys=%i, Mods: ctrl=%i alt=%i sh=%i sys=%i\n", + Name(), Accel.Get(), + Vkey, Chr, Chr >= ' ' ? Chr : '.', + Flags, + (Flags & LGI_EF_CTRL) != 0, + (Flags & LGI_EF_ALT) != 0, + (Flags & LGI_EF_SHIFT) != 0, + (Flags & LGI_EF_SYSTEM) != 0, + (modifiers & B_CONTROL_KEY) != 0, + (modifiers & B_MENU_KEY) != 0, + (modifiers & B_SHIFT_KEY) != 0, + (modifiers & B_COMMAND_KEY) != 0); + #endif + + Info->SetShortcut(Chr, modifiers); + } + + return true; +} LSubMenu *LMenuItem::GetParent() { return Parent; } void LMenuItem::ClearHandle() { Info = NULL; if (Child) Child->ClearHandle(); } bool LMenuItem::Remove() { if (!Parent) + return false; + + if (Info) { - return false; + MenuLock lck(this, _FL); + if (lck) + { + auto m = Info->Menu(); + if (m) + m->RemoveItem(Info); + } + else printf("%s:%i - Can't lock to Remove item.\n", _FL); } - + LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); Parent = NULL; + return true; } void LMenuItem::Id(int i) { _Id = i; } void LMenuItem::Separator(bool s) { if (s) - { - _Id = -2; - } + _Id = LSubMenu::ItemId_Separator; } LImageList *LMenuItem::GetImageList() { if (GetMenu()) return GetMenu()->GetImageList(); // Search tree of parents for an image list... for (auto p = GetParent(); p; ) { auto lst = p->GetImageList(); if (lst) return lst; auto pmi = p->GetParent(); if (pmi) p = pmi->GetParent(); else break; } return NULL; } void LMenuItem::Icon(int i) { _Icon = i; } void LMenuItem::Checked(bool c) { if (c) SetFlag(_Flags, ODS_CHECKED); else ClearFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Name(const char *n) { bool Status = LBase::Name(n); return Status; } void LMenuItem::Enabled(bool e) { if (e) ClearFlag(_Flags, ODS_DISABLED); else SetFlag(_Flags, ODS_DISABLED); - + + if (Menu) + Menu->PostMessage(new LMessage(M_LMENUITEM_ENABLE, Id(), e)); + else + Info->SetEnabled(e); } void LMenuItem::Focus(bool f) { } void LMenuItem::Sub(LSubMenu *s) { Child = s; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return _Id; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { - return _Id == -2; + return _Id == LSubMenu::ItemId_Separator; } bool LMenuItem::Checked() { return TestFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Enabled() { return !TestFlag(_Flags, ODS_DISABLED); } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return 0; } LSubMenu *LMenuItem::Sub() { return Child; } int LMenuItem::Icon() { return _Icon; } /////////////////////////////////////////////////////////////////////////////////////////////// struct LMenuFont { - LFont *f; + LAutoPtr f; + +} MenuFont; + +class LMenuPrivate : public BMenuBar +{ + LMenu *Menu; - LMenuFont() +public: + LMenuPrivate(LMenu *menu, const char *name) : + Menu(menu), + BMenuBar(name) + { + } + + ~LMenuPrivate() { - f = NULL; + Menu->d = NULL; + + auto bwnd = Window(); + bool locked = bwnd && bwnd->LockLooper(); + if (locked) + { + RemoveSelf(); + bwnd->UnlockLooper(); + } } - ~LMenuFont() - { - DeleteObj(f); - } -} MenuFont; - -class LMenuPrivate -{ -public: void MessageReceived(BMessage *message) { LMessage *m = (LMessage*)message; switch (message->what) { case M_LMENUITEM_ENABLE: { auto id = m->A(); auto en = m->B(); auto item = Menu->FindItem(id); if (item) { if (item->Info) item->Info->SetEnabled(en); else printf("%s:%i - M_LMENUITEM_ENABLE: no hnd to set.\n", _FL); } else printf("%s:%i - M_LMENUITEM_ENABLE: Couldn't find %i\n", _FL, (int)id); break; } case M_LSUBMENU_APPENDITEM: { LSubMenu *sub = NULL; LMenuItem *item = NULL; if (message->FindPointer("sub", (void**)&sub) == B_OK && message->FindPointer("item", (void**)&item) == B_OK) { if (sub->Handle()) { printf("M_LSUBMENU_APPENDITEM done.\n"); sub->Handle()->AddItem(item->Handle()); } else printf("%s:%i - Error: No handle.\n", _FL); } else printf("%s:%i - Error: missing pointers.\n", _FL); break; } default: BMenuBar::MessageReceived(message); break; } } }; LMenu::LMenu(const char *AppName) : d(new LMenuPrivate(this, AppName)), LSubMenu(d) { Menu = this; Info = d; } LMenu::~LMenu() { Accel.DeleteObjects(); DeleteObj(d); } bool LMenu::PostMessage(BMessage *m) { auto view = WindowHandle(); auto hnd = view ? view->WindowHandle() : NULL; if (hnd && hnd->PostMessage(m) == B_OK) return true; // printf("%s:%i - PostMessage failed. %p,%p,%p\n", _FL, menu, view, hnd); delete m; return false; } LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { - MenuFont.f = Type.Create(); - if (MenuFont.f) + if (MenuFont.f.Reset(Type.Create())) { // MenuFont.f->CodePage(LSysFont->CodePage()); } - else - { - LOG("LMenu::GetFont Couldn't create menu font.\n"); - } - } - else - { - LOG("LMenu::GetFont Couldn't get menu typeface.\n"); + else LOG("LMenu::GetFont Couldn't create menu font.\n"); } + else LOG("LMenu::GetFont Couldn't get menu typeface.\n"); - if (!MenuFont.f) + if (!MenuFont.f && + MenuFont.f.Reset(new LFont)) { - MenuFont.f = new LFont; - if (MenuFont.f) - { - *MenuFont.f = *LSysFont; - } + *MenuFont.f = *LSysFont; } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { - if (!p) + if (!p || !p->GetWindow()) { LAssert(0); return false; } - LWindow *Wnd = p->GetWindow(); - if (!Wnd) + Window = p->GetWindow(); + auto bwnd = Window->WindowHandle(); + LLocker lck(bwnd, _FL); + if (!lck.Lock()) { - LAssert(0); + LAssert(!"Can't lock."); return false; } - - Window = Wnd; + + // printf("Attaching menubar...\n"); + auto menubar = dynamic_cast(Info); + bwnd->AddChild(menubar); + lck.Unlock(); + + Window->OnPosChange(); // Force update of root view position return true; } bool LMenu::Detach() { - bool Status = false; - return Status; + if (!Window) + return false; + + auto bwnd = Window->WindowHandle(); + LLocker lck(bwnd, _FL); + if (!lck.Lock()) + { + LAssert(!"Can't lock."); + return false; + } + + bwnd->SetKeyMenuBar(NULL); + + lck.Unlock(); + return true; } bool LMenu::SetPrefAndAboutItems(int a, int b) { return false; } bool LMenu::OnKey(LView *v, LKey &k) { + LOG("LMenu::OnKey(%s):\n", v ? v->GetClass() : NULL); + k.Trace(" "); + if (k.Down()) { for (auto a: Accel) { if (a->Match(k)) { LAssert(a->GetId() > 0); Window->OnCommand(a->GetId(), 0, 0); return true; } } if (k.Alt() && !dynamic_cast(v) && !dynamic_cast(v)) { bool Hide = false; for (auto s: Items) { if (!s->Separator()) { if (Hide) { // s->Info->HideSub(); } else { auto n = s->Name(); if (ValidStr(n)) { char *Amp = strchr(n, '&'); if (Amp) { while (Amp && Amp[1] == '&') Amp = strchr(Amp + 2, '&'); char16 Accel = tolower(Amp[1]); char16 Press = tolower(k.c16); if (Accel == Press) Hide = true; } } if (Hide) { // s->Info->ShowSub(); } else { // s->Info->HideSub(); } } } } if (Hide) { return true; } } } return false; } //////////////////////////////////////////////////////////////////////////// -LAccelerator::LAccelerator(int flags, int key, int id) +LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; - Key = key; + Vkey = vkey; + Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { int Press = (uint) k.vkey; if (k.vkey == LK_RSHIFT || k.vkey == LK_LSHIFT || k.vkey == LK_RCTRL || k.vkey == LK_LCTRL || k.vkey == LK_RALT || k.vkey == LK_RALT) { return false; } - #if 1 - LOG("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s%s\n", - Press, - Press>=' '?Press:'.', - k.Ctrl()?" ctrl":"", - k.Alt()?" alt":"", - k.Shift()?" shift":"", - Key, - Key>=' '?Key:'.', - TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"", - TestFlag(Flags, LGI_EF_ALT)?" alt":"", - TestFlag(Flags, LGI_EF_SHIFT)?" shift":"", - TestFlag(Flags, LGI_EF_SYSTEM)?" system":"" - ); - #endif - - if (toupper(Press) == (uint)Key) + if + ( + !k.IsChar + && + ( + (Chr != 0 && tolower(k.c16) == tolower(Chr)) + || + (Vkey != 0 && k.vkey == Vkey) + ) + ) { if ( - ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0) && - ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0) && - ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0) && - ((TestFlag(Flags, LGI_EF_SYSTEM) ^ k.System()) == 0) + (Ctrl() ^ k.Ctrl()) == 0 && + (Alt() ^ k.Alt()) == 0 && + (Shift() ^ k.Shift()) == 0 && + (!TestFlag(Flags, LGI_EF_IS_CHAR) || k.IsChar) && + (!TestFlag(Flags, LGI_EF_IS_NOT_CHAR) || !k.IsChar) ) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { - Flags = GWF_VISIBLE; - Id = 0; - ToolButton = 0; - MenuItem = 0; - Accelerator = 0; - TipHelp = 0; - PrevValue = false; } LCommand::~LCommand() { - DeleteArray(Accelerator); - DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) - { ToolButton->Enabled(e); - } if (MenuItem) - { MenuItem->Enabled(e); - } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) - { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; - } if (MenuItem) - { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; - } if (HasChanged) - { Value(!PrevValue); - } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) - { ToolButton->Value(v); - } if (MenuItem) - { MenuItem->Checked(v); - } PrevValue = v; } diff --git a/src/haiku/PrintDC.cpp b/src/haiku/PrintDC.cpp --- a/src/haiku/PrintDC.cpp +++ b/src/haiku/PrintDC.cpp @@ -1,75 +1,69 @@ #include "lgi/common/Lgi.h" #define PS_SCALE 10 /////////////////////////////////////////////////////////////////////////////////////// class LPrintDCPrivate // : public GCups { public: class PrintPainter *p; LString PrintJobName; LString PrinterName; int Pages; LColour c; LRect Clip; LPrintDCPrivate() { p = 0; Pages = 0; } ~LPrintDCPrivate() { } bool IsOk() { #ifndef __clang__ return this != 0; #else return true; #endif } }; ///////////////////////////////////////////////////////////////////////////////////// LPrintDC::LPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName) { d = new LPrintDCPrivate(); d->PrintJobName = PrintJobName; d->PrinterName = PrinterName; ColourSpace = CsRgb24; d->Clip = Bounds(); } LPrintDC::~LPrintDC() { DeleteObj(d); } int LPrintDC::X() { return 0; } int LPrintDC::Y() { return 0; } int LPrintDC::GetBits() { return 24; } -int LPrintDC::DpiX() +LPoint LPrintDC::GetDpi() { - return 100; + return LPoint(100, 100); } - -int LPrintDC::DpiY() -{ - return 100; -} - diff --git a/src/haiku/Printer.cpp b/src/haiku/Printer.cpp --- a/src/haiku/Printer.cpp +++ b/src/haiku/Printer.cpp @@ -1,69 +1,70 @@ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Button.h" #include "lgi/common/Printer.h" //////////////////////////////////////////////////////////////////// class LPrinterPrivate { public: ::LString JobName; ::LString Printer; ::LString Err; ::LString PrinterName; - GPrintEvents *Events; + LPrintEvents *Events; LAutoPtr PrintDC; LPrinterPrivate() { Events = NULL; } ~LPrinterPrivate() { } }; //////////////////////////////////////////////////////////////////// -GPrinter::GPrinter() +LPrinter::LPrinter() { d = new LPrinterPrivate; } -GPrinter::~GPrinter() +LPrinter::~LPrinter() { DeleteObj(d); } -bool GPrinter::Browse(LView *Parent) +bool LPrinter::Browse(LView *Parent) { return false; } -bool GPrinter::Serialize(::LString &Str, bool Write) +bool LPrinter::Serialize(::LString &Str, bool Write) { if (Write) Str = d->Printer; else d->Printer = Str; return true; } -::LString GPrinter::GetErrorMsg() +LString LPrinter::GetErrorMsg() { return d->Err; } -bool GPrinter::Print(GPrintEvents *Events, const char *PrintJobName, int Pages /* = -1 */, LView *Parent /* = 0 */) +void LPrinter::Print(LPrintEvents *Events, + std::function callback, + const char *PrintJobName, + int Pages, + LView *Parent) { if (!Events) - { LgiTrace("%s:%i - Error: missing param.\n", _FL); - return false; - } - - return false; + + LAssert(!"Impl me."); } diff --git a/src/haiku/ScreenDC.cpp b/src/haiku/ScreenDC.cpp --- a/src/haiku/ScreenDC.cpp +++ b/src/haiku/ScreenDC.cpp @@ -1,475 +1,504 @@ /*hdr ** FILE: LScreenDC.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku screen DC ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include +#include -#define VIEW_CHECK if (!d->v) return; +#define LOGGING 0 +#define VIEW_CHECK(...) if (!d->v) return __VA_ARGS__; class LScreenPrivate { public: int x = 0, y = 0, Bits = 32; bool Own = false; LColour Col; LRect Client; + int Rop = GDC_SET; + NativeInt Alpha = -1; LView *View = NULL; OsView v = NULL; LScreenPrivate() { Client.ZOff(-1, -1); } ~LScreenPrivate() { } }; // Translates are cumulative... so we undo the last one before resetting it. ///////////////////////////////////////////////////////////////////////////////////////////////////// LScreenDC::LScreenDC() { d = new LScreenPrivate; d->x = GdcD->X(); d->y = GdcD->Y(); d->Bits = GdcD->GetBits(); } LScreenDC::LScreenDC(LView *view, void *param) { d = new LScreenPrivate; if (d->View = view) d->v = view->Handle(); d->Bits = GdcD->GetBits(); + /* if (d->v) d->Client = d->v->Frame(); else LgiTrace("%s:%i - LScreenDC::LScreenDC - No view?\n", _FL); + */ } LScreenDC::~LScreenDC() { DeleteObj(d); } OsPainter LScreenDC::Handle() { return d->v; } ::LString LScreenDC::Dump() { ::LString s; s.Printf("LScreenDC size=%i,%i\n", d->x, d->y); return s; } bool LScreenDC::SupportsAlphaCompositing() { - // GTK/X11 doesn't seem to support alpha compositing. - return false; + return true; +} + +LPoint LScreenDC::GetDpi() +{ + return LScreenDpi(); } bool LScreenDC::GetClient(LRect *c) { if (!c) return false; *c = d->Client; return true; } +LString GetClip(BView *v) +{ + BRegion r; + v->GetClippingRegion(&r); + LRect lr = r.Frame(); + return lr.GetStr(); +} + void LScreenDC::GetOrigin(int &x, int &y) { - if (d->Client.Valid()) - { - x = OriginX + d->Client.x1; - y = OriginY + d->Client.y1; - } - else - { - x = OriginX; - y = OriginY; - } + x = OriginX; + y = OriginY; + + #if LOGGING + printf("%p.GetOrigin=%i+%i=%i, %i+%i=%i\n", + this, + OriginX, d->Client.x1, x, + OriginY, d->Client.y1, y); + #endif } void LScreenDC::SetOrigin(int x, int y) { + VIEW_CHECK() + if (d->Client.Valid()) { - OriginX = x - d->Client.x1; - OriginY = y - d->Client.y1; + // The clipping region is relative to the offset. + // Remove it here and reinstate it after setting the origin. + d->v->ConstrainClippingRegion(NULL); } - else + + OriginX = x - d->Client.x1; + OriginY = y - d->Client.y1; + + #if LOGGING + printf("%p.SetOrigin=%i,%i (%i,%i) = %i,%i\n", + this, + x, y, + d->Client.x1, d->Client.y1, + d->Client.x1 - OriginX, + d->Client.y1 - OriginY); + #endif + + d->v->SetOrigin( d->Client.x1 - OriginX, + d->Client.y1 - OriginY); + + if (d->Client.Valid()) { - OriginX = x; - OriginY = y; + // Reset the clipping region related to the origin. + auto clp = d->Client.ZeroTranslate(); + clp.Offset(OriginX, OriginY); + d->v->ClipToRect(clp); } - } void LScreenDC::SetClient(LRect *c) { + VIEW_CHECK() + if (c) { d->Client = *c; + OriginX += d->Client.x1; + OriginY += d->Client.y1; + #if LOGGING + printf("SetClient(%s)\n", d->Client.GetStr()); + #endif + d->v->SetOrigin(OriginX, OriginY); - OriginX = -c->x1; - OriginY = -c->y1; + auto clp = d->Client.ZeroTranslate(); + // clp.Offset(OriginX, OriginY); + d->v->ClipToRect(clp); + + /* + BRegion r; + d->v->GetClippingRegion(&r); + LRect lr = r.Frame(); + printf("SetClient %s = %s (%i,%i)\n", c->GetStr(), lr.GetStr(), OriginX, OriginY); + */ } else { - if (Clip.Valid()) - ClipRgn(NULL); - - OriginX = 0; - OriginY = 0; + d->v->ConstrainClippingRegion(NULL); + d->v->SetOrigin(OriginX = 0, OriginX = 0); + d->Client.ZOff(-1, -1); - - d->Client.ZOff(-1, -1); + #if LOGGING + printf("SetClient()\n"); + #endif } } LRect *LScreenDC::GetClient() { return &d->Client; } uint LScreenDC::LineStyle() { return LSurface::LineStyle(); } uint LScreenDC::LineStyle(uint32_t Bits, uint32_t Reset) { return LSurface::LineStyle(Bits); } int LScreenDC::GetFlags() { return 0; } LRect LScreenDC::ClipRgn() { return Clip; } LRect LScreenDC::ClipRgn(LRect *c) { LRect Prev = Clip; if (c) { Clip = *c; - + d->v->ClipToRect(Clip); } else { Clip.ZOff(-1, -1); + d->v->ConstrainClippingRegion(NULL); } return Prev; } COLOUR LScreenDC::Colour() { return d->Col.Get(GetBits()); } COLOUR LScreenDC::Colour(COLOUR c, int Bits) { auto b = Bits ? Bits : GetBits(); d->Col.Set(c, b); return Colour(d->Col).Get(b); } LColour LScreenDC::Colour(LColour c) { LColour Prev = d->Col; d->Col = c; if (d->v) { - // LgiTrace("SetViewColor %s\n", c.GetStr()); d->v->SetLowColor(c); d->v->SetHighColor(c); } - else - LgiTrace("%s:%i - No view.\n", _FL); + else LgiTrace("%s:%i - No view.\n", _FL); return Prev; } int LScreenDC::Op(int ROP, NativeInt Param) { - int Prev = Op(); - - switch (ROP) - { - case GDC_SET: - { - //d->p.setRasterOp(XPainter::CopyROP); - break; - } - case GDC_OR: - { - //d->p.setRasterOp(XPainter::OrROP); - break; - } - case GDC_AND: - { - //d->p.setRasterOp(XPainter::AndROP); - break; - } - case GDC_XOR: - { - //d->p.setRasterOp(XPainter::XorROP); - break; - } - } - - return Prev; + auto prev = d->Rop; + d->Alpha = Param; + d->Rop = ROP; + return prev; } int LScreenDC::Op() { - /* - switch (d->p.rasterOp()) - { - case XPainter::CopyROP: - { - return GDC_SET; - break; - } - case XPainter::OrROP: - { - return GDC_OR; - break; - } - case XPainter::AndROP: - { - return GDC_AND; - break; - } - case XPainter::XorROP: - { - return GDC_XOR; - break; - } - } - */ - - return GDC_SET; + return d->Rop; } int LScreenDC::X() { return d->Client.Valid() ? d->Client.X() : d->x; } int LScreenDC::Y() { return d->Client.Valid() ? d->Client.Y() : d->y; } int LScreenDC::GetBits() { return d->Bits; } LPalette *LScreenDC::Palette() { - return 0; + return NULL; // not supported. } void LScreenDC::Palette(LPalette *pPal, bool bOwnIt) { - VIEW_CHECK } void LScreenDC::Set(int x, int y) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeLine(BPoint(x, y), BPoint(x, y)); } COLOUR LScreenDC::Get(int x, int y) { return 0; } void LScreenDC::HLine(int x1, int x2, int y) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeLine(BPoint(x1, y), BPoint(x2, y)); } void LScreenDC::VLine(int x, int y1, int y2) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeLine(BPoint(x, y1), BPoint(x, y2)); } void LScreenDC::Line(int x1, int y1, int x2, int y2) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); } void LScreenDC::Circle(double cx, double cy, double radius) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), radius, radius, 0, 360); } void LScreenDC::FilledCircle(double cx, double cy, double radius) { - VIEW_CHECK + VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), radius, radius, 0, 360); } void LScreenDC::Arc(double cx, double cy, double radius, double start, double end) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), radius, radius, start, end); } void LScreenDC::FilledArc(double cx, double cy, double radius, double start, double end) { - VIEW_CHECK + VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), radius, radius, start, end); } void LScreenDC::Ellipse(double cx, double cy, double x, double y) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), x, y, 0, 360); } void LScreenDC::FilledEllipse(double cx, double cy, double x, double y) { - VIEW_CHECK + VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), x, y, 0, 360); } void LScreenDC::Box(int x1, int y1, int x2, int y2) { - VIEW_CHECK + VIEW_CHECK() d->v->StrokeRect(LRect(x1, y1, x2, y2)); } void LScreenDC::Box(LRect *a) { - VIEW_CHECK + VIEW_CHECK() if (a) d->v->StrokeRect(*a); else Box(0, 0, X()-1, Y()-1); } void LScreenDC::Rectangle(int x1, int y1, int x2, int y2) { - VIEW_CHECK + VIEW_CHECK() if (x2 >= x1 && y2 >= y1) + { + // auto o = d->v->Origin(); + // printf("Rect %i,%i,%i,%i %g,%g\n", x1, y1, x2, y2, o.x, o.y); d->v->FillRect(LRect(x1, y1, x2, y2)); + } } void LScreenDC::Rectangle(LRect *a) { - VIEW_CHECK + VIEW_CHECK() LRect r = a ? *a : Bounds(); - d->v->FillRect(BRect(r.x1, r.y1, r.x2, r.y2)); + BRect br(r.x1, r.y1, r.x2, r.y2); + d->v->FillRect(br); } void LScreenDC::Polygon(int Points, LPoint *Data) { - VIEW_CHECK + VIEW_CHECK() if (!Data || Points <= 0) return; } void LScreenDC::Blt(int x, int y, LSurface *Src, LRect *a) { - VIEW_CHECK + VIEW_CHECK() if (!Src) { LgiTrace("%s:%i - No source.\n", _FL); return; } if (Src->IsScreen()) { LgiTrace("%s:%i - Can't do screen->screen blt.\n", _FL); return; } BBitmap *bmp = Src->GetBitmap(); if (!bmp) { LAssert(!"no bmp."); LgiTrace("%s:%i - No bitmap.\n", _FL); return; } + + switch (d->Rop) + { + case GDC_SET: + d->v->SetDrawingMode(B_OP_COPY); + break; + case GDC_OR: + d->v->SetDrawingMode(B_OP_ADD); + break; + case GDC_XOR: + d->v->SetDrawingMode(B_OP_INVERT); + break; + default: + case GDC_ALPHA: + d->v->SetDrawingMode(B_OP_ALPHA); + d->v->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); + + if (d->Alpha >= 0) + LgiTrace("%s:%i - WARNING: Haiku doesn't support constant alpha Blt to screen!!!\n", _FL); + break; + } if (a) { BRect bitmapRect = *a; - BRect viewRect(x, y, x + a->X(), y + a->Y()); + BRect viewRect(x, y, x + a->X() - 1, y + a->Y() - 1); d->v->DrawBitmap(bmp, bitmapRect, viewRect); #if 0 printf("ScreenDC::Blt %g,%g/%gx%g -> %g,%g/%gx%g %i,%i\n", bitmapRect.left, bitmapRect.top, bitmapRect.Width(), bitmapRect.Height(), viewRect.left, viewRect.top, viewRect.Width(), viewRect.Height(), a->X(), a->Y()); #endif } else { BRect bitmapRect = bmp->Bounds(); - BRect viewRect(x, y, x + Src->X() - 1, y + Src->Y() - 1); // This seems like a Haiku bug... shouldn't need the -1 offset here. + BRect viewRect(x, y, x + Src->X() - 1, y + Src->Y() - 1); d->v->DrawBitmap(bmp, bitmapRect, viewRect); #if 0 printf("ScreenDC::Blt %g,%g/%gx%g -> %g,%g/%gx%g %i,%i\n", bitmapRect.left, bitmapRect.top, bitmapRect.Width(), bitmapRect.Height(), viewRect.left, viewRect.top, viewRect.Width(), viewRect.Height(), x, y); #endif } + + d->v->SetDrawingMode(B_OP_COPY); } void LScreenDC::StretchBlt(LRect *Dest, LSurface *Src, LRect *s) { - VIEW_CHECK + VIEW_CHECK() LAssert(0); } void LScreenDC::Bezier(int Threshold, LPoint *Pt) { - VIEW_CHECK + VIEW_CHECK() LAssert(0); } void LScreenDC::FloodFill(int x, int y, int Mode, COLOUR Border, LRect *Bounds) { - VIEW_CHECK + VIEW_CHECK() LAssert(0); } diff --git a/src/haiku/ShowFileProp_Haiku.cpp b/src/haiku/ShowFileProp_Haiku.cpp new file mode 100755 --- /dev/null +++ b/src/haiku/ShowFileProp_Haiku.cpp @@ -0,0 +1,12 @@ +#include "lgi/common/Lgi.h" + +void LShowFileProperties(OsView Parent, const char *Filename) +{ + LAssert(!"Impl me."); +} + +bool LBrowseToFile(const char *Filename) +{ + LAssert(!"Impl me."); + return false; +} \ No newline at end of file diff --git a/src/haiku/Thread.cpp b/src/haiku/Thread.cpp --- a/src/haiku/Thread.cpp +++ b/src/haiku/Thread.cpp @@ -1,119 +1,133 @@ -#include "lgi/common/Lgi.h" #include #include #include #include +#include "lgi/common/Lgi.h" +#include "lgi/common/EventTargetThread.h" + OsThreadId GetCurrentThreadId() { return find_thread(NULL); } //////////////////////////////////////////////////////////////////////////// void *ThreadEntryPoint(void *i) { if (i) { LThread *Thread = (LThread*) i; Thread->ThreadId = GetCurrentThreadId(); // Make sure we have finished executing the setup while (Thread->State == LThread::THREAD_INIT) { LSleep(1); } pthread_detach(Thread->hThread); LString Nm = Thread->Name; // Do thread's work Thread->OnBeforeMain(); Thread->ReturnValue = Thread->Main(); Thread->OnAfterMain(); // mark thread over... Thread->State = LThread::THREAD_EXITED; - - if (Thread->DeleteOnExit) + bool DelayDelete = false; + if (Thread->ViewHandle >= 0) + { + // If DeleteOnExit is set AND ViewHandle then the LView::OnEvent handle will + // process the delete... don't do it here. + DelayDelete = PostThreadEvent(Thread->ViewHandle, M_THREAD_COMPLETED, (LMessage::Param)Thread); + // However if PostThreadEvent fails... do honour DeleteOnExit. + } + + if (!DelayDelete && Thread->DeleteOnExit) { DeleteObj(Thread); } + pthread_exit(0); } return 0; } -LThread::LThread(const char *ThreadName) +const OsThread LThread::InvalidHandle = NULL; +const OsThreadId LThread::InvalidId = 0; + +LThread::LThread(const char *ThreadName, int viewHandle) { Name = ThreadName; ThreadId = 0; State = LThread::THREAD_INIT; ReturnValue = -1; hThread = 0; DeleteOnExit = false; } LThread::~LThread() { if (!IsExited()) { Terminate(); } } int LThread::ExitCode() { return ReturnValue; } bool LThread::IsExited() { return State == LThread::THREAD_EXITED; } void LThread::Run() { if (!hThread) { State = LThread::THREAD_INIT; static int Creates = 0; int e; if (!(e = pthread_create(&hThread, NULL, ThreadEntryPoint, (void*)this))) { Creates++; State = LThread::THREAD_RUNNING; } else { const char *Err = "(unknown)"; switch (e) { case EAGAIN: Err = "EAGAIN"; break; case EINVAL: Err = "EINVAL"; break; case EPERM: Err = "EPERM"; break; case ENOMEM: Err = "ENOMEM"; break; } printf("%s,%i - pthread_create failed with the error %i (%s) (After %i creates)\n", __FILE__, __LINE__, e, Err, Creates); State = LThread::THREAD_EXITED; } } } void LThread::Terminate() { if (hThread && pthread_cancel(hThread) == 0) { State = LThread::THREAD_EXITED; } } int LThread::Main() { return 0; } diff --git a/src/haiku/View.cpp b/src/haiku/View.cpp --- a/src/haiku/View.cpp +++ b/src/haiku/View.cpp @@ -1,875 +1,1139 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku LView Implementation ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/Css.h" -#include "ViewPriv.h" -#define DEBUG_MOUSE_EVENTS 1 +#include "ViewPriv.h" +#include + +#define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #endif struct CursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32_t CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { - LAppInst->Run(false); + LAppInst->Yield(); } void *IsAttached(BView *v) { auto pview = v->Parent(); auto pwnd = v->Window(); return pwnd ? (void*)pwnd : (void*)pview; } bool LgiIsKeyDown(int Key) { LAssert(0); return false; } LKey::LKey(int Vkey, uint32_t flags) { vkey = Vkey; Flags = flags; IsChar = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// -class LBView : public BView +template +struct LBView : public Parent { LViewPrivate *d = NULL; + static uint32 MouseButtons; -public: LBView(LViewPrivate *priv) : d(priv), - BView + Parent ( "", B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS ) { Parent::SetName(d->View->GetClass()); } ~LBView() { - d->Hnd = NULL; + if (d) + d->Hnd = NULL; } void AttachedToWindow() { - d->View->OnCreate(); + if (d) + d->View->OnCreate(); + } + + LKey ConvertKey(const char *bytes, int32 numBytes) + { + LKey k; + + uint8_t *utf = (uint8_t*)bytes; + ssize_t len = numBytes; + auto w = LgiUtf8To32(utf, len); + + key_info KeyInfo; + if (get_key_info(&KeyInfo) == B_OK) + { + k.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); + k.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); + k.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); + k.System(TestFlag(KeyInfo.modifiers, B_COMMAND_KEY)); + } + + #if 0 + LString::Array a; + for (int i=0; iCurrentMessage(); + if (bmsg) + { + int32 key = 0; + if (bmsg->FindInt32("key", &key) == B_OK) + { + // Translate the function keys into the LGI address space... + switch (key) + { + case B_F1_KEY: w = LK_F1; break; + case B_F2_KEY: w = LK_F2; break; + case B_F3_KEY: w = LK_F3; break; + case B_F4_KEY: w = LK_F4; break; + case B_F5_KEY: w = LK_F5; break; + case B_F6_KEY: w = LK_F6; break; + case B_F7_KEY: w = LK_F7; break; + case B_F8_KEY: w = LK_F8; break; + case B_F9_KEY: w = LK_F9; break; + case B_F10_KEY: w = LK_F10; break; + case B_F11_KEY: w = LK_F11; break; + case B_F12_KEY: w = LK_F12; break; + default: + printf("%s:%i - Upsupported key %i.\n", _FL, key); + break; + } + } + else printf("%s:%i - No 'key' in BMessage.\n", _FL); + } + else printf("%s:%i - No BMessage.\n", _FL); + } + + k.c16 = k.vkey = w; + + } + + k.IsChar = + !( + k.System() + || + k.Alt() + ) + && + ( + (k.c16 >= ' ' && k.c16 < LK_DELETE) + || + k.c16 == LK_BACKSPACE + || + k.c16 == LK_TAB + || + k.c16 == LK_RETURN + ); + + return k; + } + + void KeyDown(const char *bytes, int32 numBytes) + { + if (!d) return; + + auto k = ConvertKey(bytes, numBytes); + k.Down(true); + + auto wnd = d->View->GetWindow(); + if (wnd) + wnd->HandleViewKey(d->View, k); + else + d->View->OnKey(k); + } + + void KeyUp(const char *bytes, int32 numBytes) + { + if (!d) return; + + auto k = ConvertKey(bytes, numBytes); + auto wnd = d->View->GetWindow(); + if (wnd) + wnd->HandleViewKey(d->View, k); + else + d->View->OnKey(k); } - void FrameMoved(BPoint newPosition) + // LWindow's get their events from their LWindowPrivate + #define IsLWindow dynamic_cast(d->View) + + void FrameMoved(BPoint p) { - d->View->Pos = Parent::Frame(); + if (!d || IsLWindow) return; + d->View->Pos.Offset(p.x - d->View->Pos.x1, p.y - d->View->Pos.y1); d->View->OnPosChange(); } void FrameResized(float newWidth, float newHeight) { - d->View->Pos = Parent::Frame(); + if (!d || IsLWindow) return; + d->View->Pos.SetSize(newWidth, newHeight); d->View->OnPosChange(); } void MessageReceived(BMessage *message) { - d->View->OnEvent(message); - BView::MessageReceived(message); + if (!d) return; + void *v = NULL; + if (message->FindPointer(LMessage::PropView, &v) == B_OK) + { + // Proxy'd event for child view... + ((LView*)v)->OnEvent((LMessage*)message); + return; + } + else d->View->OnEvent((LMessage*)message); + + if (message->what == B_MOUSE_WHEEL_CHANGED) + { + float x = 0.0f, y = 0.0f; + message->FindFloat("be:wheel_delta_x", &x); + message->FindFloat("be:wheel_delta_y", &y); + d->View->OnMouseWheel(y * 3.0f); + } + else if (message->what == M_SET_SCROLL) + { + return; + } + + Parent::MessageReceived(message); } void Draw(BRect updateRect) { + if (!d) return; LScreenDC dc(d->View); - d->View->OnPaint(&dc); + LPoint off(0,0); + d->View->_Paint(&dc, &off, NULL); } - LMouse ConvertMouse(BPoint where) + LMouse ConvertMouse(BPoint where, bool down = false) { LMouse m; BPoint loc; uint32 buttons = 0; m.Target = d->View; m.x = where.x; m.y = where.y; - - Parent::GetMouse(&loc, &buttons, false); + + if (down) + { + m.Down(true); + Parent::GetMouse(&loc, &buttons, false); + MouseButtons = buttons; // save for up click + } + else + { + buttons = MouseButtons; + } + if (buttons & B_PRIMARY_MOUSE_BUTTON) m.Left(true); if (buttons & B_TERTIARY_MOUSE_BUTTON) m.Middle(true); if (buttons & B_SECONDARY_MOUSE_BUTTON) m.Right(true); uint32 mod = modifiers(); if (mod & B_SHIFT_KEY) m.Shift(true); if (mod & B_OPTION_KEY) m.Alt(true); if (mod & B_CONTROL_KEY) m.Ctrl(true); + if (mod & B_COMMAND_KEY) m.System(true); return m; } void MouseDown(BPoint where) { - LMouse m = ConvertMouse(where); - m.Down(true); + if (!d) return; + static uint64_t lastClick = 0; + bigtime_t interval = 0; + status_t r = get_click_speed(&interval); + auto now = LCurrentTime(); + bool doubleClick = now-lastClick < (interval/1000); + lastClick = now; + + LMouse m = ConvertMouse(where, true); + m.Double(doubleClick); d->View->_Mouse(m, false); } void MouseUp(BPoint where) { + if (!d) return; LMouse m = ConvertMouse(where); m.Down(false); d->View->_Mouse(m, false); } void MouseMoved(BPoint where, uint32 code, const BMessage *dragMessage) { + if (!d) return; LMouse m = ConvertMouse(where); + m.Down( m.Left() || + m.Middle() || + m.Right()); + m.IsMove(true); d->View->_Mouse(m, true); } + + void MakeFocus(bool focus=true) + { + if (!d) return; + Parent::MakeFocus(focus); + d->View->OnFocus(focus); + } }; +template +uint32 LBView::MouseButtons = 0; + //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view), - Hnd(new LBView(this)) + Hnd(new LBView(this)) { } LViewPrivate::~LViewPrivate() { View->d = NULL; + MsgQue.DeleteObjects(); + if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); if (Hnd) { - if (Hnd->LockLooper()) - delete Hnd; - Hnd = NULL; + auto *bv = dynamic_cast*>(Hnd); + // printf("%p::~LViewPrivate View=%p bv=%p th=%u\n", this, View, bv, GetCurrentThreadId()); + if (bv) + bv->d = NULL; + + auto Wnd = Hnd->Window(); + bool locked = false; + + if (Wnd) + locked = Wnd->LockLooper(); + + if (Hnd->Parent()) + Hnd->RemoveSelf(); + + DeleteObj(Hnd); + + if (Wnd && locked) + Wnd->UnlockLooper(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); - OnFocus(f); + LLocker lck(d->Hnd, _FL); + if (lck.Lock()) + { + d->Hnd->MakeFocus(f); + lck.Unlock(); + } + + // OnFocus will be called by the LBview handler... Invalidate(); } void LView::_Delete() { SetPulse(); // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) _Capturing = 0; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) { LAppInst->AppWnd = 0; } // Hierarchy LViewI *c; while ((c = Children[0])) { if (c->GetParent() != (LViewI*)this) { printf("%s:%i - ~LView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); // Misc Pos.ZOff(-1, -1); } LView *&LView::PopupChild() { return d->Popup; } +BCursorID LgiToHaiku(LCursor c) +{ + switch (c) + { + #define _(l,h) case l: return h; + _(LCUR_Blank, B_CURSOR_ID_NO_CURSOR) + _(LCUR_Normal, B_CURSOR_ID_SYSTEM_DEFAULT) + _(LCUR_UpArrow, B_CURSOR_ID_RESIZE_NORTH) + _(LCUR_DownArrow, B_CURSOR_ID_RESIZE_SOUTH) + _(LCUR_LeftArrow, B_CURSOR_ID_RESIZE_WEST) + _(LCUR_RightArrow, B_CURSOR_ID_RESIZE_EAST) + _(LCUR_Cross, B_CURSOR_ID_CROSS_HAIR) + _(LCUR_Wait, B_CURSOR_ID_PROGRESS) + _(LCUR_Ibeam, B_CURSOR_ID_I_BEAM) + _(LCUR_SizeVer, B_CURSOR_ID_RESIZE_NORTH_SOUTH) + _(LCUR_SizeHor, B_CURSOR_ID_RESIZE_EAST_WEST) + _(LCUR_SizeBDiag, B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST) + _(LCUR_SizeFDiag, B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST) + _(LCUR_PointingHand, B_CURSOR_ID_GRAB) + _(LCUR_Forbidden, B_CURSOR_ID_NOT_ALLOWED) + _(LCUR_DropCopy, B_CURSOR_ID_COPY) + _(LCUR_DropMove, B_CURSOR_ID_MOVE) + // _(LCUR_SizeAll, + // _(LCUR_SplitV, + // _(LCUR_SplitH, + #undef _ + } + + return B_CURSOR_ID_SYSTEM_DEFAULT; +} + bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - %s\n", _FL, m.ToString().Get()); #endif if ( GetWindow() && !GetWindow()->HandleViewMouse(this, m) ) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass()); #endif return false; } #if 0 //DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (Move) { auto *o = m.Target; if (_Over != o) { #if DEBUG_MOUSE_EVENTS // if (!o) WindowFromPoint(m.x, m.y, true); LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } + + int cursor = GetCursor(m.x, m.y); + if (cursor >= 0) + { + BCursorID haikuId = LgiToHaiku((LCursor)cursor); + static BCursorID curId = B_CURSOR_ID_SYSTEM_DEFAULT; + if (curId != haikuId) + { + curId = haikuId; + + LLocker lck(Handle(), _FL); + if (lck.Lock()) + { + Handle()->SetViewCursor(new BCursor(curId)); + lck.Unlock(); + } + } + } } LView *Target = NULL; if (_Capturing) Target = dynamic_cast(_Capturing); else Target = dynamic_cast(_Over ? _Over : this); if (!Target) return false; LRect Client = Target->LView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y) || _Capturing) { if (Move) Target->OnMouseMove(m); else Target->OnMouseClick(m); } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } return true; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); - c.Size(Edge, Edge); + c.Inset(Edge, Edge); } return c; } LViewI *LView::FindControl(OsView hCtrl) { if (d->Hnd == hCtrl) { return this; } for (auto i : Children) { LViewI *Ctrl = i->FindControl(hCtrl); if (Ctrl) { return Ctrl; } } return 0; } void LView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool LView::SetPos(LRect &p, bool Repaint) { if (Pos != p) { Pos = p; LLocker lck(d->Hnd, _FL); if (lck.Lock()) { d->Hnd->ResizeTo(Pos.X(), Pos.Y()); d->Hnd->MoveTo(Pos.x1, Pos.y1); lck.Unlock(); } OnPosChange(); } return true; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { auto *ParWnd = GetWindow(); if (!ParWnd) return false; // Nothing we can do till we attach if (!InThread()) { DEBUG_INVALIDATE("%s::Invalidate out of thread\n", GetClass()); return PostEvent(M_INVALIDATE, NULL, (LMessage::Param)this); } LRect r; if (rc) { r = *rc; } else { if (Frame) r = Pos.ZeroTranslate(); else r = GetClient().ZeroTranslate(); } DEBUG_INVALIDATE("%s::Invalidate r=%s frame=%i\n", GetClass(), r.GetStr(), Frame); if (!Frame) r.Offset(_BorderSize, _BorderSize); LPoint Offset; WindowVirtualOffset(&Offset); r.Offset(Offset.x, Offset.y); DEBUG_INVALIDATE(" voffset=%i,%i = %s\n", Offset.x, Offset.y, r.GetStr()); if (!r.Valid()) { DEBUG_INVALIDATE(" error: invalid\n"); return false; } static bool Repainting = false; if (!Repainting) { Repainting = true; - LLocker lck(d->Hnd, _FL); - if (lck.Lock()) + if (d->Hnd) { - d->Hnd->Invalidate(); + LLocker lck(d->Hnd, _FL); + if (lck.Lock()) + d->Hnd->Invalidate(); } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { case M_HANDLE_IN_THREAD: { LMessage::InThreadCb *Cb = NULL; if (Msg->FindPointer(LMessage::PropCallback, (void**)&Cb) == B_OK) { + // printf("M_HANDLE_IN_THREAD before call..\n"); (*Cb)(); + // printf("M_HANDLE_IN_THREAD after call..\n"); delete Cb; + // printf("M_HANDLE_IN_THREAD after delete..\n"); } else printf("%s:%i - No Callback.\n", _FL); break; } case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CHANGE: { LViewI *Ctrl = NULL; if (GetViewById(Msg->A(), Ctrl)) { LNotification n((LNotifyType)Msg->B()); return OnNotify(Ctrl, n); } break; } case M_COMMAND: { - return OnCommand(Msg->A(), 0, (OsView) Msg->B()); + // printf("M_COMMAND %i\n", (int)Msg->A()); + return OnCommand(Msg->A(), 0, 0); + } + case M_THREAD_COMPLETED: + { + auto Th = (LThread*)Msg->A(); + if (!Th) + break; + + Th->OnComplete(); + if (Th->GetDeleteOnExit()) + delete Th; + + return true; } } return 0; } OsView LView::Handle() const { + if (!d) + { + printf("%s:%i - No priv?\n", _FL); + return NULL; + } + return d->Hnd; } bool LView::PointToScreen(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); + // printf("p=%i,%i offset=%i,%i\n", p.x, p.y, Offset.x, Offset.y); p += Offset; + // printf("p=%i,%i\n", p.x, p.y); LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } - BPoint pt = Handle()->ConvertToScreen(BPoint(Offset.x, Offset.y)); - Offset.x = pt.x; - Offset.y = pt.y; + BPoint pt = Handle()->ConvertToScreen(BPoint(p.x, p.y)); + // printf("pt=%g,%g\n", pt.x, pt.y); + p.x = pt.x; + p.y = pt.y; + // printf("p=%i,%i\n\n", p.x, p.y); return true; } bool LView::PointToView(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } BPoint pt = Handle()->ConvertFromScreen(BPoint(Offset.x, Offset.y)); Offset.x = pt.x; Offset.y = pt.y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { LLocker Locker(d->Hnd, _FL); if (!Locker.Lock()) return false; // get mouse state BPoint Cursor; uint32 Buttons; d->Hnd->GetMouse(&Cursor, &Buttons); if (ScreenCoords) d->Hnd->ConvertToScreen(&Cursor); // position m.x = Cursor.x; m.y = Cursor.y; // buttons m.Left(TestFlag(Buttons, B_PRIMARY_MOUSE_BUTTON)); m.Middle(TestFlag(Buttons, B_TERTIARY_MOUSE_BUTTON)); m.Right(TestFlag(Buttons, B_SECONDARY_MOUSE_BUTTON)); // key states key_info KeyInfo; if (get_key_info(&KeyInfo) == B_OK) { m.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); m.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); m.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); } return true; } bool LView::IsAttached() { bool attached = false; + LLocker lck(d->Hnd, _FL); if (lck.Lock()) { auto pview = d->Hnd->Parent(); auto pwnd = d->Hnd->Window(); attached = pview != NULL || pwnd != NULL; } return attached; } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { bool Status = false; + bool Debug = false; // !Stricmp(GetClass(), "LScrollBar"); LView *Parent = d->GetParent(); LAssert(Parent == NULL || Parent == parent); SetParent(parent); Parent = d->GetParent(); auto WndNull = _Window == NULL; _Window = Parent ? Parent->_Window : this; if (!parent) { LgiTrace("%s:%i - No parent window.\n", _FL); } else { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); - auto *Wnd = dynamic_cast(parent); - if (Wnd) + auto bview = parent->Handle(); + if (!bview) { - auto bwnd = parent->WindowHandle(); - if (bwnd) - { - if (bwnd->LockLooper()) - { - #if 0 - LgiTrace("%s:%i - Attaching %s to window %s (parent=%p)\n", - _FL, GetClass(), parent->GetClass(), ::IsAttached(d)); - #endif - - if (::IsAttached(d->Hnd)) - d->Hnd->RemoveSelf(); - bwnd->AddChild(d->Hnd); - - d->Hnd->ResizeTo(Pos.X(), Pos.Y()); - d->Hnd->MoveTo(Pos.x1, Pos.y1); - if (Visible()) - d->Hnd->Show(); - - bwnd->Unlock(); - - Status = true; - - #if 0 - LgiTrace("%s:%i - Attached %s/%p to window %s/%p, success (parent=%p)\n", - _FL, - GetClass(), d->Hnd, - parent->GetClass(), bwnd, - ::IsAttached(d->Hnd)); - #endif - } - else - { - LgiTrace("%s:%i - Error attaching %s to window %s, can't lock.\n", - _FL, GetClass(), parent->GetClass()); - } - } - else LgiTrace("%s:%i - Error no window handle for %s\n", _FL, parent->GetClass()); + LgiTrace("%s:%i - No bview for %s\n", _FL, GetClass()); } else { - auto bview = parent->Handle(); - if (bview) + LLocker lck(bview, _FL); + if (lck.Lock()) { - LLocker lck(bview, _FL); - if (lck.Lock()) - { - #if 0 + if (Debug) LgiTrace("%s:%i - Attaching %s to view %s\n", _FL, GetClass(), parent->GetClass()); - #endif + + d->Hnd->SetName(GetClass()); + if (::IsAttached(d->Hnd)) + d->Hnd->RemoveSelf(); + bview->AddChild(d->Hnd); - if (::IsAttached(d->Hnd)) - d->Hnd->RemoveSelf(); - bview->AddChild(d->Hnd); + d->Hnd->ResizeTo(Pos.X(), Pos.Y()); + d->Hnd->MoveTo(Pos.x1, Pos.y1); + + bool ShowView = TestFlag(GViewFlags, GWF_VISIBLE) && d->Hnd->IsHidden(); + if (Debug) + LgiTrace("%s:%i - IsHidden=%i ShowView=%i\n", _FL, d->Hnd->IsHidden(), ShowView); + if (ShowView) + d->Hnd->Show(); - d->Hnd->ResizeTo(Pos.X(), Pos.Y()); - d->Hnd->MoveTo(Pos.x1, Pos.y1); - if (Visible()) - d->Hnd->Show(); + if (TestFlag(WndFlags, GWF_FOCUS)) + d->Hnd->MakeFocus(); - Status = true; + Status = true; + + if (d->MsgQue.Length()) + { + printf("%s:%i - %s.Attach msgQue=%i\n", _FL, GetClass(), (int)d->MsgQue.Length()); + for (auto bmsg: d->MsgQue) + d->Hnd->Window()->PostMessage(bmsg); + d->MsgQue.DeleteObjects(); + } - #if 0 + if (Debug) LgiTrace("%s:%i - Attached %s/%p to view %s/%p, success\n", _FL, GetClass(), d->Hnd, parent->GetClass(), bview); - #endif - } - else - { - #if 0 - LgiTrace("%s:%i - Error attaching %s to view %s, can't lock.\n", - _FL, GetClass(), parent->GetClass()); - #endif - } + } + else + { + LgiTrace("%s:%i - Error attaching %s to view %s, can't lock.\n", + _FL, GetClass(), parent->GetClass()); } } if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } } return Status; } bool LView::Detach() { ThreadCheck(); // Detach view if (_Window) { auto *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } LViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); Par->Invalidate(&Pos); } d->Parent = 0; d->ParentI = 0; #if 0 // Windows is not doing a deep detach... so we shouldn't either? { int Count = Children.Length(); if (Count) { int Detached = 0; LViewI *c, *prev = NULL; while ((c = Children[0])) { LAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LAssert(Count == Detached); } } #endif return true; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, }; diff --git a/src/haiku/Widgets.cpp b/src/haiku/Widgets.cpp --- a/src/haiku/Widgets.cpp +++ b/src/haiku/Widgets.cpp @@ -1,227 +1,279 @@ /*hdr ** FILE: GuiDlg.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Dialog components ** ** Copyright (C) 1998 Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Slider.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" /////////////////////////////////////////////////////////////////////////////////////////// #define GreyBackground() struct LDialogPriv { int ModalStatus; int BtnId; bool IsModal, IsModeless; bool Resizable; + LDialog::OnClose ModalCb; + thread_id CallingThread = NULL; LDialogPriv() { IsModal = false; IsModeless = false; Resizable = true; ModalStatus = 0; BtnId = -1; } }; /////////////////////////////////////////////////////////////////////////////////////////// -LDialog::LDialog() +LDialog::LDialog(LViewI *parent) : #ifdef __GTK_H__ // , LWindow(gtk_dialog_new()) LWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL)), #endif ResObject(Res_Dialog) { d = new LDialogPriv(); Name("Dialog"); + if (parent) + SetParent(parent); } LDialog::~LDialog() { DeleteObj(d); } bool LDialog::IsModal() { return d->IsModal; } int LDialog::GetButtonId() { return d->BtnId; } int LDialog::OnNotify(LViewI *Ctrl, LNotification n) { LButton *b = dynamic_cast(Ctrl); if (b) { d->BtnId = b->GetId(); if (d->IsModal) EndModal(); else if (d->IsModeless) EndModeless(); } return 0; } void LDialog::Quit(bool DontDelete) { if (d->IsModal) EndModal(0); else LView::Quit(DontDelete); } void LDialog::OnPosChange() { LWindow::OnPosChange(); if (Children.Length() == 1) { List::I it = Children.begin(); LTableLayout *t = dynamic_cast((LViewI*)it); if (t) { LRect r = GetClient(); - r.Size(LTableLayout::CellSpacing, LTableLayout::CellSpacing); + r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(r); // _Dump(); } } } bool LDialog::LoadFromResource(int Resource, char *TagList) { LAutoString n; LRect p; LProfile Prof("LDialog::LoadFromResource"); bool Status = LResourceLoad::LoadFromResource(Resource, this, &p, &n, TagList); if (Status) { Prof.Add("Name."); Name(n); SetPos(p); } return Status; } bool LDialog::OnRequestClose(bool OsClose) { if (d->IsModal) { EndModal(0); return false; } return true; } -int LDialog::DoModal(OsView OverrideParent) +void LDialog::DoModal(OnClose Cb, OsView OverrideParent) { d->ModalStatus = -1; auto Parent = GetParent(); if (Parent) - { MoveSameScreen(Parent); - } d->IsModal = true; d->IsModeless = false; - // LAppInst->Run(); - printf("%s:%i - DoModal not supported.\n", _FL); - - return d->ModalStatus; + d->ModalCb = Cb; + d->CallingThread = find_thread(NULL); + + if (WindowHandle() && + Parent && + Parent->WindowHandle()) + { + // Keep this dialog above the parent window... + WindowHandle()->SetFeel(B_MODAL_SUBSET_WINDOW_FEEL); + WindowHandle()->AddToSubset(Parent->WindowHandle()); + } + else LgiTrace("%s:%i - Can't set parent for modal.\n", _FL); + + BLooper *looper = BLooper::LooperForThread(d->CallingThread); + if (!looper) + printf("%s:%i - no looper for domodal thread.\n",_FL); + + if (Attach(0)) + { + AttachChildren(); + Visible(true); + } + else printf("%s:%i - attach failed..\n", _FL); } void LDialog::EndModal(int Code) { - if (d->IsModal) + if (!d->IsModal) + { + LgiTrace("%s:%i - EndModal error: LDialog is not model.\n", _FL); + return; + } + + d->IsModal = false; + if (!d->ModalCb) + { + // If no callback is supplied, the default option is to just delete the + // dialog, closing it. + delete this; + return; + } + + BLooper *looper = BLooper::LooperForThread(d->CallingThread); + if (!looper) { - d->IsModal = false; - d->ModalStatus = Code; - LAppInst->Exit(); + LgiTrace("%s:%i - Failed to get looper for %p\n", _FL, d->CallingThread); + delete this; + return; } - else - { - // LAssert(0); - } + + BMessage *m = new BMessage(M_HANDLE_IN_THREAD); + m->AddPointer + ( + LMessage::PropCallback, + new LMessage::InThreadCb + ( + [dlg=this,cb=d->ModalCb,code=Code]() + { + // printf("%s:%i - Calling LDialog callback.. in original thread\n", _FL); + cb(dlg, code); + // printf("%s:%i - Calling LDialog callback.. done\n", _FL); + } + ) + ); + + looper->PostMessage(m); } int LDialog::DoModeless() { d->IsModal = false; d->IsModeless = true; + + Visible(true); return 0; } void LDialog::EndModeless(int Code) { Quit(Code); } extern LButton *FindDefault(LView *w); LMessage::Param LDialog::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } /////////////////////////////////////////////////////////////////////////////////////////// LControl::LControl(OsView view) : LView(view) { Pos.ZOff(10, 10); } LControl::~LControl() { } LMessage::Param LControl::OnEvent(LMessage *Msg) { return 0; } LPoint LControl::SizeOfStr(const char *Str) { int y = LSysFont->GetHeight(); LPoint Pt(0, 0); if (Str) { const char *e = 0; for (const char *s = Str; s && *s; s = e?e+1:0) { e = strchr(s, '\n'); auto Len = e ? e - s : strlen(s); LDisplayString ds(LSysFont, s, Len); Pt.x = MAX(Pt.x, ds.X()); Pt.y += y; } } return Pt; } diff --git a/src/haiku/Window.cpp b/src/haiku/Window.cpp --- a/src/haiku/Window.cpp +++ b/src/haiku/Window.cpp @@ -1,1029 +1,1044 @@ #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" +#include "MenuBar.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 +#define DEBUG_WAIT_THREAD 1 +#define DEBUG_SERIALIZE_STATE 0 + +#if DEBUG_WAIT_THREAD + #define WAIT_LOG(...) LgiTrace(__VA_ARGS__) +#else + #define WAIT_LOG(...) +#endif + +LString ToString(BRect &r) +{ + LString s; + s.Printf("%g,%g,%g,%g", r.left, r.top, r.right, r.bottom); + return s; +} /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate : public BWindow { public: LWindow *Wnd; bool SnapToEdge = false; LArray Hooks; LWindowPrivate(LWindow *wnd) : Wnd(wnd), BWindow(BRect(100,100,400,400), "...loading...", B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 0) { } ~LWindowPrivate() { - Wnd->d = NULL; + DeleteObj(Wnd->Menu); + if (IsMinimized()) + Wnd->_PrevZoom = LZoomMin; + Wnd->d = NULL; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } void FrameMoved(BPoint newPosition) { auto Pos = Frame(); + if (Pos != Wnd->Pos) { Wnd->Pos = Pos; Wnd->OnPosChange(); } + + BWindow::FrameMoved(newPosition); } void FrameResized(float newWidth, float newHeight) { auto Pos = Frame(); if (Pos != Wnd->Pos) { - Wnd->Pos = Pos; + Wnd->Pos.SetSize(newWidth, newHeight); Wnd->OnPosChange(); } + BWindow::FrameResized(newWidth, newHeight); } bool QuitRequested() { - return Wnd->OnRequestClose(false); + printf("%s::QuitRequested() starting.. %s\n", Wnd->GetClass(), Wnd->Pos.GetStr()); + auto r = Wnd->OnRequestClose(false); + // printf("%s::QuitRequested()=%i\n", Wnd->GetClass(), r); + return r; } void MessageReceived(BMessage *message) { if (message->what == M_LWINDOW_DELETE) { // printf("Processing M_LWINDOW_DELETE th=%u\n", GetCurrentThreadId()); Wnd->Handle()->RemoveSelf(); Quit(); // printf("Processed M_LWINDOW_DELETE\n"); } else { BWindow::MessageReceived(message); - Wnd->OnEvent((LMessage*)message); + + LView *view = NULL; + auto r = message->FindPointer(LMessage::PropView, &view); + if (r == B_OK) + view->OnEvent((LMessage*)message); + else + Wnd->OnEvent((LMessage*)message); } } }; /////////////////////////////////////////////////////////////////////// -#define GWND_CREATE 0x0010000 - LWindow::LWindow() : LView(0) { d = new LWindowPrivate(this); _QuitOnClose = false; Menu = NULL; _Default = 0; _Window = this; - WndFlags |= GWND_CREATE; ClearFlag(WndFlags, GWF_VISIBLE); - - _Lock = new ::LMutex; } LWindow::~LWindow() { if (LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; - DeleteObj(Menu); - DeleteObj(_Lock); + LAssert(!Menu); + WaitThread(); +} + +int LWindow::WaitThread() +{ + if (!d) + return -1; + + thread_id id = d->Thread(); + bool thisThread = id == GetCurrentThreadId(); - if (d) + WAIT_LOG("%s::~LWindow thread=%u lock=%u\n", Name(), GetCurrentThreadId(), d->LockingThread()); + if (thisThread) { - d->Quit(); + // We are in thread... can delete easily. + if (d->Lock()) + { + // printf("%s::~LWindow Quiting\n", Name()); + Handle()->RemoveSelf(); + d->Quit(); + // printf("%s::~LWindow Quit finished\n", Name()); + } + d = NULL; + return 0; // If we're in thread... no need to wait. } + + // Post event to the window's thread to delete itself... + WAIT_LOG("%s::~LWindow posting M_LWINDOW_DELETE from th=%u\n", Name(), GetCurrentThreadId()); + d->PostMessage(new BMessage(M_LWINDOW_DELETE)); + + status_t value = 0; + WAIT_LOG("wait_for_thread(%u) start..\n", id); + wait_for_thread(id, &value); + WAIT_LOG("wait_for_thread(%u) end=%i\n", id, value); + d = NULL; + + return value; } OsWindow LWindow::WindowHandle() { return d; } bool LWindow::SetIcon(const char *FileName) { LString a; if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } return false; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { LLocker lck(d, _FL); if (!lck.Lock()) return false; return d->IsActive(); } bool LWindow::SetActive() { LLocker lck(d, _FL); if (!lck.Lock()) return false; d->Activate(); return true; } bool LWindow::Visible() { LLocker lck(d, _FL); if (!lck.Lock()) return false; return !d->IsHidden(); } void LWindow::Visible(bool i) { LLocker lck(d, _FL); if (!lck.Lock()) { printf("%s:%i - Can't lock.\n", _FL); return; } if (i) - d->Show(); + { + if (d->IsHidden()) + { + printf("%s show %s\n", GetClass(), GetPos().GetStr()); + d->MoveTo(Pos.x1, Pos.y1); + d->ResizeTo(Pos.X(), Pos.Y()); + d->Show(); + } + else + printf("%s already shown\n", GetClass()); + } else - d->Hide(); + { + if (!d->IsHidden()) + d->Hide(); + } } bool LWindow::Obscured() { return false; } 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; } bool LWindow::Attach(LViewI *p) { LLocker lck(d, _FL); if (!lck.Lock()) return false; + + auto rootView = Handle(); + auto wnd = WindowHandle(); + // printf("%s:%i attach %p to %p\n", _FL, Handle(), WindowHandle()); + if (rootView && wnd) + { + wnd->AddChild(rootView); + if (rootView->IsHidden()) + rootView->Show(); + + auto menu = wnd->KeyMenuBar(); + BRect menuPos = menu ? menu->Frame() : BRect(0, 0, 0, 0); + + auto f = wnd->Frame(); + rootView->ResizeTo(f.Width(), f.Height() - menuPos.Height()); + if (menu) + rootView->MoveTo(0, menuPos.Height()); + rootView->SetResizingMode(B_FOLLOW_ALL_SIDES); + } // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Do a rough layout of child windows PourAll(); return true; } 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", + LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s\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); + (char*)(k.Ctrl()?" Ctrl":"")); } #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: return Status; } void LWindow::Raise() { } LWindowZoom LWindow::GetZoom() { if (!d) - { - LgiTrace("%s:%i - No priv ptr?\n", _FL); - return LZoomNormal; - } + return _PrevZoom; LLocker lck(d, _FL); if (!IsAttached() || lck.Lock()) { if (d->IsMinimized()) return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { LLocker lck(d, _FL); if (lck.Lock()) { d->Minimize(i == LZoomMin); } } 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) { LLocker lck(d, _FL); if (lck.Lock()) d->SetTitle(n); + return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } LPoint LWindow::GetDpi() { return LPoint(96, 96); } 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); + + r = Pos.ZeroTranslate(); + + LLocker lck(WindowHandle(), _FL); + if (lck.Lock()) + { + auto br = Handle()->Bounds(); + r = br; + + auto frm = d->Frame(); + frm.OffsetBy(-frm.left, -frm.top); + // printf("Frame=%s Bounds=%s r=%s\n", ToString(frm).Get(), ToString(br).Get(), r.GetStr()); + + lck.Unlock(); + } + return r; } +#if DEBUG_SERIALIZE_STATE +#define SERIALIZE_LOG(...) printf(__VA_ARGS__) +#else +#define SERIALIZE_LOG(...) +#endif + 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; iSetValue(FieldName, v)) + { + SERIALIZE_LOG("SerializeState: SetValue failed\n"); return false; + } } return true; } LRect &LWindow::GetPos() { return Pos; } bool LWindow::SetPos(LRect &p, bool Repaint) { + printf("SetPos %s -> %s\n", Pos.GetStr(), p.GetStr()); Pos = p; LLocker lck(d, _FL); if (lck.Lock()) { d->MoveTo(Pos.x1, Pos.y1); d->ResizeTo(Pos.X(), Pos.Y()); } + else printf("%s:%i - Failed to lock.\n", _FL); return true; } void LWindow::OnChildrenChanged(LViewI *Wnd, bool Attaching) { // Force repour } void LWindow::OnCreate() { AttachChildren(); } void LWindow::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } void LWindow::OnPosChange() { + LLocker lck(WindowHandle(), _FL); + if (lck.Lock()) + { + auto frame = WindowHandle()->Bounds(); + auto menu = WindowHandle()->KeyMenuBar(); + auto menuPos = menu ? menu->Frame() : BRect(0, 0, 0, 0); + auto rootPos = Handle()->Frame(); + if (menu) + { + if (menu->IsHidden()) // Why? + { + menu->Show(); + if (menu->IsHidden()) + { + // printf("Can't show menu?\n"); + for (auto p = menu->Parent(); p; p = p->Parent()) + printf(" par=%s %i\n", p->Name(), p->IsHidden()); + } + } + if (menuPos.Width() < 1) // Again... WHHHHY? FFS + { + float x = 0.0f, y = 0.0f; + menu->GetPreferredSize(&x, &y); + // printf("Pref=%g,%g\n", x, y); + if (y > 0.0f) + menu->ResizeTo(frame.Width(), y); + } + } + #if 0 + printf("frame=%s menu=%p,%i,%s rootpos=%s\n", + ToString(frame).Get(), menu, menu?menu->IsHidden():0, ToString(menuPos).Get(), ToString(rootPos).Get()); + #endif + int rootTop = menu ? menuPos.bottom + 1 : 0; + if (rootPos.top != rootTop) + { + Handle()->MoveTo(0, rootTop); + Handle()->ResizeTo(rootPos.Width(), frame.Height() - menuPos.Height()); + } + + lck.Unlock(); + } + LView::OnPosChange(); PourAll(); } #define IsTool(v) \ ( \ dynamic_cast(v) \ && \ dynamic_cast(v)->_IsToolBar \ ) void LWindow::PourAll() { LRect c = GetClient(); LRegion Client(c); LViewI *MenuView = 0; LRegion Update(Client); bool HasTools = false; LViewI *v; List::I Lst = Children.begin(); { LRegion Tools; for (v = *Lst; v; v = *++Lst) { bool IsMenu = MenuView == v; if (!IsMenu && IsTool(v)) { LRect OldPos = v->GetPos(); if (OldPos.Valid()) Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); // LgiTrace("%s = %s\n", v->GetClass(), Bar.GetStr()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } Lst = Children.begin(); for (LViewI *v = *Lst; v; v = *++Lst) { bool IsMenu = MenuView == v; if (!IsMenu && !IsTool(v)) { LRect OldPos = v->GetPos(); if (OldPos.Valid()) Update.Union(&OldPos); if (v->Pour(Client)) { // LRect p = v->GetPos(); // LgiTrace("%s = %s\n", v->GetClass(), p.GetStr()); if (!v->Visible()) v->Visible(true); v->Invalidate(); Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // non-pourable } } } for (int i=0; iMsg()) { case M_CLOSE: { if (OnRequestClose(false)) { Quit(); return 0; } break; } } return LView::OnEvent(m); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { int i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } bool LWindow::UnregisterHook(LView *Target) { int i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } void LWindow::OnFrontSwitch(bool b) { } LViewI *LWindow::GetFocus() { return NULL; } -#if DEBUG_SETFOCUS -static LAutoString DescribeView(LViewI *v) -{ - if (!v) - return LAutoString(NewStr("NULL")); - - char s[512]; - int ch = 0; - LArray p; - for (LViewI *i = v; i; i = i->GetParent()) - { - p.Add(i); - } - for (int n=MIN(3, p.Length()-1); n>=0; n--) - { - char Buf[256] = ""; - if (!stricmp(v->GetClass(), "LMdiChild")) - sprintf(Buf, "'%s'", v->Name()); - 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) { } 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::SetAlwaysOnTop(bool b) { } diff --git a/src/linux/Lgi/Menu.cpp b/src/linux/Lgi/Menu.cpp --- a/src/linux/Lgi/Menu.cpp +++ b/src/linux/Lgi/Menu.cpp @@ -1,1579 +1,1577 @@ /*hdr ** FILE: LMenuGtk.cpp ** AUTHOR: Matthew Allen ** DATE: 19/4/2013 ** DESCRIPTION: Gtk menus ** ** Copyright (C) 2013, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" using namespace Gtk; #define DEBUG_MENUS 0 #if DEBUG_MENUS #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif /////////////////////////////////////////////////////////////////////////////////////////////// static ::LArray Active; void SubMenuDestroy(LSubMenu *Item) { // LgiTrace("DestroySub %p %p\n", Item, Item->Info); Item->Info = NULL; } LSubMenu::LSubMenu(const char *name, bool Popup) { Menu = NULL; Parent = NULL; Info = NULL; _ContextMenuId = NULL; ActiveTs = 0; InLoop = false; if (name) { Info = GtkCast(Gtk::gtk_menu_new(), gtk_menu_shell, GtkMenuShell); // LgiTrace("CreateSub %p %p\n", this, Info); Gtk::g_signal_connect_data( Info, "destroy", (Gtk::GCallback) SubMenuDestroy, this, NULL, Gtk::G_CONNECT_SWAPPED); } Active.Add(this); } LSubMenu::~LSubMenu() { Active.Delete(this); while (Items.Length()) { LMenuItem *i = Items[0]; LAssert(i->Parent == this); DeleteObj(i); } Info.Destroy([](GtkMenuShell *s){ gtk_widget_destroy(GTK_WIDGET(s)); }); } // This will be run in the GUI thread.. gboolean LSubMenuClick(LMouse *m) { if (!m) return false; bool OverMenu = false, HasVisible = false; uint64 ActiveTs = 0; for (auto s: Active) { auto w = GTK_WIDGET(s->Handle()); auto vis = gtk_widget_is_visible(w); if (vis) { // auto src = gtk_widget_get_screen(w); // auto hnd = gdk_screen_get_root_window(src); auto hnd = gtk_widget_get_window(w); ActiveTs = MAX(s->ActiveTs, ActiveTs); HasVisible = true; GdkRectangle a; gdk_window_get_frame_extents(hnd, &a); /* LgiTrace("SubClk down=%i, pos=%i,%i sub=%i,%i-%i,%i\n", m->Down(), m->x, m->y, a.x, a.y, a.width, a.height); */ LRect rc = a; if (rc.Overlap(m->x, m->y)) OverMenu = true; } } if (m->Down() && !OverMenu && HasVisible && ActiveTs > 0) { uint64 Now = LCurrentTime(); uint64 Since = Now - ActiveTs; LgiTrace("%s:%i - LSubMenuClick, Since=" LPrintfInt64 "\n", _FL, Since); if (Since > 500) { for (auto s: Active) { auto w = GTK_WIDGET(s->Handle()); auto vis = gtk_widget_is_visible(w); if (vis && s->ActiveTs) { gtk_widget_hide(w); s->ActiveTs = 0; } } } } else LOG("LSubMenuClick Down=%i OverMenu=%i HasVIsible=%i ActiveTs=%i\n", m->Down(), OverMenu, HasVisible, ActiveTs > 0); DeleteObj(m); return false; } // This is not called in the GUI thread.. void LSubMenu::SysMouseClick(LMouse &m) { LMouse *ms = new LMouse; *ms = m; g_idle_add((GSourceFunc) LSubMenuClick, ms); } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where < 0 ? Items.Length() : Where, Shortcut); if (i) { i->Enabled(Enabled); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { gtk_menu_shell_append(Info, item); gtk_widget_show(item); } return i; } return 0; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *i = new LMenuItem; if (i) { i->Parent = this; i->Menu = Menu; i->Id(-2); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { gtk_menu_shell_append(Info, item); gtk_widget_show(item); } return i; } return 0; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { LBase::Name(Str); LMenuItem *i = new LMenuItem(Menu, this, Str, Where < 0 ? Items.Length() : Where, NULL); if (i) { i->Id(-1); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { if (Where < 0) gtk_menu_shell_append(Info, item); else gtk_menu_shell_insert(Info, item, Where); gtk_widget_show(item); } i->Child = new LSubMenu(Str); if (i->Child) { i->Child->Parent = i; i->Child->Menu = Menu; i->Child->Window = Window; GtkWidget *sub = GTK_WIDGET(i->Child->Handle()); LAssert(sub); if (i->Handle() && sub) { gtk_menu_item_set_submenu(i->Handle(), sub); gtk_widget_show(sub); } else LgiTrace("%s:%i Error: gtk_menu_item_set_submenu(%p,%p) failed\n", _FL, i->Handle(), sub); } return i->Child; } return 0; } void LSubMenu::ClearHandle() { Info = NULL; for (auto i: Items) { i->ClearHandle(); } } void LSubMenu::Empty() { LMenuItem *i; while ((i = Items[0])) { RemoveItem(i); DeleteObj(i); } } bool LSubMenu::RemoveItem(int i) { return RemoveItem(Items.ItemAt(i)); } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item)) { return Item->Remove(); } return false; } bool LSubMenu::IsContext(LMenuItem *Item) { if (!_ContextMenuId) { LMenuItem *i = GetParent(); LSubMenu *s = i ? i->GetParent() : NULL; if (s) // Walk up the chain of menus to find the top... return s->IsContext(Item); else // Ok we got to the top return false; } *_ContextMenuId = Item->Id(); Gtk::gtk_main_quit(); return true; } void GtkDeactivate(Gtk::GtkMenuShell *widget, LSubMenu *Sub) { Sub->OnActivate(false); } void LSubMenu::OnActivate(bool a) { if (!a) { if (_ContextMenuId) *_ContextMenuId = 0; if (InLoop) { Gtk::gtk_main_quit(); InLoop = false; } } } int LSubMenu::Float(LView *From, int x, int y, int Button) { #ifdef __GTK_H__ LWindow *Wnd = From->GetWindow(); if (!Wnd) return -1; Wnd->Capture(false); #else while (From && !From->Handle()) { From = dynamic_cast(From->GetParent()); } if (!From || !From->Handle()) { LOG("%s:%i - No menu handle\n", _FL); return -1; } if (From->IsCapturing()) From->Capture(false); #endif ActiveTs = LCurrentTime(); // This signal handles the case where the user cancels the menu by clicking away from it. Info.Connect("deactivate", (GCallback)GtkDeactivate, this); auto Widget = GTK_WIDGET(Info.obj); gtk_widget_show_all(Widget); int MenuId = 0; _ContextMenuId = &MenuId; LPoint Pos(x, y); gtk_menu_popup(GTK_MENU(Info.obj), NULL, NULL, NULL, NULL, Button, gtk_get_current_event_time()); InLoop = true; gtk_main(); _ContextMenuId = NULL; return MenuId; } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); // LOG("Find(%i) '%s' %i sub=%p\n", Id, i->Name(), i->Id(), Sub); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: bool StartUnderline; // Underline the first display string bool HasAccel; // The last display string should be right aligned List Strs; // Draw each alternate display string with underline // except the last in the case of HasAccel==true. LString Shortcut; LMenuItemPrivate() { HasAccel = false; StartUnderline = false; } ~LMenuItemPrivate() { Strs.DeleteObjects(); } void UpdateStrings(LFont *Font, char *n) { // Build up our display strings, Strs.DeleteObjects(); StartUnderline = false; char *Tab = strrchr(n, '\t'); if (Tab) { *Tab = 0; } char *Amp = 0, *a = n; while (a = strchr(a, '&')) { if (a[1] != '&') { Amp = a; break; } a++; } if (Amp) { // Before amp Strs.Insert(new LDisplayString(Font, n, Amp - n )); // Amp'd letter char *e = LSeekUtf8(++Amp, 1); Strs.Insert(new LDisplayString(Font, Amp, e - Amp )); // After Amp if (*e) { Strs.Insert(new LDisplayString(Font, e)); } } else { Strs.Insert(new LDisplayString(Font, n)); } if (HasAccel = (Tab != 0)) { Strs.Insert(new LDisplayString(Font, Tab + 1)); *Tab = '\t'; } else if (HasAccel = (Shortcut.Get() != 0)) { Strs.Insert(new LDisplayString(Font, Shortcut)); } } }; static LAutoString MenuItemParse(const char *s) { char buf[256], *out = buf; const char *in = s; while (in && *in && out < buf + sizeof(buf) - 1) { if (*in == '_') { *out++ = '_'; *out++ = '_'; } else if (*in != '&' || in[1] == '&') { *out++ = *in; } else { *out++ = '_'; } in++; } *out++ = 0; char *tab = strrchr(buf, '\t'); if (tab) { *tab++ = 0; } return LAutoString(NewStr(buf)); } static void MenuItemActivate(GtkMenuItem *MenuItem, LMenuItem *Item) { Item->OnGtkEvent("activate"); } static void MenuItemDestroy(GtkWidget *widget, LMenuItem *Item) { Item->OnGtkEvent("destroy"); } void LMenuItem::OnGtkEvent(::LString Event) { if (Event.Equals("activate")) { if (!Sub() && !InSetCheck) { LSubMenu *Parent = GetParent(); if (!Parent || !Parent->IsContext(this)) { auto m = GetMenu(); if (m) { // Attached to a menu, so send an event to the window LViewI *w = m->WindowHandle(); if (w) w->PostEvent(M_COMMAND, Id()); else LAssert(!"No window for menu to send to"); } else { // Could be just a popup menu... in which case do nothing. } } } } else if (Event.Equals("destroy")) { // LgiTrace("DestroyItem %p %p\n", this, Info); Info = NULL; } } LMenuItem::LMenuItem() { d = new LMenuItemPrivate(); Info = NULL; Child = NULL; Menu = NULL; Parent = NULL; InSetCheck = false; Handle(GTK_MENU_ITEM(gtk_separator_menu_item_new())); Position = -1; _Flags = 0; _Icon = -1; _Id = 0; } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int id, int Pos, const char *shortcut) { d = NULL; LAutoString Txt = MenuItemParse(txt); LBase::Name(txt); Info = NULL; Handle(GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(Txt))); Child = NULL; Menu = m; Parent = p; InSetCheck = false; Position = Pos; _Flags = 0; _Icon = -1; _Id = id; ShortCut = shortcut; ScanForAccel(); } void LMenuItem::Handle(GtkMenuItem *mi) { LAssert(Info == NULL); if (Info != mi) { Info = mi; Gtk::g_signal_connect(Info, "activate", (Gtk::GCallback) MenuItemActivate, this); Gtk::g_signal_connect(Info, "destroy", (Gtk::GCallback) MenuItemDestroy, this); } } LMenuItem::~LMenuItem() { Remove(); Info.Destroy([](GtkMenuItem *i){ gtk_widget_destroy(GTK_WIDGET(i)); }); DeleteObj(Child); DeleteObj(d); } #if GtkVer(2, 24) #include #endif #ifndef GDK_F1 enum { GDK_F1 = 0xffbe, GDK_F2, GDK_F3, GDK_F4, GDK_F5, GDK_F6, GDK_F7, GDK_F8, GDK_F9, GDK_F10, GDK_F11, GDK_F12 }; #endif // /usr/include/gtk-3.0/gdk/gdkkeysyms-compat.h Gtk::gint LgiKeyToGtkKey(int Key, const char *ShortCut) { #ifdef GDK_a LAssert(GDK_a == 'a'); LAssert(GDK_A == 'A'); #endif /* if (Key >= 'a' && Key <= 'z') return Key; */ if (Key >= 'A' && Key <= 'Z') return Key; if (Key >= '0' && Key <= '9') return Key; switch (Key) { #ifdef GDK_Delete case LK_DELETE: return GDK_Delete; #endif #ifdef GDK_Insert case LK_INSERT: return GDK_Insert; #endif #ifdef GDK_Home case LK_HOME: return GDK_Home; #endif #ifdef GDK_End case LK_END: return GDK_End; #endif #ifdef GDK_Page_Up case LK_PAGEUP: return GDK_Page_Up; #endif #ifdef GDK_Page_Down case LK_PAGEDOWN: return GDK_Page_Down; #endif #ifdef GDK_BackSpace case LK_BACKSPACE: return GDK_BackSpace; #endif #ifdef GDK_Escape case LK_ESCAPE: return GDK_Escape; #else #warning "GDK_Escape not defined." #endif case LK_UP: return GDK_Up; case LK_DOWN: return GDK_Down; case LK_LEFT: return GDK_Left; case LK_RIGHT: return GDK_Right; case LK_F1: return GDK_F1; case LK_F2: return GDK_F2; case LK_F3: return GDK_F3; case LK_F4: return GDK_F4; case LK_F5: return GDK_F5; case LK_F6: return GDK_F6; case LK_F7: return GDK_F7; case LK_F8: return GDK_F8; case LK_F9: return GDK_F9; case LK_F10: return GDK_F10; case LK_F11: return GDK_F11; case LK_F12: return GDK_F12; case ' ': #ifdef GDK_space return GDK_space; #else return ' '; #endif case ',': return GDK_comma; default: LgiTrace("Unhandled menu accelerator: 0x%x (%s)\n", Key, ShortCut); break; } return 0; } bool LMenuItem::ScanForAccel() { if (!Menu) return false; const char *Sc = ShortCut; if (!Sc) { char *n = LBase::Name(); if (n) { char *Tab = strchr(n, '\t'); if (Tab) Sc = Tab + 1; } } if (!Sc) return false; auto Keys = LString(Sc).SplitDelimit("+-"); if (Keys.Length() <= 0) return false; int Flags = 0; int Vkey = 0; int Chr = 0; for (int i=0; i 0); Gtk::gint GtkKey = LgiKeyToGtkKey(Vkey ? Vkey : Chr, Sc); LOG("scan(%s) vkey=%i, chr=%i, gtk=%i, id=%i\n", Sc, Vkey, Chr, GtkKey, Ident); if (GtkKey) { GtkWidget *w = GtkCast(Info.obj, gtk_widget, GtkWidget); Gtk::GdkModifierType mod = (Gtk::GdkModifierType) ( (TestFlag(Flags, LGI_EF_CTRL) ? Gtk::GDK_CONTROL_MASK : 0) | (TestFlag(Flags, LGI_EF_SHIFT) ? Gtk::GDK_SHIFT_MASK : 0) | (TestFlag(Flags, LGI_EF_ALT) ? Gtk::GDK_MOD1_MASK : 0) | (TestFlag(Flags, LGI_EF_SYSTEM) ? Gtk::GDK_META_MASK : 0) ); gtk_widget_add_accelerator( w, "activate", Menu->AccelGrp, GtkKey, mod, Gtk::GTK_ACCEL_VISIBLE ); gtk_widget_show_all(w); } else { LOG("%s:%i - No gtk key for '%s'\n", _FL, Sc); } Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Ident) ); } else { LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Sc); return false; } return true; } LSubMenu *LMenuItem::GetParent() { return Parent; } void LMenuItem::ClearHandle() { Info = NULL; if (Child) Child->ClearHandle(); } bool LMenuItem::Remove() { if (!Parent) { return false; } if (Info) { Gtk::GtkWidget *w = GTK_WIDGET(Info.obj); if (Gtk::gtk_widget_get_parent(w)) { Gtk::GtkContainer *c = GtkCast(Parent->Info.obj, gtk_container, GtkContainer); Gtk::gtk_container_remove(c, w); } } LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); Parent = NULL; return true; } void LMenuItem::Id(int i) { _Id = i; } void LMenuItem::Separator(bool s) { if (s) { _Id = -2; } } struct MenuItemIndex { Gtk::GtkWidget *w; int Current; int Index; MenuItemIndex() { Index = -1; Current = 0; w = NULL; } }; static void FindMenuItemIndex(Gtk::GtkWidget *w, Gtk::gpointer data) { MenuItemIndex *d = (MenuItemIndex*)data; LOG("w=%p d->w=%p name=%s cur=%i d->Index=%i\n", w, d->w, G_OBJECT_TYPE_NAME(w), d->Current, d->Index); if (w == d->w) d->Index = d->Current; d->Current++; } int gtk_container_get_child_index(GtkContainer *c, GtkWidget *child) { MenuItemIndex Idx; if (c && child) { Idx.w = child; gtk_container_foreach(c, FindMenuItemIndex, &Idx); } return Idx.Index; } bool LMenuItem::Replace(Gtk::GtkWidget *newWid) { if (!newWid || !Info) { LgiTrace("%s:%i - Error: New=%p Old=%p\n", newWid, Info.obj); return false; } // Get widget GtkWidget *w = GTK_WIDGET(Info.obj); // Is is attach already? if (gtk_widget_get_parent(w)) { // Yes! GtkContainer *c = GTK_CONTAINER(Parent->Info.obj); if (c) { // Find index int PIdx = Parent->Items.IndexOf(this); LAssert(PIdx >= 0); // Remove old item gtk_container_remove(c, w); // Add new item gtk_menu_shell_insert(Parent->Info, newWid, PIdx); gtk_widget_show(newWid); } else LgiTrace("%s:%i - GTK_CONTAINER failed.\n", _FL); } else { // Delete it g_object_unref(Info); } Handle(GTK_MENU_ITEM(newWid)); return Info != NULL; } LImageList *LMenuItem::GetImageList() { if (GetMenu()) return GetMenu()->GetImageList(); // Search tree of parents for an image list... for (auto p = GetParent(); p; ) { auto lst = p->GetImageList(); if (lst) return lst; auto pmi = p->GetParent(); if (pmi) p = pmi->GetParent(); else break; } return NULL; } gboolean LgiMenuItemDraw(GtkWidget *widget, cairo_t *cr, LMenuItem *mi) { auto cls = GTK_WIDGET_GET_CLASS(widget); cls->draw(widget, cr); mi->PaintIcon(cr); return true; } void LMenuItem::PaintIcon(Gtk::cairo_t *cr) { if (_Icon < 0) return; auto il = GetImageList(); if (!il) return; auto wid = GTK_WIDGET(Info.obj); GtkAllocation a; gtk_widget_get_allocation(wid, &a); GdkRGBA bk = {0}; gtk_style_context_get_background_color( gtk_widget_get_style_context(wid), gtk_widget_get_state_flags(wid), &bk); LScreenDC Dc(cr, a.width, a.height); il->Draw(&Dc, 7, 5, _Icon, bk.alpha ? LColour(bk) : LColour::White); } void LMenuItem::Icon(int i) { _Icon = i; if (Info) Info.Connect("draw", (GCallback)LgiMenuItemDraw, this); } void LMenuItem::Checked(bool c) { if (c) SetFlag(_Flags, ODS_CHECKED); else ClearFlag(_Flags, ODS_CHECKED); if (Info) { InSetCheck = true; // Is the item a checked menu item? if (!GTK_IS_CHECK_MENU_ITEM(Info.obj) && c) { // Create a checkable menu item... LAutoString Txt = MenuItemParse(Name()); GtkWidget *w = gtk_check_menu_item_new_with_mnemonic(Txt); // Attach our signal gulong ret = g_signal_connect_data( w, "activate", (GCallback) MenuItemActivate, this, NULL, G_CONNECT_SWAPPED); // Replace the existing menu item with this new one if (w) Replace(w); else LAssert(!"No new widget."); } if (GTK_IS_CHECK_MENU_ITEM(Info.obj)) { // Now mark is checked GtkCheckMenuItem *chk = GTK_CHECK_MENU_ITEM(Info.obj); if (chk) { Gtk::gtk_check_menu_item_set_active(chk, c); } } InSetCheck = false; } } bool LMenuItem::Name(const char *n) { bool Status = LBase::Name(n); #if GtkVer(2, 16) LAssert(Info); LAutoString Txt = MenuItemParse(n); ScanForAccel(); gtk_menu_item_set_label(Info, Txt); #else LgiTrace("Warning: can't set label after creation."); Status = false; #endif return Status; } void LMenuItem::Enabled(bool e) { if (e) ClearFlag(_Flags, ODS_DISABLED); else SetFlag(_Flags, ODS_DISABLED); if (Info) { gtk_widget_set_sensitive(GtkCast(Info.obj, gtk_widget, GtkWidget), e); } } void LMenuItem::Focus(bool f) { } void LMenuItem::Sub(LSubMenu *s) { Child = s; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return _Id; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return _Id == -2; } bool LMenuItem::Checked() { return TestFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Enabled() { return !TestFlag(_Flags, ODS_DISABLED); } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return 0; } LSubMenu *LMenuItem::Sub() { return Child; } int LMenuItem::Icon() { return _Icon; } /////////////////////////////////////////////////////////////////////////////////////////////// struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; class LMenuPrivate { public: }; LMenu::LMenu(const char *AppName) : LSubMenu("", false) { Menu = this; d = NULL; AccelGrp = Gtk::gtk_accel_group_new(); Info = GtkCast(Gtk::gtk_menu_bar_new(), gtk_menu_shell, GtkMenuShell); } LMenu::~LMenu() { Accel.DeleteObjects(); } LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); if (MenuFont.f) { // MenuFont.f->CodePage(LSysFont->CodePage()); } else { LOG("LMenu::GetFont Couldn't create menu font.\n"); } } else { LOG("LMenu::GetFont Couldn't get menu typeface.\n"); } if (!MenuFont.f) { MenuFont.f = new LFont; if (MenuFont.f) { *MenuFont.f = *LSysFont; } } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { if (!p) { LAssert(0); return false; } LWindow *Wnd = p->GetWindow(); if (!Wnd) { LAssert(0); return false; } Window = Wnd; if (Wnd->_VBox) { LAssert(!"Already has a menu"); return false; } Gtk::GtkWidget *menubar = GTK_WIDGET(Info.obj); Wnd->_VBox = Gtk::gtk_box_new(Gtk::GTK_ORIENTATION_VERTICAL, 0); Gtk::GtkBox *vbox = GTK_BOX(Wnd->_VBox); Gtk::GtkContainer *wndcontainer = GTK_CONTAINER(Wnd->Wnd); g_object_ref(Wnd->_Root); gtk_container_remove(wndcontainer, Wnd->_Root); gtk_box_pack_start(vbox, menubar, false, false, 0); gtk_box_pack_end(vbox, Wnd->_Root, true, true, 0); gtk_container_add(wndcontainer, Wnd->_VBox); gtk_widget_show_all(GTK_WIDGET(Wnd->Wnd)); g_object_unref(Wnd->_Root); gtk_window_add_accel_group(Wnd->Wnd, AccelGrp); #if 0 gtk_widget_queue_resize(GTK_WIDGET(vbox)); #else GdkRectangle allocation = Wnd->GetClient(); g_signal_emit_by_name(G_OBJECT(vbox), "size-allocate", GTK_WIDGET(vbox), &allocation, NULL, NULL); #endif return true; } bool LMenu::Detach() { bool Status = false; return Status; } bool LMenu::SetPrefAndAboutItems(int a, int b) { return false; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down()) { for (auto a: Accel) { if (a->Match(k)) { LAssert(a->GetId() > 0); Window->OnCommand(a->GetId(), 0, 0); return true; } } if (k.Alt() && !dynamic_cast(v) && !dynamic_cast(v)) { bool Hide = false; for (auto s: Items) { if (!s->Separator()) { if (Hide) { // s->Info->HideSub(); } else { char *n = s->Name(); if (ValidStr(n)) { char *Amp = strchr(n, '&'); if (Amp) { while (Amp && Amp[1] == '&') Amp = strchr(Amp + 2, '&'); char16 Accel = tolower(Amp[1]); char16 Press = tolower(k.c16); if (Accel == Press) Hide = true; } } if (Hide) { // s->Info->ShowSub(); } else { // s->Info->HideSub(); } } } } if (Hide) { return true; } } } return false; } //////////////////////////////////////////////////////////////////////////// LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; Vkey = vkey; Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { if (k.vkey == LK_RSHIFT || k.vkey == LK_LSHIFT || k.vkey == LK_RCTRL || k.vkey == LK_LCTRL || k.vkey == LK_RALT || k.vkey == LK_RALT) { return false; } #if 1 LOG("LAccelerator::Match %i(%i)%s%s%s = %i(%i)%s%s%s%s\n", k.vkey, k.c16, k.Ctrl()?" ctrl":"", k.Alt()?" alt":"", k.Shift()?" shift":"", Vkey, Chr, TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"", TestFlag(Flags, LGI_EF_ALT)?" alt":"", TestFlag(Flags, LGI_EF_SHIFT)?" shift":"", TestFlag(Flags, LGI_EF_SYSTEM)?" system":"" ); #endif if ( (Chr != 0 && toupper(k.c16) == toupper(Chr)) || (Vkey != 0 && k.vkey == Vkey) ) { LOG("...key match.\n"); if ( ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0) && ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0) && ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0) && ((TestFlag(Flags, LGI_EF_SYSTEM) ^ k.System()) == 0) ) { LOG("...mod match: Id=%i\n", Id); return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { Flags = GWF_VISIBLE; Id = 0; ToolButton = 0; MenuItem = 0; - Accelerator = 0; TipHelp = 0; PrevValue = false; } LCommand::~LCommand() { - DeleteArray(Accelerator); DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; } diff --git a/src/linux/Lgi/Printer.cpp b/src/linux/Lgi/Printer.cpp --- a/src/linux/Lgi/Printer.cpp +++ b/src/linux/Lgi/Printer.cpp @@ -1,171 +1,172 @@ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Button.h" #include "lgi/common/Printer.h" using namespace Gtk; //////////////////////////////////////////////////////////////////// class LPrinterPrivate { public: GtkPrintOperation *Op; GtkPrintSettings *Settings; ::LString JobName; ::LString Printer; ::LString Err; ::LString PrinterName; LPrintEvents *Events; LAutoPtr PrintDC; LPrinterPrivate() { Settings = NULL; Events = NULL; Op = NULL; } ~LPrinterPrivate() { if (Op) g_object_unref(Op); } }; //////////////////////////////////////////////////////////////////// LPrinter::LPrinter() { d = new LPrinterPrivate; } LPrinter::~LPrinter() { DeleteObj(d); } bool LPrinter::Browse(LView *Parent) { if (d->Settings != NULL) Gtk::gtk_print_operation_set_print_settings(d->Op, d->Settings); return false; } bool LPrinter::Serialize(::LString &Str, bool Write) { if (Write) Str = d->Printer; else d->Printer = Str; return true; } ::LString LPrinter::GetErrorMsg() { return d->Err; } static void GtkPrintBegin( GtkPrintOperation *operation, GtkPrintContext *context, LPrinterPrivate *d) { - bool Status = false; - GtkPrintSettings *settings = gtk_print_operation_get_print_settings(operation); if (settings) { d->PrinterName = gtk_print_settings_get_printer(settings); } - if (d->PrintDC.Reset(new LPrintDC(context, d->JobName, d->PrinterName))) + if (!d->PrintDC.Reset(new LPrintDC(context, d->JobName, d->PrinterName))) + return; + + d->Events->OnBeginPrint(d->PrintDC, [&](auto Pages) { - int Pages = d->Events->OnBeginPrint(d->PrintDC); if (Pages > 0) - { gtk_print_operation_set_n_pages(d->Op, Pages); - Status = true; - } - } - if (!Status) - gtk_print_operation_cancel(d->Op); + else + gtk_print_operation_cancel(d->Op); + }); } static void GtkPrintDrawPage( GtkPrintOperation *operation, GtkPrintContext *context, gint page_number, LPrinterPrivate *d) { cairo_t *ct = gtk_print_context_get_cairo_context(context); if (ct && d->PrintDC) LAssert(d->PrintDC->Handle() == ct); // Just checking it's the same handle d->Events->OnPrintPage(d->PrintDC, page_number); } -int LPrinter::Print(LPrintEvents *Events, const char *PrintJobName, int Pages /* = -1 */, LView *Parent /* = 0 */) +void LPrinter::Print( LPrintEvents *Events, + std::function Callback, + const char *PrintJobName, + int Pages, + LView *Parent) { if (!Events) { LgiTrace("%s:%i - Error: missing param.\n", _FL); return false; } d->Op = gtk_print_operation_new(); if (!d->Op) { LgiTrace("%s:%i - Error: gtk_print_operation_new failed.\n", _FL); return false; } GError *Error = NULL; GtkPrintOperationResult Result; GtkWindow *Wnd = NULL; if (Parent) { LWindow *w = Parent->GetWindow(); if (w) Wnd = w->WindowHandle(); } d->Events = Events; d->JobName = PrintJobName; g_signal_connect(G_OBJECT(d->Op), "begin-print", G_CALLBACK(GtkPrintBegin), d); g_signal_connect(G_OBJECT(d->Op), "draw-page", G_CALLBACK(GtkPrintDrawPage), d); gtk_print_operation_set_job_name(d->Op, PrintJobName); Result = gtk_print_operation_run(d->Op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, Wnd, &Error); bool Status = Result != GTK_PRINT_OPERATION_RESULT_ERROR; if (!Status) { if (Error) { LgiTrace("%s:%i - gtk_print_operation_run failed with: %i - %s\n", _FL, Error->code, Error->message); } else { LgiTrace("%s:%i - gtk_print_operation_run failed with: unknown error\n", _FL); } } if (d->Op) { g_object_unref(d->Op); d->Op = NULL; } return Status; } diff --git a/src/linux/Lgi/Widgets.cpp b/src/linux/Lgi/Widgets.cpp --- a/src/linux/Lgi/Widgets.cpp +++ b/src/linux/Lgi/Widgets.cpp @@ -1,264 +1,268 @@ /*hdr ** FILE: GuiDlg.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Dialog components ** ** Copyright (C) 1998 Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Slider.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" using namespace Gtk; #include "LgiWidget.h" /////////////////////////////////////////////////////////////////////////////////////////// #define GreyBackground() struct LDialogPriv { int ModalStatus; int BtnId; bool IsModal, IsModeless; bool Resizable; LDialogPriv() { IsModal = false; IsModeless = false; Resizable = true; ModalStatus = 0; BtnId = -1; } }; /////////////////////////////////////////////////////////////////////////////////////////// -LDialog::LDialog() +LDialog::LDialog(LViewI *parent) : #ifdef __GTK_H__ // , LWindow(gtk_dialog_new()) LWindow(gtk_window_new(GTK_WINDOW_TOPLEVEL)), #endif ResObject(Res_Dialog) { d = new LDialogPriv(); Name("Dialog"); _SetDynamic(false); + + if (parent) + SetParent(parent); } LDialog::~LDialog() { DeleteObj(d); } bool LDialog::IsModal() { return d->IsModal; } int LDialog::GetButtonId() { return d->BtnId; } int LDialog::OnNotify(LViewI *Ctrl, LNotification n) { LButton *b = dynamic_cast(Ctrl); if (b) { d->BtnId = b->GetId(); if (d->IsModal) EndModal(); else if (d->IsModeless) EndModeless(); } return 0; } void LDialog::Quit(bool DontDelete) { if (d->IsModal) EndModal(0); else LView::Quit(DontDelete); } void LDialog::OnPosChange() { LWindow::OnPosChange(); if (Children.Length() == 1) { List::I it = Children.begin(); LTableLayout *t = dynamic_cast((LViewI*)it); if (t) { LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(r); // _Dump(); } } } bool LDialog::LoadFromResource(int Resource, char *TagList) { LAutoString n; LRect p; LProfile Prof("LDialog::LoadFromResource"); bool Status = LResourceLoad::LoadFromResource(Resource, this, &p, &n, TagList); if (Status) { Prof.Add("Name."); Name(n); SetPos(p); } return Status; } bool LDialog::OnRequestClose(bool OsClose) { if (d->IsModal) { EndModal(0); return false; } return true; } bool LDialog::IsResizeable() { return d->Resizable; } void LDialog::IsResizeable(bool r) { d->Resizable = r; } bool LDialog::SetupDialog(bool Modal) { if (IsResizeable()) { gtk_window_set_default_size(Wnd, Pos.X(), Pos.Y()); } else { gtk_widget_set_size_request(GTK_WIDGET(Wnd), Pos.X(), Pos.Y()); gtk_window_set_resizable(Wnd, FALSE); } auto p = GetParent(); if (!Attach(p)) return false; LWindow::Visible(true); return true; } -int LDialog::DoModal(OsView OverrideParent) +void LDialog::DoModal(OnClose Callback, OsView OverrideParent) { d->ModalStatus = -1; auto Parent = GetParent(); if (Parent) { gtk_window_set_transient_for(GTK_WINDOW(Wnd), Parent->WindowHandle()); MoveSameScreen(Parent); } d->IsModal = true; d->IsModeless = false; SetupDialog(true); LAppInst->Run(); - return d->ModalStatus; + if (Callback) + Callback(this, d->ModalStatus); } void LDialog::EndModal(int Code) { if (d->IsModal) { d->IsModal = false; d->ModalStatus = Code; LAppInst->Exit(); } else { // LAssert(0); } } int LDialog::DoModeless() { d->IsModal = false; d->IsModeless = true; SetupDialog(false); return 0; } void LDialog::EndModeless(int Code) { Quit(Code); } extern LButton *FindDefault(LView *w); LMessage::Param LDialog::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } /////////////////////////////////////////////////////////////////////////////////////////// LControl::LControl(OsView view) : LView(view) { Pos.ZOff(10, 10); } LControl::~LControl() { } LMessage::Param LControl::OnEvent(LMessage *Msg) { return 0; } LPoint LControl::SizeOfStr(const char *Str) { int y = LSysFont->GetHeight(); LPoint Pt(0, 0); if (Str) { const char *e = 0; for (const char *s = Str; s && *s; s = e?e+1:0) { e = strchr(s, '\n'); auto Len = e ? e - s : strlen(s); LDisplayString ds(LSysFont, s, Len); Pt.x = MAX(Pt.x, ds.X()); Pt.y += y; } } return Pt; } 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,1881 +1,1879 @@ #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); } -/* -static void PixbufDestroyNotify(guchar *pixels, LSurface *data) +int LWindow::WaitThread() { - delete data; + 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(); } auto p = GetParent(); if (p) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } 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 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) { } void LWindow::SetParent(LViewI *p) { LView::SetParent(p); if (p && Wnd) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else if (!GTK_IS_WINDOW(Wnd)) LgiTrace("%s:%i - SetParent() - GTK_IS_WINDOW failed.\n", _FL); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } } 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/mac/cocoa/Menu.mm b/src/mac/cocoa/Menu.mm old mode 100755 new mode 100644 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,376 +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; 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() +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; } -bool LFileSelect::Open() +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); - return Status && Length() > 0; + 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; } -bool LFileSelect::OpenFolder() +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; } } - return Status && Length() > 0; + if (Cb) + Cb(this, Status && Length() > 0); } -bool LFileSelect::Save() +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); - return Status && Length() > 0; + if (Cb) + Cb(this, Status && Length() > 0); } diff --git a/src/win/Lgi/General.cpp b/src/win/Lgi/General.cpp --- a/src/win/Lgi/General.cpp +++ b/src/win/Lgi/General.cpp @@ -1,1159 +1,1164 @@ #define _WIN32_WINNT 0x500 // Win32 Implementation of General LGI functions #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/RegKey.h" #define DEBUG_LOG_WRITES 1 #if DEBUG_LOG_WRITES #define LOG_WRITE(...) LgiTrace(__VA_ARGS__) #else #define LOG_WRITE(...) #endif //////////////////////////////////////////////////////////////// // Implementations void LSleep(DWORD i) { ::Sleep(i); } LString LCurrentUserName() { TCHAR username[256]; DWORD username_len = sizeof(username); GetUserName(username, &username_len); return username; } bool LGetMimeTypeExtensions(const char *Mime, LArray &Ext) { auto Start = Ext.Length(); char *e; LRegKey t(false, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\MIME\\Database\\Content Type\\%s", Mime); if (t.IsOk() && (e = t.GetStr("Extension"))) { if (*e == '.') e++; Ext.Add(e); } else { #define HardCodeExtention(mime, Ext1, Ext2) \ else if (!stricmp(Mime, mime)) \ { if (Ext1) Ext.Add(Ext1); \ if (Ext2) Ext.Add(Ext2); } const char *Null = NULL; if (!Mime); HardCodeExtention("text/calendar", "ics", Null) HardCodeExtention("text/x-vcard", "vcf", Null) HardCodeExtention("text/mbox", "mbx", "mbox") HardCodeExtention("text/html", "html", Null) HardCodeExtention("text/plain", "txt", Null) HardCodeExtention("message/rfc822", "eml", Null) HardCodeExtention("audio/mpeg", "mp3", Null) HardCodeExtention("application/msword", "doc", Null) HardCodeExtention("application/pdf", "pdf", Null) } return Ext.Length() > Start; } LString LGetFileMimeType(const char *File) { if (File) { char *Dot = strrchr((char*)File, '.'); if (Dot) { bool AssertOnError = LRegKey::AssertOnError; LRegKey::AssertOnError = false; LRegKey Key(false, "HKEY_CLASSES_ROOT\\%s", Dot); if (Key.IsOk()) { char *Ct = Key.GetStr("Content Type"); if (Ct && !stricmp(Dot, ".dsw") == 0 && !stricmp(Dot, ".dsp") == 0) { return Ct; } else { // Search mime type DB. LRegKey Db(false, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\MIME\\Database\\Content Type"); List Sub; Db.GetKeyNames(Sub); for (auto k: Sub) { LRegKey Type(false, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\MIME\\Database\\Content Type\\%s", k); char *Ext = Type.GetStr("Extension"); if (Ext && stricmp(Ext, Dot) == 0) { return k; } } Sub.DeleteArrays(); // This is a hack to get around file types without a MIME database entry // but do have a .ext entry. LGetAppsForMimeType knows about the hack too. LString MimeType; MimeType.Printf("application/%s", Dot); return MimeType; } } LRegKey::AssertOnError = AssertOnError; } // no extension? // no registry entry for file type? return "application/octet-stream"; } return LString(); } bool _GetApps_Add(LArray &Apps, char *In) { LAutoString Path; if (!In) return false; while (*In && strchr(WhiteSpace, *In)) In++; if (!*In) return false; for (char *i = In; true; i++) { if (*i == '\'' || *i == '\"') { char delim = *i++; char *end = strchr(i, delim); if (!end) end = i + strlen(i); Path.Reset(NewStr(i, end-i)); In = end + (*end != 0); break; } else if (!*i || strchr(WhiteSpace, *i)) { char old = *i; *i = 0; if (LFileExists(In)) { Path.Reset(NewStr(In)); } *i = old; if (Path) { In = i + (*i != 0); break; } } if (!*i) break; } if (Path) { LStringPipe p; char *RootVar = "%SystemRoot%"; char *SysRoot = stristr(Path, RootVar); if (SysRoot) { // correct path for variables TCHAR Temp[256]; UINT Ch = GetWindowsDirectory(Temp, CountOf(Temp)); LString Tmp = Temp; if (Tmp(-1) != DIR_CHAR) Tmp += DIR_STR; p.Push(Tmp); char *End = SysRoot + strlen(RootVar); p.Push(*End == DIR_CHAR ? End + 1 : End); } else { p.Push(Path); } LAppInfo *a = new LAppInfo; if (a) { Apps[Apps.Length()] = a; a->Params = LString(In).Strip(); a->Path = p.NewGStr(); if (a->Path) { char e[MAX_PATH_LEN]; char *d = strrchr(a->Path, DIR_CHAR); if (d) strcpy_s(e, sizeof(e), d + 1); else strcpy_s(e, sizeof(e), a->Path); d = strchr(e, '.'); if (d) *d = 0; e[0] = toupper(e[0]); a->Name = e; if (ValidStr(a->Name)) { bool AllCaps = true; for (char *s=a->Name; *s; s++) { if (islower(*s)) { AllCaps = false; break; } } if (AllCaps) { Strlwr(a->Name.Get() + 1); } } } return true; } } return false; } -bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) +bool LGetAppsForMimeType(const char *Mime, LArray &Apps, int Limit) { bool Status = false; if (Mime) { if (stricmp(Mime, "application/email") == 0) { // get email app LRegKey Key(false, "HKEY_CLASSES_ROOT\\mailto\\shell\\open\\command"); if (Key.IsOk()) { // get app path char *Str = Key.GetStr(); // if (RegQueryValueEx(hKey, 0, 0, &Type, (uchar*)Str, &StrLen) == ERROR_SUCCESS) if (Str) { Status = _GetApps_Add(Apps, Str); } } } else if (!stricmp(Mime, "application/browser")) { // get default browser char *Keys[] = { "HKCU", "HKLM" }; char Base[] = "SOFTWARE\\Clients\\StartMenuInternet"; for (int i=0; i Keys; if (Shell.GetKeyNames(Keys)) { LRegKey First(false, "HKEY_CLASSES_ROOT\\Applications\\%s\\shell\\%s\\command", Application, Keys[0]); char *Path; if (Path = First.GetStr()) { Status |= _GetApps_Add(Apps, Path); } } Keys.DeleteArrays(); } } DeleteArray(Mru); } if (!Status) { // Explorers file extensions LRegKey FileExt(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s", Ext); char *Application; if (Application = FileExt.GetStr("Application")) { LRegKey App(false, "HKEY_CLASSES_ROOT\\Applications\\%s\\shell\\open\\command", Application); char *Path; if (Path = App.GetStr()) { Status = _GetApps_Add(Apps, Path); } } } if (!Status) { // get classes location LRegKey ExtEntry(false, "HKEY_CLASSES_ROOT\\%s", Ext); LRegKey TypeEntry(false, "HKEY_CLASSES_ROOT\\%s\\shell\\open\\command", ExtEntry.GetStr()); char *Path = TypeEntry.GetStr(); if (Path) { const char *c = Path; char *Part = LTokStr(c); if (Part) { char AppPath[256]; _snprintf_s(AppPath, sizeof(AppPath), "\"%s\"", Part); Status = _GetApps_Add(Apps, AppPath); DeleteArray(Part); } else { Status = _GetApps_Add(Apps, Path); } } } } } } } return Status; } LString LGetAppForMimeType(const char *Mime) { LString App; LArray Apps; if (LGetAppsForMimeType(Mime, Apps, 1)) App = Apps[0]->Path.Get(); Apps.DeleteObjects(); return App; } int LRand(int i) { return (rand() % i); } bool LPlaySound(const char *FileName, int Flags) { bool Status = false; HMODULE hDll = LoadLibrary(_T("winmm.dll")); if (hDll) { typedef BOOL (__stdcall *Proc_sndPlaySound)(LPCSTR pszSound, UINT fuSound); Proc_sndPlaySound psndPlaySound = (Proc_sndPlaySound)GetProcAddress(hDll, "sndPlaySoundA"); if (psndPlaySound) { if (LGetOs() == LGI_OS_WIN9X) { // async broken on 98? Flags = 0; } Status = psndPlaySound(FileName, Flags) != 0; } FreeLibrary(hDll); } return Status; } #include LString LErrorCodeToString(uint32_t ErrorCode) { LString Str; HMODULE hModule = NULL; LPSTR MessageBuffer = NULL; DWORD dwBufferLength; DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM ; if (ErrorCode >= NERR_BASE && ErrorCode <= MAX_NERR) { hModule = LoadLibraryEx( TEXT("netmsg.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE); if (hModule != NULL) dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE; } if (dwBufferLength = FormatMessageA(dwFormatFlags, hModule, // module to get message from (NULL == system) ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language (LPSTR) &MessageBuffer, 0, NULL)) { Str.Set(MessageBuffer, dwBufferLength); LocalFree(MessageBuffer); } if (hModule != NULL) FreeLibrary(hModule); return Str; } bool LExecute(const char *File, const char *Arguments, const char *Dir, LString *ErrorMsg) { int Error = 0; HINSTANCE Status = NULL; if (!File) return false; uint64 Now = LCurrentTime(); if (LGetOs() == LGI_OS_WIN9X) { auto f = LToNativeCp(File); auto a = LToNativeCp(Arguments); auto d = LToNativeCp(Dir); if (f) { Status = ShellExecuteA(NULL, "open", f, a, d, 5); if ((size_t)Status <= 32) Error = GetLastError(); } } else { LAutoWString f(Utf8ToWide(File)); LAutoWString a(Utf8ToWide(Arguments)); LAutoWString d(Utf8ToWide(Dir)); if (f) { Status = ShellExecuteW(NULL, L"open", f, a, d, 5); if ((size_t)Status <= 32) Error = GetLastError(); } } #ifdef _DEBUG if ((size_t)Status <= 32) LgiTrace("ShellExecuteW failed with %p (LastErr=0x%x)\n", Status, Error); if (LCurrentTime() - Now > 1000) LgiTrace("ShellExecuteW took %I64i\n", LCurrentTime() - Now); #endif if (ErrorMsg) *ErrorMsg = LErrorCodeToString(Error); return (size_t)Status > 32; } //////////////////////////////////////////////////////////////////////////////////// HKEY GetRootKey(char *s) { HKEY Root = 0; #define TestKey(Name) \ if (strncmp(s, #Name, strlen(#Name)) == 0) \ { \ Root = Name; \ } TestKey(HKEY_CLASSES_ROOT) else TestKey(HKEY_CURRENT_CONFIG) else TestKey(HKEY_CURRENT_USER) else TestKey(HKEY_LOCAL_MACHINE) else TestKey(HKEY_USERS) #undef TestKey return Root; } bool LRegKey::AssertOnError = true; LRegKey::LRegKey(bool WriteAccess, char *Key, ...) { char Buffer[1025]; k = 0; s[0] = 0; Root = (HKEY)-1; va_list Arg; va_start(Arg, Key); vsprintf_s(Buffer, Key, Arg); va_end(Arg); KeyName = Buffer; if (KeyName) { size_t Len = 0; char *SubKey = 0; #define TestKey(Long, Short) \ if (!strnicmp(KeyName, #Long, Len = strlen(#Long)) || \ !strnicmp(KeyName, #Short, Len = strlen(#Short))) \ { \ Root = Long; \ SubKey = KeyName.Get()[Len] ? KeyName.Get() + Len + 1 : 0; \ } TestKey(HKEY_CLASSES_ROOT, HKCR) else TestKey(HKEY_CURRENT_CONFIG, HKCC) else TestKey(HKEY_CURRENT_USER, HKCU) else TestKey(HKEY_LOCAL_MACHINE, HKLM) else TestKey(HKEY_USERS, HKU) else return; LONG ret = RegOpenKeyExA(Root, SubKey, 0, WriteAccess ? KEY_ALL_ACCESS : KEY_READ, &k); if (ret != ERROR_SUCCESS && ret != ERROR_FILE_NOT_FOUND) { DWORD err = GetLastError(); if (AssertOnError) LAssert(!"RegOpenKeyEx failed"); } } } LRegKey::~LRegKey() { if (k) RegCloseKey(k); } bool LRegKey::IsOk() { return k != NULL; } bool LRegKey::Create() { bool Status = false; if (!k && KeyName) { char *Sub = strchr(KeyName, '\\'); if (Sub) { LONG Ret = RegCreateKeyA(Root, Sub+1, &k); if (Ret == ERROR_SUCCESS) { Status = IsOk(); } else { DWORD err = GetLastError(); if (AssertOnError) LAssert(!"RegCreateKey failed"); } } } return Status; } char *LRegKey::Name() { return KeyName; } bool LRegKey::DeleteValue(char *Name) { if (k) { if (RegDeleteValueA(k, Name) == ERROR_SUCCESS) { return true; } else { DWORD Err = GetLastError(); LAssert(!"RegDeleteValue failed"); } } return false; } bool LRegKey::DeleteKey() { bool Status = false; if (k) { char *n = strchr(KeyName, '\\'); if (n++) { RegCloseKey(k); k = 0; HKEY Root = GetRootKey(KeyName); int Ret = RegDeleteKeyA(Root, n); Status = Ret == ERROR_SUCCESS; if (!Status) { if (AssertOnError) LAssert(!"RegDeleteKey failed."); } KeyName.Empty(); } } return false; } char *LRegKey::GetStr(const char *Name) { if (!k) { LAssert(!"No key to read from."); return NULL; } DWORD Size = sizeof(s), Type; LONG Ret = RegQueryValueExA(k, Name, 0, &Type, (uchar*)s, &Size); if (Ret != ERROR_SUCCESS) { if (AssertOnError) LAssert(!"RegQueryValueEx failed."); return NULL; } return s; } bool LRegKey::GetStr(const char *Name, LString &Str) { if (!k) { if (AssertOnError) LAssert(!"No key to read from."); return false; } DWORD Size = 0, Type; LONG Ret = RegQueryValueExA(k, Name, 0, &Type, NULL, &Size); if (Ret != ERROR_SUCCESS) goto OnError; { LString Tmp((char*)NULL, Size); Ret = RegQueryValueExA(k, Name, 0, &Type, (LPBYTE)Tmp.Get(), &Size); if (Ret != ERROR_SUCCESS) goto OnError; Str = Tmp; return true; } OnError: if (AssertOnError) LAssert(!"RegQueryValueEx failed."); return false; } bool LRegKey::SetStr(const char *Name, const char *Value) { if (!k) { LAssert(!"No key open."); return false; } auto r = RegSetValueExA(k, Name, 0, REG_SZ, (uchar*)Value, Value ? (DWORD)strlen(Value) : 0); LOG_WRITE("RegSetValueExA(%s,%s,'%s')=%i\n", KeyName.Get(), Name, Value, r); if (r != ERROR_SUCCESS) { if (AssertOnError) LAssert(!"RegSetValueEx failed."); return false; } return true; } bool LRegKey::GetInt(const char *Name, uint32_t &Value) { if (!k) return false; DWORD Size = sizeof(Value), Type; LSTATUS r = RegQueryValueExA(k, Name, 0, &Type, (uchar*)&Value, &Size); return r == ERROR_SUCCESS; } bool LRegKey::SetInt(const char *Name, uint32_t Value) { if (!k) { LgiTrace("%s:%i - No key name.\n", _FL); return false; } auto r = RegSetValueExA(k, Name, 0, REG_DWORD, (uchar*)&Value, sizeof(Value)); LOG_WRITE("RegSetValueExA(%s,%s,%i)=%i\n", KeyName.Get(), Name, Value, r); if (r == ERROR_SUCCESS) return true; LgiTrace("%s:%i - RegSetValueExA(%s) returned error: %x.\n", _FL, Name, r); return false; } bool LRegKey::GetBinary(char *Name, void *&Ptr, int &Len) { DWORD Size = 0, Type; if (k && RegQueryValueExA(k, Name, 0, &Type, 0, &Size) == ERROR_SUCCESS) { Len = Size; Ptr = new uchar[Len]; return RegQueryValueExA(k, Name, 0, &Type, (uchar*)Ptr, &Size) == ERROR_SUCCESS; } return false; } bool LRegKey::SetBinary(char *Name, void *Ptr, int Len) { LAssert(!"Not impl."); return false; } bool LRegKey::GetKeyNames(List &n) { FILETIME t; TCHAR Buf[256]; DWORD Size = CountOf(Buf), i = 0; LSTATUS Status; while ((Status = RegEnumKeyEx(k, i++, Buf, &Size, 0, 0, 0, &t)) == ERROR_SUCCESS) { n.Insert(WideToUtf8(Buf)); Size = sizeof(Buf); } return n.Length() > 0; } bool LRegKey::GetValueNames(List &n) { TCHAR Buf[256]; DWORD Type, Size = CountOf(Buf), i = 0; while (RegEnumValue(k, i++, Buf, &Size, 0, &Type, 0, 0) == ERROR_SUCCESS) { n.Insert(WideToUtf8(Buf)); Size = sizeof(Buf); } return n.Length() > 0; } ////////////////////////////////////////////////////////////////////////////////////// LString WinGetSpecialFolderPath(int Id) { LLibrary Shell("Shell32"); LString s; char16 wp[MAX_PATH_LEN] = { 0 }; pSHGetSpecialFolderPathW w = (pSHGetSpecialFolderPathW) Shell.GetAddress("SHGetSpecialFolderPathW"); if (w) { BOOL result = w(0, wp, Id, false); if (result && ValidStrW(wp)) { LAutoString Tmp(WideToUtf8(wp)); s = Tmp; } else { DWORD e = GetLastError(); LAssert(!"Error getting system folder."); } } return s; } ////////////////////////////////////////////////////////////////////// #ifndef LGI_STATIC -int LAssertDlg(LString Msg) +void LAssertDlg(LString Msg, std::function Callback) { LAlert a(LAppInst ? LAppInst->AppWnd : NULL, "Assert Failed", Msg, "Abort", "Debug", "Ignore"); - a.SetAppModal(); - return a.DoModal(); + a.SetAppModal(); + a.DoModal([Callback](auto d, auto code) + { + if (Callback) + Callback(code); + }); } #endif void _lgi_assert(bool b, const char *test, const char *file, int line) { static bool Asserting = false; if (!b) { #ifdef LGI_STATIC assert(b); #else if (Asserting || !LAppInst || !LSysFont) { // Woah boy... LgiTrace("%s:%i - Assert: %s failed.\n", file, line, test); } else { Asserting = true; printf("%s:%i - Assert failed:\n%s\n", file, line, test); #ifdef _DEBUG LString Msg; Msg.Printf("Assert failed, file: %s, line: %i\n%s", file, line, test); int Result = 0; if (LAppInst->InThread()) { // We are in the GUI thread, show the dialog inline - Result = LAssertDlg(Msg); + LAssertDlg(Msg, [](auto Result) + { + switch (Result) + { + case 1: + { + exit(-1); + break; + } + case 2: + { + // Bring up the debugger... + #if defined(_WIN64) || !defined(_MSC_VER) + assert(0); + #else + _asm int 3 + #endif + break; + } + default: + case 3: + { + break; + } + } + }); } else { // Fall back to windows UI assert(0); } - switch (Result) - { - case 1: - { - exit(-1); - break; - } - case 2: - { - // Bring up the debugger... - #if defined(_WIN64) || !defined(_MSC_VER) - assert(0); - #else - _asm int 3 - #endif - break; - } - default: - case 3: - { - break; - } - } - #endif Asserting = false; } #endif } } ////////////////////////////////////////////////////////////////////// // The following code is from: // Web: http://www.codeproject.com/Articles/28071/Toggle-hardware-data-read-execute-breakpoints-prog // License: http://www.codeproject.com/info/cpol10.aspx struct HWBRK { void *a; HANDLE hT; HWBRK_TYPE Type; HWBRK_SIZE Size; HANDLE hEv; int iReg; int Opr; bool SUCC; HWBRK() { Opr = 0; a = 0; hT = 0; hEv = 0; iReg = 0; SUCC = false; } }; static void SetBits(DWORD_PTR& dw, int lowBit, int bits, int newValue) { DWORD_PTR mask = (1 << bits) - 1; dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); } static DWORD WINAPI BreakpointThread(LPVOID lpParameter) { HWBRK* h = (HWBRK*)lpParameter; int j = 0; int y = 0; j = SuspendThread(h->hT); y = GetLastError(); CONTEXT ct = {0}; ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; j = GetThreadContext(h->hT,&ct); y = GetLastError(); int FlagBit = 0; bool Dr0Busy = false; bool Dr1Busy = false; bool Dr2Busy = false; bool Dr3Busy = false; if (ct.Dr7 & 1) Dr0Busy = true; if (ct.Dr7 & 4) Dr1Busy = true; if (ct.Dr7 & 16) Dr2Busy = true; if (ct.Dr7 & 64) Dr3Busy = true; if (h->Opr == 1) { // Remove if (h->iReg == 0) { FlagBit = 0; ct.Dr0 = 0; Dr0Busy = false; } if (h->iReg == 1) { FlagBit = 2; ct.Dr1 = 0; Dr1Busy = false; } if (h->iReg == 2) { FlagBit = 4; ct.Dr2 = 0; Dr2Busy = false; } if (h->iReg == 3) { FlagBit = 6; ct.Dr3 = 0; Dr3Busy = false; } ct.Dr7 &= ~(1 << FlagBit); } else { if (!Dr0Busy) { h->iReg = 0; ct.Dr0 = (DWORD_PTR)h->a; Dr0Busy = true; } else if (!Dr1Busy) { h->iReg = 1; ct.Dr1 = (DWORD_PTR)h->a; Dr1Busy = true; } else if (!Dr2Busy) { h->iReg = 2; ct.Dr2 = (DWORD_PTR)h->a; Dr2Busy = true; } else if (!Dr3Busy) { h->iReg = 3; ct.Dr3 = (DWORD_PTR)h->a; Dr3Busy = true; } else { h->SUCC = false; j = ResumeThread(h->hT); y = GetLastError(); SetEvent(h->hEv); return 0; } ct.Dr6 = 0; int st = 0; if (h->Type == HWBRK_TYPE_CODE) st = 0; if (h->Type == HWBRK_TYPE_READWRITE) st = 3; if (h->Type == HWBRK_TYPE_WRITE) st = 1; int le = 0; if (h->Size == HWBRK_SIZE_1) le = 0; if (h->Size == HWBRK_SIZE_2) le = 1; if (h->Size == HWBRK_SIZE_4) le = 3; if (h->Size == HWBRK_SIZE_8) le = 2; SetBits(ct.Dr7, 16 + h->iReg*4, 2, st); SetBits(ct.Dr7, 18 + h->iReg*4, 2, le); SetBits(ct.Dr7, h->iReg*2,1,1); } ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; j = SetThreadContext(h->hT,&ct); y = GetLastError(); ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; j = GetThreadContext(h->hT,&ct); y = GetLastError(); j = ResumeThread(h->hT); y = GetLastError(); h->SUCC = true; SetEvent(h->hEv); return 0; } HANDLE SetHardwareBreakpoint(HANDLE hThread, HWBRK_TYPE Type, HWBRK_SIZE Size, void *s) { HWBRK *h = new HWBRK; h->a = s; h->Size = Size; h->Type = Type; h->hT = hThread; if (hThread == GetCurrentThread()) { DWORD pid = GetCurrentThreadId(); h->hT = OpenThread(THREAD_ALL_ACCESS,0,pid); } h->hEv = CreateEvent(0, 0, 0, 0); h->Opr = 0; // Set Break HANDLE hY = CreateThread(0, 0, BreakpointThread, (LPVOID)h, 0, 0); WaitForSingleObject(h->hEv, INFINITE); CloseHandle(h->hEv); h->hEv = 0; if (hThread == GetCurrentThread()) { CloseHandle(h->hT); } h->hT = hThread; if (!h->SUCC) { delete h; return 0; } return (HANDLE)h; } bool RemoveHardwareBreakpoint(HANDLE hBrk) { HWBRK* h = (HWBRK*)hBrk; if (!h) return false; bool C = false; if (h->hT == GetCurrentThread()) { DWORD pid = GetCurrentThreadId(); h->hT = OpenThread(THREAD_ALL_ACCESS,0,pid); C = true; } h->hEv = CreateEvent(0,0,0,0); h->Opr = 1; // Remove Break HANDLE hY = CreateThread(0,0,BreakpointThread,(LPVOID)h,0,0); WaitForSingleObject(h->hEv,INFINITE); CloseHandle(h->hEv); h->hEv = 0; if (C) { CloseHandle(h->hT); } delete h; return true; } diff --git a/src/win/Lgi/Layout.cpp b/src/win/Lgi/Layout.cpp --- a/src/win/Lgi/Layout.cpp +++ b/src/win/Lgi/Layout.cpp @@ -1,218 +1,206 @@ /* ** FILE: GuiViews.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Standard Views ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Css.h" #include "lgi/common/Layout.h" ////////////////////////////////////////////////////////////////////////////// LLayout::LLayout() : LView(0) { _SettingScrollBars = 0; _PourLargest = 0; VScroll = 0; HScroll = 0; #if WINNATIVE SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } LLayout::~LLayout() { DeleteObj(VScroll); DeleteObj(HScroll); } LViewI *LLayout::FindControl(int Id) { if (VScroll && VScroll->GetId() == Id) return VScroll; if (HScroll && HScroll->GetId() == Id) return HScroll; return LView::FindControl(Id); } void LLayout::GetScrollPos(int64 &x, int64 &y) { if (HScroll) - { x = (int)HScroll->Value(); - } else - { x = 0; - } if (VScroll) - { y = (int)VScroll->Value(); - } else - { y = 0; - } } void LLayout::SetScrollPos(int64 x, int64 y) { if (HScroll) - { HScroll->Value(x); - } if (VScroll) - { VScroll->Value(y); - } } bool LLayout::SetScrollBars(bool x, bool y) { bool Status = false; if (!_SettingScrollBars) { _SettingScrollBars = true; if (y) { if (!VScroll) { VScroll = new LScrollBar; if (VScroll) { VScroll->SetVertical(true); VScroll->SetParent(this); VScroll->SetId(IDC_VSCROLL); Status = true; } } } else { if (VScroll) { DeleteObj(VScroll); Status = true; } } if (x) { if (!HScroll) { HScroll = new LScrollBar; if (HScroll) { HScroll->SetVertical(false); HScroll->SetParent(this); HScroll->SetId(IDC_HSCROLL); Status = true; } } } else { if (HScroll) { DeleteObj(HScroll); Status = true; } } _SettingScrollBars = false; } return Status; } bool LLayout::GetPourLargest() { return _PourLargest; } void LLayout::SetPourLargest(bool i) { _PourLargest = i; } LCss::Len &SelectValid(LCss::Len &a, LCss::Len &b, LCss::Len &c) { if (a.IsValid()) return a; if (b.IsValid()) return b; return c; } bool LLayout::Pour(LRegion &r) { if (!_PourLargest) return false; LRect *Largest = FindLargest(r); if (!Largest) return false; LRect p = *Largest; LCss *css = GetCss(); if (css) { LCss::Len margin = css->Margin(); LCss::Len s; LCss::Len zero; LFont *f = GetFont(); s = css->MarginTop(); p.x1 += SelectValid(s, margin, zero).ToPx(r.X(), f); s = css->MarginTop(); p.y1 += SelectValid(s, margin, zero).ToPx(r.Y(), f); s = css->MarginRight(); p.x2 -= SelectValid(s, margin, zero).ToPx(r.X(), f); s = css->MarginBottom(); p.y2 -= SelectValid(s, margin, zero).ToPx(r.Y(), f); if ((s = css->Width()).IsValid()) p.x2 = p.x1 + s.ToPx(r.X(), f) - 1; if ((s = css->Height()).IsValid()) p.y2 = p.y1 + s.ToPx(r.Y(), f) - 1; } if (p.Valid()) { SetPos(p, true); } else { LAssert(0); return false; } return true; } LMessage::Result LLayout::OnEvent(LMessage *Msg) { if (VScroll) VScroll->OnEvent(Msg); if (HScroll) HScroll->OnEvent(Msg); LMessage::Result Status = LView::OnEvent(Msg); if (Msg->Msg() == M_CHANGE && Status == -1 && GetParent()) { Status = GetParent()->OnEvent(Msg); } return Status; } diff --git a/src/win/Lgi/Menu.cpp b/src/win/Lgi/Menu.cpp --- a/src/win/Lgi/Menu.cpp +++ b/src/win/Lgi/Menu.cpp @@ -1,1334 +1,1325 @@ /*hdr ** FILE: GuiMenu.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Gui menu system ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" /////////////////////////////////////////////////////////////////////////////////////////////// #define LGI_OWNER_DRAW_MENUS LColour LGetSysColor(int nIndex) { LColour c; auto i = GetSysColor(nIndex); c.Rgb(GetRValue(i), GetGValue(i), GetBValue(i)); return c; } class LMenuPrivate { public: LColour RootMenuBack; LMenuPrivate() { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && ( Ver[0] > 5 || (Ver[0] == 5 && Ver[1] > 0) ) ) { #ifndef SPI_GETFLATMENU #define SPI_GETFLATMENU 0x1022 #endif BOOL Flat = true; SystemParametersInfo(SPI_GETFLATMENU, 0, &Flat, 0); if (Flat) RootMenuBack = LGetSysColor(COLOR_MENUBAR); else RootMenuBack = LGetSysColor(COLOR_MENU); // LgiTrace("NT: flat=%i, menu=%x\n", Flat, RootMenuBack); } else { RootMenuBack = LGetSysColor(COLOR_MENU); // LgiTrace("9x: menu=%x\n", RootMenuBack); } } }; /////////////////////////////////////////////////////////////////////////////////////////////// LSubMenu::LSubMenu(const char *name, bool Popup) { if (Popup) { Info = CreatePopupMenu(); } else { Info = CreateMenu(); } Menu = 0; Parent = 0; TrackHandle = 0; Window = 0; } LSubMenu::~LSubMenu() { if (TrackHandle) { // At least in Win98, if you call InvalidateRect(...) on a window // multiple times just after you use TracePopupMenu(...) on that // window the invalid region isn't updated. // // This behaviour is not by design, it is a bug. However this // update forces the window to repaint thus working around the // bug in windows. UpdateWindow(TrackHandle); TrackHandle = 0; } if (Info) { DestroyMenu(Info); Info = 0; } if (Parent) { Parent->Child = 0; } LMenuItem *i; while (i = Items[0]) { LAssert(i->Parent == this); DeleteObj(i); } } void LSubMenu::SysMouseClick(LMouse &m) { } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length())); LMenuItem *Item = new LMenuItem(Menu, this, Str, Id, Pos, Shortcut); if (Item) { Item->Enabled(Enabled); Items.Insert(Item, Where); Item->ScanForAccel(); } if (Menu) { Menu->OnChange(); } return Item; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *Item = new LMenuItem; if (Item) { Item->Menu = Menu; Items.Insert(Item, Where); Item->Position = (int) Items.IndexOf(Item); Item->Separator(true); Item->Parent = this; Item->Insert(Item->Position); } if (Menu) { Menu->OnChange(); } return Item; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length())); - LMenuItem *Item = new LMenuItem(Menu, this, Str, -1, Pos); + LMenuItem *Item = new LMenuItem(Menu, this, Str, ItemId_Submenu, Pos); LSubMenu *Sub = new LSubMenu; if (Item && Sub) { Items.Insert(Item, Where); Item->Sub(Sub); } else { DeleteObj(Item); DeleteObj(Sub); } if (Menu) { Menu->OnChange(); } return Sub; } void LSubMenu::Empty() { while (RemoveItem(0)); } bool LSubMenu::RemoveItem(int i) { LMenuItem *Item = Items[i]; if (Item && Item->Remove()) { DeleteObj(Item); return true; } return false; } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item) && Item->Remove()) { return true; } return false; } int LSubMenu::Float(LView *From, int x, int y, int Button) { int Cmd = 0; if (From && Info) { LViewI *Wnd = From; while (Wnd && !Wnd->Handle()) { Wnd = Wnd->GetParent(); } if (Wnd && Wnd->Handle()) { Cmd = TrackPopupMenu( Info, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | ((Button == BtnLeft) ? TPM_LEFTBUTTON : TPM_RIGHTBUTTON), x, y, 0, Wnd->Handle(), NULL); } else { LgiTrace("%s:%i - No handle to track popup menu.\n", __FILE__, __LINE__); } } return Cmd; } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: bool StartUnderline; // Underline the first display string bool HasAccel; // The last display string should be right aligned List Strs; // Draw each alternate display string with underline // except the last in the case of HasAccel==true. LString Shortcut; LMenuItemPrivate() { HasAccel = false; StartUnderline = false; } ~LMenuItemPrivate() { Strs.DeleteObjects(); } void UpdateStrings(LFont *Font, char *n) { // Build up our display strings, Strs.DeleteObjects(); StartUnderline = false; char *Tab = strrchr(n, '\t'); if (Tab) { *Tab = 0; } char *Amp = 0, *a = n; while (a = strchr(a, '&')) { if (a[1] != '&') { Amp = a; break; } a++; } if (Amp) { // Before amp Strs.Insert(new LDisplayString(Font, n, Amp - n )); // Amp'd letter char *e = LSeekUtf8(++Amp, 1); Strs.Insert(new LDisplayString(Font, Amp, e - Amp )); // After Amp if (*e) { Strs.Insert(new LDisplayString(Font, e)); } } else { Strs.Insert(new LDisplayString(Font, n)); } if (HasAccel = (Tab != 0)) { Strs.Insert(new LDisplayString(Font, Tab + 1)); *Tab = '\t'; } else if (HasAccel = (Shortcut.Get() != 0)) { Strs.Insert(new LDisplayString(Font, Shortcut)); } } }; LMenuItem::LMenuItem() { d = new LMenuItemPrivate; Menu = NULL; Parent = NULL; Child = 0; Position = -1; _Icon = -1; ZeroObj(Info); Info.cbSize = sizeof(Info); #ifdef LGI_OWNER_DRAW_MENUS Info.fMask = MIIM_DATA; Info.fType = MFT_OWNERDRAW; #if _MSC_VER >= _MSC_VER_VS2005 Info.dwItemData = (ULONG_PTR)this; #else Info.dwItemData = (DWORD)this; #endif #endif Enabled(true); } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Txt, int id, int Pos, const char *Shortcut) { d = new LMenuItemPrivate; d->Shortcut = Shortcut; Position = Pos; Parent = NULL; Menu = m; Parent = NULL; Child = 0; _Icon = -1; ZeroObj(Info); Info.cbSize = sizeof(Info); #ifdef LGI_OWNER_DRAW_MENUS Info.fMask = MIIM_DATA; Info.fType = MFT_OWNERDRAW; #if _MSC_VER >= _MSC_VER_VS2005 Info.dwItemData = (ULONG_PTR)this; #else Info.dwItemData = (DWORD)this; #endif #endif Id(id); Enabled(true); Name(Txt); Parent = p; Insert(Position); } LMenuItem::~LMenuItem() { if (Parent) { if (Parent->Handle()) { int Index = (int)Parent->Items.IndexOf(this); RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION); } Parent->Items.Delete(this); } DeleteObj(Child); DeleteObj(d); } // the following 3 functions paint the menus according the to // windows standard. but also allow for correct drawing of menuitem // icons. some implementations of windows force the program back // to the 8-bit palette when specifying the icon graphic, thus removing // control over the colours displayed. these functions remove that // limitation and also provide the application the ability to override // the default painting behaviour if desired. void LMenuItem::_Measure(LPoint &Size) { if (Separator()) { Size.x = 8; Size.y = 8; } else { LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool BaseMenu = Parent == Menu; // true if attached to a windows menu // else is a submenu int Ht = Font->GetHeight(); Size.x = BaseMenu ? 0 : 20; for (auto s: d->Strs) { Size.x += s->X(); } if (d->Shortcut || strchr(Name(), '\t')) { Size.x += 32; } if (!BaseMenu) { // leave room for child pointer Size.x += Child ? 8 : 0; } Size.y = BaseMenu ? GetSystemMetrics(SM_CYMENU)+1 : Ht+2; if (Size.y < 16) Size.y = 16; } } void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width) { bool Underline = d->StartUnderline; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; for (int i=0; i < (d->Strs.Length() - (d->HasAccel ? 1 : 0)); i++) { LDisplayString *s = d->Strs[i]; if (!s) break; s->Draw(pDC, x, y); if (Underline) { int UnderX = s->X(); pDC->Colour(Font->Fore()); pDC->Line(x, y+s->Y()-1, x+max(s->X()-2, 1), y+s->Y()-1); } Underline = !Underline; x += s->X(); } if (d->HasAccel) { LDisplayString *s = *d->Strs.rbegin(); if (s) { s->Draw(pDC, Width - s->X() - 8, y); } } } void LMenuItem::_Paint(LSurface *pDC, int Flags) { bool BaseMenu = Parent == Menu; int IconX = BaseMenu ? 5 : 20; bool Selected = TestFlag(Flags, ODS_SELECTED); bool Disabled = TestFlag(Flags, ODS_DISABLED); bool Checked = TestFlag(Flags, ODS_CHECKED); LRect r(0, 0, pDC->X()-1, pDC->Y()-1); auto Text = Name(); if (Separator()) { // paint a separator int Cy = pDC->Y() / 2; pDC->Colour(L_MENU_BACKGROUND); pDC->Rectangle(); pDC->Colour(L_LOW); pDC->Line(0, Cy-1, pDC->X()-1, Cy-1); pDC->Colour(L_LIGHT); pDC->Line(0, Cy, pDC->X()-1, Cy); } else { // paint a text menu item LColour Fore(Selected ? L_FOCUS_SEL_FORE : L_MENU_TEXT); LColour Back(BaseMenu ? Menu->d->RootMenuBack : (Selected ? LColour(L_FOCUS_SEL_BACK) : LColour(L_MENU_BACKGROUND))); int x = IconX; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; int y = (pDC->Y() - Font->GetHeight()) >> 1; // paint the background if (BaseMenu) { // for a menu, sunken on selected LRect rgn = r; if (Selected) { LThinBorder(pDC, rgn, DefaultSunkenEdge); Fore = LColour(L_MENU_TEXT); x++; y++; } // always dialog colour pDC->Colour(Back); pDC->Rectangle(&rgn); } else { // for a sub menu pDC->Colour(Back); pDC->Rectangle(); } // draw the text on top Font->Transparent(true); if (Disabled) { // disabled text if (!Selected) { Font->Colour(L_LIGHT); _PaintText(pDC, x+1, y, r.X()); } // else selected... don't draw the highlight // "grayed" text... Font->Colour(L_LOW); _PaintText(pDC, x, y-1, r.X()-1); } else { // normal coloured text Font->Colour(Fore, Back); _PaintText(pDC, x, y-1, r.X()); } LImageList *ImgLst = (Menu && Menu->GetImageList()) ? Menu->GetImageList() : Parent ? Parent->GetImageList() : 0; // draw icon/check mark if (Checked && IconX > 0) { // it's a check! int x = 4; int y = 6; pDC->Colour(Fore); pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); } else if (ImgLst && _Icon >= 0) { // it's an icon! ImgLst->Draw(pDC, 0, 0, _Icon, Back); } } } bool LMenuItem::ScanForAccel() { LString Accel; if (d->Shortcut) { Accel = d->Shortcut; } else { auto n = LBase::Name(); if (n) { auto Tab = strchr(n, '\t'); if (Tab) Accel = Tab + 1; } } if (Accel) { auto Keys = Accel.SplitDelimit("-+"); if (Keys.Length() > 0) { int Flags = 0; int Vkey = 0; int Chr = 0; bool AccelDirty = false; for (int i=0; iShortcut = LString("+").Join(Keys); LString n = Name(); LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; d->UpdateStrings(Font, n); } if ((Vkey || Chr) && Menu) { Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Id()) ); } } } return false; } LSubMenu *LMenuItem::GetParent() { return Parent; } bool LMenuItem::Remove() { bool Status = false; if (Parent) { int Index = (int) Parent->Items.IndexOf(this); Parent->Items.Delete(this); Status = RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION) != 0; int n=0; for (auto i: Parent->Items) { i->Position = n++; } } return Status; } bool LMenuItem::Update() { bool Status = FALSE; if (Parent && Parent->Handle()) { Status = SetMenuItemInfo(Parent->Handle(), Position, true, &Info) != 0; LAssert(Status); } return Status; } void LMenuItem::Icon(int i) { _Icon = i; } int LMenuItem::Icon() { return _Icon; } void LMenuItem::Id(int i) { Info.wID = i; Info.fMask |= MIIM_ID; Update(); } void LMenuItem::Separator(bool s) { if (s) { Info.fType |= MFT_SEPARATOR; } else { Info.fType &= ~MFT_SEPARATOR; } Info.fMask |= MIIM_TYPE; Update(); } void LMenuItem::Checked(bool c) { if (c) { Info.fState |= MFS_CHECKED; } else { Info.fState &= ~MFS_CHECKED; } Info.fMask |= MIIM_STATE; Update(); } bool LMenuItem::Name(const char *Txt) { bool Status = LBase::Name(Txt); if (Status) { LString n = NewStr(Txt); if (n) { // Set OS menu structure Info.dwTypeData = (LPWSTR)LBase::NameW(); Info.cch = (UINT) StrlenW(LBase::NameW()); Info.fType |= MFT_STRING; Info.fMask |= MIIM_TYPE | MIIM_DATA; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; d->UpdateStrings(Font, n); // Tell the OS Update(); } } return Status; } void LMenuItem::Enabled(bool e) { Info.fState &= ~(MFS_ENABLED|MFS_DISABLED|MFS_GRAYED); if (!e) { Info.fState |= MFS_DISABLED|MFS_GRAYED; } Info.fMask |= MIIM_STATE; Update(); // LgiTrace("%s:%i - LMenuItem::Enabled(%i) %s\n", _FL, e, Name()); } void LMenuItem::Focus(bool f) { if (f) { Info.fState |= MFS_HILITE; } else { Info.fState &= ~MFS_HILITE; } Info.fMask |= MIIM_STATE; Update(); } void LMenuItem::Sub(LSubMenu *s) { Child = s; if (Child) { Info.hSubMenu = Child->Handle(); s->Menu = Menu; s->Parent = this; } else { Info.hSubMenu = 0; } Info.fMask |= MIIM_SUBMENU; Update(); } bool LMenuItem::Insert(int Pos) { bool Status = false; if (Parent && Parent->Handle()) { LAssert(Position >= 0); Position = Pos; Status = InsertMenuItem(Parent->Handle(), Position, true, &Info) != 0; LAssert(Status); } return Status; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return Info.wID; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return (Info.fType & MFT_SEPARATOR) != 0; } bool LMenuItem::Checked() { return (Info.fState & MF_CHECKED) != 0; } bool LMenuItem::Enabled() { return (Info.fState & MFS_DISABLED) == 0; } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return (Info.fState & MFS_HILITE) != 0; } LSubMenu *LMenuItem::Sub() { return Child; } /////////////////////////////////////////////////////////////////////////////////////////////// LMenu::LMenu(const char *AppName) : LSubMenu("", false) { d = new LMenuPrivate; Menu = this; Window = NULL; } LMenu::~LMenu() { Accel.DeleteObjects(); DeleteObj(d); } void LMenu::OnChange() { if (Info && Window) { if (::GetMenu(Window->Handle()) != Info) { SetMenu(Window->Handle(), Info); } } } struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { Window = p; return true; } bool LMenu::Detach() { bool Status = FALSE; if (Window) { HMENU hWndMenu = ::GetMenu(Window->Handle()); if (hWndMenu == Info) { Status = SetMenu(Window->Handle(), NULL) != 0; if (Status) { Window = NULL; } } } return Status; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down() && k.vkey != 17) { for (auto a: Accel) { if (a->Match(k)) { LMenuItem *i = FindItem(a->GetId()); if (!i || i->Enabled()) { int Cmd = a->GetId(); Window->OnCommand(Cmd, 0, 0); return true; } } } } return false; } int LMenu::_OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case WM_MENUCHAR: { short Key = Msg->A() & 0xffff; HMENU View = (HMENU)Msg->B(); MENUITEMINFO Info; ZeroObj(Info); Info.cbSize = sizeof(Info); Info.fMask = MIIM_DATA; if (GetMenuItemInfo(View, 0, true, &Info)) { LMenuItem *Item = (LMenuItem*)Info.dwItemData; if (Item) { LSubMenu *Menu = Item->Parent; if (Menu) { int Index=0; for (auto i: Menu->Items) { auto n = i->Name(); if (n) { char *Amp = strchr(n, '&'); if (Amp && toupper(Amp[1]) == toupper(Key)) { return (MNC_EXECUTE << 16) | Index; } } Index++; } } } } break; } case WM_MEASUREITEM: { LPMEASUREITEMSTRUCT Item = (LPMEASUREITEMSTRUCT)Msg->b; if (Item) { LPoint Size; ((LMenuItem*)Item->itemData)->_Measure(Size); Item->itemWidth = Size.x; Item->itemHeight = Size.y; return true; } break; } case WM_DRAWITEM: { LPDRAWITEMSTRUCT Item = (LPDRAWITEMSTRUCT)Msg->b; if (Item && Item->CtlType == ODT_MENU) { LRect r = Item->rcItem; LScreenDC Dc(Item->hDC, Item->hwndItem); // Get the original origin. We need to do this because when // "animated menus" are on the offset starts at -3,-3 and throws // the menu items off. This is a bug in windows, but watcha // gonna do? int x, y; Dc.GetOrigin(x, y); // Clip and offset so that the menu item draws in client co-ords. Dc.SetOrigin(-r.x1+x, -r.y1+y); Dc.SetSize(r.X(), r.Y()); // Paint the item... ((LMenuItem*)Item->itemData)->_Paint(&Dc, Item->itemState); // Set the origin back the original value. Dc.SetOrigin(x, y); return true; } break; } } return 0; } bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId) { return false; } //////////////////////////////////////////////////////////////////////////// LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; Vkey = vkey; Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { if ( !k.IsChar && ( (Chr != 0 && tolower(k.c16) == tolower(Chr)) || (Vkey != 0 && k.vkey == Vkey) ) ) { if ( (Ctrl() ^ k.Ctrl()) == 0 && (Alt() ^ k.Alt()) == 0 && (Shift() ^ k.Shift()) == 0 && (!TestFlag(Flags, LGI_EF_IS_CHAR) || k.IsChar) && (!TestFlag(Flags, LGI_EF_IS_NOT_CHAR) || !k.IsChar) ) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { - Flags = GWF_VISIBLE; - Id = 0; - ToolButton = 0; - MenuItem = 0; - Accelerator = 0; - TipHelp = 0; - PrevValue = false; } LCommand::~LCommand() { - DeleteArray(Accelerator); - DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; } diff --git a/src/win/Lgi/Printer.cpp b/src/win/Lgi/Printer.cpp --- a/src/win/Lgi/Printer.cpp +++ b/src/win/Lgi/Printer.cpp @@ -1,334 +1,326 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Base64.h" #include "lgi/common/Printer.h" //////////////////////////////////////////////////////////////////// class LPrinterPrivate { public: PRINTDLG Info; LString Err; bool NeedsDC; LPrinterPrivate() { NeedsDC = false; ZeroObj(Info); Info.lStructSize = sizeof(Info); } }; ////////////////////////////////////////////////////////////////////// LPrinter::LPrinter() { d = new LPrinterPrivate; } LPrinter::~LPrinter() { DeleteObj(d); } LString LPrinter::GetErrorMsg() { return d->Err; } -int LPrinter::Print(LPrintEvents *Events, const char *PrintJobName, int Pages, LView *Parent) +#define PrintStatus(val) \ + { if (callback) callback(val); return; } + +void LPrinter::Print(LPrintEvents *Events, std::function callback, const char *PrintJobName, int Pages, LView *Parent) { if (!Events) - return false; + PrintStatus(LPrintEvents::OnBeginPrintError); if ( Parent && ( !d->Info.hDevMode || !d->Info.hDevNames ) ) { d->NeedsDC = true; bool r = Browse(Parent); d->NeedsDC = false; if (!r) - return false; + PrintStatus(LPrintEvents::OnBeginPrintError); } else { d->Info.hwndOwner = (Parent) ? Parent->Handle() : 0; d->Info.Flags = PD_RETURNDC; d->Info.hDC = 0; - /* - if (Pages >= 0) - { - d->Info.nMinPage = 1; - d->Info.nMaxPage = Pages; - } - */ if (!PrintDlg(&d->Info)) - { - return false; - } + PrintStatus(LPrintEvents::OnBeginPrintError); } if (!d->Info.hDC) - { - return false; - } + PrintStatus(LPrintEvents::OnBeginPrintError); LString PrinterName; if (d->Info.hDevNames) { DEVNAMES *Name = (DEVNAMES*)GlobalLock(d->Info.hDevNames); if (Name) { // const char *Driver = (const char *)Name + Name->wDriverOffset; const char *Device = (const char *)Name + Name->wDeviceOffset; // const char *Output = (const char *)Name + Name->wOutputOffset; // const char *Default = (const char *)Name + Name->wDefault; PrinterName = Device; GlobalUnlock(Name); } } LPrintDC dc(d->Info.hDC, PrintJobName, PrinterName); if (!dc.Handle()) { d->Err.Printf("%s:%i - StartDoc failed.\n", _FL); - return false; + PrintStatus(LPrintEvents::OnBeginPrintError); } - auto JobPages = Events->OnBeginPrint(&dc); - if (JobPages <= 0) - return JobPages; + Events->OnBeginPrint(&dc, [&](auto JobPages) + { + if (JobPages <= LPrintEvents::OnBeginPrintCancel) + PrintStatus(JobPages); - bool Status = false; - DOCINFO Info; - LAutoWString DocName(Utf8ToWide(PrintJobName ? PrintJobName : "Lgi Print Job")); + bool Status = false; + DOCINFO Info; + LAutoWString DocName(Utf8ToWide(PrintJobName ? PrintJobName : "Lgi Print Job")); - ZeroObj(Info); - Info.cbSize = sizeof(DOCINFO); - Info.lpszDocName = DocName; + ZeroObj(Info); + Info.cbSize = sizeof(DOCINFO); + Info.lpszDocName = DocName; - if (Pages > 0) - JobPages = min(JobPages, Pages); + if (Pages > 0) + JobPages = min(JobPages, Pages); - auto PageRanges = Events->GetPageRanges(); - for (int i=0; iInRanges(i + 1)) + auto PageRanges = Events->GetPageRanges(); + for (int i=0; i 0) + if (!PageRanges || PageRanges->InRanges(i + 1)) { - Status |= Events->OnPrintPage(&dc, i); - EndPage(dc.Handle()); - } - else - { - d->Err.Printf("%s:%i - StartPage failed.", _FL); - Status = false; - break; + if (StartPage(dc.Handle()) > 0) + { + Status |= Events->OnPrintPage(&dc, i); + EndPage(dc.Handle()); + } + else + { + d->Err.Printf("%s:%i - StartPage failed.", _FL); + JobPages = LPrintEvents::OnBeginPrintError; + break; + } } } - } - LString OutputFile = dc.GetOutputFileName(); - if (LFileExists(OutputFile)) - { - LBrowseToFile(OutputFile); - } - - return Status; + LString OutputFile = dc.GetOutputFileName(); + if (LFileExists(OutputFile)) + LBrowseToFile(OutputFile); + + PrintStatus(JobPages); + }); } bool LPrinter::Browse(LView *Parent) { d->Info.hwndOwner = (Parent) ? Parent->Handle() : 0; d->Info.Flags = PD_PRINTSETUP | PD_PAGENUMS; if (d->NeedsDC) d->Info.Flags |= PD_RETURNDC; /* if (d->Pages > 1) { d->Info.nMinPage = 1; d->Info.nMaxPage = d->Pages; } */ return PrintDlg(&d->Info) != 0; } /* bool GPrinter::GetPageRange(LArray &p) { if (d->Info.Flags & PD_PAGENUMS) { p[0] = d->Info.nFromPage; p[1] = d->Info.nToPage; return true; } return false; } */ #define MAGIC_PRINTDLG 0xAAFF0100 #define MAGIC_DEVMODE 0xAAFF0101 #define MAGIC_DEVNAMES 0xAAFF0102 bool LPrinter::Serialize(LString &Str, bool Write) { int Size = sizeof(d->Info); if (Write) { GMem DevMode(d->Info.hDevMode); GMem DevNames(d->Info.hDevNames); DEVMODE *Mode = (DEVMODE*) DevMode.Lock(); DEVNAMES *Names = (DEVNAMES*) DevNames.Lock(); LMemQueue Temp; int32 m; // Dlg m = MAGIC_PRINTDLG; Temp.Write(&m, sizeof(m)); m = sizeof(d->Info); Temp.Write(&m, sizeof(m)); Temp.Write((uchar*)&d->Info, m); // Mode if (Mode) { m = MAGIC_DEVMODE; Temp.Write(&m, sizeof(m)); m = (int32) DevMode.GetSize(); Temp.Write(&m, sizeof(m)); Temp.Write((uchar*) Mode, m); } // Names if (Names) { m = MAGIC_DEVNAMES; Temp.Write(&m, sizeof(m)); m = (int32) DevNames.GetSize(); Temp.Write(&m, sizeof(m)); Temp.Write((uchar*) Names, m); } DevMode.Detach(); DevNames.Detach(); // Convert to base64 auto BinLen = Temp.GetSize(); uchar *Bin = new uchar[BinLen]; if (Bin && Temp.Read(Bin, BinLen)) { auto Base64Len = BufferLen_BinTo64(BinLen); char *Base64 = new char[Base64Len+1]; if (Base64) { memset(Base64, 0, Base64Len+1); ConvertBinaryToBase64(Base64, Base64Len, Bin, BinLen); Str = Base64; } return true; } } else { bool Status = false; ZeroObj(d->Info); d->Info.lStructSize = Size; if (Str) { // Convert to binary LMemQueue Temp; auto Base64Len = Str.Length(); auto BinaryLen = BufferLen_64ToBin(Base64Len); uchar *Binary = new uchar[BinaryLen]; if (Binary) { auto Len = ConvertBase64ToBinary(Binary, BinaryLen, Str, Base64Len); Temp.Write(Binary, Len); bool Done = false; do { int Type = 0; int Size = 0; if (Temp.Read(&Type, sizeof(Type)) && Temp.Read(&Size, sizeof(Size))) { switch (Type) { case MAGIC_PRINTDLG: { if (Size == sizeof(d->Info)) { Temp.Read((uchar*) &d->Info, Size); d->Info.hDevMode = 0; d->Info.hDevNames = 0; Status = true; } else { // Grrrr: Error Done = true; } break; } case MAGIC_DEVMODE: { GMem DevMode(Size); DEVMODE *Mode = (DEVMODE*) DevMode.Lock(); if (Mode) { Temp.Read((uchar*)Mode, Size); d->Info.hDevMode = DevMode.Handle(); DevMode.Detach(); } break; } case MAGIC_DEVNAMES: { GMem DevNames(Size); DEVNAMES *Names = (DEVNAMES*) DevNames.Lock(); if (Names) { Temp.Read((uchar*)Names, Size); d->Info.hDevNames = DevNames.Handle(); DevNames.Detach(); } break; } default: { // Eof Done = true; break; } } } else { Done = true; } } while (!Done); DeleteArray(Binary); } } return Status; } return false; } \ No newline at end of file diff --git a/src/win/Lgi/View.cpp b/src/win/Lgi/View.cpp --- a/src/win/Lgi/View.cpp +++ b/src/win/Lgi/View.cpp @@ -1,2158 +1,2159 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Win32 LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Base64.h" #include "lgi/common/Com.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/DropFiles.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Css.h" #include "lgi/common/Edit.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #include "lgi/common/Thread.h" #include "ViewPriv.h" #define DEBUG_MOUSE_CLICKS 0 #define DEBUG_OVER 0 #define OLD_WM_CHAR_MODE 1 //////////////////////////////////////////////////////////////////////////////////////////////////// bool In_SetWindowPos = false; HWND LViewPrivate::hPrevCapture = 0; LViewPrivate::LViewPrivate(LView *view) : View(view) { WndStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN; WndExStyle = 0; WndDlgCode = 0; IsThemed = LResources::DefaultColours; } LViewPrivate::~LViewPrivate() { if (hTheme) { CloseThemeData(hTheme); hTheme = NULL; } if (FontOwnType == GV_FontOwned) { DeleteObj(Font); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Helper Stuff #include "zmouse.h" int MouseRollMsg = 0; #ifdef __GNUC__ #define MSH_WHEELMODULE_CLASS "MouseZ" #define MSH_WHEELMODULE_TITLE "Magellan MSWHEEL" #define MSH_SCROLL_LINES "MSH_SCROLL_LINES_MSG" #endif int _lgi_mouse_wheel_lines() { UINT nScrollLines; if (SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, (PVOID) &nScrollLines, 0)) return nScrollLines; return 3; } #define SetKeyFlag(v, k, f) if (GetKeyState(k)&0xFF00) { v |= f; } int _lgi_get_key_flags() { int Flags = 0; if (LGetOs() == LGI_OS_WIN9X) { SetKeyFlag(Flags, VK_MENU, LGI_EF_ALT); SetKeyFlag(Flags, VK_SHIFT, LGI_EF_SHIFT); SetKeyFlag(Flags, VK_CONTROL, LGI_EF_CTRL); } else // is NT/2K/XP { SetKeyFlag(Flags, VK_LMENU, LGI_EF_LALT); SetKeyFlag(Flags, VK_RMENU, LGI_EF_RALT); SetKeyFlag(Flags, VK_LSHIFT, LGI_EF_LSHIFT); SetKeyFlag(Flags, VK_RSHIFT, LGI_EF_RSHIFT); SetKeyFlag(Flags, VK_LCONTROL, LGI_EF_LCTRL); SetKeyFlag(Flags, VK_RCONTROL, LGI_EF_RCTRL); } if (GetKeyState(VK_CAPITAL)) SetFlag(Flags, LGI_EF_CAPS_LOCK); return Flags; } //////////////////////////////////////////////////////////////////////////////////////////////////// int GetInputACP() { char16 Str[16]; LCID Lcid = (NativeInt)GetKeyboardLayout(GetCurrentThreadId()) & 0xffff; GetLocaleInfo(Lcid, LOCALE_IDEFAULTANSICODEPAGE, Str, sizeof(Str)); return _wtoi(Str); } LKey::LKey(int v, uint32_t flags) { const char *Cp = 0; vkey = v; Data = flags; c16 = 0; #if OLD_WM_CHAR_MODE c16 = vkey; #else typedef int (WINAPI *p_ToUnicode)(UINT, UINT, PBYTE, LPWSTR, int, UINT); static bool First = true; static p_ToUnicode ToUnicode = 0; if (First) { ToUnicode = (p_ToUnicode) GetProcAddress(LoadLibrary("User32.dll"), "ToUnicode"); First = false; } if (ToUnicode) { BYTE state[256]; GetKeyboardState(state); char16 w[4]; int r = ToUnicode(vkey, flags & 0x7f, state, w, CountOf(w), 0); if (r == 1) { c16 = w[0]; } } #endif } //////////////////////////////////////////////////////////////////////////////////////////////////// template bool CastHwnd(T *&Ptr, HWND hWnd) { #if _MSC_VER >= _MSC_VER_VS2005 LONG_PTR user = GetWindowLongPtr(hWnd, GWLP_USERDATA); #else LONG user = GetWindowLong(hWnd, GWL_USERDATA); #endif LONG magic = GetWindowLong(hWnd, GWL_LGI_MAGIC); if (magic != LGI_GViewMagic) { TCHAR ClsName[256] = {0}; int Ch = GetClassName(hWnd, ClsName, CountOf(ClsName)); LString Cls = ClsName; // LgiTrace("%s:%i - Error: hWnd=%p/%s, GWL_LGI_MAGIC=%i\n", _FL, hWnd, Cls.Get(), magic); return false; } Ptr = dynamic_cast((LViewI*)user); return Ptr != NULL; } bool SetLgiMagic(HWND hWnd) { SetLastError(0); LONG res = SetWindowLong(hWnd, GWL_LGI_MAGIC, LGI_GViewMagic); bool Status = res != 0; if (!Status) { DWORD err = GetLastError(); Status = err == 0; } LONG v = GetWindowLong(hWnd, GWL_LGI_MAGIC); // LgiTrace("set LGI_GViewMagic for %p, %i, %i\n", hWnd, Status, v); return Status; } LRESULT CALLBACK LWindowsClass::Redir(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (m == WM_NCCREATE) { LPCREATESTRUCT Info = (LPCREATESTRUCT) b; LViewI *ViewI = (LViewI*) Info->lpCreateParams; if (ViewI) { LView *View = ViewI->GetGView(); if (View) View->_View = hWnd; #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)ViewI); #else SetWindowLong(hWnd, GWL_USERDATA, (LONG)ViewI); #endif SetLgiMagic(hWnd); } } LViewI *Wnd = (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif if (Wnd) { LMessage Msg(m, a, b); Msg.hWnd = hWnd; return Wnd->OnEvent(&Msg); } return DefWindowProcW(hWnd, m, a, b); } LRESULT CALLBACK LWindowsClass::SubClassRedir(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (m == WM_NCCREATE) { LPCREATESTRUCT Info = (LPCREATESTRUCT) b; LViewI *ViewI = 0; if (Info->lpCreateParams) { if (ViewI = (LViewI*) Info->lpCreateParams) { LView *View = ViewI->GetGView(); if (View) View->_View = hWnd; } } #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) ViewI); #else SetWindowLong(hWnd, GWL_USERDATA, (LONG) ViewI); #endif SetLgiMagic(hWnd); } LViewI *Wnd = (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif if (Wnd) { LMessage Msg(m, a, b); Msg.hWnd = hWnd; LMessage::Result Status = Wnd->OnEvent(&Msg); return Status; } return DefWindowProcW(hWnd, m, a, b); } LWindowsClass::LWindowsClass(const char *name) { Name(name); ZeroObj(Class); Class.lpfnWndProc = (WNDPROC) Redir; Class.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; Class.cbWndExtra = GWL_EXTRA_BYTES; Class.cbSize = sizeof(Class); ParentProc = 0; } LWindowsClass::~LWindowsClass() { UnregisterClassW(NameW(), LProcessInst()); Class.lpszClassName = NULL; } LWindowsClass *LWindowsClass::Create(const char *ClassName) { if (!LAppInst) return NULL; LApp::ClassContainer *Classes = LAppInst->GetClasses(); if (!Classes) return NULL; LWindowsClass *c = Classes->Find(ClassName); if (!c) { c = new LWindowsClass(ClassName); if (c) Classes->Add(ClassName, c); } return c; } bool LWindowsClass::IsSystem(const char *Cls) { if (!_stricmp(Cls, WC_BUTTONA) || !_stricmp(Cls, WC_COMBOBOXA) || !_stricmp(Cls, WC_STATICA)|| !_stricmp(Cls, WC_LISTBOXA)|| !_stricmp(Cls, WC_SCROLLBARA)|| !_stricmp(Cls, WC_HEADERA)|| !_stricmp(Cls, WC_LISTVIEWA)|| !_stricmp(Cls, WC_TREEVIEWA)|| !_stricmp(Cls, WC_COMBOBOXEXA)|| !_stricmp(Cls, WC_TABCONTROLA)|| !_stricmp(Cls, WC_IPADDRESSA)|| !_stricmp(Cls, WC_EDITA)) { return true; } return false; } bool LWindowsClass::Register() { bool Status = false; if (IsSystem(Name())) { ZeroObj(Class); Class.cbSize = sizeof(Class); Status = GetClassInfoExW(LProcessInst(), NameW(), &Class) != 0; LAssert(Status); } else // if (!Class.lpszClassName) { Class.hInstance = LProcessInst(); if (!Class.lpszClassName) Class.lpszClassName = NameW(); Status = RegisterClassExW(&Class) != 0; if (!Status) { auto err = GetLastError(); if (err == 1410) Status = true; else LAssert(Status); } } return Status; } bool LWindowsClass::SubClass(char *Parent) { bool Status = false; if (!Class.lpszClassName) { HBRUSH hBr = Class.hbrBackground; LAutoWString p(Utf8ToWide(Parent)); if (p) { if (GetClassInfoExW(LProcessInst(), p, &Class)) { ParentProc = Class.lpfnWndProc; if (hBr) { Class.hbrBackground = hBr; } Class.cbWndExtra = max(Class.cbWndExtra, GWL_EXTRA_BYTES); Class.hInstance = LProcessInst(); Class.lpfnWndProc = (WNDPROC) SubClassRedir; Class.lpszClassName = NameW(); Status = RegisterClassExW(&Class) != 0; LAssert(Status); } } } else Status = true; return Status; } LRESULT CALLBACK LWindowsClass::CallParent(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (!ParentProc) return 0; if (IsWindowUnicode(hWnd)) { return CallWindowProcW(ParentProc, hWnd, m, a, b); } else { return CallWindowProcA(ParentProc, hWnd, m, a, b); } } ////////////////////////////////////////////////////////////////////////////// LViewI *LWindowFromHandle(HWND hWnd) { if (hWnd) { SetLastError(0); int32 m = GetWindowLong(hWnd, GWL_LGI_MAGIC); #if 0 //def _DEBUG DWORD err = GetLastError(); if (err == 1413) { TCHAR name[256]; if (GetClassName(hWnd, name, sizeof(name))) { WNDCLASSEX cls; ZeroObj(cls); cls.cbSize = sizeof(WNDCLASSEX); if (GetClassInfoEx(LAppInst->GetInstance(), name, &cls)) { if (cls.cbWndExtra >= 8) { LAssert(!"Really?"); } } } } #endif if (m == LGI_GViewMagic) { return (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif } } return 0; } ////////////////////////////////////////////////////////////////////////////// const char *LView::GetClass() { return "LView"; } void LView::_Delete() { if (_View && d->DropTarget) { RevokeDragDrop(_View); } #ifdef _DEBUG // Sanity check.. // LArray HasView; for (auto c: Children) { - // LAssert(!HasView.HasItem(c)); - // HasView.Add(c); - LAssert(((LViewI*)c->GetParent()) == this || c->GetParent() == 0); + auto par = c->GetParent(); + bool ok = ((LViewI*)par) == this || par == NULL; + if (!ok) + LAssert(!"heirachy error"); } #endif // Delete myself out of my parent's list if (d->Parent) { d->Parent->OnChildrenChanged(this, false); d->Parent->DelView(this); d->Parent = 0; d->ParentI = 0; } // Delete all children LViewI *c; while (c = Children[0]) { // If it has no parent, remove the pointer from the child list, // Because the child isn't going to do it... if (c->GetParent() == 0) Children.Delete(c); // Delete the child view DeleteObj(c); } // Delete the OS representation of myself if (_View && IsWindow(_View)) { WndFlags |= GWF_DESTRUCTOR; BOOL Status = DestroyWindow(_View); LAssert(Status != 0); } // NULL my handles and flags _View = 0; WndFlags = 0; // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, this, GetClass()); #endif _Capturing = 0; } LWindow *Wnd = GetWindow(); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); // this should only exist in an ex-LWindow, due to the way // C++ deletes objects it needs to be here. DeleteObj(_Lock); } void LView::Quit(bool DontDelete) { if (_View) { if (!DontDelete) { WndFlags |= GWF_QUIT_WND; } DestroyWindow(_View); } } uint32_t LView::GetDlgCode() { return d->WndDlgCode; } void LView::SetDlgCode(uint32_t i) { d->WndDlgCode = i; } uint32_t LView::GetStyle() { return d->WndStyle; } void LView::SetStyle(uint32_t i) { d->WndStyle = i; } uint32_t LView::GetExStyle() { return d->WndExStyle; } void LView::SetExStyle(uint32_t i) { d->WndExStyle = i; } const char *LView::GetClassW32() { return d->WndClass; } void LView::SetClassW32(const char *c) { d->WndClass = c; } LWindowsClass *LView::CreateClassW32(const char *Class, HICON Icon, int AddStyles) { if (Class) { SetClassW32(Class); } if (GetClassW32()) { LWindowsClass *c = LWindowsClass::Create(GetClassW32()); if (c) { if (Icon) { c->Class.hIcon = Icon; } if (AddStyles) { c->Class.style |= AddStyles; } c->Register(); return c; } } return 0; } bool LView::IsAttached() { return _View && IsWindow(_View); } bool LView::Attach(LViewI *p) { bool Status = false; SetParent(p); LView *Parent = d->GetParent(); if (Parent && !_Window) _Window = Parent->_Window; const char *ClsName = GetClassW32(); if (!ClsName) ClsName = GetClass(); if (ClsName) { // Real window with HWND bool Enab = Enabled(); // Check the class is created bool IsSystemClass = LWindowsClass::IsSystem(ClsName); LWindowsClass *Cls = LWindowsClass::Create(ClsName); if (Cls) { auto r = Cls->Register(); if (!r) { LAssert(0); } } else if (!IsSystemClass) return false; LAssert(!Parent || Parent->Handle() != 0); DWORD Style = GetStyle(); DWORD ExStyle = GetExStyle() & ~WS_EX_CONTROLPARENT; if (!TestFlag(WndFlags, GWF_SYS_BORDER)) ExStyle &= ~(WS_EX_CLIENTEDGE | WS_EX_WINDOWEDGE); auto Text = LBase::NameW(); LAutoWString WCls(Utf8ToWide(ClsName)); _View = CreateWindowExW(ExStyle, WCls, Text, Style, Pos.x1, Pos.y1, Pos.X(), Pos.Y(), Parent ? Parent->Handle() : 0, NULL, LProcessInst(), (LViewI*) this); #ifdef _DEBUG if (!_View) { DWORD e = GetLastError(); LgiTrace("%s:%i - CreateWindowExW failed with 0x%x\n", _FL, e); LAssert(!"CreateWindowEx failed"); } #endif if (_View) { Status = (_View != NULL); if (d->Font) SendMessage(_View, WM_SETFONT, (WPARAM) d->Font->Handle(), 0); if (d->DropTarget) RegisterDragDrop(_View, d->DropTarget); if (TestFlag(WndFlags, GWF_FOCUS)) SetFocus(_View); if (d->WantsPulse > 0) { SetPulse(d->WantsPulse); d->WantsPulse = -1; } } OnAttach(); } else { // Virtual window (no HWND) Status = true; } if (Status && d->Parent) { if (!d->Parent->HasView(this)) { d->Parent->AddView(this); } d->Parent->OnChildrenChanged(this, true); } return Status; } bool LView::Detach() { bool Status = false; if (_Window) { LWindow *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } if (d->Parent) { d->Parent->DelView(this); d->Parent->OnChildrenChanged(this, false); d->Parent = 0; d->ParentI = 0; Status = true; WndFlags &= ~GWF_FOCUS; if (_Capturing == this) { if (_View) ReleaseCapture(); #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, this, GetClass()); #endif _Capturing = 0; } if (_View) { WndFlags &= ~GWF_QUIT_WND; BOOL Status = DestroyWindow(_View); DWORD Err = GetLastError(); LAssert(Status != 0); } } return Status; } LRect &LView::GetClient(bool InClientSpace) { static LRect Client; if (_View) { RECT rc; GetClientRect(_View, &rc); Client = rc; } else { Client.Set(0, 0, Pos.X()-1, Pos.Y()-1); if (dynamic_cast(this) || dynamic_cast(this)) { Client.x1 += GetSystemMetrics(SM_CXFRAME); Client.x2 -= GetSystemMetrics(SM_CXFRAME); Client.y1 += GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION); Client.y2 -= GetSystemMetrics(SM_CYFRAME); } else if (Sunken() || Raised()) { Client.Inset(_BorderSize, _BorderSize); } } if (InClientSpace) Client.Offset(-Client.x1, -Client.y1); return Client; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } #ifndef GCL_HCURSOR #define GCL_HCURSOR -12 #endif bool LgiToWindowsCursor(OsView Hnd, LCursor Cursor) { char16 *Set = 0; switch (Cursor) { case LCUR_UpArrow: Set = IDC_UPARROW; break; case LCUR_Cross: Set = IDC_CROSS; break; case LCUR_Wait: Set = IDC_WAIT; break; case LCUR_Ibeam: Set = IDC_IBEAM; break; case LCUR_SizeVer: Set = IDC_SIZENS; break; case LCUR_SizeHor: Set = IDC_SIZEWE; break; case LCUR_SizeBDiag: Set = IDC_SIZENESW; break; case LCUR_SizeFDiag: Set = IDC_SIZENWSE; break; case LCUR_SizeAll: Set = IDC_SIZEALL; break; case LCUR_PointingHand: { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver[0] >= 5) { #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif Set = IDC_HAND; } // else not supported break; } case LCUR_Forbidden: Set = IDC_NO; break; // Not impl case LCUR_SplitV: break; case LCUR_SplitH: break; case LCUR_Blank: break; } HCURSOR cur = LoadCursor(0, Set ? Set : IDC_ARROW); SetCursor(cur); if (Hnd) SetWindowLongPtr(Hnd, GCL_HCURSOR, (LONG_PTR)cur); return true; } bool LView::PointToScreen(LPoint &p) { POINT pt = {p.x, p.y}; LViewI *t = this; while ( t && t->GetParent() && !t->Handle()) { pt.x += t->GetPos().x1; pt.y += t->GetPos().y1; t = t->GetParent(); } ClientToScreen(t->Handle(), &pt); p.x = pt.x; p.y = pt.y; return true; } bool LView::PointToView(LPoint &p) { POINT pt = {p.x, p.y}; LViewI *t = this; while ( t && t->GetParent() && !t->Handle()) { pt.x -= t->GetPos().x1; pt.y -= t->GetPos().y1; t = t->GetParent(); } ScreenToClient(t->Handle(), &pt); p.x = pt.x; p.y = pt.y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { // position POINT p; GetCursorPos(&p); if (!ScreenCoords) { ScreenToClient(_View, &p); } m.x = p.x; m.y = p.y; m.Target = this; // buttons m.Flags = ((GetAsyncKeyState(VK_LBUTTON)&0x8000) ? LGI_EF_LEFT : 0) | ((GetAsyncKeyState(VK_MBUTTON)&0x8000) ? LGI_EF_MIDDLE : 0) | ((GetAsyncKeyState(VK_RBUTTON)&0x8000) ? LGI_EF_RIGHT : 0) | ((GetAsyncKeyState(VK_CONTROL)&0x8000) ? LGI_EF_CTRL : 0) | ((GetAsyncKeyState(VK_MENU) &0x8000) ? LGI_EF_ALT : 0) | ((GetAsyncKeyState(VK_LWIN) &0x8000) ? LGI_EF_SYSTEM : 0) | ((GetAsyncKeyState(VK_RWIN) &0x8000) ? LGI_EF_SYSTEM : 0) | ((GetAsyncKeyState(VK_SHIFT) &0x8000) ? LGI_EF_SHIFT : 0); if (m.Flags & (LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT)) { m.Flags |= LGI_EF_DOWN; } return true; } bool LView::SetPos(LRect &p, bool Repaint) { bool Status = true; LRect OldPos = Pos; if (Pos != p) { Pos = p; if (_View) { HWND hOld = GetFocus(); bool WasVis = IsWindowVisible(_View) != 0; In_SetWindowPos = true; Status = SetWindowPos( _View, NULL, Pos.x1, Pos.y1, Pos.X(), Pos.Y(), // ((Repaint) ? 0 : SWP_NOREDRAW) | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER) != 0; In_SetWindowPos = false; } else if (GetParent()) { OnPosChange(); } if (Repaint) { Invalidate(); } } return Status; } bool LView::Invalidate(LRect *r, bool Repaint, bool Frame) { if (_View) { bool Status = false; if (Frame) { RedrawWindow( _View, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN | ((Repaint) ? RDW_UPDATENOW : 0)); } else { if (r) { Status = InvalidateRect(_View, &((RECT)*r), false) != 0; } else { RECT c = GetClient(); Status = InvalidateRect(_View, &c, false) != 0; } } if (Repaint) { UpdateWindow(_View); } return Status; } else { LRect Up; LViewI *p = this; if (r) { Up = *r; } else { Up.Set(0, 0, Pos.X()-1, Pos.Y()-1); } if (dynamic_cast(this)) return true; while (p && !p->Handle()) { LViewI *Par = p->GetParent(); LView *VPar = Par?Par->GetGView():0; LRect w = p->GetPos(); LRect c = p->GetClient(false); if (Frame && p == this) Up.Offset(w.x1, w.y1); else Up.Offset(w.x1 + c.x1, w.y1 + c.y1); p = Par; } if (p && p->Handle()) { return p->Invalidate(&Up, Repaint); } } return false; } void CALLBACK LView::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, uint32_t dwTime) { LView *View = (LView*) idEvent; if (View) { View->OnPulse(); } } void LView::SetPulse(int Length) { if (_View) { if (Length > 0) { d->TimerId = SetTimer(_View, (UINT_PTR) this, Length, (TIMERPROC) TimerProc); } else { KillTimer(_View, d->TimerId); d->TimerId = 0; } } else { d->WantsPulse = Length; } } static int ConsumeTabKey = 0; bool SysOnKey(LView *w, LMessage *m) { if (m->a == VK_TAB && (m->m == WM_KEYDOWN || m->m == WM_SYSKEYDOWN) ) { if (!TestFlag(w->d->WndDlgCode, DLGC_WANTTAB) && !TestFlag(w->d->WndDlgCode, DLGC_WANTALLKEYS)) { // push the focus to the next control bool Shifted = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; LViewI *Wnd = GetNextTabStop(w, Shifted); if (Wnd) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\\n", _FL, Wnd->Handle()); } ConsumeTabKey = 2; ::SetFocus(Wnd->Handle()); return true; } } } return false; } #ifdef _MSC_VER #include "vsstyle.h" void LView::DrawThemeBorder(LSurface *pDC, LRect &r) { if (!d->hTheme) d->hTheme = OpenThemeData(_View, VSCLASS_EDIT); if (d->hTheme) { RECT rc = r; int StateId; if (!Enabled()) StateId = EPSN_DISABLED; else if (GetFocus() == _View) StateId = EPSN_FOCUSED; else StateId = EPSN_NORMAL; // LgiTrace("ThemeDraw %s: %i\n", GetClass(), StateId); RECT clip[4]; clip[0] = LRect(r.x1, r.y1, r.x1 + 1, r.y2); // left clip[1] = LRect(r.x1 + 2, r.y1, r.x2 - 2, r.y1 + 1); // top clip[2] = LRect(r.x2 - 1, r.y1, r.x2, r.y2); // right clip[3] = LRect(r.x1 + 2, r.y2 - 1, r.x2 - 2, r.y2); // bottom LColour cols[4] = { LColour(255, 0, 0), LColour(0, 255, 0), LColour(0, 0, 255), LColour(255, 255, 0) }; for (int i=0; iColour(cols[i]); pDC->Rectangle(&tmp); #else DrawThemeBackground(d->hTheme, pDC->Handle(), EP_EDITBORDER_NOSCROLL, StateId, &rc, &clip[i]); #endif } pDC->Colour(L_MED); pDC->Set(r.x1, r.y1); pDC->Set(r.x2, r.y1); pDC->Set(r.x1, r.y2); pDC->Set(r.x2, r.y2); r.Inset(2, 2); } else { LWideBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); d->IsThemed = false; } } #else void LView::DrawThemeBorder(LSurface *pDC, LRect &r) { LWideBorder(pDC, r, DefaultSunkenEdge); } #endif bool IsKeyChar(LKey &k, int vk) { if (k.Ctrl() || k.Alt() || k.System()) return false; switch (vk) { case VK_BACK: case VK_TAB: case VK_RETURN: case VK_SPACE: case 0xba: // ; case 0xbb: // = case 0xbc: // , case 0xbd: // - case 0xbe: // . case 0xbf: // / case 0xc0: // ` case 0xdb: // [ case 0xdc: // | case 0xdd: // ] case 0xde: // ' return true; } if (vk >= VK_NUMPAD0 && vk <= VK_DIVIDE) return true; if (vk >= '0' && vk <= '9') return true; if (vk >= 'A' && vk <= 'Z') return true; return false; } #define KEY_FLAGS (~(MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) LMessage::Result LView::OnEvent(LMessage *Msg) { int Status = 0; if (Msg->Msg() == MouseRollMsg) { HWND hFocus = GetFocus(); if (_View) { int Flags = ((GetKeyState(VK_SHIFT)&0xF000) ? VK_SHIFT : 0) | ((GetKeyState(VK_CONTROL)&0xF000) ? VK_CONTROL : 0); PostMessage(hFocus, WM_MOUSEWHEEL, MAKELONG(Flags, (short)Msg->a), Msg->b); } return 0; } if (_View) { switch (Msg->m) { #if 1 case WM_CTLCOLORBTN: case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)Msg->A(); HWND hwnd = (HWND)Msg->B(); LViewI *v = FindControl(hwnd); LView *gv = v ? v->GetGView() : NULL; if (gv) { int Depth = dynamic_cast(gv) ? 1 : 10; LColour Fore = gv->StyleColour(LCss::PropColor, LColour(), Depth); LColour Back = gv->StyleColour(LCss::PropBackgroundColor, LColour(), Depth); if (Fore.IsValid()) { COLORREF c = RGB(Fore.r(), Fore.g(), Fore.b()); SetTextColor(hdc, c); } if (Back.IsValid()) { COLORREF c = RGB(Back.r(), Back.g(), Back.b()); SetBkColor(hdc, c); SetDCBrushColor(hdc, c); } if (Fore.IsValid() || Back.IsValid()) { #if !defined(DC_BRUSH) #define DC_BRUSH 18 #endif return (LRESULT) GetStockObject(DC_BRUSH); } } goto ReturnDefaultProc; return 0; } #endif case 5700: { // I forget what this is for... break; } case WM_ERASEBKGND: { return 1; } case WM_GETFONT: { LFont *f = GetFont(); if (!f || f == LSysFont) return (LMessage::Result) LSysFont->Handle(); return (LMessage::Result) f->Handle(); break; } case WM_MENUCHAR: case WM_MEASUREITEM: { return LMenu::_OnEvent(Msg); break; } case WM_DRAWITEM: { DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT*)Msg->B(); if (di) { if (di->CtlType == ODT_MENU) { return LMenu::_OnEvent(Msg); } /* else if (di->CtlType == ODT_BUTTON) { LView *b; if (CastHwnd(b, di->hwndItem) && b->GetCss()) { LScreenDC dc(di->hDC, di->hwndItem); switch (di->itemAction) { case ODA_DRAWENTIRE: { LRect c = di->rcItem; LMemDC m(c.X(), c.Y(), GdcD->GetColourSpace()); HDC hdc = m.StartDC(); m.Colour(LColour(255, 0, 255)); m.Line(0, 0, m.X()-1, m.Y()-1); LONG s = GetWindowLong(_View, GWL_STYLE); SetWindowLong(_View, GWL_STYLE, (s & ~BS_TYPEMASK) | BS_PUSHBUTTON); SendMessage(_View, WM_PRINT, (WPARAM)hdc, PRF_ERASEBKGND|PRF_CLIENT); SetWindowLong(_View, GWL_STYLE, (s & ~BS_TYPEMASK) | BS_OWNERDRAW); m.EndDC(); dc.Blt(0, 0, &m); break; } case ODA_FOCUS: { break; } case ODA_SELECT: { break; } } return true; } } */ } if (!(WndFlags & GWF_DIALOG)) goto ReturnDefaultProc; break; } case WM_ENABLE: { Invalidate(&Pos); break; } case WM_HSCROLL: case WM_VSCROLL: { LViewI *Wnd = FindControl((HWND) Msg->b); if (Wnd) { Wnd->OnEvent(Msg); } break; } case WM_GETDLGCODE: { // we handle all tab control stuff return DLGC_WANTALLKEYS; // d->WndDlgCode | DLGC_WANTTAB; } case WM_MOUSEWHEEL: { // short fwKeys = LOWORD(Msg->a); // key flags short zDelta = (short) HIWORD(Msg->a); // wheel rotation int nScrollLines = - _lgi_mouse_wheel_lines(); double Lines = ((double)zDelta * (double)nScrollLines) / WHEEL_DELTA; if (ABS(Lines) < 1.0) Lines *= 1.0 / ABS(Lines); // LgiTrace("Lines = %g, zDelta = %i, nScrollLines = %i\n", Lines, zDelta, nScrollLines); // Try giving the event to the current window... if (!OnMouseWheel(Lines)) { // Find the window under the cursor... and try giving it the mouse wheel event short xPos = (short) LOWORD(Msg->b); // horizontal position of pointer short yPos = (short) HIWORD(Msg->b); // vertical position of pointer POINT Point = {xPos, yPos}; HWND hUnder = ::WindowFromPoint(Point); HWND hParent = ::GetParent(hUnder); if (hUnder && hUnder != _View && // Don't want to send ourselves a message... hParent != _View) // WM_MOUSEWHEEL will propagate back up to us and cause an infinite loop { // Do a post event in case the window is deleting... at least it won't crash. PostMessage(hUnder, Msg->m, Msg->a, Msg->b); } } return 0; } case M_CHANGE: { LWindow *w = GetWindow(); LAutoPtr note((LNotification*)Msg->B()); LViewI *Ctrl = w ? w->FindControl((int)Msg->a) : 0; if (Ctrl) { LAssert(note.Get() != NULL); return OnNotify(Ctrl, note ? *note : LNotifyNull); } else { LgiTrace("Ctrl %i not found.\n", Msg->a); } break; } case M_COMMAND: { // LViewI *Ci = FindControl((HWND) Msg->b); // LView *Ctrl = Ci ? Ci->GetGView() : 0; LView *Ctrl; if (Msg->b && CastHwnd(Ctrl, (HWND)Msg->b)) { short Code = HIWORD(Msg->a); switch (Code) { case CBN_CLOSEUP: { PostMessage(_View, WM_COMMAND, MAKELONG(Ctrl->GetId(), CBN_EDITCHANGE), Msg->b); break; } case CBN_EDITCHANGE: // COMBO { Ctrl->SysOnNotify(Msg->Msg(), Code); OnNotify(Ctrl, LNotifyValueChanged); break; } /* case BN_CLICKED: // BUTTON case EN_CHANGE: // EDIT */ default: { Ctrl->SysOnNotify(Msg->Msg(), Code); break; } } } break; } case WM_NCDESTROY: { #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(_View, GWLP_USERDATA, 0); #else SetWindowLong(_View, GWL_USERDATA, 0); #endif _View = NULL; if (WndFlags & GWF_QUIT_WND) { delete this; } break; } case WM_CLOSE: { if (OnRequestClose(false)) { Quit(); } break; } case WM_DESTROY: { OnDestroy(); break; } case WM_CREATE: { SetId(d->CtrlId); LWindow *w = GetWindow(); if (w && w->GetFocus() == this) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p) (%s)\\n", __FILE__, __LINE__, Handle(), GetClass()); } SetFocus(_View); } } if (TestFlag(GViewFlags, GWF_DROP_TARGET)) { DropTarget(true); } OnCreate(); break; } case WM_SETFOCUS: { LWindow *w = GetWindow(); if (w) { w->SetFocus(this, LWindow::GainFocus); } else { // This can happen in popup sub-trees of views. Where the focus // is tracked separately from the main LWindow. OnFocus(true); Invalidate((LRect*)NULL, false, true); } break; } case WM_KILLFOCUS: { LWindow *w = GetWindow(); if (w) { w->SetFocus(this, LWindow::LoseFocus); } else { // This can happen when the LWindow is being destroyed Invalidate((LRect*)NULL, false, true); OnFocus(false); } break; } case WM_WINDOWPOSCHANGED: { if (!IsIconic(_View)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (Info) { if (Info->x == -32000 && Info->y == -32000) { #if 0 LgiTrace("WM_WINDOWPOSCHANGED %i,%i,%i,%i (icon=%i)\\n", Info->x, Info->y, Info->cx, Info->cy, IsIconic(Handle())); #endif } else { LRect r; r.ZOff(Info->cx-1, Info->cy-1); r.Offset(Info->x, Info->y); if (r.Valid() && r != Pos) { Pos = r; } } } OnPosChange(); } if (!(WndFlags & GWF_DIALOG)) { goto ReturnDefaultProc; } break; } case WM_CAPTURECHANGED: { LViewI *Wnd; if (Msg->B() && CastHwnd(Wnd, (HWND)Msg->B())) { if (Wnd != _Capturing) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> %p/%s\n", _FL, _Capturing, _Capturing?_Capturing->GetClass():0, Wnd, Wnd?Wnd->GetClass() : 0); #endif _Capturing = Wnd; } } else if (_Capturing) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, _Capturing, _Capturing?_Capturing->GetClass():0); #endif _Capturing = NULL; } break; } case M_MOUSEENTER: { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = 0; LViewI *MouseOver = WindowFromPoint(Ms.x, Ms.y); if (MouseOver && _Over != MouseOver && !(MouseOver == this || MouseOver->Handle() == 0)) { if (_Capturing) { if (MouseOver == _Capturing) { Ms = lgi_adjust_click(Ms, _Capturing); _Capturing->OnMouseEnter(Ms); } } else { if (_Over) { LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseExit(m); #if DEBUG_OVER LgiTrace("Enter.LoseOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif } _Over = MouseOver; if (_Over) { #if DEBUG_OVER LgiTrace("Enter.GetOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseEnter(m); } } } break; } case M_MOUSEEXIT: { if (_Over) { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = 0; bool Mine = false; if (_Over->Handle()) { Mine = _Over == this; } else { for (LViewI *o = _Capturing ? _Capturing : _Over; o; o = o->GetParent()) { if (o == this) { Mine = true; break; } } } if (Mine) { if (_Capturing) { LMouse m = lgi_adjust_click(Ms, _Capturing); _Capturing->OnMouseExit(m); } else { #if DEBUG_OVER LgiTrace("Exit.LoseOver=%p '%-20s'\n", _Over, _Over->Name()); #endif _Over->OnMouseExit(Ms); _Over = 0; } } } break; } case WM_MOUSEMOVE: { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags(); Ms.IsMove(true); if (TestFlag(Msg->a, MK_LBUTTON)) SetFlag(Ms.Flags, LGI_EF_LEFT); if (TestFlag(Msg->a, MK_RBUTTON)) SetFlag(Ms.Flags, LGI_EF_RIGHT); if (TestFlag(Msg->a, MK_MBUTTON)) SetFlag(Ms.Flags, LGI_EF_MIDDLE); SetKeyFlag(Ms.Flags, VK_MENU, MK_ALT); Ms.Down((Msg->a & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) != 0); LViewI *MouseOver = WindowFromPoint(Ms.x, Ms.y); if (_Over != MouseOver) { if (_Over) { #if DEBUG_OVER LgiTrace("Move.LoseOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseExit(m); } _Over = MouseOver; if (_Over) { LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseEnter(m); #if DEBUG_OVER LgiTrace("Move.GetOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif } } // int CurX = Ms.x, CurY = Ms.y; LCursor Cursor = (_Over ? _Over : this)->GetCursor(Ms.x, Ms.y); LgiToWindowsCursor(_View, Cursor); #if 0 LgiTrace("WM_MOUSEMOVE %i,%i target=%p/%s, over=%p/%s, cap=%p/%s\n", Ms.x, Ms.y, Ms.Target, Ms.Target?Ms.Target->GetClass():0, _Over, _Over?_Over->GetClass():0, _Capturing, _Capturing?_Capturing->GetClass():0); #endif if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else return 0; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) { Ms.Target->OnMouseMove(Ms); } break; } case WM_NCHITTEST: { POINT Pt = { LOWORD(Msg->b), HIWORD(Msg->b) }; ScreenToClient(_View, &Pt); int Hit = OnHitTest(Pt.x, Pt.y); if (Hit >= 0) { // LgiTrace("%I64i Hit=%i\n", LCurrentTime(), Hit); return Hit; } if (!(WndFlags & GWF_DIALOG)) { goto ReturnDefaultProc; } break; } case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: case WM_LBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_LEFT; Ms.Down(Msg->m != WM_LBUTTONUP); Ms.Double(Msg->m == WM_LBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; #if DEBUG_MOUSE_CLICKS LString Msg; Msg.Printf("%s.Click", Ms.Target->GetClass()); Ms.Trace(Msg); #endif LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_RBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_RIGHT; Ms.Down(Msg->m != WM_RBUTTONUP); Ms.Double(Msg->m == WM_RBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; #if DEBUG_MOUSE_CLICKS LString Msg; Msg.Printf("%s.Click", Ms.Target->GetClass()); Ms.Trace(Msg); #endif LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_MBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_MIDDLE; Ms.Down(Msg->m != WM_MBUTTONUP); Ms.Double(Msg->m == WM_MBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_XBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONUP: { LMouse Ms; int Clicked = (Msg->a >> 16) & 0xffff; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_MIDDLE; Ms.Button1(TestFlag(Clicked, XBUTTON1)); Ms.Button2(TestFlag(Clicked, XBUTTON2)); Ms.Down(Msg->m != WM_XBUTTONUP); Ms.Double(Msg->m == WM_XBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_SYSKEYUP: case WM_SYSKEYDOWN: case WM_KEYDOWN: case WM_KEYUP: { static char AltCode[32]; bool IsDialog = TestFlag(WndFlags, GWF_DIALOG); bool IsDown = Msg->m == WM_KEYDOWN || Msg->m == WM_SYSKEYDOWN; int KeyFlags = _lgi_get_key_flags(); HWND hwnd = _View; if (SysOnKey(this, Msg)) { // LgiTrace("SysOnKey true, Msg=0x%x %x,%x\n", Msg->m, Msg->a, Msg->b); return 0; } else { // Key LKey Key((int)Msg->a, (int)Msg->b); Key.Flags = KeyFlags; Key.Down(IsDown); Key.IsChar = false; if (Key.Ctrl()) { Key.c16 = (char16)Msg->a; } if (Key.c16 == VK_TAB && ConsumeTabKey) { ConsumeTabKey--; } else { LWindow *Wnd = GetWindow(); if (Wnd) { if (Key.Alt() || Key.Ctrl() || (Key.c16 < 'A' || Key.c16 > 'Z')) { Wnd->HandleViewKey(this, Key); } } else { OnKey(Key); } } if (Msg->m == WM_SYSKEYUP || Msg->m == WM_SYSKEYDOWN) { if (Key.vkey >= VK_F1 && Key.vkey <= VK_F12 && Key.Alt() == false) { // So in LgiIde if you press F10 (debug next) you get a hang // sometimes in DefWindowProc. Until I figure out what's going // on this code exits before calling DefWindowProc without // breaking other WM_SYSKEY* functionality (esp Alt+F4). return 0; } } } if (!IsDialog) { // required for Alt-Key function (eg Alt-F4 closes window) goto ReturnDefaultProc; } break; } #if OLD_WM_CHAR_MODE case WM_CHAR: { LKey Key((int)Msg->a, (int)Msg->b); Key.Flags = _lgi_get_key_flags(); Key.Down(true); Key.IsChar = true; bool Shift = Key.Shift(); bool Caps = TestFlag(Key.Flags, LGI_EF_CAPS_LOCK); if (!(Shift ^ Caps)) { Key.c16 = ToLower(Key.c16); } else { Key.c16 = ToUpper(Key.c16); } if (Key.c16 == LK_TAB && ConsumeTabKey) { ConsumeTabKey--; } else { LWindow *Wnd = GetWindow(); if (Wnd) { Wnd->HandleViewKey(this, Key); } else { OnKey(Key); } } break; } #endif case M_SET_WND_STYLE: { SetWindowLong(Handle(), GWL_STYLE, (LONG)Msg->b); SetWindowPos( Handle(), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE | SWP_FRAMECHANGED); break; } case WM_PAINT: { _Paint(); break; } case WM_NCPAINT: { if (GetWindow() != this && !TestFlag(WndFlags, GWF_SYS_BORDER)) { HDC hDC = GetWindowDC(_View); LScreenDC Dc(hDC, _View, true); LRect p(0, 0, Dc.X()-1, Dc.Y()-1); OnNcPaint(&Dc, p); } goto ReturnDefaultProc; break; } case WM_NCCALCSIZE: { LMessage::Param Status = 0; int Edge = (Sunken() || Raised()) ? _BorderSize : 0; RECT *rc = NULL; if (Msg->a) { NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS*) Msg->b; rc = p->rgrc; } else { rc = (RECT*)Msg->b; } if (!(WndFlags & GWF_DIALOG)) { Status = DefWindowProcW(_View, Msg->m, Msg->a, Msg->b); } if (Edge && rc && !TestFlag(WndFlags, GWF_SYS_BORDER)) { rc->left += Edge; rc->top += Edge; rc->right -= Edge; rc->bottom -= Edge; return 0; } return Status; } case WM_NOTIFY: { NMHDR *Hdr = (NMHDR*)Msg->B(); if (Hdr) { LView *Wnd; if (CastHwnd(Wnd, Hdr->hwndFrom)) Wnd->SysOnNotify(Msg->Msg(), Hdr->code); } break; } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } default: { if (!(WndFlags & GWF_DIALOG)) goto ReturnDefaultProc; break; } } } return 0; ReturnDefaultProc: #ifdef _DEBUG uint64 start = LCurrentTime(); #endif LRESULT r = DefWindowProcW(_View, Msg->m, Msg->a, Msg->b); #ifdef _DEBUG uint64 now = LCurrentTime(); if (now - start > 1000) { LgiTrace("DefWindowProc(0x%.4x, %i, %i) took %ims\n", Msg->m, Msg->a, Msg->b, (int)(now - start)); } #endif return r; } LViewI *LView::FindControl(OsView hCtrl) { if (_View == hCtrl) { return this; } for (List::I i = Children.begin(); i.In(); i++) { LViewI *Ctrl = (*i)->FindControl(hCtrl); if (Ctrl) return Ctrl; } return 0; } diff --git a/src/win/Lgi/Window.cpp b/src/win/Lgi/Window.cpp --- a/src/win/Lgi/Window.cpp +++ b/src/win/Lgi/Window.cpp @@ -1,1395 +1,1397 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Panel.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/Button.h" #include "lgi/common/Notifications.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #define DEBUG_WINDOW_PLACEMENT 0 #define DEBUG_HANDLE_VIEW_KEY 0 #define DEBUG_HANDLE_VIEW_MOUSE 0 -#define DEBUG_SERIALIZE_STATE 0 +#define DEBUG_SERIALIZE_STATE 1 #define DEBUG_SETFOCUS 0 extern bool In_SetWindowPos; typedef UINT (WINAPI *ProcGetDpiForWindow)(_In_ HWND hwnd); typedef UINT (WINAPI *ProcGetDpiForSystem)(VOID); LLibrary User32("User32"); LPoint LGetDpiForWindow(HWND hwnd) { static bool init = false; static ProcGetDpiForWindow pGetDpiForWindow = NULL; static ProcGetDpiForSystem pGetDpiForSystem = NULL; if (!init) { init = true; pGetDpiForWindow = (ProcGetDpiForWindow) User32.GetAddress("GetDpiForWindow"); pGetDpiForSystem = (ProcGetDpiForSystem) User32.GetAddress("GetDpiForSystem"); } if (pGetDpiForWindow && pGetDpiForSystem) { auto Dpi = hwnd ? pGetDpiForWindow(hwnd) : pGetDpiForSystem(); return LPoint(Dpi, Dpi); } return LScreenDpi(); } /////////////////////////////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; class LWindowPrivate { public: LArray Hooks; bool SnapToEdge; bool AlwaysOnTop; LWindowZoom Show; bool InCreate; LAutoPtr Wp; LPoint Dpi; // Focus stuff LViewI *Focus; LWindowPrivate() { Focus = NULL; InCreate = true; Show = LZoomNormal; SnapToEdge = false; AlwaysOnTop = false; } ~LWindowPrivate() { } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return (ssize_t)Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { _Window = this; d = new LWindowPrivate; Menu = 0; _Dialog = NULL; SetStyle(GetStyle() | WS_TILEDWINDOW | WS_CLIPCHILDREN); SetStyle(GetStyle() & ~WS_CHILD); SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); LWindowsClass *c = LWindowsClass::Create(GetClass()); if (c) { c->Register(); } Visible(false); _Default = 0; _Lock = new LMutex("LWindow"); _QuitOnClose = false; } LWindow::~LWindow() { if (LAppInst && LAppInst->AppWnd == this) { LAppInst->AppWnd = 0; } if (Menu) { Menu->Detach(); DeleteObj(Menu); } DeleteObj(_Lock); DeleteObj(d); } +int LWindow::WaitThread() +{ + // No thread to wait on... + return 0; +} + bool LWindow::SetIcon(const char *Icon) { return CreateClassW32(LAppInst->Name(), LoadIcon(LProcessInst(), (LPCWSTR)Icon)) != 0; } LViewI *LWindow::GetFocus() { return d->Focus; } static LAutoString DescribeView(LViewI *v) { if (!v) return LAutoString(NewStr("NULL")); char s[512]; int ch = 0; ::LArray p; for (LViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (auto n=MIN(3, (ssize_t)p.Length()-1); n>=0; n--) { v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, ">%s", v->GetClass()); } return LAutoString(NewStr(s)); } static bool HasParentPopup(LViewI *v) { for (; v; v = v->GetParent()) { if (dynamic_cast(v)) return true; } return false; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { LViewI *This = this; if (ctrl == This && d->Focus) { // The main LWindow is getting focus. // Check if we can re-focus the previous child focus... LView *v = d->Focus->GetGView(); if (v && !HasParentPopup(v)) { // We should never return focus to a popup, or it's child. if (!(v->WndFlags & GWF_FOCUS)) { // Yes, the child view doesn't think it has focus... // So re-focus it... if (v->Handle()) { // Non-virtual window... ::SetFocus(v->Handle()); } v->WndFlags |= GWF_FOCUS; v->OnFocus(true); v->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) refocusing: %s\n", _set.Get(), TypeName, _foc.Get()); #endif return; } } } // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non LView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool b) { d->SnapToEdge = b; } bool LWindow::GetAlwaysOnTop() { return d->AlwaysOnTop; } void LWindow::SetAlwaysOnTop(bool b) { d->AlwaysOnTop = b; if (_View) SetWindowPos(_View, b ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); } void LWindow::Raise() { if (_View) { DWORD dwFGProcessId; DWORD dwFGThreadId = GetWindowThreadProcessId(_View, &dwFGProcessId); DWORD dwThisThreadId = GetCurrentThreadId(); AttachThreadInput(dwThisThreadId, dwFGThreadId, true); SetForegroundWindow(_View); BringWindowToTop(_View); if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", __FILE__, __LINE__, _View); } ::SetFocus(_View); AttachThreadInput(dwThisThreadId, dwFGThreadId, false); } } LWindowZoom LWindow::GetZoom() { if (IsZoomed(Handle())) { return LZoomMax; } else if (IsIconic(Handle())) { return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (_View && IsWindowVisible(_View)) { switch (i) { case LZoomMax: { ShowWindow(Handle(), SW_MAXIMIZE); break; } case LZoomMin: { ShowWindow(Handle(), SW_MINIMIZE); break; } case LZoomNormal: { if (!Visible()) { Visible(true); } if (IsIconic(Handle()) || IsZoomed(Handle())) { ShowWindow(Handle(), SW_NORMAL); } LYield(); RECT r; GetWindowRect(Handle(), &r); if (r.left != Pos.x1 || r.top != Pos.y1) { SetWindowPos(Handle(), 0, Pos.x1, Pos.y1, Pos.X(), Pos.Y(), SWP_NOZORDER); } break; } } } d->Show = i; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return true; } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { #if DEBUG_HANDLE_VIEW_MOUSE m.Trace("HandleViewMouse"); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { LView *t = d->Hooks[i].Target; if (!t->OnViewMouse(v, m)) { #if DEBUG_HANDLE_VIEW_MOUSE - if (!m.IsMove()) + if (m.IsMove()) LgiTrace(" Hook %i of %i ate mouse event: '%s'\n", i, d->Hooks.Length(), d->Hooks[i].Target->GetClass()); #endif return false; } } } #if DEBUG_HANDLE_VIEW_MOUSE if (!m.IsMove()) LgiTrace(" Passing mouse event to '%s'\n", v->GetClass()); #endif return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { #if DEBUG_HANDLE_VIEW_KEY char msg[256]; sprintf_s(msg, sizeof(msg), "HandleViewKey, v=%s", v ? v->GetClass() : "NULL"); k.Trace(msg); #endif // Any window in a pop up always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Popup %s handling key.\n", p->GetClass()); #endif return v->OnKey(k); } } // Allow any hooks to see the key... #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Hooks.Length()=%i.\n", (int)d->Hooks.Length()); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Hook[%i] %s handling key.\n", i, d->Hooks[i].Target->GetClass()); #endif return true; } } } // Give the key to the focused window... if (d->Focus && d->Focus->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Focus %s handling key.\n", d->Focus->GetClass()); #endif return true; } // Check default controls p = 0; if (k.c16 == VK_RETURN) { if (!_Default) p = _Default = FindControl(IDOK); else p = _Default; #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using _Default ctrl (%s).\n", p ? p->GetClass() : "NULL"); #endif } else if (k.c16 == VK_ESCAPE) { p = FindControl(IDCANCEL); if (p) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using IDCANCEL ctrl (%s).\n", p->GetClass()); #endif } } if (p && p->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Default control %s handled key.\n", p->GetClass()); #endif return true; } // Menu shortcut? if (Menu && Menu->OnKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Menu handled key.\n"); #endif return true; } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" No one handled key.\n"); #endif return false; } void LWindow::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } bool LWindow::Obscured() { RECT tRect; bool isObscured = false; if (GetWindowRect(_View, &tRect)) { RECT nRect; HWND walker = _View; while (walker = ::GetNextWindow(walker, GW_HWNDPREV)) { if (IsWindowVisible(walker)) { if ((::GetWindowRect(walker, &nRect))) { RECT iRect; IntersectRect(&iRect, &tRect, &nRect); if (iRect.bottom || iRect.top || iRect.left || iRect.right) { isObscured = true; break; } } } } } return isObscured; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool v) { if (v) PourAll(); if (v) { SetStyle(GetStyle() | WS_VISIBLE); if (_View) { LWindowZoom z = d->Show; char *Cmd = 0; LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(*Wp); Wp->flags = 2; Wp->ptMaxPosition.x = -1; Wp->ptMaxPosition.y = -1; if (d->Show == LZoomMax) { Wp->showCmd = SW_MAXIMIZE; Cmd = "SW_MAXIMIZE"; } else if (d->Show == LZoomMin) { Wp->showCmd = SW_MINIMIZE; Cmd = "SW_MINIMIZE"; } else { Wp->showCmd = SW_NORMAL; Cmd = "SW_NORMAL"; } Wp->rcNormalPosition = Pos; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(_View, Wp); if (d->InCreate) d->Wp = Wp; } } } else { #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - Visible(%i)\n", __FILE__, __LINE__, v); #endif LView::Visible(v); } if (v) { OnZoom(d->Show); } } static bool IsAppWnd(HWND h) { if (!IsWindowVisible(h)) return false; auto flags = GetWindowLong(h, GWL_STYLE); if (flags & WS_POPUP) return false; return true; } bool LWindow::IsActive() { auto top = GetTopWindow(GetDesktopWindow()); while (top && !IsAppWnd(top)) top = ::GetWindow(top, GW_HWNDNEXT); return top == _View; } bool LWindow::SetActive() { if (!_View) return false; return SetWindowPos(_View, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) != 0; } void LWindow::PourAll() { LRegion Client(GetClient()); LRegion Update; bool HasTools = false; { LRegion Tools; for (auto v: Children) { LView *k = dynamic_cast(v); if (k && k->_IsToolBar) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } // LgiTrace("Client=%s\n", Client.Bound().GetStr()); for (auto v: Children) { LView *k = dynamic_cast(v); if (!(k && k->_IsToolBar)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible // v->Visible(FALSE); } } } for (int i=0; iMsg()) { case WM_DPICHANGED: { d->Dpi.x = HIWORD(Msg->A()); d->Dpi.y = LOWORD(Msg->A()); OnPosChange(); break; } case M_ASSERT_UI: { - int *Result = (int*)Msg->A(); - LString *Str = (LString*)Msg->B(); - if (Result) - { - extern int LAssertDlg(LString Msg); - *Result = LAssertDlg(Str ? *Str : "Error: no msg."); - } - else assert(!"Invalid param"); + LAutoPtr Str((LString*)Msg->A()); + extern void LAssertDlg(LString Msg, std::function Callback); + if (Str) + LAssertDlg(Str ? *Str : "Error: no msg.", NULL); break; } case M_SET_WINDOW_PLACEMENT: { /* Apparently if you use SetWindowPlacement inside the WM_CREATE handler, then the restored rect doesn't "stick", it gets stomped on by windows. So this code... RESETS it to be what we set earlier. Windows sucks. */ if (!d->Wp || !_View) break; LRect r = d->Wp->rcNormalPosition; if (!LView::Visible()) d->Wp->showCmd = SW_HIDE; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, r.GetStr(), d->Wp->showCmd); #endif SetWindowPlacement(_View, d->Wp); d->Wp.Reset(); break; } case WM_SYSCOLORCHANGE: { LColour::OnChange(); break; } case WM_WINDOWPOSCHANGING: { bool Icon = IsIconic(Handle()) != 0; bool Zoom = IsZoomed(Handle()) != 0; if (!Icon && (_Dialog || !Zoom)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (!Info) break; if (Info->flags == (SWP_NOSIZE | SWP_NOMOVE) && _Dialog) { // Info->flags |= SWP_NOZORDER; Info->hwndInsertAfter = _Dialog->Handle(); } if (GetMinimumSize().x && GetMinimumSize().x > Info->cx) { Info->cx = GetMinimumSize().x; } if (GetMinimumSize().y && GetMinimumSize().y > Info->cy) { Info->cy = GetMinimumSize().y; } /* This is broken on windows 10... windows get stuck on the edge of the desktop. RECT Rc; if (d->SnapToEdge && SystemParametersInfo(SPI_GETWORKAREA, 0, &Rc, SPIF_SENDCHANGE)) { LRect r = Rc; LRect p(Info->x, Info->y, Info->x + Info->cx - 1, Info->y + Info->cy - 1); if (r.Valid() && p.Valid()) { int Snap = 12; if (abs(p.x1 - r.x1) <= Snap) { // Snap left edge Info->x = r.x1; } else if (abs(p.x2 - r.x2) <= Snap) { // Snap right edge Info->x = r.x2 - Info->cx + 1; } if (abs(p.y1 - r.y1) <= Snap) { // Snap top edge Info->y = r.y1; } else if (abs(p.y2 - r.y2) <= Snap) { // Snap bottom edge Info->y = r.y2 - Info->cy + 1; } } } */ } break; } case WM_SIZE: { if (Visible()) { LWindowZoom z = d->Show; switch (Msg->a) { case SIZE_MINIMIZED: { z = LZoomMin; break; } case SIZE_MAXIMIZED: { z = LZoomMax; break; } case SIZE_RESTORED: { z = LZoomNormal; break; } } if (z != d->Show) { OnZoom(d->Show = z); } } Status = LView::OnEvent(Msg) != 0; break; } case WM_CREATE: { if (d->AlwaysOnTop) SetAlwaysOnTop(true); PourAll(); OnCreate(); if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } d->InCreate = false; if (d->Wp) { PostEvent(M_SET_WINDOW_PLACEMENT); } break; } case WM_WINDOWPOSCHANGED: { d->Wp.Reset(); Status = LView::OnEvent(Msg) != 0; break; } case WM_QUERYENDSESSION: case WM_CLOSE: { bool QuitApp; bool OsShuttingDown = Msg->Msg() == WM_QUERYENDSESSION; if (QuitApp = OnRequestClose(OsShuttingDown)) { Quit(); } if (Msg->Msg() == WM_CLOSE) { return 0; } else { return QuitApp; } break; } case WM_SYSCOMMAND: { if (Msg->a == SC_CLOSE) { if (OnRequestClose(false)) { Quit(); } return 0; } else { Status = LView::OnEvent(Msg) != 0; } break; } case WM_DROPFILES: { HDROP hDrop = (HDROP) Msg->a; if (hDrop) { LArray FileNames; int Count = 0; Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } OnReceiveFiles(FileNames); FileNames.DeleteArrays(); } break; } case M_HANDLEMOUSEMOVE: { // This receives events fired from the LMouseHookPrivate class so that // non-LGI windows create mouse hook events as well. LTempView v((OsView)Msg->B()); LMouse m; m.x = LOWORD(Msg->A()); m.y = HIWORD(Msg->A()); HandleViewMouse(&v, m); break; } case M_COMMAND: { HWND OurWnd = Handle(); // copy onto the stack, because // we might lose the 'this' object in the // OnCommand handler which would delete // the memory containing the handle. Status = OnCommand((int) Msg->a, 0, (OsView) Msg->b); if (!IsWindow(OurWnd)) { // The window was deleted so break out now break; } // otherwise fall thru to the LView handler } default: { Status = (int) LView::OnEvent(Msg); break; } } return Status; } LPoint LWindow::GetDpi() { if (!d->Dpi.x) d->Dpi = LGetDpiForWindow(_View); return d->Dpi; } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); LPointF r( Dpi.x / 96.0, Dpi.y / 96.0 ); return r; } LRect &LWindow::GetPos() { if (_View && IsZoomed(_View)) { static LRect r; RECT rc; GetWindowRect(_View, &rc); r = rc; return r; } return Pos; } void LWindow::OnPosChange() { PourAll(); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { auto i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { #if WINNATIVE LButton *Btn; if (Btn = dynamic_cast(_Default)) Btn->Default(false); #endif _Default = v; #if WINNATIVE if (Btn = dynamic_cast(_Default)) Btn->Default(true); #endif } bool LWindow::UnregisterHook(LView *Target) { auto i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; #if DEBUG_SERIALIZE_STATE LgiTrace("LWindow::SerializeState(%p, %s, %i)\n", Store, FieldName, Load); #endif if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i v=%s\n", __LINE__, v.Str()); #endif LToken t(v.Str(), ";"); for (int i=0; iSetValue(FieldName, v)) return false; } return true; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } diff --git a/src/win/Widgets/Dialog_Win.cpp b/src/win/Widgets/Dialog_Win.cpp --- a/src/win/Widgets/Dialog_Win.cpp +++ b/src/win/Widgets/Dialog_Win.cpp @@ -1,402 +1,419 @@ /*hdr ** FILE: GWidgets.cpp ** AUTHOR: Matthew Allen ** DATE: 8/9/1998 ** DESCRIPTION: Dialog components ** ** Copyright (C) 1998-2001 Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" #include "lgi/common/LgiRes.h" struct LDialogPriv { bool IsModal = false, IsModeless = false, _Resizable = true; int ModalStatus = -1; int BtnId = -1; int ModalResult = -1; + // Modal state + OsView ParentHnd = NULL; + LWindow *ParentWnd = NULL; + LDialog::OnClose Callback; + }; /////////////////////////////////////////////////////////////////////////////////////////// -LDialog::LDialog() +LDialog::LDialog(LViewI *parent) : ResObject(Res_Dialog) { d = new LDialogPriv; _Window = this; Name("Dialog"); + if (parent) + SetParent(parent); SetStyle(GetStyle() & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX)); SetStyle(GetStyle() | WS_DLGFRAME); } LDialog::~LDialog() { + LAssert(!d->IsModal && !d->IsModeless); // Can't delete while still active... DeleteObj(d); } bool LDialog::LoadFromResource(int Resource, char *TagList) { LAutoString n; bool Status = LResourceLoad::LoadFromResource(Resource, this, &Pos, &n, TagList); if (Status && n) Name(n); return Status; } LRESULT CALLBACK DlgRedir(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (m == WM_INITDIALOG) { LDialog *NewWnd = (LDialog*) b; NewWnd->_View = hWnd; #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)(LViewI*)NewWnd); #else SetWindowLong(hWnd, GWL_USERDATA, (LONG)(LViewI*)NewWnd); #endif } LViewI *Wnd = (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 #pragma warning(disable : 4312) GetWindowLongPtr(hWnd, GWLP_USERDATA); #pragma warning(default : 4312) #else GetWindowLong(hWnd, GWL_USERDATA); #endif if (Wnd) { LMessage Msg(m, a, b); return Wnd->OnEvent(&Msg); } return 0; } bool LDialog::OnRequestClose(bool OsClose) { return true; } bool LDialog::IsModal() { return d->IsModal; } -int LDialog::DoModal(OsView ParentHnd) +void LDialog::DoModal(OnClose Callback, OsView ParentHnd) { - int Status = -1; - d->IsModal = true; + d->Callback = Callback; LViewI *p = GetParent(); if (p && p->GetWindow() != p) p = p->GetWindow(); if (Attach(0)) { AttachChildren(); - LWindow *ParentWnd = dynamic_cast(p); - if (ParentWnd) - ParentWnd->_Dialog = this; + d->ParentWnd = dynamic_cast(p); + if (d->ParentWnd) + d->ParentWnd->_Dialog = this; if (p) { LRect pp = p->GetPos(); if (pp.Valid()) { int cx = pp.x1 + (pp.X() >> 1); int cy = pp.y1 + (pp.Y() >> 1); LRect np = GetPos(); np.Offset( cx - (np.X() >> 1) - np.x1, cy - (np.Y() >> 1) - np.y1); SetPos(np); MoveOnScreen(); } } Visible(true); - OsView pwnd = ParentHnd ? ParentHnd : (p ? p->Handle() : NULL); - if (pwnd) - EnableWindow(pwnd, false); - - while (d->ModalResult < 0) - { - LYield(); // This is fixed by the haiku branch - LSleep(20); - } - - if (pwnd) - EnableWindow(pwnd, true); - - if (ParentWnd) - ParentWnd->_Dialog = NULL; - - Visible(false); - Status = d->ModalResult; + d->ParentHnd = d->ParentHnd ? d->ParentHnd : (p ? p->Handle() : NULL); + if (d->ParentHnd) + EnableWindow(d->ParentHnd, false); } else { LAssert(!"Attach failed."); } - - return Status; +} + +void LDialog::EndModal(int Code) +{ + if (!d->IsModal) + { + LAssert(!"Not a modal dialog."); + return; + } + + // This is so the calling code can unwind all it's stack frames without + // worrying about accessing things that have been deleted. + PostEvent(M_DIALOG_END_MODAL, (LMessage::Param)Code); } static char *BaseStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; int LDialog::DoModeless() { int Status = -1; LAssert(!_View); if (_View) return Status; d->IsModeless = true; d->IsModal = false; LViewI *p = GetParent(); if (p && p->GetWindow() != p) p = p->GetWindow(); if (Attach(0)) { AttachChildren(); if (p && Handle() && p->Handle()) { #ifdef _WIN64 SetWindowLongPtr(Handle(), GWLP_HWNDPARENT, (LONG_PTR)p->Handle()); #else SetWindowLong(Handle(), GWL_HWNDPARENT, (LONG)p->Handle()); #endif } if (p) { LRect pp = p->GetPos(); int cx = pp.x1 + (pp.X() >> 1); int cy = pp.y1 + (pp.Y() >> 1); LRect np = GetPos(); np.Offset( cx - (np.X() >> 1) - np.x1, cy - (np.Y() >> 1) - np.y1); SetPos(np); MoveOnScreen(); } Visible(true); Status = true; } else { LAssert(!"Attach failed."); } return Status; } LMessage::Result LDialog::OnEvent(LMessage *Msg) { switch (Msg->m) { case WM_CREATE: { LRect r = Pos; Pos.ZOff(-1, -1); SetPos(r); // resets the dialog to the correct // size when large fonts are used if (GetAlwaysOnTop()) SetAlwaysOnTop(true); AttachChildren(); if (!_Default) SetDefault(FindControl(IDOK)); LResources::StyleElement(this); // This was commented out. I've re-introduced it until such time // as there is a good reason not to have it enabled. If such a reason // arises, update this comment to reflect that. OnCreate(); // If we don't return true here the LWindow::OnEvent handler for // WM_CREATE will call OnCreate again. return true; } + case M_DIALOG_END_MODAL: + { + // See ::EndModal for comment on why this is here. + d->ModalResult = max((int)Msg->A(), 0); + + if (d->ParentHnd) + EnableWindow(d->ParentHnd, true); + if (d->ParentWnd) + d->ParentWnd->_Dialog = NULL; + + Visible(false); + + d->IsModal = false; + + if (d->Callback) + d->Callback(this, d->ModalResult); + else + delete this; // default action is to delete the dialog + return 1; + } } return LWindow::OnEvent(Msg); } int LDialog::GetButtonId() { return d->BtnId; } int LDialog::OnNotify(LViewI *Ctrl, LNotification n) { LButton *b = dynamic_cast(Ctrl); if (b) { d->BtnId = b->GetId(); if (d->IsModal) EndModal(d->BtnId); else if (d->IsModeless) EndModeless(); } return 0; } void LDialog::Quit(bool DontDelete) { if (d->IsModal) EndModal(0); else LView::Quit(DontDelete); } void LDialog::OnPosChange() { if (Children.Length() == 1) { List::I it = Children.begin(); LLayout *t = dynamic_cast((LViewI*)it); if (t) { LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(r); } } } -void LDialog::EndModal(int Code) -{ - if (d->IsModal) - d->ModalResult = max(Code, 0); -} - void LDialog::EndModeless(int Code) { if (d->IsModeless) Quit(Code != 0); } /////////////////////////////////////////////////////////////////////////////////////////// LControl::LControl(char *SubClassName) : LView(0) { SubClass = 0; SetOnDelete = NULL; if (SubClassName) { SetClassW32(SubClassName); SubClass = LWindowsClass::Create(SubClassName); // Owned by the LWindowsClass object } Pos.ZOff(10, 10); } LControl::~LControl() { if (SetOnDelete) *SetOnDelete = true; } LMessage::Result LControl::OnEvent(LMessage *Msg) { LMessage::Result Status = 0; // Pre-OS event handler switch (Msg->m) { case WM_CREATE: { SetId(GetId()); OnCreate(); break; } case WM_SETTEXT: { if (IsWindowUnicode(_View)) LBase::NameW((char16*)Msg->b); else LBase::Name((char*)Msg->b); break; } case WM_GETDLGCODE: case WM_NOTIFY: case WM_COMMAND: case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { bool Deleted = false; SetOnDelete = &Deleted; Status = LView::OnEvent(Msg); if (Deleted) return Status; SetOnDelete = NULL; break; } case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: { // These should never be called.. but just in case. return LView::OnEvent(Msg); break; } case WM_NCDESTROY: { Status = LView::OnEvent(Msg); break; } } // OS event handler if (SubClass) Status = SubClass->CallParent(Handle(), Msg->m, Msg->a, Msg->b); return Status; } LPoint LControl::SizeOfStr(const char *Str) { LPoint Pt(0, 0); if (Str) { for (const char *s=Str; s && *s; ) { const char *e = strchr(s, '\n'); if (!e) e = s + strlen(s); LDisplayString ds(LSysFont, (char*)s, e - s); Pt.y += ds.Y(); Pt.x = max(Pt.x, ds.X()); s = (*e=='\n') ? e + 1 : 0; } } return Pt; }