diff --git a/ResourceEditor/LgiResProj.xml b/ResourceEditor/LgiResProj.xml
--- a/ResourceEditor/LgiResProj.xml
+++ b/ResourceEditor/LgiResProj.xml
@@ -1,158 +1,158 @@
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
linux/Makefile.linux
.\LgiRes_vs2015.sln
./lgires
./lgires
gcc
/home/matthew/Code/MidiHw/mc4/resources/mc4.lr8
/home/matthew/Code/MidiHw/mc4/resources/mc4.lr8
../include
resources
../include
resources
../include/lgi/linux
../include/lgi/linux/Gtk
../include/lgi/linux
../include/lgi/linux/Gtk
../include/lgi/win
../include/lgi/win
Executable
magic
pthread
-static-libgcc
`pkg-config --libs gtk+-3.0`
magic
pthread
-static-libgcc
`pkg-config --libs gtk+-3.0`
`pkg-config --cflags gtk+-3.0`
`pkg-config --cflags gtk+-3.0`
lgires
lgires
4
4
0
1
diff --git a/ResourceEditor/linux/Makefile.linux b/ResourceEditor/linux/Makefile.linux
--- a/ResourceEditor/linux/Makefile.linux
+++ b/ResourceEditor/linux/Makefile.linux
@@ -1,494 +1,390 @@
#!/usr/bin/make
#
# This makefile generated by LgiIde
# http://www.memecode.com/lgi.php
#
.SILENT :
CC = gcc
CPP = g++
-Target = ../lgires
+Target = ./lgires
ifndef Build
Build = Debug
endif
MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
BuildDir = $(Build)
-Flags = -fPIC -w -fno-inline -fpermissive
+CFlags = -MMD -MP -g -fPIC -fno-inline
+CppFlags = $(CFlags) -fpermissive -std=c++14
ifeq ($(Build),Debug)
- Flags += -MMD -MP -g -std=c++14
+ CFlags += -g
+ CppFlags += -g
Tag = d
Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX
Libs = \
-lmagic \
-lpthread \
-static-libgcc \
`pkg-config --libs gtk+-3.0` \
-llgi-gtk3$(Tag) \
- -L../../$(BuildDir)
+ -L../$(BuildDir)
Inc = \
- -I../resources \
+ -I./resources \
`pkg-config --cflags gtk+-3.0` \
- -I../../include/lgi/linux/Gtk \
- -I../../include/lgi/linux \
- -I../../include
+ -I../include/lgi/linux/Gtk \
+ -I../include/lgi/linux \
+ -I../include
else
- Flags += -MMD -MP -s -Os -std=c++14
+ CFlags += -s -Os
+ CppFlags += -s -Os
Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX
Libs = \
-lmagic \
-lpthread \
-static-libgcc \
`pkg-config --libs gtk+-3.0` \
-llgi-gtk3$(Tag) \
- -L../../$(BuildDir)
+ -L../$(BuildDir)
Inc = \
- -I../resources \
+ -I./resources \
`pkg-config --cflags gtk+-3.0` \
- -I../../include/lgi/linux/Gtk \
- -I../../include/lgi/linux \
- -I../../include
+ -I../include/lgi/linux/Gtk \
+ -I../include/lgi/linux \
+ -I../include
endif
# Dependencies
-Source = ../../src/common/Lgi/OptionsFile.cpp \
- ../../src/common/Lgi/LgiMain.cpp \
- ../../src/common/Lgi/DocApp.cpp \
- ../../src/common/Lgi/About.cpp \
- ../../src/common/Gdc2/Filters/Lzw.cpp \
- ../../src/common/Gdc2/Filters/Gif.cpp \
- ../../../../../../../src/ShowLanguages.cpp \
- ../../../../../../../src/Search.cpp \
- ../../../../../../../src/LgiResApp.cpp \
- ../../../../../../../src/LgiRes_TableLayout.cpp \
- ../../../../../../../src/LgiRes_String.cpp \
- ../../../../../../../src/LgiRes_Menu.cpp \
- ../../../../../../../src/LgiRes_Dialog.cpp \
- ../../../../../../../src/LgiRes_Css.cpp \
- ../../../../../../../src/LgiRes_ControlTree.cpp
+Source = src/ShowLanguages.cpp \
+ src/Search.cpp \
+ src/LgiResApp.cpp \
+ src/LgiRes_TableLayout.cpp \
+ src/LgiRes_String.cpp \
+ src/LgiRes_Menu.cpp \
+ src/LgiRes_Dialog.cpp \
+ src/LgiRes_Css.cpp \
+ src/LgiRes_ControlTree.cpp \
+ ../src/common/Lgi/OptionsFile.cpp \
+ ../src/common/Lgi/LgiMain.cpp \
+ ../src/common/Lgi/DocApp.cpp \
+ ../src/common/Lgi/About.cpp \
+ ../src/common/Gdc2/Filters/Lzw.cpp \
+ ../src/common/Gdc2/Filters/Gif.cpp
SourceC := $(filter %.c,$(Source))
ObjectsC := $(SourceC:.c=.o)
SourceCpp := $(filter %.cpp,$(Source))
ObjectsCpp := $(SourceCpp:.cpp=.o)
Objects := $(notdir $(ObjectsC) $(ObjectsCpp))
Objects := $(addprefix $(BuildDir)/,$(Objects))
Deps := $(patsubst %.o,%.d,$(Objects))
$(BuildDir)/%.o: %.c
mkdir -p $(@D)
echo $(notdir $<) [$(Build)]
- $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@
+ $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@
$(BuildDir)/%.o: %.cpp
mkdir -p $(@D)
echo $(notdir $<) [$(Build)]
- $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@
+ $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@
# Target
# Executable target
-$(Target) : ../../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects)
+$(Target) : ../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects)
mkdir -p $(BuildDir)
@echo Linking $(Target) [$(Build)]...
$(CPP) -Wl,-export-dynamic,-R. -o \
$(Target) $(Objects) $(Libs)
@echo Done.
-../../$(BuildDir)/liblgi-gtk3$(Tag).so : ../../include/lgi/common/App.h \
- ../../include/lgi/common/Array.h \
- ../../include/lgi/common/AutoPtr.h \
- ../../include/lgi/common/Box.h \
- ../../include/lgi/common/Button.h \
- ../../include/lgi/common/Cancel.h \
- ../../include/lgi/common/CheckBox.h \
- ../../include/lgi/common/ClipBoard.h \
- ../../include/lgi/common/Colour.h \
- ../../include/lgi/common/ColourSpace.h \
- ../../include/lgi/common/Com.h \
- ../../include/lgi/common/Combo.h \
- ../../include/lgi/common/Containers.h \
- ../../include/lgi/common/Css.h \
- ../../include/lgi/common/CssTools.h \
- ../../include/lgi/common/DataDlg.h \
- ../../include/lgi/common/DateTime.h \
- ../../include/lgi/common/Dialog.h \
- ../../include/lgi/common/DisplayString.h \
- ../../include/lgi/common/DocView.h \
- ../../include/lgi/common/Dom.h \
- ../../include/lgi/common/DragAndDrop.h \
- ../../include/lgi/common/DropFiles.h \
- ../../include/lgi/common/Edit.h \
- ../../include/lgi/common/Error.h \
- ../../include/lgi/common/EventTargetThread.h \
- ../../include/lgi/common/File.h \
- ../../include/lgi/common/FileSelect.h \
- ../../include/lgi/common/Filter.h \
- ../../include/lgi/common/FindReplaceDlg.h \
- ../../include/lgi/common/Font.h \
- ../../include/lgi/common/FontCache.h \
- ../../include/lgi/common/FontSelect.h \
- ../../include/lgi/common/Gdc2.h \
- ../../include/lgi/common/GdcTools.h \
- ../../include/lgi/common/GdiLeak.h \
- ../../include/lgi/common/HashTable.h \
- ../../include/lgi/common/Input.h \
- ../../include/lgi/common/ItemContainer.h \
- ../../include/lgi/common/Json.h \
- ../../include/lgi/common/Layout.h \
- ../../include/lgi/common/Lgi.h \
- ../../include/lgi/common/LgiClasses.h \
- ../../include/lgi/common/LgiCommon.h \
- ../../include/lgi/common/LgiDefs.h \
- ../../include/lgi/common/LgiInc.h \
- ../../include/lgi/common/LgiInterfaces.h \
- ../../include/lgi/common/LgiMsgs.h \
- ../../include/lgi/common/LgiRes.h \
- ../../include/lgi/common/LgiString.h \
- ../../include/lgi/common/LgiUiBase.h \
- ../../include/lgi/common/Library.h \
- ../../include/lgi/common/List.h \
- ../../include/lgi/common/ListItemCheckBox.h \
- ../../include/lgi/common/ListItemRadioBtn.h \
- ../../include/lgi/common/Mem.h \
- ../../include/lgi/common/Menu.h \
- ../../include/lgi/common/Message.h \
- ../../include/lgi/common/Mime.h \
- ../../include/lgi/common/Mru.h \
- ../../include/lgi/common/Mutex.h \
- ../../include/lgi/common/Net.h \
- ../../include/lgi/common/NetTools.h \
- ../../include/lgi/common/Notifications.h \
- ../../include/lgi/common/OptionsFile.h \
- ../../include/lgi/common/Password.h \
- ../../include/lgi/common/PixelRops.h \
- ../../include/lgi/common/Point.h \
- ../../include/lgi/common/Popup.h \
- ../../include/lgi/common/PopupList.h \
- ../../include/lgi/common/PopupNotification.h \
- ../../include/lgi/common/Printer.h \
- ../../include/lgi/common/Progress.h \
- ../../include/lgi/common/ProgressDlg.h \
- ../../include/lgi/common/ProgressView.h \
- ../../include/lgi/common/Properties.h \
- ../../include/lgi/common/RadioGroup.h \
- ../../include/lgi/common/Rect.h \
- ../../include/lgi/common/RefCount.h \
- ../../include/lgi/common/Res.h \
- ../../include/lgi/common/ScrollBar.h \
- ../../include/lgi/common/Slider.h \
- ../../include/lgi/common/Stream.h \
- ../../include/lgi/common/StringClass.h \
- ../../include/lgi/common/StringLayout.h \
- ../../include/lgi/common/StructuredIo.h \
- ../../include/lgi/common/StructuredLog.h \
- ../../include/lgi/common/SubProcess.h \
- ../../include/lgi/common/TableLayout.h \
- ../../include/lgi/common/TabView.h \
- ../../include/lgi/common/TextFile.h \
- ../../include/lgi/common/TextLabel.h \
- ../../include/lgi/common/TextLog.h \
- ../../include/lgi/common/TextView3.h \
- ../../include/lgi/common/Thread.h \
- ../../include/lgi/common/ThreadEvent.h \
- ../../include/lgi/common/Token.h \
- ../../include/lgi/common/ToolBar.h \
- ../../include/lgi/common/ToolTip.h \
- ../../include/lgi/common/Tree.h \
- ../../include/lgi/common/Unicode.h \
- ../../include/lgi/common/UnrolledList.h \
- ../../include/lgi/common/Variant.h \
- ../../include/lgi/common/View.h \
- ../../include/lgi/common/Widgets.h \
- ../../include/lgi/common/Window.h \
- ../../include/lgi/common/XmlTree.h \
- ../../include/lgi/linux/Gtk/LgiOsClasses.h \
- ../../include/lgi/linux/Gtk/LgiOsDefs.h \
- ../../include/lgi/linux/Gtk/LgiWidget.h \
- ../../include/lgi/mac/cocoa/LCocoaView.h \
- ../../include/lgi/mac/cocoa/LgiMac.h \
- ../../include/lgi/mac/cocoa/LgiOs.h \
- ../../include/lgi/mac/cocoa/LgiOsClasses.h \
- ../../include/lgi/mac/cocoa/LgiOsDefs.h \
- ../../include/lgi/mac/cocoa/ObjCWrapper.h \
- ../../include/lgi/mac/cocoa/SymLookup.h \
- ../../private/common/ViewPriv.h \
- ../../private/linux/AppPriv.h \
- ../../src/common/Gdc2/15Bit.cpp \
- ../../src/common/Gdc2/16Bit.cpp \
- ../../src/common/Gdc2/24Bit.cpp \
- ../../src/common/Gdc2/32Bit.cpp \
- ../../src/common/Gdc2/8Bit.cpp \
- ../../src/common/Gdc2/Alpha.cpp \
- ../../src/common/Gdc2/Colour.cpp \
- ../../src/common/Gdc2/Filters/Filter.cpp \
- ../../src/common/Gdc2/Font/Charset.cpp \
- ../../src/common/Gdc2/Font/DisplayString.cpp \
- ../../src/common/Gdc2/Font/Font.cpp \
- ../../src/common/Gdc2/Font/FontSystem.cpp \
- ../../src/common/Gdc2/Font/FontType.cpp \
- ../../src/common/Gdc2/Font/StringLayout.cpp \
- ../../src/common/Gdc2/Font/TypeFace.cpp \
- ../../src/common/Gdc2/GdcCommon.cpp \
- ../../src/common/Gdc2/Path/Path.cpp \
- ../../src/common/Gdc2/Rect.cpp \
- ../../src/common/Gdc2/Surface.cpp \
- ../../src/common/Gdc2/Tools/ColourReduce.cpp \
- ../../src/common/Gdc2/Tools/GdcTools.cpp \
- ../../src/common/General/Containers.cpp \
- ../../src/common/General/DateTime.cpp \
- ../../src/common/General/ExeCheck.cpp \
- ../../src/common/General/FileCommon.cpp \
- ../../src/common/General/Password.cpp \
- ../../src/common/General/Properties.cpp \
- ../../src/common/Hash/md5/md5.c \
- ../../src/common/Hash/md5/md5.h \
- ../../src/common/Hash/sha1/sha1.c \
- ../../src/common/Hash/sha1/sha1.h \
- ../../src/common/Lgi/Alert.cpp \
- ../../src/common/Lgi/AppCommon.cpp \
- ../../src/common/Lgi/Css.cpp \
- ../../src/common/Lgi/CssTools.cpp \
- ../../src/common/Lgi/DataDlg.cpp \
- ../../src/common/Lgi/DragAndDropCommon.cpp \
- ../../src/common/Lgi/FileSelect.cpp \
- ../../src/common/Lgi/FindReplace.cpp \
- ../../src/common/Lgi/FontSelect.cpp \
- ../../src/common/Lgi/GuiUtils.cpp \
- ../../src/common/Lgi/Input.cpp \
- ../../src/common/Lgi/LgiCommon.cpp \
- ../../src/common/Lgi/Library.cpp \
- ../../src/common/Lgi/LMsg.cpp \
- ../../src/common/Lgi/MemStream.cpp \
- ../../src/common/Lgi/MenuCommon.cpp \
- ../../src/common/Lgi/Mru.cpp \
- ../../src/common/Lgi/Mutex.cpp \
- ../../src/common/Lgi/Object.cpp \
- ../../src/common/Lgi/OptionsFile.cpp \
- ../../src/common/Lgi/Rand.cpp \
- ../../src/common/Lgi/Stream.cpp \
- ../../src/common/Lgi/SubProcess.cpp \
- ../../src/common/Lgi/ThreadCommon.cpp \
- ../../src/common/Lgi/ThreadEvent.cpp \
- ../../src/common/Lgi/ToolTip.cpp \
- ../../src/common/Lgi/TrayIcon.cpp \
- ../../src/common/Lgi/Variant.cpp \
- ../../src/common/Lgi/ViewCommon.cpp \
- ../../src/common/Lgi/WindowCommon.cpp \
- ../../src/common/Net/Base64.cpp \
- ../../src/common/Net/MDStringToDigest.cpp \
- ../../src/common/Net/Net.cpp \
- ../../src/common/Net/NetTools.cpp \
- ../../src/common/Net/Uri.cpp \
- ../../src/common/Resource/LgiRes.cpp \
- ../../src/common/Resource/Res.cpp \
- ../../src/common/Skins/Gel/Gel.cpp \
- ../../src/common/Text/DocView.cpp \
- ../../src/common/Text/String.cpp \
- ../../src/common/Text/TextView3.cpp \
- ../../src/common/Text/Token.cpp \
- ../../src/common/Text/Unicode.cpp \
- ../../src/common/Text/Utf8.cpp \
- ../../src/common/Text/XmlTree.cpp \
- ../../src/common/Widgets/Bitmap.cpp \
- ../../src/common/Widgets/Box.cpp \
- ../../src/common/Widgets/Button.cpp \
- ../../src/common/Widgets/CheckBox.cpp \
- ../../src/common/Widgets/Combo.cpp \
- ../../src/common/Widgets/Edit.cpp \
- ../../src/common/Widgets/ItemContainer.cpp \
- ../../src/common/Widgets/List.cpp \
- ../../src/common/Widgets/Panel.cpp \
- ../../src/common/Widgets/Popup.cpp \
- ../../src/common/Widgets/Progress.cpp \
- ../../src/common/Widgets/ProgressDlg.cpp \
- ../../src/common/Widgets/RadioGroup.cpp \
- ../../src/common/Widgets/ScrollBar.cpp \
- ../../src/common/Widgets/Slider.cpp \
- ../../src/common/Widgets/Splitter.cpp \
- ../../src/common/Widgets/StatusBar.cpp \
- ../../src/common/Widgets/TableLayout.cpp \
- ../../src/common/Widgets/TabView.cpp \
- ../../src/common/Widgets/TextLabel.cpp \
- ../../src/common/Widgets/ToolBar.cpp \
- ../../src/common/Widgets/Tree.cpp \
- ../../src/linux/General/File.cpp \
- ../../src/linux/General/Mem.cpp \
- ../../src/linux/General/ShowFileProp_Linux.cpp \
- ../../src/linux/Gtk/Gdc2.cpp \
- ../../src/linux/Gtk/LgiWidget.cpp \
- ../../src/linux/Gtk/MemDC.cpp \
- ../../src/linux/Gtk/PrintDC.cpp \
- ../../src/linux/Gtk/ScreenDC.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 \
- ../../../../../../../include/lgi/common/App.h \
- ../../../../../../../include/lgi/common/Array.h \
- ../../../../../../../include/lgi/common/AutoPtr.h \
- ../../../../../../../include/lgi/common/Base64.h \
- ../../../../../../../include/lgi/common/Bitmap.h \
- ../../../../../../../include/lgi/common/Box.h \
- ../../../../../../../include/lgi/common/Button.h \
- ../../../../../../../include/lgi/common/CairoSurface.h \
- ../../../../../../../include/lgi/common/Cancel.h \
- ../../../../../../../include/lgi/common/Capabilities.h \
- ../../../../../../../include/lgi/common/Charset.h \
- ../../../../../../../include/lgi/common/CheckBox.h \
- ../../../../../../../include/lgi/common/ClipBoard.h \
- ../../../../../../../include/lgi/common/Colour.h \
- ../../../../../../../include/lgi/common/ColourSpace.h \
- ../../../../../../../include/lgi/common/Combo.h \
- ../../../../../../../include/lgi/common/Containers.h \
- ../../../../../../../include/lgi/common/Core.h \
- ../../../../../../../include/lgi/common/Css.h \
- ../../../../../../../include/lgi/common/CssTools.h \
- ../../../../../../../include/lgi/common/CurrentTime.h \
- ../../../../../../../include/lgi/common/DataDlg.h \
- ../../../../../../../include/lgi/common/DateTime.h \
- ../../../../../../../include/lgi/common/Dialog.h \
- ../../../../../../../include/lgi/common/DisplayString.h \
- ../../../../../../../include/lgi/common/DocView.h \
- ../../../../../../../include/lgi/common/Dom.h \
- ../../../../../../../include/lgi/common/DomFields.h \
- ../../../../../../../include/lgi/common/DragAndDrop.h \
- ../../../../../../../include/lgi/common/DropFiles.h \
- ../../../../../../../include/lgi/common/Edit.h \
- ../../../../../../../include/lgi/common/Emoji.h \
- ../../../../../../../include/lgi/common/Error.h \
- ../../../../../../../include/lgi/common/EventTargetThread.h \
- ../../../../../../../include/lgi/common/File.h \
- ../../../../../../../include/lgi/common/FileSelect.h \
- ../../../../../../../include/lgi/common/Filter.h \
- ../../../../../../../include/lgi/common/FindReplaceDlg.h \
- ../../../../../../../include/lgi/common/Font.h \
- ../../../../../../../include/lgi/common/FontCache.h \
- ../../../../../../../include/lgi/common/FontSelect.h \
- ../../../../../../../include/lgi/common/Gdc2.h \
- ../../../../../../../include/lgi/common/GdcTools.h \
- ../../../../../../../include/lgi/common/GdiLeak.h \
- ../../../../../../../include/lgi/common/HashTable.h \
- ../../../../../../../include/lgi/common/ImageList.h \
- ../../../../../../../include/lgi/common/Input.h \
- ../../../../../../../include/lgi/common/ItemContainer.h \
- ../../../../../../../include/lgi/common/Json.h \
- ../../../../../../../include/lgi/common/Layout.h \
- ../../../../../../../include/lgi/common/Lgi.h \
- ../../../../../../../include/lgi/common/LgiClasses.h \
- ../../../../../../../include/lgi/common/LgiCommon.h \
- ../../../../../../../include/lgi/common/LgiDefs.h \
- ../../../../../../../include/lgi/common/LgiInc.h \
- ../../../../../../../include/lgi/common/LgiInterfaces.h \
- ../../../../../../../include/lgi/common/LgiMsgs.h \
- ../../../../../../../include/lgi/common/LgiNetInc.h \
- ../../../../../../../include/lgi/common/LgiRes.h \
- ../../../../../../../include/lgi/common/LgiString.h \
- ../../../../../../../include/lgi/common/LgiUiBase.h \
- ../../../../../../../include/lgi/common/Library.h \
- ../../../../../../../include/lgi/common/LibraryUtils.h \
- ../../../../../../../include/lgi/common/List.h \
- ../../../../../../../include/lgi/common/LMallocArray.h \
- ../../../../../../../include/lgi/common/Mail.h \
- ../../../../../../../include/lgi/common/Mem.h \
- ../../../../../../../include/lgi/common/Menu.h \
- ../../../../../../../include/lgi/common/Message.h \
- ../../../../../../../include/lgi/common/Mru.h \
- ../../../../../../../include/lgi/common/Mutex.h \
- ../../../../../../../include/lgi/common/Net.h \
- ../../../../../../../include/lgi/common/NetTools.h \
- ../../../../../../../include/lgi/common/Notifications.h \
- ../../../../../../../include/lgi/common/OptionsFile.h \
- ../../../../../../../include/lgi/common/Palette.h \
- ../../../../../../../include/lgi/common/Panel.h \
- ../../../../../../../include/lgi/common/Password.h \
- ../../../../../../../include/lgi/common/Path.h \
- ../../../../../../../include/lgi/common/PixelRops.h \
- ../../../../../../../include/lgi/common/Point.h \
- ../../../../../../../include/lgi/common/Popup.h \
- ../../../../../../../include/lgi/common/PopupNotification.h \
- ../../../../../../../include/lgi/common/Printer.h \
- ../../../../../../../include/lgi/common/Profile.h \
- ../../../../../../../include/lgi/common/Progress.h \
- ../../../../../../../include/lgi/common/ProgressDlg.h \
- ../../../../../../../include/lgi/common/ProgressView.h \
- ../../../../../../../include/lgi/common/Properties.h \
- ../../../../../../../include/lgi/common/RadioGroup.h \
- ../../../../../../../include/lgi/common/Range.h \
- ../../../../../../../include/lgi/common/Rect.h \
- ../../../../../../../include/lgi/common/RefCount.h \
- ../../../../../../../include/lgi/common/RegKey.h \
- ../../../../../../../include/lgi/common/Res.h \
- ../../../../../../../include/lgi/common/Rops.h \
- ../../../../../../../include/lgi/common/ScrollBar.h \
- ../../../../../../../include/lgi/common/SkinEngine.h \
- ../../../../../../../include/lgi/common/Slider.h \
- ../../../../../../../include/lgi/common/Splitter.h \
- ../../../../../../../include/lgi/common/StatusBar.h \
- ../../../../../../../include/lgi/common/Stream.h \
- ../../../../../../../include/lgi/common/StringClass.h \
- ../../../../../../../include/lgi/common/StringLayout.h \
- ../../../../../../../include/lgi/common/StructuredIo.h \
- ../../../../../../../include/lgi/common/SubProcess.h \
- ../../../../../../../include/lgi/common/TableLayout.h \
- ../../../../../../../include/lgi/common/TabView.h \
- ../../../../../../../include/lgi/common/TextLabel.h \
- ../../../../../../../include/lgi/common/TextLog.h \
- ../../../../../../../include/lgi/common/TextView3.h \
- ../../../../../../../include/lgi/common/Thread.h \
- ../../../../../../../include/lgi/common/ThreadEvent.h \
- ../../../../../../../include/lgi/common/Token.h \
- ../../../../../../../include/lgi/common/ToolBar.h \
- ../../../../../../../include/lgi/common/ToolTip.h \
- ../../../../../../../include/lgi/common/TrayIcon.h \
- ../../../../../../../include/lgi/common/Tree.h \
- ../../../../../../../include/lgi/common/Undo.h \
- ../../../../../../../include/lgi/common/Unicode.h \
- ../../../../../../../include/lgi/common/UnicodeString.h \
- ../../../../../../../include/lgi/common/UnrolledList.h \
- ../../../../../../../include/lgi/common/Variant.h \
- ../../../../../../../include/lgi/common/View.h \
- ../../../../../../../include/lgi/common/Widgets.h \
- ../../../../../../../include/lgi/common/Window.h \
- ../../../../../../../include/lgi/common/XmlTree.h \
- ../../../../../../../include/lgi/linux/Gtk/LgiWidget.h \
- ../../../../../../../include/lgi/linux/Gtk/LgiWinManGlue.h \
- ../../../../../../../include/lgi/linux/SymLookup.h \
- ../../../../../../../include/lgi/mac/cocoa/LCocoaView.h \
- ../../../../../../../include/lgi/mac/cocoa/LgiOsClasses.h \
- ../../../../../../../include/lgi/mac/cocoa/LgiOsDefs.h \
- ../../../../../../../include/lgi/mac/cocoa/ObjCWrapper.h \
- ../../../../../../../private/common/FontPriv.h \
- ../../../../../../../private/common/ViewPriv.h \
- ../../../../../../../private/linux/AppPriv.h \
- ../../../../../../../src/common/Gdc2/RopsCases.cpp \
- ../../../../../../../src/common/Hash/md5/md5.h \
- ../../../../../../../src/common/Hash/sha1/sha1.h
+../$(BuildDir)/liblgi-gtk3$(Tag).so : ../include/lgi/common/App.h \
+ ../include/lgi/common/Array.h \
+ ../include/lgi/common/AutoPtr.h \
+ ../include/lgi/common/Base64.h \
+ ../include/lgi/common/Bitmap.h \
+ ../include/lgi/common/Box.h \
+ ../include/lgi/common/Button.h \
+ ../include/lgi/common/CairoSurface.h \
+ ../include/lgi/common/Cancel.h \
+ ../include/lgi/common/Capabilities.h \
+ ../include/lgi/common/Charset.h \
+ ../include/lgi/common/CheckBox.h \
+ ../include/lgi/common/ClipBoard.h \
+ ../include/lgi/common/Colour.h \
+ ../include/lgi/common/ColourSpace.h \
+ ../include/lgi/common/Com.h \
+ ../include/lgi/common/Combo.h \
+ ../include/lgi/common/Containers.h \
+ ../include/lgi/common/Core.h \
+ ../include/lgi/common/Css.h \
+ ../include/lgi/common/CssTools.h \
+ ../include/lgi/common/CurrentTime.h \
+ ../include/lgi/common/DataDlg.h \
+ ../include/lgi/common/DateTime.h \
+ ../include/lgi/common/Dialog.h \
+ ../include/lgi/common/DisplayString.h \
+ ../include/lgi/common/DocView.h \
+ ../include/lgi/common/Dom.h \
+ ../include/lgi/common/DomFields.h \
+ ../include/lgi/common/DragAndDrop.h \
+ ../include/lgi/common/DropFiles.h \
+ ../include/lgi/common/Edit.h \
+ ../include/lgi/common/Emoji.h \
+ ../include/lgi/common/Error.h \
+ ../include/lgi/common/EventTargetThread.h \
+ ../include/lgi/common/File.h \
+ ../include/lgi/common/FileSelect.h \
+ ../include/lgi/common/Filter.h \
+ ../include/lgi/common/FindReplaceDlg.h \
+ ../include/lgi/common/Font.h \
+ ../include/lgi/common/FontCache.h \
+ ../include/lgi/common/FontSelect.h \
+ ../include/lgi/common/Gdc2.h \
+ ../include/lgi/common/GdcTools.h \
+ ../include/lgi/common/GdiLeak.h \
+ ../include/lgi/common/HashTable.h \
+ ../include/lgi/common/ImageList.h \
+ ../include/lgi/common/Input.h \
+ ../include/lgi/common/ItemContainer.h \
+ ../include/lgi/common/Json.h \
+ ../include/lgi/common/Layout.h \
+ ../include/lgi/common/Lgi.h \
+ ../include/lgi/common/LgiClasses.h \
+ ../include/lgi/common/LgiCommon.h \
+ ../include/lgi/common/LgiDefs.h \
+ ../include/lgi/common/LgiInc.h \
+ ../include/lgi/common/LgiInterfaces.h \
+ ../include/lgi/common/LgiMsgs.h \
+ ../include/lgi/common/LgiRes.h \
+ ../include/lgi/common/LgiString.h \
+ ../include/lgi/common/LgiUiBase.h \
+ ../include/lgi/common/Library.h \
+ ../include/lgi/common/LibraryUtils.h \
+ ../include/lgi/common/List.h \
+ ../include/lgi/common/ListItemCheckBox.h \
+ ../include/lgi/common/ListItemRadioBtn.h \
+ ../include/lgi/common/LMallocArray.h \
+ ../include/lgi/common/Mail.h \
+ ../include/lgi/common/Matrix.h \
+ ../include/lgi/common/Mem.h \
+ ../include/lgi/common/Menu.h \
+ ../include/lgi/common/Message.h \
+ ../include/lgi/common/Mime.h \
+ ../include/lgi/common/Mru.h \
+ ../include/lgi/common/Mutex.h \
+ ../include/lgi/common/Net.h \
+ ../include/lgi/common/Notifications.h \
+ ../include/lgi/common/OAuth2.h \
+ ../include/lgi/common/OptionsFile.h \
+ ../include/lgi/common/Palette.h \
+ ../include/lgi/common/Panel.h \
+ ../include/lgi/common/Password.h \
+ ../include/lgi/common/Path.h \
+ ../include/lgi/common/PixelRops.h \
+ ../include/lgi/common/Point.h \
+ ../include/lgi/common/Popup.h \
+ ../include/lgi/common/PopupList.h \
+ ../include/lgi/common/PopupNotification.h \
+ ../include/lgi/common/Printer.h \
+ ../include/lgi/common/Profile.h \
+ ../include/lgi/common/Progress.h \
+ ../include/lgi/common/ProgressDlg.h \
+ ../include/lgi/common/ProgressView.h \
+ ../include/lgi/common/Properties.h \
+ ../include/lgi/common/RadioGroup.h \
+ ../include/lgi/common/Range.h \
+ ../include/lgi/common/Rect.h \
+ ../include/lgi/common/RectF.h \
+ ../include/lgi/common/RefCount.h \
+ ../include/lgi/common/RegKey.h \
+ ../include/lgi/common/Res.h \
+ ../include/lgi/common/Rops.h \
+ ../include/lgi/common/ScrollBar.h \
+ ../include/lgi/common/SkinEngine.h \
+ ../include/lgi/common/Slider.h \
+ ../include/lgi/common/Splitter.h \
+ ../include/lgi/common/StatusBar.h \
+ ../include/lgi/common/Store3Defs.h \
+ ../include/lgi/common/Stream.h \
+ ../include/lgi/common/StringClass.h \
+ ../include/lgi/common/StringLayout.h \
+ ../include/lgi/common/StructuredIo.h \
+ ../include/lgi/common/StructuredLog.h \
+ ../include/lgi/common/SubProcess.h \
+ ../include/lgi/common/TableLayout.h \
+ ../include/lgi/common/TabView.h \
+ ../include/lgi/common/TextFile.h \
+ ../include/lgi/common/TextLabel.h \
+ ../include/lgi/common/TextLog.h \
+ ../include/lgi/common/TextView3.h \
+ ../include/lgi/common/Thread.h \
+ ../include/lgi/common/ThreadEvent.h \
+ ../include/lgi/common/Token.h \
+ ../include/lgi/common/ToolBar.h \
+ ../include/lgi/common/ToolTip.h \
+ ../include/lgi/common/TrayIcon.h \
+ ../include/lgi/common/Tree.h \
+ ../include/lgi/common/Undo.h \
+ ../include/lgi/common/Unicode.h \
+ ../include/lgi/common/UnicodeString.h \
+ ../include/lgi/common/UnrolledList.h \
+ ../include/lgi/common/Uri.h \
+ ../include/lgi/common/Variant.h \
+ ../include/lgi/common/View.h \
+ ../include/lgi/common/Widgets.h \
+ ../include/lgi/common/Window.h \
+ ../include/lgi/common/XmlTree.h \
+ ../include/lgi/linux/Gtk/LgiOsClasses.h \
+ ../include/lgi/linux/Gtk/LgiOsDefs.h \
+ ../include/lgi/linux/Gtk/LgiWidget.h \
+ ../include/lgi/linux/Gtk/LgiWinManGlue.h \
+ ../include/lgi/linux/SymLookup.h \
+ ../include/lgi/mac/cocoa/LCocoaView.h \
+ ../include/lgi/mac/cocoa/LgiMac.h \
+ ../include/lgi/mac/cocoa/LgiOs.h \
+ ../include/lgi/mac/cocoa/LgiOsClasses.h \
+ ../include/lgi/mac/cocoa/LgiOsDefs.h \
+ ../include/lgi/mac/cocoa/ObjCWrapper.h \
+ ../include/lgi/mac/cocoa/SymLookup.h \
+ ../private/common/FontPriv.h \
+ ../private/common/ViewPriv.h \
+ ../private/linux/AppPriv.h \
+ ../src/common/Gdc2/15Bit.cpp \
+ ../src/common/Gdc2/16Bit.cpp \
+ ../src/common/Gdc2/24Bit.cpp \
+ ../src/common/Gdc2/32Bit.cpp \
+ ../src/common/Gdc2/8Bit.cpp \
+ ../src/common/Gdc2/Alpha.cpp \
+ ../src/common/Gdc2/Colour.cpp \
+ ../src/common/Gdc2/Filters/Filter.cpp \
+ ../src/common/Gdc2/Font/Charset.cpp \
+ ../src/common/Gdc2/Font/DisplayString.cpp \
+ ../src/common/Gdc2/Font/Font.cpp \
+ ../src/common/Gdc2/Font/FontSystem.cpp \
+ ../src/common/Gdc2/Font/FontType.cpp \
+ ../src/common/Gdc2/Font/StringLayout.cpp \
+ ../src/common/Gdc2/Font/TypeFace.cpp \
+ ../src/common/Gdc2/GdcCommon.cpp \
+ ../src/common/Gdc2/Path/Path.cpp \
+ ../src/common/Gdc2/Rect.cpp \
+ ../src/common/Gdc2/RopsCases.cpp \
+ ../src/common/Gdc2/Surface.cpp \
+ ../src/common/Gdc2/Tools/ColourReduce.cpp \
+ ../src/common/Gdc2/Tools/GdcTools.cpp \
+ ../src/common/General/Containers.cpp \
+ ../src/common/General/DateTime.cpp \
+ ../src/common/General/ExeCheck.cpp \
+ ../src/common/General/FileCommon.cpp \
+ ../src/common/General/Password.cpp \
+ ../src/common/General/Properties.cpp \
+ ../src/common/Hash/md5/md5.c \
+ ../src/common/Hash/md5/md5.h \
+ ../src/common/Hash/sha1/sha1.c \
+ ../src/common/Hash/sha1/sha1.h \
+ ../src/common/Lgi/Alert.cpp \
+ ../src/common/Lgi/AppCommon.cpp \
+ ../src/common/Lgi/Css.cpp \
+ ../src/common/Lgi/CssTools.cpp \
+ ../src/common/Lgi/DataDlg.cpp \
+ ../src/common/Lgi/DragAndDropCommon.cpp \
+ ../src/common/Lgi/FileSelect.cpp \
+ ../src/common/Lgi/FindReplace.cpp \
+ ../src/common/Lgi/FontSelect.cpp \
+ ../src/common/Lgi/GuiUtils.cpp \
+ ../src/common/Lgi/Input.cpp \
+ ../src/common/Lgi/LgiCommon.cpp \
+ ../src/common/Lgi/Library.cpp \
+ ../src/common/Lgi/LMsg.cpp \
+ ../src/common/Lgi/MemStream.cpp \
+ ../src/common/Lgi/MenuCommon.cpp \
+ ../src/common/Lgi/Mru.cpp \
+ ../src/common/Lgi/Mutex.cpp \
+ ../src/common/Lgi/Object.cpp \
+ ../src/common/Lgi/OptionsFile.cpp \
+ ../src/common/Lgi/Rand.cpp \
+ ../src/common/Lgi/Stream.cpp \
+ ../src/common/Lgi/SubProcess.cpp \
+ ../src/common/Lgi/ThreadCommon.cpp \
+ ../src/common/Lgi/ThreadEvent.cpp \
+ ../src/common/Lgi/ToolTip.cpp \
+ ../src/common/Lgi/TrayIcon.cpp \
+ ../src/common/Lgi/Variant.cpp \
+ ../src/common/Lgi/ViewCommon.cpp \
+ ../src/common/Lgi/WindowCommon.cpp \
+ ../src/common/Net/Base64.cpp \
+ ../src/common/Net/MDStringToDigest.cpp \
+ ../src/common/Net/Net.cpp \
+ ../src/common/Net/Uri.cpp \
+ ../src/common/Resource/LgiRes.cpp \
+ ../src/common/Resource/Res.cpp \
+ ../src/common/Skins/Gel/Gel.cpp \
+ ../src/common/Text/DocView.cpp \
+ ../src/common/Text/String.cpp \
+ ../src/common/Text/TextView3.cpp \
+ ../src/common/Text/Token.cpp \
+ ../src/common/Text/Unicode.cpp \
+ ../src/common/Text/Utf8.cpp \
+ ../src/common/Text/XmlTree.cpp \
+ ../src/common/Widgets/Bitmap.cpp \
+ ../src/common/Widgets/Box.cpp \
+ ../src/common/Widgets/Button.cpp \
+ ../src/common/Widgets/CheckBox.cpp \
+ ../src/common/Widgets/Combo.cpp \
+ ../src/common/Widgets/Edit.cpp \
+ ../src/common/Widgets/ItemContainer.cpp \
+ ../src/common/Widgets/List.cpp \
+ ../src/common/Widgets/Panel.cpp \
+ ../src/common/Widgets/Popup.cpp \
+ ../src/common/Widgets/Progress.cpp \
+ ../src/common/Widgets/ProgressDlg.cpp \
+ ../src/common/Widgets/RadioGroup.cpp \
+ ../src/common/Widgets/ScrollBar.cpp \
+ ../src/common/Widgets/Slider.cpp \
+ ../src/common/Widgets/Splitter.cpp \
+ ../src/common/Widgets/StatusBar.cpp \
+ ../src/common/Widgets/TableLayout.cpp \
+ ../src/common/Widgets/TabView.cpp \
+ ../src/common/Widgets/TextLabel.cpp \
+ ../src/common/Widgets/ToolBar.cpp \
+ ../src/common/Widgets/Tree.cpp \
+ ../src/linux/General/File.cpp \
+ ../src/linux/General/Mem.cpp \
+ ../src/linux/General/ShowFileProp_Linux.cpp \
+ ../src/linux/Gtk/Gdc2.cpp \
+ ../src/linux/Gtk/LgiWidget.cpp \
+ ../src/linux/Gtk/MemDC.cpp \
+ ../src/linux/Gtk/PrintDC.cpp \
+ ../src/linux/Gtk/ScreenDC.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
export Build=$(Build); \
- $(MAKE) -C ../../ -f Makefile.linux
+ $(MAKE) -C ../ -f ./linux/Makefile.linux
-include $(Objects:.o=.d)
# Clean just this target
clean :
rm -rf $(BuildDir) $(Target)
@echo Cleaned $(BuildDir) $(Target)
# Clean all targets
cleanall :
rm -rf $(BuildDir) $(Target)
@echo Cleaned $(BuildDir) $(Target)
- +make -C "../../" -f "Makefile.linux" clean
+ +make -C "../" -f ./linux/Makefile.linux clean
VPATH=$(BuildDir) \
- ../../../../../../../src \
- ../../src/common/Lgi \
- ../../src/common/Gdc2/Filters
+ ../src/common/Lgi \
+ ../src/common/Gdc2/Filters \
+ ./src
diff --git a/include/lgi/common/RichTextEdit.h b/include/lgi/common/RichTextEdit.h
--- a/include/lgi/common/RichTextEdit.h
+++ b/include/lgi/common/RichTextEdit.h
@@ -1,238 +1,238 @@
/// \file
/// \author Matthew Allen
/// \brief A unicode text editor
#ifndef _RICH_TEXT_EDIT_H_
#define _RICH_TEXT_EDIT_H_
#include "lgi/common/DocView.h"
#include "lgi/common/Undo.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Capabilities.h"
#include "lgi/common/FindReplaceDlg.h"
#if _DEBUG
#include "lgi/common/Tree.h"
#endif
enum RichEditMsgs
{
M_BLOCK_MSG = M_USER + 0x1000,
M_IMAGE_LOAD_FILE,
M_IMAGE_SET_SURFACE,
M_IMAGE_ERROR,
M_IMAGE_COMPONENT_MISSING,
M_IMAGE_PROGRESS,
M_IMAGE_RESAMPLE,
M_IMAGE_FINISHED,
M_IMAGE_COMPRESS,
M_IMAGE_ROTATE,
M_IMAGE_FLIP,
M_IMAGE_LOAD_STREAM,
M_COMPONENT_INSTALLED, // A = LString *ComponentName
};
extern char Delimiters[];
/// Styled unicode text editor control.
class
#if defined(MAC)
LgiClass
#endif
LRichTextEdit :
public LDocView,
public ResObject,
public LDragDropTarget,
public LCapabilityClient
{
friend bool RichText_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User);
public:
- enum GTextViewSeek
+ enum LTextViewSeek
{
PrevLine,
NextLine,
StartLine,
EndLine
};
protected:
class LRichTextPriv *d;
friend class LRichTextPriv;
bool IndexAt(int x, int y, ssize_t &Off, int &LineHint);
// Overridables
virtual void PourText(ssize_t Start, ssize_t Length);
virtual void PourStyle(ssize_t Start, ssize_t Length);
virtual void OnFontChange();
virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour);
public:
// Construction
LRichTextEdit( int Id,
int x = 0,
int y = 0,
int cx = 100,
int cy = 100,
LFontType *FontInfo = 0);
~LRichTextEdit();
const char *GetClass() { return "LRichTextEdit"; }
// Data
const char *Name();
bool Name(const char *s);
const char16 *NameW();
bool NameW(const char16 *s);
int64 Value();
void Value(int64 i);
const char *GetMimeType() { return "text/html"; }
int GetSize();
const char *GetCharset();
void SetCharset(const char *s);
ssize_t HitTest(int x, int y);
bool DeleteSelection(char16 **Cut = 0);
bool SetSpellCheck(class LSpellCheck *sp);
bool GetFormattedContent(const char *MimeType, LString &Out, LArray *Media = NULL);
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
// Font
LFont *GetFont();
void SetFont(LFont *f, bool OwnIt = false);
void SetFixedWidthFont(bool i);
// Options
void SetTabSize(uint8_t i);
void SetReadOnly(bool i);
bool ShowStyleTools();
void ShowStyleTools(bool b);
enum RectType
{
ContentArea,
ToolsArea,
// CapabilityArea,
// CapabilityBtn,
FontFamilyBtn,
FontSizeBtn,
BoldBtn,
ItalicBtn,
UnderlineBtn,
ForegroundColourBtn,
BackgroundColourBtn,
MakeLinkBtn,
RemoveLinkBtn,
RemoveStyleBtn,
EmojiBtn,
HorzRuleBtn,
MaxArea
};
LRect GetArea(RectType Type);
/// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW
void SetWrapType(LDocWrapType i);
// State / Selection
void SetCursor(int i, bool Select, bool ForceFullUpdate = false);
ssize_t IndexAt(int x, int y);
bool IsDirty();
void IsDirty(bool d);
bool HasSelection();
void UnSelectAll();
void SelectWord(size_t From);
void SelectAll();
ssize_t GetCaret(bool Cursor = true);
bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1);
size_t GetLines();
void GetTextExtent(int &x, int &y);
char *GetSelection();
void SetStylePrefix(LString s);
bool IsBusy(bool Stop = false);
// File IO
bool Open(const char *Name, const char *Cs = 0);
bool Save(const char *Name, const char *Cs = 0);
// Clipboard IO
bool Cut();
bool Copy();
bool Paste();
// Undo/Redo
void Undo();
void Redo();
bool GetUndoOn();
void SetUndoOn(bool b);
// Action UI
virtual void DoGoto(std::function Callback);
virtual void DoCase(std::function Callback, bool Upper);
virtual void DoFind(std::function Callback);
virtual void DoFindNext(std::function Callback);
virtual void DoReplace(std::function Callback);
// Action Processing
bool ClearDirty(bool Ask, const char *FileName = 0);
void UpdateScrollBars(bool Reset = false);
int GetLine();
void SetLine(int Line);
LDocFindReplaceParams *CreateFindReplaceParams();
void SetFindReplaceParams(LDocFindReplaceParams *Params);
void OnAddStyle(const char *MimeType, const char *Styles);
// Object Events
bool OnFind(LFindReplaceCommon *Params);
bool OnReplace(LFindReplaceCommon *Params);
bool OnMultiLineTab(bool In);
void OnSetHidden(int Hidden);
void OnPosChange();
void OnCreate();
void OnEscape(LKey &K);
bool OnMouseWheel(double Lines);
// Capability target stuff
// bool NeedsCapability(const char *Name, const char *Param = NULL);
// void OnInstall(CapsHash *Caps, bool Status);
// void OnCloseInstaller();
// Window Events
void OnFocus(bool f);
void OnMouseClick(LMouse &m);
void OnMouseMove(LMouse &m);
bool OnKey(LKey &k);
void OnPaint(LSurface *pDC);
LMessage::Result OnEvent(LMessage *Msg);
int OnNotify(LViewI *Ctrl, LNotification n);
void OnPulse();
int OnHitTest(int x, int y);
bool OnLayout(LViewLayoutInfo &Inf);
// D'n'd target
int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState);
int OnDrop(LArray &Data, LPoint Pt, int KeyState);
// Virtuals
virtual bool Insert(int At, char16 *Data, int Len);
virtual bool Delete(int At, int Len);
virtual void OnEnter(LKey &k);
virtual void OnUrl(char *Url);
virtual void DoContextMenu(LMouse &m);
#if _DEBUG
void DumpNodes(LTree *Root);
void SelectNode(LString Param);
#endif
};
#endif
diff --git a/include/lgi/common/TextLog.h b/include/lgi/common/TextLog.h
--- a/include/lgi/common/TextLog.h
+++ b/include/lgi/common/TextLog.h
@@ -1,169 +1,169 @@
/// \file
/// \author Matthew Allen
#ifndef _LTEXTLOG_H_
#define _LTEXTLOG_H_
#include "lgi/common/TextView3.h"
#include "lgi/common/Net.h"
template
class LThreadSafeTextView : public TView, public LStream
{
protected:
bool ProcessReturns;
size_t Pos;
LMutex Sem;
LArray Txt;
void ProcessTxt()
{
if (Txt.Length() == 0)
return;
LArray Local;
if (Sem.LockWithTimeout(250, _FL))
{
// Minimize time locked by moving the text to a local var
Local.Swap(Txt);
Sem.Unlock();
}
else
{
TView::PostEvent(M_LOG_TEXT); // Try again later...
return;
}
if (Local.Length())
{
LString msg;
msg.Printf("LTextLog::ProcessTxt(%" PRIu64 ")", (uint64)Txt.Length());
LProfile p(msg, 200);
Add(Local.AddressOf(), Local.Length());
}
}
public:
LThreadSafeTextView(int id) : TView(id, 0, 0, 2000, 1000), Sem("LThreadSafeTextView")
{
ProcessReturns = true;
Pos = 0;
TView::Sunken(true);
TView::SetPourLargest(true);
TView::SetUndoOn(false);
TView::SetWrapType(TEXTED_WRAP_NONE);
}
void OnCreate()
{
TView::OnCreate();
// ProcessTxt();
}
void OnPulse()
{
ProcessTxt();
}
virtual void Add(char16 *w, ssize_t chars = -1)
{
ssize_t Len = chars >= 0 ? chars : StrlenW(w);
bool AtEnd = TView::GetCaret() == TView::Size;
if (ProcessReturns)
{
auto *s = w, *prev = w;
auto Ins = [this](char16 *s, ssize_t len)
{
if (len > 0)
{
auto del = MIN(TView::Size - (ssize_t)Pos, len);
if (del > 0)
TView::Delete(Pos, del);
TView::Insert(Pos, s, len);
Pos += len;
}
};
for (s = w;
chars >= 0 ? s < w + chars : *s;
s++)
{
if (*s == '\r')
{
Ins(prev, s - prev);
prev = s + 1;
while (Pos > 0 && TView::Text[Pos-1] != '\n')
Pos--;
}
else if (*s == '\n')
{
Pos = TView::Size;
}
}
Ins(prev, s - prev);
}
else
{
TView::Insert(TView::Size, w, Len);
}
TView::Invalidate();
if (AtEnd)
TView::SetCaret(TView::Size, false);
}
int64 SetSize(int64 s)
{
TView::Name(0);
return 0;
}
bool Write(const LString &s)
{
return Write(s.Get(), s.Length()) == s.Length();
}
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
{
LAutoWString w(Utf8ToWide((char*)Buffer, Size));
if (!w)
return 0;
size_t OldLen = Txt.Length();
if (Sem.Lock(_FL))
{
Txt.Add(w.Get(), Strlen(w.Get()));
Sem.Unlock();
}
if (!OldLen
#if LGI_VIEW_HANDLE
&& TView::Handle()
#endif
)
{
TView::PostEvent(M_LOG_TEXT);
}
return Size;
}
LMessage::Result OnEvent(LMessage *m)
{
if (m->Msg() == M_LOG_TEXT)
{
ProcessTxt();
}
return TView::OnEvent(m);
}
};
typedef LThreadSafeTextView LTextLog;
-#ifdef _GTEXTVIEW4_H
+#ifdef _LTEXTVIEW4_H
typedef LThreadSafeTextView LTextLog4;
#endif
#endif
diff --git a/include/lgi/common/TextView3.h b/include/lgi/common/TextView3.h
--- a/include/lgi/common/TextView3.h
+++ b/include/lgi/common/TextView3.h
@@ -1,439 +1,439 @@
/// \file
/// \author Matthew Allen
/// \brief A unicode text editor
#ifndef __GTEXTVIEW3_H
#define __GTEXTVIEW3_H
#include
#include "lgi/common/DocView.h"
#include "lgi/common/Undo.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Css.h"
#include "lgi/common/UnrolledList.h"
#include "lgi/common/FindReplaceDlg.h"
// use CRLF as opposed to just LF
// internally it uses LF only... this is just to remember what to
// save out as.
#define TEXTED_USES_CR 0x00000001
#define TAB_SIZE 4
#define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str
extern char Delimiters[];
class LTextView3;
/// Unicode text editor control.
class LgiClass
LTextView3 :
public LDocView,
public ResObject,
public LDragDropTarget
{
friend struct LTextView3Undo;
friend bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User);
public:
enum Messages
{
M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421,
M_TEXTVIEW_FIND,
M_TEXTVIEW_REPLACE,
M_TEXT_POUR_CONTINUE,
};
enum StyleOwners
{
STYLE_NONE,
STYLE_IDE,
STYLE_SPELLING,
STYLE_FIND_MATCHES,
STYLE_ADDRESS,
STYLE_URL,
};
class LStyle
{
protected:
void RefreshLayout(size_t Start, ssize_t Len);
public:
/// The view the style is for
LTextView3 *View;
/// When you write several bits of code to do styling assign them
/// different owner id's so that they can manage the lifespan of their
/// own styles. LTextView3::PourStyle is owner '0', anything else it
/// will leave alone.
StyleOwners Owner;
/// The start index into the text buffer of the region to style.
ssize_t Start;
/// The length of the styled region
ssize_t Len;
/// The font to draw the styled text in
LFont *Font;
/// The colour to draw with. If transparent, then the default
/// line colour is used.
LColour Fore, Back;
/// Cursor
LCursor Cursor;
/// Optional extra decor not supported by the fonts
LCss::TextDecorType Decor;
/// Colour for the optional decor.
LColour DecorColour;
/// Application base data
LVariant Data;
LStyle(StyleOwners owner = STYLE_NONE)
{
Owner = owner;
View = NULL;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
}
LStyle(const LStyle &s)
{
Owner = s.Owner;
View = s.View;
Font = s.Font;
Start = s.Start;
Len = s.Len;
Decor = s.Decor;
DecorColour = s.DecorColour;
Fore = s.Fore;
Back = s.Back;
Data = s.Data;
Cursor = s.Cursor;
}
LStyle &Construct(LTextView3 *view, StyleOwners owner)
{
View = view;
Owner = owner;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
return *this;
}
void Empty()
{
Start = -1;
Len = 0;
}
bool Valid()
{
return Start >= 0 && Len > 0;
}
size_t End() const { return Start + Len; }
/// \returns true if style is the same
bool operator ==(const LStyle &s)
{
return Owner == s.Owner &&
Start == s.Start &&
Len == s.Len &&
Fore == s.Fore &&
Back == s.Back &&
Decor == s.Decor;
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(LStyle &s)
{
return Overlap(s.Start, s.Len);
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(ssize_t sStart, ssize_t sLen)
{
if (sStart + sLen - 1 < Start ||
sStart >= Start + Len)
return false;
return true;
}
void Union(const LStyle &s)
{
if (Start < 0)
{
Start = s.Start;
Len = s.Len;
}
else
{
Start = MIN(Start, s.Start);
Len = MAX(End(), s.End()) - Start;
}
}
};
friend class LTextView3::LStyle;
protected:
// Internal classes
- enum GTextViewSeek
+ enum LTextViewSeek
{
PrevLine,
NextLine,
StartLine,
EndLine
};
class LTextLine : public LRange
{
public:
LRect r; // Screen location
LColour c; // Colour of line... transparent = default colour
LColour Back; // Background colour or transparent
LTextLine()
{
Start = -1;
Len = 0;
r.ZOff(-1, -1);
}
virtual ~LTextLine() {}
bool Overlap(ssize_t i)
{
return i>=Start && i<=Start+Len;
}
size_t CalcLen(char16 *Text)
{
char16 *c = Text + Start, *e = c;
while (*e && *e != '\n')
e++;
return Len = e - c;
}
};
class LTextView3Private *d;
friend class LTextView3Private;
// Options
bool Dirty = false;
bool CanScrollX = false;
// Display
LFont *Font = NULL;
LFont *Bold = NULL; // Bold variant of 'Font'
LFont *Underline = NULL; // Underline variant of 'Font'
LFont *FixedFont = NULL;
int LineY = 1;
ssize_t SelStart = -1, SelEnd = -1;
int DocOffset = 0;
int MaxX = 0;
bool Blink = true;
uint64 BlinkTs = 0;
int ScrollX = 0;
LRect CursorPos;
/// true if the text pour process is still ongoing
bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots
// of related edits at the same time. And then manually pour once
// finished.
bool PartialPour = false; // True if the pour is happening in the background. It's not threaded
// but taking place in the GUI thread via timer.
size_t PartialPourLines = 0;// Partial pour max lines, if we restart this tracks the max we saw...
bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true)
List Line;
LUnrolledList Style; // sorted in 'Start' order
typedef LUnrolledList::Iter StyleIter;
// For ::Name(...)
char *TextCache = NULL;
// Data
char16 *Text;
ssize_t Cursor;
ssize_t Size;
ssize_t Alloc;
// Undo stuff
bool UndoOn = true;
LUndo UndoQue;
struct LTextView3Undo *UndoCur = NULL;
// private methods
List::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0);
LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { return *GetTextLineIt(Offset, Index); }
- ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where);
+ ssize_t SeekLine(ssize_t Offset, LTextViewSeek Where);
int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin);
bool ScrollToOffset(size_t Off);
int ScrollYLine();
int ScrollYPixel();
LRect DocToScreen(LRect r);
ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards);
void InternalPulse();
// styles
bool InsertStyle(LAutoPtr s);
LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1);
LStyle *HitStyle(ssize_t i);
int GetColumn();
int SpaceDepth(char16 *Start, char16 *End);
int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false);
// Overridables
virtual void PourText(size_t Start, ssize_t Length);
virtual void PourStyle(size_t Start, ssize_t Length);
virtual void OnFontChange();
virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour);
virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false);
#ifdef _DEBUG
// debug
uint64 _PourTime;
uint64 _StyleTime;
uint64 _PaintTime;
#endif
void LogLines();
bool ValidateLines(bool CheckBox = false);
public:
// Construction
LTextView3( int Id,
int x = 0, int y = 0,
int cx = 100, int cy = 100,
LFontType *FontInfo = NULL);
~LTextView3();
const char *GetClass() override { return "LTextView3"; }
// Data
const char *Name() override;
bool Name(const char *s) override;
const char16 *NameW() override;
bool NameW(const char16 *s) override;
int64 Value() override;
void Value(int64 i) override;
const char *GetMimeType() override { return "text/plain"; }
size_t Length() { return Size; }
LString operator[](ssize_t LineIdx);
const char16 *TextAtLine(size_t Index);
ssize_t HitText(int x, int y, bool Nearest);
void DeleteSelection(char16 **Cut = 0);
// Font
LFont *GetFont() override;
LFont *GetBold();
void SetFont(LFont *f, bool OwnIt = false) override;
void SetFixedWidthFont(bool i) override;
// Options
void SetTabSize(uint8_t i) override;
void SetBorder(int b);
void SetReadOnly(bool i) override;
void SetCrLf(bool crlf) override;
/// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW
void SetWrapType(LDocWrapType i) override;
// State / Selection
ssize_t GetCaret(bool Cursor = true) override;
virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override;
ssize_t IndexAt(int x, int y) override;
bool IsDirty() override { return Dirty; }
void IsDirty(bool d) { Dirty = d; }
bool HasSelection() override;
void UnSelectAll() override;
void SelectWord(size_t From) override;
void SelectAll() override;
bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override;
size_t GetLines() override;
void GetTextExtent(int &x, int &y) override;
char *GetSelection() override;
LRange GetSelectionRange();
// File IO
bool Open(const char *Name, const char *Cs = NULL) override;
bool Save(const char *Name, const char *Cs = NULL) override;
const char *GetLastError();
// Clipboard IO
bool Cut() override;
bool Copy() override;
bool Paste() override;
// Undo/Redo
void Undo();
void Redo();
bool GetUndoOn() { return UndoOn; }
void SetUndoOn(bool b) { UndoOn = b; }
// Action UI
virtual void DoGoto(std::function Callback);
virtual void DoCase(std::function Callback, bool Upper);
virtual void DoFind(std::function Callback) override;
virtual void DoFindNext(std::function Callback);
virtual void DoReplace(std::function Callback) override;
// Action Processing
void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0);
void UpdateScrollBars(bool Reset = false);
ssize_t GetLine();
void SetLine(int64_t Line, bool select = false);
LDocFindReplaceParams *CreateFindReplaceParams() override;
void SetFindReplaceParams(LDocFindReplaceParams *Params) override;
// Object Events
virtual bool OnFind( const char16 *Find,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
virtual bool OnReplace( const char16 *Find,
const char16 *Replace,
bool All,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
bool OnMultiLineTab(bool In);
void OnSetHidden(int Hidden);
void OnPosChange() override;
void OnCreate() override;
void OnEscape(LKey &K) override;
bool OnMouseWheel(double Lines) override;
// Window Events
void OnFocus(bool f) override;
void OnMouseClick(LMouse &m) override;
void OnMouseMove(LMouse &m) override;
bool OnKey(LKey &k) override;
void OnPaint(LSurface *pDC) override;
LMessage::Result OnEvent(LMessage *Msg) override;
int OnNotify(LViewI *Ctrl, LNotification n) override;
void OnPulse() override;
int OnHitTest(int x, int y) override;
bool OnLayout(LViewLayoutInfo &Inf) override;
int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override;
int OnDrop(LArray &Data, LPoint Pt, int KeyState) override;
LCursor GetCursor(int x, int y) override;
// Virtuals
virtual bool Insert(size_t At, const char16 *Data, ssize_t Len);
virtual bool Delete(size_t At, ssize_t Len);
virtual void OnEnter(LKey &k) override;
virtual void OnUrl(char *Url) override;
virtual void DoContextMenu(LMouse &m);
virtual bool OnStyleClick(LStyle *style, LMouse *m);
virtual bool OnStyleMenu(LStyle *style, LSubMenu *m);
virtual void OnStyleMenuClick(LStyle *style, int i);
};
#endif
diff --git a/include/lgi/common/TextView4.h b/include/lgi/common/TextView4.h
--- a/include/lgi/common/TextView4.h
+++ b/include/lgi/common/TextView4.h
@@ -1,444 +1,448 @@
/// \file
/// \author Matthew Allen
/// \brief A unicode text editor
-#ifndef _GTEXTVIEW4_H
-#define _GTEXTVIEW4_H
+#ifndef _LTEXTVIEW4_H
+#define _LTEXTVIEW4_H
#include "lgi/common/DocView.h"
#include "lgi/common/Undo.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/Css.h"
#include "lgi/common/UnrolledList.h"
#include "lgi/common/FindReplaceDlg.h"
// use CRLF as opposed to just LF
// internally it uses LF only... this is just to remember what to
// save out as.
#define TEXTED_USES_CR 0x00000001
#define TAB_SIZE 4
#define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str
extern char Delimiters[];
class LTextView4;
/// Unicode text editor control.
class LgiClass LTextView4 :
public LDocView,
public ResObject,
public LDragDropTarget
{
friend struct LTextView4Undo;
friend bool Text4_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User);
public:
+ constexpr static int ALLOC_BLOCK = 64;
+
enum Messages
{
M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421,
M_TEXTVIEW_FIND,
M_TEXTVIEW_REPLACE,
M_TEXT_POUR_CONTINUE,
};
enum StyleOwners
{
STYLE_NONE,
STYLE_IDE,
STYLE_SPELLING,
STYLE_FIND_MATCHES,
STYLE_ADDRESS,
STYLE_URL,
};
- class LStyle
+ class LStyle : public LRange
{
protected:
void RefreshLayout(size_t Start, ssize_t Len);
public:
/// The view the style is for
- LTextView4 *View;
+ LTextView4 *View = NULL;
/// When you write several bits of code to do styling assign them
/// different owner id's so that they can manage the lifespan of their
/// own styles. LTextView4::PourStyle is owner '0', anything else it
/// will leave alone.
StyleOwners Owner;
- /// The start index into the text buffer of the region to style.
- ssize_t Start;
- /// The length of the styled region
- ssize_t Len;
/// The font to draw the styled text in
- LFont *Font;
+ LFont *Font = NULL;
/// The colour to draw with. If transparent, then the default
/// line colour is used.
LColour Fore, Back;
/// Cursor
LCursor Cursor;
/// Optional extra decor not supported by the fonts
LCss::TextDecorType Decor;
/// Colour for the optional decor.
LColour DecorColour;
/// Application base data
LVariant Data;
LStyle(StyleOwners owner = STYLE_NONE)
{
Owner = owner;
View = NULL;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
}
LStyle(const LStyle &s)
{
Owner = s.Owner;
View = s.View;
Font = s.Font;
Start = s.Start;
Len = s.Len;
Decor = s.Decor;
DecorColour = s.DecorColour;
Fore = s.Fore;
Back = s.Back;
Data = s.Data;
Cursor = s.Cursor;
}
LStyle &Construct(LTextView4 *view, StyleOwners owner)
{
View = view;
Owner = owner;
Font = NULL;
Empty();
Cursor = LCUR_Normal;
Decor = LCss::TextDecorNone;
return *this;
}
void Empty()
{
Start = -1;
Len = 0;
}
bool Valid()
{
return Start >= 0 && Len > 0;
}
size_t End() const { return Start + Len; }
/// \returns true if style is the same
bool operator ==(const LStyle &s)
{
return Owner == s.Owner &&
Start == s.Start &&
Len == s.Len &&
Fore == s.Fore &&
Back == s.Back &&
Decor == s.Decor;
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(LStyle &s)
{
return Overlap(s.Start, s.Len);
}
/// Returns true if this style overlaps the position of 's'
bool Overlap(ssize_t sStart, ssize_t sLen)
{
if (sStart + sLen - 1 < Start ||
sStart >= Start + Len)
return false;
return true;
}
void Union(const LStyle &s)
{
if (Start < 0)
{
Start = s.Start;
Len = s.Len;
}
else
{
Start = MIN(Start, s.Start);
Len = MAX(End(), s.End()) - Start;
}
}
};
friend class LTextView4::LStyle;
protected:
// Internal classes
- enum GTextViewSeek
+ enum LTextViewSeek
{
PrevLine,
NextLine,
StartLine,
EndLine
};
class LTextLine : public LRange
{
public:
LRect r; // Screen location
LColour c; // Colour of line... transparent = default colour
LColour Back; // Background colour or transparent
bool NewLine = false;
+ const char *File = NULL;
+ int Line = -1;
- LTextLine()
+ LTextLine(const char *file, int line)
{
+ File = file;
+ Line = line;
Start = -1;
r.ZOff(-1, -1);
}
virtual ~LTextLine() {}
size_t CalcLen(char16 *Text)
{
- char16 *c = Text + Start, *e = c;
+ char16 *c = Text + Start, *e = c;
while (*e && *e != '\n')
e++;
+ NewLine = *e == '\n';
+
return Len = e - c;
}
bool Overlap(ssize_t Val)
{
return (Val == Start) || (Val >= Start && Val < (End() + NewLine));
}
ssize_t EndNewLine()
{
return End() + NewLine;
}
};
class LTextView4Private *d;
friend class LTextView4Private;
// Options
- bool Dirty;
- bool CanScrollX;
+ bool Dirty = false;
+ bool CanScrollX = false;
// Display
- LFont *Font;
- LFont *Bold; // Bold variant of 'Font'
- LFont *Underline; // Underline variant of 'Font'
+ LFont *Font = NULL;
+ LFont *Bold = NULL; // Bold variant of 'Font'
+ LFont *Underline = NULL; // Underline variant of 'Font'
- LFont *FixedFont;
- int LineY;
- ssize_t SelStart, SelEnd;
- int DocOffset;
- int MaxX;
- bool Blink;
- uint64 BlinkTs;
- int ScrollX;
+ LFont *FixedFont = NULL;
+ int LineY = -1;
+ ssize_t SelStart = -1, SelEnd = -1;
+ int DocOffset = 0;
+ int MaxX = 0;
+ bool Blink = true;
+ uint64 BlinkTs = 0;
+ int ScrollX = 0;
LRect CursorPos;
/// true if the text pour process is still ongoing
- bool PourEnabled; // True if pouring the text happens on edit. Turn off if doing lots
- // of related edits at the same time. And then manually pour once
- // finished.
- bool PartialPour; // True if the pour is happening in the background. It's not threaded
- // but taking place in the GUI thread via timer.
- bool AdjustStylePos; // Insert/Delete moved styles automatically to match (default: true)
+ bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots
+ // of related edits at the same time. And then manually pour once
+ // finished.
+ bool PartialPour = false; // True if the pour is happening in the background. It's not threaded
+ // but taking place in the GUI thread via timer.
+ bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true)
LArray Line;
LUnrolledList Style; // sorted in 'Start' order
typedef LUnrolledList::Iter StyleIter;
// For ::Name(...)
- char *TextCache;
+ char *TextCache = NULL;
// Data
- char16 *Text;
- ssize_t Cursor;
- ssize_t Size;
- ssize_t Alloc;
+ char16 *Text = NULL;
+ ssize_t Cursor = 0;
+ ssize_t Size = 0;
+ ssize_t Alloc = ALLOC_BLOCK;
// Undo stuff
- bool UndoOn;
+ bool UndoOn = true;
LUndo UndoQue;
- struct LTextView4Undo *UndoCur;
+ struct LTextView4Undo *UndoCur = NULL;
// private methods
LArray::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0);
LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0)
{
auto it = GetTextLineIt(Offset, Index);
return it != Line.end() ? *it : NULL;
}
- ssize_t SeekLine(ssize_t Offset, GTextViewSeek Where);
+ ssize_t SeekLine(ssize_t Offset, LTextViewSeek Where);
int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin);
bool ScrollToOffset(size_t Off);
int ScrollYLine();
int ScrollYPixel();
LRect DocToScreen(LRect r);
ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards);
void InternalPulse();
// styles
bool InsertStyle(LAutoPtr s);
LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1);
LStyle *HitStyle(ssize_t i);
int GetColumn();
int SpaceDepth(char16 *Start, char16 *End);
int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false);
// Overridables
virtual void PourText(size_t Start, ssize_t Length);
virtual void PourStyle(size_t Start, ssize_t Length);
virtual void OnFontChange();
virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour);
virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false);
#ifdef _DEBUG
// debug
- uint64 _PourTime;
- uint64 _StyleTime;
- uint64 _PaintTime;
+ uint64 _PourTime = 0;
+ uint64 _StyleTime = 0;
+ uint64 _PaintTime = 0;
#endif
void LogLines();
bool ValidateLines(bool CheckBox = false);
public:
// Construction
LTextView4( int Id,
int x = 0, int y = 0,
int cx = 100, int cy = 100,
LFontType *FontInfo = NULL);
~LTextView4();
const char *GetClass() override { return "LTextView4"; }
// Data
const char *Name() override;
bool Name(const char *s) override;
const char16 *NameW() override;
bool NameW(const char16 *s) override;
int64 Value() override;
void Value(int64 i) override;
const char *GetMimeType() override { return "text/plain"; }
size_t Length() const { return Size; }
LString operator[](ssize_t LineIdx);
ssize_t HitText(int x, int y, bool Nearest);
void DeleteSelection(char16 **Cut = 0);
// Font
LFont *GetFont() override;
LFont *GetBold();
void SetFont(LFont *f, bool OwnIt = false) override;
void SetFixedWidthFont(bool i) override;
// Options
void SetTabSize(uint8_t i) override;
void SetBorder(int b);
void SetReadOnly(bool i) override;
void SetCrLf(bool crlf) override;
/// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW
void SetWrapType(LDocWrapType i) override;
// State / Selection
ssize_t GetCaret(bool Cursor = true) override;
virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override;
ssize_t IndexAt(int x, int y) override;
bool IsDirty() override { return Dirty; }
void IsDirty(bool d) { Dirty = d; }
bool HasSelection() override;
void UnSelectAll() override;
void SelectWord(size_t From) override;
void SelectAll() override;
bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override;
size_t GetLines() override;
void GetTextExtent(int &x, int &y) override;
char *GetSelection() override;
LRange GetSelectionRange();
// File IO
bool Open(const char *Name, const char *Cs = 0) override;
bool Save(const char *Name, const char *Cs = 0) override;
const char *GetLastError();
// Clipboard IO
bool Cut() override;
bool Copy() override;
bool Paste() override;
// Undo/Redo
void Undo();
void Redo();
bool GetUndoOn() { return UndoOn; }
void SetUndoOn(bool b) { UndoOn = b; }
// Action UI
virtual void DoGoto(std::function Callback);
virtual void DoCase(std::function Callback, bool Upper);
virtual void DoFind(std::function Callback) override;
virtual void DoFindNext(std::function Callback);
virtual void DoReplace(std::function Callback) override;
// Action Processing
void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0);
void UpdateScrollBars(bool Reset = false);
ssize_t GetLine();
void SetLine(int64_t Line);
LDocFindReplaceParams *CreateFindReplaceParams() override;
void SetFindReplaceParams(LDocFindReplaceParams *Params) override;
// Object Events
virtual bool OnFind( const char16 *Find,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
virtual bool OnReplace( const char16 *Find,
const char16 *Replace,
bool All,
bool MatchWord,
bool MatchCase,
bool SelectionOnly,
bool SearchUpwards);
bool OnMultiLineTab(bool In);
void OnSetHidden(int Hidden);
void OnPosChange() override;
void OnCreate() override;
void OnEscape(LKey &K) override;
bool OnMouseWheel(double Lines) override;
// Window Events
void OnFocus(bool f) override;
void OnMouseClick(LMouse &m) override;
void OnMouseMove(LMouse &m) override;
bool OnKey(LKey &k) override;
void OnPaint(LSurface *pDC) override;
LMessage::Result OnEvent(LMessage *Msg) override;
int OnNotify(LViewI *Ctrl, LNotification n) override;
void OnPulse() override;
int OnHitTest(int x, int y) override;
bool OnLayout(LViewLayoutInfo &Inf) override;
int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override;
int OnDrop(LArray &Data, LPoint Pt, int KeyState) override;
LCursor GetCursor(int x, int y) override;
// Virtuals
virtual bool Insert(size_t At, const char16 *Data, ssize_t Len);
virtual bool Delete(size_t At, ssize_t Len);
virtual void OnEnter(LKey &k) override;
virtual void OnUrl(char *Url) override;
virtual void DoContextMenu(LMouse &m);
virtual bool OnStyleClick(LStyle *style, LMouse *m);
virtual bool OnStyleMenu(LStyle *style, LSubMenu *m);
virtual void OnStyleMenuClick(LStyle *style, int i);
};
#endif // _GTEXTVIEW4_H
diff --git a/src/common/Gdc2/Filters/Pcx.cpp b/src/common/Gdc2/Filters/Pcx.cpp
--- a/src/common/Gdc2/Filters/Pcx.cpp
+++ b/src/common/Gdc2/Filters/Pcx.cpp
@@ -1,539 +1,539 @@
/*hdr
** FILE: Pcx.cpp
** AUTHOR: Matthew Allen
** DATE: 8/9/1998
** DESCRIPTION: Pcx file filter
**
** Copyright (C) 1997-8, Matthew Allen
** fret@memecode.com
*/
#include
#include
#include
#include "lgi/common/Gdc2.h"
#include "lgi/common/StringClass.h"
#include "lgi/common/Variant.h"
#include "lgi/common/Palette.h"
class GdcPcx : public LFilter {
public:
Format GetFormat() { return FmtPcx; }
int GetCapabilites() { return FILTER_CAP_READ | FILTER_CAP_WRITE; }
IoStatus ReadImage(LSurface *Out, LStream *In);
IoStatus WriteImage(LStream *Out, LSurface *In);
bool GetVariant(const char *n, LVariant &v, char *a)
{
if (!stricmp(n, LGI_FILTER_TYPE))
{
v = "Pcx";
}
else if (!stricmp(n, LGI_FILTER_EXTENSIONS))
{
v = "PCX";
}
else return false;
return true;
}
};
// Object Factory
class GdcPcxFactory : public LFilterFactory
{
bool CheckFile(const char *File, int Access, const uchar *Hint)
{
return (File) ? stristr(File, ".pcx") != 0 : false;
}
LFilter *NewObject()
{
return new GdcPcx;
}
} PcxFactory;
// PCX
#define swapb(i) ((i>>8) | ((i&0x00FF)<<8))
typedef struct {
uchar Magic;
uchar Type;
uchar Compression;
uchar Bits;
ushort x1;
ushort y1;
ushort x2;
ushort y2;
ushort DevX;
ushort DevY;
uchar Palette[48];
uchar Unknown; // 0
uchar Planes;
ushort Line; // Bytes per plane
ushort PalType; // 1
} PCX_HEADER;
LFilter::IoStatus GdcPcx::ReadImage(LSurface *pDC, LStream *In)
{
IoStatus Status = IoError;
if (pDC && In)
{
PCX_HEADER Header;
In->Read(&Header, sizeof(Header));
if (Header.Magic == 0x0A)
{
int Bits = Header.Bits * Header.Planes;
int Sx = Header.x2 - Header.x1 + 1;
int Sy = Header.y2 - Header.y1 + 1;
// int Len = (((Sx * Bits + 15) / 16) * 2);
LPalette *Pal = 0;
uchar PalData[768];
memcpy(PalData, Header.Palette, 48);
switch (Bits)
{
case 1:
{
Pal = new LPalette(PalData, 2);
break;
}
case 4:
{
Pal = new LPalette(PalData, 16);
break;
}
case 8:
{
In->SetPos(In->GetSize()-768);
In->Read(PalData, 768);
Pal = new LPalette(PalData);
break;
}
}
if (Pal)
{
pDC->Palette(Pal);
}
In->SetPos(128);
- if (pDC->Create(Sx, Sy, MAX(8, Bits)))
+ if (pDC->Create(Sx, Sy, CsIndex8))
{
int Line = GetLineLength(pDC);
uchar *buf = new uchar[Line];
uchar *pbuf = new uchar[Header.Line];
uchar *dest = 0;
if (buf && pbuf)
{
uint update = 10,x,y = 0;
uchar a,b,c,p;
while (y < Sy && update)
{
memset(buf, 0, Line);
for (p=0; pRead(&a, 1);
if (a > 192)
{
In->Read(&b, 1);
c = a - 192;
memset(dest, b, c);
x += c;
dest += c;
}
else
{
*dest = a;
x++;
dest++;
}
}
switch (Bits)
{
case 1:
{
uchar *d = buf,*s = pbuf;
uchar Mask = 0x80;
for (int x=0; x>= 1;
if (!Mask)
{
Mask = 0x80;
s++;
}
}
break;
}
case 4:
{
uchar *d = buf,*s = pbuf;
uchar mask = 0x80;
uchar bp = 1 << p;
for (int i=0; i>= 1;
if (!mask)
{
mask = 0x80;
s++;
}
}
break;
}
case 8:
{
memcpy(buf, pbuf, Line);
break;
}
case 24:
{
int Offset = 2 - p;
uchar *d = buf,*s = pbuf;
for (int x=0; xSetSize(0);
LPalette *Pal = pDC->Palette();
PCX_HEADER Header;
int Bits = pDC->GetBits();
char c;
// Fill out header struct and save it
Header.Magic = 10;
Header.Type = 5;
Header.Compression = 1;
switch (pDC->GetBits())
{
case 8:
{
Header.Bits = 8;
Header.Planes = 1;
if (Pal)
{
switch (Pal->GetSize())
{
case 2:
{
Header.Bits = 1;
Header.Planes = 1;
Bits = 1;
break;
}
case 16:
{
Header.Bits = 1;
Header.Planes = 4;
Bits = 4;
break;
}
}
}
break;
}
default:
{
Header.Bits = 8;
Header.Planes = 3;
break;
}
}
Header.x1 = 0;
Header.y1 = 0;
Header.x2 = pDC->X() - 1;
Header.y2 = pDC->Y() - 1;
Header.DevX = GdcD->X();
Header.DevY = GdcD->Y();
memset(Header.Palette, 0, sizeof(Header.Palette));
if (Pal)
{
for (int i=0; iGetSize(), 16); i++)
{
uchar *p = Header.Palette + (i * 3);
p[0] = (*Pal)[i]->r;
p[1] = (*Pal)[i]->g;
p[2] = (*Pal)[i]->b;
}
}
Header.Unknown = 0;
Header.Line = ((pDC->X() * Header.Bits) + 7) / 8;
Header.Line += Header.Line % 2;
Header.PalType = 1;
Out->Write(&Header, sizeof(Header));
// pad header to 128 bytes in length with zeros
c = 0;
int i;
for (i=0; i<128-sizeof(Header); i++)
{
Out->Write(&c, 1);
}
// Write image data, plane by plane
uchar *Buf = new uchar[Header.Line];
if (Buf)
{
for (int y=0; yY(); y++)
{
// Extract the data for this plane
for (int Plane=0; PlaneX(); x++)
{
if (pDC->Get(x, y) & 1)
{
*d |= Mask;
}
Mask >>= 1;
if (!Mask)
{
Mask = 0x80;
d++;
}
}
break;
}
case 4:
{
uchar *d = Buf;
uchar Mask = 0x80;
uchar PBit = 1 << Plane;
for (int x=0; xX(); x++)
{
if (pDC->Get(x, y) & PBit)
{
*d |= Mask;
}
Mask >>= 1;
if (!Mask)
{
Mask = 0x80;
d++;
}
}
break;
}
case 8:
{
uchar *d = Buf;
for (int x=0; xX(); x++)
{
*d++ = pDC->Get(x, y);
}
break;
}
case 16:
{
uchar *d = Buf;
if (Plane == 0)
{
for (int x=0; xX(); x++)
{
*d++ = R16(pDC->Get(x, y)) << 3;
}
}
if (Plane == 1)
{
for (int x=0; xX(); x++)
{
*d++ = G16(pDC->Get(x, y)) << 2;
}
}
if (Plane == 2)
{
for (int x=0; xX(); x++)
{
*d++ = B16(pDC->Get(x, y)) << 3;
}
}
break;
}
case 24:
{
uchar *d = Buf;
uchar *s = (*pDC)[y];
for (int x=0; xX(); x++)
{
*d++ = s[2-Plane];
s += 3;
}
break;
}
case 32:
{
uchar *d = Buf;
uchar *s = (*pDC)[y];
for (int x=0; xX(); x++)
{
*d++ = s[1+Plane];
s += 4;
}
break;
}
}
// Compress the data and write it to the file
for (int i=0; i 1)
{
// archived section
// Size: Len - 192
i += Len;
Len |= 0xC0;
Out->Write(&Len, 1);
Out->Write(&c, 1);
}
else
{
if (c & 0xC0)
{
// this colour can get confused with an archive byte
// so we store it as a archive of length 1
// this sux big time because every byte takes up
// 2 bytes in the file :(
Len = 0xC1;
Out->Write(&Len, 1);
Out->Write(&c, 1);
}
else
{
// write the colour to the file
Out->Write(&c, 1);
}
i++;
}
}
}
}
DeleteArray(Buf);
}
// Write out any 256 colour palette
if (Pal && Pal->GetSize() > 16)
{
c = 12;
Out->Write(&c, 1);
for (i=0; iGetSize(); i++)
{
Out->Write(&(*Pal)[i]->r, 1);
Out->Write(&(*Pal)[i]->g, 1);
Out->Write(&(*Pal)[i]->b, 1);
}
}
Status = IoSuccess;
}
return Status;
}
diff --git a/src/common/Gdc2/Filters/TransparentDlg.cpp b/src/common/Gdc2/Filters/TransparentDlg.cpp
--- a/src/common/Gdc2/Filters/TransparentDlg.cpp
+++ b/src/common/Gdc2/Filters/TransparentDlg.cpp
@@ -1,49 +1,62 @@
-#include "lgi/common/Gdc2.h"
+#include "lgi/common/Lgi.h"
+#include "lgi/common/TableLayout.h"
#include "TransparentDlg.h"
#ifdef FILTER_UI
#ifdef WIN32
#define TRANS_DLG_Y 145
#else
#define TRANS_DLG_Y 110
#endif
#include "lgi/common/RadioGroup.h"
#include "lgi/common/Button.h"
+enum Ctrls
+{
+ IDC_TABLE = 100,
+ IDC_GRP,
+};
+
LTransparentDlg::LTransparentDlg(LView *parent, LVariant *trans)
{
Trans = trans;
SetParent(parent);
Name("Tranparency Settings");
- LRect r(0, 0, 200, TRANS_DLG_Y);
+ LRect r(0, 0, 200, TRANS_DLG_Y);
SetPos(r);
+ ScaleSizeToDpi();
MoveToCenter();
- Children.Insert(Grp = new LRadioGroup(100, 10, 10, 180, 65, "Transparency"));
+ auto tbl = new LTableLayout(IDC_TABLE);
+ AddView(tbl);
+ auto c = tbl->GetCell(0, 0);
+ c->Add(Grp = new LRadioGroup(IDC_GRP, 0, 0, 180, 65, "Transparency"));
if (Grp)
{
- Grp->Append(10, 20, "Opaque");
- Grp->Append(10, 40, "Use background colour");
+ Grp->Append(0, 0, "Opaque");
+ Grp->Append(0, 30, "Use background colour");
Grp->Value((Trans) ? 1 : 0);
}
- Children.Insert(new LButton(IDOK, 65, 82, 60, 20, "Ok"));
+ c = tbl->GetCell(0, 1);
+ c->TextAlign(LCss::AlignCenter);
+ c->Add(new LButton(IDOK, 0, 0, -1, -1, "Ok"));
}
int LTransparentDlg::OnNotify(LViewI *Ctrl, LNotification n)
{
switch (Ctrl->GetId())
{
case IDOK:
{
*Trans = Grp->Value() == 1;
EndModal(1);
break;
}
}
return 0;
}
#endif
diff --git a/src/common/Gdc2/Icc.cpp b/src/common/Gdc2/Icc.cpp
--- a/src/common/Gdc2/Icc.cpp
+++ b/src/common/Gdc2/Icc.cpp
@@ -1,1188 +1,1188 @@
/**
\file
\author Matthew Allen
\brief Colour management class
*/
/*
This class requires the little cms library to do the underlying
work of colour conversion. It does however partially parse the
ICC profile data and extract the information into a DOM structure
to allow some sort of access. If you don't have little cms then
change USE_LCMS to 0.
If you want to add support for a new tag, first implement a structure
to mimic the format of the tag like the struct IccDescTag. Then add
a case for the tag to TagDom::TagDom(...) that instances a new DOM
object to display the contents of your struct.
*/
#ifdef WIN32
#define USE_LCMS 0
#else
#define USE_LCMS 0
#endif
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Icc.h"
#if USE_LCMS
#include "lcms.h"
#endif
///////////////////////////////////////////////////////////////////////////////
uint32_t Swap32(uint32_t i)
{
#if LGI_LITTLE_ENDIAN
return ((i & 0xff000000) >> 24) |
((i & 0x00ff0000) >> 8) |
((i & 0x0000ff00) << 8) |
((i & 0x000000ff) << 24);
#else
return i;
#endif
}
uint16 Swap16(uint16 i)
{
#if LGI_LITTLE_ENDIAN
return ((i & 0xff00) >> 8) |
((i & 0x00ff) << 8);
#else
return i;
#endif
}
enum IccColourSpace
{
IccCsXYZ = 0x58595A20, // 'XYZ '
IccCslab = 0x4C616220, // 'Lab '
IccCsluv = 0x4C757620, // 'Luv '
IccCsYCbCr = 0x59436272, // 'YCbr'
IccCsYxy = 0x59787920, // 'Yxy '
IccCsRgb = 0x52474220, // 'RGB '
IccCsGray = 0x47524159, // 'GRAY'
IccCsHsv = 0x48535620, // 'HSV '
IccCsHls = 0x484C5320, // 'HLS '
IccCsCmyk = 0x434D594B, // 'CMYK'
IccCsCmy = 0x434D5920, // 'CMY '
IccCs2Colour = 0x32434C52, // '2CLR'
IccCs3Colour = 0x33434C52, // '3CLR'
IccCs4Colour = 0x34434C52, // '4CLR'
IccCs5Colour = 0x35434C52, // '5CLR'
IccCs6Colour = 0x36434C52, // '6CLR'
IccCs7Colour = 0x37434C52, // '7CLR'
IccCs8Colour = 0x38434C52, // '8CLR'
IccCs9Colour = 0x39434C52, // '9CLR'
IccCs10Colour = 0x41434C52, // 'ACLR'
IccCs11Colour = 0x42434C52, // 'BCLR'
IccCs12Colour = 0x43434C52, // 'CCLR'
IccCs13Colour = 0x44434C52, // 'DCLR'
IccCs14Colour = 0x45434C52, // 'ECLR'
IccCs15Colour = 0x46434C52, // 'FCLR'
};
enum IccProfileClass
{
IccProfileInputDevice = 0x73636E72, // 'scnr'
IccProfileDisplay = 0x6D6E7472, // 'mntr'
IccProfileOutput = 0x70727472, // 'prtr'
IccProfileDeviceLink = 0x6C696E6B, // 'link'
IccProfileColorSpaceConversion = 0x73706163, // 'spac'
IccProfileAbstract = 0x61627374, // 'abst'
IccProfileNamed = 0x6E6D636C, // 'nmcl'
};
struct S15Fixed16
{
int16 i;
uint16 f;
double d()
{
return i + ((double)f/0xffff);
}
};
struct U16Fixed16
{
uint16 i;
uint16 f;
double d()
{
return i + ((double)f/0xffff);
}
};
struct U1Fixed15
{
uint16 i:1;
uint16 f:15;
double d()
{
return i + ((double)f/0x7fff);
}
};
struct U8Fixed8
{
uint8_t i;
uint8_t f;
double d()
{
return i + ((double)f/0xff);
}
};
struct IccDateTime
{
uint16 Year;
uint16 Month;
uint16 Day;
uint16 Hour;
uint16 Minute;
uint16 Second;
};
struct IccResponse16Number
{
uint16 Number;
uint16 Reserved;
S15Fixed16 Value;
};
struct IccXYZ
{
S15Fixed16 x;
S15Fixed16 y;
S15Fixed16 z;
};
struct IccProfileHeader
{
uint32_t ProfileSize;
uint32_t PreferedCmmType;
uint32_t Version;
IccProfileClass Class;
IccColourSpace InputSpace;
uint32_t ConnectionSpace;
IccDateTime CreationDate;
uint32_t Magic; // 'acsp'
uint32_t PlatformSig;
uint32_t Flags;
uint32_t DeviceManufacturer;
uint32_t DeviceModel;
uint8_t DeviceAttributes[8];
uint32_t RenderingIntent;
IccXYZ PcsD50;
uint32_t CreatorSig;
char ProfileId[16];
uint8_t Reserved[28];
};
struct IccTag
{
uint32_t Sig;
uint32_t Offset;
uint32_t Size;
};
struct IccTagTable
{
uint32_t Tags;
IccTag Tag[1];
};
struct IccLocalString
{
uint16 Lang;
uint16 Country;
uint32_t Len;
uint32_t Offset;
};
struct IccMultiLocalUnicode
{
uint32_t Sig;
uint32_t Reserved;
uint32_t Count;
uint32_t RecordSize;
IccLocalString Str[1];
};
struct IccDescTag
{
uint32_t Sig;
uint32_t Reserved;
uint32_t Size;
char String[1];
- bool IsOk() { return Swap32(Sig) == 'desc'; }
+ bool IsOk() { return Swap32(Sig) == Lgi4CC("desc"); }
};
struct NamedColour
{
char Name[32];
uint16 PcsCoords[3];
uint16 Coords[1]; // variable
NamedColour *Next(int Ch)
{
return (NamedColour*) ( ((char*)this) + sizeof(NamedColour) + ((Ch-1) * sizeof(uint16)) );
}
};
struct IccNameColourTag
{
uint32_t Sig;
uint32_t Reserved;
uint32_t Flags;
uint32_t Count;
uint32_t Coords;
char Prefix[32];
char Suffix[32];
NamedColour Colours[1];
- bool IsOk() { return Swap32(Sig) == 'ncl2'; }
+ bool IsOk() { return Swap32(Sig) == Lgi4CC("ncl2"); }
};
struct IccTextTag
{
uint32_t Sig;
uint32_t Reserved;
char String[1];
- bool IsOk() { return Swap32(Sig) == 'text'; }
+ bool IsOk() { return Swap32(Sig) == Lgi4CC("text"); }
};
struct IccCurve
{
uint32_t Sig;
uint32_t Reserved;
uint32_t Count;
uint16 Values[1];
- bool IsOk() { return Swap32(Sig) == 'curv'; }
+ bool IsOk() { return Swap32(Sig) == Lgi4CC("curv"); }
};
struct IccXYZTag
{
uint32_t Sig;
uint32_t Reserved;
IccXYZ Values[1];
- bool IsOk() { return Swap32(Sig) == 'XYZ '; }
+ bool IsOk() { return Swap32(Sig) == Lgi4CC("XYZ "); }
};
class ValueDom : public LDom
{
char *Txt;
public:
ValueDom(uint32_t i, const char *name)
{
char s[256];
uint32_t is = i;
sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is);
Txt = NewStr(s);
}
ValueDom(IccProfileClass i, const char *name)
{
char s[256];
uint32_t is = i;
sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is);
Txt = NewStr(s);
}
ValueDom(IccColourSpace i, const char *name)
{
char s[256];
uint32_t is = i;
sprintf(s, "%s: %i (%4.4s)", name, Swap32(i), (char*)&is);
Txt = NewStr(s);
}
ValueDom(uint16 i, const char *name)
{
char s[256];
sprintf(s, "%s: %i", name, Swap16(i));
Txt = NewStr(s);
}
ValueDom(const char *str, const char *name)
{
char s[256] = "(error)";
if (name && str)
sprintf(s, "%s: %s", name, str);
else if (name)
strcpy(s, name);
else if (str)
strcpy(s, str);
Txt = NewStr(s);
}
ValueDom(IccDateTime &d, const char *name)
{
char s[256];
sprintf(s, "%s: %i/%i/%i %i:%i:%i", name,
Swap16(d.Day),
Swap16(d.Month),
Swap16(d.Year),
Swap16(d.Hour),
Swap16(d.Minute),
Swap16(d.Second));
Txt = NewStr(s);
}
~ValueDom()
{
DeleteArray(Txt);
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Text") == 0)
{
Value = Txt;
}
else return false;
return true;
}
};
#define DomAdd(fld) \
Dom.Add(new ValueDom(h->fld, #fld));
class LocalStringDom : public LDom
{
char *Base;
IccLocalString *Str;
public:
LocalStringDom(char *b, IccLocalString *s)
{
Base = b;
Str = s;
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Text") == 0)
{
int Off = Swap32(Str->Offset);
int Len = Swap32(Str->Len);
char16 *u = NewStrW((char16*)(Base+Off), Len);
if (!u)
return false;
for (char16 *p = u; *p; p++)
*p = Swap16(*p);
char *u8 = WideToUtf8(u);
DeleteArray(u);
char s[512];
sprintf(s, "'%2.2s' = '%s'", (char*)&Str->Lang, u8);
Value = "Localized Unicode";
}
else return false;
return true;
}
};
class LocalUnicodeDom : public LDom
{
IccMultiLocalUnicode *h;
LArray Dom;
public:
LocalUnicodeDom(IccMultiLocalUnicode *header)
{
h = header;
- if (Swap32(h->Sig) == 'mluc')
+ if (Swap32(h->Sig) == Lgi4CC("mluc"))
{
for (int i=0; iCount); i++)
{
Dom.Add(new LocalStringDom((char*)header, h->Str + i));
}
}
else
{
Dom.Add(new ValueDom("Incorrect signature", "Error"));
}
}
~LocalUnicodeDom()
{
Dom.DeleteObjects();
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Children") == 0)
{
Value.SetList();
for (int i=0; iInsert(new LVariant(Dom[i]));
}
}
else if (stricmp(Name, "Text") == 0)
{
Value = "Localized Unicode";
}
else return false;
return true;
}
};
class TagDom : public LDom
{
IccTag *h;
LArray Dom;
char *Txt;
public:
TagDom(IccTag *tag, char *header);
~TagDom();
bool GetVariant(const char *Name, LVariant &Value, const char *Array);
};
class HeaderDom : public LDom
{
IccProfileHeader *h;
LArray Dom;
public:
HeaderDom(IccProfileHeader *header)
{
h = header;
DomAdd(ProfileSize);
DomAdd(PreferedCmmType);
DomAdd(Version);
DomAdd(Class);
DomAdd(InputSpace);
DomAdd(ConnectionSpace);
DomAdd(CreationDate);
DomAdd(PlatformSig);
DomAdd(Flags);
DomAdd(DeviceManufacturer);
DomAdd(DeviceModel);
DomAdd(RenderingIntent);
DomAdd(CreatorSig);
// DomAdd(ProfileId);
}
~HeaderDom()
{
Dom.DeleteObjects();
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Children") == 0)
{
Value.SetList();
for (int i=0; iInsert(new LVariant(Dom[i]));
}
}
else if (stricmp(Name, "Text") == 0)
{
Value = "Header";
}
else return false;
return true;
}
};
class CurveDom : public LDom
{
IccCurve *c;
public:
CurveDom(IccCurve *curve)
{
c = curve;
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Text") == 0)
{
char s[256];
int Count = Swap32(c->Count);
if (Count == 0)
{
sprintf(s, "Identity Curve");
}
else if (Count == 1)
{
U8Fixed8 p = *((U8Fixed8*)c->Values);
sprintf(s, "Gamma Curve: %f", p.d());
}
else
{
sprintf(s, "Parametric Curve with %i points", Count);
}
Value = s;
}
else return false;
return true;
}
};
class XyzDom : public LDom
{
IccXYZTag *x;
int Len;
public:
XyzDom(IccXYZTag *xyz, int len)
{
x = xyz;
Len = Swap32(len);
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Text") == 0)
{
char s[1024] = "";
char *p = s;
IccXYZ *e = (IccXYZ*) (((char*)x) + Len);
for (IccXYZ *v = x->Values; v < e; v++)
{
if (p > s + sizeof(s) - 64)
break;
sprintf(p, "%f,%f,%f ", v->x.d(), v->y.d(), v->z.d());
p += strlen(p);
}
Value = s;
}
else return false;
return true;
}
};
class ChildDom : public LDom
{
public:
LArray Dom;
char *Txt;
ChildDom()
{
Txt = 0;
}
~ChildDom()
{
DeleteArray(Txt);
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Text") == 0)
{
Value = Txt;
}
else if (stricmp(Name, "Children") == 0)
{
Value.SetList();
for (int i=0; iInsert(new LVariant(Dom[i]));
}
}
else if (stricmp(Name, "Expand") == 0)
{
Value = false;
}
else return false;
return true;
}
};
class NclDom : public ChildDom
{
IccNameColourTag *n;
int Len;
public:
NclDom(IccNameColourTag *named, int len)
{
n = named;
n->Count = Swap32(n->Count);
n->Coords = Swap32(n->Coords);
Len = Swap32(len);
char s[1024] = "";
sprintf(s, "Named colour count: %i", n->Count);
Txt = NewStr(s);
NamedColour *c = n->Colours;
int Block = 100;
for (int i=0; iCount; i+=Block)
{
ChildDom *v = new ChildDom;
if (v)
{
int End = MIN(i + Block - 1, n->Count - 1);
Dom.Add(v);
sprintf(s, "%i-%i", i, End);
v->Txt = NewStr(s);
for (int k=0; kName,
(double)Swap16(c->PcsCoords[0]) / 0xffff,
(double)Swap16(c->PcsCoords[1]) / 0xffff,
(double)Swap16(c->PcsCoords[2]) / 0xffff);
v->Dom.Add(new ValueDom(s, "Name"));
c = c->Next(n->Coords);
}
}
}
}
};
TagDom::TagDom(IccTag *tag, char *header)
{
Txt = 0;
h = tag;
DomAdd(Offset);
DomAdd(Size);
int Off = Swap32(h->Offset);
uint32_t *Ptr = (uint32_t*) (header + Off);
switch (Swap32(h->Sig))
{
- case 'desc':
+ case Lgi4CC("desc"):
{
- if (Swap32(*Ptr) == 'mluc')
+ if (Swap32(*Ptr) == Lgi4CC("mluc"))
{
Dom.Add(new LocalUnicodeDom((IccMultiLocalUnicode*)(header + Off )));
}
- else if (Swap32(*Ptr) == 'desc')
+ else if (Swap32(*Ptr) == Lgi4CC("desc"))
{
IccDescTag *Tag = (IccDescTag*) Ptr;
Dom.Add(new ValueDom(Txt = Tag->String, "Description"));
}
break;
}
- case 'cprt':
+ case Lgi4CC("cprt"):
{
IccTextTag *Tag = (IccTextTag*)Ptr;
if (Tag->IsOk())
{
Dom.Add(new ValueDom(Txt = Tag->String, "Copyright"));
}
break;
}
- case 'rTRC':
- case 'gTRC':
- case 'bTRC':
+ case Lgi4CC("rTRC"):
+ case Lgi4CC("gTRC"):
+ case Lgi4CC("bTRC"):
{
IccCurve *c = (IccCurve*) Ptr;
if (c->IsOk())
{
Dom.Add(new CurveDom(c));
}
break;
}
- case 'rXYZ':
- case 'gXYZ':
- case 'bXYZ':
+ case Lgi4CC("rXYZ"):
+ case Lgi4CC("gXYZ"):
+ case Lgi4CC("bXYZ"):
{
IccXYZTag *Xyz = (IccXYZTag*) Ptr;
if (Xyz->IsOk())
{
Dom.Add(new XyzDom(Xyz, h->Size));
}
break;
}
- case 'ncl2':
+ case Lgi4CC("ncl2"):
{
IccNameColourTag *Ncl = (IccNameColourTag*) Ptr;
if (Ncl->IsOk())
{
Dom.Add(new NclDom(Ncl, h->Size));
}
break;
}
}
}
TagDom::~TagDom()
{
Dom.DeleteObjects();
}
bool TagDom::GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Children") == 0)
{
Value.SetList();
for (int i=0; iInsert(new LVariant(Dom[i]));
}
}
else if (stricmp(Name, "Text") == 0)
{
char s[256];
sprintf(s, "Tag '%4.4s'", (char*)&h->Sig);
Value = s;
}
else if (stricmp(Name, "Expand") == 0)
{
Value = (int)(Dom.Length() > 2);
}
else if (Txt && stricmp(Name, "Name") == 0)
{
Value = Txt;
}
else return false;
return true;
}
class TagTableDom : public LDom
{
IccTagTable *t;
LArray Dom;
public:
TagTableDom(IccTagTable *table, char *header)
{
t = table;
for (int i=0; iTags); i++)
{
Dom.Add(new TagDom(t->Tag + i, header));
}
}
~TagTableDom()
{
Dom.DeleteObjects();
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Children") == 0)
{
Value.SetList();
for (int i=0; iInsert(new LVariant(Dom[i]));
}
}
else if (stricmp(Name, "Text") == 0)
{
Value = "Tag Table";
}
else if (stricmp(Name, "Name") == 0)
{
for (int i=0; iTags; i++)
{
- if (Swap32(t->Tag[i].Sig) == 'desc')
+ if (Swap32(t->Tag[i].Sig) == Lgi4CC("desc"))
{
Value = Dom[i];
break;
}
}
}
else return false;
return true;
}
};
///////////////////////////////////////////////////////////////////////////////
class LIccProfilePrivate
{
public:
char *Err;
char *Name;
int64 Len;
char *Data;
#if USE_LCMS
cmsHPROFILE Profile;
#endif
LArray Dom;
LIccProfilePrivate()
{
Err = 0;
Data = 0;
Len = 0;
Name = 0;
#if USE_LCMS
Profile = 0;
#endif
LAssert(sizeof(IccProfileHeader) == 128);
}
~LIccProfilePrivate()
{
Empty();
}
IccProfileHeader *Header()
{
return (IccProfileHeader*)Data;
}
IccTagTable *TagTable()
{
return Data ? (IccTagTable*)(Data + sizeof(IccProfileHeader)) : 0;
}
void Empty()
{
#if USE_LCMS
if (Profile)
{
cmsCloseProfile(Profile);
Profile = 0;
}
#endif
Dom.DeleteObjects();
DeleteArray(Err);
DeleteArray(Name);
DeleteArray(Data);
Len = 0;
}
bool GetTag(uint32_t Tag, char *&Ptr, int &Size)
{
IccTagTable *t = TagTable();
if (t)
{
for (int i=0; iTags; i++)
{
if (t->Tag[i].Sig == Tag)
{
Ptr = Data + t->Tag[i].Offset;
Size = t->Tag[i].Size;
return true;
}
}
}
return false;
}
void SetErr(const char *e)
{
DeleteArray(Err);
Err = NewStr(e);
}
};
///////////////////////////////////////////////////////////////////////////////
LIccProfile::LIccProfile(char *file)
{
d = new LIccProfilePrivate;
}
LIccProfile::~LIccProfile()
{
DeleteObj(d);
}
bool LIccProfile::CreateNamed(const char *name)
{
if (name)
{
#if USE_LCMS
d->Empty();
if (stricmp(name, "sRGB") == 0)
{
d->Profile = cmsCreate_sRGBProfile();
}
if (d->Profile)
{
return true;
}
#endif
}
return false;
}
bool LIccProfile::Open(const char *file)
{
LFile f;
if (file && f.Open(file, O_READ))
{
return Open(&f);
}
return false;
}
bool LIccProfile::Open(LStream *Stream)
{
if (Stream)
{
d->Empty();
d->Len = Stream->GetSize();
d->Data = new char[d->Len];
if (d->Data)
{
if (Stream->Read(d->Data, d->Len) == d->Len)
{
IccProfileHeader *h = d->Header();
- if (Swap32(h->Magic) == 'acsp')
+ if (Swap32(h->Magic) == Lgi4CC("acsp"))
{
#if USE_LCMS
d->Profile = cmsOpenProfileFromMem(d->Data, d->Len);
#endif
d->Dom.Add(new HeaderDom(d->Header()));
d->Dom.Add(new TagTableDom(d->TagTable(), d->Data ));
LVariant Desc;
if (GetValue("Children[1].Name.Name", Desc))
{
d->Name = NewStr(Desc.Str());
}
return true;
}
else
{
d->SetErr("Not a valid profile.");
}
}
else
{
d->SetErr("Failed to read stream.");
}
}
else
{
d->SetErr("Mem alloc failed.");
}
}
else
{
d->SetErr("Invalid parameter.");
}
return false;
}
bool LIccProfile::Save(const char *file)
{
LFile f;
if (f.Open(file, O_WRITE))
{
f.SetSize(0);
return Save(&f);
}
return false;
}
bool LIccProfile::Save(LStream *stream)
{
if (stream)
{
if (d->Data && d->Len > 0)
{
return stream->Write(d->Data, d->Len) == d->Len;
}
}
return false;
}
char *LIccProfile::GetError()
{
return d->Err;
}
char *LIccProfile::GetName()
{
return d->Name;
}
bool LIccProfile::Convert(COLOUR *Out32, COLOUR In32, LIccProfile *Profile)
{
return false;
}
bool LIccProfile::Convert(LSurface *Dest, LSurface *Src, LIccProfile *Profile)
{
#if USE_LCMS
if (!Dest || !Src || !Profile)
{
d->SetErr("Invalid parameter(s).");
return false;
}
if (Dest->X() != Src->X() ||
Dest->Y() != Src->Y())
{
d->SetErr("Source and Dest images are different sizes.");
return false;
}
if
(
!
(
(Dest->GetBits() == 32 && Src->GetBits() == 32)
||
(Dest->GetBits() == 24 && Src->GetBits() == 24)
)
)
{
d->SetErr("Must be RGB or RGBA images.");
return false;
}
if (!d->Profile)
{
d->SetErr("Dest profile error.");
return false;
}
if (!Profile->d->Profile)
{
d->SetErr("Src profile error.");
return false;
}
cmsHTRANSFORM t = cmsCreateTransform( Profile->d->Profile,
Src->GetBits() == 32 ? TYPE_BGRA_8 : TYPE_BGR_8,
d->Profile,
Src->GetBits() == 32 ? TYPE_BGRA_8 : TYPE_BGR_8,
d->Header() ? d->Header()->RenderingIntent : 0,
0);
if (t)
{
uchar *Buf = 0;
int Bytes = 0;
if (Src == Dest)
{
Bytes = (Src->GetBits() / 8) * Src->X();
Buf = new uchar[Bytes];
}
for (int y=0; yY(); y++)
{
uchar *sp = (*Src)[y];
uchar *dp = (*Dest)[y];
if (sp && dp)
{
if (Buf)
{
memcpy(Buf, sp, Bytes);
}
cmsDoTransform(t, Buf ? Buf : sp, dp, Src->X());
}
}
DeleteArray(Buf);
cmsDeleteTransform(t);
return true;
}
else
{
d->SetErr("Couldn't create colour transform.");
}
#else
d->SetErr("LCMS support not compiled in.");
#endif
return false;
}
bool LIccProfile::GetVariant(const char *Name, LVariant &Value, const char *Array)
{
if (!Name)
return false;
if (stricmp(Name, "Children") == 0)
{
if (Array)
{
Value = d->Dom[atoi(Array)];
}
else
{
Value.SetList();
for (int i=0; iDom.Length(); i++)
{
Value.Value.Lst->Insert(new LVariant(d->Dom[i]));
}
}
}
else if (stricmp(Name, "Text") == 0)
{
Value = "ICC Colour Profile";
}
else return false;
return true;
}
diff --git a/src/common/Lgi/ProgressStatusPane.cpp b/src/common/Lgi/ProgressStatusPane.cpp
--- a/src/common/Lgi/ProgressStatusPane.cpp
+++ b/src/common/Lgi/ProgressStatusPane.cpp
@@ -1,63 +1,60 @@
/*
** FILE: LProgressStatusPane.cpp
** AUTHOR: Matthew Allen
** DATE: 13/12/1999
** DESCRIPTION: Progress meter for the status bar
**
** Copyright (C) 1999, Matthew Allen
** fret@memecode.com
*/
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/ProgressStatusPane.h"
////////////////////////////////////////////////////////////////////////////////
LProgressStatusPane::LProgressStatusPane() : DoEvery(200)
{
SetWidth(100);
Sunken(true);
_BorderSize = 1;
}
LProgressStatusPane::~LProgressStatusPane()
{
}
void LProgressStatusPane::OnPaint(LSurface *pDC)
{
LRect r = GetClient();
pDC->Colour(L_MED);
pDC->Box(&r);
r.Inset(1, 1);
pDC->Box(&r);
r.Inset(1, 1);
double Pos = (High != Low) ? (double) Val / ((double) High - (double) Low) : 0;
int x = (int) (r.X() * Pos);
if (x < r.X())
{
pDC->Rectangle(r.x1+x, r.y1, r.x2, r.y2);
}
pDC->Colour(L_FOCUS_SEL_BACK);
pDC->Rectangle(r.x1, r.y1, r.x1+x-1, r.y2);
}
int64 LProgressStatusPane::Value()
{
return Progress::Value();
}
void LProgressStatusPane::Value(int64 v)
{
Progress::Value(v);
if (DoNow() || !v)
- {
Invalidate();
- LYield();
- }
}
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,5440 +1,5440 @@
#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.NewLStr();
#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;
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;
}
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)
{
auto DoSave = [this, OnStatus](bool ok, const char *FileName)
{
Save(FileName);
if (OnStatus)
OnStatus(ok);
};
if (!FileName)
{
LFileSelect *Select = new LFileSelect;
Select->Parent(this);
Select->Save([FileName=LString(FileName), DoSave](auto s, auto ok)
{
if (ok)
DoSave(ok, s->Name());
else
DoSave(ok, FileName);
delete s;
});
}
else DoSave(true, FileName);
}
else if (Answer == IDCANCEL)
{
if (OnStatus)
OnStatus(false);
return;
}
}
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();
}
}
}
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);
}
}
if (Callback)
Callback(Text != NULL);
}
ssize_t LTextView3::GetLine()
{
ssize_t Idx = 0;
GetTextLine(Cursor, &Idx);
return Idx + 1;
}
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;
}
}
void LTextView3::DoGoto(std::function Callback)
{
LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text");
Dlg->DoModal([this, Dlg, Callback](auto d, auto code)
{
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;
}
}
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);
}
if (OnStatus)
OnStatus(Status);
}
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();
}
auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto 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();
}
DoFindNext([this, Callback](bool ok)
{
Focus(true);
if (Callback)
Callback(ok);
});
},
u);
Dlg->DoModal(NULL);
}
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;
}
}
}
auto LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind);
auto LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace);
auto Dlg = new LReplaceDlg(this, [this, LastFind8, LastReplace8](auto Dlg, auto Action)
{
LReplaceDlg *Replace = dynamic_cast(Dlg);
LAssert(Replace != NULL);
LAutoString FindMem(LastFind8);
LAutoString ReplaceMem(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();
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)
+ssize_t LTextView3::SeekLine(ssize_t Offset, LTextViewSeek 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 = new LInput(this, s, "Indent Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
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 = new LInput(this, s, "Tab Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
if (code)
SetTabSize((uint8_t)Atoi(i->GetStr().Get()));
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(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(NULL);
return true;
break;
}
case 'g':
case 'G':
{
if (k.Down())
{
DoGoto(NULL);
}
return true;
break;
}
case 'h':
case 'H':
{
if (k.Down())
{
DoReplace(NULL);
}
return true;
break;
}
case 'u':
case 'U':
{
if (!GetReadOnly())
{
if (k.Down())
{
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(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,5520 +1,5478 @@
#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)
+ #define PAINT_AFTER_LINE LColour(240, 240, 240)
#else
- #define PAINT_AFTER_LINE Back
+ #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 :
+class LDocFindReplaceParams4 :
public LDocFindReplaceParams,
public LMutex
{
public:
// Find/Replace History
LAutoWString LastFind;
LAutoWString LastReplace;
- bool MatchCase;
- bool MatchWord;
- bool SelectionOnly;
- bool SearchUpwards;
+ bool MatchCase = false;
+ bool MatchWord = false;
+ bool SelectionOnly = false;
+ bool SearchUpwards = false;
- GDocFindReplaceParams4() : LMutex("GDocFindReplaceParams4")
+ LDocFindReplaceParams4() : LMutex("LDocFindReplaceParams4")
{
- MatchCase = false;
- MatchWord = false;
- SelectionOnly = false;
- SearchUpwards = false;
}
};
class LTextView4Private : public LCss, public LMutex
{
public:
- LTextView4 *View;
+ LTextView4 *View = NULL;
LRect rPadding;
- int PourX;
- bool LayoutDirty;
- ssize_t DirtyStart, DirtyLen;
+ int PourX = -1;
+ bool LayoutDirty = true;
+ ssize_t DirtyStart = 0, DirtyLen = 0;
LColour UrlColour;
- bool CenterCursor;
- ssize_t WordSelectMode;
+ bool CenterCursor = false;
+ ssize_t WordSelectMode = -1;
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;
+ ssize_t VScrollCache = -1;
// Find/Replace Params
- bool OwnFindReplaceParams;
- GDocFindReplaceParams4 *FindReplaceParams;
+ bool OwnFindReplaceParams = true;
+ LDocFindReplaceParams4 *FindReplaceParams = NULL;
// Map buffer
- ssize_t MapLen;
- char16 *MapBuf;
+ ssize_t MapLen = 0;
+ char16 *MapBuf = NULL;
//
// 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;
+ FindReplaceParams = new LDocFindReplaceParams4;
}
~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;
+ if (Text)
+ *Text = 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());
+ p.Print(" [%i] alloc.ln=%i, %i+%i+%i=%i, %s\n",
+ Idx, i->Line,
+ (int)i->Start, (int)i->Len, i->NewLine, (int)(i->Start + i->Len + i->NewLine),
+ i->r.GetStr());
Idx++;
}
LgiTrace(p.NewLStr());
#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 (l->NewLine)
{
if (*e == '\n')
+ {
e++;
- else if (WrapType == TEXTED_WRAP_REFLOW)
- e++;
+ }
+ else
+ {
+ LAssert(!"Expecting new line.");
+ return false;
+ }
}
+
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;
+ LTextLine *l = new LTextLine(_FL);
l->Start = Pos;
char16 *c = Text + Pos;
char16 *e = c;
while (*e && *e != '\n')
e++;
l->Len = e - c;
l->NewLine = *e == '\n';
#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.NewLStr();
#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;
+ LTextLine *l = new LTextLine(_FL);
if (l)
{
l->Start = i;
l->Len = e - i;
l->NewLine = Text[e] == '\n';
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;
+ LTextLine *l = new LTextLine(_FL);
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));
+ auto After = Size - At;
+ if (After > 0)
+ memmove(Text+(At+Len), Text+At, After * 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);
+ Line.Add(Cur = new LTextLine(_FL));
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;
Cur->NewLine = Text[Cur->End()] == '\n';
// Create a new line...
- Cur = new LTextLine();
+ Cur = new LTextLine(_FL);
if (!Cur)
return false;
Cur->Start = Pos + 1;
- Cur->CalcLen(Text);
- Cur->NewLine = Text[Cur->End()] == '\n';
Line.AddAt(++Idx, Cur);
}
}
Prof.Add("CalcLen");
// Make sure the last Line's length is set..
Cur->CalcLen(Text);
+ printf("CalcLen, size=%i, start=%i, len=%i\n",
+ (int)Size, (int)Cur->Start, (int)Cur->Len);
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
goto OnError;
}
else mid = s + ((e - s) >> 1);
auto l = Line[mid];
auto end = l->EndNewLine();
if (Offset < l->Start)
e = mid - 1;
else if (Offset > end)
s = mid + 1;
else
{
if (!Line[mid]->Overlap(Offset))
goto OnError;
if (Index)
*Index = mid;
return Line.begin(mid);
}
}
if (!Line[s]->Overlap(Offset))
goto OnError;
if (Index)
*Index = s;
return Line.begin(s);
OnError:
return Line.end();
}
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;
}
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)
{
auto DoSave = [this, OnStatus](bool ok, const char *FileName)
{
Save(FileName);
if (OnStatus)
OnStatus(ok);
};
if (!FileName)
{
LFileSelect *Select = new LFileSelect;
Select->Parent(this);
Select->Save([FileName=LString(FileName), DoSave](auto Select, auto ok)
{
if (ok)
DoSave(ok, Select->Name());
else
DoSave(ok, FileName);
delete Select;
});
}
else DoSave(true, FileName);
}
else if (Answer == IDCANCEL)
{
if (OnStatus)
OnStatus(false);
return;
}
}
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();
}
}
}
}
void LTextView4::DoCase(std::function Callback, bool Upper)
{
if (Text)
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
if (Min < Max)
{
if (UndoOn)
{
LAutoPtr Obj(new LTextView4Undo(this));
LTextView4Undo *u = UndoCur ? UndoCur : Obj;
if (u)
u->AddChange(Min, Max - Min, UndoChange);
if (Obj)
UndoQue += Obj.Release();
}
for (ssize_t i=Min; i= 'a' && Text[i] <= 'z')
Text[i] = Text[i] - 'a' + 'A';
}
else
{
if (Text[i] >= 'A' && Text[i] <= 'Z')
Text[i] = Text[i] - 'A' + 'a';
}
}
Dirty = true;
d->SetDirty(Min, 0);
Invalidate();
SendNotify(LNotifyDocChanged);
}
}
if (Callback)
Callback(Text != NULL);
}
ssize_t LTextView4::GetLine()
{
ssize_t Idx = 0;
GetTextLine(Cursor, &Idx);
return Idx + 1;
}
void LTextView4::SetLine(int64_t i)
{
LTextLine *l = Line.ItemAt(i - 1);
if (l)
{
d->CenterCursor = true;
SetCaret(l->Start, false);
d->CenterCursor = false;
}
}
void LTextView4::DoGoto(std::function Callback)
{
LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text");
Dlg->DoModal([this, Dlg, Callback](auto d, auto code)
{
auto ok = code == IDOK && Dlg->GetStr();
if (ok)
SetLine(Dlg->GetStr().Int());
if (Callback)
Callback(ok);
delete Dlg;
});
}
LDocFindReplaceParams *LTextView4::CreateFindReplaceParams()
{
- return new GDocFindReplaceParams4;
+ return new LDocFindReplaceParams4;
}
void LTextView4::SetFindReplaceParams(LDocFindReplaceParams *Params)
{
if (Params)
{
if (d->OwnFindReplaceParams)
{
DeleteObj(d->FindReplaceParams);
}
d->OwnFindReplaceParams = false;
- d->FindReplaceParams = (GDocFindReplaceParams4*) Params;
+ d->FindReplaceParams = (LDocFindReplaceParams4*) Params;
}
}
void LTextView4::DoFindNext(std::function Callback)
{
bool Status = false;
if (InThread())
{
if (d->FindReplaceParams->Lock(_FL))
{
if (d->FindReplaceParams->LastFind)
Status = OnFind(d->FindReplaceParams->LastFind,
d->FindReplaceParams->MatchWord,
d->FindReplaceParams->MatchCase,
d->FindReplaceParams->SelectionOnly,
d->FindReplaceParams->SearchUpwards);
d->FindReplaceParams->Unlock();
}
}
else if (IsAttached())
{
Status = PostEvent(M_TEXTVIEW_FIND);
}
if (Callback)
Callback(Status);
}
void LTextView4::DoFind(std::function Callback)
{
LString u;
if (HasSelection())
{
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd);
u = LString(Text + Min, Max - Min);
}
else
{
u = d->FindReplaceParams->LastFind.Get();
}
auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto 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();
}
DoFindNext([this, Callback](bool ok)
{
Focus(true);
if (Callback)
Callback(ok);
});
},
u);
Dlg->DoModal(NULL);
}
void LTextView4::DoReplace(std::function Callback)
{
bool SingleLineSelection = false;
SingleLineSelection = HasSelection();
if (SingleLineSelection)
{
LRange Sel = GetSelectionRange();
for (ssize_t i = Sel.Start; i < Sel.End(); i++)
{
if (Text[i] == '\n')
{
SingleLineSelection = false;
break;
}
}
}
LAutoString LastFind8(SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind));
LAutoString LastReplace8(WideToUtf8(d->FindReplaceParams->LastReplace));
auto Dlg = new LReplaceDlg(this,
[this](auto Dlg, auto Action)
{
LReplaceDlg *Replace = dynamic_cast(Dlg);
LAssert(Replace != NULL);
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();
Dlg->DoModal(NULL);
}
void LTextView4::SelectWord(size_t From)
{
for (SelStart = From; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = From; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Invalidate();
}
typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n);
ptrdiff_t LTextView4::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
if (!ValidStrW(Find))
return -1;
ssize_t FindLen = StrlenW(Find);
// Setup range to search
ssize_t Begin, End;
if (SelectionOnly && HasSelection())
{
Begin = MIN(SelStart, SelEnd);
End = MAX(SelStart, SelEnd);
}
else
{
Begin = 0;
End = Size;
}
// Look through text...
ssize_t i;
bool Wrap = false;
if (Cursor > End - FindLen)
{
Wrap = true;
if (SearchUpwards)
i = End - FindLen;
else
i = Begin;
}
else
{
i = Cursor;
}
if (i < Begin) i = Begin;
if (i > End) i = End;
StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW;
char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]);
for (; SearchUpwards ? i >= Begin : i <= End - FindLen; i += SearchUpwards ? -1 : 1)
{
if
(
(MatchCase ? Text[i] : toupper(Text[i]))
==
FindCh
)
{
char16 *Possible = Text + i;
if (CmpFn(Possible, Find, FindLen) == 0)
{
if (MatchWord)
{
// Check boundaries
if (Possible > Text) // Check off the start
{
if (!IsWordBoundry(Possible[-1]))
continue;
}
if (i + FindLen < Size) // Check off the end
{
if (!IsWordBoundry(Possible[FindLen]))
continue;
}
}
LRange r(Possible - Text, FindLen);
if (!r.Overlap(Cursor))
return r.Start;
}
}
if (!Wrap && (i + 1 > End - FindLen))
{
Wrap = true;
i = Begin;
End = Cursor;
}
}
return -1;
}
bool LTextView4::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
// Not sure what this is doing???
if (HasSelection() &&
SelEnd < SelStart)
{
Cursor = SelStart;
}
#if FEATURE_HILIGHT_ALL_MATCHES
// Clear existing styles for matches
for (StyleIter s = Style.begin(); s != Style.end(); )
{
if (s->Owner == STYLE_FIND_MATCHES)
Style.Delete(s);
else
s++;
}
ssize_t FindLen = StrlenW(Find);
ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc;
if (FirstLoc >= 0)
{
SetCaret(FirstLoc, false);
SetCaret(FirstLoc + FindLen, true);
}
ssize_t Old = Cursor;
if (!SearchUpwards)
Cursor += FindLen;
while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc)
{
LAutoPtr s(new LStyle(STYLE_FIND_MATCHES));
s->Start = Loc;
s->Len = FindLen;
s->Fore = LColour(L_FOCUS_SEL_FORE);
s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE));
InsertStyle(s);
Cursor = Loc + FindLen;
}
Cursor = Old;
ScrollToOffset(Cursor);
Invalidate();
#else
ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (Loc >= 0)
{
SetCaret(Loc, false);
SetCaret(Loc + StrlenW(Find), true);
return true;
}
#endif
return false;
}
bool LTextView4::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards)
{
THREAD_CHECK();
if (ValidStrW(Find))
{
// int Max = -1;
ssize_t FindLen = StrlenW(Find);
ssize_t ReplaceLen = StrlenW(Replace);
// size_t OldCursor = Cursor;
ptrdiff_t First = -1;
while (true)
{
ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards);
if (First < 0)
{
First = Loc;
}
else if (Loc == First)
{
break;
}
if (Loc >= 0)
{
ssize_t OldSelStart = SelStart;
ssize_t OldSelEnd = SelEnd;
Delete(Loc, FindLen);
Insert(Loc, Replace, ReplaceLen);
SelStart = OldSelStart;
SelEnd = OldSelEnd - FindLen + ReplaceLen;
Cursor = Loc + ReplaceLen;
}
if (!All)
{
return Loc >= 0;
}
if (Loc < 0) break;
}
}
return false;
}
-ssize_t LTextView4::SeekLine(ssize_t Offset, GTextViewSeek Where)
+ssize_t LTextView4::SeekLine(ssize_t Offset, LTextViewSeek Where)
{
THREAD_CHECK();
switch (Where)
{
case PrevLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset--;
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case NextLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
Offset++;
break;
}
case StartLine:
{
for (; Offset > 0 && Text[Offset] != '\n'; Offset--)
;
if (Offset > 0)
Offset++;
break;
}
case EndLine:
{
for (; Offset < Size && Text[Offset] != '\n'; Offset++)
;
break;
}
default:
{
LAssert(false);
break;
}
}
return Offset;
}
bool LTextView4::OnMultiLineTab(bool In)
{
bool Status = false;
ssize_t Min = MIN(SelStart, SelEnd);
ssize_t Max = MAX(SelStart, SelEnd), i;
Min = SeekLine(Min, StartLine);
int Ls = 0;
LArray p;
for (i=Min; i=0; i--)
{
if (In)
{
// <-
ssize_t n = Indexes[i], Space = 0;
for (; Space
ssize_t Len = Indexes[i];
for (; Text[Len] != '\n' && Len Indexes[i])
{
if (HardTabs)
{
char16 Tab[] = {'\t', 0};
Insert(Indexes[i], Tab, 1);
Max++;
}
else
{
char16 *Sp = new char16[IndentSize];
if (Sp)
{
for (int n=0; nChanges.Length())
{
UndoQue += UndoCur;
UndoCur = NULL;
}
else
{
DeleteObj(UndoCur);
}
SelStart = Min;
SelEnd = Cursor = Max;
PourEnabled = true;
PourText(Min, Max - Min);
PourStyle(Min, Max - Min);
d->SetDirty(Min, Max-Min);
Invalidate();
Status = true;
return Status;
}
void LTextView4::OnSetHidden(int Hidden)
{
}
void LTextView4::OnPosChange()
{
static bool Processing = false;
if (!Processing)
{
Processing = true;
LLayout::OnPosChange();
LRect c = GetClient();
bool ScrollYNeeded = c.Y() < (Line.Length() * LineY);
bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL);
if (ScrollChange)
{
// printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded);
SetScrollBars(false, ScrollYNeeded);
}
UpdateScrollBars();
if (GetWrapType() && d->PourX != X())
{
d->PourX = X();
d->SetDirty(0, Size);
}
Processing = false;
}
}
int LTextView4::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState)
{
Formats.Supports("text/uri-list");
Formats.Supports("text/html");
Formats.Supports("UniformResourceLocatorW");
return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE;
}
int LTextView4::OnDrop(LArray &Data, LPoint Pt, int KeyState)
{
int Status = DROPEFFECT_NONE;
for (unsigned i=0; iIsBinary())
{
OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length);
OsChar *s = (OsChar*) Data->Value.Binary.Data;
int len = 0;
while (s < e && s[len])
{
len++;
}
LAutoWString w
(
(char16*)LNewConvertCp
(
LGI_WideCharset,
s,
(
sizeof(OsChar) == 1
?
"utf-8"
:
LGI_WideCharset
),
len * sizeof(*s)
)
);
Insert(Cursor, w, len);
Invalidate();
return DROPEFFECT_COPY;
}
}
else if (dd.IsFileDrop())
{
// We don't directly handle file drops... pass up to the parent
bool FoundTarget = false;
for (LViewI *p = GetParent(); p; p = p->GetParent())
{
LDragDropTarget *t = p->DropTarget();
if (t)
{
Status = t->OnDrop(Data, Pt, KeyState);
if (Status != DROPEFFECT_NONE)
{
FoundTarget = true;
break;
}
}
}
if (!FoundTarget)
{
auto Wnd = GetWindow();
if (Wnd)
{
LDropFiles files(dd);
Wnd->OnReceiveFiles(files);
}
}
}
}
return Status;
}
void LTextView4::OnCreate()
{
SetWindow(this);
DropTarget(true);
#ifndef WINDOWS
if (Ctrls.Length() == 0)
#endif
SetPulse(PULSE_TIMEOUT);
#ifndef WINDOWS
Ctrls.Add(this);
#endif
}
void LTextView4::OnEscape(LKey &K)
{
}
bool LTextView4::OnMouseWheel(double l)
{
if (VScroll)
{
int64 NewPos = VScroll->Value() + (int)l;
NewPos = limit(NewPos, 0, (ssize_t)GetLines());
VScroll->Value(NewPos);
Invalidate();
}
return true;
}
void LTextView4::OnFocus(bool f)
{
Invalidate();
}
ssize_t LTextView4::HitText(int x, int y, bool Nearest)
{
if (!Text)
return 0;
bool Down = y >= 0;
auto Y = VScroll ? VScroll->Value() : 0;
if (Y < (ssize_t)Line.Length())
y += Line[Y]->r.y1;
while (Y>=0 && Y<(ssize_t)Line.Length())
{
auto l = Line[Y];
if (l->r.Overlap(x, y))
{
// Over a line
int At = x - l->r.x1;
ssize_t Char = 0;
LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0);
Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate);
return l->Start + Char;
}
else if (y >= l->r.y1 && y <= l->r.y2)
{
// Click horizontally before of after line
if (x < l->r.x1)
return l->Start;
else if (x > l->r.x2)
return l->Start + l->Len;
}
if (Down)
Y++;
else
Y--;
}
// outside text area
if (Down)
{
if (Line.Length())
{
if (y > Line.Last()->r.y2)
{
// end of document
return Size;
}
}
}
return 0;
}
void LTextView4::Undo()
{
int Old = UndoQue.GetPos();
UndoQue.Undo();
if (Old && !UndoQue.GetPos())
{
Dirty = false;
SendNotify(LNotifyDocChanged);
}
}
void LTextView4::Redo()
{
UndoQue.Redo();
}
void LTextView4::DoContextMenu(LMouse &m)
{
LSubMenu RClick;
LAutoString ClipText;
{
LClipBoard Clip(this);
ClipText.Reset(NewStr(Clip.Text()));
}
LStyle *s = HitStyle(HitText(m.x, m.y, true));
if (s)
{
if (OnStyleMenu(s, &RClick))
{
RClick.AppendSeparator();
}
}
RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection());
RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0);
RClick.AppendSeparator();
RClick.AppendItem("Copy All", IDM_COPY_ALL, true);
RClick.AppendItem("Select All", IDM_SELECT_ALL, true);
RClick.AppendSeparator();
RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo());
RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo());
RClick.AppendSeparator();
auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true);
if (i) i->Checked(GetFixedWidthFont());
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true);
if (i) i->Checked(AutoIndent);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true);
if (i) i->Checked(ShowWhiteSpace);
i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true);
if (i) i->Checked(HardTabs);
RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true);
RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true);
if (Environment)
Environment->AppendItems(&RClick, NULL);
int Id = 0;
m.ToScreen();
switch (Id = RClick.Float(this, m))
{
case IDM_FIXED:
{
SetFixedWidthFont(!GetFixedWidthFont());
SendNotify(LNotifyFixedWidthChanged);
break;
}
case IDM_CUT:
{
Cut();
break;
}
case IDM_COPY:
{
Copy();
break;
}
case IDM_PASTE:
{
Paste();
break;
}
case IDM_COPY_ALL:
{
SelectAll();
Copy();
break;
}
case IDM_SELECT_ALL:
{
SelectAll();
break;
}
case IDM_UNDO:
{
Undo();
break;
}
case IDM_REDO:
{
Redo();
break;
}
case IDM_AUTO_INDENT:
{
AutoIndent = !AutoIndent;
break;
}
case IDM_SHOW_WHITE:
{
ShowWhiteSpace = !ShowWhiteSpace;
Invalidate();
break;
}
case IDM_HARD_TABS:
{
HardTabs = !HardTabs;
break;
}
case IDM_INDENT_SIZE:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", IndentSize);
LInput *i = new LInput(this, s, "Indent Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
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 = new LInput(this, s, "Tab Size:", "Text");
i->DoModal([this, i](auto dlg, auto code)
{
if (code)
SetTabSize((uint8_t)Atoi(i->GetStr().Get()));
delete i;
});
break;
}
default:
{
if (s)
{
OnStyleMenuClick(s, Id);
}
if (Environment)
{
Environment->OnMenu(this, Id, 0);
}
break;
}
}
}
bool LTextView4::OnStyleClick(LStyle *style, LMouse *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
if
(
(!m)
||
(m->Left() && m->Down() && m->Double())
)
{
LString s(Text + style->Start, style->Len);
if (s)
OnUrl(s);
return true;
}
break;
}
default:
break;
}
return false;
}
bool LTextView4::OnStyleMenu(LStyle *style, LSubMenu *m)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
if (LIsValidEmail(s))
m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true);
else
m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true);
m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true);
return true;
}
default:
break;
}
return false;
}
void LTextView4::OnStyleMenuClick(LStyle *style, int i)
{
switch (style->Owner)
{
case STYLE_URL:
{
LString s(Text + style->Start, style->Len);
switch (i)
{
case IDM_NEW:
case IDM_OPEN:
{
if (s)
OnUrl(s);
break;
}
case IDM_COPY_URL:
{
if (s)
{
LClipBoard Clip(this);
Clip.Text(s);
}
break;
}
}
break;
}
default:
break;
}
}
void LTextView4::OnMouseClick(LMouse &m)
{
bool Processed = false;
m.x += ScrollX;
if (m.Down())
{
if (!m.IsContextMenu())
{
Focus(true);
ssize_t Hit = HitText(m.x, m.y, true);
if (Hit >= 0)
{
SetCaret(Hit, m.Shift());
LStyle *s = HitStyle(Hit);
if (s)
Processed = OnStyleClick(s, &m);
}
if (!Processed && m.Double())
{
d->WordSelectMode = Cursor;
SelectWord(Cursor);
}
else
{
d->WordSelectMode = -1;
}
}
else
{
DoContextMenu(m);
return;
}
}
if (!Processed)
{
Capture(m.Down());
}
}
int LTextView4::OnHitTest(int x, int y)
{
#ifdef WIN32
if (GetClient().Overlap(x, y))
{
return HTCLIENT;
}
#endif
return LView::OnHitTest(x, y);
}
void LTextView4::OnMouseMove(LMouse &m)
{
m.x += ScrollX;
ssize_t Hit = HitText(m.x, m.y, true);
if (IsCapturing())
{
if (d->WordSelectMode < 0)
{
SetCaret(Hit, m.Left());
}
else
{
ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode;
ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode;
for (SelStart = Min; SelStart > 0; SelStart--)
{
if (strchr(SelectWordDelim, Text[SelStart]))
{
SelStart++;
break;
}
}
for (SelEnd = Max; SelEnd < Size; SelEnd++)
{
if (strchr(SelectWordDelim, Text[SelEnd]))
{
break;
}
}
Cursor = SelEnd;
Invalidate();
}
}
}
LCursor LTextView4::GetCursor(int x, int y)
{
LRect c = GetClient();
c.Offset(-c.x1, -c.y1);
LStyle *s = NULL;
if (c.Overlap(x, y))
{
ssize_t Hit = HitText(x, y, true);
s = HitStyle(Hit);
}
return s ? s->Cursor : LCUR_Ibeam;
}
int LTextView4::GetColumn()
{
int x = 0;
LTextLine *l = GetTextLine(Cursor);
if (l)
{
for (ssize_t i=l->Start; i> 1);
m.Target = this;
DoContextMenu(m);
}
else if (k.IsChar)
{
switch (k.vkey)
{
default:
{
// process single char input
if
(
!GetReadOnly()
&&
(
(k.c16 >= ' ' || k.vkey == LK_TAB)
&&
k.c16 != 127
)
)
{
if (k.Down())
{
// letter/number etc
if (SelStart >= 0)
{
bool MultiLine = false;
if (k.vkey == LK_TAB)
{
size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd);
for (size_t i=Min; iLen : 0;
if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize))
{
int x = GetColumn();
int Add = IndentSize - (x % IndentSize);
if (HardTabs && ((x + Add) % TabSize) == 0)
{
int Rx = x;
size_t Remove;
for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--);
ssize_t Chars = (ssize_t)Cursor - Remove;
Delete(Remove, Chars);
Insert(Remove, &k.c16, 1);
Cursor = Remove + 1;
Invalidate();
}
else
{
char16 *Sp = new char16[Add];
if (Sp)
{
for (int n=0; nLen : 0;
SetCaret(Cursor + Add, false, Len != NewLen - 1);
}
DeleteArray(Sp);
}
}
}
else
{
char16 In = k.GetChar();
if (In == '\t' &&
k.Shift() &&
Cursor > 0)
{
l = GetTextLine(Cursor);
if (Cursor > l->Start)
{
if (Text[Cursor-1] == '\t')
{
Delete(Cursor - 1, 1);
SetCaret(Cursor, false, false);
}
else if (Text[Cursor-1] == ' ')
{
ssize_t Start = (ssize_t)Cursor - 1;
while (Start >= l->Start && strchr(" \t", Text[Start-1]))
Start--;
int Depth = SpaceDepth(Text + Start, Text + Cursor);
int NewDepth = Depth - (Depth % IndentSize);
if (NewDepth == Depth && NewDepth > 0)
NewDepth -= IndentSize;
int Use = 0;
while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth)
Use++;
Delete(Start + Use, Cursor - Start - Use);
SetCaret(Start + Use, false, false);
}
}
}
else if (In && Insert(Cursor, &In, 1))
{
l = GetTextLine(Cursor);
size_t NewLen = (l) ? l->Len : 0;
SetCaret(Cursor + 1, false, Len != NewLen - 1);
}
}
}
return true;
}
break;
}
case LK_RETURN:
#if defined MAC
case LK_KEYPADENTER:
#endif
{
if (GetReadOnly())
break;
if (k.Down() && k.IsChar)
{
OnEnter(k);
}
return true;
break;
}
case LK_BACKSPACE:
{
if (GetReadOnly())
break;
if (k.Ctrl())
{
// Ctrl+H
}
else if (k.Down())
{
if (SelStart >= 0)
{
// delete selection
DeleteSelection();
}
else
{
char Del = Cursor > 0 ? Text[Cursor-1] : 0;
if (Del == ' ' && (!HardTabs || IndentSize != TabSize))
{
// Delete soft tab
int x = GetColumn();
int Max = x % IndentSize;
if (Max == 0) Max = IndentSize;
ssize_t i;
for (i=Cursor-1; i>=0; i--)
{
if (Max-- <= 0 || Text[i] != ' ')
{
i++;
break;
}
}
if (i < 0) i = 0;
if (i < Cursor - 1)
{
ssize_t Del = (ssize_t)Cursor - i;
Delete(i, Del);
// SetCursor(i, false, false);
// Invalidate();
break;
}
}
else if (Del == '\t' && HardTabs && IndentSize != TabSize)
{
int x = GetColumn();
Delete(--Cursor, 1);
for (int c=GetColumn(); c 0)
{
Delete(Cursor - 1, 1);
}
}
}
return true;
break;
}
}
}
else // not a char
{
switch (k.vkey)
{
case LK_TAB:
return true;
case LK_RETURN:
{
return !GetReadOnly();
}
case LK_BACKSPACE:
{
if (!GetReadOnly())
{
if (k.Alt())
{
if (k.Down())
{
if (k.Ctrl())
{
Redo();
}
else
{
Undo();
}
}
}
else if (k.Ctrl())
{
if (k.Down())
{
ssize_t Start = Cursor;
while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0)
Cursor--;
Delete(Cursor, Start - Cursor);
Invalidate();
}
}
return true;
}
break;
}
case LK_F3:
{
if (k.Down())
{
DoFindNext(NULL);
}
return true;
break;
}
case LK_LEFT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MIN(SelStart, SelEnd), false);
}
else if (Cursor > 0)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_StartOfLine;
}
else
if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
bool StartWhiteSpace = IsWhiteSpace(Text[n]);
bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]);
if (!StartWhiteSpace ||
Text[n] == '\n')
{
n--;
}
// Skip ws
for (; n > 0 && strchr(" \t", Text[n]); n--)
;
if (Text[n] == '\n')
{
n--;
}
else if (!StartWhiteSpace || !LeftWhiteSpace)
{
if (IsDelimiter(Text[n]))
{
for (; n > 0 && IsDelimiter(Text[n]); n--);
}
else
{
for (; n > 0; n--)
{
//IsWordBoundry(Text[n])
if (IsWhiteSpace(Text[n]) ||
IsDelimiter(Text[n]))
{
break;
}
}
}
}
if (n > 0) n++;
}
else
{
// single char
n--;
}
SetCaret(n, k.Shift());
}
}
return true;
break;
}
case LK_RIGHT:
{
if (k.Down())
{
if (SelStart >= 0 &&
!k.Shift())
{
SetCaret(MAX(SelStart, SelEnd), false);
}
else if (Cursor < Size)
{
ssize_t n = Cursor;
#ifdef MAC
if (k.System())
{
goto Jump_EndOfLine;
}
else if (k.Alt())
#else
if (k.Ctrl())
#endif
{
// word move/select
if (IsWhiteSpace(Text[n]))
{
for (; nStart, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len);
ssize_t CharX = PrevLine.CharAt(ScreenX);
SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_DOWN:
{
if (k.Alt())
return false;
if (k.Down())
{
#ifdef MAC
if (k.Ctrl())
goto LTextView4_PageDown;
#endif
auto It = GetTextLineIt(Cursor);
if (It != Line.end())
{
auto l = *It;
It++;
if (It != Line.end())
{
LTextLine *Next = *It;
LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start);
int ScreenX = CurLine.X();
LDisplayString NextLine(Font, Text + Next->Start, Next->Len);
ssize_t CharX = NextLine.CharAt(ScreenX);
SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_END:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(Size, k.Shift());
}
else
{
#ifdef MAC
Jump_EndOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
SetCaret(l->Start + l->Len, k.Shift());
}
}
}
return true;
break;
}
case LK_HOME:
{
if (k.Down())
{
if (k.Ctrl())
{
SetCaret(0, k.Shift());
}
else
{
#ifdef MAC
Jump_StartOfLine:
#endif
LTextLine *l = GetTextLine(Cursor);
if (l)
{
char16 *Line = Text + l->Start;
char16 *s;
char16 SpTab[] = {' ', '\t', 0};
for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++);
ssize_t Whitespace = SubtractPtr(s, Line);
if (l->Start + Whitespace == Cursor)
{
SetCaret(l->Start, k.Shift());
}
else
{
SetCaret(l->Start + Whitespace, k.Shift());
}
}
}
}
return true;
break;
}
case LK_PAGEUP:
{
#ifdef MAC
LTextView4_PageUp:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_PAGEDOWN:
{
#ifdef MAC
LTextView4_PageDown:
#endif
if (k.Down())
{
LTextLine *l = GetTextLine(Cursor);
if (l)
{
int DisplayLines = Y() / LineY;
ssize_t CurLine = Line.IndexOf(l);
LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1));
if (New)
{
SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift());
}
}
}
return true;
break;
}
case LK_INSERT:
{
if (k.Down())
{
if (k.Ctrl())
{
Copy();
}
else if (k.Shift())
{
if (!GetReadOnly())
{
Paste();
}
}
}
return true;
break;
}
case LK_DELETE:
{
if (!GetReadOnly())
{
if (k.Down())
{
if (SelStart >= 0)
{
if (k.Shift())
{
Cut();
}
else
{
DeleteSelection();
}
}
else if (Cursor < Size &&
Delete(Cursor, 1))
{
Invalidate();
}
}
return true;
}
break;
}
default:
{
if (k.c16 == 17) break;
if (k.CtrlCmd() &&
!k.Alt())
{
switch (k.GetChar())
{
case 0xbd: // Ctrl+'-'
{
if (k.Down() &&
Font->PointSize() > 1)
{
Font->PointSize(Font->PointSize() - 1);
OnFontChange();
Invalidate();
}
break;
}
case 0xbb: // Ctrl+'+'
{
if (k.Down() &&
Font->PointSize() < 100)
{
Font->PointSize(Font->PointSize() + 1);
OnFontChange();
Invalidate();
}
break;
}
case 'a':
case 'A':
{
if (k.Down())
{
// select all
SelStart = 0;
SelEnd = Size;
Invalidate();
}
return true;
break;
}
case 'y':
case 'Y':
{
if (!GetReadOnly())
{
if (k.Down())
{
Redo();
}
return true;
}
break;
}
case 'z':
case 'Z':
{
if (!GetReadOnly())
{
if (k.Down())
{
if (k.Shift())
{
Redo();
}
else
{
Undo();
}
}
return true;
}
break;
}
case 'x':
case 'X':
{
if (!GetReadOnly())
{
if (k.Down())
{
Cut();
}
return true;
}
break;
}
case 'c':
case 'C':
{
if (k.Shift())
return false;
if (k.Down())
Copy();
return true;
break;
}
case 'v':
case 'V':
{
if (!k.Shift() &&
!GetReadOnly())
{
if (k.Down())
{
Paste();
}
return true;
}
break;
}
case 'f':
{
if (k.Down())
{
DoFind(NULL);
}
return true;
break;
}
case 'g':
case 'G':
{
if (k.Down())
{
DoGoto(NULL);
}
return true;
break;
}
case 'h':
case 'H':
{
if (k.Down())
{
DoReplace(NULL);
}
return true;
break;
}
case 'u':
case 'U':
{
if (!GetReadOnly())
{
if (k.Down())
{
DoCase(NULL, k.Shift());
}
return true;
}
break;
}
case LK_RETURN:
{
if (!GetReadOnly() && !k.Shift())
{
if (k.Down())
{
OnEnter(k);
}
return true;
}
break;
}
}
}
break;
}
}
}
return false;
}
void LTextView4::OnEnter(LKey &k)
{
// enter
if (SelStart >= 0)
{
DeleteSelection();
}
char16 InsertStr[256] = {'\n', 0};
LTextLine *CurLine = GetTextLine(Cursor);
if (CurLine && AutoIndent)
{
int WsLen = 0;
for (; WsLen < CurLine->Len &&
WsLen < (Cursor - CurLine->Start) &&
strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++);
if (WsLen > 0)
{
memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16));
InsertStr[WsLen+1] = 0;
}
}
if (Insert(Cursor, InsertStr, StrlenW(InsertStr)))
{
SetCaret(Cursor + StrlenW(InsertStr), false, true);
}
}
int LTextView4::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin)
{
int w = x;
int Size = f->TabSize();
for (char16 *c = s; SubtractPtr(c, s) < Len; )
{
if (*c == 9)
{
w = ((((w-Origin) + Size) / Size) * Size) + Origin;
c++;
}
else
{
char16 *e;
for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++);
LDisplayString ds(f, c, SubtractPtr(e, c));
w += ds.X();
c = e;
}
}
return w - x;
}
int LTextView4::ScrollYLine()
{
return (VScroll) ? (int)VScroll->Value() : 0;
}
int LTextView4::ScrollYPixel()
{
return ScrollYLine() * LineY;
}
LRect LTextView4::DocToScreen(LRect r)
{
r.Offset(0, d->rPadding.y1 - ScrollYPixel());
return r;
}
void LTextView4::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour)
{
pDC->Colour(colour);
pDC->Rectangle(&r);
}
void LTextView4::OnPaint(LSurface *pDC)
{
#if LGI_EXCEPTIONS
try
{
#endif
#if PROFILE_PAINT
char s[256];
sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size);
LProfile Prof(s);
#endif
if (d->LayoutDirty)
{
#if PROFILE_PAINT
Prof.Add("PourText");
#endif
PourText(d->DirtyStart, d->DirtyLen);
#if PROFILE_PAINT
Prof.Add("PourStyle");
#endif
PourStyle(d->DirtyStart, d->DirtyLen);
d->LayoutDirty = false;
}
#if PROFILE_PAINT
Prof.Add("Setup");
#endif
LRect r = GetClient();
r.x2 += ScrollX;
int Ox, Oy;
pDC->GetOrigin(Ox, Oy);
pDC->SetOrigin(Ox+ScrollX, Oy);
#if 0
// Coverage testing...
pDC->Colour(Rgb24(255, 0, 255), 24);
pDC->Rectangle();
#endif
LSurface *pOut = pDC;
bool DrawSel = false;
bool HasFocus = Focus();
// printf("%s:%i - HasFocus = %i\n", _FL, HasFocus);
LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE));
LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK));
LCss::ColorDef ForeDef, BkDef;
if (GetCss())
{
ForeDef = GetCss()->Color();
BkDef = GetCss()->BackgroundColor();
}
LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT));
LColour Back
(
/*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb
?
LColour(BkDef.Rgb32, 32)
:
Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED)
);
// LColour Whitespace = Fore.Mix(Back, 0.85f);
if (!Enabled())
{
Fore = LColour(L_LOW);
Back = LColour(L_MED);
}
#ifdef DOUBLE_BUFFER_PAINT
LMemDC *pMem = new LMemDC;
pOut = pMem;
#endif
if (Text &&
Font
#ifdef DOUBLE_BUFFER_PAINT
&&
pMem &&
pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits())
#endif
)
{
ssize_t SelMin = MIN(SelStart, SelEnd);
ssize_t SelMax = MAX(SelStart, SelEnd);
// font properties
Font->Colour(Fore, Back);
// Font->WhitespaceColour(Whitespace);
Font->Transparent(false);
// draw margins
pDC->Colour(PAINT_BORDER);
// top margin
pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1);
// left margin
{
LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2);
OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER);
}
// draw lines of text
int k = ScrollYLine();
LTextLine *l = NULL;
int Dy = 0;
if (k < Line.Length())
Dy = -Line[k]->r.y1;
ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes
if (k < Line.Length() &&
(l = Line[k]) &&
SelStart >= 0 &&
SelStart < l->Start &&
SelEnd > l->Start)
{
// start of visible area is in selection
// init to selection colour
DrawSel = true;
Font->Colour(SelectedText, SelectedBack);
NextSelection = SelMax;
}
StyleIter Si = Style.begin();
LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0);
DocOffset = (l) ? l->r.y1 : 0;
#if PROFILE_PAINT
Prof.Add("foreach Line loop");
#endif
// loop through all visible lines
int y = d->rPadding.y1;
while ( k < Line.Length() &&
(l = Line[k]) &&
l->r.y1+Dy < r.Y())
{
LRect Tr = l->r;
#ifdef DOUBLE_BUFFER_PAINT
Tr.Offset(-Tr.x1, -Tr.y1);
#else
Tr.Offset(0, y - Tr.y1);
#endif
//LRect OldTr = Tr;
// deal with selection change on beginning of line
if (NextSelection == l->Start)
{
// selection change
DrawSel = !DrawSel;
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
// How many chars on this line have we
// processed so far:
ssize_t Done = 0;
bool LineHasSelection = NextSelection >= l->Start &&
NextSelection < l->Start + l->Len;
// Fractional pixels we have moved so far:
int MarginF = d->rPadding.x1 << LDisplayString::FShift;
int FX = MarginF;
int FY = Tr.y1 << LDisplayString::FShift;
// loop through all sections of similar text on a line
while (Done < l->Len)
{
// decide how big this block is
int RtlTrailingSpace = 0;
ssize_t Cur = l->Start + Done;
ssize_t Block = l->Len - Done;
// check for style change
if (NextStyle &&
(ssize_t)NextStyle->End() <= l->Start)
NextStyle = GetNextStyle(Si);
if (NextStyle)
{
// start
if (l->Overlap(NextStyle->Start) &&
NextStyle->Start > Cur &&
NextStyle->Start - Cur < Block)
{
Block = NextStyle->Start - Cur;
}
// end
ssize_t StyleEnd = NextStyle->Start + NextStyle->Len;
if (l->Overlap(StyleEnd) &&
StyleEnd > Cur &&
StyleEnd - Cur < Block)
{
Block = StyleEnd - Cur;
}
}
// check for next selection change
// this may truncate the style
if (NextSelection > Cur &&
NextSelection - Cur < Block)
{
Block = NextSelection - Cur;
}
LAssert(Block != 0); // sanity check
if (NextStyle && // There is a style
(Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block
Cur >= NextStyle->Start && // && we're inside the styled area
Cur < NextStyle->Start+NextStyle->Len)
{
LFont *Sf = NextStyle->Font ? NextStyle->Font : Font;
if (Sf)
{
// draw styled text
if (NextStyle->Fore.IsValid())
Sf->Fore(NextStyle->Fore);
if (NextStyle->Back.IsValid())
Sf->Back(NextStyle->Back);
else if (l->Back.IsValid())
Sf->Back(l->Back);
else
Sf->Back(Back);
Sf->Transparent(false);
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Sf,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
if (NextStyle->Decor == LCss::TextDecorSquiggle)
{
pOut->Colour(NextStyle->DecorColour);
int x = FX >> LDisplayString::FShift;
int End = x + Ds.X();
while (x < End)
{
pOut->Set(x, Tr.y2-(x%2));
x++;
}
}
FX += Ds.FX();
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Sf->Colour(fore, back);
}
else LAssert(0);
}
else
{
// draw a block of normal text
LAssert(l->Start + Done >= 0);
LDisplayString Ds( Font,
MapText(Text + (l->Start + Done),
Block,
RtlTrailingSpace != 0),
Block + RtlTrailingSpace);
Ds.SetDrawOffsetF(FX - MarginF);
Ds.ShowVisibleTab(ShowWhiteSpace);
Ds.FDraw(pOut, FX, FY, 0, LineHasSelection);
FX += Ds.FX();
}
if (NextStyle &&
Cur+Block >= NextStyle->Start+NextStyle->Len)
{
// end of this styled block
NextStyle = GetNextStyle(Si);
}
if (NextSelection == Cur+Block)
{
// selection change
DrawSel = !DrawSel;
if (DrawSel)
{
Font->Colour(SelectedText, SelectedBack);
}
else
{
LColour fore = l->c.IsValid() ? l->c : Fore;
LColour back = l->Back.IsValid() ? l->Back : Back;
Font->Colour(fore, back);
}
NextSelection = (NextSelection == SelMin) ? SelMax : -1;
}
Done += Block + RtlTrailingSpace;
} // end block loop
Tr.x1 = FX >> LDisplayString::FShift;
// eol processing
ssize_t EndOfLine = l->Start+l->Len;
if (EndOfLine >= SelMin && EndOfLine < SelMax)
{
// draw the '\n' at the end of the line as selected
// LColour bk = Font->Back();
pOut->Colour(Font->Back());
pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2);
Tr.x2 += 7;
}
else Tr.x2 = Tr.x1;
// draw any space after text
pOut->Colour(PAINT_AFTER_LINE);
pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2);
// cursor?
if (HasFocus)
{
// draw the cursor if on this line
if (Cursor >= l->Start && Cursor <= l->Start+l->Len)
{
CursorPos.ZOff(1, LineY-1);
ssize_t At = Cursor-l->Start;
LDisplayString Ds(Font, MapText(Text+l->Start, At), At);
Ds.ShowVisibleTab(ShowWhiteSpace);
int CursorX = Ds.X();
CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1);
if (CanScrollX)
{
// Cursor on screen check
LRect Scr = GetClient();
Scr.Offset(ScrollX, 0);
LRect Cur = CursorPos;
if (Cur.x2 > Scr.x2 - 5) // right edge check
{
ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40;
Invalidate();
}
else if (Cur.x1 < Scr.x1 && ScrollX > 0)
{
ScrollX = MAX(0, Cur.x1 - 40);
Invalidate();
}
}
if (Blink)
{
LRect c = CursorPos;
#ifdef DOUBLE_BUFFER_PAINT
c.Offset(-d->rPadding.x1, -y);
#endif
pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192));
pOut->Rectangle(&c);
}
#if WINNATIVE
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
COMPOSITIONFORM Cf;
Cf.dwStyle = CFS_POINT;
Cf.ptCurrentPos.x = CursorPos.x1;
Cf.ptCurrentPos.y = CursorPos.y1;
ImmSetCompositionWindow(hIMC, &Cf);
ImmReleaseContext(Handle(), hIMC);
}
#endif
}
}
#if DRAW_LINE_BOXES
{
uint Style = pDC->LineStyle(LSurface::LineAlternate);
LColour Old = pDC->Colour(LColour::Red);
pDC->Box(&OldTr);
pDC->Colour(Old);
pDC->LineStyle(Style);
LString s;
s.Printf("%i, %i", Line.IndexOf(l), l->Start);
LDisplayString ds(LSysFont, s);
LSysFont->Transparent(true);
ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1);
}
#endif
#ifdef DOUBLE_BUFFER_PAINT
// dump to screen
pDC->Blt(d->rPadding.x1, y, pOut);
#endif
y += LineY;
k++;
} // end of line loop
// draw any space under the lines
if (y <= r.y2)
{
pDC->Colour(Back);
// pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2);
}
#ifdef DOUBLE_BUFFER_PAINT
DeleteObj(pMem);
#endif
}
else
{
// default drawing: nothing
pDC->Colour(Back);
pDC->Rectangle(&r);
}
// _PaintTime = LCurrentTime() - StartTime;
#ifdef PAINT_DEBUG
if (GetNotify())
{
char s[256];
sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime);
LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s);
GetNotify()->OnEvent(&m);
}
#endif
// printf("PaintTime: %ims\n", _PaintTime);
#if LGI_EXCEPTIONS
}
catch (...)
{
LgiMsg(this, "LTextView4::OnPaint crashed.", "Lgi");
}
#endif
}
LMessage::Result LTextView4::OnEvent(LMessage *Msg)
{
switch (Msg->Msg())
{
case M_TEXT_UPDATE_NAME:
{
if (d->Lock(_FL))
{
Name(d->SetName);
d->SetName.Empty();
d->Unlock();
}
break;
}
case M_TEXTVIEW_FIND:
{
if (InThread())
DoFindNext(NULL);
else
LgiTrace("%s:%i - Not in thread.\n", _FL);
break;
}
case M_TEXTVIEW_REPLACE:
{
// DoReplace();
break;
}
case M_CUT:
{
Cut();
break;
}
case M_COPY:
{
Copy();
break;
}
case M_PASTE:
{
Paste();
break;
}
#if defined WIN32
case WM_GETTEXTLENGTH:
{
return Size;
}
case WM_GETTEXT:
{
int Chars = (int)Msg->A();
char *Out = (char*)Msg->B();
if (Out)
{
char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars);
if (In)
{
int Len = (int)strlen(In);
memcpy(Out, In, Len);
DeleteArray(In);
return Len;
}
}
return 0;
}
/* This is broken... the IME returns garbage in the buffer. :(
case WM_IME_COMPOSITION:
{
if (Msg->b & GCS_RESULTSTR)
{
HIMC hIMC = ImmGetContext(Handle());
if (hIMC)
{
int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);
char *Buf = new char[Size];
if (Buf)
{
ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size);
char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size);
if (Utf)
{
Insert(Cursor, Utf, StrlenW(Utf));
DeleteArray(Utf);
}
DeleteArray(Buf);
}
ImmReleaseContext(Handle(), hIMC);
}
return 0;
}
break;
}
*/
#endif
}
return LLayout::OnEvent(Msg);
}
int LTextView4::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl->GetId() == IDC_VSCROLL && VScroll)
{
if (n.Type == LNotifyScrollBarCreate)
{
UpdateScrollBars();
}
Invalidate();
}
return 0;
}
void LTextView4::InternalPulse()
{
if (!ReadOnly)
{
uint64 Now = LCurrentTime();
if (!BlinkTs)
BlinkTs = Now;
else if (Now - BlinkTs > CURSOR_BLINK)
{
Blink = !Blink;
LRect p = CursorPos;
p.Offset(-ScrollX, 0);
Invalidate(&p);
BlinkTs = Now;
}
}
if (PartialPour)
PourText(Size, 0);
}
void LTextView4::OnPulse()
{
#ifdef WINDOWS
InternalPulse();
#else
for (auto c: Ctrls)
c->InternalPulse();
#endif
}
void LTextView4::OnUrl(char *Url)
{
if (Environment)
Environment->OnNavigate(this, Url);
else
{
LUri u(Url);
bool Email = LIsValidEmail(Url);
const char *Proto = Email ? "mailto" : u.sProtocol;
LString App = LGetAppForProtocol(Proto);
if (App)
LExecute(App, Url);
else
LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto);
}
}
bool LTextView4::OnLayout(LViewLayoutInfo &Inf)
{
Inf.Width.Min = 32;
Inf.Width.Max = -1;
Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4;
Inf.Height.Max = -1;
return true;
}
///////////////////////////////////////////////////////////////////////////////
class LTextView4_Factory : public LViewFactory
{
LView *NewView(const char *Class, LRect *Pos, const char *Text)
{
if (_stricmp(Class, "LTextView4") == 0)
{
return new LTextView4(-1, 0, 0, 2000, 2000);
}
return 0;
}
} TextView4_Factory;
diff --git a/src/common/Widgets/CheckBox.cpp b/src/common/Widgets/CheckBox.cpp
--- a/src/common/Widgets/CheckBox.cpp
+++ b/src/common/Widgets/CheckBox.cpp
@@ -1,463 +1,468 @@
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/SkinEngine.h"
#include "lgi/common/CheckBox.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/StringLayout.h"
#include "lgi/common/LgiRes.h"
static int PadX1Px = 20;
static int PadX2Px = 6;
#ifdef MAC
static int PadYPx = 8;
static int TextYOffset = 6;
#else
static int PadYPx = 0;
static int TextYOffset = 0;
#endif
static int MinYSize = 16;
class LCheckBoxPrivate : public LMutex, public LStringLayout
{
LCheckBox *Ctrl;
public:
int64 Val;
bool Over;
bool Three;
LRect ValuePos;
LCheckBoxPrivate(LCheckBox *ctrl) :
LMutex("LCheckBoxPrivate"),
LStringLayout(LAppInst->GetFontCache())
{
Ctrl = ctrl;
Val = 0;
Over = false;
Three = false;
Wrap = true;
AmpersandToUnderline = true;
ValuePos.ZOff(-1, -1);
}
bool PreLayout(int32 &Min, int32 &Max)
{
if (Lock(_FL))
{
DoPreLayout(Min, Max);
Unlock();
}
else return false;
return true;
}
bool Layout(int Px)
{
if (Lock(_FL))
{
DoLayout(Px, MinYSize);
Unlock();
}
else return false;
return true;
}
};
///////////////////////////////////////////////////////////////////////////////////////////
// Check box
LCheckBox::LCheckBox(int id, int x, int y, int cx, int cy, const char *name, int InitState) :
ResObject(Res_CheckBox)
{
d = new LCheckBoxPrivate(this);
Name(name);
LPoint Max = d->GetMax();
if (cx < 0) cx = Max.x + PadX1Px + PadX2Px;
if (cy < 0) cy = MAX(Max.y, MinYSize) + PadYPx;
d->Val = InitState;
LRect r(x, y, x+cx, y+cy);
SetPos(r);
SetId(id);
SetTabStop(true);
}
LCheckBox::~LCheckBox()
{
DeleteObj(d);
}
#ifdef WINNATIVE
int LCheckBox::SysOnNotify(int Msg, int Code)
{
return 0;
}
#endif
void LCheckBox::OnAttach()
{
LResources::StyleElement(this);
OnStyleChange();
LView::OnAttach();
}
void LCheckBox::OnStyleChange()
{
if (d->Lock(_FL))
{
d->Empty();
d->Add(LView::Name(), GetCss());
d->DoLayout(X());
d->Unlock();
Invalidate();
}
}
int LCheckBox::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl == (LViewI*)this && n.Type == LNotifyActivate)
{
Value(!Value());
}
return 0;
}
LMessage::Result LCheckBox::OnEvent(LMessage *m)
{
return LView::OnEvent(m);
}
bool LCheckBox::ThreeState()
{
return d->Three;
}
void LCheckBox::ThreeState(bool t)
{
d->Three = t;
}
int64 LCheckBox::Value()
{
return d->Val;
}
void LCheckBox::Value(int64 i)
{
if (d->Val != i)
{
d->Val = i;
Invalidate(&d->ValuePos);
SendNotify(LNotifyValueChanged);
}
}
bool LCheckBox::Name(const char *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::Name(n);
d->Empty();
d->Add(n, GetCss());
d->SetBaseFont(GetFont());
auto x = X();
d->DoLayout(x ? x : GdcD->X());
d->Unlock();
}
return Status;
}
bool LCheckBox::NameW(const char16 *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::NameW(n);
d->Empty();
d->Add(LBase::Name(), GetCss());
d->SetBaseFont(GetFont());
auto x = X();
d->DoLayout(x ? x : GdcD->X());
d->Unlock();
}
return Status;
}
void LCheckBox::SetFont(LFont *Fnt, bool OwnIt)
{
LAssert(Fnt && Fnt->Handle());
if (d->Lock(_FL))
{
LView::SetFont(Fnt, OwnIt);
d->Unlock();
}
d->Layout(X());
Invalidate();
}
void LCheckBox::OnMouseClick(LMouse &m)
{
if (Enabled())
{
int Click = IsCapturing();
Capture(d->Over = m.Down());
if (m.Down())
{
Focus(true);
}
LRect r(0, 0, X()-1, Y()-1);
if (!m.Down() &&
r.Overlap(m.x, m.y) &&
Click)
{
if (d->Three)
{
switch (d->Val)
{
case 0:
Value(2); break;
case 2:
Value(1); break;
default:
Value(0); break;
}
}
else
{
Value(!d->Val);
}
}
else
{
Invalidate(&d->ValuePos);
}
}
}
void LCheckBox::OnMouseEnter(LMouse &m)
{
if (IsCapturing())
{
d->Over = true;
Invalidate(&d->ValuePos);
}
}
void LCheckBox::OnMouseExit(LMouse &m)
{
if (IsCapturing())
{
d->Over = false;
Invalidate(&d->ValuePos);
}
}
bool LCheckBox::OnKey(LKey &k)
{
switch (k.vkey)
{
case LK_SPACE:
{
if (!k.Down())
Value(!Value());
return true;
}
}
return false;
}
void LCheckBox::OnFocus(bool f)
{
Invalidate();
}
void LCheckBox::OnPosChange()
{
d->Layout(X());
}
bool LCheckBox::OnLayout(LViewLayoutInfo &Inf)
{
if (!Inf.Width.Min)
{
auto n = Name();
if (n)
{
d->PreLayout(Inf.Width.Min,
Inf.Width.Max);
// FIXME: no wrapping support so....
Inf.Width.Min = Inf.Width.Max;
Inf.Width.Min += PadX1Px + PadX2Px;
Inf.Width.Max += PadX1Px + PadX2Px;
}
else
{
- Inf.Width.Min = Inf.Width.Max = BoxSize();
+ auto Fnt = GetFont();
+ Inf.Width.Min = Inf.Width.Max = Fnt->Ascent() + 2;
}
}
else
{
d->Layout(Inf.Width.Max);
Inf.Height.Min = d->GetMin().y + PadYPx;
Inf.Height.Max = d->GetMax().y + PadYPx;
}
return true;
}
int LCheckBox::BoxSize()
{
auto Fnt = GetFont();
int Px = (int) Fnt->Ascent() + 2;
+
+ printf("BoxSize Px=%i Y()=%i\n", Px, Y());
+
if (Px > Y())
Px = Y();
+
return Px;
}
void LCheckBox::OnPaint(LSurface *pDC)
{
#if 0
pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle();
#endif
if (LApp::SkinEngine &&
TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_CHECKBOX))
{
// auto Fnt = GetFont();
int Px = BoxSize();
LSkinState State;
State.pScreen = pDC;
State.MouseOver = d->Over;
State.aText = d->GetStrs();
d->ValuePos.Set(0, 0, Px-1, Px-1);
d->ValuePos.Offset(0, (Y()-d->ValuePos.Y())>>1);
State.Rect = d->ValuePos;
LApp::SkinEngine->OnPaint_LCheckBox(this, &State);
}
else
{
bool en = Enabled();
LRect r = GetClient();
#if defined MAC && !LGI_COCOA
d->ValuePos.Set(0, 0, PadX1Px, MinYSize);
#else
d->ValuePos.Set(0, 0, 12, 12);
#endif
if (d->ValuePos.y2 < r.y2)
{
pDC->Colour(L_MED);
pDC->Rectangle(0, d->ValuePos.y2+1, d->ValuePos.x2, r.y2);
}
LRect t = r;
t.x1 = d->ValuePos.x2 + 1;
// LColour cFore = StyleColour(LCss::PropColor, LC_TEXT);
LColour cBack = StyleColour(LCss::PropBackgroundColor, LColour(L_MED));
if (d->Lock(_FL))
{
int Ty = MAX(0, r.Y() - d->GetBounds().Y()) >> 1;
LPoint pt(t.x1, t.y1 + MIN(Ty, TextYOffset));
d->Paint(pDC, pt, cBack, t, en, false);
d->Unlock();
}
#if defined LGI_CARBON
if (!cBack.IsTransparent())
{
pDC->Colour(cBack);
pDC->Rectangle(d->ValuePos.x1, d->ValuePos.y1, d->ValuePos.x2, Y()-1);
}
LRect c = GetClient();
#if 0
pDC->Colour(LColour(255, 0, 0));
pDC->Box(&c);
pDC->Line(c.x1, c.y1, c.x2, c.y2);
pDC->Line(c.x2, c.y1, c.x1, c.y2);
#endif
for (LViewI *v = this; v && !v->Handle(); v = v->GetParent())
{
LRect p = v->GetPos();
c.Offset(p.x1, p.y1);
}
HIRect Bounds = c;
HIThemeButtonDrawInfo Info;
HIRect LabelRect;
Info.version = 0;
Info.state = d->Val ? kThemeStatePressed : (Enabled() ? kThemeStateActive : kThemeStateInactive);
Info.kind = kThemeCheckBox;
Info.value = d->Val ? kThemeButtonOn : kThemeButtonOff;
Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone;
OSStatus e = HIThemeDrawButton( &Bounds,
&Info,
pDC->Handle(),
kHIThemeOrientationNormal,
&LabelRect);
if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e);
#else
LWideBorder(pDC, d->ValuePos, DefaultSunkenEdge);
pDC->Colour(d->Over || !en ? L_MED : L_WORKSPACE);
pDC->Rectangle(&d->ValuePos);
pDC->Colour(en ? L_TEXT : L_LOW);
if (d->Three && d->Val == 2)
{
for (int y=d->ValuePos.y1; y<=d->ValuePos.y2; y++)
{
for (int x=d->ValuePos.x1; x<=d->ValuePos.x2; x++)
{
if ( (x&1) ^ (y&1) )
{
pDC->Set(x, y);
}
}
}
}
else if (d->Val)
{
pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-1);
pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+2, d->ValuePos.x2-2, d->ValuePos.y2-1);
pDC->Line(d->ValuePos.x1+2, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-2);
pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+1);
pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-2, d->ValuePos.x2-2, d->ValuePos.y1+1);
pDC->Line(d->ValuePos.x1+2, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+2);
}
#endif
}
}
diff --git a/src/common/Widgets/RadioGroup.cpp b/src/common/Widgets/RadioGroup.cpp
--- a/src/common/Widgets/RadioGroup.cpp
+++ b/src/common/Widgets/RadioGroup.cpp
@@ -1,861 +1,861 @@
#if !defined(_WIN32) || (XP_BUTTON != 0)
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/SkinEngine.h"
#include "lgi/common/RadioGroup.h"
#include "lgi/common/CheckBox.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/StringLayout.h"
#define RADIO_GRID 4
///////////////////////////////////////////////////////////////////////////////////////////
// Radio group
static int MinYSize = 16;
class LRadioGroupPrivate : public LMutex, public LStringLayout
{
LRadioGroup *Ctrl;
public:
static int NextId;
int Val;
int MaxLayoutWidth;
LHashTbl,LViewLayoutInfo*> Info;
LRadioGroupPrivate(LRadioGroup *g) :
LMutex("LRadioGroupPrivate"),
LStringLayout(LAppInst->GetFontCache())
{
Ctrl = g;
Val = 0;
MaxLayoutWidth = 0;
AmpersandToUnderline = true;
}
~LRadioGroupPrivate()
{
Info.DeleteObjects();
}
bool PreLayout(int32 &Min, int32 &Max)
{
if (Lock(_FL))
{
DoPreLayout(Min, Max);
Unlock();
}
else return false;
return true;
}
bool Layout(int Px)
{
if (Lock(_FL))
{
DoLayout(Px, MinYSize);
Unlock();
}
else return false;
return true;
}
};
int LRadioGroupPrivate::NextId = 10000;
LRadioGroup::LRadioGroup(int id, int x, int y, int cx, int cy, const char *name, int Init)
: ResObject(Res_Group)
{
d = new LRadioGroupPrivate(this);
Name(name);
LRect r(x, y, x+cx, y+cy);
SetPos(r);
SetId(id);
d->Val = Init;
LResources::StyleElement(this);
}
LRadioGroup::~LRadioGroup()
{
DeleteObj(d);
}
void LRadioGroup::OnStyleChange()
{
if (d->Lock(_FL))
{
d->Empty();
d->Add(LView::Name(), GetCss());
d->DoLayout(X());
d->Unlock();
Invalidate();
}
}
bool LRadioGroup::OnLayout(LViewLayoutInfo &Inf)
{
auto children = IterateViews();
const int BORDER_PX = 2;
int MinPx = (RADIO_GRID + BORDER_PX) * 2;
if (!Inf.Width.Max)
{
// Work out the width...
d->PreLayout(Inf.Width.Min, Inf.Width.Max);
Inf.Width.Min += MinPx;
Inf.Width.Max += MinPx;
d->Info.DeleteObjects();
// Inf.Width.Min = 16 + TextPx;
// Inf.Width.Max = RADIO_GRID + BORDER_PX * 2;
for (LViewI *w: children)
{
LAutoPtr c(new LViewLayoutInfo);
if (w->OnLayout(*c))
{
// Layout enabled control
Inf.Width.Min = MAX(Inf.Width.Min, c->Width.Min + MinPx);
Inf.Width.Max += c->Width.Max + RADIO_GRID;
d->Info.Add(w, c.Release());
}
else
{
// Non layout enabled control
Inf.Width.Min = MAX(Inf.Width.Min, w->X() + (RADIO_GRID << 1));
Inf.Width.Max += w->X() + RADIO_GRID;
}
}
if (Inf.Width.Max < Inf.Width.Min)
Inf.Width.Max = Inf.Width.Min;
d->MaxLayoutWidth = Inf.Width.Max;
}
else
{
d->Layout(Inf.Width.Max);
Inf.Height.Min = d->GetMin().y + MinPx;
Inf.Height.Max = d->GetMax().y + MinPx;
// Working out the height, and positioning the controls
// Inf.Height.Min = d->Txt ? d->Txt->Y() : 16;
bool Horiz = d->MaxLayoutWidth <= Inf.Width.Max;
int Cx = BORDER_PX + RADIO_GRID, Cy = d->GetMin().y;
int LastY = 0;
for (LViewI *w: children)
{
LViewLayoutInfo *c = d->Info.Find(w);
if (c)
{
if (w->OnLayout(*c))
{
LRect r(Cx, Cy, Cx + c->Width.Max - 1, Cy + c->Height.Max - 1);
w->SetPos(r);
if (Horiz)
// Horizontal layout
Cx += r.X() + RADIO_GRID;
else
// Vertical layout
Cy += r.Y() + RADIO_GRID;
LastY = MAX(LastY, r.y2);
}
else LAssert(!"This shouldn't fail.");
}
else
{
// Non layout control... just use existing size
LRect r = w->GetPos();
r.Offset(Cx - r.x1, Cy - r.y1);
w->SetPos(r);
if (Horiz)
// Horizontal layout
Cx += r.X() + RADIO_GRID;
else
// Vertical layout
Cy += r.Y() + RADIO_GRID;
LastY = MAX(LastY, r.y2);
}
}
Inf.Height.Min = Inf.Height.Max = LastY + RADIO_GRID * 2 + BORDER_PX;
}
return true;
}
void LRadioGroup::OnAttach()
{
LResources::StyleElement(this);
OnStyleChange();
LView::OnAttach();
}
LMessage::Result LRadioGroup::OnEvent(LMessage *m)
{
return LView::OnEvent(m);
}
bool LRadioGroup::Name(const char *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::Name(n);
d->Empty();
d->Add(n, GetCss());
d->SetBaseFont(GetFont());
d->DoLayout(X());
d->Unlock();
}
return Status;
}
bool LRadioGroup::NameW(const char16 *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::NameW(n);
d->Empty();
d->Add(LBase::Name(), GetCss());
d->SetBaseFont(GetFont());
d->DoLayout(X());
d->Unlock();
}
return Status;
}
void LRadioGroup::SetFont(LFont *Fnt, bool OwnIt)
{
LAssert(Fnt && Fnt->Handle());
if (d->Lock(_FL))
{
LView::SetFont(Fnt, OwnIt);
d->Unlock();
}
d->Layout(X());
Invalidate();
}
void LRadioGroup::OnCreate()
{
AttachChildren();
Value(d->Val);
}
int64 LRadioGroup::Value()
{
int i=0;
for (auto w: Children)
{
LRadioButton *But = dynamic_cast(w);
if (But)
{
if (But->Value())
{
d->Val = i;
break;
}
i++;
}
}
return d->Val;
}
void LRadioGroup::Value(int64 Which)
{
d->Val = (int)Which;
int i=0;
for (auto w: Children)
{
LRadioButton *But = dynamic_cast(w);
if (But)
{
if (i == Which)
{
But->Value(true);
break;
}
i++;
}
}
}
int LRadioGroup::OnNotify(LViewI *Ctrl, LNotification n)
{
LViewI *v = GetNotify() ? GetNotify() : GetParent();
if (v)
{
if (dynamic_cast(Ctrl))
{
return v->OnNotify(this, n);
}
else
{
return v->OnNotify(Ctrl, n);
}
}
return 0;
}
void LRadioGroup::OnPaint(LSurface *pDC)
{
if (LApp::SkinEngine &&
TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_GROUP))
{
LSkinState State;
State.pScreen = pDC;
State.MouseOver = false;
State.aText = d->GetStrs();
LApp::SkinEngine->OnPaint_LRadioGroup(this, &State);
}
else
{
// LColour Fore = StyleColour(LCss::PropColor, LC_TEXT);
LColour Back = StyleColour(LCss::PropBackgroundColor, LColour(L_MED));
if (!Back.IsTransparent())
{
pDC->Colour(Back);
pDC->Rectangle();
}
int y = d->GetMin().y;
LRect b(0, y/2, X()-1, Y()-1);
LWideBorder(pDC, b, EdgeXpChisel);
LPoint TxtPt(6, 0);
LRect TxtRc = d->GetBounds();
TxtRc.Offset(TxtPt.x, TxtPt.y);
d->Paint(pDC, TxtPt, Back, TxtRc, Enabled(), false);
}
}
LRadioButton *LRadioGroup::Append(int x, int y, const char *name)
{
LRadioButton *But = new LRadioButton(d->NextId++, x, y, -1, -1, name);
if (But)
{
Children.Insert(But);
}
return But;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Radio button
class LRadioButtonPrivate : public LMutex, public LStringLayout
{
public:
LRadioButton *Ctrl;
bool Val;
bool Over;
LRect Btn;
LColour BackCol;
LArray GroupIDs;
LRadioButtonPrivate(LRadioButton *c) :
LMutex("LRadioButtonPrivate"),
LStringLayout(LAppInst->GetFontCache())
{
Btn.ZOff(-1,-1);
Ctrl = c;
Val = 0;
Over = 0;
AmpersandToUnderline = true;
}
~LRadioButtonPrivate()
{
}
- LRect GetBtn()
+ LRect GetBtn(int BoxPx)
{
- auto CtrlHt = Ctrl->Y();
auto Fnt = Ctrl->GetFont();
int Px = (int) (Fnt->Ascent() + 0.5);
- if (Px > CtrlHt)
- Px = CtrlHt;
+ if (BoxPx > 0 && Px > BoxPx)
+ Px = BoxPx;
Btn.ZOff(Px-1, Px-1);
- Btn.Offset(0, (CtrlHt-Btn.Y())>>1);
+ if (BoxPx > 0)
+ Btn.Offset(0, (BoxPx-Btn.Y())>>1);
return Btn;
}
bool PreLayout(int32 &Min, int32 &Max)
{
if (Lock(_FL))
{
DoPreLayout(Min, Max);
Unlock();
}
else return false;
return true;
}
bool Layout(int Px)
{
if (Lock(_FL))
{
DoLayout(Px);
Unlock();
/*
if (Min.y < MinYSize)
Min.y = MinYSize;
if (Max.y < MinYSize)
Max.y = MinYSize;
*/
}
else return false;
return true;
}
};
static int PadXPx = 24; // 13px for circle, 11px padding to text.
#ifdef MAC
static int PadYPx = 6;
#else
static int PadYPx = 4;
#endif
LRadioButton::LRadioButton(int id, int x, int y, int cx, int cy, const char *name)
: ResObject(Res_RadioBox)
{
d = new LRadioButtonPrivate(this);
Name(name);
if (cx < 0) cx = d->GetBounds().X() + PadXPx;
if (cy < 0) cy = d->GetBounds().Y() + PadYPx;
LRect r(x, y, x+cx, y+cy);
SetPos(r);
SetId(id);
d->Val = false;
d->Over = false;
SetTabStop(true);
#if WINNATIVE
SetDlgCode(GetDlgCode() | DLGC_WANTARROWS);
#endif
}
LRadioButton::~LRadioButton()
{
DeleteObj(d);
}
bool LRadioButton::SetGroup(LArray CtrlIds)
{
auto w = GetWindow();
if (!w)
return false;
// This ctrl should be in the ID list.
auto id = GetId();
if (!CtrlIds.HasItem(id))
CtrlIds.Add(id);
for (auto i: CtrlIds)
{
LRadioButton *button;
if (!w->GetViewById(i, button))
return false;
button->d->GroupIDs = CtrlIds;
}
return true;
}
void LRadioButton::OnAttach()
{
LResources::StyleElement(this);
OnStyleChange();
LView::OnAttach();
}
void LRadioButton::OnStyleChange()
{
if (d->Lock(_FL))
{
d->Empty();
d->Add(LView::Name(), GetCss());
d->DoLayout(X());
d->Unlock();
Invalidate();
}
}
bool LRadioButton::Name(const char *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::Name(n);
d->Empty();
d->Add(n, GetCss());
d->SetBaseFont(GetFont());
d->DoLayout(X());
d->Unlock();
}
return Status;
}
bool LRadioButton::NameW(const char16 *n)
{
bool Status = false;
if (d->Lock(_FL))
{
Status = LView::NameW(n);
d->Empty();
d->Add(LBase::Name(), GetCss());
d->SetBaseFont(GetFont());
d->DoLayout(X());
d->Unlock();
}
return Status;
}
void LRadioButton::SetFont(LFont *Fnt, bool OwnIt)
{
LAssert(Fnt && Fnt->Handle());
if (d->Lock(_FL))
{
LView::SetFont(Fnt, OwnIt);
d->Unlock();
}
d->Layout(X());
Invalidate();
}
bool LRadioButton::OnLayout(LViewLayoutInfo &Inf)
{
- auto Btn = d->GetBtn();
+ auto Btn = d->GetBtn(-1);
int Pad = Btn.X() + 4;
if (!Inf.Width.Max)
{
d->PreLayout(Inf.Width.Min, Inf.Width.Max);
// FIXME: Wrapping labels not supported yet. So use the max width.
Inf.Width.Min = Inf.Width.Max;
Inf.Width.Min += Pad;
Inf.Width.Max += Pad;
}
else
{
d->Layout(Inf.Width.Max);
Inf.Height.Min = Inf.Height.Max = MAX(d->GetMin().y, Btn.Y());
}
return true;
}
int LRadioButton::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl == (LViewI*)this && n.Type == LNotifyActivate)
{
Value(true);
}
return 0;
}
int64 LRadioButton::Value()
{
return d->Val;
}
void LRadioButton::Value(int64 i)
{
if (d->Val != (i != 0))
{
if (i)
{
// remove the value from the currently selected radio value
if (d->GroupIDs.Length())
{
if (auto w = GetWindow())
{
for (auto id: d->GroupIDs)
{
if (id == GetId())
continue;
LRadioButton *button;
if (w->GetViewById(id, button))
{
if (button->Value())
button->Value(false);
}
}
}
}
else
{
// Use the automatic mode... iterate through sibling views.
if (auto p = GetParent())
{
for (auto c: p->IterateViews())
{
LRadioButton *b = dynamic_cast(c);
if (b && b != this && b->d->Val)
{
b->d->Val = false;
b->Invalidate();
}
}
}
}
}
d->Val = i != 0;
Invalidate();
if (i)
SendNotify();
}
}
void LRadioButton::OnMouseClick(LMouse &m)
{
if (Enabled())
{
bool WasCapturing = IsCapturing();
if (m.Down()) Focus(true);
Capture(m.Down());
d->Over = m.Down();
LRect r(0, 0, X()-1, Y()-1);
if (!m.Down() &&
r.Overlap(m.x, m.y) &&
WasCapturing)
{
Value(true);
}
else
{
Invalidate();
}
}
}
void LRadioButton::OnMouseEnter(LMouse &m)
{
if (Enabled() && IsCapturing())
{
d->Over = true;
Invalidate();
}
}
void LRadioButton::OnMouseExit(LMouse &m)
{
if (Enabled() && IsCapturing())
{
d->Over = false;
Invalidate();
}
}
bool LRadioButton::OnKey(LKey &k)
{
bool Status = false;
int Move = 0;
switch (k.vkey)
{
case LK_UP:
case LK_LEFT:
{
if (k.Down())
{
Move = -1;
}
Status = true;
break;
}
case LK_RIGHT:
case LK_DOWN:
{
if (k.Down())
{
Move = 1;
}
Status = true;
break;
}
case LK_SPACE:
{
if (k.Down())
{
Value(1);
}
return true;
}
}
if (Move)
{
List Btns;
for (LViewI *c: GetParent()->IterateViews())
{
LRadioButton *b = dynamic_cast(c);
if (b) Btns.Insert(b);
}
if (Btns.Length() > 1)
{
ssize_t Index = Btns.IndexOf(this);
if (Index >= 0)
{
LRadioButton *n = Btns[(Index + Move + Btns.Length()) % Btns.Length()];
if (n)
{
n->Focus(true);
}
}
}
}
return Status;
}
void LRadioButton::OnFocus(bool f)
{
Invalidate();
}
void LRadioButton::OnPaint(LSurface *pDC)
{
if (LApp::SkinEngine &&
TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_RADIO))
{
LSkinState State;
State.pScreen = pDC;
State.MouseOver = d->Over;
State.aText = d->GetStrs();
State.View = this;
- State.Rect = d->GetBtn();
+ State.Rect = d->GetBtn(Y());
LColour Back = StyleColour(LCss::PropBackgroundColor, LColour(L_MED));
State.ForceUpdate = d->BackCol != Back;
d->BackCol = Back;
LApp::SkinEngine->OnPaint_LRadioButton(this, &State);
}
else
{
LRect r(0, 0, X()-1, Y()-1);
LRect c(0, 0, 12, 12);
// LColour Fore = StyleColour(LCss::PropColor, LC_TEXT, 4);
LColour Back = StyleColour(LCss::PropBackgroundColor, LColour(L_MED));
// bool e = Enabled();
LRect fill(c.x2 + 1, r.y1, r.x2, r.x2);
LPoint TxtPt(c.x2 + 11, (r.Y() - d->GetBounds().Y()) >> 1);
d->Paint(pDC, TxtPt, Back, fill, Enabled(), false);
#if defined LGI_CARBON
LRect cli = GetClient();
for (LViewI *v = this; v && !v->Handle(); v = v->GetParent())
{
LRect p = v->GetPos();
cli.Offset(p.x1, p.y1);
}
pDC->Colour(Back);
pDC->Rectangle(cli.x1, cli.y1, c.x2, cli.y2);
LRect rc(c.x1, c.y1 + 4, c.x2 - 1, c.y2 - 1);
HIRect Bounds = rc;
HIThemeButtonDrawInfo Info;
HIRect LabelRect;
Info.version = 0;
Info.state = d->Val ? kThemeStatePressed : (Enabled() ? kThemeStateActive : kThemeStateInactive);
Info.kind = kThemeRadioButton;
Info.value = d->Val ? kThemeButtonOn : kThemeButtonOff;
Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone;
OSStatus err = HIThemeDrawButton(&Bounds,
&Info,
pDC->Handle(),
kHIThemeOrientationNormal,
&LabelRect);
if (err) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, err);
#else
// Draw border
pDC->Colour(L_LOW);
pDC->Line(c.x1+1, c.y1+9, c.x1+1, c.y1+10);
pDC->Line(c.x1, c.y1+4, c.x1, c.y1+8);
pDC->Line(c.x1+1, c.y1+2, c.x1+1, c.y1+3);
pDC->Line(c.x1+2, c.y1+1, c.x1+3, c.y1+1);
pDC->Line(c.x1+4, c.y1, c.x1+8, c.y1);
pDC->Line(c.x1+9, c.y1+1, c.x1+10, c.y1+1);
pDC->Colour(L_SHADOW);
pDC->Set(c.x1+2, c.y1+9);
pDC->Line(c.x1+1, c.y1+4, c.x1+1, c.y1+8);
pDC->Line(c.x1+2, c.y1+2, c.x1+2, c.y1+3);
pDC->Set(c.x1+3, c.y1+2);
pDC->Line(c.x1+4, c.y1+1, c.x1+8, c.y1+1);
pDC->Set(c.x1+9, c.y1+2);
pDC->Colour(L_LIGHT);
pDC->Line(c.x1+11, c.y1+2, c.x1+11, c.y1+3);
pDC->Line(c.x1+12, c.y1+4, c.x1+12, c.y1+8);
pDC->Line(c.x1+11, c.y1+9, c.x1+11, c.y1+10);
pDC->Line(c.x1+9, c.y1+11, c.x1+10, c.y1+11);
pDC->Line(c.x1+4, c.y1+12, c.x1+8, c.y1+12);
pDC->Line(c.x1+2, c.y1+11, c.x1+3, c.y1+11);
/// Draw center
bool e = Enabled();
pDC->Colour(d->Over || !e ? L_MED : L_WORKSPACE);
pDC->Rectangle(c.x1+2, c.y1+4, c.x1+10, c.y1+8);
pDC->Box(c.x1+3, c.y1+3, c.x1+9, c.y1+9);
pDC->Box(c.x1+4, c.y1+2, c.x1+8, c.y1+10);
// Draw value
if (d->Val)
{
pDC->Colour(e ? L_TEXT : L_LOW);
pDC->Rectangle(c.x1+4, c.y1+5, c.x1+8, c.y1+7);
pDC->Rectangle(c.x1+5, c.y1+4, c.x1+7, c.y1+8);
}
#endif
}
}
#endif