diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,670 +1,683 @@ cmake_minimum_required (VERSION 3.18) project(Lgi) if (WIN32) set(CMAKE_SYSTEM_VERSION 10.0.19041.0) endif() if (FALSE) # dump all vars for debugging. get_cmake_property(_variableNames VARIABLES) list (SORT _variableNames) foreach (_variableName ${_variableNames}) message(STATUS "${_variableName}=${${_variableName}}") endforeach() endif() include(build.cmake) set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_CXX_STANDARD 14) set(LGI_VERSION_MAJOR 1) set(LGI_VERSION_MINOR 0) if (UNIX AND NOT APPLE AND NOT HAIKU) set(LINUX TRUE) endif() if (WIN32) message("CMAKE_GENERATOR=${CMAKE_GENERATOR}") message("CMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") if (CMAKE_GENERATOR MATCHES "Win64" OR CMAKE_GENERATOR_PLATFORM MATCHES "x64") set(WORDSIZE 64) set(OPENSSL_DIR "c:/Program Files/OpenSSL-Win64") else() set(WORDSIZE 32) set(OPENSSL_DIR "c:/Program Files (x86)/OpenSSL-Win32") endif() set(OPENSSL_INCLUDE_DIR "${OPENSSL_DIR}/include") if (CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION MATCHES "14393") message("SDK too old: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") endif() endif() get_directory_property(hasParent PARENT_DIRECTORY) if(hasParent) set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}" PARENT_SCOPE) endif() set(LGI_DIR "${CMAKE_CURRENT_LIST_DIR}") # External libs: get_filename_component(CODE ../.. ABSOLUTE) get_filename_component(CODELIB ${CODE}/../codelib ABSOLUTE) if (NOT EXISTS "${CODELIB}") message(WARNING "Expecting external libraries in: ${CODELIB}") else() message("CODELIB=${CODELIB}") endif() message("CODELIB=${CODELIB}") if(hasParent) set(CODELIB "${CODELIB}" PARENT_SCOPE) endif() set(ICONV_PATH "${CODELIB}/libiconv") if (NOT EXISTS "${ICONV_PATH}/include") if (APPLE) if (EXISTS "/opt/local/include/iconv.h") set(ICONV_PATH "/opt/local") else() find_package(Iconv REQUIRED) endif() elseif (LINUX OR HAIKU) find_package(Iconv REQUIRED) else() message(FATAL_ERROR "Missing iconv at: ${ICONV_PATH}") endif() endif() if (NOT LGI_LIB_ONLY) if (HAIKU) find_package(PNG REQUIRED) set(LIBPNG_TARGET PNG::PNG) else() get_filename_component(PNG_LIBRARY "${CODELIB}/libpng" ABSOLUTE) if (EXISTS ${PNG_LIBRARY} AND NOT TARGET libpng16) add_subdirectory(${PNG_LIBRARY} libpng) set(LIBPNG_TARGET libpng16) endif() endif() if (HAIKU) find_package(JPEG REQUIRED) else() get_filename_component(JPEG_LIBRARY "${CODELIB}/libjpeg-9a" ABSOLUTE) if (NOT TARGET libjpeg9a) add_subdirectory(${CODELIB}/libjpeg-9a libjpeg) endif() endif() endif() if (APPLE) # SET(GUI_TYPE MACOSX_BUNDLE) find_library(COCOA_LIBRARY Cocoa) mark_as_advanced (COCOA_LIBRARY) set(PUBLIC_LIBS ${COCOA_LIBRARY}) elseif (LINUX) find_package(PkgConfig) pkg_check_modules(GTK3 REQUIRED gtk+-3.0) pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4) set(PUBLIC_LIBS pthread crypt z) endif() # Lgi source code: set (CommonSource src/common/Gdc2/15Bit.cpp src/common/Gdc2/16Bit.cpp src/common/Gdc2/24Bit.cpp src/common/Gdc2/32Bit.cpp src/common/Gdc2/64Bit.cpp src/common/Gdc2/8Bit.cpp src/common/Gdc2/Alpha.cpp include/lgi/common/Filter.h src/common/Gdc2/Filters/Filter.cpp include/lgi/common/DisplayString.h src/common/Gdc2/Font/DisplayString.cpp include/lgi/common/StringLayout.h src/common/Gdc2/Font/StringLayout.cpp include/lgi/common/Font.h src/common/Gdc2/Font/Font.cpp src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/Font/FontType.cpp src/common/Gdc2/Font/TypeFace.cpp include/lgi/common/Charset.h src/common/Gdc2/Font/Charset.cpp include/lgi/common/Colour.h src/common/Gdc2/Colour.cpp include/lgi/common/Gdc2.h src/common/Gdc2/GdcCommon.cpp include/lgi/common/Rect.h src/common/Gdc2/Rect.cpp src/common/Gdc2/Surface.cpp include/lgi/common/Path.h src/common/Gdc2/Path/Path.cpp src/common/Gdc2/Tools/ColourReduce.cpp include/lgi/common/Containers.h src/common/General/Containers.cpp include/lgi/common/DateTime.h src/common/General/DateTime.cpp src/common/General/ExeCheck.cpp include/lgi/common/File.h src/common/General/FileCommon.cpp include/lgi/common/Growl.h src/common/General/Growl.cpp src/common/General/Password.cpp src/common/Hash/md5/md5.c src/common/Hash/sha1/sha1.c src/common/Net/Base64.cpp src/common/Net/Uri.cpp include/lgi/common/Net.h src/common/Net/Net.cpp src/common/Net/MDStringToDigest.cpp src/common/Lgi/Alert.cpp include/lgi/common/Css.h src/common/Lgi/Css.cpp include/lgi/common/CssTools.h src/common/Lgi/CssTools.cpp include/lgi/common/FindReplaceDlg.h src/common/Lgi/FindReplace.cpp include/lgi/common/FontSelect.h src/common/Lgi/FontSelect.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Input.cpp include/lgi/common/Library.h src/common/Lgi/Library.cpp include/lgi/common/Mru.h src/common/Lgi/Mru.cpp include/lgi/common/Mutex.h src/common/Lgi/Mutex.cpp src/common/Lgi/Object.cpp include/lgi/common/OptionsFile.h src/common/Lgi/OptionsFile.cpp include/lgi/common/SubProcess.h include/lgi/common/Stream.h src/common/Lgi/Stream.cpp src/common/Lgi/MemStream.cpp include/lgi/common/Thread.h src/common/Lgi/ThreadCommon.cpp src/common/Lgi/ThreadEvent.cpp include/lgi/common/ToolTip.h src/common/Lgi/ToolTip.cpp include/lgi/common/TrayIcon.h src/common/Lgi/TrayIcon.cpp include/lgi/common/Variant.h src/common/Lgi/Variant.cpp include/lgi/common/App.h src/common/Lgi/AppCommon.cpp include/lgi/common/View.h src/common/Lgi/ViewCommon.cpp include/lgi/common/Window.h src/common/Lgi/WindowCommon.cpp include/lgi/common/DragAndDrop.h src/common/Lgi/DragAndDropCommon.cpp include/lgi/common/Lgi.h src/common/Lgi/LgiCommon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp include/lgi/common/DropFiles.h src/common/Lgi/DropFiles.cpp include/lgi/common/Menu.h src/common/Lgi/MenuCommon.cpp include/lgi/common/LgiRes.h src/common/Resource/LgiRes.cpp src/common/Resource/Res.cpp src/common/Skins/Gel/Gel.cpp include/lgi/common/DocView.h src/common/Text/DocView.cpp include/lgi/common/StringClass.h include/lgi/common/LgiString.h src/common/Text/String.cpp include/lgi/common/TextView3.h src/common/Text/TextView3.cpp include/lgi/common/TextView4.h src/common/Text/TextView4.cpp include/lgi/common/Token.h src/common/Text/Token.cpp include/lgi/common/Unicode.h src/common/Text/Unicode.cpp include/lgi/common/Unicode.h src/common/Text/Utf8.cpp include/lgi/common/XmlTree.h src/common/Text/XmlTree.cpp include/lgi/common/Button.h src/common/Widgets/Button.cpp include/lgi/common/Bitmap.h src/common/Widgets/Bitmap.cpp include/lgi/common/Box.h src/common/Widgets/Box.cpp src/common/Widgets/ItemContainer.cpp include/lgi/common/List.h src/common/Widgets/List.cpp include/lgi/common/Panel.h src/common/Widgets/Panel.cpp include/lgi/common/Popup.h src/common/Widgets/Popup.cpp include/lgi/common/ProgressDlg.h src/common/Widgets/ProgressDlg.cpp include/lgi/common/RadioGroup.h src/common/Widgets/RadioGroup.cpp include/lgi/common/Splitter.h src/common/Widgets/Splitter.cpp include/lgi/common/StatusBar.h src/common/Widgets/StatusBar.cpp include/lgi/common/TableLayout.h src/common/Widgets/TableLayout.cpp include/lgi/common/TabView.h src/common/Widgets/TabView.cpp include/lgi/common/TextLabel.h src/common/Widgets/TextLabel.cpp include/lgi/common/ToolBar.h src/common/Widgets/ToolBar.cpp include/lgi/common/Tree.h src/common/Widgets/Tree.cpp include/lgi/common/CheckBox.h include/lgi/common/Combo.h include/lgi/common/Edit.h include/lgi/common/Layout.h include/lgi/common/Slider.h include/lgi/common/Progress.h include/lgi/common/ClipBoard.h include/lgi/common/FileSelect.h ) if (APPLE) set(Source ${CommonSource} src/common/Widgets/CheckBox.cpp src/common/Widgets/Combo.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Edit.cpp src/common/Lgi/Layout.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp src/mac/cocoa/ShowFileProp_Mac.mm src/mac/common/MemDC.cpp src/mac/common/PrintDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/File.mm src/mac/cocoa/Mem.mm src/mac/cocoa/App.mm src/mac/cocoa/ClipBoard.mm src/mac/cocoa/DragAndDrop.mm src/mac/cocoa/General.mm src/mac/cocoa/FileSelect.mm src/mac/cocoa/Menu.mm src/mac/cocoa/Printer.mm src/mac/cocoa/Thread.mm src/mac/cocoa/View.mm src/mac/cocoa/Widgets.mm src/mac/cocoa/Window.mm src/mac/cocoa/CocoaView.mm src/mac/cocoa/Gdc2.mm ) elseif (LINUX) set(Source ${CommonSource} src/linux/Gtk/Gdc2.cpp src/linux/Gtk/MemDC.cpp src/linux/Gtk/PrintDC.cpp src/linux/Gtk/ScreenDC.cpp src/linux/Gtk/LgiWidget.cpp src/linux/General/File.cpp src/linux/General/Mem.cpp src/linux/General/ShowFileProp_Linux.cpp src/linux/Lgi/App.cpp src/linux/Lgi/ClipBoard.cpp src/linux/Lgi/DragAndDrop.cpp src/linux/Lgi/General.cpp src/linux/Lgi/Layout.cpp src/linux/Lgi/Menu.cpp src/linux/Lgi/Printer.cpp src/linux/Lgi/Thread.cpp src/linux/Lgi/View.cpp - src/linux/Lgi/Widgets.cpp src/linux/Lgi/Window.cpp + src/linux/Lgi/Dialog.cpp src/common/Lgi/FileSelect.cpp src/common/Widgets/CheckBox.cpp src/common/Widgets/Edit.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Combo.cpp src/common/Widgets/RadioGroup.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp ) elseif (MSVC) set(Source ${CommonSource} src/win/General/Lgi.manifest src/win/Gdc2/Gdc2.cpp src/win/Gdc2/GdiLeak.cpp src/win/Gdc2/MemDC.cpp src/win/Gdc2/PrintDC.cpp src/win/Gdc2/ScreenDC.cpp src/win/General/File.cpp src/win/General/Mem.cpp src/win/General/ShowFileProp_Win.cpp src/win/INet/MibAccess.cpp src/win/Lgi/App.cpp src/win/Lgi/ClipBoard.cpp src/win/Lgi/DragAndDrop.cpp src/win/Lgi/FileSelect.cpp src/win/Lgi/General.cpp src/win/Lgi/Layout.cpp src/win/Lgi/Menu.cpp src/win/Lgi/Printer.cpp src/win/Lgi/ScrollBar.cpp src/win/Lgi/Thread.cpp src/win/Lgi/View.cpp src/win/Lgi/Window.cpp src/win/Lgi/LgiException.cpp src/win/Widgets/Button_Win.cpp src/common/Widgets/CheckBox.cpp # Use XP check box src/win/Widgets/Combo_Win.cpp src/win/Widgets/Dialog_Win.cpp src/win/Widgets/Edit_Win.cpp src/win/Widgets/RadioGroup_Win.cpp src/win/Widgets/Slider_Win.cpp src/win/Widgets/Progress_Win.cpp) elseif (HAIKU) set(Source ${CommonSource} src/common/Lgi/FileSelect.cpp src/common/Widgets/Combo.cpp src/common/Widgets/Edit.cpp src/common/Widgets/CheckBox.cpp src/common/Widgets/ScrollBar.cpp src/common/Widgets/Progress.cpp src/common/Widgets/Slider.cpp src/haiku/App.cpp src/haiku/ClipBoard.cpp src/haiku/DragAndDrop.cpp src/haiku/File.cpp src/haiku/Gdc2.cpp src/haiku/General.cpp src/haiku/Layout.cpp src/haiku/Mem.cpp src/haiku/MemDC.cpp src/haiku/Menu.cpp src/haiku/PrintDC.cpp src/haiku/Printer.cpp src/haiku/ScreenDC.cpp src/haiku/ShowFileProp_Haiku.cpp src/haiku/Thread.cpp src/haiku/View.cpp src/haiku/Widgets.cpp src/haiku/Window.cpp) endif() if (WIN32) list(APPEND Source src/win/Lgi/SubProcess.cpp) else() list(APPEND Source src/posix/SubProcess.cpp) endif() add_library(lgi SHARED ${Source}) add_library(lgi-stub STATIC src/common/Gdc2/Font/Charset.cpp src/common/General/Containers.cpp src/common/General/DateTime.cpp src/common/General/FileCommon.cpp src/common/Lgi/Library.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/Mutex.cpp src/common/Lgi/Stream.cpp src/common/Lgi/ThreadCommon.cpp src/common/Lgi/Variant.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/Rand.cpp src/common/Text/String.cpp src/common/Text/Token.cpp src/common/Text/Unicode.cpp) target_compile_definitions(lgi-stub PRIVATE LGI_LIBRARY + PUBLIC LGI_STATIC) target_include_directories(lgi-stub PUBLIC include) if (DEFINED LGI_LIB_POSTFIX) set_target_properties(lgi PROPERTIES DEBUG_POSTFIX ${LGI_LIB_POSTFIX}d RELEASE_POSTFIX ${LGI_LIB_POSTFIX}) endif() if (APPLE) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK "YES" ) set_target_properties(lgi PROPERTIES FRAMEWORK true) set_target_properties(lgi PROPERTIES XCODE_ATTRIBUTE_INSTALL_PATH @executable_path/../Frameworks/) target_include_directories(lgi PUBLIC include/lgi/mac/cocoa /opt/local/include PRIVATE private/mac ) target_compile_definitions(lgi PUBLIC MAC LGI_COCOA ) target_link_libraries(lgi PUBLIC ${EXTRA_LIBS}) target_compile_options(lgi PUBLIC -Wno-nullability-completeness) # lgi stub settings target_include_directories(lgi-stub PUBLIC include/lgi/mac/cocoa /opt/local/include PRIVATE private/mac) target_compile_definitions(lgi-stub PUBLIC MAC LGI_COCOA ) target_compile_options(lgi-stub PUBLIC -Wno-nullability-completeness) set(OBJCPP_SOURCE src/common/Gdc2/Font/FontSystem.cpp src/common/Gdc2/GdcCommon.cpp src/common/Lgi/LgiCommon.cpp src/common/Lgi/GuiUtils.cpp src/common/Lgi/Process.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/TrayIcon.cpp src/common/Lgi/LMsg.cpp src/common/Lgi/ViewCommon.cpp src/common/Lgi/AppCommon.cpp src/common/Lgi/DropFiles.cpp src/common/Widgets/Popup.cpp src/common/General/DateTime.cpp src/mac/common/MemDC.cpp src/mac/common/ScreenDC.cpp src/mac/cocoa/General.cpp src/posix/SubProcess.cpp ) set_source_files_properties(${OBJCPP_SOURCE} PROPERTIES COMPILE_FLAGS "-x objective-c++") elseif (LINUX) target_compile_definitions(lgi PUBLIC LINUX POSIX ) target_compile_options(lgi PUBLIC -fPIC -w -fno-inline -fpermissive ) target_include_directories(lgi PUBLIC include/lgi/linux/Gtk include/lgi/linux ${GTK3_INCLUDE_DIRS} ${GST_INCLUDE_DIRS} PRIVATE private/linux ) target_link_libraries(lgi PUBLIC ${GTK3_LIBRARIES} ${GST_LIBRARIES} magic ) + target_include_directories(lgi-stub + PUBLIC + include/lgi/linux/Gtk + include/lgi/linux + ${GTK3_INCLUDE_DIRS} + ${GST_INCLUDE_DIRS} + PRIVATE + private/linux) + target_compile_definitions(lgi-stub + PUBLIC + LINUX) + elseif (MSVC) target_compile_definitions(lgi PUBLIC WINDOWS WIN64 _WINDLL _UNICODE UNICODE ) target_compile_options(lgi PRIVATE /W2 ) target_include_directories(lgi PUBLIC include/lgi/win PRIVATE private/win) target_link_libraries(lgi PUBLIC ComCtl32.lib Ws2_32.lib UxTheme.lib imm32.lib) target_sources(lgi-stub PRIVATE src/win/General/File.cpp src/win/General/Mem.cpp src/win/Lgi/General.cpp src/win/Lgi/Thread.cpp) target_include_directories(lgi-stub PUBLIC include/lgi/win) target_compile_definitions(lgi-stub PUBLIC WINDOWS _UNICODE UNICODE) elseif (HAIKU) target_include_directories(lgi-stub PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi-stub PUBLIC HAIKU _GNU_SOURCE) target_include_directories(lgi PUBLIC include/lgi/haiku PRIVATE private/haiku) target_compile_definitions(lgi PUBLIC HAIKU _GNU_SOURCE) target_link_libraries(lgi PUBLIC gnu network be) endif() target_link_libraries(lgi PUBLIC ${PUBLIC_LIBS}) target_compile_definitions(lgi PUBLIC LGI_RES PRIVATE LGI_LIBRARY ) target_include_directories(lgi PUBLIC include PRIVATE private/common ${PNGLIB_INCLUDE_DIR} ${ICONV_PATH}/include ) set_target_properties(lgi PROPERTIES LINKER_LANGUAGE CXX) if(LINUX) add_subdirectory(src/linux/CrashHandler) endif() if (LGI_LIB_ONLY) message(STATUS "Skipping LGI apps, tests and utils") else() # main apps add_subdirectory(ide) add_subdirectory(lvc) add_subdirectory(resourceEditor) # other subfolders of smaller utils and tests add_subdirectory(utils) add_subdirectory(test) add_subdirectory(src/common/Net/libntlm-0.4.2) endif() diff --git a/src/common/General/DateTime.cpp b/src/common/General/DateTime.cpp --- a/src/common/General/DateTime.cpp +++ b/src/common/General/DateTime.cpp @@ -1,2372 +1,2372 @@ /* ** FILE: LDateTime.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Date Time Object ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _DEFAULT_SOURCE #include #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" constexpr const char *LDateTime::WeekdaysShort[7]; constexpr const char *LDateTime::WeekdaysLong[7]; constexpr const char *LDateTime::MonthsShort[12]; constexpr const char *LDateTime::MonthsLong[12]; #if !defined(WINDOWS) #define MIN_YEAR 1800 #endif #if defined(LINUX) #define USE_ZDUMP 1 #elif defined(HAIKU) #include "lgi/common/TimeZoneInfo.h" #endif #define DEBUG_DST_INFO 0 ////////////////////////////////////////////////////////////////////////////// uint16 LDateTime::DefaultFormat = GDTF_DEFAULT; char LDateTime::DefaultSeparator = '/'; uint16 LDateTime::GetDefaultFormat() { if (DefaultFormat == GDTF_DEFAULT) { #ifdef WIN32 TCHAR s[80] = _T("1"); GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE, s, CountOf(s)); switch (_tstoi(s)) { case 0: DefaultFormat = GDTF_MONTH_DAY_YEAR; break; default: case 1: DefaultFormat = GDTF_DAY_MONTH_YEAR; break; case 2: DefaultFormat = GDTF_YEAR_MONTH_DAY; break; } GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, s, sizeof(s)); if (_tstoi(s) == 1) { DefaultFormat |= GDTF_24HOUR; } else { DefaultFormat |= GDTF_12HOUR; } if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, s, sizeof(s))) DefaultSeparator = (char)s[0]; if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, s, sizeof(s))) { char Sep[] = { DefaultSeparator, '/', '\\', '-', '.', 0 }; LString Str = s; auto t = Str.SplitDelimit(Sep); for (int i=0; i= low && (v) <= high) bool LDateTime::IsValid() const { return InRange(_Day, 1, 31) && InRange(_Year, 1600, 2100) && InRange(_Thousands, 0, 999) && InRange(_Month, 1, 12) && InRange(_Seconds, 0, 59) && InRange(_Minutes, 0, 59) && InRange(_Hours, 0, 23) && InRange(_Tz, -780, 780); } void LDateTime::SetTimeZone(int NewTz, bool ConvertTime) { if (ConvertTime && NewTz != _Tz) { // printf("SetTimeZone: %i\n", NewTz - _Tz); AddMinutes(NewTz - _Tz); } _Tz = NewTz; } int LDateTime::SystemTimeZone(bool ForceUpdate) { if (ForceUpdate || CurTz == NO_ZONE) { CurTz = 0; CurTzOff = 0; #ifdef MAC #ifdef LGI_COCOA NSTimeZone *timeZone = [NSTimeZone localTimeZone]; if (timeZone) { NSDate *Now = [NSDate date]; CurTz = (int) [timeZone secondsFromGMTForDate:Now] / 60; CurTzOff = [timeZone daylightSavingTimeOffsetForDate:Now] / 60; CurTz -= CurTzOff; } #elif defined LGI_CARBON CFTimeZoneRef tz = CFTimeZoneCopySystem(); CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); Boolean dst = CFTimeZoneIsDaylightSavingTime(tz, now); if (dst) { CFAbsoluteTime next = CFTimeZoneGetNextDaylightSavingTimeTransition(tz, now); CurTz = CFTimeZoneGetSecondsFromGMT(tz, next + 100) / 60; } else { CurTz = CFTimeZoneGetSecondsFromGMT(tz, now) / 60; } CurTzOff = CFTimeZoneGetDaylightSavingTimeOffset(tz, now) / 60; CFRelease(tz); #endif #elif defined(WIN32) timeb tbTime; ftime(&tbTime); CurTz = -tbTime.timezone; TIME_ZONE_INFORMATION Tzi; if (GetTimeZoneInformation(&Tzi) == TIME_ZONE_ID_DAYLIGHT) CurTzOff = -Tzi.DaylightBias; #elif defined(LINUX) || defined(HAIKU) int six_months = (365 * 24 * 60 * 60) / 2; time_t now = 0, then = 0; time (&now); then = now - six_months; tm now_tz, then_tz; tm *t = localtime_r(&now, &now_tz); if (t) { localtime_r(&then, &then_tz); CurTz = now_tz.tm_gmtoff / 60; if (now_tz.tm_isdst) { CurTzOff = (now_tz.tm_gmtoff - then_tz.tm_gmtoff) / 60; CurTz = then_tz.tm_gmtoff / 60; } else // This is not DST so there is no offset right? CurTzOff = 0; // (then_tz.tm_gmtoff - now_tz.tm_gmtoff) / 60; } else return NO_ZONE; #else #error "Impl me." #endif } return CurTz + CurTzOff; } int LDateTime::SystemTimeZoneOffset() { if (CurTz == NO_ZONE) SystemTimeZone(); return CurTzOff; } #if defined WIN32 LDateTime ConvertSysTime(SYSTEMTIME &st, int year) { LDateTime n; if (st.wYear) { n.Year(st.wYear); n.Month(st.wMonth); n.Day(st.wDay); } else { n.Year(year); n.Month(st.wMonth); // Find the 'nth' matching weekday, starting from the first day in the month n.Day(1); LDateTime c = n; for (int i=0; iCompare(b); } #elif USE_ZDUMP static bool ParseValue(char *s, LString &var, LString &val) { if (!s) return false; char *e = strchr(s, '='); if (!e) return false; *e++ = 0; var = s; val = e; *e = '='; return var != 0 && val != 0; } #endif /* Testing code... LDateTime Start, End; LArray Info; Start.Set("1/1/2010"); End.Set("31/12/2014"); LDateTime::GetDaylightSavingsInfo(Info, Start, &End); LStringPipe p; for (int i=0; i,int> { MonthHash() { for (int i=0; i &Info, LDateTime &Start, LDateTime *End) { bool Status = false; #if defined(WIN32) TIME_ZONE_INFORMATION Tzi; auto r = GetTimeZoneInformation(&Tzi); if (r > TIME_ZONE_ID_UNKNOWN) { Info.Length(0); // Find the dates for the previous year from Start. This allows // us to cover the start of the current year. LDateTime s = ConvertSysTime(Tzi.StandardDate, Start.Year() - 1); LDateTime d = ConvertSysTime(Tzi.DaylightDate, Start.Year() - 1); // Create initial Info entry, as the last change in the previous year auto *i = &Info.New(); if (s < d) { // Year is: Daylight->Standard->Daylight LDateTime tmp = d; i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; } else { // Year is: Standard->Daylight->Standard LDateTime tmp = s; i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; } for (auto y=Start.Year(); y<=(End?End->Year():Start.Year()); y++) { if (s < d) { // Cur year, first event: end of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.StandardDate, y); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; // Cur year, second event: start of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.DaylightDate, y); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; } else { // Cur year, first event: start of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.DaylightDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; // Cur year, second event: end of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.StandardDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->Utc = tmp; } } Status = true; } #elif defined(MAC) LDateTime From = Start; From.AddMonths(-6); LDateTime To = End ? *End : Start; To.AddMonths(6); auto ToUnix = To.GetUnix(); auto tz = [NSTimeZone systemTimeZone]; auto startDate = [[NSDate alloc] initWithTimeIntervalSince1970:(From.Ts() / Second64Bit) - Offset1800]; while (startDate) { auto next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; auto &i = Info.New(); auto nextTs = [next timeIntervalSince1970]; i.UtcTimeStamp = (nextTs + Offset1800) * Second64Bit; i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); #if DEBUG_DST_INFO { LDateTime dt; dt.Set(i.UtcTimeStamp); LgiTrace("%s:%i - Ts=%s Off=%i\n", _FL, dt.Get().Get(), i.Offset); } #endif if (nextTs >= ToUnix) break; [startDate release]; startDate = next; } if (startDate) [startDate release]; #elif USE_ZDUMP if (!Zdump.Length()) { static bool First = true; auto linkLoc = "/etc/localtime"; #if defined(LINUX) auto zoneLoc = "/usr/share/zoneinfo"; #elif defined(HAIKU) auto zoneLoc = "/boot/system/data/zoneinfo"; #else #error "Impl me" #endif if (!LFileExists(linkLoc)) { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: '%s' doesn't exist.\n" " It should link to something in the '%s' tree.\n", _FL, linkLoc, zoneLoc); #ifdef HAIKU LgiTrace(" To fix that: pkgman install timezone_data and then create the '%s' link.\n", linkLoc); #endif } return First = false; } auto f = popen(LString::Fmt("zdump -v %s", linkLoc), "r"); if (f) { char s[1024]; size_t r; LStringPipe p(1024); while ((r = fread(s, 1, sizeof(s), f)) > 0) p.Write(s, (int)r); fclose(f); Zdump = p.NewLStr().Split("\n"); } else { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: zdump didn't run.\n", _FL); #ifdef HAIKU LgiTrace("To fix that: pkgman install timezone_data\n"); #endif } return First = false; } } MonthHash Lut; LDateTime Prev; int PrevOff = 0; for (auto Line: Zdump) { auto l = Line.SplitDelimit(" \t"); if (l.Length() >= 16 && l[0].Equals("/etc/localtime")) { // /etc/localtime Sat Oct 3 15:59:59 2037 UTC = Sun Oct 4 01:59:59 2037 EST isdst=0 gmtoff=36000 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LDateTime Utc; Utc.Year(l[5].Int()); #if DEBUG_DST_INFO if (Utc.Year() < 2020) continue; // printf("DST: %s\n", Line.Get()); #endif auto Tm = l[4].SplitDelimit(":"); if (Tm.Length() != 3) { #if DEBUG_DST_INFO printf("%s:%i - Tm '%s' has wrong parts: %s\n", _FL, l[4].Get(), Line.Get()); #endif continue; } Utc.Hours(Tm[0].Int()); Utc.Minutes(Tm[1].Int()); Utc.Seconds(Tm[2].Int()); if (Utc.Minutes() < 0) { #if DEBUG_DST_INFO printf("%s:%i - Mins is zero: %s\n", _FL, l[4].Get()); #endif continue; } int m = Lut.Find(l[2]); if (!m) { #if DEBUG_DST_INFO printf("%s:%i - Unknown month '%s'\n", _FL, l[2].Get()); #endif continue; } Utc.Day(l[3].Int()); Utc.Month(m); LString Var, Val; if (!ParseValue(l[14], Var, Val) || Var != "isdst") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } if (!ParseValue(l[15], Var, Val) || Var != "gmtoff") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } int Off = atoi(Val) / 60; - if (Utc.Ts() == 0) + if (Utc.Ts().Valid() == 0) continue; if (Prev.Year() && Prev < Start && Start < Utc) { // Emit initial entry for 'start' auto &inf = Info.New(); Prev.Get(inf.Utc); inf.Offset = PrevOff; #if DEBUG_DST_INFO printf("Info: Start=%s %i\n", Prev.Get().Get(), inf.Offset); #endif } if (Utc > Start) { // Emit furthur entries for DST events between start and end. auto &inf = Info.New(); Utc.Get(inf.Utc); inf.Offset = Off; #if DEBUG_DST_INFO printf("Info: Next=%s %i\n", Utc.Get().Get(), inf.Offset); #endif if (End && Utc > *End) { // printf("Utc after end: %s > %s\n", Utc.Get().Get(), End->Get().Get()); break; } } Prev = Utc; PrevOff = Off; } } Status = Info.Length() > 1; #elif defined(HAIKU) LTimeZoneInfo tzinfo; if (!tzinfo.Read()) { #if DEBUG_DST_INFO LgiTrace("%s:%i - info read failed.\n", _FL); #endif return false; } Status = tzinfo.GetDaylightSavingsInfo(Info, Start, End); #if DEBUG_DST_INFO if (!Status) printf("%s:%i - GetDaylightSavingsInfo failed.\n", _FL); #endif #else LAssert(!"Not implemented."); #endif return Status; } bool LDateTime::DstToLocal(LArray &Dst, LDateTime &dt) { if (dt.GetTimeZone()) { LAssert(!"Should be a UTC date."); return true; } #if DEBUG_DST_INFO LgiTrace("DstToLocal: %s\n", dt.Get().Get()); #endif LAssert(Dst.Length() > 1); // Needs to have at least 2 entries...? for (size_t i=0; i= start && dt < end; if (InRange) { dt.SetTimeZone(a.Offset, true); #if DEBUG_DST_INFO LgiTrace("\tRng[%i]: %s -> %s, SetTimeZone(%g), dt=%s\n", (int)i, start.Get().Get(), end.Get().Get(), (double)a.Offset/60.0, dt.Get().Get()); #endif return true; } } auto Last = Dst.Last(); LDateTime d; d.Set(Last.Utc); if (dt >= d && dt.Year() == d.Year()) { // If it's after the last DST change but in the same year... it's ok... // Just use the last offset. dt.SetTimeZone(Last.Offset, true); return true; } #if DEBUG_DST_INFO for (auto d: Dst) LgiTrace("Dst: %s = %i\n", d.GetLocal().Get().Get(), d.Offset); #endif LgiTrace("%s:%i - No valid DST range for: %s\n", _FL, dt.Get().Get()); LAssert(!"No valid DST range for this date."); return false; } int LDateTime::DayOfWeek() const { int Index = 0; int Day = IsLeapYear() ? 29 : 28; switch (_Year / 100) { case 19: { Index = 3; break; } case 20: { Index = 2; break; } } // get year right int y = _Year % 100; int r = y % 12; Index = (Index + (y / 12) + r + (r / 4)) % 7; // get month right if (_Month % 2 == 0) { // even month if (_Month > 2) Day = _Month; } else { // odd month switch (_Month) { case 1: { Day = 31; if (IsLeapYear()) { Index = Index > 0 ? Index - 1 : Index + 6; } break; } case 11: case 3: { Day = 7; break; } case 5: { Day = 9; break; } case 7: { Day = 11; break; } case 9: { Day = 5; break; } } } // get day right int Diff = Index - (Day - _Day); while (Diff < 0) Diff += 7; return Diff % 7; } LDateTime LDateTime::Now() { LDateTime dt; dt.SetNow(); return dt; } LDateTime &LDateTime::SetNow() { #ifdef WIN32 SYSTEMTIME stNow; auto sysTz = SystemTimeZone(); if (_Tz == sysTz) GetLocalTime(&stNow); else GetSystemTime(&stNow); #if 1 _Day = stNow.wDay; _Month = stNow.wMonth; _Year = stNow.wYear; _Hours = stNow.wHour; _Minutes = stNow.wMinute; _Seconds = stNow.wSecond; _Thousands = stNow.wMilliseconds; if (_Tz && _Tz != sysTz) { // Adjust to this objects timezone... auto tz = _Tz; _Tz = 0; SetTimeZone(tz, true); } #else // This is actually less efficient. FILETIME ftNow; SystemTimeToFileTime(&stNow, &ftNow); uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; OsTime(i64); #endif #else time_t now; time(&now); struct tm *time = localtime(&now); if (time) *this = time; #ifndef LGI_STATIC else { LgiTrace("%s:%i - Error: localtime failed, now=%u\n", _FL, now); } #endif #endif return *this; } #define Convert24HrTo12Hr(h) ( (h) == 0 ? 12 : (h) > 12 ? (h) % 12 : (h) ) #define Convert24HrToAmPm(h) ( (h) >= 12 ? "p" : "a" ) LString LDateTime::GetDate() const { char s[32]; int Ch = GetDate(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetDate(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_DATE_MASK) { case GDTF_MONTH_DAY_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%2.2i" :"%i" , _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; default: case GDTF_DAY_MONTH_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%2.2i" :"%i" , _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; case GDTF_YEAR_MONTH_DAY: Ch += sprintf_s(Str+Ch, SLen-Ch, "%i", _Year); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); break; } } return Ch; } LString LDateTime::GetTime() const { char s[32]; int Ch = GetTime(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetTime(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_TIME_MASK) { case GDTF_12HOUR: default: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i%s", Convert24HrTo12Hr(_Hours), _Minutes, _Seconds, Convert24HrToAmPm(_Hours)); break; } case GDTF_24HOUR: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i", _Hours, _Minutes, _Seconds); break; } } } return Ch; } LTimeStamp LDateTime::Ts() const { LTimeStamp ts; Get(ts); return ts; } uint64_t LDateTime::GetUnix() { LTimeStamp s; Get(s); #if defined(WINDOWS) return s.Get() / LDateTime::Second64Bit / 116445168000000000LL; #else return s.Get() / LDateTime::Second64Bit - Offset1800; #endif } bool LDateTime::SetUnix(uint64 s) { #if defined(WINDOWS) return Set(s * LDateTime::Second64Bit + 116445168000000000LL); #else return Set((s + Offset1800) * LDateTime::Second64Bit); #endif } bool LDateTime::Set(const LTimeStamp &s) { #if defined WIN32 FILETIME Utc; SYSTEMTIME System; // Adjust to the desired timezone uint64 u = s.Get() + ((int64)_Tz * 60 * Second64Bit); Utc.dwHighDateTime = u >> 32; Utc.dwLowDateTime = u & 0xffffffff; if (!FileTimeToSystemTime(&Utc, &System)) return false; _Year = System.wYear; _Month = System.wMonth; _Day = System.wDay; _Hours = System.wHour; _Minutes = System.wMinute; _Seconds = System.wSecond; _Thousands = System.wMilliseconds; return true; #else Set(s.Unix()); _Thousands = s.Get() % Second64Bit; return true; #endif } bool LDateTime::Set(struct tm *t, bool inferTimezone) { if (!t) return false; _Year = t->tm_year + 1900; _Month = t->tm_mon + 1; _Day = t->tm_mday; _Hours = t->tm_hour; _Minutes = t->tm_min; _Seconds = t->tm_sec; _Thousands = 0; if (inferTimezone) { #ifdef WINDOWS #define timegm _mkgmtime #endif auto diff = timegm(t) - mktime(t); _Tz = (int16)(diff / 60); } return true; } bool LDateTime::Set(time_t tt) { struct tm *t; if (_Tz) tt += _Tz * 60; #if !defined(_MSC_VER) || _MSC_VER < _MSC_VER_VS2005 t = gmtime(&tt); if (t) #else struct tm tmp; if (_gmtime64_s(t = &tmp, &tt) == 0) #endif { return Set(t, false); } return false; } uint64_t LDateTime::OsTime() const { #ifdef WINDOWS FILETIME Utc; SYSTEMTIME System; System.wYear = _Year; System.wMonth = limit(_Month, 1, 12); System.wDay = limit(_Day, 1, 31); System.wHour = limit(_Hours, 0, 23); System.wMinute = limit(_Minutes, 0, 59); System.wSecond = limit(_Seconds, 0, 59); System.wMilliseconds = limit(_Thousands, 0, 999); System.wDayOfWeek = DayOfWeek(); if (SystemTimeToFileTime(&System, &Utc)) { uint64_t s = ((uint64_t)Utc.dwHighDateTime << 32) | Utc.dwLowDateTime; if (_Tz) // Adjust for timezone s -= (int64)_Tz * 60 * Second64Bit; return s; } else { DWORD Err = GetLastError(); LAssert(!"SystemTimeToFileTime failed."); } #else if (_Year < MIN_YEAR) return 0; struct tm t; ZeroObj(t); t.tm_year = _Year - 1900; t.tm_mon = _Month - 1; t.tm_mday = _Day; t.tm_hour = _Hours; t.tm_min = _Minutes; t.tm_sec = _Seconds; t.tm_isdst = -1; time_t sec = timegm(&t); if (sec == -1) return 0; if (_Tz) { // Adjust the output to UTC from the current timezone. sec -= _Tz * 60; } return sec; #endif return 0; } bool LDateTime::OsTime(uint64_t ts) { return Set((time_t)ts); } bool LDateTime::Get(LTimeStamp &s) const { #ifdef WINDOWS if (!IsValid()) { LAssert(!"Needs a valid date."); return false; } s.Ref() = OsTime(); if (!s.Valid()) return false; return true; #else if (_Year < MIN_YEAR) return false; auto sec = OsTime(); s.Ref() = (uint64)(sec + Offset1800) * Second64Bit + _Thousands; return true; #endif } LString LDateTime::Get() const { char buf[32]; int Ch = GetDate(buf, sizeof(buf)); buf[Ch++] = ' '; Ch += GetTime(buf+Ch, sizeof(buf)-Ch); return LString(buf, Ch); } void LDateTime::Get(char *Str, size_t SLen) const { if (Str) { GetDate(Str, SLen); size_t len = strlen(Str); if (len < SLen - 1) { Str[len++] = ' '; GetTime(Str+len, SLen-len); } } } bool LDateTime::Set(const char *Str) { if (!Str) return false; if (Strlen(Str) > 100) return false; char Local[256]; strcpy_s(Local, sizeof(Local), Str); char *Sep = strchr(Local, ' '); if (Sep) { *Sep++ = 0; if (!SetTime(Sep)) return false; } if (!SetDate(Local)) return false; return true; } void LDateTime::Month(char *m) { int i = IsMonth(m); if (i >= 0) _Month = i + 1; } int DateComponent(const char *s) { int64 i = Atoi(s); return i ? (int)i : LDateTime::IsMonth(s); } bool LDateTime::SetDate(const char *Str) { bool Status = false; if (Str) { auto T = LString(Str).SplitDelimit("/-.,_\\"); if (T.Length() == 3) { int i[3] = { DateComponent(T[0]), DateComponent(T[1]), DateComponent(T[2]) }; int fmt = _Format & GDTF_DATE_MASK; // Do some guessing / overrides. // Don't let _Format define the format completely. if (i[0] > 1000) { fmt = GDTF_YEAR_MONTH_DAY; } else if (i[2] > 1000) { if (i[0] > 12) fmt = GDTF_DAY_MONTH_YEAR; else if (i[1] > 12) fmt = GDTF_MONTH_DAY_YEAR; } switch (fmt) { case GDTF_MONTH_DAY_YEAR: { _Month = i[0]; _Day = i[1]; _Year = i[2]; break; } case GDTF_DAY_MONTH_YEAR: { _Day = i[0]; _Month = i[1]; _Year = i[2]; break; } case GDTF_YEAR_MONTH_DAY: { _Year = i[0]; _Month = i[1]; _Day = i[2]; break; } default: { _Year = i[2]; if ((DefaultFormat & GDTF_DATE_MASK) == GDTF_MONTH_DAY_YEAR) { // Assume m/d/yyyy _Day = i[1]; _Month = i[0]; } else { // Who knows??? // Assume d/m/yyyy _Day = i[0]; _Month = i[1]; } break; } } if (_Year < 100) { LAssert(_Day < 1000 && _Month < 1000); if (_Year >= 80) _Year += 1900; else _Year += 2000; } Status = true; } else { // Fall back to fuzzy matching auto T = LString(Str).SplitDelimit(" ,"); MonthHash Lut; int FMonth = 0; int FDay = 0; int FYear = 0; for (unsigned i=0; i 0) { if (i >= 1000) { FYear = i; } else if (i < 32) { FDay = i; } } } else { int i = Lut.Find(p); if (i) FMonth = i; } } if (FMonth && FDay) { Day(FDay); Month(FMonth); } if (FYear) { Year(FYear); } else { LDateTime Now; Now.SetNow(); Year(Now.Year()); } } } return Status; } bool LDateTime::SetTime(const char *Str) { if (!Str) return false; auto T = LString(Str).SplitDelimit(":."); if (T.Length() < 2 || T.Length() > 4) return false; #define SetClamp(out, in, minVal, maxVal) \ out = (int)Atoi(in.Get(), 10, 0); \ if (out > maxVal) out = maxVal; \ else if (out < minVal) out = minVal SetClamp(_Hours, T[0], 0, 23); SetClamp(_Minutes, T[1], 0, 59); SetClamp(_Seconds, T[2], 0, 59); _Thousands = 0; const char *s = T.Last(); if (s) { if (strchr(s, 'p') || strchr(s, 'P')) { if (_Hours != 12) _Hours += 12; } else if (strchr(s, 'a') || strchr(s, 'A')) { if (_Hours == 12) _Hours -= 12; } } if (T.Length() > 3) { LString t = "0."; t += s; _Thousands = (int) (t.Float() * 1000); } return true; } int LDateTime::IsWeekDay(const char *s) { for (unsigned n=0; n= 4) { Year((int)t[0].Int()); Month((int)t[1].Int()); Day((int)t[2].Int()); } else if (t[2].Length() >= 4) { Day((int)t[0].Int()); Month((int)t[1].Int()); Year((int)t[2].Int()); } else { LAssert(!"Unknown date format?"); return false; } } } else if (a[i].Length() == 4) Year((int)a[i].Int()); else if (!Day()) Day((int)a[i].Int()); } else if (IsAlpha(*c)) { int WkDay = IsWeekDay(c); if (WkDay >= 0) continue; int Mnth = IsMonth(c); if (Mnth >= 0) Month(Mnth + 1); } else if (*c == '-' || *c == '+') { c++; if (strlen(c) == 4) { // Timezone.. int64 Tz = a[i].Int(); int Hrs = (int) (Tz / 100); int Min = (int) (Tz % 100); SetTimeZone(Hrs * 60 + Min, false); } } } return IsValid(); } int LDateTime::Sizeof() { return sizeof(int) * 7; } bool LDateTime::Serialize(LFile &f, bool Write) { int32 i; if (Write) { #define wf(fld) i = fld; f << i; wf(_Day); wf(_Month); wf(_Year); wf(_Thousands); wf(_Seconds); wf(_Minutes); wf(_Hours); } else { #define rf(fld) f >> i; fld = i; rf(_Day); rf(_Month); rf(_Year); rf(_Thousands); rf(_Seconds); rf(_Minutes); rf(_Hours); } return true; } /* bool LDateTime::Serialize(ObjProperties *Props, char *Name, bool Write) { #ifndef LGI_STATIC if (Props && Name) { struct _Date { uint8_t Day; uint8_t Month; int16_t Year; uint8_t Hour; uint8_t Minute; uint16_t ThouSec; }; LAssert(sizeof(_Date) == 8); if (Write) { _Date d; d.Day = _Day; d.Month = _Month; d.Year = _Year; d.Hour = _Hours; d.Minute = _Minutes; d.ThouSec = (_Seconds * 1000) + _Thousands; return Props->Set(Name, &d, sizeof(d)); } else // Read { void *Ptr; int Len; if (Props->Get(Name, Ptr, Len) && sizeof(_Date) == Len) { _Date *d = (_Date*) Ptr; _Day = d->Day; _Month = d->Month; _Year = d->Year; _Hours = d->Hour; _Minutes = d->Minute; _Seconds = d->ThouSec / 1000; _Thousands = d->ThouSec % 1000; return true; } } } #endif return false; } */ int LDateTime::Compare(const LDateTime *Date) const { // this - *Date auto ThisTs = IsValid() ? Ts() : LTimeStamp(); auto DateTs = Date->IsValid() ? Date->Ts() : LTimeStamp(); if (ThisTs.Get() & 0x800000000000000) { Get(ThisTs); } // If these ever fire, the cast to int64_t will overflow LAssert((ThisTs.Get() & 0x800000000000000) == 0); LAssert((DateTs.Get() & 0x800000000000000) == 0); auto Diff = ThisTs - DateTs; if (Diff < 0) return -1; return Diff > 0 ? 1 : 0; } #define DATETIME_OP(op) \ bool LDateTime::operator op(const LDateTime &dt) const \ { \ auto a = Ts(); \ auto b = dt.Ts(); \ return a op b; \ } DATETIME_OP(<) DATETIME_OP(<=) DATETIME_OP(>) DATETIME_OP(>=) bool LDateTime::operator ==(const LDateTime &dt) const { return _Year == dt._Year && _Month == dt._Month && _Day == dt._Day && _Hours == dt._Hours && _Minutes == dt._Minutes && _Seconds == dt._Seconds && _Thousands == dt._Thousands; } bool LDateTime::operator !=(const LDateTime &dt) const { return _Year != dt._Year || _Month != dt._Month || _Day != dt._Day || _Hours != dt._Hours || _Minutes != dt._Minutes || _Seconds != dt._Seconds || _Thousands != dt._Thousands; } int LDateTime::DiffMonths(const LDateTime &dt) { int a = (Year() * 12) + Month(); int b = (dt.Year() * 12) + dt.Month(); return b - a; } LDateTime LDateTime::operator -(const LDateTime &dt) { LTimeStamp a, b; Get(a); dt.Get(b); /// Resolution of a second when using 64 bit timestamps int64 Sec = Second64Bit; int64 Min = 60 * Sec; int64 Hr = 60 * Min; int64 Day = 24 * Hr; int64 d = (int64)a.Get() - (int64)b.Get(); LDateTime r; r._Day = (int16) (d / Day); d -= r._Day * Day; r._Hours = (int16) (d / Hr); d -= r._Hours * Hr; r._Minutes = (int16) (d / Min); d -= r._Minutes * Min; r._Seconds = (int16) (d / Sec); #ifdef WIN32 d -= r._Seconds * Sec; r._Thousands = (int16) (d / 10000); #else r._Thousands = 0; #endif return r; } LDateTime LDateTime::operator +(const LDateTime &dt) { LDateTime s = *this; s.AddMonths(dt.Month()); s.AddDays(dt.Day()); s.AddHours(dt.Hours()); s.AddMinutes(dt.Minutes()); // s.AddSeconds(dt.Seconds()); return s; } LDateTime &LDateTime::operator =(const LDateTime &t) { _Day = t._Day; _Year = t._Year; _Thousands = t._Thousands; _Month = t._Month; _Seconds = t._Seconds; _Minutes = t._Minutes; _Hours = t._Hours; _Tz = t._Tz; _Format = t._Format; return *this; } LDateTime &LDateTime::operator =(struct tm *time) { if (time) { _Seconds = time->tm_sec; _Minutes = time->tm_min; _Hours = time->tm_hour; _Day = time->tm_mday; _Month = time->tm_mon + 1; _Year = time->tm_year + 1900; } else Empty(); return *this; } bool LDateTime::IsSameDay(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month() && Year() == d.Year(); } bool LDateTime::IsSameMonth(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month(); } bool LDateTime::IsSameYear(LDateTime &d) const { return Year() == d.Year(); } LDateTime LDateTime::StartOfDay() const { LDateTime dt = *this; dt.Hours(0); dt.Minutes(0); dt.Seconds(0); dt.Thousands(0); return dt; } LDateTime LDateTime::EndOfDay() const { LDateTime dt = *this; dt.Hours(23); dt.Minutes(59); dt.Seconds(59); dt.Thousands(999); return dt; } bool LDateTime::IsLeapYear(int Year) const { if (Year < 0) Year = _Year; if (Year % 4 != 0) return false; if (Year % 400 == 0) return true; if (Year % 100 == 0) return false; return true; } int LDateTime::DaysInMonth() const { if (_Month == 2 && IsLeapYear()) { return 29; } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return _Month >= 1 && _Month <= 12 ? DaysInMonth[_Month-1] : 0; } void LDateTime::AddSeconds(int64 Seconds) { LTimeStamp i; if (Get(i)) { i.Ref() += Seconds * Second64Bit; Set(i); } } void LDateTime::AddMinutes(int64 Minutes) { LTimeStamp i; if (Get(i)) { int64 delta = Minutes * 60 * Second64Bit; i.Ref() += delta; Set(i); } } void LDateTime::AddHours(int64 Hours) { LTimeStamp i; if (Get(i)) { i.Ref() += Hours * HourLength * Second64Bit; Set(i); } } bool LDateTime::AddDays(int64 Days) { if (!Days) return true; LTimeStamp Ts; if (!Get(Ts)) return false; Ts.Ref() += Days * LDateTime::DayLength * Second64Bit; bool b = Set(Ts); return b; } void LDateTime::AddMonths(int64 Months) { int64 m = _Month + Months; do { if (m < 1) { _Year--; m += 12; } else if (m > 12) { _Year++; m -= 12; } else { break; } } while (1); _Month = (int16) m; if (_Day > DaysInMonth()) _Day = DaysInMonth(); } LString LDateTime::DescribePeriod(double seconds) { int mins = (int) (seconds / 60); seconds -= mins * 60; int hrs = mins / 60; mins -= hrs * 60; int days = hrs / 24; hrs -= days * 24; LString s; if (days > 0) s.Printf("%id %ih %im %is", days, hrs, mins, (int)seconds); else if (hrs > 0) s.Printf("%ih %im %is", hrs, mins, (int)seconds); else if (mins > 0) s.Printf("%im %is", mins, (int)seconds); else s.Printf("%is", (int)seconds); return s; } LString LDateTime::DescribePeriod(LDateTime to) { auto ThisTs = Ts(); auto ToTs = to.Ts(); auto diff = ThisTs < ToTs ? ToTs - ThisTs : ThisTs - ToTs; auto seconds = (double)diff / LDateTime::Second64Bit; return DescribePeriod(seconds); } int LDateTime::MonthFromName(const char *Name) { if (Name) { for (int m=0; m<12; m++) { if (strnicmp(Name, MonthsShort[m], strlen(MonthsShort[m])) == 0) { return m + 1; break; } } } return -1; } bool LDateTime::Decode(const char *In) { // Test data: // // Tue, 6 Dec 2005 1:25:32 -0800 Empty(); if (!In) { LAssert(0); return false; } bool Status = false; // Tokenize delimited by whitespace LString::Array T = LString(In).SplitDelimit(", \t\r\n"); if (T.Length() < 2) { if (T[0].IsNumeric()) { // Some sort of timestamp? uint64_t Ts = Atoi(T[0].Get()); if (Ts > 0) { return SetUnix(Ts); } else return false; } else { // What now? return false; } } else { bool GotDate = false; for (unsigned i=0; i 31) { // Y/M/D? Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else if (Date[2].Int() > 31) { // D/M/Y? Day((int)Date[0].Int()); Year((int)Date[2].Int()); } else { // Ambiguous year... bool YrFirst = true; if (Date[0].Length() == 1) YrFirst = false; // else we really can't tell.. just go with year first if (YrFirst) { Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else { Day((int)Date[0].Int()); Year((int)Date[2].Int()); } LDateTime Now; Now.SetNow(); if (Year() + 2000 <= Now.Year()) Year(2000 + Year()); else Year(1900 + Year()); } if (Date[1].IsNumeric()) Month((int)Date[1].Int()); else { int m = MonthFromName(Date[1]); if (m > 0) Month(m); } GotDate = true; Status = true; } else if (s.Find(":") >= 0) { // whole time // Do some validation bool Valid = true; for (char *c = s; *c && Valid; c++) { if (!(IsDigit(*c) || *c == ':')) Valid = false; } if (Valid) { LString::Array Time = s.Split(":"); if (Time.Length() == 2 || Time.Length() == 3) { // Hour int i = (int) Time[0].Int(); if (i >= 0) Hours(i); if (s.Lower().Find("p") >= 0) { if (Hours() < 12) Hours(Hours() + 12); } // Minute i = (int) Time[1].Int(); if (i >= 0) Minutes(i); if (Time.Length() == 3) { // Second i = (int) Time[2].Int(); if (i >= 0) Seconds(i); } Status = true; } } } else if (IsAlpha(s(0))) { // text int m = MonthFromName(s); if (m > 0) Month(m); } else if (strchr("+-", *s)) { // timezone DoTimeZone: LDateTime Now; double OurTmz = (double)Now.SystemTimeZone() / 60; if (s && strchr("-+", *s) && strlen(s) == 5) { #if 1 int i = atoi(s); int hr = i / 100; int min = i % 100; SetTimeZone(hr * 60 + min, false); #else // adjust for timezone char Buf[32]; memcpy(Buf, s, 3); Buf[3] = 0; double TheirTmz = atof(Buf); memcpy(Buf+1, s + 3, 2); TheirTmz += (atof(Buf) / 60); if (Tz) { *Tz = TheirTmz; } double AdjustHours = OurTmz - TheirTmz; AddMinutes((int) (AdjustHours * 60)); #endif } else { // assume GMT AddMinutes((int) (OurTmz * 60)); } } else if (s.IsNumeric()) { int Count = 0; for (char *c = s; *c; c++) { if (!IsDigit(*c)) break; Count++; } if (Count <= 2) { if (Day()) { // We already have a day... so this might be // a 2 digit year... LDateTime Now; Now.SetNow(); int Yr = atoi(s); if (2000 + Yr <= Now.Year()) Year(2000 + Yr); else Year(1900 + Yr); } else { // A day number (hopefully)? Day((int)s.Int()); } } else if (Count == 4) { if (!Year()) { // A year! Year((int)s.Int()); Status = true; } else { goto DoTimeZone; } // My one and only Y2K fix // d.Year((Yr < 100) ? (Yr > 50) ? 1900+Yr : 2000+Yr : Yr); } } } } return Status; } bool LDateTime::GetVariant(const char *Name, LVariant &Dst, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: // Type: Int32 Dst = Year(); break; case DateMonth: // Type: Int32 Dst = Month(); break; case DateDay: // Type: Int32 Dst = Day(); break; case DateHour: // Type: Int32 Dst = Hours(); break; case DateMinute: // Type: Int32 Dst = Minutes(); break; case DateSecond: // Type: Int32 Dst = Seconds(); break; case DateDate: // Type: String { char s[32]; GetDate(s, sizeof(s)); Dst = s; break; } case DateTime: // Type: String { char s[32]; GetTime(s, sizeof(s)); Dst = s; break; } case TypeString: // Type: String case DateDateAndTime: // Type: String { char s[32]; Get(s, sizeof(s)); Dst = s; break; } case TypeInt: // Type: Int64 case DateTimestamp: // Type: Int64 { LTimeStamp i; if (Get(i)) Dst = (int64)i.Get(); break; } case DateSecond64Bit: { Dst = Second64Bit; break; } default: { return false; } } return true; } bool LDateTime::SetVariant(const char *Name, LVariant &Value, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: Year(Value.CastInt32()); break; case DateMonth: Month(Value.CastInt32()); break; case DateDay: Day(Value.CastInt32()); break; case DateHour: Hours(Value.CastInt32()); break; case DateMinute: Minutes(Value.CastInt32()); break; case DateSecond: Seconds(Value.CastInt32()); break; case DateDate: SetDate(Value.Str()); break; case DateTime: SetTime(Value.Str()); break; case DateDateAndTime: Set(Value.Str()); break; case DateTimestamp: Set((uint64)Value.CastInt64()); break; default: return false; } return true; } bool LDateTime::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { switch (LStringToDomProp(Name)) { case DateSetNow: SetNow(); if (ReturnValue) *ReturnValue = true; break; case DateSetStr: if (Args.Length() < 1) return false; bool Status; if (Args[0]->Type == GV_INT64) Status = Set((uint64) Args[0]->Value.Int64); else Status = Set(Args[0]->Str()); if (ReturnValue) *ReturnValue = Status; break; case DateGetStr: { char s[256] = ""; Get(s, sizeof(s)); if (ReturnValue) *ReturnValue = s; break; } default: return false; } return true; } #ifdef _DEBUG #define DATE_ASSERT(i) \ if (!(i)) \ { \ LAssert(!"LDateTime unit test failed."); \ return false; \ } bool LDateTime_Test() { // Check 64bit get/set LDateTime t("1/1/2017 0:0:0"); LTimeStamp i; DATE_ASSERT(t.Get(i)); LgiTrace("Get='%s'\n", t.Get().Get()); auto i2 = i + (24ULL * 60 * 60 * LDateTime::Second64Bit); LDateTime t2; t2.SetFormat(GDTF_DAY_MONTH_YEAR); t2.Set(i2); LString s = t2.Get(); LgiTrace("Set='%s'\n", s.Get()); DATE_ASSERT(!stricmp(s, "2/1/2017 12:00:00a") || !stricmp(s, "2/01/2017 12:00:00a")); t.SetNow(); LgiTrace("Now.Local=%s Tz=%.2f\n", t.Get().Get(), t.GetTimeZoneHours()); t2 = t; t2.ToUtc(); LgiTrace("Now.Utc=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); t2.ToLocal(); LgiTrace("Now.Local=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); DATE_ASSERT(t == t2); return true; } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// LTimeStamp <imeStamp::operator =(const time_t unixTime) { #if defined(WINDOWS) ts = (unixTime + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK; #else ts = (unixTime + LDateTime::Offset1800) * LDateTime::Second64Bit; #endif return *this; } LTimeStamp <imeStamp::operator =(const LDateTime &dt) { dt.Get(*this); return *this; } time_t LTimeStamp::Unix() const { #if defined(WINDOWS) return (ts / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH; #else return (ts / LDateTime::Second64Bit) - LDateTime::Offset1800; #endif } diff --git a/src/common/Widgets/ScrollBar.cpp b/src/common/Widgets/ScrollBar.cpp --- a/src/common/Widgets/ScrollBar.cpp +++ b/src/common/Widgets/ScrollBar.cpp @@ -1,734 +1,734 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/LgiRes.h" #define DrawBorder(dc, r, edge) LThinBorder(dc, r, edge) #if defined(LGI_CARBON) #define MAC_SKIN 1 #elif defined(LGI_COCOA) #define MAC_LOOK 1 #else #define WINXP_LOOK 1 #endif enum ScrollZone { BTN_NONE, BTN_SUB, BTN_SLIDE, BTN_ADD, BTN_PAGE_SUB, BTN_PAGE_ADD, }; class LScrollBarPrivate { public: LScrollBar *Widget; bool Vertical; int64 Value, Min, Max, Page; LRect Sub, Add, Slide, PageSub, PageAdd; int Clicked; bool Over; int SlideOffset; int Ignore; LScrollBarPrivate(LScrollBar *w) { Ignore = 0; Widget = w; Vertical = true; Value = Min = 0; Max = -1; Page = 1; Clicked = BTN_NONE; Over = false; } bool IsVertical() { return Vertical; } int IsOver() { return Over ? Clicked : BTN_NONE; } void DrawIcon(LSurface *pDC, LRect &r, bool Add, LSystemColour c) { pDC->Colour(c); int IconSize = MAX(r.X(), r.Y()) * 2 / 6; int Cx = r.x1 + (r.X() >> 1); int Cy = r.y1 + (r.Y() >> 1); int Off = (IconSize >> 1) * (Add ? 1 : -1); int x = Cx + (IsVertical() ? 0 : Off); int y = Cy + (IsVertical() ? Off : 0); if (Add) { if (IsOver() == BTN_ADD) { x++; y++; } if (IsVertical()) { // down for (int i=0; iLine(x-i, y, x+i, y); } else { // right for (int i=0; iLine(x, y-i, x, y+i); } } else { if (IsOver() == BTN_SUB) { x++; y++; } if (IsVertical()) { // up for (int i=0; iLine(x-i, y, x+i, y); } else { // left for (int i=0; iLine(x, y-i, x, y+i); } } } void OnPaint(LSurface *pDC) { LColour SlideCol(L_MED); SlideCol.Rgb( (255 + SlideCol.r()) >> 1, (255 + SlideCol.g()) >> 1, (255 + SlideCol.b()) >> 1); #if MAC_LOOK || MAC_SKIN pDC->Colour(SlideCol); pDC->Rectangle(); if (IsValid()) { LRect r = Slide; r.Inset(3, 3); pDC->Colour(L_LOW); // pDC->Rectangle(&r); double rad = (IsVertical() ? (double)r.X() : (double)r.Y()) / 2; double cx = (double)r.x1 + rad; pDC->FilledArc(cx, r.y1 + rad, rad, 0, 180); pDC->Rectangle(r.x1, r.y1 + rad, r.x2, r.y2 - rad); pDC->FilledArc(cx, r.y2 - rad, rad, 180, 0); } #elif WINXP_LOOK // left/up button LRect r = Sub; DrawBorder(pDC, r, IsOver() == BTN_SUB ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, false, IsValid() ? L_BLACK : L_LOW); // right/down r = Add; DrawBorder(pDC, r, IsOver() == BTN_ADD ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); DrawIcon(pDC, r, true, IsValid() ? L_BLACK : L_LOW); // printf("Paint %ix%i, %s\n", pDC->X(), pDC->Y(), Widget->GetPos().GetStr()); if (IsValid()) { // slide space pDC->Colour(SlideCol); pDC->Rectangle(&PageSub); pDC->Rectangle(&PageAdd); // slide button r = Slide; DrawBorder(pDC, r, DefaultRaisedEdge); // IsOver() == BTN_SLIDE ? SUNKEN : RAISED); pDC->Colour(L_MED); if (r.Valid()) pDC->Rectangle(&r); } else { pDC->Colour(SlideCol); pDC->Rectangle(&Slide); } #else #error "No look and feel defined." #endif } int GetWidth() { return IsVertical() ? Widget->X() : Widget->Y(); } int GetLength() { return (IsVertical() ? Widget->Y() : Widget->X()) #if !MAC_LOOK - (GetWidth() * 2) #endif ; } int64 GetRange() { return Max >= Min ? Max - Min + 1 : 0; } bool IsValid() { return Max >= Min; } void CalcRegions() { LRect r = Widget->GetPos(); Vertical = r.Y() > r.X(); int w = GetWidth(); int len = GetLength(); // Button sizes #if MAC_LOOK Sub.ZOff(-1, -1); Add.ZOff(-1, -1); #else Sub.ZOff(w-1, w-1); Add.ZOff(w-1, w-1); // Button positions if (IsVertical()) Add.Offset(0, Widget->GetPos().Y()-w); else Add.Offset(Widget->GetPos().X()-w, 0); #endif // Slider int64 Start, End; #if LGI_SDL int MinSize = w; // Touch UI needs large slide.... #else int MinSize = 18; #endif // printf("Calc %i, " LPrintfInt64 ", " LPrintfInt64 "\n", IsValid(), Min, Max); if (IsValid()) { int64 Range = GetRange(); int64 Size = Range ? MIN((int)Page, Range) * len / Range : len; if (Size < MinSize) Size = MinSize; Start = Range > Page ? Value * (len - Size) / (Range - (int)Page) : 0; End = Start + Size; /* printf("Range=%i Page=%i Size=%i Start=%i End=%i\n", (int)Range, (int)Page, (int)Size, (int)Start, (int)End); */ if (IsVertical()) { Slide.ZOff(w-1, (int) (End-Start-1)); #if MAC_LOOK Slide.Offset(0, (int) (r.y1+Start)); #else Slide.Offset(0, (int) (Sub.y2+1+Start)); #endif if (Start > 1) { PageSub.x1 = Slide.x1; #if MAC_LOOK PageSub.y1 = 0; #else PageSub.y1 = Sub.y2 + 1; #endif PageSub.x2 = Slide.x2; PageSub.y2 = Slide.y1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.y1 - 2) { PageAdd.x1 = Slide.x1; PageAdd.x2 = Slide.x2; PageAdd.y1 = Slide.y2 + 1; #if MAC_LOOK PageAdd.y2 = r.Y()-1; #else PageAdd.y2 = Add.y1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } else { Slide.ZOff((int)(End-Start-1), w-1); Slide.Offset((int)(Sub.x2+1+Start), 0); if (Start > 1) { PageSub.y1 = Slide.y1; #if MAC_LOOK PageSub.x1 = 0; #else PageSub.x1 = Sub.x2 + 1; #endif PageSub.y2 = Slide.y2; PageSub.x2 = Slide.x1 - 1; } else { PageSub.ZOff(-1, -1); } if (End < Add.x1 - 2) { PageAdd.y1 = Slide.y1; PageAdd.y2 = Slide.y2; PageAdd.x1 = Slide.x2 + 1; #if MAC_LOOK PageAdd.x2 = r.X() - 1; #else PageAdd.x2 = Add.x1 - 1; #endif } else { PageAdd.ZOff(-1, -1); } } } else { PageAdd.ZOff(-1, -1); PageSub.ZOff(-1, -1); Slide = Widget->GetClient(); if (IsVertical()) { Slide.Inset(0, Sub.y2 + 1); } else { Slide.Inset(Sub.x2 + 1, 0); } } } int OnHit(int x, int y) { #if MAC_SKIN HIThemeTrackDrawInfo Info; LRect Client = Widget->GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = Min; Info.max = Max - Page; Info.value = Value; Info.reserved = 0; Info.attributes = (Widget->Vertical() ? 0 : kThemeTrackHorizontal) | (Widget->Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Widget->Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = Page; Info.trackInfo.scrollbar.pressState = false; HIPoint pt = {(CGFloat)x, (CGFloat)y}; ControlPartCode part; Boolean b = HIThemeHitTestTrack(&Info, &pt, &part); if (b) { switch (part) { case kAppearancePartUpButton: return BTN_SUB; case kAppearancePartDownButton: return BTN_ADD; case 129: return BTN_SLIDE; case kControlPageUpPart: return BTN_PAGE_SUB; case kControlPageDownPart: return BTN_PAGE_ADD; default: printf("%s:%i - Unknown scroll bar hittest value: %i\n", _FL, part); break; } } #else if (Sub.Overlap(x, y)) return BTN_SUB; if (Slide.Overlap(x, y)) return BTN_SLIDE; if (Add.Overlap(x, y)) return BTN_ADD; if (PageSub.Overlap(x, y)) return BTN_PAGE_SUB; if (PageAdd.Overlap(x, y)) return BTN_PAGE_ADD; #endif return BTN_NONE; } int OnClick(int Btn, int x, int y) { if (IsValid()) { switch (Btn) { case BTN_SUB: { SetValue(Value-1); break; } case BTN_ADD: { SetValue(Value+1); break; } case BTN_PAGE_SUB: { SetValue(Value-Page); break; } case BTN_PAGE_ADD: { SetValue(Value+Page); break; } case BTN_SLIDE: { SlideOffset = IsVertical() ? y - Slide.y1 : x - Slide.x1; break; } } } return false; } void SetValue(int64 i) { if (i < Min) { i = Min; } if (IsValid() && i > Max - Page + 1) { i = MAX(Min, Max - Page + 1); } if (Value != i) { Value = i; CalcRegions(); Widget->Invalidate(); LViewI *n = Widget->GetNotify() ? Widget->GetNotify() : Widget->GetParent(); if (n) - n->OnNotify(Widget, LNotifyItemChange); + n->OnNotify(Widget, LNotifyValueChanged); } } }; ///////////////////////////////////////////////////////////////////////////////////// LScrollBar::LScrollBar() : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); } LScrollBar::LScrollBar(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_ScrollBar) { d = new LScrollBarPrivate(this); SetId(id); if (name) Name(name); if (cx > cy) { SetVertical(false); } LResources::StyleElement(this); } LScrollBar::~LScrollBar() { // printf("%s:%i - %p::~LScrollBar\n", _FL, this); DeleteObj(d); } bool LScrollBar::Valid() { return d->Max > d->Min; } int LScrollBar::SCROLL_BAR_SIZE = 0; int LScrollBar::GetScrollSize() { if (!SCROLL_BAR_SIZE) SCROLL_BAR_SIZE = std::max(15, LScreenDpi().x / 6); return SCROLL_BAR_SIZE; } bool LScrollBar::Attach(LViewI *p) { bool Status = LControl::Attach(p); #if 0 printf("%p::Attach scroll bar to %s, Status=%i, Vis=%i\n", this, p->GetClass(), Status, Visible()); #endif return Status; } void LScrollBar::OnPaint(LSurface *pDC) { #if MAC_SKIN #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif HIThemeTrackDrawInfo Info; LRect Client = GetClient(); HIRect Rc = Client; Info.version = 0; Info.kind = kThemeScrollBarMedium; Info.bounds = Rc; Info.min = d->Min; Info.max = d->Max - d->Page + 1; Info.value = d->Value; Info.reserved = 0; Info.attributes = (Vertical() ? 0 : kThemeTrackHorizontal) | (Focus() ? kThemeTrackHasFocus : 0) | kThemeTrackShowThumb; Info.enableState = Enabled() ? kThemeTrackActive : kThemeTrackDisabled; Info.filler1 = 0; Info.trackInfo.scrollbar.viewsize = d->Page; Info.trackInfo.scrollbar.pressState = false; CGContextRef Cr = pDC->Handle(); OSStatus e = HIThemeDrawTrack(&Info, NULL, Cr, kHIThemeOrientationNormal); if (e) printf("%s:%i - HIThemeDrawTrack failed with %li\n", _FL, e); #else d->OnPaint(pDC); #endif } void LScrollBar::OnPosChange() { d->CalcRegions(); } void LScrollBar::OnMouseClick(LMouse &m) { if (d->Max >= d->Min) { int Hit = d->OnHit(m.x, m.y); Capture(m.Down()); if (m.Down()) { if (Hit != d->Clicked) { d->Clicked = Hit; d->Over = true; Invalidate(); d->OnClick(Hit, m.x, m.y); if (Hit != BTN_SLIDE) { d->Ignore = 2; SetPulse(50); } } } else { if (d->Clicked) { d->Clicked = BTN_NONE; d->Over = false; Invalidate(); } } } } void LScrollBar::OnMouseMove(LMouse &m) { if (IsCapturing()) { if (d->Clicked == BTN_SLIDE) { if (d->GetLength()) { int64 Range = d->GetRange(); int Len = d->GetLength(); int Size = d->IsVertical() ? d->Slide.Y() : d->Slide.X(); int Px = (d->IsVertical() ? m.y : m.x) - d->GetWidth() - d->SlideOffset; int64 Value = Px * (Range - d->Page) / (Len - Size); d->SetValue(Value); } } else { int Hit = d->OnHit(m.x, m.y); bool Over = Hit == d->Clicked; if (Over != d->Over) { d->Over = Over; Invalidate(); } } } } bool LScrollBar::OnKey(LKey &k) { return false; } bool LScrollBar::OnMouseWheel(double Lines) { return false; } bool LScrollBar::Vertical() { return d->Vertical; } void LScrollBar::SetVertical(bool v) { d->Vertical = v; d->CalcRegions(); Invalidate(); } int64 LScrollBar::Value() { return d->Value; } void LScrollBar::Value(int64 v) { d->SetValue(v); } LRange LScrollBar::GetRange() const { return LRange(d->Min, d->Max - d->Min + 1); } void LScrollBar::Limits(int64 &Low, int64 &High) { Low = d->Min; High = d->Max; } bool LScrollBar::SetRange(const LRange &r) { if (d->Min != r.Start || d->Max != r.End() - 1) { d->Min = r.Start; d->Max = r.End() - 1; d->Page = MIN(d->Page, d->GetRange()); d->CalcRegions(); Invalidate(); OnConfigure(); } return true; } void LScrollBar::SetLimits(int64 Low, int64 High) { SetRange(LRange(Low, High-Low+1)); } int64 LScrollBar::Page() { return d->Page; } void LScrollBar::SetPage(int64 i) { if (d->Page != i) { d->Page = MAX(i, 1); d->CalcRegions(); Invalidate(); OnConfigure(); } } LMessage::Result LScrollBar::OnEvent(LMessage *Msg) { return LView::OnEvent(Msg); } void LScrollBar::OnPulse() { if (d->Ignore > 0) { d->Ignore--; } else { LMouse m; if (GetMouse(m)) { int Hit = d->OnHit(m.x, m.y); if (Hit == d->Clicked) { d->OnClick(d->Clicked, m.x, m.y); } } } } diff --git a/src/linux/CrashHandler/CMakeLists.txt b/src/linux/CrashHandler/CMakeLists.txt --- a/src/linux/CrashHandler/CMakeLists.txt +++ b/src/linux/CrashHandler/CMakeLists.txt @@ -1,19 +1,22 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(GTK3 REQUIRED gtk+-3.0) set(CMAKE_CXX_STANDARD 14) add_executable(crash-handler CrashHandler.cpp) target_include_directories(crash-handler PRIVATE ../../../include) +target_compile_definitions(crash-handler + PRIVATE + LGI_STATIC) if (LINUX) target_include_directories(crash-handler PRIVATE ../../../include/lgi/linux/Gtk ${GTK3_INCLUDE_DIRS}) endif() \ No newline at end of file diff --git a/src/linux/Lgi/Window.cpp b/src/linux/Lgi/Window.cpp --- a/src/linux/Lgi/Window.cpp +++ b/src/linux/Lgi/Window.cpp @@ -1,1904 +1,1904 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Popup.h" #include "lgi/common/Panel.h" #include "lgi/common/Notifications.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" using namespace Gtk; #undef Status #include "LgiWidget.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 extern Gtk::GdkDragAction EffectToDragAction(int Effect); /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate { public: int Sx = 0, Sy = 0; LKey LastKey; LArray Hooks; bool SnapToEdge = false; LString Icon; LRect Decor; gulong DestroySig = 0; LAutoPtr IconImg; LAttachState AttachState = LUnattached; bool ShowTitleBar = true; bool WillFocus = true; // State GdkWindowState State; bool HadCreateEvent = false; // Focus stuff OsView FirstFocus = NULL; LViewI *Focus = NULL; bool Active = false; LWindowPrivate() { Decor.ZOff(-1, -1); State = (Gtk::GdkWindowState)0; LastKey.vkey = 0; LastKey.c16 = 0; LastKey.Data = 0; LastKey.IsChar = 0; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////// #define GWND_CREATE 0x0010000 LWindow::LWindow(GtkWidget *w) : LView(0) { d = new LWindowPrivate; _QuitOnClose = false; Wnd = GTK_WINDOW(w); if (Wnd) { gtk_window_set_decorated(Wnd, d->ShowTitleBar); g_object_set_data(G_OBJECT(Wnd), "LViewI", (LViewI*)this); } _RootAlloc.ZOff(-1, -1); _Window = this; WndFlags |= GWND_CREATE; ClearFlag(WndFlags, GWF_VISIBLE); _Lock = new LMutex("LWindow"); } LWindow::~LWindow() { d->AttachState = LDetaching; if (Wnd && d->DestroySig > 0) { // As we are already in the destructor, we don't want // GtkWindowDestroy to try and delete the object again. g_signal_handler_disconnect(Wnd, d->DestroySig); } if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (_Root) { lgi_widget_detach(_Root); _Root = NULL; } // This needs to be before the 'gtk_widget_destroy' because that will delete the menu's widgets. DeleteObj(Menu); if (Wnd) { gtk_widget_destroy(GTK_WIDGET(Wnd)); Wnd = NULL; } d->AttachState = LUnattached; DeleteObj(d); DeleteObj(_Lock); } int LWindow::WaitThread() { return 0; // Nop for linux } bool LWindow::SetIcon(const char *FileName) { LString a; if (Wnd) { if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } if (!LFileExists(FileName)) { LgiTrace("%s:%i - SetIcon failed to find '%s'\n", _FL, FileName); return false; } else { #if defined(LINUX) LAppInst->SetApplicationIcon(FileName); #endif #if _MSC_VER GError *error = NULL; if (gtk_window_set_icon_from_file(Wnd, FileName, &error)) return true; #else // On windows this is giving a red for blue channel swap error... if (d->IconImg.Reset(GdcD->Load(a))) gtk_window_set_icon(Wnd, d->IconImg->CreatePixBuf()); #endif } } if (FileName != d->Icon.Get()) d->Icon = FileName; return d->Icon != NULL; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { return d->Active; } bool LWindow::SetActive() { if (!Wnd) return false; gtk_window_present(Wnd); return true; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool i) { ThreadCheck(); auto w = GTK_WIDGET(Wnd); if (!w) return; if (i) gtk_widget_show(w); else gtk_widget_hide(w); } bool LWindow::Obscured() { return d->State == GDK_WINDOW_STATE_WITHDRAWN || d->State == GDK_WINDOW_STATE_ICONIFIED; } void LWindow::_OnViewDelete() { delete this; } void LWindow::OnGtkRealize() { d->AttachState = LAttached; LView::OnGtkRealize(); } void LWindow::OnGtkDelete() { // Delete everything we own... // DeleteObj(Menu); #if 0 while (Children.Length()) { LViewI *c = Children.First(); c->Detach(); } #else for (unsigned i=0; iGetGView(); if (v) v->OnGtkDelete(); } #endif // These will be destroyed by GTK after returning from LWindowCallback Wnd = NULL; #ifndef __GTK_H__ _View = NULL; #endif } LRect *LWindow::GetDecorSize() { return d->Decor.x2 >= 0 ? &d->Decor : NULL; } void LWindow::SetDecor(bool Visible) { if (Wnd) gtk_window_set_decorated (Wnd, Visible); else LgiTrace("%s:%i - No window to set decor.\n", _FL); } LViewI *LWindow::WindowFromPoint(int x, int y, bool Debug) { if (!_Root) return NULL; auto rpos = GtkGetPos(_Root).ZeroTranslate(); if (!rpos.Overlap(x, y)) return NULL; return LView::WindowFromPoint(x - rpos.x1, y - rpos.y1, Debug); } bool LWindow::TranslateMouse(LMouse &m) { m.Target = WindowFromPoint(m.x, m.y, false); if (!m.Target) return false; LViewI *w = this; for (auto p = m.Target; p; p = p->GetParent()) { if (p == w) { auto ppos = GtkGetPos(GTK_WIDGET(WindowHandle())); m.x -= ppos.x1; m.y -= ppos.y1; break; } else { auto pos = p->GetPos(); m.x -= pos.x1; m.y -= pos.y1; } } return true; } gboolean LWindow::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { if (!event) { printf("%s:%i - No event.\n", _FL); return FALSE; } #if 0 if (event->type != 28) LgiTrace("%s::OnGtkEvent(%i) name=%s\n", GetClass(), event->type, Name()); #endif switch (event->type) { case GDK_DELETE: { bool Close = OnRequestClose(false); if (Close) OnGtkDelete(); return !Close; } case GDK_DESTROY: { delete this; return true; } case GDK_KEY_PRESS: case GDK_KEY_RELEASE: { auto ModFlags = LAppInst->GetKeyModFlags(); auto e = &event->key; #define KEY(name) GDK_KEY_##name LKey k; k.Down(e->type == GDK_KEY_PRESS); k.c16 = k.vkey = e->keyval; k.Shift((e->state & ModFlags->Shift) != 0); k.Ctrl((e->state & ModFlags->Ctrl) != 0); k.Alt((e->state & ModFlags->Alt) != 0); k.System((e->state & ModFlags->System) != 0); #if 0//def _DEBUG if (k.vkey == GDK_KEY_Meta_L || k.vkey == GDK_KEY_Meta_R) break; #endif k.IsChar = !k.Ctrl() && !k.Alt() && !k.System() && (k.c16 >= ' ') && (k.c16 >> 8 != 0xff); if (e->keyval > 0xff && e->string != NULL) { // Convert string to unicode char auto *i = e->string; ptrdiff_t len = strlen(i); k.c16 = LgiUtf8To32((uint8_t *&) i, len); } switch (k.vkey) { case GDK_KEY_ISO_Left_Tab: case KEY(Tab): k.IsChar = true; k.c16 = k.vkey = LK_TAB; break; case KEY(Return): case KEY(KP_Enter): k.IsChar = true; k.c16 = k.vkey = LK_RETURN; break; case GDK_KEY_BackSpace: k.c16 = k.vkey = LK_BACKSPACE; k.IsChar = !k.Ctrl() && !k.Alt() && !k.System(); break; case KEY(Left): k.vkey = k.c16 = LK_LEFT; break; case KEY(Right): k.vkey = k.c16 = LK_RIGHT; break; case KEY(Up): k.vkey = k.c16 = LK_UP; break; case KEY(Down): k.vkey = k.c16 = LK_DOWN; break; case KEY(Page_Up): k.vkey = k.c16 = LK_PAGEUP; break; case KEY(Page_Down): k.vkey = k.c16 = LK_PAGEDOWN; break; case KEY(Home): k.vkey = k.c16 = LK_HOME; break; case KEY(End): k.vkey = k.c16 = LK_END; break; case KEY(Delete): k.vkey = k.c16 = LK_DELETE; break; #define KeyPadMap(gdksym, ch, is) \ case gdksym: k.c16 = ch; k.IsChar = is; break; KeyPadMap(KEY(KP_0), '0', true) KeyPadMap(KEY(KP_1), '1', true) KeyPadMap(KEY(KP_2), '2', true) KeyPadMap(KEY(KP_3), '3', true) KeyPadMap(KEY(KP_4), '4', true) KeyPadMap(KEY(KP_5), '5', true) KeyPadMap(KEY(KP_6), '6', true) KeyPadMap(KEY(KP_7), '7', true) KeyPadMap(KEY(KP_8), '8', true) KeyPadMap(KEY(KP_9), '9', true) KeyPadMap(KEY(KP_Space), ' ', true) KeyPadMap(KEY(KP_Tab), '\t', true) KeyPadMap(KEY(KP_F1), LK_F1, false) KeyPadMap(KEY(KP_F2), LK_F2, false) KeyPadMap(KEY(KP_F3), LK_F3, false) KeyPadMap(KEY(KP_F4), LK_F4, false) KeyPadMap(KEY(KP_Home), LK_HOME, false) KeyPadMap(KEY(KP_Left), LK_LEFT, false) KeyPadMap(KEY(KP_Up), LK_UP, false) KeyPadMap(KEY(KP_Right), LK_RIGHT, false) KeyPadMap(KEY(KP_Down), LK_DOWN, false) KeyPadMap(KEY(KP_Page_Up), LK_PAGEUP, false) KeyPadMap(KEY(KP_Page_Down), LK_PAGEDOWN, false) KeyPadMap(KEY(KP_End), LK_END, false) KeyPadMap(KEY(KP_Begin), LK_HOME, false) KeyPadMap(KEY(KP_Insert), LK_INSERT, false) KeyPadMap(KEY(KP_Delete), LK_DELETE, false) KeyPadMap(KEY(KP_Equal), '=', true) KeyPadMap(KEY(KP_Multiply), '*', true) KeyPadMap(KEY(KP_Add), '+', true) KeyPadMap(KEY(KP_Separator), '|', true) // is this right? KeyPadMap(KEY(KP_Subtract), '-', true) KeyPadMap(KEY(KP_Decimal), '.', true) KeyPadMap(KEY(KP_Divide), '/', true) } if (ModFlags->Debug) { :: LString Msg; Msg.Printf("e->state=%x %s", e->state, ModFlags->FlagsToString(e->state).Get()); k.Trace(Msg); } auto v = d->Focus ? d->Focus : this; if (!HandleViewKey(v->GetGView(), k)) { if (!k.Down()) return false; if (k.vkey == LK_TAB || k.vkey == KEY(ISO_Left_Tab)) { // Do tab between controls LArray a; BuildTabStops(this, a); int idx = a.IndexOf((LViewI*)v); if (idx >= 0) { idx += k.Shift() ? -1 : 1; int next_idx = idx == 0 ? a.Length() -1 : idx % a.Length(); LViewI *next = a[next_idx]; if (next) { // LgiTrace("Setting focus to %i of %i: %s, %s, %i\n", next_idx, a.Length(), next->GetClass(), next->GetPos().GetStr(), next->GetId()); next->Focus(true); } } } else if (k.System()) { if (ToLower(k.c16) == 'q') { auto AppWnd = LAppInst->AppWnd; auto Wnd = AppWnd ? AppWnd : this; if (Wnd->OnRequestClose(false)) { Wnd->Quit(); return true; } } } else return false; } break; } case GDK_CONFIGURE: { GdkEventConfigure *c = &event->configure; Pos.Set(c->x, c->y, c->x+c->width-1, c->y+c->height-1); // printf("%s::GDK_CONFIGURE %s\n", GetClass(), Pos.GetStr()); OnPosChange(); return FALSE; break; } case GDK_FOCUS_CHANGE: { d->Active = event->focus_change.in; #if 0 printf("%s/%s::GDK_FOCUS_CHANGE(%i)\n", GetClass(), Name(), event->focus_change.in); #endif break; } case GDK_WINDOW_STATE: { d->State = event->window_state.new_window_state; break; } case GDK_PROPERTY_NOTIFY: { gchar *Name = gdk_atom_name (event->property.atom); if (!Name) break; if (!_stricmp(Name, "_NET_FRAME_EXTENTS")) { // printf("PropChange: %i - %s\n", event->property.atom, Name); unsigned long *extents = NULL; if (gdk_property_get(event->property.window, gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE), gdk_atom_intern ("CARDINAL", FALSE), 0, sizeof (unsigned long) * 4, FALSE, NULL, NULL, NULL, (guchar **)&extents)) { d->Decor.Set(extents[0], extents[2], extents[1], extents[3]); g_free(extents); } else printf("%s:%i - Error: gdk_property_get failed.\n", _FL); } g_free(Name); break; } case GDK_UNMAP: { // LgiTrace("%s:%i - Unmap %s\n", _FL, GetClass()); break; } case GDK_VISIBILITY_NOTIFY: { // LgiTrace("%s:%i - Visible %s\n", _FL, GetClass()); break; } case GDK_DRAG_ENTER: { LgiTrace("%s:%i - GDK_DRAG_ENTER\n", _FL); break; } case GDK_DRAG_LEAVE: { LgiTrace("%s:%i - GDK_DRAG_LEAVE\n", _FL); break; } case GDK_DRAG_MOTION: { LgiTrace("%s:%i - GDK_DRAG_MOTION\n", _FL); break; } case GDK_DRAG_STATUS: { LgiTrace("%s:%i - GDK_DRAG_STATUS\n", _FL); break; } case GDK_DROP_START: { LgiTrace("%s:%i - GDK_DROP_START\n", _FL); break; } case GDK_DROP_FINISHED: { LgiTrace("%s:%i - GDK_DROP_FINISHED\n", _FL); break; } default: { printf("%s:%i - Unknown event %i\n", _FL, event->type); return false; } } return true; } static gboolean GtkWindowDestroy(GtkWidget *widget, LWindow *This) { delete This; return true; } static void GtkWindowRealize(GtkWidget *widget, LWindow *This) { #if 0 LgiTrace("GtkWindowRealize, This=%p(%s\"%s\")\n", This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif This->OnGtkRealize(); } void GtkRootResize(GtkWidget *widget, GdkRectangle *r, LView *This) { LWindow *w = This->GetWindow(); if (w) { if (r) { w->_RootAlloc = *r; #ifdef _DEBUG // printf("%s got root alloc: %s\n", w->GetClass(), w->_RootAlloc.GetStr()); #endif } else LgiTrace("%s:%i - no alloc rect param?\n", _FL); w->PourAll(); } } void LWindowUnrealize(GtkWidget *widget, LWindow *wnd) { // printf("%s:%i - LWindowUnrealize %s\n", _FL, wnd->GetClass()); } bool DndPointMap(LViewI *&v, LPoint &p, LDragDropTarget *&t, LWindow *Wnd, int x, int y) { LRect cli = Wnd->GetClient(); t = NULL; v = Wnd->WindowFromPoint(x - cli.x1, y - cli.y1, false); if (!v) { LgiTrace("%s:%i - @ %i,%i\n", _FL, x, y); return false; } v->WindowVirtualOffset(&p); p.x = x - p.x; p.y = y - p.y; for (LViewI *view = v; !t && view; view = view->GetParent()) t = view->DropTarget(); if (t) return true; LgiTrace("%s:%i - No target for %s\n", _FL, v->GetClass()); return false; } void LWindowDragBegin(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataDelete(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataGet(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return; for (auto &d: t->Data) { auto type = gdk_atom_name(gtk_selection_data_get_data_type(data)); if (d.Format.Equals(type)) { gint length = 0; auto ptr = gtk_selection_data_get_data_with_length(data, &length); if (ptr) { d.Data[0].SetBinary(length, (void*)ptr, false); } break; } } } int GetAcceptFmts(::LString::Array &Formats, GdkDragContext *context, LDragDropTarget *t, LPoint &p) { int KeyState = 0; LDragFormats Fmts(true); int Flags = DROPEFFECT_NONE; GList *targets = gdk_drag_context_list_targets(context); Gtk::GList *i = targets; while (i) { auto a = gdk_atom_name((GdkAtom)i->data); if (a) Fmts.Supports(a); i = i->next; } Fmts.SetSource(false); Flags = t->WillAccept(Fmts, p, KeyState); auto Sup = Fmts.GetSupported(); for (auto &s: Sup) Formats.New() = s; return Flags; } gboolean LWindowDragDataDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { // Map the point to a view... LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; t->Data.Length(0); // Request the data... LArray Data; LString::Array Formats; int KeyState = 0; int Flags = GetAcceptFmts(Formats, context, t, p); for (auto f: Formats) { t->Data.New().Format = f; gtk_drag_get_data(widget, context, gdk_atom_intern(f, true), time); } // Wait for the data to arrive... uint64_t Start = LCurrentTime(); while (LCurrentTime()-Start < 2000) { int HasData = 0; for (auto d: t->Data) if (d.Data.Length() > 0) HasData++; if (HasData >= Formats.Length()) break; } auto Result = t->OnDrop(t->Data, p, KeyState); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Result != DROPEFFECT_NONE; } void LWindowDragEnd(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); return false; } void LWindowDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; LString::Array Formats; int Flags = GetAcceptFmts(Formats, context, t, p); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Flags != DROPEFFECT_NONE; } bool LWindow::Attach(LViewI *p) { bool Status = false; ThreadCheck(); // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Create a window if needed.. if (!Wnd) Wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (Wnd) { SetTitleBar(d->ShowTitleBar); SetWillFocus(d->WillFocus); auto Widget = GTK_WIDGET(Wnd); LView *i = this; if (Pos.X() > 0 && Pos.Y() > 0) gtk_window_resize(Wnd, Pos.X(), Pos.Y()); auto Obj = G_OBJECT(Wnd); g_object_set_data(Obj, "LViewI", (LViewI*)this); d->DestroySig = g_signal_connect(Obj, "destroy", G_CALLBACK(GtkWindowDestroy), this); g_signal_connect(Obj, "delete_event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-in-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-out-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "window-state-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "property-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "configure-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "unmap-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "visibility-notify-event",G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "realize", G_CALLBACK(GtkWindowRealize), i); g_signal_connect(Obj, "unrealize", G_CALLBACK(LWindowUnrealize), i); g_signal_connect(Obj, "drag-begin", G_CALLBACK(LWindowDragBegin), i); g_signal_connect(Obj, "drag-data-delete", G_CALLBACK(LWindowDragDataDelete), i); g_signal_connect(Obj, "drag-data-get", G_CALLBACK(LWindowDragDataGet), i); g_signal_connect(Obj, "drag-data-received", G_CALLBACK(LWindowDragDataReceived), i); g_signal_connect(Obj, "drag-drop", G_CALLBACK(LWindowDragDataDrop), i); g_signal_connect(Obj, "drag-end", G_CALLBACK(LWindowDragEnd), i); g_signal_connect(Obj, "drag-failed", G_CALLBACK(LWindowDragFailed), i); g_signal_connect(Obj, "drag-leave", G_CALLBACK(LWindowDragLeave), i); g_signal_connect(Obj, "drag-motion", G_CALLBACK(LWindowDragMotion), i); #if 0 g_signal_connect(Obj, "button-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "button-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "motion-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "scroll-event", G_CALLBACK(GtkViewCallback), i); #endif gtk_widget_add_events( Widget, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK); gtk_window_set_title(Wnd, LBase::Name()); d->AttachState = LAttaching; // g_action_map_add_action_entries (G_ACTION_MAP(Wnd), app_entries, G_N_ELEMENTS (app_entries), Wnd); // This call sets up the GdkWindow handle _Root = lgi_widget_new(this, true); gtk_widget_realize(Widget); gtk_window_move(Wnd, Pos.x1, Pos.y1); if (_Root) { g_signal_connect(_Root, "size-allocate", G_CALLBACK(GtkRootResize), i); auto container = GTK_CONTAINER(Wnd); LAssert(container != NULL); if (!gtk_widget_get_parent(_Root)) gtk_container_add(container, _Root); gtk_widget_show(_Root); } // Do a rough layout of child windows PourAll(); // Add icon if (d->Icon) { SetIcon(d->Icon); d->Icon.Empty(); } auto p = GetParent(); if (p) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } Status = true; } return Status; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return LView::OnRequestClose(OsShuttingDown); } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { if (m.Down() && !m.IsMove()) { bool InPopup = false; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) { InPopup = true; break; } } if (!InPopup && LPopup::CurrentPopups.Length()) { for (int i=0; iVisible()) p->Visible(false); } } } for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { if (!d->Hooks[i].Target->OnViewMouse(v, m)) { return false; } } } return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { bool Status = false; LViewI *Ctrl = 0; #if DEBUG_HANDLEVIEWKEY bool Debug = 1; // k.vkey == LK_RETURN; char SafePrint = k.c16 < ' ' ? ' ' : k.c16; // if (Debug) { LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s (d->Focus=%s/%p)\n", v->GetClass(), v, k.c16, k.IsChar, (char*)(k.Down()?" Down":" Up"), (char*)(k.Shift()?" Shift":""), (char*)(k.Alt()?" Alt":""), (char*)(k.Ctrl()?" Ctrl":""), d->Focus?d->Focus->GetClass():0, d->Focus); } #endif // Any window in a popup always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tSending key to popup\n"); #endif return v->OnKey(k); } } // Give key to popups if (LAppInst && LAppInst->GetMouseHook() && LAppInst->GetMouseHook()->OnViewKey(v, k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMouseHook got key\n"); #endif goto AllDone; } // Allow any hooks to see the key... for (int i=0; iHooks.Length(); i++) { #if DEBUG_HANDLEVIEWKEY // if (Debug) LgiTrace("\tHook[%i]\n", i); #endif if (d->Hooks[i].Flags & LKeyEvents) { LView *Target = d->Hooks[i].Target; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i].Target=%p %s\n", i, Target, Target->GetClass()); #endif if (Target->OnViewKey(v, k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i] ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", i, SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } } } // Give the key to the window... if (v->OnKey(k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tView ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif Status = true; goto AllDone; } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\t%s didn't eat '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", v->GetClass(), SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif // Window didn't want the key... switch (k.vkey) { case LK_RETURN: #ifdef LK_KEYPADENTER case LK_KEYPADENTER: #endif { Ctrl = _Default; break; } case LK_ESCAPE: { Ctrl = FindControl(IDCANCEL); break; } } // printf("Ctrl=%p\n", Ctrl); if (Ctrl) { if (Ctrl->Enabled()) { if (Ctrl->OnKey(k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tDefault Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } // else printf("OnKey()=false\n"); } // else printf("Ctrl=disabled\n"); } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\tNo default ctrl to handle key.\n"); #endif if (Menu) { Status = Menu->OnKey(v, k); if (Status) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMenu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif } } // Tab through controls if (k.vkey == LK_TAB && k.Down() && !k.IsChar) { LViewI *Wnd = GetNextTabStop(v, k.Shift()); #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tTab moving focus shift=%i Wnd=%p\n", k.Shift(), Wnd); #endif if (Wnd) Wnd->Focus(true); } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } AllDone: if (d) d->LastKey = k; return Status; } void LWindow::Raise() { if (Wnd) gtk_window_present(Wnd); } LWindowZoom LWindow::GetZoom() { switch (d->State) { case GDK_WINDOW_STATE_ICONIFIED: return LZoomMin; case GDK_WINDOW_STATE_MAXIMIZED: return LZoomMax; default: break; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (!Wnd) { // LgiTrace("%s:%i - No window.\n", _FL); return; } ThreadCheck(); switch (i) { case LZoomMin: { gtk_window_iconify(Wnd); break; } case LZoomNormal: { gtk_window_deiconify(Wnd); gtk_window_unmaximize(Wnd); break; } case LZoomMax: { gtk_window_maximize(Wnd); break; } default: { LgiTrace("%s:%i - Error: unsupported zoom.\n", _FL); break; } } } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { if (v && v->GetWindow() == this) { if (_Default != v) { LViewI *Old = _Default; _Default = v; if (Old) Old->Invalidate(); if (_Default) _Default->Invalidate(); } } else { _Default = 0; } } bool LWindow::SetTitleBar(bool ShowTitleBar) { d->ShowTitleBar = ShowTitleBar; if (Wnd) { ThreadCheck(); gtk_window_set_decorated(Wnd, ShowTitleBar); } return true; } bool LWindow::Name(const char *n) { if (Wnd) { ThreadCheck(); gtk_window_set_title(Wnd, n); } return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } struct CallbackParams { LRect Menu; int Depth; CallbackParams() { Menu.ZOff(-1, -1); Depth = 0; } }; void ClientCallback(GtkWidget *w, CallbackParams *p) { const char *Name = gtk_widget_get_name(w); if (Name && !_stricmp(Name, "GtkMenuBar")) { GtkAllocation alloc = {0}; gtk_widget_get_allocation(w, &alloc); p->Menu.ZOff(alloc.width-1, alloc.height-1); // LgiTrace("GtkMenuBar = %s\n", p->Menu.GetStr()); } if (!p->Menu.Valid()) { p->Depth++; if (GTK_IS_CONTAINER(w)) gtk_container_forall(GTK_CONTAINER(w), (GtkCallback)ClientCallback, p); p->Depth--; } } -LPoint LWindow::GetDpi() +LPoint LWindow::GetDpi() const { return LScreenDpi(); } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); return LPointF((double)Dpi.x/96.0, (double)Dpi.y/96.0); } LRect &LWindow::GetClient(bool ClientSpace) { static LRect r; if (_RootAlloc.Valid()) { if (ClientSpace) r = _RootAlloc.ZeroTranslate(); else r = _RootAlloc; } else { // Use something vaguely plausible before we're mapped r = LView::GetClient(ClientSpace); } #if 0 if (Wnd) { CallbackParams p; gtk_container_forall(GTK_CONTAINER(Wnd), (GtkCallback)ClientCallback, &p); if (p.Menu.Valid()) { if (ClientSpace) r.y2 -= p.Menu.Y(); else r.y1 += p.Menu.Y(); } } #endif return r; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; if (Load) { ::LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; // printf("SerializeState load %s\n", v.Str()); LToken t(v.Str(), ";"); for (int i=0; iName()); v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, "%s>%s", Buf, v->GetClass()); } return LAutoString(NewStr(s)); } #endif void LWindow::SetFocus(LViewI *ctrl, FocusType type) { #if DEBUG_SETFOCUS const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } #endif switch (type) { case GainFocus: { if (d->Focus == ctrl) { #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LgiTrace("SetFocus(%s, %s) already has focus.\n", _ctrl.Get(), TypeName); #endif return; } if (d->Focus) { LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus LView: %s\n", _foc.Get()); #endif gv->_Focus(false); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus view: %s (active=%i)\n", _foc.Get(), IsActive()); #endif d->Focus->OnFocus(false); d->Focus->Invalidate(); } } d->Focus = ctrl; if (d->Focus) { #if DEBUG_SETFOCUS static int Count = 0; #endif LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing LView\n", _set.Get(), TypeName, Count++); #endif gv->_Focus(true); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing nonGView (active=%i)\n", _set.Get(), TypeName, Count++, IsActive()); #endif d->Focus->OnFocus(true); d->Focus->Invalidate(); } } break; } case LoseFocus: { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LAutoString _Focus = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) d->Focus=%s\n", _Ctrl.Get(), TypeName, _Focus.Get()); #endif if (ctrl == d->Focus) { d->Focus = NULL; } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) on delete\n", _Ctrl.Get(), TypeName); #endif d->Focus = NULL; } break; } } } bool LWindow::SetWillFocus(bool f) { d->WillFocus = f; if (Wnd) { if (f) { } else { gtk_window_set_type_hint(Wnd, GDK_WINDOW_TYPE_HINT_POPUP_MENU); // printf("%s:%i - gtk_window_set_type_hint=GDK_WINDOW_TYPE_HINT_POPUP_MENU.\n", _FL); } } return true; } void LWindow::SetDragHandlers(bool On) { } void LWindow::SetParent(LViewI *p) { LView::SetParent(p); if (p && Wnd) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else if (!GTK_IS_WINDOW(Wnd)) LgiTrace("%s:%i - SetParent() - GTK_IS_WINDOW failed.\n", _FL); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } } bool LWindow::IsAttached() { return d->AttachState == LAttaching || d->AttachState == LAttached; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m.x, m.y); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } void LWindow::Quit(bool DontDelete) { ThreadCheck(); if (Wnd) { d->AttachState = LDetaching; auto wnd = Wnd; Wnd = NULL; gtk_widget_destroy(GTK_WIDGET(wnd)); } } void LWindow::SetAlwaysOnTop(bool b) { } diff --git a/test/LiteHtml/src/main.cpp b/test/LiteHtml/src/main.cpp --- a/test/LiteHtml/src/main.cpp +++ b/test/LiteHtml/src/main.cpp @@ -1,357 +1,376 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" #undef min #undef max #include "litehtml/html.h" const char *AppName = "LgiLiteHtml"; enum Ctrls { IDC_BOX = 100, IDC_LOCATION, IDC_BROWSER, }; #define NOT_IMPL LAssert(!"not impl"); class HtmlView : public LLayout, public litehtml::document_container { litehtml::document::ptr doc; protected: LWindow *wnd = NULL; LRect client; LHashTbl, LFont*> fontMap; LColour Convert(const litehtml::web_color &c) { return LColour(c.red, c.green, c.blue, c.alpha); } litehtml::uint_ptr create_font( const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics *fm) { litehtml::uint_ptr hnd; do { hnd = LRand(10000); } while (fontMap.Find(hnd) != NULL); LFont *fnt = new LFont; bool status = fnt->Create(faceName, LCss::Len(LCss::LenPt, size) ); if (!status) LgiTrace("%s:%i - failed to create font(%s,%i)\n", _FL, faceName, size); fontMap.Add(hnd, fnt); + if (fm) + { + LDisplayString ds(fnt, "x"); + fm->height = fnt->GetHeight(); + fm->ascent = fnt->Ascent(); + fm->descent = fnt->Descent(); + fm->x_height = ds.Y(); + fm->draw_spaces = false; + } + return hnd; } void delete_font(litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (fnt) { delete fnt; fontMap.Delete(hFont); } } int text_width(const char* text, litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (!fnt) return 0; LDisplayString ds(fnt, text); return ds.X(); } void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(hFont); if (!pDC || !Fnt) return; LDisplayString ds(Fnt, text); Fnt->Fore(Convert(color)); Fnt->Transparent(true); ds.Draw(pDC, pos.x, pos.y); } int pt_to_px(int pt) const { auto dpi = wnd->GetDpi(); int px = pt * dpi.x / 72; return px; } int get_default_font_size() const { return 12; // LSysFont->PointSize(); } const char *get_default_font_name() const { return LSysFont->Face(); } void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(marker.font); if (!pDC) return; pDC->Colour(Convert(marker.color)); switch (marker.marker_type) { case litehtml::list_style_type_disc: { - LDisplayString ds(Fnt ? Fnt : LSysFont, "o"); - ds.Draw(pDC, marker.pos.x, marker.pos.y); + pDC->FilledCircle(marker.pos.x, marker.pos.y, 3); break; } default: { NOT_IMPL; break; } } } void load_image(const char *src, const char *baseurl, bool redraw_on_ready) { NOT_IMPL } void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) { NOT_IMPL } void draw_background(litehtml::uint_ptr hdc, const std::vector &bg) { NOT_IMPL } void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) { auto pDC = (LSurface*)hdc; NOT_IMPL } void set_caption(const char* caption) { wnd->Name(caption); } void set_base_url(const char* base_url) { NOT_IMPL } void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) { NOT_IMPL } void on_anchor_click(const char* url, const litehtml::element::ptr& el) { NOT_IMPL } void set_cursor(const char* cursor) { NOT_IMPL } void transform_text(litehtml::string& text, litehtml::text_transform tt) { NOT_IMPL } void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) { NOT_IMPL } void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) { NOT_IMPL } void del_clip() { NOT_IMPL } void get_client_rect(litehtml::position &out) const { out = litehtml::position(client.x1, client.y1, client.X(), client.Y()); } litehtml::element::ptr create_element( const char* tag_name, const litehtml::string_map& attributes, const std::shared_ptr& doc) { return NULL; } void get_media_features(litehtml::media_features& media) const { // NOT_IMPL } void get_language(litehtml::string& language, litehtml::string& culture) const { NOT_IMPL } /* litehtml::string resolve_color(const litehtml::string &color) const { NOT_IMPL return litehtml::string(); } */ public: HtmlView(int id) { SetId(id); } ~HtmlView() { // Do this before releasing other owned objects, like the fontMap. doc.reset(); } void OnAttach() { wnd = GetWindow(); } bool SetUrl(LString url) { if (LFileExists(url)) { auto html_text = LReadFile(url); if (html_text) { doc = litehtml::document::createFromString(html_text, this); Invalidate(); return doc != NULL; } } return false; } void OnPaint(LSurface *pDC) { #ifdef WINDOWS LDoubleBuffer buf(pDC); #endif client = GetClient(); if (doc) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); auto width = pDC->X(); int r = doc->render(width); if (r) { auto width = doc->content_width(); auto height = doc->content_height(); if (height > Y()) { SetScrollBars(false, true); if (VScroll) { VScroll->SetRange(height); VScroll->SetPage(Y()); } } litehtml::position clip(0, 0, pDC->X(), pDC->Y()); doc->draw((litehtml::uint_ptr)pDC, 0, VScroll?-VScroll->Value():0, &clip); } } else { LLayout::OnPaint(pDC); } } int OnNotify(LViewI *c, LNotification n) { + // printf("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); if (c->GetId() == IDC_VSCROLL && n.Type == LNotifyValueChanged) { + // printf("Inval\n"); Invalidate(); } return LLayout::OnNotify(c, n); } }; class App : public LWindow { LBox *box = NULL; LEdit *location = NULL; HtmlView *browser = NULL; public: App() { LRect r(200, 200, 1400, 1000); SetPos(r); Name(AppName); MoveToCenter(); SetQuitOnClose(true); if (Attach(0)) { AddView(box = new LBox(IDC_BOX, true)); box->AddView(location = new LEdit(IDC_LOCATION, 0, 0, 100, 20)); box->AddView(browser = new HtmlView(IDC_BROWSER)); box->Value(LSysFont->GetHeight() + 8); AttachChildren(); location->Focus(true); Visible(true); } } + void SetUrl(LString s) + { + if (location) + location->Name(s); + if (browser) + browser->SetUrl(s); + } + void OnReceiveFiles(LArray &Files) { - browser->SetUrl(Files[0]); + SetUrl(Files[0]); } }; int LgiMain(OsAppArguments &AppArgs) { LApp app(AppArgs, "application/x-lgi-litehtml"); if (app.IsOk()) { app.AppWnd = new App; app.Run(); } return 0; }