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