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[] = { "", "/*", "*/", "
{
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("%s>", Tag.Get());
}
}
else if (Tag)
{
if (Text())
{
p.Write((char*)">", 1);
TextToStream(p, Text());
p.Print("%s>", 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